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

Data storytelling — বিশ্লেষণ থেকে সিদ্ধান্ত আর report

Data Storytelling — From Analysis to Decision and Report

Taught in Bangla, this lesson turns a simulated delivery analysis into a decision: answer-first writing with the pyramid principle and situation–complication–resolution, a default chart redrawn with a message title, misleading truncated, cherry-picked and dual-axis charts measured in numbers, uncertainty shown as intervals and natural frequencies, colour-blind checks, and a one-page memo whose numbers code recomputes and asserts, from real runs.

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

  • বিশ্লেষণের আগে সিদ্ধান্তের প্রশ্ন, শ্রোতা আর “তাহলে কী?” ঠিক করা
  • Pyramid principle আর situation–complication–resolution দিয়ে answer-first report আর slide সাজানো
  • একটি chart দুইবার আঁকা: জঞ্জালভরা default থেকে বার্তাসহ পরিষ্কার chart, আর বিভ্রান্তিকর chart সংখ্যা দিয়ে ধরা
  • অ-প্রযুক্তিগত পাঠককে interval, পরিসর আর গোনা সংখ্যায় অনিশ্চয়তা দেখানো; রঙকানা-বান্ধব রং আর alt text
  • এক পাতার decision memo লেখা, যার প্রতিটি সংখ্যা কোড আবার হিসাব করে যাচাই করে; AI-এর সারাংশে কারণের বাড়াবাড়ি ধরা

মডিউল ১২-এর আগের পর্বগুলো বিশ্লেষণের হাতিয়ার দিয়েছে: পর্ব ৬১ (Data science-এর জন্য SQL), পর্ব ৬২ আর ৬৩ (A/B test-এর পরিকল্পনা আর বিশ্লেষণ), পর্ব ৬৪ (Causal inference-এর পরিচয়), পর্ব ৬৫ (Customer analytics) আর পর্ব ৬৬ (Dashboard). এই পর্ব শেষ ধাপের: ফলটিকে এমনভাবে বলা, যাতে সঠিক মানুষ সঠিক সিদ্ধান্ত নিতে পারেন। Chart বাছাইয়ের মূল নিয়ম AI-এর ভিত্তি কোর্সের পর্ব ২৯-এ, আর lie factor, truncated axis, রঙকানা দৃষ্টির simulation ও action title একই কোর্সের পর্ব ৩২-এ শেখানো হয়েছে; এখানে সেগুলো আবার শেখানো হবে না, বরং একটি আসল সিদ্ধান্তের কাজে লাগানো হবে। Confidence interval-এর অর্থ AI-এর ভিত্তি কোর্সের পর্ব ৩৭-এ আছে।

চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python: NumPy, pandas, matplotlib); প্রতিটি ব্লক আলাদা, যেকোনোটি আগে চালাতে পারেন। প্রথমবার Python আর package নামতে কয়েক সেকেন্ড লাগে।

১ · প্রশ্ন, শ্রোতা আর “তাহলে কী?” — বিশ্লেষণ থেকে সিদ্ধান্ত

ডাক্তারের চেম্বারের কথা ভাবুন। রক্ত পরীক্ষার রিপোর্টে বিশটি সংখ্যা থাকে। ভালো ডাক্তার রোগীকে বিশটি সংখ্যা পড়ে শোনান না; বলেন, “আপনার চিনির মাত্রা বেশি, ভাত কমাতে হবে, তিন মাস পর আবার পরীক্ষা।” সংখ্যাগুলো ফাইলে থাকে, যাতে আরেকজন ডাক্তার মিলিয়ে দেখতে পারেন। Data storytelling ঠিক এটাই: আগে সিদ্ধান্ত আর তার কারণ, পরে প্রমাণ, আর পুরো হিসাব যাচাইয়ের জন্য আলাদা করে রাখা।

একটি বিশ্লেষণ শুরু হয় প্রায়ই অস্পষ্ট অনুরোধে: “delivery-র ডেটাটা একটু দেখুন তো।” এই পর্বের উদাহরণে বাজারঘর (কাল্পনিক অনলাইন দোকান) সপ্তাহ ৯ থেকে ঢাকার বাইরের delivery fee-র নিয়ম বদলেছে। কয়েক সপ্তাহ পর অভিযোগ আসে, সিলেটে পার্সেল দেরিতে পৌঁছাচ্ছে। “ডেটা দেখা” কোনো প্রশ্ন নয়। কাজের প্রশ্নটি পর্ব ০২-এর মতো একটি সিদ্ধান্ত থেকে আসে: সিলেটে courier-দের মধ্যে অর্ডার ভাগের নিয়ম কি বদলানো উচিত, আর কতটা?

প্রশ্নের পরেই শ্রোতা। একই ফল চারজন চারভাবে চান:

শ্রোতাতিনি যা জানতে চানকী দেবেন
Operations প্রধানকী করব, কবে, ঝুঁকি কীএক পাতার memo, প্রথম বাক্যে সুপারিশ
অর্থ বিভাগখরচ আর ফেরতের টাকায় প্রভাবসংখ্যার টেবিল, অনুমানের পরিসরসহ
Courier-এর সঙ্গে সম্পর্ক দেখেন যিনিকার দোষ বলা হচ্ছে, তা ন্যায্য কি নাcourier-ভিত্তিক তুলনা, সতর্ক ভাষায়
Data দলপদ্ধতি ঠিক কি না, আবার চালানো যায় কি নাappendix, কোড, ডেটার snapshot

তৃতীয় প্রশ্নটি সবচেয়ে কঠিন: “তাহলে কী?” (so what). “সিলেটে দেরির হার ১৬.৮%” একটি তথ্য। “সিলেটে দেরি ফি বদলের পর আড়াই গুণ হয়েছে, অন্য জেলায় হয়নি, আর বাড়তি অর্ডারের বেশির ভাগ গেছে একটি courier-এর কাছে” একটি পর্যবেক্ষণ। “তাই সিলেটের অর্ডার ভাগের নিয়ম চার সপ্তাহের পাইলটে বদলে দেখা উচিত” একটি সুপারিশ। প্রতিটি বাক্যের পর “তাহলে কী?” জিজ্ঞেস করুন, যতক্ষণ না একটি কাজের কথা আসে।

ডেটা — ১৬ সপ্তাহের delivery

নিচের deliveries.py seed দেওয়া কোডে ১৬ সপ্তাহের প্রতিটি delivery বানায়: সপ্তাহ, জেলা (পাঁচটি), courier (A বা B) আর দেরি হলো কি না। ডেটা কাল্পনিক; জেলার নাম শুধু পরিচিত প্রেক্ষাপটের জন্য, কোনো বাস্তব courier বা জেলার পরিসংখ্যান নয়। Simulation-এ একটি জিনিস লুকানো আছে, যা বিশ্লেষককে খুঁজে বের করতে হবে: সপ্তাহ ৯ থেকে সিলেটে অর্ডার বাড়ে, আর বাড়তি ভার পড়ে Courier B-র ওপর।

DIST = ["Dhaka", "Chattogram", "Sylhet", "Rajshahi", "Khulna"]
BASE = [0.06, 0.07, 0.07, 0.08, 0.07]        # জেলাভেদে দেরির মূল হার

def make_deliveries(seed=67):
    r = np.random.default_rng(seed)
    parts = []
    for w in range(1, 17):
        after = w >= 9                           # সপ্তাহ 9 থেকে নতুন delivery fee
        for d, base in zip(DIST, BASE):
            syl = after and d == "Sylhet"
            n = int(r.poisson(260 if d == "Dhaka" else (170 if syl else 120)))
            cB = r.random(n) < (0.75 if syl else 0.40)          # Courier B-এর ভাগ
            p = base + 0.01 * cB + 0.10 * (cB & syl)            # সিলেটে B চাপে পড়ে
            parts.append(pd.DataFrame({"week": w, "district": d,
                                       "courier": np.where(cB, "B", "A"),
                                       "late": (r.random(n) < p).astype(int)}))
    return pd.concat(parts, ignore_index=True)

সব মিলিয়ে ১২,১৮৬টি delivery, যার ১,০১৮টি দেরিতে। বাস্তবে আপনি simulation-এর নিয়ম জানবেন না, শুধু টেবিলটি জানবেন।

২ · Answer first — pyramid principle আর situation–complication–resolution

বিশ্লেষক যে ক্রমে কাজ করেন (ডেটা → হিসাব → ফল → সুপারিশ), সেই ক্রমে লিখলে ব্যস্ত পাঠক সুপারিশ পর্যন্ত পৌঁছান না। তাই উল্টো ক্রম: আগে উত্তর, তারপর তার পক্ষের যুক্তি, তারপর প্রমাণ। সামরিক লেখায় এর নাম BLUF (bottom line up front).

এই ভাবনার সবচেয়ে পরিচিত রূপ Barbara Minto-র pyramid principle. তিনি McKinsey-তে লেখার প্রশিক্ষণ দিতে গিয়ে এটি গড়ে তোলেন, আর তাঁর The Pyramid Principle বইটি কয়েক দশক ধরে নানা সংস্করণে ছাপা হচ্ছে। নিয়ম তিনটি: চূড়ায় একটি মূল বার্তা; তার নিচে তিন-চারটি মূল যুক্তি, যারা একসঙ্গে বার্তাটিকে সমর্থন করে; প্রতিটি যুক্তির নিচে তার প্রমাণ।

ভূমিকা লেখার জন্য Minto একটি ছোট কাঠামো দেন: situation–complication–resolution (SCR). Situation: পাঠক যা আগে থেকে জানেন ও মানেন (“সপ্তাহ ৯ থেকে নতুন delivery fee চালু”)। Complication: যা বদলেছে বা সমস্যা তৈরি করেছে (“তারপর থেকে সিলেটে দেরি আড়াই গুণ”)। Resolution: আপনার উত্তর (“অর্ডার ভাগের নিয়ম বদলে চার সপ্তাহের পাইলট”)।

Pyramid আর SCR — একটি memo-র কঙ্কাল

চূড়ায় উত্তর, মাঝে যুক্তি, নিচে প্রমাণ; ভূমিকায় S → C → R

Pyramid principle and situation-complication-resolution উত্তর (সুপারিশ) সিলেটে ভাগ বদলে ৪ সপ্তাহের পাইলট কী ঘটেছে কোথায়, কেন কী করব chart আর পরিসর courier-ভিত্তিক হার খরচ আর ঝুঁকি ভূমিকার ক্রম Situation Complication Resolution যা জানা → যা বদলেছে → উত্তর
ছবি ২.১ — পাঠক ওপর থেকে পড়েন আর যেখানে খুশি থামেন। শুধু চূড়া পড়লেও সিদ্ধান্ত জানেন; মাঝের স্তর পড়লে কারণ জানেন; নিচের স্তর তাঁদের জন্য, যাঁরা যাচাই করতে চান।

নিচের stepper একটি সাধারণ “পদ্ধতি-আগে” বাক্যকে ধাপে ধাপে answer-first শিরোনামে বদলায়। প্রতিটি সংখ্যা অংশ ৬-এর কোড থেকে আসা।

