সরাসরি পর্বে যান
পর্ব ২৯ · ৭৯-এর মধ্যে · মডিউল ৫

কোন chart কখন — distribution, তুলনা, সম্পর্ক আর সময়

Choosing the Right Chart — Distribution, Comparison, Relationship, Composition and Time

Taught in Bangla, this lesson picks charts from questions: data types, the Cleveland and McGill perception ranking, histogram bin width, reading box plots and ECDFs, bar chart rules, grouped versus stacked bars, why pies struggle, fixing overplotting with alpha, jitter, hexbin and log scales, line charts and small multiples, and when a table beats a chart.

এই পর্বে যা শিখবেন

  • একটি প্রশ্নকে ডেটার ধরন (nominal, ordinal, discrete, continuous, সময়) আর কাজের পরিবারে ভেঙে chart বাছাই করা
  • কেন position বেশি নির্ভুল আর angle, area, রং কম — Cleveland ও McGill-এর perception ক্রম, সীমাসহ
  • Histogram-এর bin width, box plot-এর প্রতিটি অংশ, ECDF আর ছোট ডেটায় strip plot পড়া ও আঁকা
  • Bar chart-এর নিয়ম (শূন্য থেকে, সাজানো, লম্বা নামে আড়াআড়ি), dot plot, grouped বনাম stacked bar, pie-র সমস্যা
  • Scatter-এর overplotting সারানো, log scale, line chart, চলমান গড় আর small multiples; কখন chart নয়, টেবিল

আগের পর্বে, পর্ব ২৮ · matplotlib-এর শুরু-তে, chart আঁকার যন্ত্র হাতে নিয়েছেন: figure, axes, plot, bar, scatter, hist. এই পর্বে দ্বিতীয় দক্ষতা: একটি প্রশ্নের উত্তর কোন chart সবচেয়ে কম ভুলে দেখায়, তা বাছাই করা। উদাহরণ বাজারঘর (এই কোর্সের কাল্পনিক অনলাইন দোকান) থেকে: delivery-র দিন, পেমেন্ট পদ্ধতি, অর্ডারের দাম আর পাঁচ জেলার মাসিক বিক্রি। কোড চলবে পাতাতেই, Pyodide দিয়ে, matplotlib 3.10.8-এ; প্রথমবার package নামতে কয়েক সেকেন্ড লাগে। ছবির লেখা ইংরেজিতে, কারণ ব্রাউজারের matplotlib-এ বাংলা অক্ষর ঠিকমতো জোড়া লাগে না (পর্ব ২৮ দেখুন)। ডেস্কটপের run চলেছে matplotlib 3.11.2, NumPy 2.5.4 আর Python 3.14-এ।

১ · আগে প্রশ্ন, পরে chart — প্রশ্নের পাঁচ পরিবার

ডাক্তারের কাছে গিয়ে কেউ বলেন না “আমাকে একটা পরীক্ষা দিন”। ডাক্তার আগে জানতে চান সমস্যা কী: জ্বর হলে রক্ত পরীক্ষা, হাড়ে ব্যথা হলে X-ray, বুকে চাপ হলে ECG. পরীক্ষা বাছাই হয় প্রশ্ন থেকে। Data visualization-এ chart হলো সেই পরীক্ষা। “বিক্রির একটা chart বানাও” একটি অসম্পূর্ণ অনুরোধ; “ঈদের মাসে কোন জেলায় বিক্রি সবচেয়ে বেশি বেড়েছে” একটি প্রশ্ন, আর এর উত্তরের জন্য chart প্রায় নিজে থেকেই ঠিক হয়ে যায়।

ডেটা নিয়ে যত প্রশ্ন করা হয়, তার প্রায় সবই পাঁচটি পরিবারের কোনো একটিতে পড়ে। প্রতিটি পরিবারের নিজস্ব chart আছে, আর এই পর্বের অংশ ৪ থেকে ৮ এই ক্রমেই সাজানো।

📊 Distribution — কেমন ছড়ানো?

একটি সংখ্যার কলামের মান কোথায় ঘন, কোথায় পাতলা, কত দূর পর্যন্ত যায়। “বেশিরভাগ অর্ডার কত দিনে পৌঁছায়?”

histogram, box plot, ECDF, strip

📏 তুলনা — কোনটা বড়?

কয়েকটি দলের একটি করে সংখ্যা পাশাপাশি। “কোন জেলায় গড় rating সবচেয়ে কম?”

bar, dot plot, grouped bar

🔗 সম্পর্ক — একটা বাড়লে অন্যটা?

দুটি সংখ্যার কলাম একসঙ্গে। “দামি অর্ডার কি দেরিতে পৌঁছায়?”

scatter, hexbin, 2-D histogram

🧩 অংশ — সমগ্রের কত ভাগ?

একটি মোটকে ভাগে ভাগ করা। “অর্ডারের কত শতাংশ cash on delivery?”

sorted bar, stacked bar, 100% bar

📈 সময় — কীভাবে বদলাচ্ছে?

একটি মান সময়ের ক্রমে। “গত দুই বছরে রাজশাহীর বিক্রি কতটা বাড়ল?”

line chart, small multiples

পরিবার চেনার পর দ্বিতীয় প্রশ্ন: যে কলামগুলো লাগবে, সেগুলো কী ধরনের ডেটা (অংশ ২)। তৃতীয় প্রশ্ন: সেই ধরনের ডেটা চোখ কোন রূপে সবচেয়ে ভালো পড়ে (অংশ ৩)। নিচের ধাপে বাজারঘরের একটি সত্যিকারের ধরনের প্রশ্ন এই তিন ছাঁকনির মধ্য দিয়ে যাচ্ছে।

  1. প্রশ্নঢাকার বাইরে দেরি?এক বাক্যে
  2. ডেটার ধরনdays, regiondiscrete + nominal
  3. কাজের পরিবারdistribution × ২দুই দলের তুলনা
  4. Chart২টি ECDF / histogramএকই x-axis
  1. প্রশ্ন এক বাক্যে। “delivery-র chart” নয়, “ঢাকার বাইরের অর্ডার কি বেশি দেরিতে পৌঁছায়?”. প্রশ্নটি লিখে ফেললেই বোঝা যায় কোন দুটি কলাম লাগবে আর উত্তর কেমন হবে।
  2. ডেটার ধরন চেনা। delivery_days পূর্ণসংখ্যা দিন, মানে discrete সংখ্যা; region দুটি নাম, যার কোনো ক্রম নেই, মানে nominal. সংখ্যা axis-এ বসবে, নাম দিয়ে দল ভাগ হবে।
  3. কাজের পরিবার। একটি সংখ্যার কলাম দুই দলে ভাগ করে তুলনা: এটি আসলে দুটি distribution-এর তুলনা। শুধু গড় দেখালে “গড়ে ৩ দিন বেশি” বলা যায়, কিন্তু “ঢাকার বাইরে কেউ কি ২ দিনে পায়?” বলা যায় না।
  4. Chart বাছাই। একই axes-এ দুটি ECDF সবচেয়ে কম জিনিস লুকায়; দুটি histogram-ও চলে, যদি bin আর x-axis একই হয় আর একটির নিচে অন্যটি থাকে। অনুশীলন ৪-এ এই chart-টিই আঁকবেন।
  5. বাদ দেওয়া আর শেষ ছোঁয়া। গড়ের pie অর্থহীন (দুটি গড় কোনো সমগ্রের অংশ নয়), আর শুধু গড়ের bar ছড়ানো লুকায়। শিরোনামে উত্তরটিই লিখুন; “Delivery days by region” নয়, “Outside Dhaka takes about 3 days longer”.
মূল ধারণা

Chart বাছাইয়ের ক্রম: প্রশ্ন → কলাম আর তাদের ধরন → কাজের পরিবার → encoding → chart. Chart-এর নাম দিয়ে শুরু করলে (“একটা pie বানাই”) ডেটাকে chart-এর মাপে কাটতে হয়; প্রশ্ন দিয়ে শুরু করলে chart ডেটার মাপে আসে।

২ · ডেটার ধরন — nominal, ordinal, discrete, continuous আর সময়

ক্রিকেটের স্কোরকার্ডে চার রকম তথ্য পাশাপাশি থাকে: ব্যাটসম্যানের নাম, ব্যাটিং অর্ডারের ক্রম (১, ২, ৩…), কয়টি চার মেরেছেন, আর strike rate. নামের গড় হয় না; ক্রমের গড় অর্থহীন; চারের সংখ্যা পূর্ণসংখ্যা; strike rate যেকোনো দশমিক হতে পারে। ১৯৪৬ সালে মনোবিজ্ঞানী S. S. Stevens মাপের এমন স্তরগুলোকে নাম দেন nominal, ordinal, interval আর ratio; আজকের ডেটা বিশ্লেষণে সাধারণত আরও সহজ একটি ভাগ চলে, যা নিচের টেবিলে বাজারঘরের কলাম দিয়ে দেখানো হলো।

ধরনঅর্থবাজারঘরের কলামযে encoding মানায়
Nominal (categorical)শুধু নাম, কোনো ক্রম নেইdistrict, payment, categoryaxis-এ আলাদা অবস্থান, আলাদা রং (hue), আকৃতি
Ordinalক্রম আছে, কিন্তু ধাপ সমান নয়rating 1–5, “ছোট/মাঝারি/বড়” প্যাকেটক্রম মেনে অবস্থান, একই রঙের হালকা থেকে গাঢ়
Discrete numericগোনা যায় এমন পূর্ণসংখ্যাqty, delivery_daysঅবস্থান, দৈর্ঘ্য; histogram-এ bin প্রতি একটি মান
Continuous numericমাপা মান, মাঝে যেকোনো মান সম্ভবprice_paisa (প্রায় continuous), ওজনঅবস্থান, দৈর্ঘ্য; প্রয়োজনে log scale
সময় (temporal)ক্রম আর সমান ব্যবধান, সঙ্গে ক্যালেন্ডারdatex-axis-এ বাঁ থেকে ডানে, line chart

দুটি সূক্ষ্ম জায়গা। প্রথমত, rating সংখ্যা দেখতে হলেও ordinal: ৪ আর ৫-এর ফারাক ১ আর ২-এর ফারাকের সমান কি না, কেউ জানে না, আর “৩.৮ গড়” খুব ভিন্ন distribution থেকে আসতে পারে; তাই ১ থেকে ৫ প্রতিটির ভাগ দেখানো প্রায়ই বেশি সৎ। pandas-এ ক্রমসহ categorical dtype (ordered=True) দিলে chart-এ ক্রম ঠিক থাকে। দ্বিতীয়ত, delivery_days আসলে continuous সময়, কিন্তু পূর্ণ দিনে গোল করা; তাই খুব সরু bin-এ ফাঁকা দাঁত দেখা যায় (অংশ ৪)।

একটি মান বা নামকে ছবির কোনো চাক্ষুষ গুণে (অবস্থান, দৈর্ঘ্য, কোণ, ক্ষেত্রফল, রং, আকৃতি) বসানোকে বলে visual encoding. ফরাসি মানচিত্রবিদ Jacques Bertin ১৯৬৭ সালের “Sémiologie graphique” বইয়ে এই গুণগুলোর তালিকা দেন। প্রথম নিয়ম: যে ক্রম ডেটায় নেই, তা ছবিতে আনবেন না (nominal জেলাকে হালকা থেকে গাঢ় রঙে আঁকলে মনে হয় একটি জেলা “বেশি”), আর যে ক্রম আছে, তা হারাবেন না (rating-কে এলোমেলো রঙে আঁকলে ক্রম হারায়)।

৩ · চোখ কোনটা ভালো পড়ে — position, length, angle, area আর রং

বাজারের দোকানে দুটি লাউ পাশাপাশি রাখলে কোনটা লম্বা, এক নজরে বলা যায়। দুটি আলাদা ঝুড়িতে রাখলে কঠিন। আর দুটি কুমড়োর কোনটা দেড় গুণ বড়, তা চোখে আন্দাজ করা আরও কঠিন: আয়তন বা ক্ষেত্রফলের তুলনায় চোখ ভুল করে। chart পড়ার সময়ও চোখ এমন আচরণ করে। ১৯৮৪ সালে Bell Labs-এর William S. Cleveland আর Robert McGill একটি প্রভাবশালী প্রবন্ধে (“Graphical Perception”, Journal of the American Statistical Association) মানুষকে chart দেখিয়ে দুটি মানের অনুপাত আন্দাজ করতে বলেন আর ভুল মাপেন। ফল আর তত্ত্ব মিলিয়ে যে ক্রম দাঁড়ায়, তাকে বলা হয় graphical perception-এর ক্রম।

চোখ কোন encoding কত নির্ভুল পড়ে — Cleveland ও McGill-এর ক্রম

Elementary perceptual tasks, ranked from most to least accurate for judging quantities (after Cleveland & McGill, 1984)

Graphical perception-এর ক্রম: অবস্থান থেকে রঙের গাঢ়তা ↑ বেশি নির্ভুল ১ · একই স্কেলে অবস্থান ২ · আলাদা স্কেলে অবস্থান ৩ · দৈর্ঘ্য, দিক, কোণ ৪ · ক্ষেত্রফল ৫ · আয়তন, বক্রতা ৬ · রঙের গাঢ়তা dot plot, scatter, bar-এর মাথা small multiples-এর panel bar (দৈর্ঘ্য), pie (কোণ) bubble, treemap 3D bar, 3D pie heatmap, রঙিন মানচিত্র
ছবি ২৯.১ — ওপরের দিকের encoding দিয়ে দুটি মানের অনুপাত আন্দাজে মানুষের ভুল কম হয়েছে, নিচের দিকে বেশি। ধাপ ১-এ দুটি বিন্দু একই y-axis মেনে বসে, তাই উচ্চতা সরাসরি তুলনা হয়; ধাপ ২-এ axis আলাদা, তুলনায় এক ধাপ বেশি পরিশ্রম। ধাপ ৩-এ বাঁ দিকের দুটি bar একই রেখা থেকে শুরু হয়নি, ডানে একটি pie-র টুকরো (কোণ)। ধাপ ৪-এ বড় বৃত্তটির ব্যাসার্ধ ছোটটির প্রায় দেড় গুণ, কিন্তু ক্ষেত্রফল দুই গুণের বেশি; চোখ সাধারণত এই পার্থক্য কম আন্দাজ করে। ক্রমটি “অনুপাত আন্দাজ” কাজের জন্য; অন্য কাজে (যেমন হঠাৎ চোখে পড়া) রং খুব কার্যকর।

এই ক্রমকে নিয়ম নয়, ঝোঁক হিসেবে পড়ুন; এর ইতিহাস আর সীমা ভাবনার প্রশ্ন ১-এ। ব্যবহারিক শিক্ষা তিনটি:

  • সবচেয়ে গুরুত্বপূর্ণ তুলনাটি অবস্থানে বসান। যে দুটি মান পাঠককে মেলাতে হবে, সেগুলো একই axis-এ, পাশাপাশি।
  • রং দিন দল চেনাতে, মাপ বোঝাতে নয়। nominal দলের জন্য আলাদা hue (matplotlib-এর default tab10 চলে), ক্রমের জন্য এক রঙের হালকা-গাঢ় (viridis, cividis). শুধু লাল-সবুজের পার্থক্যে অর্থ রাখবেন না, অনেকে এ দুটি রং আলাদা করতে পারেন না; বিস্তারিত পর্ব ৩২-এ।
  • ক্ষেত্রফল আর আয়তন এড়িয়ে চলুন যেখানে নির্ভুল তুলনা দরকার। 3D bar বা 3D pie-তে পেছনের অংশ ছোট দেখায়, শুধু দৃষ্টিকোণের কারণে।

৪ · Distribution — histogram, bin width, box plot, ECDF আর strip

পরীক্ষার খাতা দেখা শেষে একজন শিক্ষক জানতে চান: বেশিরভাগ ছাত্র কত পেয়েছে, কতজন ফেল, কেউ কি অস্বাভাবিক বেশি পেয়েছে? গড় নম্বর একটি সংখ্যা, এই প্রশ্নগুলোর উত্তর দেয় না। দরকার পুরো distribution: কোন মান কতবার এসেছে। বাজারঘরের ক্ষেত্রে প্রশ্ন “একটি অর্ডার পৌঁছাতে কত দিন লাগে?”. পাতার কোড ৬০৩টি অর্ডার বানায়: ৩৬০টি ঢাকার ভেতরে (বেশিরভাগ ১–২ দিন), ২৪০টি ঢাকার বাইরে (বেশিরভাগ ৪–৬ দিন), আর ৩টি হারিয়ে গিয়ে অনেক দেরিতে পৌঁছানো পার্সেল।

Histogram আর bin width

Histogram সংখ্যার axis-কে সমান মাপের খোপে (bin) ভাগ করে, প্রতিটি খোপে কয়টি মান পড়েছে গোনে, আর সেই সংখ্যাকে bar-এর উচ্চতা বানায়। পর্ব ২৪-এর binning আর গোনা, শুধু ছবিতে। সব সিদ্ধান্তের মধ্যে একটির প্রভাব সবচেয়ে বেশি: bin width, মানে প্রতিটি খোপ কত চওড়া। খুব সরু হলে প্রতিটি খোপে অল্প মান, ছবি এলোমেলো দাঁতের মতো; খুব চওড়া হলে সব মিশে একটি ঢিবি। নিচের কোড একই ডেটা তিনটি bin width-এ আঁকে, তারপর একই ডেটার box plot, ECDF আর ৩০টি অর্ডারের strip plot.

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
dhaka = rng.poisson(1.0, 360) + 1        # ঢাকার ভেতরে: বেশিরভাগ ১–২ দিন
outside = rng.poisson(1.5, 240) + 4      # ঢাকার বাইরে: বেশিরভাগ ৪–৬ দিন
days = np.concatenate([dhaka, outside, [13, 15, 21]])  # ৩টি দেরির পার্সেল
print("orders:", days.size, "| mean:", round(days.mean(), 2),
      "| median:", np.median(days))
q1, q3 = np.percentile(days, [25, 75])
print("Q1:", q1, "Q3:", q3, "IQR:", q3 - q1, "| fence:", q3 + 1.5 * (q3 - q1))

fig, axes = plt.subplots(1, 3, figsize=(7.5, 2.6), dpi=100)
for ax, w in zip(axes, [0.25, 1, 4]):
    edges = np.arange(0.5, 22.5 + w, w)  # সীমা আধা দিনে, পূর্ণসংখ্যায় নয়
    counts, _ = np.histogram(days, edges)
    print(f"width {w}: {len(edges) - 1} bins, first {counts[:6].tolist()}")
    ax.hist(days, bins=edges, color="#2563eb", edgecolor="white", lw=0.5)
    ax.set(title=f"bin width = {w} day", xlabel="Delivery days")
axes[0].set_ylabel("Orders")
fig.tight_layout()

fig2, (a1, a2, a3) = plt.subplots(1, 3, figsize=(7.5, 2.6), dpi=100)
a1.boxplot(days, orientation="horizontal", widths=0.5)
a1.set(title="Box plot", xlabel="Delivery days", yticks=[])
a2.ecdf(days, color="#2563eb")
a2.axhline(0.5, ls=":", color="gray")
a2.set(title="ECDF", xlabel="Delivery days", ylabel="Share of orders")
few = rng.choice(days, 30, replace=False)
a3.scatter(few, rng.uniform(-0.2, 0.2, 30), s=18, alpha=0.6)
a3.set(title="Strip + jitter (30)", xlabel="Delivery days", yticks=[], ylim=(-1, 1))
fig2.tight_layout()
print("outliers (> fence):", sorted(days[days > q3 + 1.5 * (q3 - q1)].tolist()))
plt.show()

আসল আউটপুট (ব্রাউজারের Pyodide, NumPy 2.4.6, matplotlib 3.10.8; দুটি ছবি কোডের নিচে আঁকা হয়):

orders: 603 | mean: 3.47 | median: 3.0
Q1: 2.0 Q3: 5.0 IQR: 3.0 | fence: 9.5
width 0.25: 88 bins, first [0, 0, 132, 0, 0, 0]
width 1: 22 bins, first [132, 131, 63, 90, 75, 61]
width 4: 6 bins, first [416, 180, 4, 2, 0, 1]
outliers (> fence): [10, 11, 13, 15, 21]

গড় ৩.৪৭ দিন, median ৩ দিন; কিন্তু ছবিতে দেখবেন ৩ দিনে অর্ডার আশপাশের দিনগুলোর চেয়ে কম। ১ দিনের bin (মাঝের ছবি) আসল গল্প বলে: প্রথম ছয়টি খোপ 132, 131, 63, 90, 75, 61 — ১–২ দিনে একটি চূড়া (ঢাকা), ৩ দিনে খাদ, ৪–৫ দিনে দ্বিতীয় চূড়া (ঢাকার বাইরে)। এমন দুই চূড়ার আকৃতিকে বলে bimodal distribution, আর এটি প্রায়ই সংকেত দেয় যে ডেটায় দুটি আলাদা দল মিশে আছে। ৪ দিনের bin (ডানে) প্রথম খোপে 416 আর দ্বিতীয়তে 180 দেখায়: খাদটি খোপের ভেতরে ঢুকে গেছে, তাই মনে হয় একটি সাধারণ ঢাল। ০.২৫ দিনের bin (বাঁয়ে) প্রথম ছয়টির মধ্যে পাঁচটি খোপ 0: ডেটা পূর্ণসংখ্যা, তাই ১.০ থেকে ১.২৫-এর খোপে সব ১ দিনের অর্ডার, আর ১.২৫ থেকে ২.০ পর্যন্ত খোপগুলো ফাঁকা। এই “চিরুনি” ডেটার নয়, bin-এর সৃষ্টি। কোডে খোপের সীমা আধা দিনে (০.৫, ১.৫, …) রাখা হয়েছে, যাতে প্রতিটি পূর্ণসংখ্যা খোপের মাঝখানে পড়ে, সীমায় নয়।

দ্বিতীয় ছবির box plot আর outlier-এর লাইন নিচের ধাপে অংশ ধরে পড়া হবে। এখানে শুধু খেয়াল করুন: box plot-এ দুই চূড়া একেবারেই দেখা যায় না, একটি সাধারণ বাক্স। ECDF-এ ৩ দিনের ধাপে লাফ ছোট (খাদ), আর ৫০% রেখা ৩ দিনে কাটে, মানে median. Strip plot-এ মাত্র ৩০টি অর্ডার, প্রতিটি নিজে একটি বিন্দু; ছোট ডেটায় এটিই সবচেয়ে সৎ ছবি।

matplotlib-এর hist ডেটা যেমনই হোক, নিজে থেকে ১০টি bin নেয়। NumPy-র bins="auto" দুটি নিয়ম মেলায়: Sturges (১৯২৬; bin সংখ্যা = ⌈log2 n⌉ + 1, এখানে ⌈৯.২৪⌉ + ১ = ১১) আর Freedman–Diaconis (১৯৮১; width = 2 × IQR × n−1/3 = 2 × 3 × 603−1/3 ≈ ০.৭১ দিন), আর সরু width-টি নেয়। আমাদের ডেস্কটপ run-এ (NumPy 2.5.4) np.histogram_bin_edges(days, "auto") দিয়েছে ২০টি bin, width ১.০: integer ডেটায় NumPy-র source code width-কে ১-এর নিচে নামতে দেয় না। নিয়মগুলো শুরু মাত্র; দুই-তিনটি width চেষ্টা করুন, আর যে আকৃতি সবগুলোতে টিকে থাকে, শুধু সেটিকে বিশ্বাস করুন।