প্রতিটি ধাপে লেখাটি আর পরিবর্তনের কারণ
ধাপ
লেখা
কেন
  1. শুরুর খসড়া। বিশ্লেষকের প্রথম বাক্য প্রায়ই পদ্ধতির বর্ণনা। এটি সত্য, কিন্তু পাঠক এখনও জানেন না কেন পড়বেন।
  2. তাহলে কী? প্রথম আসল খবর: সিলেটে দেরির হার ৬.৬% থেকে ১৬.৮%. আগে আর পরে দুটি সংখ্যাই দিন, শুধু “বেড়েছে” নয়।
  3. কোথায়? তুলনা ছাড়া একটি বৃদ্ধি কোনো সাধারণ ঢেউও হতে পারে। বাকি চার জেলায় ৭.৫% থেকে ৭.৪%: সমস্যাটি সিলেটে সীমিত।
  4. কেন হতে পারে? সিলেটে Courier B-র ভাগ ৩৯% থেকে ৭৫%, আর ফি বদলের পরে B-র দেরি ১৯.৮%, A-র ৭.৯%. এটি একটি সম্ভাব্য প্রক্রিয়া; আগে-পরের তুলনা কারণ প্রমাণ করে না (অংশ ৮)।
  5. কী করব? সুপারিশটি একটি পরীক্ষা: B-র ভাগ কমিয়ে চার সপ্তাহের পাইলট, আর আগে থেকে লেখা সফলতার মাপ (দেরি ১০%-এর নিচে)।
  6. শিরোনাম। শেষ বাক্যটি একাই memo-র কাজ করে: কী ঘটেছে আর কী চাই। বাকি সব এর নিচে প্রমাণ।

Slide deck-এও একই নিয়ম: প্রতিটি slide-এর শিরোনাম একটি পূর্ণ বাক্য, যা ওই slide-এর বার্তা বলে। শুধু শিরোনামগুলো পরপর পড়লেই পুরো গল্প বোঝা গেলে deck-টি ঠিক আছে। শুরুতে একটি executive summary slide, শেষে সুপারিশ আর পরের ধাপ, আর পদ্ধতির খুঁটিনাটি appendix-এ।

৩ · একটি chart দুইবার আঁকা — default থেকে বার্তাসহ chart

AI-এর ভিত্তি কোর্সের পর্ব ৩২-এ দেখেছেন, action title, direct labeling আর অপ্রয়োজনীয় সাজ (chartjunk) বাদ দিলে chart পড়া সহজ হয়। সিদ্ধান্তের report-এ আরেকটি প্রশ্ন জরুরি: এই chart কোন যুক্তির প্রমাণ? Pyramid-এর “কী ঘটেছে” স্তরের প্রমাণ হলো সিলেট বনাম বাকিদের তুলনা, ফি বদলের আগে ও পরে। তাই chart-এ ঠিক এই তুলনাটিই চোখে পড়তে হবে, অন্য কিছু নয়।

নিচের ব্লক প্রথমে pandas-এর default chart আঁকে: পাঁচ জেলা, পাঁচ রং, legend, grid, শিরোনামে শুধু বিষয়। তারপর একই ডেটায় পরিষ্কার chart: চার জেলাকে এক ধূসর রেখায় মেলানো (তারা একই রকম আচরণ করে, টেবিলে দেখা যায়), সিলেট এক উজ্জ্বল রঙে, রেখার শেষে নাম, দুই সময়ের গড়ের বিন্দুরেখা, ফি বদলের সপ্তাহে একটি দাগ আর annotation, আর শিরোনামে বার্তা।

import pandas as pd                 # deliveries.py-এর জন্য
import matplotlib.pyplot as plt
from deliveries import make_deliveries

df = make_deliveries()
wk = df.pivot_table(index="week", columns="district", values="late") * 100
print(wk.loc[[1, 8, 9, 16]].round(1))                  # কয়েকটি সপ্তাহ, % হিসেবে

# আগে: default chart — পাঁচ রং, legend, grid, কোনো বার্তা নেই
fig1, ax = plt.subplots(figsize=(6.4, 3.6))
wk.plot(ax=ax, marker="o", grid=True)
ax.set(title="Late delivery rate by district", xlabel="week", ylabel="late")

# পরে: সিলেট বনাম বাকি চার জেলা একসঙ্গে, সরাসরি label, শিরোনামে বার্তা
syl = df.district == "Sylhet"
two = {"Sylhet": df[syl].groupby("week").late.mean() * 100,
       "other 4 districts": df[~syl].groupby("week").late.mean() * 100}
fig2, ax = plt.subplots(figsize=(6.4, 3.6))
for (name, s), c in zip(two.items(), ["#D55E00", "#808080"]):
    ax.plot(s.index, s, color=c, lw=2.6 if name == "Sylhet" else 1.6)
    ax.text(16.3, s.iloc[-1], name, va="center", color=c, fontsize=10)
    for lo, hi in [(1, 8), (9, 16)]:                   # দুই সময়ের গড় রেখা
        m = s.loc[lo:hi].mean()
        ax.hlines(m, lo, hi, color=c, ls=":", lw=1)
        print(f"{name:18s} সপ্তাহ {lo:2d}-{hi:2d}: গড় {m:4.1f}%")
ax.axvline(8.5, color="#404040", ls="--", lw=1)
ax.text(8.7, 22, "new delivery fee from week 9", fontsize=9, va="top")
ax.spines[["top", "right"]].set_visible(False)
ax.set(xlim=(1, 20), ylim=(0, 22), xticks=[1, 4, 8, 12, 16], xlabel="week",
       ylabel="late deliveries (%)")
ax.set_title("Late deliveries in Sylhet rose from about 7% to 17%\n"
             "after the fee change; other districts stayed near 7%",
             loc="left", fontsize=11)

আসল আউটপুট (ব্রাউজারের Python; দুটি ছবি এর নিচে আঁকা হয়):

district  Chattogram  Dhaka  Khulna  Rajshahi  Sylhet
week
1                6.4    4.7     6.0       7.1     5.8
8                7.7    8.9     8.9       6.0     3.1
9                8.7    4.5     9.6       5.8    21.1
16              10.2    6.5     8.2      10.5    16.8
Sylhet             সপ্তাহ  1- 8: গড়  6.6%
Sylhet             সপ্তাহ  9-16: গড় 16.7%
other 4 districts  সপ্তাহ  1- 8: গড়  7.5%
other 4 districts  সপ্তাহ  9-16: গড়  7.4%

প্রথম ছবিতে পাঁচটি রেখা একে অপরকে কাটে; সপ্তাহ ৫-এ রাজশাহীর ১৪% বা সপ্তাহ ৮-এ সিলেটের ৩.১% চোখ টানে, যা আসলে সাপ্তাহিক এলোমেলো ওঠানামা। দ্বিতীয় ছবিতে চোখ প্রথমেই যায় সপ্তাহ ৯-এর লাফে, আর গড়ের বিন্দুরেখা দুটি সংখ্যাকে আকার দেয়: সিলেটে ৬.৬% থেকে ১৬.৭%, বাকিদের ৭.৫% থেকে ৭.৪%. শিরোনাম একাই বার্তাটি বলে।

একটি সূক্ষ্ম পার্থক্য: এখানে সিলেটের পরের গড় ১৬.৭%, অথচ TL;DR আর memo-তে ১৬.৮%. দুটিই ঠিক, কিন্তু আলাদা জিনিস: এখানে আটটি সাপ্তাহিক হারের গড়, memo-তে আট সপ্তাহের সব অর্ডার একসঙ্গে ধরে হার (২২০ ÷ ১,৩১০)। সপ্তাহভেদে অর্ডারের সংখ্যা আলাদা বলে দুটি মেলে না। Report-এ একটি সংজ্ঞা বেছে সবখানে সেটিই ব্যবহার করুন আর appendix-এ লিখে দিন।

চার জেলা মেলানো কি ঠিক হলো? একত্র করা (aggregation) পার্থক্য লুকাতে পারে, যেমন Simpson-এর paradox. এখানে প্রথম টেবিল আর default chart দেখায়, চারটি জেলা একই সীমার মধ্যে ওঠানামা করে, তাই মেলানো যুক্তিসংগত। মেলানোর আগে আলাদা করে দেখুন, আর সিদ্ধান্তটি appendix-এ লিখুন। আলাদা আলাদা জেলা দেখাতে চাইলে পাঁচটি ছোট chart পাশাপাশি (small multiples) এক chart-এ পাঁচ রঙের চেয়ে ভালো।

৪ · একই ডেটা, তিনটি ভুল গল্প — chart দেখে সিদ্ধান্ত কীভাবে বদলায়

AI-এর ভিত্তি কোর্সের পর্ব ৩২-এ truncated axis, dual axis, বেছে নেওয়া সময়ের জানালা (cherry-picking) আর Tufte-এর lie factor এসেছে। সিদ্ধান্তের প্রেক্ষাপটে প্রশ্নটি একটু আলাদা: আপনার হাতে অন্য কারও বানানো একটি slide deck, আর আপনাকে বলতে হবে কোন chart কোন ভুল সিদ্ধান্তের দিকে ঠেলছে। নিচের ব্লক বাজারঘরের একই ডেটা তিন জোড়া chart-এ আঁকে, প্রতিটি জোড়ায় বাঁয়ে বিভ্রান্তিকর আর ডানে সৎ রূপ, আর ছবির ধারণা কতটা বদলায় তা সংখ্যায় ছাপে।

import pandas as pd                 # deliveries.py-এর জন্য
import matplotlib.pyplot as plt
from deliveries import make_deliveries

df = make_deliveries()
r = df[df.week <= 8].groupby("courier").late.mean() * 100      # A বনাম B, সপ্তাহ 1-8
s = df[df.district == "Sylhet"].groupby("week").late.mean() * 100
t = df.groupby("week").agg(orders=("late", "size"), late=("late", "mean"))
fig, ax = plt.subplots(3, 2, figsize=(8, 9.5))
fig.subplots_adjust(hspace=0.6, wspace=0.45)
for a, lo, name in [(ax[0, 0], 6, "Truncated y-axis"), (ax[0, 1], 0, "Honest: starts at 0")]:
    a.bar(r.index, r, color=["#0072B2", "#E69F00"])
    a.set(ylim=(lo, 9), title=name, ylabel="late % (weeks 1-8)")
    print(f"{name:20s}: B-র bar A-র {(r.B - lo) / (r.A - lo):.2f} গুণ লম্বা")
print(f"আসল অনুপাত B/A = {r.B / r.A:.2f}  (A {r.A:.2f}%, B {r.B:.2f}%)")
ax[1, 0].plot(s.loc[9:11], "o-", color="#D55E00")
ax[1, 0].set(title="Cherry-picked: weeks 9-11", ylim=(0, 22), xticks=[9, 10, 11])
ax[1, 1].plot(s, "o-", color="#D55E00", ms=3)
ax[1, 1].axhline(s.loc[1:8].mean(), color="gray", ls=":")
ax[1, 1].set(title="Full window: weeks 1-16", ylim=(0, 22), xticks=[1, 8, 16])
print(f"সপ্তাহ 9→11: {s[9]:.1f}% → {s[11]:.1f}% ({s[11] / s[9] - 1:+.0%}); "
      f"কিন্তু 9-16-এর গড় {s.loc[9:].mean():.1f}%, 1-8-এর {s.loc[:8].mean():.1f}%")
a2 = ax[2, 0].twinx()                                  # দুই y-axis: স্কেল ইচ্ছেমতো
ax[2, 0].plot(t.orders, color="#0072B2")
a2.plot(t.late * 100, color="#D55E00")
ax[2, 0].set(title="Dual axes: look in step?", ylabel="orders")
a2.set(ylim=(5.5, 11), ylabel="late %")
idx = t / t.loc[1:8].mean() * 100                      # সপ্তাহ 1-8-এর গড় = 100
ax[2, 1].plot(idx.orders, color="#0072B2", label="orders")
ax[2, 1].plot(idx.late, color="#D55E00", label="late rate")
ax[2, 1].set(title="Honest: both indexed, 1 axis", ylabel="index (weeks 1-8 = 100)")
ax[2, 1].legend(frameon=False, fontsize=8)
ch = idx.loc[9:].mean() - 100
print(f"সপ্তাহ 9-16-এ গড় পরিবর্তন: orders {ch.orders:+.1f}%, late rate {ch.late:+.1f}%")

আসল আউটপুট (ব্রাউজারের Python; ছয়টি প্যানেলের একটি ছবি এর নিচে আঁকা হয়):

Truncated y-axis    : B-র bar A-র 4.98 গুণ লম্বা
Honest: starts at 0 : B-র bar A-র 1.31 গুণ লম্বা
আসল অনুপাত B/A = 1.31  (A 6.51%, B 8.52%)
সপ্তাহ 9→11: 21.1% → 12.1% (-43%); কিন্তু 9-16-এর গড় 16.7%, 1-8-এর 6.6%
সপ্তাহ 9-16-এ গড় পরিবর্তন: orders +7.2%, late rate +27.5%

Truncated axis: ফি বদলের আগে Courier B-র দেরি A-র ১.৩১ গুণ (৮.৫২% বনাম ৬.৫১%), কিন্তু ৬% থেকে শুরু হওয়া axis-এ B-র bar প্রায় ৫ গুণ লম্বা। Lie factor = ছবির আপেক্ষিক পার্থক্য ÷ ডেটার আপেক্ষিক পার্থক্য = (৪.৯৮ − ১) ÷ (১.৩১ − ১) ≈ ১৩. এমন slide দেখে কেউ B-র চুক্তি বাতিল করতে চাইতে পারেন, অথচ সমস্যাটি শুধু সিলেটে, চাপের সময়ে। Cherry-picked window: সপ্তাহ ৯ থেকে ১১-তে সিলেটের দেরি ২১.১% থেকে ১২.১%: “৪৩% কমেছে, সমস্যা নিজে থেকে মিটছে।” পুরো ১৬ সপ্তাহে দেখা যায়, সপ্তাহ ৯-এর চূড়া থেকে নামা শুধু ওঠানামা; পরের আট সপ্তাহের গড় ১৬.৭%, আগের ৬.৬%-এর আড়াই গুণ। এই slide সিদ্ধান্তকে “কিছু করার দরকার নেই”-এর দিকে ঠেলে।

Dual axes: বাঁয়ে অর্ডারের axis ৭২২ থেকে ৮১০ (মাত্র ১২%-এর পরিসর), ডানে দেরির axis ৫.৫% থেকে ১১%; দুটি স্কেল এমনভাবে বাছা যে রেখা দুটি পাশাপাশি চলে, আর মনে হয় “অর্ডার বাড়লেই দেরি বাড়ে, তাই বেশি অর্ডার নেওয়াই সমস্যা।” একই axis-এ দুটিকে সপ্তাহ ১–৮-এর গড় = ১০০ ধরে সূচক (index) করলে দেখা যায়, অর্ডার বেড়েছে গড়ে ৭.২%, দেরির হার ২৭.৫%: দেরি অর্ডারের অনুপাতে অনেক বেশি বেড়েছে, আর সেটি এসেছে মূলত একটি জেলা থেকে।

Chartমনে যে ধারণা জন্মায়ভুল সিদ্ধান্তসৎ রূপ
৬% থেকে শুরু barB পাঁচ গুণ খারাপB-কে সব জেলা থেকে বাদশূন্য থেকে bar, অথবা dot plot আর আসল অনুপাত লেখা
সপ্তাহ ৯–১১সমস্যা মিটে যাচ্ছেকিছু না করাপুরো সময়, আগের গড়ের রেখাসহ
দুই y-axisঅর্ডার বাড়লেই দেরিবিক্রি কমানো বা ফি আবার বাড়ানোএকই axis-এ সূচক, বা ওপর-নিচে দুটি প্যানেল

তিনটি chart-এর কোনো সংখ্যাই মিথ্যা নয়; বিভ্রান্তি আসে বাছাই থেকে। তাই অন্যের chart যাচাইয়ের তিন প্রশ্ন: axis কোথা থেকে শুরু? দেখানো সময়ের বাইরে কী আছে? দুটি স্কেল থাকলে কে বেছেছে আর কেন?

৫ · অ-প্রযুক্তিগত পাঠকের জন্য অনিশ্চয়তা — interval, পরিসর আর সংখ্যার শব্দ

আবহাওয়ার পূর্বাভাসে “কাল বৃষ্টি হতে পারে” শুনে কেউ ছাতা নেন, কেউ নেন না। “কাল বৃষ্টির সম্ভাবনা ৭০%” শুনলে বেশির ভাগ মানুষ একই রকম বোঝেন। Report-এর অনিশ্চয়তাও তেমন: শব্দে বললে প্রত্যেক পাঠক নিজের মতো সংখ্যা বসান; সংখ্যায় বললে সবাই একই জিনিস পড়েন।

একটি একক সংখ্যা, যেমন “+১০.২”, হলো point estimate: ডেটা থেকে সবচেয়ে ভালো অনুমান, কিন্তু নড়াচড়ার কোনো খবর নেই। পাশে confidence interval দিলে পাঠক জানেন অনুমানটি কতটা নড়তে পারে। দুটি অনুপাতের পার্থক্যের সহজ রূপে standard error:

SE = √( p১(১ − p১)/n১ + p২(১ − p২)/n২ ), ৯৫% পরিসর ≈ পার্থক্য ± ১.৯৬ × SE

এখানে p১ আর p২ আগে ও পরের দেরির হার, n১ আর n২ অর্ডারের সংখ্যা, আর ১.৯৬ normal বণ্টনের ৯৭.৫ percentile (normal approximation). পরিসরটি শুধু নমুনার এলোমেলোতা ধরে; ভুল তুলনা বা লুকানো কারণ ধরে না। পরিসর বের করার আরও পথ, যেমন bootstrap, এই কোর্সের পর্ব ২১-এ।

অ-প্রযুক্তিগত পাঠকের জন্য একই পরিসর তিনভাবে আঁকা যায়। নিচের ব্লক সিলেট আর বাকি চার জেলার পরিবর্তন তিনটি প্যানেলে দেখায়: শুধু বিন্দু, বিন্দু আর error bar, আর একটি সরল পরিসর-bar যার পাশে লেখা “+৮ থেকে +১৩”। শেষে memo-র রং দুটি পরীক্ষা করে: AI-এর ভিত্তি কোর্সের পর্ব ৩২-এর একই Machado (২০০৯) matrix দিয়ে deuteranopia (সবুজ-সংবেদী cone না থাকা) simulate করে দুটি রঙের দূরত্ব কতটা কমে, আর তাদের luminance কত।

import numpy as np
import pandas as pd                 # deliveries.py-এর জন্য
import matplotlib.pyplot as plt
from deliveries import make_deliveries

df = make_deliveries()
g = df.assign(after=df.week >= 9, syl=df.district == "Sylhet")
rows = []
for name, part in [("other 4", g[~g.syl]), ("Sylhet", g[g.syl])]:
    a, b = part[~part.after].late, part[part.after].late
    d = (b.mean() - a.mean()) * 100                    # শতাংশ-বিন্দুতে পরিবর্তন
    se = np.sqrt(b.var() / len(b) + a.var() / len(a)) * 100
    rows.append((name, d, d - 1.96 * se, d + 1.96 * se))
    print(f"{name:8s}: {d:+5.1f} pp, 95% CI [{d - 1.96 * se:+5.1f}, {d + 1.96 * se:+5.1f}]")
fig, ax = plt.subplots(1, 3, figsize=(9, 2.6), sharey=True)
for k, (name, d, lo, hi) in enumerate(rows):
    ax[0].plot(d, k, "o", color="#0072B2")
    ax[1].errorbar(d, k, xerr=[[d - lo], [hi - d]], fmt="o", color="#0072B2", capsize=4)
    ax[2].barh(k, hi - lo, left=lo, height=0.35, color="#56B4E9")
    ax[2].text(hi + 0.4, k, f"{lo:+.0f} to {hi:+.0f}", va="center", fontsize=9)
for a, t in zip(ax, ["point only", "point + 95% interval", "range bar"]):
    a.axvline(0, color="gray", lw=0.8)
    a.set(title=t, xlim=(-4, 20), yticks=[0, 1], yticklabels=["other 4", "Sylhet"])
ax[1].set_xlabel("change in late rate after the fee change (percentage points)")
def lin(h):                                            # hex রং → linear RGB
    c = np.array([int(h[i:i + 2], 16) for i in (1, 3, 5)]) / 255
    return np.where(c <= 0.04045, c / 12.92, ((c + 0.055) / 1.055) ** 2.4)
DEU = np.array([[0.367322, 0.860646, -0.227968],       # Machado et al. 2009,
                [0.280085, 0.672501, 0.047413],        # deuteranopia, severity 1.0
                [-0.011820, 0.042940, 0.968881]])
Y = np.array([0.2126, 0.7152, 0.0722])                 # আপেক্ষিক উজ্জ্বলতা (luminance)
for p, q in [("#2CA02C", "#D62728"), ("#0072B2", "#D55E00")]:
    x, y = lin(p), lin(q)
    print(f"{p} বনাম {q}: দূরত্ব {np.linalg.norm(x - y):.2f} → deuteranopia-তে "
          f"{np.linalg.norm(DEU @ x - DEU @ y):.2f}; luminance {Y @ x:.2f} বনাম {Y @ y:.2f}")

আসল আউটপুট (ব্রাউজারের Python; তিন প্যানেলের ছবি এর নিচে):

other 4 :  -0.1 pp, 95% CI [ -1.1,  +0.9]
Sylhet  : +10.2 pp, 95% CI [ +7.7, +12.8]
#2CA02C বনাম #D62728: দূরত্ব 0.73 → deuteranopia-তে 0.07; luminance 0.26 বনাম 0.16
#0072B2 বনাম #D55E00: দূরত্ব 0.80 → deuteranopia-তে 0.55; luminance 0.15 বনাম 0.22