Box plot পড়া, অংশ ধরে ধরে

John Tukey তাঁর ১৯৭৭ সালের “Exploratory Data Analysis” বইয়ে box plot (box-and-whisker plot) জনপ্রিয় করেন। এটি পাঁচটি সংখ্যার ছবি: সবচেয়ে ছোট, প্রথম চতুর্থাংশ, median, তৃতীয় চতুর্থাংশ, সবচেয়ে বড়, আর তার বাইরে অস্বাভাবিক মান আলাদা বিন্দুতে। নিচের ধাপে ১১টি অর্ডারের delivery-র দিন দিয়ে একটি box plot তৈরি হচ্ছে: [1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 12].

১১টি অর্ডারের box plot median 3 Q1=2Q3=4.5 fence 8.25 outlier 024681012
  1. সাজানো মান। ১১টি অর্ডার সংখ্যারেখায়, একই দিনের অর্ডার ওপরে ওপরে। ১২ দিনের একটি অর্ডার বাকিদের থেকে অনেক দূরে। এখনো কোনো সারাংশ নেই, শুধু ডেটা।
  2. Median. সাজানো ১১টির মাঝেরটি, ৬ নম্বর মান: 3. অর্ধেক অর্ডার ৩ দিন বা কম, অর্ধেক ৩ দিন বা বেশি। ১২ দিনের অর্ডারটি median-কে নড়াতে পারে না।
  3. বাক্স: মাঝের অর্ধেক। Q1 (২৫তম percentile) 2, Q3 (৭৫তম) 4.5; NumPy-র default linear নিয়মে ৭.৫ নম্বর অবস্থান মানে ৪ আর ৫-এর মাঝামাঝি। বাক্সের দৈর্ঘ্য IQR = 4.5 − 2 = 2.5: মাঝের অর্ধেক অর্ডার এই আড়াই দিনের মধ্যে।
  4. সীমা (fence). Tukey-র প্রথা: বাক্সের প্রান্ত থেকে 1.5 × IQR দূরে সীমা। ওপরে 4.5 + 3.75 = 8.25, নিচে 2 − 3.75 = −1.75 (০-এর নিচে, ছবির বাইরে)। সীমাটি নিজে ছবিতে আঁকা হয় না; এটি শুধু হিসাবের দাগ।
  5. দাড়ি (whisker). বাক্স থেকে সীমার ভেতরের সবচেয়ে দূরের আসল মান পর্যন্ত: নিচে 1, ওপরে 6. দাড়ি 8.25 পর্যন্ত যায় না, কারণ ৬ থেকে ৮.২৫-এর মধ্যে কোনো অর্ডার নেই।
  6. Outlier আর গড়। 12 সীমার বাইরে, তাই আলাদা বিন্দু। গড় 43 / 11 ≈ 3.91, median 3-এর ডানে: একটি দেরির পার্সেল গড়কে টেনেছে। ছবিতে সবুজ ত্রিভুজ গড়। Box plot-এ সাধারণত গড় থাকে না; matplotlib-এ showmeans=True দিলে আসে।

এবার কোডের দ্বিতীয় ছবিতে ফিরুন। ৬০৩টি অর্ডারে Q1 2, Q3 5, IQR 3, তাই সীমা 5 + 4.5 = 9.5, আর আউটপুটের শেষ লাইনে outlier 10, 11, 13, 15, 21. প্রথম দুটি ঢাকার বাইরের সাধারণ দেরি, শেষ তিনটি বানানো হারানো পার্সেল: “outlier” মানে একটি হিসাবের দাগ পার হয়েছে, “ভুল ডেটা” নয়। আর দুই চূড়া box plot-এ পুরো লুকিয়েছে; অনেক দলের তুলনায় box plot চমৎকার, কিন্তু একটি দলের আকৃতি বুঝতে আগে histogram বা ECDF দেখুন।

ECDF আর strip plot

ECDF (empirical cumulative distribution function) প্রতিটি মান x-এর জন্য আঁকে “x বা তার কম মানের অংশ কত”। এতে কোনো bin নেই, তাই কিছু লুকানোও নেই, আর ব্যবসার প্রশ্নের উত্তর সরাসরি আসে: আমাদের ডেটায় ৩ দিনের মধ্যে ৫৪% অর্ডার, ৬ দিনের মধ্যে প্রায় ৯২%, ৭ দিনের মধ্যে ৯৭% পৌঁছেছে (ডেস্কটপ run-এ হিসাব করা)। “৯০% অর্ডার কত দিনে পৌঁছায়?” প্রশ্নের জন্য y = 0.9-এ একটি অনুভূমিক রেখা টেনে x পড়ুন। matplotlib 3.8 থেকে ax.ecdf() সরাসরি আছে। দুটি দলের ECDF একই axes-এ আঁকলে যে রেখা ডানে, সেই দলের delivery ধীর।

অল্প ডেটায়, ধরুন একটি নতুন জেলার প্রথম ২০টি অর্ডার, চতুর্থাংশ নিজেই অস্থির; তখন strip plot: প্রতিটি মান একটি বিন্দু। একই মানের বিন্দু আলাদা দেখাতে একটু এলোমেলো সরানো হয়, যাকে বলে jitter; কোডে y দিকে ±0.2, যার কোনো অর্থ নেই। seaborn-এ এর তৈরি রূপ stripplot আর swarmplot, পর্ব ৩০-এ আসবে।

৫ · তুলনা — bar chart-এর নিয়ম, dot plot, grouped বনাম stacked

কয়েকটি দলের একটি করে সংখ্যা তুলনার সবচেয়ে প্রচলিত chart হলো bar chart. ১৭৮৬ সালে স্কটিশ প্রকৌশলী William Playfair তাঁর “Commercial and Political Atlas”-এ bar আর line chart ব্যবহার করেন, যা এদের প্রথম দিকের পরিচিত উদাহরণ বলে ধরা হয়। Bar সহজ, কিন্তু তিনটি নিয়ম না মানলে বিভ্রান্ত করে।

  1. Axis শূন্য থেকে শুরু। Bar মান দেখায় দৈর্ঘ্য দিয়ে। ঢাকার গড় rating 4.2 আর সিলেটের 4.0; axis 3.9 থেকে শুরু করলে ঢাকার bar সিলেটের দ্বিগুণের চেয়ে লম্বা দেখায় (0.3 বনাম 0.1, তিন গুণ), অথচ পার্থক্য ৫%. দৈর্ঘ্য অনুপাত মিথ্যা বলে। যদি ছোট পার্থক্যই গল্প হয়, bar নয়, dot plot ব্যবহার করুন।
  2. মান অনুযায়ী সাজান। জেলা বা পণ্যের নাম nominal, তাদের কোনো স্বাভাবিক ক্রম নেই; বর্ণমালার ক্রমে রাখলে পাঠককে চোখ দিয়ে সাজাতে হয়। বড় থেকে ছোট সাজালে প্রথম নজরেই ক্রম আর প্রথম-শেষ পড়া যায়। ব্যতিক্রম: ordinal বা সময় (rating 1–5, মাস), যেগুলোর নিজস্ব ক্রম আছে।
  3. লম্বা নামে আড়াআড়ি bar. “কাঁচা আমের আচার, ৫০০ গ্রাম” ধরনের পণ্যের নাম x-axis-এ বসালে হেলিয়ে লিখতে হয়, যা পড়া কষ্টকর। ax.barh দিয়ে আড়াআড়ি bar আঁকলে নাম বাঁ পাশে সোজা থাকে; দশটির বেশি দলেও আড়াআড়ি ভালো।

matplotlib-এ সাজানো আড়াআড়ি bar-এর মূল কাঠামো (উদাহরণ, চালানো ফল অংশ ৭-এ দেখবেন):

order = np.argsort(values)              # ছোট থেকে বড়
ax.barh(np.array(names)[order], values[order])  # বড়টি ওপরে
ax.set_xlim(0, None)                    # দৈর্ঘ্য শূন্য থেকে
ax.bar_label(ax.containers[0], fmt="%.1f")   # দরকার হলে মান লেখা

Dot plot Cleveland নিজেই জনপ্রিয় করেছিলেন: প্রতিটি দলের একটি সারি, মানের জায়গায় একটি বিন্দু। মান অবস্থান দিয়ে পড়া হয়, তাই axis শূন্য থেকে শুরু না করলেও মিথ্যা হয় না; একই সারিতে দুটি বিন্দু (গত বছর, এ বছর) যুক্ত করলে পরিবর্তনও দেখায়।

দুটি nominal কলাম একসঙ্গে, যেমন জেলা আর পেমেন্ট পদ্ধতি, হলে দুই পথ। Grouped bar-এ প্রতিটি জেলায় পেমেন্টের bar-গুলো পাশাপাশি, সবগুলো শূন্য থেকে: জেলার ভেতরে পদ্ধতিগুলোর তুলনা সহজ। Stacked bar-এ একটির ওপর আরেকটি: মোট উচ্চতা জেলার মোট অর্ডার, তাই মোটের তুলনা সহজ, আর একেবারে নিচের অংশটিও (একই baseline). কিন্তু মাঝের অংশগুলোর শুরু আলাদা উচ্চতায়, ঠিক ছবি ২৯.১-এর ধাপ ৩-এর মতো না-মেলানো দৈর্ঘ্য, তাই সেগুলোর তুলনা কঠিন। নিয়ম: প্রশ্ন যদি “মোট” নিয়ে, stacked; যদি “ভেতরের ভাগ” নিয়ে, grouped; আর তিন-চারটির বেশি ভাগ হলে grouped bar-ও ভিড় করে, তখন small multiples (অংশ ৮) ভাবুন।

৬ · সম্পর্ক — scatter, overplotting আর log scale

“দামি অর্ডার কি দেরিতে পৌঁছায়?” — দুটি সংখ্যার কলামের সম্পর্কের প্রশ্ন। উত্তরের প্রথম chart scatter plot: প্রতিটি অর্ডার একটি বিন্দু, x-এ দাম, y-এ দিন। ১০০টি অর্ডারে এটি চমৎকার কাজ করে। কিন্তু বাজারঘরের এক মাসের ৩,০০০ অর্ডারে বিন্দুগুলো একটির ওপর আরেকটি পড়ে একটি নিরেট দাগ হয়ে যায়, আর কোথায় ১০টি অর্ডার, কোথায় ২০০টি, বোঝা যায় না। একে বলে overplotting. সঙ্গে দুটি বাড়তি সমস্যা: দিন পূর্ণসংখ্যা, তাই বিন্দু কয়েকটি অনুভূমিক রেখায় জমে; আর দাম skewed (বেশিরভাগ ছোট, অল্প কয়েকটি অনেক বড়), তাই ছোট দামের অর্ডার বাঁ কোণে চাপা পড়ে। নিচের ধাপে ১৫০টি অর্ডারের একটি ছোট নমুনায় সমাধানগুলো একটি একটি করে প্রয়োগ হচ্ছে।

Overplotting-এর সমাধান ধাপে ধাপে 159 days 0500010000অর্ডারের দাম (টাকা) 100100010000অর্ডারের দাম (টাকা, log)
  1. Default scatter. বড় নিরেট বিন্দু। ১৫০টির প্রায় ৯০% অর্ডার ২,০০০ টাকার নিচে, তাই x-axis-এর বাঁ দিকের এক-পঞ্চমাংশে সব জমে আছে; প্রতিটি দিনের সারিতে বিন্দুগুলো জুড়ে একটি মোটা দাগ।
  2. ছোট marker. বিন্দুর আকার কমালে কিছু ফাঁক বেরোয়, কিন্তু একই জায়গায় পাঁচটি বিন্দু আর একটি বিন্দু এখনো দেখতে এক।
  3. Alpha. প্রতিটি বিন্দু ২৫% অস্বচ্ছ (alpha 0.25); যেখানে অনেক বিন্দু একটির ওপর আরেকটি, সেখানে রং জমে গাঢ় হয়। এখন ঘনত্ব একটু পড়া যায়: ১–২ দিনের সারির বাঁ অংশ সবচেয়ে গাঢ়।
  4. Jitter. দিন পূর্ণসংখ্যা, তাই y-এ ±0.3 এলোমেলো সরানো; প্রতিটি সারি একটি সরু ফিতে হয়ে খুলে গেল। কিন্তু দামের দিক এখনো বাঁ কোণে চাপা।
  5. Log x-axis. ১০০ থেকে ১,০০০ আর ১,০০০ থেকে ১০,০০০ সমান দূরত্ব পেল। ছোট দামের অর্ডারগুলো ছড়িয়ে পড়ল, আর বোঝা গেল বেশিরভাগ অর্ডার কয়েকশো থেকে দুই-তিন হাজার টাকার মধ্যে।
  6. খোপে গোনা। বিন্দু না এঁকে x-y সমতলকে খোপে ভাগ করে প্রতিটি খোপে কয়টি অর্ডার গুনে রং: এখানে চৌকো খোপ (2-D histogram), কোডে ষড়ভুজ (hexbin). সবচেয়ে গাঢ় খোপ প্রায় ৮০০–১,৮০০ টাকা আর ২ দিন, ১৮টি অর্ডার। লাখ বিন্দুতেও এই ছবি একই রকম পরিষ্কার।

এবার পুরো ৩,০০০ অর্ডার। কোডটি দাম বানায় NumPy-র lognormal থেকে (পর্ব ১৬-এর random generator), integer পয়সায় রাখে, আর ছবির জন্য টাকায় ভাগ করে। তারপর তিনটি panel: default scatter, ছোট-স্বচ্ছ-jitter-সহ log x, আর hexbin-এ log x.

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
n = 3000
paisa = (rng.lognormal(np.log(80_000), 0.9, n)).round().astype(np.int64)
taka = paisa / 100                               # plot-এর জন্য টাকা
far = rng.random(n) < 0.4                        # ৪০% অর্ডার ঢাকার বাইরে
days = 1 + rng.poisson(np.where(far, 3.0, 0.8) + 0.4 * np.log10(taka / 100))
print("taka: median", round(np.median(taka)), "| mean", round(taka.mean()),
      "| max", round(taka.max()))