তিন প্যানেল: শুধু বিন্দুতে বাকি চার জেলার −০.১ আর সিলেটের +১০.২ দুটিই সমান “নিশ্চিত” দেখায়। Error bar যোগ করলে দেখা যায়, চার জেলার পরিসর (−১.১ থেকে +০.৯) শূন্যকে ঘিরে আছে, আর সিলেটের পরিসর (+৭.৭ থেকে +১২.৮) শূন্য থেকে অনেক দূরে। পরিসর-bar একই কথা বলে, কিন্তু অ-প্রযুক্তিগত পাঠকের কাছে বেশি স্বাভাবিক: “বৃদ্ধি সম্ভবত ৮ থেকে ১৩ শতাংশ-বিন্দুর মধ্যে।” বিন্দু না থাকায় পাঠক একটি সংখ্যাকে অতিরিক্ত নির্ভুল ধরেন না। তবে পরিসরের কোন মান বেশি সম্ভাব্য, তা দেখায় না।

রং: matplotlib-এর পুরোনো default সবুজ (#2CA02C) আর লাল (#D62728) সাধারণ দৃষ্টিতে অনেক দূরে (০.৭৩), কিন্তু deuteranopia simulation-এ প্রায় এক (০.০৭), আর luminance-এও কাছাকাছি (০.২৬ বনাম ০.১৬)। Okabe–Ito palette-এর নীল (#0072B2) আর vermillion (#D55E00) simulation-এর পরও আলাদা থাকে (০.৫৫)। তাই এই পর্বের chart-এ সিলেট vermillion, অন্যগুলো ধূসর বা নীল। দূরত্বটি linear RGB-তে একটি সরল মাপ, চোখের অনুভূত পার্থক্যের নির্ভুল মাপ নয়।

শব্দ বনাম সংখ্যা

“সম্ভবত”, “সম্ভাবনা আছে”, “প্রায় নিশ্চিত” শব্দগুলো পাঠকভেদে খুব আলাদা সংখ্যা বোঝায়। ১৯৬৪ সালে CIA-র বিশ্লেষক Sherman Kent তাঁর প্রবন্ধ “Words of Estimative Probability”-তে দেখান, একই গোয়েন্দা রিপোর্টের “serious possibility” কথাটি সহকর্মীরা খুব ভিন্ন সম্ভাবনায় পড়েছিলেন, আর শব্দের পাশে সংখ্যার পরিসর বেঁধে দেওয়ার প্রস্তাব দেন (words of estimative probability). দুটি আরও ফাঁদ আছে। শতাংশ-বিন্দু আর শতাংশ গুলিয়ে ফেলা: ৬.৬% থেকে ১৬.৮% হলো +১০.২ শতাংশ-বিন্দু, আবার আপেক্ষিক পরিবর্তনে +১৫৬%. শুধু “১৫৬% বেড়েছে” লিখলে অনেকে ভাবেন দেরি এখন ১৫৬%-এর কাছাকাছি। আর শতাংশের বদলে গোনা সংখ্যা (natural frequency), যেমন “প্রতি ১০০ অর্ডারে ৭টি থেকে ১৭টি”, অনেক পাঠকের কাছে সহজ।

প্রতিটি ধাপে একটি খসড়া বাক্য, তার সমস্যা আর সংখ্যায় রূপ
খসড়া
সমস্যা
সংখ্যায়
  1. “অনেক”। আগে আর পরের হার, আর তাদের পার্থক্য শতাংশ-বিন্দুতে: ৬.৬% থেকে ১৬.৮%, +১০.২.
  2. আপেক্ষিক বদল একা নয়। ১৬.৮ ÷ ৬.৬ ≈ ২.৫৬, অর্থাৎ +১৫৬%. এটি নাটকীয় শোনায়, তাই সবসময় দুই আসল হারসহ লিখুন।
  3. “সম্ভবত”-র বদলে পরিসর। ৯৫% পরিসর +৭.৭ থেকে +১২.৮. পরিসরের নিচের প্রান্তও বড় বৃদ্ধি, তাই “বৃদ্ধিটা এলোমেলো ওঠানামা নয়” বলা যায়।
  4. “কোনো বদল নেই”। −০.১ আর পরিসর −১.১ থেকে +০.৯: ঠিক শূন্য প্রমাণিত হয়নি, তবে বদল থাকলেও তা এক শতাংশ-বিন্দুর কম।
  5. ভবিষ্যতের দাবি। ১২.৬% একটি সরল দৃশ্যকল্প: B-র ভাগ ৪০% আর প্রতিটি courier-এর এখনকার হার ধরে (০.৪ × ১৯.৮ + ০.৬ × ৭.৯)। Courier-এর হার ভার বদলালে বদলাবে, তাই এটি প্রতিশ্রুতি নয়।
  6. গোনা সংখ্যা। আগে প্রতি ১০০ অর্ডারে প্রায় ৭টি, এখন ১৭টি দেরিতে; সপ্তাহে গড়ে ১৬৪টি অর্ডারে প্রায় ১৭টি বাড়তি দেরি।

সবার জন্য পড়ার যোগ্য — রং, alt text আর লেখায় সারাংশ

পুরুষদের একটি উল্লেখযোগ্য অংশের লাল-সবুজ রঙ আলাদা করতে অসুবিধা হয় (color-vision deficiency); তাই অর্থ শুধু লাল বনাম সবুজে রাখবেন না। রঙকানা-বান্ধব palette (যেমন Masataka Okabe আর Kei Ito-র প্রস্তাবিত আট রঙ), উজ্জ্বলতার পার্থক্য, আর সরাসরি label একসঙ্গে ব্যবহার করুন। প্রতিটি ছবির alt text-এ chart-এর ধরন, কী মাপা আর মূল বার্তা লিখুন; যেমন: “রেখাচিত্র: সাপ্তাহিক দেরির হার, সিলেট বনাম বাকি চার জেলা; সপ্তাহ ৯-এর পর সিলেট প্রায় ৭% থেকে ১৭%-এ ওঠে, বাকিরা প্রায় ৭%-এ থাকে।” এই পাতার কোড-চালানো ব্যবস্থা ছবির শিরোনামকেই alt text বানায়, তাই বার্তাসহ শিরোনাম এখানে দ্বিগুণ কাজ করে।

৬ · এক পাতার decision memo — প্রতিটি সংখ্যা কোড থেকে

Memo লেখার সময় সবচেয়ে সাধারণ ভুল বিশ্লেষণে নয়, নকলে: notebook থেকে সংখ্যা হাতে কপি, পরে ডেটা আপডেট হলো, কিন্তু memo-র সংখ্যা পুরোনো রয়ে গেল; বা একটি শতাংশ ভুল কলাম থেকে নেওয়া। এর সমাধান AI-এর ভিত্তি কোর্সের পর্ব ১২-এর reproducibility-র ভাবনা, report-এর স্তরে: memo-র প্রতিটি সংখ্যা একটি function ডেটা থেকে হিসাব করবে, আর memo-তে লেখা সংখ্যাগুলো সেই function-এর ফলের সঙ্গে assertion দিয়ে মেলানো হবে। ডেটা বা কোড বদলালে, আর memo বদলানো না হলে, assertion থেমে গিয়ে জানিয়ে দেবে।

import numpy as np
import pandas as pd
from deliveries import make_deliveries

def memo_numbers(df):
    """memo-র প্রতিটি সংখ্যা ডেটা থেকে আবার হিসাব; হাতে কিছু লেখা নয়"""
    after, syl = df.week >= 9, df.district == "Sylhet"
    m = lambda mask: df.late[mask].mean() * 100
    a, b = df.late[syl & ~after], df.late[syl & after]
    d = (b.mean() - a.mean()) * 100
    se = np.sqrt(a.var() / len(a) + b.var() / len(b)) * 100
    sb = lambda mask: (df.courier[mask] == "B").mean() * 100
    rA, rB = (df[syl & after].groupby("courier").late.mean() * 100)[["A", "B"]]
    return {"syl_before": m(syl & ~after), "syl_after": m(syl & after),
            "diff": d, "diff_lo": d - 1.96 * se, "diff_hi": d + 1.96 * se,
            "other_before": m(~syl & ~after), "other_after": m(~syl & after),
            "shareB_before": sb(syl & ~after), "shareB": sb(syl & after),
            "orders_growth": ((syl & after).sum() / (syl & ~after).sum() - 1) * 100,
            "late_B": rB, "late_A": rA,
            "late_per_week": b.sum() / 8 - a.mean() * len(b) / 8,
            "scenario": 0.40 * rB + 0.60 * rA}

N = memo_numbers(make_deliveries())
for k, v in N.items():
    print(f"{k:13s} {v:6.1f}")
MEMO = {"syl_before": 6.6, "syl_after": 16.8, "diff": 10.2, "diff_lo": 7.7,
        "diff_hi": 12.8, "other_before": 7.5, "other_after": 7.4, "shareB_before": 38.8,
        "shareB": 74.7, "orders_growth": 38.8, "late_B": 19.8, "late_A": 7.9,
        "late_per_week": 16.7, "scenario": 12.6}
bad = {k: (v, round(N[k], 1)) for k, v in MEMO.items() if round(N[k], 1) != v}
assert not bad, f"memo-র সংখ্যা ডেটার সঙ্গে মেলে না: {bad}"
print(f"memo-র {len(MEMO)}টি সংখ্যাই ডেটা থেকে আবার মিলেছে")

আসল আউটপুট (ব্রাউজারের Python):

syl_before       6.6
syl_after       16.8
diff            10.2
diff_lo          7.7
diff_hi         12.8
other_before     7.5
other_after      7.4
shareB_before   38.8
shareB          74.7
orders_growth   38.8
late_B          19.8
late_A           7.9
late_per_week   16.7
scenario        12.6
memo-র 14টি সংখ্যাই ডেটা থেকে আবার মিলেছে

late_per_week হলো ফি বদলের পরে সিলেটে সপ্তাহে যত দেরি হলো (২২০ ÷ ৮ = ২৭.৫), তার থেকে আগের হার বজায় থাকলে যত হতো (০.০৬৫৭ × ১,৩১০ ÷ ৮ ≈ ১০.৮), সেটি বাদ দিয়ে: প্রায় ১৬.৭টি বাড়তি দেরি। scenario অংশ ৫-এর stepper-এর সরল দৃশ্যকল্প। এই কোড লেখার সময় প্রথম খসড়ায় দুটি সংখ্যা (বাড়তি দেরি আর দৃশ্যকল্প) হাতে ভুল লেখা ছিল, আর assertion ঠিক সেই দুটি ধরেছিল; এটিই তার কাজ। MEMO-র একটি সংখ্যা বদলে চালালে আপনিও AssertionError দেখবেন। বাস্তব প্রকল্পে এই function একটি script-এ থাকে, ডেটার snapshot-এর hash আর library-র version-সহ (পর্ব ০৪-এর মতো)।

এবার memo নিজে। নিচের লেখাটি একটি নমুনা: এই পর্বের জন্য লেখা, কোনো বাস্তব প্রতিষ্ঠানের নয়; তবে প্রতিটি সংখ্যা ওপরের ব্লকের আসল আউটপুট থেকে। এক পাতায়, decision memo-র কাঠামোয়: শিরোনামে উত্তর, SCR, পরিসরসহ প্রভাব, সুপারিশ আর ঝুঁকি।

নমুনা memo

বিষয়: সিলেটে দেরি আড়াই গুণ; Courier B-র ভাগ কমিয়ে চার সপ্তাহের পাইলট চাই
প্রাপক: Operations প্রধান · তথ্য: সপ্তাহ ১–১৬, ১২,১৮৬টি delivery

সুপারিশ: সিলেটের অর্ডারে Courier B-র ভাগ ৭৫% থেকে ৪০%-এ নামিয়ে চার সপ্তাহের পাইলট চালান; সিলেটের দেরির হার ১০%-এর নিচে নামলে নিয়মটি স্থায়ী করুন।

প্রেক্ষাপট (S): সপ্তাহ ৯ থেকে ঢাকার বাইরে নতুন delivery fee চালু। সমস্যা (C): তারপর থেকে সিলেটে দেরির হার ৬.৬% থেকে ১৬.৮%: +১০.২ শতাংশ-বিন্দু, সম্ভাব্য পরিসর +৭.৭ থেকে +১২.৮. প্রতি ১০০ অর্ডারে ৭টি থেকে ১৭টি; সপ্তাহে প্রায় ১৭টি বাড়তি দেরি। বাকি চার জেলায় ৭.৫% থেকে ৭.৪%, অর্থাৎ বদল নেই। উত্তর (R): একই সময়ে সিলেটে অর্ডার প্রায় ৩৯% বেড়েছে, আর বাড়তি অর্ডারের বেশির ভাগ গেছে Courier B-তে (ভাগ ৩৮.৮% থেকে ৭৪.৭%). এই আট সপ্তাহে সিলেটে B-র দেরি ১৯.৮%, A-র ৭.৯%; ফি বদলের আগে দুজনের হার প্রায় সমান ছিল। সবচেয়ে সম্ভাব্য ব্যাখ্যা B-র ওপর অতিরিক্ত ভার।

প্রত্যাশা: B-র ভাগ ৪০% হলে আর প্রতিটি courier-এর এখনকার হার না বদলালে সিলেটের দেরি প্রায় ১২.৬%. এটি সরল হিসাব; A-র ওপর ভার বাড়লে তার হারও বাড়তে পারে। তাই লক্ষ্য ১০%, আর পাইলট সেটিই মাপবে।

ঝুঁকি ও সীমা: (১) এটি আগে-পরের তুলনা, পরীক্ষা নয়; একই সময়ে আবহাওয়া বা রাস্তার অবস্থা বদলে থাকলে তা-ও দায়ী হতে পারে। (২) Courier A-র বাড়তি ক্ষমতা আছে কি না, পাইলটের আগে নিশ্চিত করতে হবে। (৩) Courier B-র সঙ্গে চুক্তির শর্ত দেখে নিতে হবে। পদ্ধতি, সংজ্ঞা (“দেরি” = প্রতিশ্রুত তারিখের পরে পৌঁছানো) আর কোড appendix-এ।

খেয়াল করুন memo কী বলে না: “ফি বদলের কারণে দেরি বেড়েছে” বা “B খারাপ courier”. আগে-পরের তুলনা দিয়ে কারণ প্রমাণ করা যায় না; কারণ খোঁজার পদ্ধতি (যেমন difference-in-differences) পর্ব ৬৪ (Causal inference-এর পরিচয়)-এর বিষয়, আর সুপারিশটি নিজেই একটি পরীক্ষা, যার নকশা পর্ব ৬২ (A/B test-এর পরিকল্পনা)-এর মতো করা যায়। আর memo-তে কোনো p-value নেই: সিদ্ধান্তের পাঠকের কাজে লাগে প্রভাবের আকার আর পরিসর (effect size), p-value নয়।

৭ · Report আর slide deck — executive summary, appendix আর যাচাইয়ের পথ

লম্বা report-এর কাঠামোও pyramid-এর মতো। প্রথম পাতায় executive summary: memo-র মতো উত্তর, তিনটি মূল যুক্তি আর সুপারিশ। মূল অংশে প্রতিটি যুক্তির জন্য একটি করে অংশ, প্রতিটিতে একটি chart আর তার বার্তা শিরোনামে। আর appendix-এ সব যা যাচাইয়ের জন্য লাগে, কিন্তু সিদ্ধান্তের জন্য নয়।

Executive summary-তেAppendix-এ
উত্তর আর সুপারিশ, প্রথম বাক্যেডেটার উৎস, সময়, বাদ দেওয়া সারি আর কেন
প্রভাবের আকার আর পরিসর, সাধারণ ভাষায়সংজ্ঞা (“দেরি”, “আগে-পরে”), পরিসরের পদ্ধতি
একটি বা দুটি chart, বার্তাসহ শিরোনামবাকি chart, জেলাভিত্তিক টেবিল, small multiples
ঝুঁকি আর কী জানা নেইযেসব বিশ্লেষণ চেষ্টা করে বাদ দেওয়া হয়েছে, আর কেন
পরের ধাপ আর তারিখকোড, ডেটার hash, library version, কীভাবে আবার চালাবেন

Appendix-এর শেষ সারিটি বিশেষ জরুরি: “যেসব বিশ্লেষণ বাদ দেওয়া হয়েছে”। বিশ্লেষক অনেক পথে হাঁটেন: কোন সপ্তাহ, কোন জেলা, কোন সংজ্ঞা। শেষে যে পথটি সবচেয়ে সুন্দর গল্প দেয়, শুধু সেটি দেখালে পর্ব ০৪-এর garden of forking paths-এর সমস্যা হয়। অনেক জেলা আর অনেক metric একসঙ্গে দেখে সবচেয়ে বড়টি তুলে ধরলে পর্ব ২২-এর একাধিক তুলনার সমস্যা। এখানে প্রশ্নটি এসেছিল গ্রাহকের অভিযোগ থেকে, ডেটা ঘেঁটে নয়; সেটিও লিখে রাখুন।

Report-এর পাঁচটি সাধারণ ফাঁদ। (১) পদ্ধতি দিয়ে শুরু, উত্তর শেষ পাতায়। (২) শিরোনামে বিষয় (“জেলাভিত্তিক দেরি”), বার্তা নয়। (৩) শুধু আপেক্ষিক পরিবর্তন, আসল হার ছাড়া। (৪) আগে-পরের তুলনাকে কারণ বলে লেখা। (৫) হাতে কপি করা সংখ্যা, যা পরের ডেটা আপডেটে আর মেলে না।

আরও গভীরে: অনিশ্চয়তা দেখানোর গবেষণায় Jessica Hullman ও সহকর্মীরা দেখিয়েছেন, error bar পাঠকেরা প্রায়ই ভুল পড়েন, আর সম্ভাব্য ফলগুলো একটির পর একটি দেখানো (hypothetical outcome plots, ২০১৫) বা গোনা বিন্দু (quantile dotplot) দিয়ে অনেকে ভালো সিদ্ধান্ত নেন বলে তাঁদের পরীক্ষায় পাওয়া গেছে। IPCC-র জলবায়ু report-এ শব্দের সঙ্গে সংখ্যা বাঁধা থাকে (যেমন “likely” = ৬৬–১০০%), তবু Budescu ও সহকর্মীদের পরীক্ষায় পাঠকেরা এসব শব্দকে প্রায়ই মাঝামাঝি সংখ্যার দিকে টেনে পড়েছেন; শব্দের পাশে সংখ্যা লিখে দেওয়ায় ব্যাখ্যা উন্নত হয়েছিল।

পরীক্ষা ও ইন্টারভিউতে: এই ধরনের প্রশ্ন বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় প্রায়ই আসে।

  • দেরির হার ৮% থেকে ১০%: পরিবর্তন কত? +২ শতাংশ-বিন্দু, আপেক্ষিক +২৫%; দুটিই আসল হারসহ লিখুন।
  • ৬০০টির মধ্যে ৪০টি আর ৬০০টির মধ্যে ৯০টি দেরি: পার্থক্যের ৯৫% পরিসর? ০.০৮৩ ± ১.৯৬ × ০.০১৭৮ ≈ ০.০৪৮ থেকে ০.১১৮.
  • SCR কী? Situation (যা জানা), Complication (যা বদলেছে), Resolution (উত্তর); Minto-র ভূমিকা লেখার কাঠামো।
  • ৬ থেকে শুরু axis-এ ৬.৫ আর ৮.৫-এর bar-এর অনুপাত? (৮.৫ − ৬) ÷ (৬.৫ − ৬) = ৫, আসল অনুপাত ১.৩১.
  • Decision memo-র ক্রম? শিরোনামে উত্তর, সুপারিশ, প্রেক্ষাপট ও সমস্যা, পরিসরসহ প্রভাব, ঝুঁকি, তারপর appendix.

৮ · AI-এর যুগে — AI-এর লেখা সারাংশে কারণের বাড়াবাড়ি ধরা

বিশ্লেষণের টেবিল দিয়ে AI assistant-কে “একটি executive summary লিখে দিন” বললে কয়েক সেকেন্ডে সাবলীল লেখা আসে। সমস্যা হলো, AI প্রায়ই সংযোগকে কারণ বানায় (“X-এর ফলে Y”), অনিশ্চয়তা মুছে দেয় (“নিশ্চিতভাবে”), সবচেয়ে নাটকীয় সংখ্যাটি বেছে নেয়, আর কখনো এমন সংখ্যা লেখে যা টেবিলে নেই। নিচের stepper একটি নমুনা AI-সারাংশ (এই পর্বের জন্য বানানো) বাক্য ধরে যাচাই করে।

নমুনা AI-সারাংশের প্রতিটি বাক্য, তার সমস্যা আর সংশোধন
AI লিখেছে
সমস্যা
সংশোধন
  1. “caused”. ডেটা শুধু দেখায় কখন বেড়েছে। একই সময়ে অর্ডারের ভাগ বদলেছে, আর অন্য কোনো confounding variable (আবহাওয়া, রাস্তা) থাকতে পারে। “পরে বেড়েছে” লিখুন।
  2. “155%”. আসল আপেক্ষিক বদল ১৫৫.৭%, অর্থাৎ প্রায় ১৫৬%. বড় কথা হলো আসল হার আর শতাংশ-বিন্দু নেই। দুটি হার, পার্থক্য আর পরিসর একসঙ্গে লিখুন।
  3. “responsible”. ফি বদলের আগে সিলেটে B-র দেরি ৬.৮%, A-র ৬.৪%. B-র সমস্যা দেখা দিয়েছে ভার বাড়ার পরে; তাই সমাধান হয়তো ভাগ বদল, চুক্তি বাতিল নয়। মানুষ বা প্রতিষ্ঠানকে দায়ী করার ভাষা সবচেয়ে সাবধানে লিখুন।
  4. “resolving”. AI সবচেয়ে উঁচু সপ্তাহ (২১.১%) থেকে গুনেছে। পুরো সময়ে পরের আট সপ্তাহের গড় ১৬.৭%: সমস্যা মিটছে না।
  5. “will bring below 7%”. “will” নিশ্চয়তা দেয়, আর ৭% সংখ্যাটি কোনো হিসাব থেকে আসেনি। আমাদের সরল দৃশ্যকল্প প্রায় ১২.৬% বলে; আসল উত্তর পাইলট দেবে।
  6. পুরো সারাংশ। AI-এর লেখার প্রতিটি সংখ্যা memo_numbers()-এর ফলের সঙ্গে মেলান; যেটি মেলে না, বা যার উৎস নেই, সেটি বাদ। তারপর “caused”, “responsible”, “will”, “definitely”-র মতো শব্দ খুঁজে প্রতিটি যাচাই করুন।
AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
সংযোগ বা আগে-পরেকে “কারণ” লেখেপরীক্ষা বা causal পদ্ধতি ছাড়া কারণ প্রমাণ হয় না“caused”, “led to”, “due to” খুঁজে বদলান
পরিসর বাদ দিয়ে একটি সংখ্যা দেয়পাঠক অনুমানকে নির্ভুল ধরেনপ্রতিটি প্রভাবের পাশে পরিসর চান
টেবিলে নেই এমন সংখ্যা বা গোল করা ভুল সংখ্যাসাবলীল লেখায় বানানো সংখ্যা চোখ এড়ায়প্রতিটি সংখ্যা কোডের ফলের সঙ্গে assert
পুরোনো matplotlib API, যেমন plt.style.use("seaborn")Style-এর নাম বদলেছে; নতুন version-এ errorনিজের version-এর docs; কোড চালিয়ে দেখুন
Chart-এর axis কেটে বা দুই axis দিয়ে “নাটকীয়” করেসিদ্ধান্ত ভুল দিকে যায়অংশ ৪-এর তিন প্রশ্ন
Training-set বা একটি ভাগের metric-কে চূড়ান্ত ফল বলেআশাবাদী, একবারের ফলTest বা কয়েক seed-এর ফল আর পরিসর চান

প্রম্পটে ডেটার সারাংশ আর সংজ্ঞা দিন, গ্রাহকের ব্যক্তিগত ডেটা নয়; আর AI-কে লিখতে বলুন, নিজে নয়, প্রতিটি দাবির উৎস কোন সংখ্যা।

আমি একটি অনলাইন দোকানের delivery বিশ্লেষণের এক পাতার decision memo লিখছি। নিচে সংখ্যাগুলোর টেবিল দিলাম (আগে-পরের দেরির হার, পার্থক্য আর 95% পরিসর, courier-ভিত্তিক হার)। ডেটা আগে-পরের পর্যবেক্ষণ, কোনো পরীক্ষা নয়। ১. Answer-first কাঠামোয় (শিরোনামে উত্তর, তারপর situation–complication–resolution, সুপারিশ, ঝুঁকি) ২০০ শব্দের খসড়া দাও। ২. শুধু টেবিলের সংখ্যা ব্যবহার করো; প্রতিটি সংখ্যার পরে বন্ধনীতে লেখো টেবিলের কোন সারি থেকে নিয়েছ। ৩. "caused", "due to", "will", "definitely"-র মতো কারণ বা নিশ্চয়তার শব্দ ব্যবহার করবে না; প্রতিটি প্রভাবের সঙ্গে পরিসর দাও। ৪. পরিবর্তন শতাংশ-বিন্দু আর আপেক্ষিক, দুইভাবেই আসল হারসহ লেখো। ৫. শেষে আলাদা করে বলো, কোন দাবিগুলো এই ডেটা দিয়ে প্রমাণ করা যায় না, আর কোন পরীক্ষা লাগবে।

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

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

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

প্র ০১Challenger দুর্ঘটনার আগের রাতের chart নিয়ে Tufte-এর বিশ্লেষণ data storytelling সম্পর্কে কী শেখায়, আর তাঁর ব্যাখ্যা নিয়ে বিতর্ক কী?

১৯৮৬ সালের জানুয়ারিতে মহাকাশযান Challenger উৎক্ষেপণের পরপরই ভেঙে পড়ে; পরে তদন্তে দেখা যায়, ঠান্ডায় একটি রকেট booster-এর O-ring নামের সিল ঠিকমতো কাজ করেনি। উৎক্ষেপণের আগের রাতে প্রকৌশলীরা ঠান্ডার ঝুঁকি নিয়ে সতর্ক করেছিলেন, আর তাঁদের হাতে আগের উৎক্ষেপণগুলোর O-ring-এর ক্ষতির তথ্য ছিল।

Edward Tufte তাঁর ১৯৯৭ সালের বই Visual Explanations-এ যুক্তি দেন, সেই রাতের chart-গুলো তথ্যটি ঠিকভাবে দেখায়নি: ক্ষতির তথ্য সাজানো ছিল উৎক্ষেপণের ক্রমে, তাপমাত্রার ক্রমে নয়, আর কিছু chart-এ শুধু ক্ষতি হওয়া উৎক্ষেপণগুলো ছিল। সব উৎক্ষেপণকে তাপমাত্রা বনাম ক্ষতির একটি scatter-এ রাখলে, তাঁর মতে, ঠান্ডায় ঝুঁকি স্পষ্ট দেখা যেত, বিশেষত যখন পরের দিনের পূর্বাভাসিত তাপমাত্রা আগের সব উৎক্ষেপণের চেয়ে অনেক কম। শিক্ষাটি এই পর্বের মূল কথা: তথ্য থাকা আর সঠিক তুলনা দেখানো এক নয়; chart-কে সিদ্ধান্তের প্রশ্নটি (“এই তাপমাত্রায় উৎক্ষেপণ নিরাপদ?”) সরাসরি দেখাতে হবে।

তবে বিতর্কও আছে। পরে কয়েকজন গবেষক (যেমন Robison ও সহকর্মীদের ২০০২ সালের একটি প্রবন্ধ) যুক্তি দেন, Tufte-এর ব্যাখ্যা প্রকৌশলীদের কাছে সেই রাতে যা ছিল আর সিদ্ধান্তের সাংগঠনিক চাপকে সরল করেছে, আর chart একাই দুর্ঘটনার কারণ বলা অতিরিক্ত। সমাজবিজ্ঞানী Diane Vaughan-এর গবেষণা ঝুঁকিকে ধীরে ধীরে “স্বাভাবিক” মনে করার সাংগঠনিক প্রবণতার ওপর জোর দেয়। ২০০৩ সালের Columbia দুর্ঘটনার তদন্ত বোর্ড আবার একটি ঘন slide-এর সমালোচনা করে, যেখানে মূল সতর্কবার্তা নিচের ছোট bullet-এ চাপা পড়েছিল।

মূল উপলব্ধি: ভালো chart একা সিদ্ধান্ত বাঁচায় না, কিন্তু খারাপ chart আর চাপা পড়া সতর্কবার্তা ভুল সিদ্ধান্ত সহজ করে; সবচেয়ে জরুরি ঝুঁকিটি শিরোনামে থাকা উচিত, নিচের bullet-এ নয়।

প্র ০২Florence Nightingale-এর chart কেন data storytelling-এর পুরোনো উদাহরণ হিসেবে পড়ানো হয়, আর তা থেকে কী সতর্কতা নেওয়া উচিত?

ক্রিমিয়ার যুদ্ধের (১৮৫৩–১৮৫৬) সময় Florence Nightingale ব্রিটিশ সামরিক হাসপাতালে কাজ করেন। যুদ্ধের পরে তিনি পরিসংখ্যানবিদ William Farr-এর সহায়তায় সৈন্যদের মৃত্যুর তথ্য বিশ্লেষণ করেন, আর ১৮৫৮ সালের দিকে কয়েকটি polar area diagram (অনেকে “coxcomb” বলেন) প্রকাশ করেন। প্রতিটি মাসের একটি খণ্ড, আর রং দিয়ে আলাদা করা প্রতিরোধযোগ্য রোগ, আঘাত আর অন্য কারণে মৃত্যু। ছবিটি দেখায়, যুদ্ধের আঘাতের চেয়ে অনেক বেশি সৈন্য মারা গেছেন রোগে, যা পরিচ্ছন্নতা দিয়ে ঠেকানো যেত।

কেন এটি storytelling-এর উদাহরণ? কারণ Nightingale-এর লক্ষ্য ছিল একটি সিদ্ধান্ত: সেনা-হাসপাতালের স্বাস্থ্যব্যবস্থার সংস্কার। তাঁর শ্রোতা ছিলেন রাজনীতিক আর কর্মকর্তা, যাঁরা দীর্ঘ টেবিল পড়বেন না। তিনি একটি মূল বার্তা বেছে নিয়েছিলেন, আর ছবিটি তা এক নজরে দেখাত; টেবিলগুলো ছিল সঙ্গের report-এ, যাচাইয়ের জন্য। এটি এই পর্বের pyramid-এর আদি রূপ: চূড়ায় বার্তা, নিচে প্রমাণ।

সতর্কতাও আছে। Polar area chart-এ মান বসে ক্ষেত্রফলে, আর খণ্ডের ব্যাসার্ধ মানের বর্গমূলের অনুপাতে না হলে ছবি পার্থক্য বাড়িয়ে দেখায়; AI-এর ভিত্তি কোর্সের পর্ব ৩২-এর আকার আর ক্ষেত্রফলের ফাঁকি এখানে প্রাসঙ্গিক। ইতিহাসবিদেরা আরও বলেন, মৃত্যুহার কমার কৃতিত্ব কতটা কোন সংস্কারের, তা নিয়ে আলোচনা আছে, আর Nightingale-এর বার্তাও কিছুটা সংস্কারের পক্ষে বাছাই করা ছিল। প্ররোচনার উদ্দেশ্যে বানানো chart যত সফল, তত সাবধানে যাচাই করা দরকার, এমনকি উদ্দেশ্য মহৎ হলেও।

মূল উপলব্ধি: সফল data story একটি সিদ্ধান্ত, একটি শ্রোতা আর একটি বার্তা দিয়ে শুরু হয়, আর যাচাইয়ের তথ্য পাশে রাখে; প্ররোচনার শক্তি যত বেশি, সততার দায়ও তত বেশি।

প্র ০৩“Likely” বা “সম্ভবত”-র মতো শব্দ কেন বিপজ্জনক, আর Sherman Kent থেকে IPCC পর্যন্ত এর সমাধানের চেষ্টা কেমন?

Sherman Kent ছিলেন যুক্তরাষ্ট্রের গোয়েন্দা বিশ্লেষণের একজন প্রভাবশালী ব্যক্তি। ১৯৬৪ সালে প্রকাশিত “Words of Estimative Probability” প্রবন্ধে তিনি একটি ঘটনার কথা লেখেন: ১৯৫১ সালের একটি গোয়েন্দা মূল্যায়নে যুগোস্লাভিয়া আক্রমণের “serious possibility” লেখা হয়েছিল, আর পরে সহকর্মীদের জিজ্ঞেস করে তিনি দেখেন, কেউ এটিকে প্রায় ২০% আর কেউ প্রায় ৮০% সম্ভাবনা ভেবেছেন। একই বাক্য, একই দলের মানুষ, অথচ সিদ্ধান্তের জন্য সম্পূর্ণ ভিন্ন বার্তা।

তাঁর প্রস্তাব ছিল শব্দের সঙ্গে সংখ্যার পরিসর বেঁধে দেওয়া, যেমন “probable” মোটামুটি ৭৫% আর “almost certain” মোটামুটি ৯৩%, দুইপাশে কিছু ছাড়সহ। গোয়েন্দা জগতে এটি পুরোপুরি চালু হতে কয়েক দশক লাগে; কিছু বিশ্লেষক শব্দে সংখ্যা বসানোকে মিথ্যা নির্ভুলতা বলে আপত্তি করেছিলেন। পরে বিভিন্ন দেশের গোয়েন্দা সংস্থা এমন মানচিত্র গ্রহণ করে বলে জানা যায়।

জলবায়ু বিজ্ঞানে IPCC একই সমাধান নেয়: তাদের নির্দেশিকায় “very likely” মানে ৯০–১০০%, “likely” ৬৬–১০০%, “about as likely as not” ৩৩–৬৬%. কিন্তু David Budescu ও সহকর্মীদের ২০০৯ সালের পরীক্ষায় পাঠকেরা এসব শব্দকে প্রায়ই নির্দেশিকার চেয়ে মাঝামাঝি সংখ্যায় পড়েছেন, অর্থাৎ শব্দের মানে লেখকের চেয়ে কম জোরালো বুঝেছেন; শব্দের পাশে সংখ্যা লিখে দিলে ব্যাখ্যা নির্দেশিকার কাছাকাছি আসে বলে তাঁরা জানান। বাংলায় সমস্যাটি আরও জটিল হতে পারে, কারণ “সম্ভবত”, “হয়তো”, “নিশ্চয়ই”-র ব্যবহার কথ্য ভাষায় খুব নমনীয়।

বাজারঘরের memo-তে তাই নিয়ম সরল: সংখ্যা আর পরিসর প্রধান, শব্দ সহায়ক। শব্দ ব্যবহার করলে প্রথমবার তার সংখ্যাও লিখুন।

মূল উপলব্ধি: সম্ভাবনার শব্দ লেখকের কাছে স্পষ্ট, পাঠকের কাছে অস্পষ্ট; সিদ্ধান্তের লেখায় সংখ্যা দিন, আর শব্দ দিলে তার সংখ্যার মানে বেঁধে দিন।

প্র ০৪Reinhart–Rogoff-এর spreadsheet ভুল report-এর reproducibility সম্পর্কে কী শেখায়?

২০১০ সালে অর্থনীতিবিদ Carmen Reinhart আর Kenneth Rogoff “Growth in a Time of Debt” নামের একটি প্রবন্ধে জানান, সরকারি ঋণ মোট দেশজ উৎপাদনের প্রায় ৯০% ছাড়ালে দেশগুলোর গড় প্রবৃদ্ধি অনেক কম দেখা যায়। ইউরোপ আর যুক্তরাষ্ট্রে ব্যয় সংকোচনের নীতি-বিতর্কে এই সংখ্যাটি ব্যাপকভাবে উদ্ধৃত হয়।

২০১৩ সালে University of Massachusetts Amherst-এর Thomas Herndon, Michael Ash আর Robert Pollin তাঁদের ফল আবার হিসাব করার চেষ্টা করেন। লেখকদের দেওয়া spreadsheet-এ তাঁরা দেখেন, একটি গড়ের সূত্রে কয়েকটি দেশের সারি বাদ পড়েছিল; সঙ্গে কিছু তথ্য বাদ দেওয়া আর দেশগুলোকে ওজন দেওয়ার পদ্ধতি নিয়েও প্রশ্ন তোলেন। সংশোধনের পর উচ্চ-ঋণের দেশগুলোর গড় প্রবৃদ্ধি মূল দাবির চেয়ে অনেক বেশি আসে। Reinhart আর Rogoff spreadsheet-এর ভুলটি স্বীকার করেন, তবে তাঁদের মূল উপসংহার, অর্থাৎ উচ্চ ঋণ আর কম প্রবৃদ্ধির সম্পর্ক, টিকে থাকে বলে যুক্তি দেন; কোনটি কারণ আর কোনটি ফল, সে বিতর্ক আলাদা।

এই পর্বের দৃষ্টিতে তিনটি শিক্ষা। প্রথমত, ভুলটি ধরা পড়েছিল কারণ spreadsheet অন্যদের হাতে দেওয়া হয়েছিল; appendix-এ কোড আর ডেটা দেওয়াই যাচাইয়ের পথ খোলে। দ্বিতীয়ত, হাতে টানা সূত্রের পরিসর (যেমন কোন সারি থেকে কোন সারি) একটি নিঃশব্দ ভুলের উৎস; কোডে লেখা ফিল্টার আর assert দিয়ে সারির সংখ্যা মেলানো এমন ভুল ধরে। তৃতীয়ত, একটি আকর্ষণীয় সীমারেখা (৯০%) সিদ্ধান্তের গল্পে খুব শক্তিশালী হয়ে ওঠে, তাই সেটির পেছনের বাছাইগুলো appendix-এ খোলাখুলি থাকা উচিত।

মূল উপলব্ধি: Report-এর সংখ্যা ততটাই বিশ্বাসযোগ্য, যতটা সহজে অন্য কেউ তা আবার হিসাব করতে পারেন; কোড থেকে সংখ্যা আর assertion সেই পথ সস্তা করে।

প্র ০৫সারাংশ লিখতে গিয়ে কারণের দাবি কেন বেড়ে যায় — প্রেস রিলিজ থেকে AI-এর সারাংশ পর্যন্ত?

Petroc Sumner ও সহকর্মীরা ২০১৪ সালে BMJ-তে যুক্তরাজ্যের কয়েকটি বিশ্ববিদ্যালয়ের স্বাস্থ্য-গবেষণার প্রেস রিলিজ আর সংশ্লিষ্ট সংবাদ বিশ্লেষণ করেন। তাঁরা দেখান, প্রেস রিলিজের একটি উল্লেখযোগ্য অংশেই বাড়াবাড়ি ছিল: পর্যবেক্ষণমূলক গবেষণার সম্পর্ককে কারণ বলা, প্রাণীর ওপর পরীক্ষার ফলকে মানুষের জন্য বলা, বা পরামর্শ দেওয়া; আর প্রেস রিলিজে বাড়াবাড়ি থাকলে সংবাদেও বাড়াবাড়ি বেশি দেখা গেছে। অর্থাৎ বাড়াবাড়ি প্রায়ই শুরু হয় উৎসের কাছেই, সারাংশ লেখার ধাপে।

কেন হয়? সারাংশ মানে ছোট করা, আর ছোট করার সময় সবার আগে বাদ পড়ে শর্ত আর সতর্কতা: “এই নমুনায়”, “পর্যবেক্ষণে”, “পরিসর এত থেকে এত”। “X-এর পরে Y বেড়েছে” থেকে “X-এর ফলে Y” মাত্র কয়েকটি শব্দের পথ, আর পাঠকের কাছে দ্বিতীয়টি বেশি কাজের মনে হয়। Large language model-ও ঠিক এই ধরনের লেখা থেকে শেখে; বিজ্ঞান-প্রবন্ধের LLM-সারাংশ নিয়ে সাম্প্রতিক কিছু গবেষণা (যেমন Uwe Peters আর Benjamin Chin-Yee-র ২০২৫ সালের প্রবন্ধ) জানায়, অনেক model-এর সারাংশ মূল লেখার চেয়ে বেশি সাধারণীকরণ করেছে, যদিও ফল model আর প্রম্পটভেদে আলাদা।

বাজারঘরের memo-তে বিপদটি বাস্তব: “ফি বদলের কারণে দেরি বেড়েছে” লিখলে কেউ ফি ফিরিয়ে আনতে পারেন, অথচ আসল সমস্যা হয়তো courier-এর ভার। প্রতিকার তিনটি: সারাংশ লেখার সময় দাবির ধরন চিহ্নিত করা (বর্ণনা, পূর্বাভাস না কারণ); কারণের শব্দ শুধু তখন, যখন পরীক্ষা বা উপযুক্ত causal পদ্ধতি আছে; আর AI-এর সারাংশে “caused”, “due to”, “will” খুঁজে প্রতিটির প্রমাণ মেলানো।

মূল উপলব্ধি: সংক্ষেপ করার সময় সতর্কতাই প্রথমে হারায়; মানুষ বা AI যে-ই লিখুক, সারাংশের প্রতিটি কারণ-দাবি মূল প্রমাণের সঙ্গে মিলিয়ে দেখতে হয়।

অনুশীলন

হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। একটির জন্য কোড লেখা লাগবে।

  1. হাতের হিসাব: একটি জেলায় ফি বদলের আগে ৬০০টি অর্ডারের ৪০টি দেরিতে, পরে ৬০০টির ৯০টি। (ক) দুই হার, শতাংশ-বিন্দুতে পার্থক্য আর আপেক্ষিক পরিবর্তন বের করুন। (খ) Normal approximation-এ পার্থক্যের ৯৫% পরিসর বের করুন। (গ) অ-প্রযুক্তিগত পাঠকের জন্য এক বাক্যে লিখুন।

    (ক) p১ = ৪০/৬০০ = ০.০৬৬৭ (৬.৭%), p২ = ৯০/৬০০ = ০.১৫০ (১৫%). পার্থক্য +৮.৩ শতাংশ-বিন্দু; আপেক্ষিক ০.১৫ ÷ ০.০৬৬৭ − ১ = +১২৫%.

    (খ) SE = √(০.০৬৬৭ × ০.৯৩৩৩/৬০০ + ০.১৫ × ০.৮৫/৬০০) = √(০.০০০১০৪ + ০.০০০২১৩) = √০.০০০৩১৬ ≈ ০.০১৭৮. পরিসর ০.০৮৩৩ ± ১.৯৬ × ০.০১৭৮ = ০.০৮৩৩ ± ০.০৩৪৯, অর্থাৎ +৪.৮ থেকে +১১.৮ শতাংশ-বিন্দু।

    (গ) “ফি বদলের পরে দেরি প্রতি ১০০ অর্ডারে প্রায় ৭টি থেকে ১৫টি হয়েছে; বৃদ্ধি সম্ভবত ৫ থেকে ১২ শতাংশ-বিন্দুর মধ্যে। এটি আগে-পরের তুলনা, তাই ফি-ই কারণ কি না, তা এখনও প্রমাণিত নয়।” সংখ্যাগুলো একটি ছোট Python হিসাবে যাচাই করা।

  2. সমালোচনা ও নকশা: একটি দলের ১২ slide-এর deck: প্রথম slide “পদ্ধতি”, দ্বিতীয় “ডেটা পরিষ্কার করা”, তারপর আটটি chart যাদের শিরোনাম “Late rate by district”, “Late rate by courier” ইত্যাদি; একটি chart-এর y-axis ৬% থেকে শুরু; সুপারিশ শেষ slide-এ: “ফি বদলের কারণে দেরি বেড়েছে, তাই ফি আগের মতো করা হোক।” কোনো পরিসর নেই। কী কী সমস্যা, আর কীভাবে নতুন করে সাজাবেন?

    সমস্যা: (১) উত্তর শেষে, পদ্ধতি শুরুতে: ব্যস্ত পাঠক সুপারিশ পর্যন্ত পৌঁছাবেন না। (২) শিরোনামে বিষয়, বার্তা নয়; শুধু শিরোনাম পড়ে গল্প বোঝা যায় না। (৩) ৬% থেকে শুরু axis পার্থক্য কয়েক গুণ বাড়িয়ে দেখায়। (৪) কোনো পরিসর নেই, তাই কোন পার্থক্য এলোমেলো, বোঝা যায় না। (৫) আগে-পরের তুলনাকে কারণ বলা হয়েছে, আর জেলাভেদে পার্থক্য (শুধু সিলেট) বিবেচনা হয়নি; সুপারিশটি ভুল লক্ষ্যে যেতে পারে। (৬) আটটি chart-এর বেশির ভাগ কোনো যুক্তির প্রমাণ নয়।

    নতুন সাজানো: slide ১: executive summary, action title-এ উত্তর আর সুপারিশ। slide ২: সিলেট বনাম বাকি চার জেলা, পরিসরসহ। slide ৩: সিলেটে courier-ভিত্তিক হার আর ভাগ। slide ৪: সুপারিশ, পাইলটের নকশা আর সফলতার মাপ। slide ৫: ঝুঁকি আর কী জানা নেই। বাকি সব (পদ্ধতি, পরিষ্কার করা, জেলাভিত্তিক small multiples, কোড) appendix-এ।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে, আর দুটি সংখ্যা কেন আলাদা।
    import pandas as pd
    d = pd.DataFrame({"week": [1, 1, 1, 1, 2, 2], "late": [1, 0, 0, 0, 1, 1]})
    print(d.late.mean().round(3))
    print(d.groupby("week").late.mean().mean().round(3))
    0.5
    0.625

    প্রথমটি সব অর্ডার একসঙ্গে: ৬টির ৩টি দেরি, ০.৫. দ্বিতীয়টি আগে প্রতি সপ্তাহের হার (সপ্তাহ ১: ১/৪ = ০.২৫, সপ্তাহ ২: ২/২ = ১.০), তারপর তাদের গড়: ০.৬২৫. দ্বিতীয় পদ্ধতিতে ছোট সপ্তাহ বড় সপ্তাহের সমান ওজন পায়। এই পর্বের chart-এর ১৬.৭% আর memo-র ১৬.৮%-এর পার্থক্যও এখান থেকেই; report-এ একটি সংজ্ঞা বেছে লিখে দিন। আউটপুট একটি ডেস্কটপ run-এ (pandas 3.1) যাচাই করা।

  4. কোড লিখুন: একটি function reader_sentence(before, after, lo, hi) লিখুন, যা আগে ও পরের হার (শতাংশে) আর পার্থক্যের পরিসর নিয়ে অ-প্রযুক্তিগত পাঠকের জন্য একটি ইংরেজি বাক্য বানায়: শতাংশ-বিন্দু, আপেক্ষিক পরিবর্তন, প্রতি ১০০ অর্ডারে গোনা সংখ্যা আর পরিসর। Function-টি যাচাই করবে যে পার্থক্য পরিসরের ভেতরে।
    def reader_sentence(before, after, lo, hi):
        """শতাংশ-বিন্দু, আপেক্ষিক বদল আর প্রতি ১০০ অর্ডারে গোনা সংখ্যা একসঙ্গে"""
        d = after - before
        assert lo <= d <= hi, "পরিসরের বাইরে অনুমান"
        return (f"Late rate {before:.1f}% -> {after:.1f}% ({d:+.1f} pp, {after / before - 1:+.0%}); "
                f"about {round(before)} -> {round(after)} late per 100 orders; "
                f"plausible range {lo:+.1f} to {hi:+.1f} pp")
    
    print(reader_sentence(6.6, 16.8, 7.7, 12.8))

    একটি ডেস্কটপ run-এ (Python 3.14) আউটপুট:

    Late rate 6.6% -> 16.8% (+10.2 pp, +155%); about 7 -> 17 late per 100 orders; plausible range +7.7 to +12.8 pp

    লক্ষ করুন, গোল করা ৬.৬ আর ১৬.৮ থেকে আপেক্ষিক বদল +১৫৫% আসে, আর আসল ভগ্নাংশ থেকে ১৫৫.৭% বা প্রায় ১৫৬%. তাই function-কে গোল করা সংখ্যা নয়, memo_numbers()-এর আসল সংখ্যা দিন, আর গোল করুন শুধু লেখার সময়।

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

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

ক্রমএকটি বিশ্লেষণ থেকে decision memo পর্যন্ত কাজের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. সিদ্ধান্তের প্রশ্ন আর শ্রোতা ঠিক করা
  2. বিশ্লেষণ, আর প্রতিটি সংখ্যা একটি function-এ হিসাব করা
  3. “তাহলে কী?” জিজ্ঞেস করে এক বাক্যের উত্তর আর সুপারিশ লেখা
  4. Pyramid আর SCR-এ memo সাজানো, পরিসর আর ঝুঁকিসহ
  5. Memo-র সংখ্যা assertion দিয়ে মিলিয়ে বাকি সব appendix-এ রাখা

একটি হার ৮% থেকে ১০% হলো। কোন বর্ণনাটি সঠিক আর সম্পূর্ণ?

  • হার ২% বেড়েছে
  • ৮% থেকে ১০%: +২ শতাংশ-বিন্দু, আপেক্ষিক +২৫%
  • হার ২৫% বেড়েছে, এটুকুই যথেষ্ট
  • হার প্রায় দ্বিগুণ হয়েছে

একটি bar chart-এ দুটি মান ৬.৫ আর ৮.৫, আর y-axis শুরু হয়েছে ৬ থেকে। দ্বিতীয় bar প্রথমটির কত গুণ লম্বা দেখায়?

  • প্রায় ১.৩১ গুণ
  • ২ গুণ
  • ৫ গুণ
  • axis-এর শুরু দৈর্ঘ্যের অনুপাতে প্রভাব ফেলে না

একটি memo-র ভূমিকা situation–complication–resolution কাঠামোয় লেখা হচ্ছে। “নতুন delivery fee চালুর পর থেকে একটি জেলায় দেরির হার আড়াই গুণ হয়েছে” বাক্যটি কোন অংশে বসবে?

  • Situation
  • Complication
  • Resolution
  • Appendix

একটি অনলাইন দোকানের ডেটায় দেখা গেল, নতুন delivery fee চালুর পরে একটি জেলায় দেরি বেড়েছে, অন্য জেলায় বাড়েনি। কোনো পরীক্ষা চালানো হয়নি। Executive summary-র কোন বাক্যটি প্রমাণের সঙ্গে সবচেয়ে সংগত?

  • নতুন fee-র কারণে ওই জেলায় দেরি বেড়েছে
  • Fee আগের মতো করলে দেরি নিশ্চিতভাবে কমবে
  • বৃদ্ধিটি এলোমেলো, কারণ অন্য জেলায় বাড়েনি
  • Fee বদলের পরে ওই জেলায় দেরি বেড়েছে, অন্য জেলায় নয়; কারণ যাচাইয়ে একটি পাইলট দরকার

একটি report-এর সংখ্যা যাতে ডেটা আপডেটের পরেও ভুল না থাকে, তার সবচেয়ে নির্ভরযোগ্য উপায় কোনটি?

  • সংখ্যাগুলো একটি function দিয়ে ডেটা থেকে হিসাব করা, আর লেখার সংখ্যা assertion দিয়ে মেলানো
  • Notebook থেকে সাবধানে হাতে কপি করা
  • AI-কে টেবিল দিয়ে সংখ্যা বসাতে বলা
  • সব সংখ্যা chart-এর ছবিতে রাখা, লেখায় নয়

একটি chart-এ দুটি দল শুধু লাল আর সবুজ রঙে আলাদা করা। রঙকানা দৃষ্টির পাঠকের জন্য সবচেয়ে ভালো সমাধান কোনটি?

  • লাল আর সবুজ আরও উজ্জ্বল করা
  • একটি বড় legend যোগ করা
  • রঙকানা-বান্ধব palette, উজ্জ্বলতার পার্থক্য আর রেখার পাশে সরাসরি নাম
  • Chart বাদ দিয়ে শুধু টেবিল দেওয়া

শব্দকোষ

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

মনে রাখুন

  1. সিদ্ধান্ত আগে: প্রশ্ন একটি সিদ্ধান্ত থেকে, শ্রোতা অনুযায়ী রূপ, আর প্রতিটি বাক্যের পর “তাহলে কী?” যতক্ষণ না একটি কাজ আসে।
  2. Answer first: চূড়ায় উত্তর, মাঝে যুক্তি, নিচে প্রমাণ; ভূমিকায় situation–complication–resolution; slide-এর শিরোনাম পূর্ণ বাক্যে বার্তা।
  3. এক chart, এক বার্তা: যে তুলনা যুক্তির প্রমাণ, শুধু সেটি চোখে পড়ুক; truncated axis, বেছে নেওয়া সময় আর দুই axis-এর ছবি সংখ্যায় যাচাই করুন।
  4. অনিশ্চয়তা সংখ্যায়: দুই হার, শতাংশ-বিন্দু আর আপেক্ষিক বদল, পরিসর আর গোনা সংখ্যা; শব্দ দিলে তার সংখ্যা বেঁধে দিন। রং রঙকানা-বান্ধব, alt text-এ বার্তা।
  5. সংখ্যা কোড থেকে: memo-র প্রতিটি সংখ্যা function থেকে, assertion দিয়ে মেলানো; appendix-এ সংজ্ঞা, বাদ দেওয়া বিশ্লেষণ আর কোড। AI-এর সারাংশে কারণ, নিশ্চয়তা আর উৎসহীন সংখ্যা খুঁজুন।

পরের পর্বে: মডিউল ১২ এখানে শেষ। পর্ব ৬৮ (Model serving — FastAPI, batch scoring আর ONNX) দিয়ে শুরু হবে মডিউল ১৩, production ML আর ক্যাপস্টোন।

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

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