print("share above 5,000 taka:", round((taka > 5000).mean(), 3))
print("distinct (value, day) dots on screen ~", len(set(zip(
    (taka // 50).tolist(), days.tolist()))), "of", n)
print("corr(log10 taka, days):",
      round(np.corrcoef(np.log10(taka), days)[0, 1], 3))
print("median days: under 500 taka", np.median(days[taka < 500]),
      "| over 3,000 taka", np.median(days[taka > 3000]))

fig, ax = plt.subplots(1, 3, figsize=(7.5, 2.8), dpi=100, sharey=True)
ax[0].scatter(taka, days)                        # default: বড় marker, alpha নেই
ax[0].set(title="Default scatter", xlabel="Order value (taka)",
          ylabel="Delivery days")
jit = days + rng.uniform(-0.3, 0.3, n)           # দিন পূর্ণসংখ্যা: একটু ছড়ানো
ax[1].scatter(taka, jit, s=4, alpha=0.15, color="#2563eb")
ax[1].set(xscale="log", title="Small + alpha + log x",
          xlabel="Order value (taka, log)")
hb = ax[2].hexbin(taka, days, gridsize=(18, 10), xscale="log",
                  mincnt=1, cmap="viridis")
ax[2].set(title="Hexbin + log x", xlabel="Order value (taka, log)")
fig.colorbar(hb, ax=ax[2], label="Orders")
fig.subplots_adjust(left=0.08, right=0.9, bottom=0.2, wspace=0.12)
plt.show()

আসল আউটপুট (ব্রাউজারের Pyodide, matplotlib 3.10.8):

taka: median 800 | mean 1174 | max 13983
share above 5,000 taka: 0.02
distinct (value, day) dots on screen ~ 565 of 3000
corr(log10 taka, days): 0.091
median days: under 500 taka 3.0 | over 3,000 taka 3.0

Median দাম ৮০০ টাকা, কিন্তু গড় ১,১৭৪, আর সবচেয়ে বড় অর্ডার প্রায় ১৪,০০০: গড় median-এর অনেক ওপরে, এটিই ডানদিকে লম্বা লেজের (right-skewed) চিহ্ন। মাত্র ২% অর্ডার ৫,০০০ টাকার ওপরে, অথচ linear x-axis-এর অর্ধেকের বেশি জায়গা তারাই নিয়েছে। “distinct dots” লাইন একটি মোটা হিসাব: ৫০ টাকা চওড়া আর ১ দিন উঁচু খোপ ধরলে ৩,০০০ অর্ডার মাত্র ৫৬৫টি আলাদা জায়গায় বসে, মানে গড়ে প্রতিটি দৃশ্যমান বিন্দুর নিচে পাঁচটির বেশি অর্ডার লুকিয়ে। প্রথম panel তাই প্রায় তথ্যহীন। দ্বিতীয় panel-এ ছোট, স্বচ্ছ, jitter-সহ বিন্দু আর log x মিলে দেখা যায় ঘন অংশ কয়েকশো থেকে দুই হাজার টাকা আর ১–৩ দিন। তৃতীয় panel-এ hexbin প্রতিটি ষড়ভুজে অর্ডার গুনে viridis রঙে দেখায় (হলুদ সবচেয়ে ঘন), পাশে colorbar-এ সংখ্যা।

আর প্রশ্নের উত্তর? log দামের সঙ্গে দিনের correlation মাত্র ০.০৯১, আর ৫০০ টাকার নিচের ও ৩,০০০ টাকার ওপরের অর্ডারের median দিন একই, ৩. কোডে দামের একটি ছোট প্রভাব রাখা হয়েছিল (0.4 * log10), কিন্তু ঢাকার ভেতর-বাইরের পার্থক্যের তুলনায় তা নগণ্য। ভালো chart-এর একটি কাজ হলো “সম্পর্ক নেই বা খুব দুর্বল” এই উত্তরও সৎভাবে দেখানো। আর correlation কোনো কারণ প্রমাণ করে না; এর বিস্তারিত পর্ব ৩৭ · ডেটার পরিসংখ্যান-এ।

Log scale কখন: মান কয়েক গুণ জুড়ে ছড়ানো (দাম, আয়, জনসংখ্যা) আর প্রশ্নটি অনুপাতের (“দ্বিগুণ”, “দশ গুণ”). সতর্কতা: শূন্য বা ঋণাত্মক মান log-এ আঁকা যায় না (symlog আছে, কিন্তু পড়া কঠিন); axis-এ স্পষ্ট লিখুন যে এটি log; আর bar chart-এ log প্রায় কখনো নয়, কারণ bar-এর দৈর্ঘ্য শূন্য থেকে মাপা।

Hexbin-এর ধারণা সাধারণত Daniel Carr ও সহকর্মীদের ১৯৮৭ সালের একটি প্রবন্ধের সঙ্গে যুক্ত করা হয়। ষড়ভুজে কেন্দ্র থেকে প্রান্তের দূরত্ব চৌকো খোপের চেয়ে বেশি সমান, তাই ঘনত্বের ছবি একটু মসৃণ। চৌকো খোপের জন্য ax.hist2d; log x-এ hexbin-কে xscale="log" দিতে হয়, যাতে খোপগুলোও log জায়গায় সমান হয়।

৭ · অংশ আর সমগ্র — stacked bar, 100% bar আর pie কেন কঠিন

“অর্ডারের কত ভাগ কোন পেমেন্ট পদ্ধতিতে?” — একটি সমগ্রকে ভাগে ভাগ করার প্রশ্ন, যাকে বলে composition. এর সবচেয়ে পরিচিত chart pie chart, যার প্রথম দিকের পরিচিত ব্যবহারও Playfair-এর, ১৮০১ সালের “Statistical Breviary” বইয়ে। Pie ভাগ দেখায় কোণ আর ক্ষেত্রফল দিয়ে, যা ছবি ২৯.১-এ অবস্থানের অনেক নিচে। ভাগগুলো খুব আলাদা হলে (৭০%, ২০%, ১০%) pie বোঝা যায়; কাছাকাছি হলে প্রায় অসম্ভব। নিচের কোড বাজারঘরের ১,০০০ অর্ডারের পাঁচটি পেমেন্ট পদ্ধতির একই পাঁচটি সংখ্যা pie আর সাজানো bar-এ পাশাপাশি আঁকে; তারপর জেলাভেদে পেমেন্টের মিশ্রণ একটি 100% stacked bar-এ।

import numpy as np
import matplotlib.pyplot as plt

# ১,০০০ অর্ডারের পেমেন্ট পদ্ধতি (বাজারঘরের কাল্পনিক হিসাব)
counts = {"Bank": 170, "COD": 231, "Card": 187, "Nagad": 198, "bKash": 214}
names, n = list(counts), np.array(list(counts.values()))
share = n / n.sum() * 100
for k, s in sorted(zip(names, share), key=lambda t: -t[1]):
    print(f"{k:6s} {s:5.1f}%")

fig, (a1, a2) = plt.subplots(1, 2, figsize=(7.5, 3), dpi=100)
a1.pie(n, labels=names, startangle=90, counterclock=False)
a1.set_title("Pie: which is 2nd?")
order = np.argsort(n)                        # ছোট থেকে বড়, তাই বড়টি ওপরে
a2.barh(np.array(names)[order], share[order], color="#2563eb")
a2.set(title="Sorted bar", xlabel="Share of orders (%)", xlim=(0, 25))
fig.tight_layout()

# ১০০% stacked bar: জেলাভেদে পেমেন্টের মিশ্রণ
rng = np.random.default_rng(42)
districts = ["Dhaka", "Chattogram", "Khulna", "Rajshahi", "Sylhet"]
p_cod = [0.15, 0.25, 0.35, 0.40, 0.30]
mix = np.array([rng.multinomial(200, [c, (1 - c) * .4, (1 - c) * .3,
                (1 - c) * .2, (1 - c) * .1]) for c in p_cod]) / 2
print("COD share by district (%):", mix[:, 0].tolist())
fig2, ax = plt.subplots(figsize=(7.5, 2.8), dpi=100)
left = np.zeros(5)
for j, m in enumerate(["COD", "bKash", "Nagad", "Card", "Bank"]):
    ax.barh(districts, mix[:, j], left=left, label=m)
    left += mix[:, j]
ax.set(xlabel="Share of orders (%)", xlim=(0, 100), title="Payment mix (100% bars)")
ax.invert_yaxis()                            # Dhaka ওপরে
ax.legend(fontsize=8, loc="center left", bbox_to_anchor=(1, 0.5))
fig2.tight_layout()
plt.show()

আসল আউটপুট (ব্রাউজারের Pyodide, matplotlib 3.10.8):

COD     23.1%
bKash   21.4%
Nagad   19.8%
Card    18.7%
Bank    17.0%
COD share by district (%): [17.0, 24.0, 31.0, 45.5, 31.5]

পাঁচটি ভাগ ১৭% থেকে ২৩%, পাশাপাশি দুটির পার্থক্য এক থেকে দুই শতাংশ বিন্দুর মধ্যে। Pie-টি একবার দেখে বলার চেষ্টা করুন কোনটি দ্বিতীয়: bKash না Nagad? Card না Bank? টুকরোগুলো বৃত্তের ভিন্ন জায়গায়, ভিন্ন দিকে হেলানো, আর চোখকে কোণ তুলনা করতে হয়। সাজানো bar-এ উত্তর তাৎক্ষণিক: COD, bKash, Nagad, Card, Bank, কারণ সব bar একই রেখা থেকে শুরু, তুলনা হয় একই স্কেলে অবস্থান দিয়ে। Pie-র টুকরোয় শতাংশ লিখে দিলে পাঠক ছবি নয়, সংখ্যা পড়েন: সেটি আসলে গোল করে সাজানো একটি টেবিল।

দ্বিতীয় ছবিতে বাঁ দিকের প্রথম অংশ COD, যার ভাগ জেলায় জেলায় তুলনা সহজ (ঢাকায় ১৭%, রাজশাহীতে ৪৫.৫%), কারণ সবগুলো একই বাঁ রেখা থেকে শুরু। মাঝের bKash বা Nagad-এর শুরু প্রতিটি জেলায় আলাদা, তাই চোখকে দৈর্ঘ্য আন্দাজ করতে হয়। শিক্ষা: গল্পের কেন্দ্রের ভাগটি প্রথমে বসান; আর 100% bar মোট অর্ডার লুকায়।

Pie কখন চলে: দুই বা তিনটি ভাগ, আর বার্তাটি “অর্ধেকের বেশি” বা “প্রায় এক-চতুর্থাংশ” ধরনের মোটা অনুপাত। কখনো না: পাঁচটির বেশি ভাগ, কাছাকাছি ভাগ, একাধিক pie পাশাপাশি তুলনা, 3D pie (পেছনের টুকরো ছোট দেখায়), আর যোগফল ১০০% নয় এমন সংখ্যা (যেমন একাধিক উত্তর দেওয়া যায় এমন জরিপ)। Donut chart একই সমস্যা বহন করে, কারণ কেন্দ্র কেটে ফেলায় কোণ আরও কম দেখা যায়।

নোট: treemap একটি আয়তক্ষেত্রকে ভাগের মাপের ছোট আয়তক্ষেত্রে ভাগ করে, ভাগের ভেতরে ভাগসহ (বিভাগ → পণ্য); Ben Shneiderman ১৯৯০-এর দিকে ডিস্কের ফাইলের আকার দেখাতে এটি প্রস্তাব করেন। স্তরের মোটা ছবিতে কাজের, কিন্তু ক্ষেত্রফলের তুলনা কঠিন। matplotlib-এ এটি সরাসরি নেই; Plotly-তে আছে (পর্ব ৩১)।

৮ · সময় — line chart, marker, চলমান গড় আর small multiples

আবহাওয়ার খবরে গত সাত দিনের তাপমাত্রা একটি রেখায় দেখানো হয়, আর দর্শক এক নজরে বোঝেন “ঠান্ডা বাড়ছে”। Line chart-এ x-axis সময়, বাঁ থেকে ডানে, আর বিন্দুগুলো যুক্ত করা রেখা বলে “মাঝের সময়েও মান মোটামুটি এর মাঝামাঝি ছিল”। এই অন্তর্নিহিত দাবিটি মনে রাখলে line chart-এর নিয়মগুলো নিজেই আসে:

  • রেখা শুধু সময়ে। জেলার নাম বা পণ্যের মতো nominal x-এ বিন্দু যুক্ত করবেন না; খুলনা আর রাজশাহীর “মাঝে” কিছু নেই।
  • বিরল বা অসমান বিন্দুতে marker. মাসে একবার মাপা মানে marker="o" দিয়ে আসল মাপের জায়গা দেখান; ফাঁকা মাসে রেখা ভেঙে দিন (NaN রাখলে matplotlib নিজে ভাঙে)।
  • ঝাঁকুনির ওপর চলমান গড়। হালকা রঙে আসল রেখা, গাঢ় রঙে moving average, যেমন পর্ব ২৬-এর rolling(7).mean(). আসল রেখা মুছবেন না: চলমান গড় দেরিতে সাড়া দেয়।
  • y-axis-এর শুরু প্রশ্ন অনুযায়ী। Line মান দেখায় অবস্থান দিয়ে, তাই শূন্য বাধ্যতামূলক নয়; তবে সরু পরিসর ছোট ওঠানামাকে নাটকীয় দেখায় (পর্ব ৩২)।

এক জেলার রেখা সহজ। পাঁচ জেলা একসঙ্গে হলে প্রলোভন হয় পাঁচটি রেখা এক ছবিতে, আর ফল হয় spaghetti chart: রেখাগুলো জট পাকায়, legend আর রেখার মধ্যে চোখ বারবার যাতায়াত করে, আর ছোট জেলার রেখা বড় জেলার স্কেলে চ্যাপ্টা হয়ে যায়। বিকল্প small multiples: একই রকম ছোট chart-এর সারি, প্রতিটিতে একটি জেলা। নামটি জনপ্রিয় করেন Edward Tufte তাঁর ১৯৮০-৯০-এর দশকের বইগুলোতে, বিশেষ করে “Envisioning Information” (১৯৯০)-এ; ধারণাটি তার আগেও ছিল। নিচের কোড বাজারঘরের পাঁচ জেলার ২৪ মাসের কাল্পনিক বিক্রি (হাজার টাকায়) দুইভাবে আঁকে: এক ছবিতে পাঁচ রেখা, আর পাঁচটি panel-এ, প্রতিটিতে হালকা আসল রেখা আর গাঢ় ৩ মাসের চলমান গড়।

import numpy as np, pandas as pd
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
months = pd.date_range("2024-11-01", periods=24, freq="MS")
base = {"Dhaka": 900, "Chattogram": 420, "Khulna": 180,
        "Rajshahi": 160, "Sylhet": 150}             # হাজার টাকা/মাস
growth = {"Dhaka": 0.004, "Chattogram": 0.012, "Khulna": -0.01,
          "Rajshahi": 0.03, "Sylhet": 0.005}
t = np.arange(24)
sales = pd.DataFrame({d: b * (1 + growth[d]) ** t * rng.normal(1, 0.08, 24)
                      for d, b in base.items()}, index=months)
sales.loc["2025-03-01", "Sylhet"] *= 1.8            # কাল্পনিক একটি উৎসবের মাস
print((sales.iloc[-3:].mean() / sales.iloc[:3].mean() - 1).mul(100).round(0))

fig, ax = plt.subplots(figsize=(7.5, 3), dpi=100)
sales.plot(ax=ax, lw=1.5)                            # সব জেলা এক ছবিতে
ax.set(title="One chart, five lines", ylabel="Sales ('000 taka)")
ax.legend(fontsize=7, loc="center left", bbox_to_anchor=(1, 0.5))
fig.tight_layout()

fig2, axes = plt.subplots(1, 5, figsize=(7.5, 2.4), dpi=100, sharex=True)
for ax, d in zip(axes, sales.columns):
    ax.plot(sales.index, sales[d], lw=0.8, color="#94a3b8")
    ax.plot(sales.index, sales[d].rolling(3).mean(), lw=2, color="#2563eb")
    ax.set_title(d, fontsize=9)
    ax.set_ylim(0, None)                             # প্রতিটি panel শূন্য থেকে
    ax.tick_params(labelsize=7)
    ax.set_xticks(months[[0, 12, 23]], ["Nov 24", "Nov 25", "Oct 26"],
                  rotation=90)
axes[0].set_ylabel("Sales ('000 taka)")
fig2.tight_layout()
plt.show()

আসল আউটপুট (ব্রাউজারের Pyodide, pandas 3.0.2, matplotlib 3.10.8):

Dhaka         10.0
Chattogram    34.0
Khulna       -27.0
Rajshahi      71.0
Sylhet        21.0
dtype: float64

সংখ্যাগুলো শেষ তিন মাসের গড় বনাম প্রথম তিন মাসের গড়ের শতাংশ পরিবর্তন: রাজশাহী +৭১%, চট্টগ্রাম +৩৪%, সিলেট +২১%, ঢাকা +১০%, আর খুলনা −২৭%. এবার প্রথম ছবিতে এই গল্প খুঁজুন। ঢাকা আর চট্টগ্রামের রেখা স্পষ্ট, কিন্তু বাকি তিনটি ১৫০–৩০০-এর সরু পট্টিতে জড়িয়ে আছে; রাজশাহীর সবচেয়ে বড় বৃদ্ধি আর খুলনার পতন প্রায় চোখে পড়ে না, আর কোন রঙ কোন জেলা, তা বারবার legend দেখে মেলাতে হয়। দ্বিতীয় ছবিতে প্রতিটি panel নিজের মাপে, শূন্য থেকে: রাজশাহীর খাড়া ওঠা, খুলনার নামা, আর সিলেটে ২০২৫-এর মার্চের একটি চূড়া (কাল্পনিক উৎসবের মাস) আলাদা করে পড়া যায়। সিলেটের গাঢ় রেখায় দেখুন, একটি মাসের লাফ ৩ মাসের চলমান গড়ে তিন মাস জুড়ে উঁচু হয়ে গেছে: চলমান গড়ের দাম।

একটি সিদ্ধান্ত সচেতনভাবে নেওয়া হয়েছে: panel-গুলোর y-axis আলাদা (sharey নেই)। লাভ: প্রতিটি জেলার আকৃতি, মানে বৃদ্ধি বা পতনের ধরন, পরিষ্কার। ক্ষতি: panel-এর উচ্চতা দিয়ে জেলার আকার তুলনা করা যায় না; ঢাকার ১,০০০ আর সিলেটের ১৭৫ দেখতে সমান উঁচু। প্রশ্ন “কোন জেলা বড়” হলে sharey=True; “কীভাবে বদলাচ্ছে” হলে আলাদা y. আরেকটি পথ: সব রেখা হালকা ধূসরে, আর গল্পের এক-দুটি জেলা রঙে।

৯ · সিদ্ধান্তের টেবিল — আর কখন chart নয়, টেবিল

পুরো পর্বকে একটি টেবিলে আনা যায়। এটি মুখস্থের তালিকা নয়, প্রথম পছন্দের একটি মানচিত্র; প্রতিটি সারির “এড়িয়ে চলুন” কলামটিও সমান গুরুত্বপূর্ণ।

প্রশ্নডেটাপ্রথম পছন্দবিকল্পএড়িয়ে চলুন
একটি সংখ্যা কেমন ছড়ানো?১টি numerichistogram (২-৩টি bin width দেখে)ECDF; অল্প ডেটায় stripশুধু গড়ের একটি সংখ্যা
কয়েকটি দলের distribution তুলনাnumeric + nominalপাশাপাশি box plot, বা একই axes-এ ECDFদলে দলে histogram, ওপর-নিচেগড়ের bar, error bar ছাড়া
দলে দলে একটি মান, কোনটা বড়?nominal + ১টি মানসাজানো bar, শূন্য থেকেdot plot (কাছাকাছি মানে)বর্ণমালার ক্রম, কাটা axis-এর bar
দুটি সংখ্যার সম্পর্ক২টি numericscatterবড় ডেটায় alpha বা hexbin; skewed হলে log৩,০০০ নিরেট বিন্দু; দুটি y-axis
সমগ্রের কত ভাগ?nominal + অংশসাজানো bar (শতাংশ)দলে দলে 100% bar; ২-৩ ভাগে pie৫টির বেশি ভাগের pie, 3D pie
সময়ের সঙ্গে পরিবর্তনসময় + numericline chartঝাঁকুনিতে চলমান গড়; বিরল মাপে markernominal x-এ রেখা
অনেক দলের সময়ের পরিবর্তনসময় + numeric + nominalsmall multiplesধূসর রেখা + ১-২টি রঙিন৫টির বেশি রঙিন রেখার spaghetti
অল্প কয়েকটি সঠিক সংখ্যাযেকোনোটেবিলএকটি বাক্যে লেখাসংখ্যা লেখা pie

কখন টেবিলই ভালো

চায়ের দোকানের হিসাব খাতায় কেউ chart আঁকেন না; দরকার “আজ কত বাকি পড়ল”, একটি সঠিক সংখ্যা। Chart-এর শক্তি আকৃতি আর প্রবণতা; টেবিলের শক্তি নির্দিষ্ট মান খোঁজা। টেবিল ভালো যখন পাঠক নির্দিষ্ট মান খুঁজবেন বা হিসাবে বসাবেন, সংখ্যা খুব কম (তিন জেলার দুটি মান), কলামের একক আলাদা (টাকা, দিন, শতাংশ), বা নির্ভুলতাই মূল কথা। উল্টো দিকে, Anscombe’s quartet (ভাবনার প্রশ্ন ৪) মনে করায় যে সারাংশের টেবিল আকৃতি লুকাতে পারে; তাই প্রায়ই দুটিই: chart-এ আকৃতি, পাশে ছোট টেবিলে মূল সংখ্যা।

আরও গভীরে। Leland Wilkinson-এর “The Grammar of Graphics” (১৯৯৯) chart-কে ডেটা, aesthetic mapping (encoding), geometric object, scale, coordinate আর facet-এর মিশ্রণ হিসেবে দেখান; R-এর ggplot2 আর Python-এর plotnine ও seaborn-এর objects interface এই চিন্তা থেকে এসেছে, আর small multiples সেখানে “facet”. Jock Mackinlay ১৯৮৬ সালে তাঁর APT সিস্টেমে Cleveland ও McGill-এর ক্রমকে ডেটার ধরন অনুযায়ী আলাদা করেন: quantitative ডেটায় position ও length সেরা, কিন্তু nominal ডেটায় hue আর shape অনেক ওপরে উঠে আসে; আধুনিক “chart সুপারিশ” টুলগুলোর অনেকেই এই ধরনের নিয়ম ব্যবহার করে। Histogram-এর bin width আসলে bias আর variance-এর আপস: চওড়া bin স্থির কিন্তু আকৃতি মুছে দেয়, সরু bin আকৃতি রাখে কিন্তু এলোমেলো; kernel density estimate একই আপস একটি bandwidth দিয়ে করে, যা পর্ব ৩০-এ seaborn-এর kdeplot-এ দেখবেন।

পরীক্ষা ও ইন্টারভিউতে। বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এমন প্রশ্ন প্রায়ই আসে:
● Box plot-এ বাক্স, দাড়ি আর বিন্দু কী বোঝায়? — বাক্স Q1 থেকে Q3 (মাঝের ৫০%), ভেতরের দাগ median, দাড়ি 1.5 × IQR সীমার ভেতরের শেষ মান পর্যন্ত, বাইরের বিন্দু outlier.
● Q1 = 2, Q3 = 5 হলে outlier-এর ওপরের সীমা? — IQR 3, সীমা 5 + 4.5 = 9.5.
● Bar chart-এর axis শূন্য থেকে কেন? — bar দৈর্ঘ্য দিয়ে মান দেখায়; কাটা axis দৈর্ঘ্যের অনুপাত মিথ্যা করে। Line বা dot plot-এ এই শর্ত নেই।
● Pie-র বদলে কী, আর কেন? — সাজানো bar; একই স্কেলে অবস্থান কোণের চেয়ে নির্ভুল পড়া যায় (Cleveland ও McGill, ১৯৮৪)।
● লাখ বিন্দুর scatter কীভাবে পড়বেন? — ছোট marker ও alpha, অথবা hexbin বা 2-D histogram; skewed অক্ষে log scale.

১০ · AI-এর যুগে — AI সহকারীর বানানো chart যাচাই

“এই CSV থেকে কয়েকটা সুন্দর chart বানাও” লিখলে AI সহকারী মুহূর্তে কোড দেবে, কিন্তু প্রশ্ন না পেলে chart-এর ধরন আন্দাজ করে, প্রায়ই কলামের নাম দেখে। সাধারণ ভুলগুলো:

  • প্রশ্ন ছাড়া chart: প্রতিটি কলামের জন্য একটি করে histogram বা pie, কোনো প্রশ্নের উত্তর ছাড়াই; ৮টি পেমেন্ট পদ্ধতির pie, বর্ণমালার ক্রমে bar.
  • পুরোনো বা বানানো API: boxplot(labels=...) (matplotlib 3.9 থেকে নাম tick_labels), vert=False (3.10-এ orientation এসেছে), seaborn-এর পুরোনো distplot (বহু আগে বাতিলের পথে), বা অস্তিত্বহীন argument; আর plt.style.use("seaborn"), যা এখন "seaborn-v0_8".
  • লুকানো সিদ্ধান্ত: default ১০টি bin, কাটা axis-এর bar, স্বয়ংক্রিয় y-সীমা, বা dropna() দিয়ে চুপচাপ সারি বাদ; সঙ্গে warnings.filterwarnings("ignore").
  • ভুল পরিসংখ্যানের দাবি: scatter দেখে “দাম বাড়লে delivery দেরি হয়” বলা, যখন correlation ০.০৯; বা পুরো ডেটার chart দেখে model-এর feature বাছাই, যা পরে test-এর তথ্য ফাঁস করতে পারে (পর্ব ৫৬).
  • অযাচাই করা সংখ্যা: chart-এর শিরোনামে AI-এর লেখা শতাংশ, যা কোডের হিসাবের সঙ্গে মেলে না।

প্রম্পটে প্রশ্ন, ডেটার ধরন, version আর না-চাওয়া জিনিসগুলো স্পষ্ট বলুন। আসল ক্রেতার ডেটা নয়, বানানো ছোট নমুনা দিন।

আমি Python 3.14, pandas 3.0 আর matplotlib 3.10-এ কাজ করছি। DataFrame-এর কলাম: district (৫টি নাম, nominal), delivery_days (পূর্ণসংখ্যা দিন), price_paisa (int64, খুব skewed), payment (৫টি নাম)। বানানো উদাহরণ সারি: Khulna, 4, 125000, COD. প্রশ্ন ১: ঢাকা আর ঢাকার বাইরের delivery_days-এর distribution কতটা আলাদা? একই axes-এ দুটি ECDF আঁকো (ax.ecdf), আর ৯০% অর্ডার কত দিনে পৌঁছায় তা ছাপো। প্রশ্ন ২: payment-এর ভাগ: pie নয়, শতাংশের সাজানো আড়াআড়ি bar, x-axis শূন্য থেকে। প্রশ্ন ৩: দাম (টাকায়, price_paisa / 100) বনাম delivery_days: hexbin, log x (xscale="log"), আর correlation ছাপো; কোনো কারণের দাবি করবে না। প্রতিটি chart-এর শিরোনাম ইংরেজিতে, উত্তরটি বাক্য হিসেবে। কোনো warning চাপা দেবে না, কোনো সারি নিঃশব্দে বাদ দেবে না। কোনো function বা parameter matplotlib 3.10-এ আছে কি না নিশ্চিত না হলে বলো; বানিয়ে লিখবে না।

AI-এর chart হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Cleveland আর McGill কীভাবে প্রমাণ করলেন যে অবস্থান কোণের চেয়ে ভালো, আর তাঁদের ফলকে কতটা সাধারণ নিয়ম ধরা যায়?

১৯৮০-এর দশকের শুরুতে Bell Labs-এ William Cleveland আর Robert McGill একটি প্রশ্নকে পরীক্ষাযোগ্য করেন, যা তখন প্রায় রুচির বিষয় ধরা হতো: কোন chart “ভালো”? তত্ত্বটি ছিল, chart পড়া মানে কয়েকটি মৌলিক চাক্ষুষ কাজ: অবস্থান, দৈর্ঘ্য, কোণ বা ক্ষেত্রফল তুলনা। পরীক্ষায় অংশগ্রহণকারীরা দুটি চিহ্নিত মানের মধ্যে ছোটটি বড়টির কত শতাংশ, তা আন্দাজ করেন, আর ভুল মাপা হয়। একই সংখ্যা bar আর pie-তে দেখালে bar-এ (অবস্থান) ভুল ধারাবাহিকভাবে কম ছিল।

১৯৮৪ সালের প্রবন্ধে তাঁরা ছয় স্তরের ক্রম দেন: একই স্কেলে অবস্থান; আলাদা স্কেলে অবস্থান; দৈর্ঘ্য, দিক ও কোণ; ক্ষেত্রফল; আয়তন ও বক্রতা; রঙের গাঢ়তা। সব অংশ সমান শক্ত প্রমাণে দাঁড়ানো নয়; কিছু পরীক্ষা থেকে, কিছু আগের মনোবিজ্ঞানের ফল থেকে। ২০১০ সালে Jeffrey Heer আর Michael Bostock অনলাইনে অনেক বেশি মানুষ নিয়ে পুনরাবৃত্তি করে মূল ক্রম মোটামুটি টিকে থাকে বলে জানান।

সীমাও আছে। পরীক্ষার কাজ ছিল অনুপাত আন্দাজ; কিন্তু chart দিয়ে আমরা প্রবণতা ধরি, ব্যতিক্রম খুঁজি, দল চিনি। হঠাৎ চোখে পড়ার কাজে রং খুব শক্তিশালী, আর “এই ভাগটি অর্ধেকের বেশি?” ধরনের প্রশ্নে pie-ও ভালো করে বলে কিছু গবেষণা আছে। তাই ক্রমটিকে ডিফল্ট ঝোঁক হিসেবে নিন। বাজারঘরের পেমেন্টের run-এ প্রভাবটি নিজেই দেখেছেন: একই পাঁচটি সংখ্যা bar-এ তাৎক্ষণিক, pie-তে অনুমান।

মূল উপলব্ধি: chart-এর “ভালো-মন্দ” রুচির চেয়ে বেশি কিছু; মানুষের চোখ কোন তুলনায় কত ভুল করে, তা মাপা যায়, আর সেই মাপ বলে সবচেয়ে জরুরি তুলনাটি একই স্কেলের অবস্থানে রাখতে।

প্র ০২Pie chart এত সমালোচিত হলেও কেন টিকে আছে, আর Florence Nightingale-এর বিখ্যাত diagram কি আসলে pie ছিল?

William Playfair ১৮০১ সালে “Statistical Breviary”-তে বৃত্ত ভাগ করে একটি সাম্রাজ্যের অংশগুলো দেখান; এটিকে সাধারণত pie-র প্রথম দিকের পরিচিত ব্যবহার ধরা হয়। পরে Charles Minard মানচিত্রের ওপর pie বসান। জনপ্রিয়তার কারণ সহজ: বৃত্ত মানে “পুরোটা”, টুকরো মানে “ভাগ”; পিঠা ভাগ করার অভিজ্ঞতা সবার আছে।

Florence Nightingale-এর ১৮৫৮ সালের বিখ্যাত ছবিটিকে প্রায়ই “pie chart” বলা হয়, কিন্তু সেটি polar area diagram (“coxcomb” বা “rose”). প্রতিটি মাসের জন্য সমান কোণের টুকরো, আর টুকরোর ক্ষেত্রফল সেই মাসের মৃত্যু: যুদ্ধের আঘাতে, প্রতিরোধযোগ্য রোগে, অন্য কারণে। উদ্দেশ্য ছিল ক্রিমিয়ার যুদ্ধের সেনা হাসপাতালে রোগে মৃত্যু যে আঘাতের চেয়ে অনেক বেশি, তা নীতিনির্ধারকদের দেখানো। ইতিহাসবিদরা উল্লেখ করেন, ক্ষেত্রফলের encoding বড় মানকে আরও বড় দেখায়, যা তাঁর যুক্তিকে জোরালো করেছিল: chart শুধু তথ্য নয়, যুক্তিও।

আধুনিক সমালোচনা আসে Cleveland ও McGill-এর মতো পরীক্ষা থেকে। তবে সব গবেষণা একদিকে নয়; ১৯৯১ সালে Ian Spence আর Stephan Lewandowsky-র পরীক্ষায় কিছু অংশ-সমগ্রের কাজে pie bar-এর চেয়ে খারাপ করেনি বলে জানানো হয়। ভারসাম্যপূর্ণ অবস্থান: দুই-তিনটি ভাগ আর মোটা অনুপাতে pie চলে; অনেক বা কাছাকাছি ভাগে চলে না, আর বাজারঘরের ১৭% থেকে ২৩%-এর পাঁচটি ভাগ শেষ দলে পড়ে।

মূল উপলব্ধি: pie টিকে আছে কারণ “অংশ আর পুরো”-র রূপক সহজ; কিন্তু সেই রূপক নির্ভুল তুলনা দেয় না, তাই ভাগ যত বেশি আর কাছাকাছি, সাজানো bar তত ভালো।

প্র ০৩Tukey কেন ঠিক 1.5 × IQR-কে outlier-এর সীমা বেছেছিলেন, আর box plot কখন বিভ্রান্ত করে?

John Tukey ১৯৭০-এর দশকে “exploratory data analysis” ধারণা জনপ্রিয় করেন: আগে ডেটা দেখা, তারপর model. তাঁর ১৯৭৭ সালের বই কাগজ-কলমে আঁকার উপযোগী পদ্ধতিতে ভরা, আর box plot তার একটি। এখানে গড় আর standard deviation নয়, median আর চতুর্থাংশ, কারণ সেগুলো outlier-এর টানে সরে না; এই গুণকে বলে robustness.

1.5 সংখ্যাটির পেছনে গভীর সূত্র নেই; এটি একটি সুবিধাজনক বাছাই হিসেবেই প্রচলিত। একটি হিসাব যুক্তিটি দেখায়: normal distribution-এ Q1 আর Q3 গড় থেকে প্রায় ০.৬৭৪σ দূরে, IQR প্রায় ১.৩৪৯σ, আর সীমা প্রায় ২.৬৯৮σ দূরে। এর বাইরে পড়ার সম্ভাবনা প্রায় ০.৭%, মানে ১,০০০টি normal মানে গড়ে প্রায় ৭টি “outlier”. বড় ডেটায় কয়েকটি বিন্দু বাইরে পড়া তাই স্বাভাবিক। Skewed ডেটায়, যেমন অর্ডারের দাম, ডানদিকে অনেক বিন্দু বাইরে পড়ে শুধু লেজের কারণে; সেখানে log করে আঁকা যুক্তিসংগত।

বড় দুর্বলতা এই পর্বের run-এই দেখেছেন: দুই চূড়া পুরো লুকিয়ে একটি সাধারণ বাক্স। একই box plot-এর পেছনে এক চূড়া, দুই চূড়া বা সমান ছড়ানো ডেটা থাকতে পারে, আর ১০–২০টি মানে চতুর্থাংশ নিজেই অস্থির। সমাধান: box plot-এর ওপর আসল বিন্দু, violin plot, বা পাশে histogram; পর্ব ৩০-এ seaborn দিয়ে এগুলো সহজে আঁকবেন।

মূল উপলব্ধি: box plot একটি সংক্ষিপ্তসার, আর সংক্ষিপ্তসার কিছু বাদ দেয়; ১.৫ × IQR একটি প্রথা, প্রকৃতির নিয়ম নয়, আর outlier মানে “খুঁজে দেখুন”, “মুছে ফেলুন” নয়।

প্র ০৪Anscombe’s quartet আর Datasaurus কী শেখায়, আর তবুও কখন টেবিল chart-এর চেয়ে ভালো?

১৯৭৩ সালে Francis Anscombe “Graphs in Statistical Analysis” প্রবন্ধে চারটি ডেটাসেট প্রকাশ করেন, প্রতিটিতে ১১টি (x, y) জোড়া। চারটির x-এর গড় ৯, y-এর গড় প্রায় ৭.৫, correlation প্রায় ০.৮২, least squares রেখাও প্রায় একই; সারাংশের টেবিলে চারটি অভিন্ন। অথচ scatter-এ একটি ছড়ানো সরলরেখা, একটি নিখুঁত বাঁক, একটি সরলরেখা যার একটি বিন্দু দূরে, আর একটিতে একটিমাত্র বিন্দু পুরো correlation বানিয়েছে। তিনি বিরোধিতা করছিলেন সেই মনোভাবের, যে হিসাব নির্ভুল আর chart শুধু অলংকার।

২০১৭ সালে Justin Matejka আর George Fitzmaurice (Autodesk Research) “Datasaurus Dozen”-এ একটি ডাইনোসর-আকৃতির বিন্দুগুচ্ছকে ধাপে ধাপে সরিয়ে এক ডজন আকৃতি বানান, যাদের গড়, standard deviation আর correlation দুই দশমিক পর্যন্ত একই। AI-এর কাজে শিক্ষা সরাসরি: model-এর আগে ডেটা দেখা (পর্ব ৬২-এর EDA), কারণ একটি সংখ্যার মেট্রিক আকৃতির ভুল ধরে না।

তবু টেবিলের জায়গা আছে। Chart নির্দিষ্ট মান পড়তে দুর্বল: bar দেখে “খুলনায় ১৮৭ না ১৯২” বলা যায় না। হিসাবের প্রতিবেদন, অল্প সংখ্যা, ভিন্ন এককের কলাম বা মান খুঁজে কাজে লাগানোর ক্ষেত্রে টেবিল সৎ আর দ্রুত। ক্রিকেটের স্কোরকার্ড টেবিল, worm chart গ্রাফ; দুটিই টিকে আছে কারণ দুটি ভিন্ন প্রশ্নের উত্তর দেয়।

মূল উপলব্ধি: সারাংশ সংখ্যা আকৃতি লুকায়, তাই আগে দেখুন; কিন্তু মান খোঁজা আর নির্ভুলতার কাজে টেবিল chart-এর চেয়ে ভালো, আর প্রশ্নই ঠিক করে কোনটি লাগবে।

প্র ০৫Small multiples ধারণা কোথা থেকে এল, আর panel-গুলোর axis এক রাখা না রাখার সিদ্ধান্ত কেন এত গুরুত্বপূর্ণ?

“Small multiples” নামটি সাধারণত Edward Tufte-র সঙ্গে যুক্ত; “The Visual Display of Quantitative Information” (১৯৮৩) আর “Envisioning Information” (১৯৯০) বইয়ে তিনি একই নকশার ছোট ছবির সারিকে তুলনার সবচেয়ে কার্যকর উপায়গুলোর একটি বলেন। ধারণাটি পুরোনো: ১৮৭৮ সালে Eadweard Muybridge দৌড়ন্ত ঘোড়ার ধারাবাহিক ছবিতে দেখান যে এক মুহূর্তে চারটি পা-ই মাটি ছাড়ে; সেই সারি আসলে সময়ের small multiples.

শক্তি হলো, পাঠক একটি panel পড়তে শিখলে বাকিগুলো বিনা পরিশ্রমে পড়েন: axis, রং আর চিহ্ন একই, বদলায় শুধু ডেটা, legend-এ যাতায়াত নেই। দাম হলো, ছবি ২৯.১-এর মতো আলাদা স্কেলে অবস্থান তুলনা এক ধাপ কম নির্ভুল।

Axis-এর সিদ্ধান্ত গল্প বদলে দেয়। সব panel-এ একই y (sharey=True) দিলে আকারের তুলনা সৎ, কিন্তু ছোট দলের রেখা চ্যাপ্টা: বাজারঘরের run-এ সিলেটের রেখা ঢাকার ১,০০০-এর স্কেলে প্রায় সমতল হতো, আর রাজশাহীর ৭১% বৃদ্ধি চোখে পড়ত না। আলাদা y-এ আকৃতি স্পষ্ট, কিন্তু পাঠক ভুল করে উচ্চতা তুলনা করতে পারেন। মাঝামাঝি পথ: শতাংশ পরিবর্তনে রূপান্তর করে একই y; বিস্তারিত পর্ব ৩২-এ।

মূল উপলব্ধি: small multiples জটকে তুলনায় বদলায়, কিন্তু shared বা আলাদা axis-এর সিদ্ধান্ত ঠিক করে পাঠক আকার তুলনা করবেন না আকৃতি; প্রশ্ন অনুযায়ী বেছে নিন আর স্পষ্ট লিখে দিন।

অনুশীলন

প্রথমটি হিসাবের, দ্বিতীয়টি আউটপুট অনুমানের, তৃতীয়টি নকশা ও সমালোচনার, শেষটি কোড লেখার।

  1. হিসাব — box plot-এর পাঁচটি সংখ্যা: একটি নতুন জেলার ১১টি অর্ডারের delivery-র দিন [2, 3, 3, 4, 4, 5, 5, 6, 7, 9, 16]. NumPy-র default (linear) নিয়মে বের করুন: (ক) median, Q1, Q3 আর IQR, (খ) 1.5 × IQR নিয়মে দুই সীমা, (গ) দুই দাড়ি কোথায় শেষ, (ঘ) কোন মান outlier, (ঙ) গড়। আর (চ) ২০০টি অর্ডারে Sturges-এর নিয়মে bin কয়টি?

    (ক) n = 11, median ৬ নম্বর মান = 5. Q1-এর অবস্থান 0.25 × 10 = 2.5 (০ থেকে গোনা), মানে ৩ আর ৪-এর মাঝামাঝি: 3.5. Q3-এর অবস্থান 7.5, মানে ৬ আর ৭-এর মাঝামাঝি: 6.5. IQR = 6.5 − 3.5 = 3. (খ) নিচে 3.5 − 4.5 = −1, ওপরে 6.5 + 4.5 = 11. (গ) নিচের দাড়ি সীমার ভেতরের সবচেয়ে ছোট মান 2-এ, ওপরের দাড়ি 11-এর নিচের সবচেয়ে বড় মান 9-এ। (ঘ) শুধু 16. (ঙ) যোগফল 64, গড় 64 / 11 ≈ 5.82, median-এর ডানে, কারণ 16 গড়কে টেনেছে। (চ) log2 200 ≈ 7.64, ⌈7.64⌉ + 1 = 9.

    আমাদের ডেস্কটপ run-এ (NumPy 2.5.4) np.percentile(p, [25, 50, 75]) দিয়েছে [3.5 5. 6.5] আর গড় 5.818…, হাতের হিসাবের সঙ্গে মিলেছে।

  2. আউটপুট অনুমান করুন: চালানোর আগে দুটি লাইনের counts আর edges লিখুন, তারপর import numpy as np দিয়ে চালিয়ে মেলান।
    d = [1, 1, 2, 2, 2, 3, 7]
    print(np.histogram(d, bins=[0.5, 1.5, 2.5, 3.5, 7.5]))
    print(np.histogram(d, bins=3))
    (array([2, 3, 1, 1]), array([0.5, 1.5, 2.5, 3.5, 7.5]))
    (array([5, 1, 1]), array([1., 3., 5., 7.]))

    প্রথম লাইনে নিজের দেওয়া সীমা: ১ দুটি, ২ তিনটি, ৩ একটি, আর ৩.৫ থেকে ৭.৫-এর চওড়া খোপে ৭ একটি। দ্বিতীয় লাইনে NumPy সর্বনিম্ন ১ থেকে সর্বোচ্চ ৭ পর্যন্ত তিনটি সমান খোপ বানায়, width 2: [1, 3)-এ ১, ১, ২, ২, ২ মিলিয়ে পাঁচটি, [3, 5)-এ ৩, আর শেষ খোপ [5, 7] দুই দিকেই বন্ধ, তাই ৭ সেখানে। আমাদের ডেস্কটপ run-এ ঠিক এই আউটপুট এসেছে। লক্ষ করুন, দ্বিতীয় histogram-এ ১ আর ২ দিনের পার্থক্য পুরো মুছে গেছে, আর প্রথমটিতে শেষ খোপ অন্যদের চেয়ে চার গুণ চওড়া: অসমান খোপে bar-এর উচ্চতা তুলনা বিভ্রান্তিকর, তখন density=True লাগে।

  3. নকশা ও সমালোচনা: বাজারঘরের একটি মাসিক ড্যাশবোর্ডে আছে: (১) ৯টি পণ্য-বিভাগের বিক্রির ভাগের 3D pie; (২) পাঁচ জেলার গড় rating-এর bar, y-axis 3.8 থেকে, জেলা বর্ণমালার ক্রমে; (৩) ২০,০০০ অর্ডারের দাম বনাম rating-এর নিরেট scatter, linear axis; (৪) ১২টি পণ্যের দৈনিক বিক্রির ১২ রঙের রেখা এক ছবিতে। প্রতিটির সমস্যা বলুন আর একটি ভালো বিকল্প দিন।
    • (১) 3D pie, ৯ ভাগ: কোণ আর ক্ষেত্রফল দুর্বল encoding, ৯টি ভাগ কাছাকাছি হলে ক্রম পড়া যায় না, আর 3D দৃষ্টিকোণ সামনের টুকরো বড় দেখায়। বিকল্প: শতাংশের সাজানো আড়াআড়ি bar, শূন্য থেকে; দরকার হলে ছোট ভাগগুলো “Other”-এ মিলিয়ে।
    • (২) কাটা axis আর বর্ণমালার ক্রম: 3.8 থেকে শুরু করলে 4.0 আর 4.2-এর bar দৈর্ঘ্যে দ্বিগুণ দেখায়, পার্থক্য ৫%. বিকল্প: মান অনুযায়ী সাজানো dot plot (axis 3.8 থেকে চলে, কারণ অবস্থান), বা শূন্য থেকে bar; আর rating ordinal বলে প্রতিটি জেলায় ১–৫-এর ভাগের 100% bar আরও সৎ।
    • (৩) ২০,০০০ নিরেট বিন্দু: overplotting, আর rating মাত্র পাঁচটি মান, তাই পাঁচটি মোটা রেখা; দাম skewed বলে সব বাঁ কোণে। বিকল্প: log x-এ প্রতিটি rating-এর জন্য দামের box plot, বা hexbin; অথবা দামকে কয়েকটি খোপে ভাগ করে প্রতিটি খোপের rating-এর ভাগ।
    • (৪) ১২ রঙের spaghetti: ১২টি আলাদা করে চেনার মতো রং নেই, রেখা জড়ায়, আর দৈনিক ঝাঁকুনি প্রবণতা ঢাকে। বিকল্প: ৩ × ৪-এর small multiples, প্রতিটিতে হালকা দৈনিক রেখা আর গাঢ় ৭ দিনের চলমান গড়; বা সব ধূসর আর গল্পের ২টি পণ্য রঙিন।

    প্রতিটির আগে প্রশ্নটি লিখলে (“কোন বিভাগ সবচেয়ে বড়?”, “কোন পণ্যের বিক্রি বাড়ছে?”) বিকল্প chart প্রায় নিজেই আসে।

  4. কোড লিখুন — দুই দলের ECDF: অংশ ৪-এর কোডের মতো seed 42 দিয়ে dhaka = rng.poisson(1.0, 360) + 1 আর outside = rng.poisson(1.5, 240) + 4 বানান। একই axes-এ দুটি ECDF আঁকুন, y = 0.9-এ একটি রেখা দিন, আর প্রতিটি দলের median, ৯০% অর্ডার কত দিনে পৌঁছায়, আর ৩ দিনের মধ্যে পৌঁছানোর শতাংশ ছাপুন।
    rng = np.random.default_rng(42)
    dhaka = rng.poisson(1.0, 360) + 1
    outside = rng.poisson(1.5, 240) + 4
    fig, ax = plt.subplots(figsize=(6, 3.5), dpi=100)
    for name, d in [("Inside Dhaka", dhaka), ("Outside Dhaka", outside)]:
        ax.ecdf(d, label=name)
        print(name, "| median:", np.median(d), "| 90% within:",
              np.percentile(d, 90), "days | within 3 days:",
              round((d <= 3).mean() * 100, 1), "%")
    ax.axhline(0.9, ls=":", color="gray")
    ax.set(xlabel="Delivery days", ylabel="Share of orders delivered",
           title="Delivery time, inside vs outside Dhaka")
    ax.legend(); fig.tight_layout(); plt.show()

    আমাদের ডেস্কটপ run-এর আউটপুট (matplotlib 3.11.2, NumPy 2.5.4):

    Inside Dhaka | median: 2.0 | 90% within: 3.0 days | within 3 days: 90.6 %
    Outside Dhaka | median: 5.0 | 90% within: 7.0 days | within 3 days: 0.0 %

    ঢাকার ভেতরে median ২ দিন, ৯০% অর্ডার ৩ দিনে; বাইরে median ৫ দিন, ৯০% অর্ডার ৭ দিনে, আর ৩ দিনের মধ্যে একটিও নয় (কোডে বাইরের সব অর্ডার কমপক্ষে ৪ দিন, এটি বানানো ডেটার বৈশিষ্ট্য, বাস্তবের দাবি নয়)। ছবিতে বাইরের রেখা পুরোটাই ডানে সরানো: “ঢাকার বাইরে প্রায় ৩ দিন বেশি” কথাটি median আর ৯০% দুই জায়গাতেই সত্য। এটিই অংশ ১-এর stepper-এর প্রশ্নের উত্তর, আর এই ছবির শিরোনামেও সেটি লেখা যেত।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি ব্যবসার প্রশ্ন থেকে একটি সৎ chart পর্যন্ত পৌঁছানোর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ❓ প্রশ্নটি এক বাক্যে লেখা
  2. 🏷️ দরকারি কলাম আর তাদের ধরন (nominal, ordinal, numeric, সময়) চেনা
  3. 🎯 কাজের পরিবার ঠিক করা: distribution, তুলনা, সম্পর্ক, অংশ বা সময়
  4. 📊 সবচেয়ে জরুরি তুলনাটি অবস্থানে রেখে chart বাছাই
  5. 🔍 bin width, axis-এর শুরু, scale আর সারির সংখ্যা যাচাই
  6. ✍️ শিরোনামে উত্তরটি বাক্য হিসেবে লেখা

Cleveland ও McGill-এর (১৯৮৪) graphical perception-এর ক্রম অনুযায়ী দুটি মানের অনুপাত সবচেয়ে নির্ভুলভাবে আন্দাজ করা যায় কোন encoding-এ?

  • pie-র টুকরোর কোণ
  • একই স্কেলে অবস্থান
  • বৃত্তের ক্ষেত্রফল
  • রঙের গাঢ়তা

একটি ডেটায় Q1 = 2 দিন আর Q3 = 5 দিন। Tukey-র 1.5 × IQR নিয়মে কোন মানটি outlier হিসেবে আলাদা বিন্দুতে আঁকা হবে?

  • 1
  • 9
  • 10
  • 5

পাঁচটি জেলার গড় rating 4.0, 4.05, 4.1, 4.15 আর 4.2. একটি bar chart-এর y-axis 3.95 থেকে শুরু করলে মূল সমস্যা কী?

  • bar-এর দৈর্ঘ্যের অনুপাত আসল অনুপাতকে অনেক বাড়িয়ে দেখায়
  • matplotlib error দেয়
  • rating-এর bar আঁকাই যায় না
  • পার্থক্যগুলো ছোট দেখায়

৩,০০০ অর্ডারের দাম (খুব skewed) বনাম delivery-র দিনের (পূর্ণসংখ্যা) scatter একটি নিরেট দাগ হয়ে গেছে। কোন সমাধানটি সবচেয়ে কাজের?

  • marker আরও বড় করা
  • দাম আর দিনের pie chart
  • 3D scatter
  • log x-axis-এ hexbin (বা ছোট marker, alpha আর jitter)

১,০০০ অর্ডারের পাঁচটি পেমেন্ট পদ্ধতির ভাগ 23.1%, 21.4%, 19.8%, 18.7% আর 17.0%. পাঠক যেন সহজে ক্রম আর পার্থক্য পড়তে পারেন, তার জন্য কোন chart সবচেয়ে উপযুক্ত?

  • pie chart
  • বড় থেকে ছোট সাজানো আড়াআড়ি bar
  • donut chart
  • line chart

পাঁচ জেলার ২৪ মাসের বিক্রি; একটি জেলা অন্যদের চেয়ে ছয় গুণ বড়। প্রশ্ন “প্রতিটি জেলার বিক্রি কীভাবে বদলাচ্ছে?”. কোনটি সবচেয়ে ভালো?

  • এক ছবিতে পাঁচটি রঙিন রেখা
  • প্রতি মাসের stacked bar
  • small multiples: পাঁচটি ছোট line chart, প্রতিটি নিজের y-axis-এ
  • ২৪ মাসের মোটের একটি pie

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রশ্ন → ডেটার ধরন → কাজের পরিবার → chart; পাঁচ পরিবার: distribution, তুলনা, সম্পর্ক, অংশ, সময়। শিরোনামে উত্তরটি লিখুন।
  2. সবচেয়ে জরুরি তুলনা একই স্কেলের অবস্থানে; দৈর্ঘ্য, কোণ, ক্ষেত্রফল আর রঙের গাঢ়তা ক্রমে দুর্বল (Cleveland ও McGill, ১৯৮৪)। রং দল চেনাতে।
  3. Histogram-এ দুই-তিনটি bin width দেখুন; integer ডেটায় খোপের সীমা আধা মানে। Box plot দুই চূড়া লুকায়; ECDF কিছু লুকায় না; অল্প ডেটায় strip.
  4. Bar শূন্য থেকে, মান অনুযায়ী সাজানো, লম্বা নামে আড়াআড়ি; কাছাকাছি মানে dot plot; কাছাকাছি পাঁচ ভাগে pie নয়, সাজানো bar.
  5. বড় scatter-এ alpha, jitter, hexbin; skewed অক্ষে log. অনেক দলের সময়-রেখায় small multiples, আর axis share করার সিদ্ধান্ত প্রশ্ন অনুযায়ী; অল্প সঠিক সংখ্যায় টেবিল।

পরের পর্বে: seaborn — DataFrame থেকে সরাসরি পরিসংখ্যানের chart: এক লাইনে দলে দলে histogram আর box plot, violin, KDE, আর এই পর্বের small multiples seaborn-এর facet দিয়ে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান