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

Causal inference-এর পরিচয় — DAG, matching আর difference-in-differences

Introduction to Causal Inference — DAGs, Matching and Difference-in-Differences

Taught in Bangla, this lesson defines ATE, ATT and selection bias with potential outcomes, uses DAGs and the backdoor criterion, and estimates a seeded coupon effect with regression, propensity score matching and IPW plus balance checks. It runs difference-in-differences with cluster SEs, pre-trends and placebo tests, and shows a real desktop run of DoWhy and EconML.

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

  • Potential outcomes দিয়ে ATE, ATT আর selection bias সংজ্ঞায়িত করা, আর randomisation কেন bias সরায় তা দেখানো
  • DAG-এ confounder, mediator আর collider চেনা, আর backdoor criterion দিয়ে adjustment set বাছা
  • Regression adjustment, propensity score, nearest-neighbour matching আর IPW চালানো, আর standardised mean difference দিয়ে balance যাচাই
  • 2×2 টেবিল আর OLS interaction দিয়ে difference-in-differences, cluster-robust SE, pre-trend আর placebo যাচাই
  • Regression discontinuity, instrumental variable, E-value, DoWhy/EconML আর পর্যবেক্ষণের ডেটার সৎ সীমা

AI-এর ভিত্তি কোর্সের পর্ব ৩৭-এ দেখেছেন, correlation মানেই কারণ নয়, আর একটি তৃতীয় চলক (confounder) দুটি জিনিসকে একসঙ্গে নাড়াতে পারে। এই পর্ব সেখান থেকে এগোয়: কারণের প্রভাব ঠিক কী সংখ্যা, কোন অনুমানে তা ডেটা থেকে মাপা যায়, আর মাপার পদ্ধতিগুলো কীভাবে চলে। চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python-এ NumPy, pandas, statsmodels, scikit-learn আর matplotlib দিয়ে)। প্রথম তিনটি একই namespace ভাগ করে; প্রথমবার scikit-learn নামতে প্রায় ১০ সেকেন্ড লাগে।

১ · সম্পর্ক থেকে কারণ — potential outcomes, ATE আর ATT

ডাক্তার জানতে চান, ওষুধে রোগী সেরেছেন কি না। আদর্শ উত্তর: একই রোগী, একই দিনে, ওষুধ খেলে আর না খেলে কী হতো, দুটি তুলনা। কিন্তু একজন রোগী একসঙ্গে দুটি পথে হাঁটতে পারেন না। যে পথ নেওয়া হয়নি, তার ফল চিরকাল অজানা।

এই ভাবনার আনুষ্ঠানিক রূপ potential outcomes কাঠামো। Jerzy Neyman ১৯২৩ সালে কৃষি-পরীক্ষার প্রসঙ্গে এর প্রথম রূপ লেখেন, আর Donald Rubin ১৯৭৪ সাল থেকে একে পর্যবেক্ষণের ডেটায় ছড়িয়ে দেন। প্রতিটি গ্রাহক i-এর দুটি সম্ভাব্য ফল: Yi(১) মানে coupon পেলে পরের মাসের খরচ, Yi(০) মানে না পেলে। Ti = ১ হলে coupon পেয়েছেন। ব্যক্তিগত প্রভাব Yi(১) − Yi(০), কিন্তু দেখা যায় শুধু একটি ফল। Paul Holland ১৯৮৬ সালে একে বলেন causal inference-এর মৌলিক সমস্যা।

তাই আমরা ব্যক্তি ছেড়ে গড়ের দিকে যাই:

ATE = E[Y(১) − Y(০)]  ·  ATT = E[Y(১) − Y(০) | T = ১]

E[·] মানে গড় (প্রত্যাশিত মান)। ATE সবার গড় প্রভাব: “সবাইকে coupon দিলে বনাম কাউকে না দিলে” পার্থক্য। ATT শুধু যাঁরা আসলে coupon পেয়েছেন তাঁদের গড় প্রভাব: “যাঁদের দিয়েছি, তাঁদের কাজে লেগেছে কি?” প্রশ্ন আলাদা, তাই সংখ্যাও আলাদা হতে পারে। আর সরল তুলনাটি একটি অভেদ (identity) মেনে ভেঙে যায়:

E[Y | T=১] − E[Y | T=০] = ATT + (E[Y(০) | T=১] − E[Y(০) | T=০])

দ্বিতীয় অংশ selection bias: coupon না পেলেও যাঁরা পেয়েছেন আর যাঁরা পাননি, তাঁদের খরচ কতটা আলাদা হতো। অনুগত গ্রাহক বেশি coupon পেলে এই অংশ বড় ধনাত্মক। নিচের stepper চারজন কাল্পনিক গ্রাহকে পুরো হিসাবটি দেখায়।

খরচ টাকায়; T = 1 মানে coupon পেয়েছেন (বাবুল আর দীপু অনুগত)
ধাপ
আনিকা (T=0)
বাবুল (T=1)
চম্পা (T=0)
দীপু (T=1)
ফল
  1. সব-জানা দৃষ্টি। ধরুন আমরা প্রত্যেকের দুটি সম্ভাব্য খরচই জানি। বাবুল আর দীপু অনুগত, coupon ছাড়াও বেশি কেনেন।
  2. ব্যক্তিগত প্রভাব। চারটি পার্থক্য ১০০, ৫০, ১৫০, ২০; গড় ATE = ৮০ টাকা।
  3. ATT. coupon পেয়েছেন বাবুল আর দীপু; তাঁদের প্রভাবের গড় ৩৫. যাঁদের দেওয়া হয়েছে, তাঁদের কাছে coupon কম কাজের।
  4. বাস্তব। প্রত্যেকের একটি ফলই দেখা যায়; বাকিটি “?”। এটিই মৌলিক সমস্যা।
  5. সরল পার্থক্য। পাওয়া দুজনের গড় ৬৩৫, না পাওয়া দুজনের ২৫০; পার্থক্য ৩৮৫, আসল ATE-র প্রায় পাঁচ গুণ।
  6. ভাঙা হিসাব। পাওয়াদের Y(০)-এর গড় ৬০০, না পাওয়াদের ২৫০: selection bias ৩৫০. তাই ৩৮৫ = ATT ৩৫ + bias ৩৫০.

এই কাঠামোর একটি নীরব শর্ত SUTVA: একজনের coupon অন্যজনের ফল বদলায় না, আর coupon-এর একটিই রূপ। বন্ধুকে coupon শেয়ার করলে শর্তটি ভাঙে; এমন network প্রভাব পর্ব ৬২ (A/B test-এর পরিকল্পনা — power, sample size আর peeking-এর ফাঁদ)-এর বিষয়।

২ · Randomisation কেন কাজ করে, আর বাজারঘরের coupon-এ confounding

মুদ্রা ছুড়ে coupon দিলে T আর (Y(০), Y(১)) পরস্পর-নিরপেক্ষ হয়: অনুগত আর অননুগত, দুই দলেই সমান হারে পড়েন। তখন E[Y(০) | T=১] = E[Y(০) | T=০], selection bias শূন্য, আর সরল পার্থক্যই ATE-র নিরপেক্ষ অনুমান। Ronald Fisher ১৯২০–৩০-এর দশকে কৃষি-পরীক্ষায় এই যুক্তি দাঁড় করান (The Design of Experiments, ১৯৩৫)। এ কারণেই randomised controlled trial বা A/B test কারণ-প্রশ্নের সোনার মান; পরিকল্পনা আর বিশ্লেষণ পর্ব ৬২ আর পর্ব ৬৩ (A/B test-এর বিশ্লেষণ — t-test, chi-square, CUPED আর ভুল ব্যাখ্যা)-এ।

কিন্তু বাজারঘর গত মাসে coupon দিয়েছে marketing দলের পছন্দে, এলোমেলোভাবে নয়। নিচের seed দেওয়া ডেটায় অর্ডার বেশি (অনুগত) হলে coupon পাওয়ার সম্ভাবনা বেশি, আর একই আনুগত্য খরচও বাড়ায়: অর্ডার সংখ্যা তাই একটি confounder চলক। Simulation দুটি সম্ভাব্য ফল (y0, y1) রাখে, তাই আসল ATE জানা; বাস্তবে হাতে থাকে শুধু spend.

def coupon_data(n=2000, seed=64):
    r = np.random.default_rng(seed)
    orders = r.poisson(4, n)                       # গত ছয় মাসের অর্ডার: আনুগত্যের মাপ
    tenure = r.integers(1, 49, n)                  # অ্যাকাউন্টের বয়স, মাস
    dhaka = (r.random(n) < 0.4).astype(int)        # ঢাকা শহরের ঠিকানা
    logit = -2.2 + 0.5 * orders + 0.015 * tenure - 0.4 * dhaka
    coupon = (r.random(n) < 1 / (1 + np.exp(-logit))).astype(int)   # অনুগতরা বেশি পান
    y0 = 250 + 70 * orders + 4 * orders ** 2 + 3 * tenure + 80 * dhaka + r.normal(0, 120, n)
    y1 = y0 + 40 + 15 * orders                     # coupon-এর আসল প্রভাব: অনুগতদের বেশি
    spend = np.where(coupon == 1, y1, y0).round()  # দেখা যায় শুধু একটি
    return pd.DataFrame({"orders": orders, "tenure": tenure, "dhaka": dhaka,
                         "coupon": coupon, "spend": spend, "y0": y0, "y1": y1})

নিচের ব্লক তিনটি অনুমান পাশাপাশি রাখে: সরল পার্থক্য, regression adjustment (OLS-এ coupon-এর সঙ্গে confounder-গুলো রেখে coupon-এর সহগ), আর একই মানুষদের মধ্যে মুদ্রা ছুড়ে coupon দিলে কী হতো।

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
from bazar_causal import coupon_data

df = coupon_data()
true_ate = (df.y1 - df.y0).mean()                   # শুধু simulation জানে
true_att = (df.y1 - df.y0)[df.coupon == 1].mean()
print(df.groupby("coupon")[["orders", "tenure", "dhaka"]].mean().round(2))

naive = df.spend[df.coupon == 1].mean() - df.spend[df.coupon == 0].mean()
reg = smf.ols("spend ~ coupon + orders + tenure + dhaka", data=df).fit()
reg2 = smf.ols("spend ~ coupon + orders + I(orders**2) + tenure + dhaka", data=df).fit()
print(f"সরল পার্থক্য        {naive:7.1f}")
print(f"regression (রৈখিক)  {reg.params['coupon']:7.1f}  SE {reg.bse['coupon']:.1f}")
print(f"regression (+orders²) {reg2.params['coupon']:5.1f}  SE {reg2.bse['coupon']:.1f}")
print(f"আসল ATE {true_ate:.1f} | আসল ATT {true_att:.1f}")

rng = np.random.default_rng(1)                      # একই মানুষ, কিন্তু coupon মুদ্রা ছুড়ে
coin = rng.random(len(df)) < 0.5
spend_r = np.where(coin, df.y1, df.y0)
print(f"randomised: সরল পার্থক্য {spend_r[coin].mean() - spend_r[~coin].mean():.1f}, "
      f"গড় orders {df.orders[coin].mean():.2f} বনাম {df.orders[~coin].mean():.2f}")

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

        orders  tenure  dhaka
coupon                       
0         3.16   24.22   0.48
1         4.69   25.78   0.36
সরল পার্থক্য          267.6
regression (রৈখিক)     98.7  SE 6.0
regression (+orders²) 102.4  SE 5.8
আসল ATE 98.8 | আসল ATT 110.4
randomised: সরল পার্থক্য 88.8, গড় orders 3.93 বনাম 3.92

দুই দল আলাদা: coupon পাওয়াদের গড় অর্ডার ৪.৬৯, না পাওয়াদের ৩.১৬. তাই সরল পার্থক্য ২৬৭.৬ টাকা, আসল ATE ৯৮.৮-এর আড়াই গুণের বেশি। Adjustment: confounder তিনটি রাখলে coupon-এর সহগ ৯৮.৭ (SE ৬.০), আসলের খুব কাছে। orders² যোগ করলে ১০২.৪: প্রভাব যখন মানুষভেদে আলাদা (এখানে ৪০ + ১৫ × orders), OLS-এর একটি সহগ ATE বা ATT কোনোটিই ঠিক নয়, বরং মানুষভেদে প্রভাবের একটি ওজনদার গড়; ওজন বেশি পান তাঁরা, যাঁদের coupon পাওয়া-না-পাওয়া সবচেয়ে অনিশ্চিত ছিল। Randomised: একই মানুষে মুদ্রা ছুড়লে দুই দলের গড় অর্ডার ৩.৯৩ আর ৩.৯২, প্রায় সমান, আর সরল পার্থক্য ৮৮.৮: ATE-র কাছে, পার্থক্য ১০ টাকা, যা এই অনুমানের প্রায় ১১.৬ টাকার SE-র মধ্যেই (আলাদা হিসাবে মাপা)।

Regression adjustment দুটি অনুমানে দাঁড়ায়। (১) Unconfoundedness: মাপা confounder X ধরে রাখলে coupon পাওয়া আর সম্ভাব্য ফল পরস্পর-নিরপেক্ষ, অর্থাৎ X-এর বাইরে আর কোনো লুকানো কারণ নেই। (২) Overlap: X-এর প্রতিটি মানে coupon পাওয়া আর না পাওয়া দুই ধরনের মানুষই আছেন। প্রথমটি ডেটা দিয়ে প্রমাণ করা যায় না; দ্বিতীয়টি যাচাই করা যায় (অংশ ৪)। কোন চলক X-এ রাখবেন, তার উত্তর দেয় পরের অংশের ছবি।

৩ · DAG — confounder, mediator, collider আর backdoor criterion

Causal DAG (directed acyclic graph) হলো চলকগুলোর একটি ছবি, যেখানে A → B মানে “A সরাসরি B-কে প্রভাবিত করে”, আর কোনো বৃত্ত নেই। AI-এর ভিত্তি কোর্সের পর্ব ৫২-এর Bayesian network-ও DAG; তফাত হলো, causal DAG-এর তীর কারণের দাবি, শুধু সম্ভাবনার গঠন নয়। Judea Pearl ১৯৯০-এর দশকে এই ভাষা দাঁড় করান (Biometrika-য় ১৯৯৫ সালের প্রবন্ধ আর Causality বই, ২০০০)। তিন চলকের তিনটি মৌলিক গঠন:

Fork: confounder

T ← L → Y. আনুগত্য L coupon আর খরচ দুটিই বাড়ায়, তাই T আর Y-এর মধ্যে ভুয়া সম্পর্ক। L ধরলে (adjust করলে) পথ বন্ধ।

Chain: mediator

T → M → Y. Coupon পেয়ে গ্রাহক app বেশি খোলেন (mediator M), তাতে খরচ বাড়ে। এটি প্রভাবেরই অংশ; M ধরলে প্রভাবের একটি অংশ মুছে যায়।

Collider: দুই তীরের মিলন

T → R ← Y. Collider R (review লেখা) নিজে থেকে পথ বন্ধ রাখে; R ধরলে বা শুধু R = ১ সারি নিলে পথ খুলে যায়।

বাজারঘরের coupon-এর একটি causal DAG

L confounder, M mediator, R collider, Z শুধু T-কে নাড়ায়

Causal DAG for the coupon example L · আনুগত্য Z · SMS T · coupon Y · খরচ M · app খোলা R · review
ছবি ৩.১ — একটি কাল্পনিক DAG. তীরগুলো বিশ্লেষকের দাবি; ডেটা থেকে নয়, ব্যবসার জ্ঞান থেকে আঁকা। Z (এলাকাভিত্তিক SMS প্রচারণা) শুধু T-কে নাড়ায়, তাই অংশ ৬-এ instrument-এর উদাহরণ।

এখন প্রশ্ন: কোন চলকগুলো ধরলে T → Y-এর মোট প্রভাব পাওয়া যায়? Pearl-এর backdoor criterion: একটি সেট S বৈধ, যদি (ক) S-এর কোনো চলক T-এর বংশধর (descendant, T থেকে তীর ধরে পৌঁছানো যায় এমন) না হয়, আর (খ) S, T-তে ঢোকা তীর দিয়ে শুরু হওয়া (“পেছনের দরজা”) প্রতিটি পথ বন্ধ করে। পথ বন্ধ হয়, যদি পথের কোনো fork বা chain-এর মাঝের চলক S-এ থাকে, অথবা কোনো collider (বা তার বংশধর) S-এর বাইরে থাকে।

DAG: Z → T, L → T, L → Y, T → Y, T → M → Y, T → R ← Y
ধাপ
পথ
রায়
  1. সব পথ। তীরের দিক না মেনে T থেকে Y পর্যন্ত চারটি পথ। প্রথম দুটি (সরাসরি, আর M হয়ে) coupon-এর আসল প্রভাব বহন করে।
  2. Backdoor বাছা। যে পথ T-তে ঢোকা তীর দিয়ে শুরু, সেটিই পেছনের দরজা: শুধু T ← L → Y. Collider R-এর পথটি এমনিতেই বন্ধ।
  3. কিছু না ধরলে। Backdoor খোলা, তাই সরল পার্থক্যে আনুগত্যের প্রভাব মিশে যায়: অংশ ২-এর ২৬৭.৬ বনাম ৯৮.৮.
  4. Z ধরলে। Z শুধু T-কে নাড়ায়, backdoor পথে নেই, তাই কিছু বন্ধ হয় না। বরং এমন চলক ধরলে অনুমানের variance বাড়ে, আর লুকানো confounder থাকলে bias-ও বাড়তে পারে।
  5. L ধরলে। Fork-এর মাঝের চলক ধরা হলো, একমাত্র backdoor বন্ধ, আর L T-এর বংশধর নয়: সেট বৈধ। অংশ ২-এর regression ঠিক এটিই করেছে।
  6. M বা R যোগ করলে। দুটিই T-এর পরে ঘটে। M ধরলে T → M → Y অংশটি মুছে শুধু সরাসরি প্রভাব থাকে; R ধরলে বন্ধ collider-পথ খুলে নতুন ভুয়া সম্পর্ক জন্মায়।

শেষ ধাপের collider ফাঁদটি সবচেয়ে অবাক করা, কারণ “বেশি চলক ধরলে ক্ষতি কী?” ভাবনাটি স্বাভাবিক। Joseph Berkson ১৯৪৬ সালে হাসপাতালের রোগীদের মধ্যে দুটি রোগের ভুয়া সম্পর্ক দেখিয়ে এর একটি রূপ বর্ণনা করেন, যা এখন collider bias বা Berkson's paradox নামে পরিচিত। নিচের simulation-এ coupon এলোমেলো, delivery দেরি এলোমেলো, আর দুটি পরস্পর-নিরপেক্ষ; কিন্তু দুটিই review লেখার সম্ভাবনা বাড়ায়।

import numpy as np
import pandas as pd

r = np.random.default_rng(64)
n = 20000
coupon = r.random(n) < 0.5                   # মুদ্রা ছুড়ে coupon
late = r.random(n) < 0.3                     # delivery দেরি: coupon-এর সঙ্গে সম্পর্কহীন
p_review = 0.05 + 0.25 * coupon + 0.40 * late   # দুটিই review লেখায় ঠেলে দেয়
review = r.random(n) < p_review              # collider: coupon → review ← late
d = pd.DataFrame({"coupon": coupon, "late": late, "review": review})

def late_rate(s):                            # coupon পাওয়া আর না-পাওয়াদের দেরির হার
    return s.groupby("coupon").late.mean().round(3).to_dict()
print("সবাই:            ", late_rate(d), " corr", round(np.corrcoef(coupon, late)[0, 1], 3))
rv = d[d.review]
print("শুধু review লেখা:", late_rate(rv), " corr",
      round(np.corrcoef(rv.coupon, rv.late)[0, 1], 3), f" n={len(rv)}")
nr = d[~d.review]
print("review লেখেননি:  ", late_rate(nr), " corr", round(np.corrcoef(nr.coupon, nr.late)[0, 1], 3))

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

সবাই:             {False: 0.303, True: 0.303}  corr -0.001
শুধু review লেখা: {False: 0.812, True: 0.496}  corr -0.289  n=5883
review লেখেননি:   {False: 0.201, True: 0.162}  corr -0.049

সবার মধ্যে দুই দলেই দেরির হার ০.৩০৩, correlation প্রায় শূন্য (−০.০০১)। শুধু review লেখা ৫,৮৮৩ জনে coupon না পাওয়াদের দেরির হার ০.৮১২, পাওয়াদের ০.৪৯৬; correlation −০.২৮৯. কারণ: coupon ছাড়া কেউ review লিখলে তার সম্ভাব্য ব্যাখ্যা দেরি; coupon থাকলে review-এর ব্যাখ্যা আগেই আছে। কেউ যদি শুধু review থেকে সিদ্ধান্ত টানেন (“coupon পেলে delivery কম দেরি হয়”), তা পুরো ভুয়া। Review না লেখাদের মধ্যেও ছোট ভুয়া সম্পর্ক (−০.০৪৯): collider ধরে ভাগ করলেই, যেকোনো ভাগে।

“সব চলক control করুন” নিয়মটি ভুল। Treatment-এর পরে মাপা চলক (coupon-এর পরের app খোলা, review, ফেরত) সাধারণত mediator বা collider; regression-এ রাখবেন না। শুধু review দেওয়া গ্রাহক, শুধু টিকে থাকা গ্রাহক বা শুধু অভিযোগ করা গ্রাহকের ডেটা নিলেও একই ফাঁদ, কারণ সারি বাছাই নিজেই একটি collider ধরা।

৪ · Propensity score — matching, IPW আর balance যাচাই

Regression-এ ফলের (খরচের) একটি model ঠিকঠাক লিখতে হয়। বিকল্প পথ: ফলের model না লিখে coupon পাওয়ার model লেখা। Paul Rosenbaum আর Donald Rubin ১৯৮৩ সালে (Biometrika) দেখান, propensity score e(x) = P(T = ১ | X = x) একটি “balancing score”: একই e(x)-এর মানুষদের মধ্যে X-এর বণ্টন দুই দলে সমান। তাই অনেক চলকের বদলে একটি সংখ্যায় তুলনা করা যায়। e(x) অনুমান করা হয় AI-এর ভিত্তি কোর্সের পর্ব ৫৯-এর logistic regression দিয়ে।

পরীক্ষার খাতা দেখার মতো ভাবুন। দুই কোচিংয়ের ফল তুলনা করতে চাইলে, আগের পরীক্ষায় একই নম্বর পাওয়া দুজন করে ছাত্র জোড়া বানান, এক কোচিং থেকে একজন, অন্যটি থেকে একজন। Propensity score সেই “আগের নম্বর”, শুধু অনেক বৈশিষ্ট্য মিলিয়ে একটি সংখ্যা।

দুটি ব্যবহার:

  • Matching (ATT): প্রতিটি coupon-পাওয়া গ্রাহকের জন্য সবচেয়ে কাছের e(x)-এর একজন না-পাওয়া গ্রাহক খোঁজা (১-nearest neighbour, একই control একাধিকবার চলতে পারে)। জোড়ার পার্থক্যের গড় ATT. খোঁজা হয় k-d tree দিয়ে (algorithm="kd_tree"; ব্রাউজারের Python-এ default পথটি একটি অপ্রাসঙ্গিক warning ছাপে)।
  • IPW (ATE): coupon-পাওয়াদের ওজন ১/e আর না-পাওয়াদের ১/(১ − e), যেখানে e তাঁর propensity score. যাঁর coupon পাওয়ার কথা কম ছিল অথচ পেয়েছেন, তিনি বিরল, তাই ভারী: তিনি একা অনেক “অদেখা” মানুষের প্রতিনিধি। ধারণাটি জরিপ-পরিসংখ্যানের Horvitz–Thompson (১৯৫২) থেকে। এখানে দলভেদে ওজনসহ গড় নেওয়া হয় (Hájek রূপ), আর e-কে ০.০২ থেকে ০.৯৮-এ কেটে চরম ওজন ঠেকানো হয়।

কাজ শেষে balance যাচাই বাধ্যতামূলক। প্রতিটি চলকের standardised mean difference: SMD = (x̄treated − x̄control) ÷ √((s²treated + s²control) ÷ ২), যেখানে x̄ গড় আর s² variance. |SMD| < ০.১ একটি প্রচলিত হাতে-কলমে সীমা (Austin-এর লেখায় জনপ্রিয়), কোনো পরীক্ষার নিয়ম নয়। ছবিতে এগুলো পাশাপাশি দেখানোর ছবিকে বলে love plot.

import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors
X = ["orders", "tenure", "dhaka"]

def estimate(d):                                   # PS → matching-এ ATT, IPW-তে ATE
    t, y = d.coupon.to_numpy() == 1, d.spend.to_numpy()
    e = LogisticRegression(C=1e10, max_iter=1000).fit(d[X], t).predict_proba(d[X])[:, 1]
    e = np.clip(e, 0.02, 0.98)                     # চরম ওজন ঠেকাতে PS কাটা
    nn = NearestNeighbors(n_neighbors=1, algorithm="kd_tree").fit(e[~t, None])
    m = np.flatnonzero(~t)[nn.kneighbors(e[t, None])[1][:, 0]]   # প্রতিটি treated-এর জোড়া
    w = np.where(t, 1 / e, 1 / (1 - e))
    ate = np.average(y[t], weights=w[t]) - np.average(y[~t], weights=w[~t])
    return y[t].mean() - y[m].mean(), ate, t, m, w

def smd(a, b, wa=None, wb=None):                   # standardised mean difference
    return (np.average(a, weights=wa) - np.average(b, weights=wb)) / np.sqrt((a.var() + b.var()) / 2)

att, ate, t, m, w = estimate(df)
bal = pd.DataFrame({c: [smd(df[c][t], df[c][~t]), smd(df[c][t], df[c].to_numpy()[m]),
                        smd(df[c][t], df[c][~t], w[t], w[~t])] for c in X},
                   index=["before", "matched", "IPW"]).T.round(3)
print(bal, "\nসবচেয়ে বড় ওজন", w.max().round(1), "| জোড়ায় আলাদা control", len(set(m)))
boot, rng = [], np.random.default_rng(0)
for _ in range(200):                               # bootstrap: PS-সহ পুরো পদ্ধতি আবার
    boot.append(estimate(df.iloc[rng.integers(0, len(df), len(df))])[:2])
lo, hi = np.percentile(boot, [2.5, 97.5], axis=0)
print(f"matching ATT {att:.1f} [{lo[0]:.1f}, {hi[0]:.1f}] | আসল ATT {true_att:.1f}")
print(f"IPW ATE      {ate:.1f} [{lo[1]:.1f}, {hi[1]:.1f}] | আসল ATE {true_ate:.1f}")
fig, ax = plt.subplots(figsize=(6, 2.8))
for k, mk in zip(bal.columns, "osD"):
    ax.plot(bal[k].abs(), bal.index, mk, label=k)
ax.axvline(0.1, ls="--", c="gray"); ax.legend(); fig.subplots_adjust(left=0.18, bottom=0.2)
ax.set(xlabel="|standardised mean difference|", title="Covariate balance (love plot)")
plt.show()

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

        before  matched    IPW
orders   0.839    0.011  0.013
tenure   0.113   -0.022 -0.012
dhaka   -0.242   -0.002  0.002 
সবচেয়ে বড় ওজন 16.6 | জোড়ায় আলাদা control 346
matching ATT 115.0 [95.9, 136.9] | আসল ATT 110.4
IPW ATE      106.5 [90.8, 121.0] | আসল ATE 98.8

আগে: orders-এর SMD ০.৮৩৯, সীমা ০.১-এর আট গুণ; dhaka −০.২৪২. পরে: matching আর IPW দুটিতেই সব |SMD| ০.০২৫-এর নিচে, ছবিতে সব বিন্দু ড্যাশ রেখার বাঁয়ে। অনুমান: matching-এর ATT ১১৫.০ (আসল ১১০.৪), IPW-এর ATE ১০৬.৫ (আসল ৯৮.৮); দুটি সত্যই bootstrap পরিসরের ভেতরে, আর সরল ২৬৭.৬ থেকে অনেক দূরে। সবচেয়ে বড় ওজন ১৬.৬, তাই এখানে e কাটার প্রভাব নগণ্য; ওজন শতকের ঘরে গেলে overlap দুর্বল, আর IPW তখন কয়েকজন মানুষের ওপর দাঁড়িয়ে থাকে। Matching-এ ৩৪৬ জন আলাদা control একাধিক জোড়ায় ব্যবহৃত হয়েছেন।

Overlap যাচাই: দুই দলের e-এর histogram পাশাপাশি আঁকুন। কোনো অঞ্চলে শুধু এক দলের মানুষ থাকলে (যেমন e > ০.৯৫-এ প্রায় কেউ coupon ছাড়া নেই), সেখানে তুলনার মতো কেউ নেই; তখন e কাটা (clipping) বা ওই মানুষদের বাদ দেওয়া (trimming, যেমন e ০.১ থেকে ০.৯-এর বাইরে বাদ) প্রচলিত পথ। কিন্তু trimming করলে estimand বদলায়: তখন উত্তর শুধু “তুলনীয়” মানুষদের জন্য, আর রিপোর্টে সেটি স্পষ্ট লিখতে হয়। Overlap দুর্বল হলে regression-ও নিরাপদ নয়; সে তখন ডেটা নেই এমন জায়গায় সরলরেখা টেনে অনুমান করে।

দুটি সৎ সতর্কতা। (১) Balance শুধু মাপা চলকের; লুকানো confounder-এর balance কোনো ছবিতে দেখা যায় না। (২) Abadie আর Imbens (Econometrica, ২০০৮) দেখান, নির্দিষ্ট সংখ্যক প্রতিবেশীর matching-এ সাধারণ bootstrap তাত্ত্বিকভাবে বৈধ নয়; তাঁদের নিজস্ব SE সূত্র প্রচলিত। এখানে bootstrap পরিসরটি তাই একটি মোটা দাগের নির্দেশক; IPW-এর জন্য PS-সহ পুরো পদ্ধতির bootstrap প্রচলিত পথ (পর্ব ২১-এর bootstrap দেখুন)।

৫ · Difference-in-differences — delivery fee বাড়ানোর প্রভাব

সপ্তাহ ৯ থেকে বাজারঘর ১৬টি জেলার ৬টিতে delivery fee বাড়িয়েছে। প্রশ্ন: সাপ্তাহিক অর্ডার (প্রতি ১,০০০ গ্রাহকে) কতটা কমল? দুটি সরল তুলনাই ভুল। শুধু আগে-পরে তুলনায় সময়ের প্রভাব (মৌসুম, ঈদ, সাধারণ প্রবৃদ্ধি) মিশে যায়। শুধু fee-বাড়ানো বনাম না-বাড়ানো জেলার তুলনায় জেলার নিজস্ব পার্থক্য মিশে যায় (fee বাড়ানো হয়েছে যেখানে চাহিদা আগে থেকেই বেশি)।

Difference-in-differences (DiD) দুটি পার্থক্য একসঙ্গে বাদ দেয়:

DiD = (ȳtreated, পরে − ȳtreated, আগে) − (ȳcontrol, পরে − ȳcontrol, আগে)

ȳ মানে সেই ঘরের গড় অর্ডার। Control জেলার পরিবর্তনটি “fee না বাড়ালে treated জেলায় কী হতো”-র প্রতিনিধি। এর মূল অনুমান parallel trends: fee না বাড়ালে দুই দলের গড় একই হারে বদলাত। মাত্রা আলাদা হতে পারে, ধারা নয়। ক্রিকেটের ভাষায়: দুই দলের রান রেট একই হারে বাড়ছিল; ১০ ওভারের পর এক দলের ব্যাটার বদলালে, রান রেটের ফাঁক কতটা বদলাল সেটিই প্রভাব।

সাপ্তাহিক অর্ডারের গড় (একটি হাতের উদাহরণ)
ধাপ
control
treated
ফল
  1. চারটি ঘর। দুই দল × দুই সময়। Treated জেলার চাহিদা আগে থেকেই ১০ বেশি; DiD এই মাত্রার ফাঁক নিয়ে চিন্তা করে না।
  2. শুধু treated-এর আগে-পরে। −৫, কিন্তু এর মধ্যে এই সময়ে সব জেলার সাধারণ পরিবর্তনও আছে।
  3. Control-এর পরিবর্তন। একই সময়ে fee না বাড়ানো জেলায় +৩. Parallel trends মানলে treated-এরও fee ছাড়া +৩ হতো।
  4. পার্থক্যের পার্থক্য। −৫ − (+৩) = −৮: fee বাড়ানোয় সাপ্তাহিক অর্ডার আনুমানিক ৮ কমেছে।
  5. শুধু “পরে” তুলনা। ৫৫ বনাম ৫৩, যেন fee বাড়িয়ে অর্ডার বেড়েছে; আসলে এটি জেলার আগের মাত্রার ফাঁক। DiD দুই ধরনের ভুলই সরায়।

একই হিসাব regression-এ: y = β0 + β1·treated + β2·post + β3·(treated × post) + ε, যেখানে treated আর post ০/১ চলক, আর ε বাকি এলোমেলোতা। Interaction-এর সহগ β3 ঠিক DiD. Regression-এর সুবিধা SE: একই জেলার ১৬ সপ্তাহের অর্ডার পরস্পর-সম্পর্কিত, তাই সাধারণ SE অতি-আশাবাদী। Bertrand, Duflo আর Mullainathan (QJE, ২০০৪) দেখান, এমন ডেটায় সাধারণ SE ব্যবহার করলে ভুয়া “significant” ফল অনেক বেড়ে যায়। সমাধান হলো জেলা ধরে cluster-robust SE নেওয়া। নিচের ব্লক দুটি জগৎ চালায়: একটিতে parallel trends সত্যি, অন্যটিতে fee-বাড়ানো জেলার চাহিদা আগে থেকেই প্রতি সপ্তাহে ০.৬ করে নামছিল। আসল প্রভাব দুটিতেই −৫. শেষে একটি placebo test: শুধু সপ্তাহ ১–৮ নিয়ে ভান করা হয় fee বেড়েছে সপ্তাহ ৫-এ।

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.formula.api as smf
from bazar_causal import fee_panel

def did(p, name):                        # 2×2 হাতে, তারপর OLS-এর interaction
    t = p.groupby(["treated", "post"]).orders.mean().unstack()
    by_hand = (t.loc[1, 1] - t.loc[1, 0]) - (t.loc[0, 1] - t.loc[0, 0])
    fit = smf.ols("orders ~ treated * post", data=p).fit(
        cov_type="cluster", cov_kwds={"groups": p.district})
    lo, hi = fit.conf_int().loc["treated:post"]
    print(f"{name:13s} হাতে {by_hand:6.2f} | OLS {fit.params['treated:post']:6.2f}, "
          f"cluster SE {fit.bse['treated:post']:.2f}, 95% CI [{lo:.2f}, {hi:.2f}]")
    return t.round(2)

good = fee_panel()                       # parallel trends সত্যি; আসল প্রভাব -5
bad = fee_panel(trend_gap=-0.6)          # treated জেলার চাহিদা আগে থেকেই নামছিল
print(did(good, "সমান্তরাল"))
did(bad, "ধারা ভাঙা")
for name, p in [("placebo ঠিক", good), ("placebo ভাঙা", bad)]:   # শুধু সপ্তাহ 1–8, ভান 5-এ
    pre = p[p.week <= 8]
    did(pre.assign(post=(pre.week >= 5).astype(int)), name)
fig, axs = plt.subplots(1, 2, figsize=(8, 3), sharey=True)
for ax, p, ttl in zip(axs, [good, bad], ["Parallel pre-trends", "Diverging pre-trends"]):
    wk = p.groupby(["week", "treated"]).orders.mean().unstack()
    ax.plot(wk.index, wk[0], "o-", ms=3, label="control districts")
    ax.plot(wk.index, wk[1], "s-", ms=3, label="fee raised (week 9)")
    ax.axvline(8.5, ls="--", c="gray"); ax.set(title=ttl, xlabel="week")
axs[0].set_ylabel("orders per 1,000 customers"); axs[0].legend(fontsize=8)
fig.subplots_adjust(bottom=0.17, wspace=0.08)
plt.show()

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

সমান্তরাল     হাতে  -5.35 | OLS  -5.35, cluster SE 0.50, 95% CI [-6.33, -4.36]
post         0      1
treated              
0        58.27  60.20
1        71.03  67.61
ধারা ভাঙা     হাতে -10.15 | OLS -10.15, cluster SE 0.50, 95% CI [-11.13, -9.16]
placebo ঠিক   হাতে   0.71 | OLS   0.71, cluster SE 0.51, 95% CI [-0.29, 1.72]
placebo ভাঙা  হাতে  -1.69 | OLS  -1.69, cluster SE 0.51, 95% CI [-2.69, -0.68]

সমান্তরাল জগৎ: টেবিল থেকে হাতে (৬৭.৬১ − ৭১.০৩) − (৬০.২০ − ৫৮.২৭) = −৫.৩৫, OLS-এর interaction-ও −৫.৩৫; CI −৬.৩৩ থেকে −৪.৩৬, আসল −৫ ভেতরে। ভাঙা জগৎ: −১০.১৫, CI −১১.১৩ থেকে −৯.১৬: সরু, আত্মবিশ্বাসী, আর ভুল। আগের নামার ধারা (প্রতি সপ্তাহে −০.৬, আগে-পরের মাঝবিন্দুর দূরত্ব ৮ সপ্তাহ, তাই প্রায় −৪.৮) প্রভাবের ঘাড়ে চেপেছে। SE এই ভুল ধরে না, কারণ SE শুধু এলোমেলোতা মাপে, অনুমানের ভুল নয়। Placebo: ঠিক জগতে ভানের প্রভাব ০.৭১, CI শূন্য ছোঁয়; ভাঙা জগতে −১.৬৯, CI শূন্যের নিচে: সতর্ক-সংকেত। ছবির ডান অংশে সপ্তাহ ৯-এর আগেই কমলা রেখা নামছে।

Pre-trend যাচাই প্রমাণ নয়। আগের ধারা সমান্তরাল দেখালে শুধু বলা যায় অনুমানটি ভাঙার দৃশ্যমান চিহ্ন নেই; fee বাড়ানোর সপ্তাহেই অন্য কিছু (যেমন treated জেলায় প্রতিযোগীর নতুন অফার) ঘটলে কোনো যাচাই তা ধরবে না। আর ১৬টি cluster কম; cluster সংখ্যা কম হলে cluster-robust SE-ও আশাবাদী হতে পারে, তখন wild cluster bootstrap-এর মতো পদ্ধতি প্রচলিত (Cameron, Gelbach আর Miller, ২০০৮)।

DiD-এর একটি বিখ্যাত প্রয়োগ David Card আর Alan Krueger-এর ১৯৯৪ সালের ন্যূনতম মজুরির গবেষণা: New Jersey-তে মজুরি বাড়ার আগে-পরে পাশের Pennsylvania-র fast-food দোকানের সঙ্গে তুলনা। এর আগের এক উদাহরণ হিসেবে প্রায়ই John Snow-এর ১৮৫০-এর দশকের লন্ডনের কলেরা-বিশ্লেষণের কথা বলা হয়, যেখানে দুটি পানি-সরবরাহ কোম্পানির এলাকার মৃত্যুহার তুলনা করা হয়েছিল।

৬ · আরও দুটি নকশা — regression discontinuity আর instrumental variable

এই দুটি পদ্ধতির পুরো চর্চা এখানে নয়; ধারণা আর শর্তগুলো জানা থাকলে কখন বিশেষজ্ঞের সাহায্য নেবেন বুঝবেন।

Regression discontinuity

বাজারঘরে ১,০০০ টাকার বেশি অর্ডারে delivery ফ্রি। ৯৯০ আর ১,০১০ টাকার গ্রাহকেরা প্রায় একই রকম, পার্থক্য শুধু ফ্রি delivery. সীমার দুই পাশের ফল তুলনা করলে সীমার কাছের মানুষের প্রভাব পাওয়া যায়। RDD-এর ধারণা Thistlethwaite আর Campbell-এর (১৯৬০) বলে পরিচিত। ফাঁদ: গ্রাহক ইচ্ছা করে ঝুড়িতে একটি জিনিস যোগ করে সীমা পার হলে দুই পাশ আর তুলনীয় নয় (“manipulation”); সীমার কাছে মানুষের ঘনত্ব হঠাৎ লাফালে সন্দেহ করুন। আর ফলটি শুধু সীমার কাছের মানুষের জন্য।

Instrumental variable

Z এমন একটি চলক যা T-কে নাড়ায় কিন্তু Y-তে পৌঁছায় শুধু T হয়ে, যেমন DAG-এর SMS. এলোমেলো এলাকায় SMS পাঠানো হলো: SMS app ব্যবহারের হার ০.২০ বাড়াল, আর খরচ বাড়াল ৩০ টাকা। Wald অনুমান = ৩০ ÷ ০.২০ = ১৫০ টাকা, শুধু যাঁদের SMS-এ আচরণ বদলায় তাঁদের প্রভাব (IV-এর LATE; Imbens আর Angrist, ১৯৯৪)। শর্ত: Z সত্যিই T নাড়ায়, Y-তে অন্য পথ নেই (exclusion; যাচাই করা যায় না), আর কেউ উল্টো আচরণ করেন না। দুর্বল instrument-এ ছোট হর ভুলকে বহুগুণ করে।

Card, Angrist আর Imbens ২০২১ সালে অর্থনীতির নোবেল-স্মারক পুরস্কার পান, মূলত এমন “প্রাকৃতিক পরীক্ষা” থেকে কারণ খোঁজার পদ্ধতি নিয়ে কাজের জন্য। মূল কথা একই: কোথাও একটি এলোমেলো-ঘেঁষা কারণে treatment বদলেছে, এমন জায়গা খোঁজা।

৭ · লুকানো confounder, DoWhy/EconML আর পর্যবেক্ষণের সীমা

অংশ ২–৪-এর সব পদ্ধতি দাঁড়িয়ে আছে “সব confounder মাপা হয়েছে” অনুমানের ওপর। বাস্তবে আয়, পরিবারের আকার, প্রতিযোগীর অফার প্রায়ই অজানা। Sensitivity analysis প্রশ্ন বদলায়: “লুকানো confounder কত শক্তিশালী হলে আমার সিদ্ধান্ত উল্টে যেত?” এর একটি জনপ্রিয় রূপ VanderWeele আর Ding-এর (২০১৭) E-value: risk ratio RR > ১ হলে E = RR + √(RR × (RR − ১))। যেমন RR = ২ হলে E = ২ + √২ ≈ ৩.৪১: লুকানো confounder-কে treatment আর ফল দুটির সঙ্গেই অন্তত ৩.৪১ গুণ risk ratio-য় জড়িত থাকতে হবে, তবেই পুরো প্রভাব ব্যাখ্যা হতো। এটি একটি নির্দেশক, প্রমাণ নয়; ধারণাটির পূর্বসূরি Cornfield ও সহকর্মীদের ১৯৫৯ সালের ধূমপান-ক্যান্সার যুক্তি।

DoWhy আর EconML দিয়ে সত্যিকারের run

DoWhy লাইব্রেরি কাজটিকে চার ধাপে বাঁধে: DAG দিয়ে model, identify (backdoor সেট খোঁজা), estimate, আর refute (ভুয়া treatment দিয়ে ফল ভেঙে পড়ে কি না দেখা)। শুরু Microsoft Research-এ, এখন PyWhy নামের open-source উদ্যোগের অংশ। EconML মানুষভেদে প্রভাব (CATE) অনুমানের পদ্ধতি দেয়, যেমন double machine learning (Chernozhukov ও সহকর্মী, ২০১৮)। দুটির কোনোটিই ব্রাউজারের Python-এ নেই, তাই ডেস্কটপে একটি আলাদা uv project-এ একই bazar_causal.py দিয়ে সত্যিকারের run (একটি যন্ত্র, একটি run; লেখার সময়ে dowhy ০.১৪-এর ঘোষিত Python সীমা ৩.১৪-এর নিচে, তাই Python ৩.১৩):

import sys, warnings, logging
import numpy as np, networkx as nx
import dowhy, econml
from dowhy import CausalModel
from econml.dml import LinearDML
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier
from bazar_causal import coupon_data

warnings.filterwarnings("ignore"); logging.getLogger("dowhy").setLevel(logging.ERROR)
print(f"Python {sys.version.split()[0]} | dowhy {dowhy.__version__} | econml {econml.__version__}")
df = coupon_data()
data = df[["orders", "tenure", "dhaka", "coupon", "spend"]].astype({"coupon": bool})
g = nx.DiGraph([(c, "coupon") for c in ("orders", "tenure", "dhaka")] +
               [(c, "spend") for c in ("orders", "tenure", "dhaka")] + [("coupon", "spend")])
model = CausalModel(data=data, treatment="coupon", outcome="spend", graph=g)
est = model.identify_effect()
print("backdoor set:", est.get_backdoor_variables())
for m, unit in [("backdoor.linear_regression", "ate"),
                ("backdoor.propensity_score_weighting", "ate"),
                ("backdoor.propensity_score_matching", "att")]:
    e = model.estimate_effect(est, method_name=m, target_units=unit)
    print(f"{m:38s} {unit}: {e.value:7.1f}")
lin = model.estimate_effect(est, method_name="backdoor.linear_regression")
ref = model.refute_estimate(est, lin, method_name="placebo_treatment_refuter",
                            placebo_type="permute", num_simulations=50, random_seed=0)
print(f"placebo refuter: new effect {ref.new_effect:.1f}, p-value {ref.refutation_result['p_value']:.2f}")
dml = LinearDML(model_y=GradientBoostingRegressor(random_state=0),
                model_t=GradientBoostingClassifier(random_state=0),
                discrete_treatment=True, random_state=0)
dml.fit(df.spend, df.coupon, X=df[["orders"]], W=df[["tenure", "dhaka"]])
print(f"LinearDML: effect = {dml.intercept_:.1f} + {dml.coef_[0]:.1f} x orders  (truth 40 + 15 x orders)")
lo, hi = dml.effect_interval(np.array([[2], [8]]), alpha=0.05)
print("effect at orders=2, 8:", dml.effect(np.array([[2], [8]])).round(1), "95% CI", lo.round(1), hi.round(1))
~/bazarghar-causal$ uv init --python 3.13 --vcs none
…
~/bazarghar-causal$ uv add dowhy econml
…
~/bazarghar-causal$ uv run python causal_check.py
Python 3.13.5 | dowhy 0.14 | econml 0.17.0
backdoor set: ['tenure', 'dhaka', 'orders']
backdoor.linear_regression             ate:    98.7
backdoor.propensity_score_weighting    ate:   106.7
backdoor.propensity_score_matching     att:   123.2
placebo refuter: new effect -0.3, p-value 0.48
LinearDML: effect = 61.2 + 10.5 x orders  (truth 40 + 15 x orders)
effect at orders=2, 8: [ 82.3 145.3] 95% CI [ 65.2 117. ] [ 99.3 173.7]

DoWhy DAG থেকে নিজেই backdoor সেট (তিনটি confounder) বের করেছে; linear regression পাতার ব্লকের সঙ্গে হুবহু (৯৮.৭)। IPW ১০৬.৭, পাতার ১০৬.৫-এর কাছে (ভেতরের propensity model-এর সেটিং আলাদা হতে পারে)। Matching-এর ATT ১২৩.২, পাতার ১১৫.০ থেকে বেশ দূরে: DoWhy-র matching-এর ভেতরের খুঁটিনাটি (কী দিয়ে দূরত্ব মাপা হয়) আলাদা, আর matching অনুমান এমন খুঁটিনাটিতে নড়ে। Placebo refuter coupon কলাম এলোমেলো করে ৫০ বার চালায়; প্রভাব −০.৩, প্রত্যাশিতভাবে প্রায় শূন্য। এই পরীক্ষা লুকানো confounder ধরে না, শুধু কোডের বড় ভুল ধরে। LinearDML-এর রেখা ৬১.২ + ১০.৫ × orders, আসল ৪০ + ১৫ × orders; orders = ২ আর ৮-এ আসল মান ৭০ আর ১৬০, দুটিই ৯৫% পরিসরের ভেতরে, তবে ঢাল কম ধরা পড়েছে। লেখার সময়ের version প্রথম লাইনে; API বদলায়, তাই নিজের version-এর docs দেখুন।

পর্যবেক্ষণের ডেটার সৎ সীমা

Robert LaLonde ১৯৮৬ সালে একটি চাকরি-প্রশিক্ষণ কর্মসূচির randomised পরীক্ষার ফলকে মানদণ্ড ধরে দেখান, সাধারণ পর্যবেক্ষণভিত্তিক পদ্ধতিগুলো প্রায়ই সেই ফল থেকে অনেক দূরে, এমনকি উল্টো দিকে যায়। পরে propensity score দিয়ে আরও কাছে পৌঁছানোর দাবি এসেছে, আর সেই দাবির সংবেদনশীলতা নিয়ে তর্কও হয়েছে। চিকিৎসায় menopause-এর পরে hormone therapy নিয়ে পর্যবেক্ষণের গবেষণা হৃদরোগে সুরক্ষার ইঙ্গিত দিয়েছিল; ২০০২ সালে Women's Health Initiative-এর randomised trial সেই সুরক্ষা খুঁজে পায়নি বলে জানায়, আর ব্যাখ্যা নিয়ে আলোচনা এখনো চলে। শিক্ষা: পদ্ধতির নাম যত জটিলই হোক, শক্তি আসে অনুমান থেকে। সিদ্ধান্তের রিপোর্টে লিখুন estimand (ATE না ATT), DAG, কোন অনুমান যাচাই হয়েছে (balance, overlap, pre-trend, placebo) আর কোনটি হয়নি (unconfoundedness, exclusion), আর sensitivity. সম্ভব হলে একটি ছোট A/B test-ই সবচেয়ে সৎ উত্তর।

আরও গভীরে। ভিন্ন সময়ে ভিন্ন জেলায় fee বাড়লে (staggered adoption) সাধারণ two-way fixed effects DiD আগে-treated জেলাকে control বানিয়ে ভুল ওজন দিতে পারে; Goodman-Bacon (২০২১) আর Callaway–Sant'Anna (২০২১) এর বিশ্লেষণ ও বিকল্প দেন। Regression আর propensity model একসঙ্গে ব্যবহার করে “doubly robust” (AIPW) অনুমান যেকোনো একটি model ঠিক থাকলেই সামঞ্জস্যপূর্ণ। আর Pearl-এর do-calculus দেখায় backdoor ছাড়াও কোন DAG-এ প্রভাব শনাক্ত করা যায়, যেমন front-door criterion.

পরীক্ষা ও ইন্টারভিউতে। প্র: ATE আর ATT কবে আলাদা? উ: প্রভাব মানুষভেদে আলাদা আর treatment পাওয়া মানুষ বাকিদের থেকে আলাদা হলে। প্র: DAG L → T, L → Y, T → M → Y-এ কী ধরবেন? উ: শুধু L; M mediator. প্র: e = ০.২৫-এর একজন treated-এর IPW ওজন? উ: ১/০.২৫ = ৪. প্র: Control ৪০ → ৪৪, treated ৫০ → ৪৯; DiD? উ: −১ − ৪ = −৫. প্র: Feature importance বা SHAP কি কারণ বলে? উ: না, model কীসের ওপর ভর করে তা বলে (পর্ব ৫৭)।

৮ · AI-এর যুগে — causal দাবি যাচাই

“Coupon-এর প্রভাব বের করে দাও” বললে AI assistant দ্রুত কোড দেয়, কিন্তু ভুলগুলো প্রায়ই চুপচাপ থাকে:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
সব কলাম regression-এ ঢোকায়, treatment-এর পরের চলকসহMediator প্রভাব কাটে, collider bias আনেDAG আঁকুন; শুধু treatment-এর আগের চলক
SHAP বা feature importance-কে “প্রভাব” বলেPredictive ভর আর কারণ আলাদাestimand লিখে causal পদ্ধতি চান
পুরোনো বা বানানো API: cov_kws, DoWhy-র ভুল method নামError দেয় বা ভুল কিছু চলেনিজের version-এর docs; ছোট উদাহরণে চালান
Balance না দেখেই matching-এর ফল রিপোর্টMatching ব্যর্থ হলেও সংখ্যা আসেSMD টেবিল আর overlap দেখুন
DiD-এ সাধারণ SE, pre-trend নেইঅতি-আশাবাদী CI; ভাঙা অনুমান অদেখাCluster SE, pre-trend ছবি, placebo
“Causal” শব্দটি নিশ্চিত সুরে লেখেঅনুমান ছাড়া দাবি দাঁড়ায় নাঅনুমান আর sensitivity লেখা চান

প্রম্পটে ডেটার কাঠামো দিন, আসল গ্রাহকের ডেটা নয়; উত্তরের সঙ্গে যাচাইয়ের কোড চেয়ে নিন।

বাজারঘরের coupon ডেটা: কলাম orders, tenure, dhaka (coupon-এর আগে মাপা), coupon (0/1, marketing দল বেছে দিয়েছে), app_opens আর review (coupon-এর পরে মাপা), spend (পরের মাসের খরচ)। ১. একটি causal DAG প্রস্তাব করো, আর backdoor criterion দিয়ে বলো কোন চলক adjust করব, কোনগুলো করব না আর কেন। ২. ATE আর ATT-এর জন্য propensity score (scikit-learn LogisticRegression), nearest-neighbour matching আর IPW-এর কোড দাও; standardised mean difference টেবিল আর overlap যাচাইসহ। ৩. প্রতিটি অনুমানের bootstrap CI দাও, আর বলো কোন ক্ষেত্রে bootstrap বৈধ নয়। ৪. কোন অনুমানগুলো ডেটা দিয়ে যাচাই করা যায় না, আর একটি sensitivity analysis-এর পথ বলো। ৫. statsmodels বা DoWhy-র যে API ব্যবহার করবে, তার version আর docs-এর নাম বলো; নিশ্চিত না হলে স্পষ্ট বলো।

AI-এর দেওয়া causal বিশ্লেষণ হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১ধূমপান আর ফুসফুসের ক্যান্সারের বিতর্ক থেকে লুকানো confounder নিয়ে কী শেখা যায়?

১৯৫০ সালের দিকে Doll ও Hill (যুক্তরাজ্য) আর Wynder ও Graham (যুক্তরাষ্ট্র) ধূমপায়ীদের মধ্যে ফুসফুসের ক্যান্সার অনেক বেশি দেখান। মানুষকে এলোমেলোভাবে ধূমপান করানো নৈতিকভাবে অসম্ভব, তাই প্রমাণ ছিল পর্যবেক্ষণের। আধুনিক পরিসংখ্যানের অন্যতম স্থপতি Ronald Fisher এর বিরুদ্ধে যুক্তি দেন: হয়তো একটি জিনগত বৈশিষ্ট্য মানুষকে ধূমপানের দিকেও ঠেলে আর ক্যান্সারের ঝুঁকিও বাড়ায়। এটি ঠিক একটি লুকানো confounder-এর দাবি। পরে জানা যায়, তিনি তামাক শিল্পের পরামর্শক হিসেবেও কাজ করেছিলেন, যা তাঁর অবস্থান নিয়ে প্রশ্ন তোলে।

উত্তরটি এসেছিল Jerome Cornfield ও সহকর্মীদের ১৯৫৯ সালের প্রবন্ধে। তাঁরা দেখান, ভারী ধূমপায়ীদের ঝুঁকি যদি প্রায় নয় গুণ হয়, তবে লুকানো confounder-কে ধূমপায়ীদের মধ্যে অন্তত নয় গুণ বেশি প্রচলিত হতে হবে, তবেই পুরো সম্পর্ক ব্যাখ্যা হয়। এত শক্তিশালী অজানা কারণ অবিশ্বাস্য। এটি sensitivity analysis-এর প্রথম দিকের রূপ, আর আজকের E-value সেই যুক্তির আধুনিক সংস্করণ। পাশাপাশি আরও প্রমাণ জমেছিল: মাত্রা বাড়লে ঝুঁকি বাড়ে, ছাড়লে কমে, প্রাণী-পরীক্ষায় প্রক্রিয়া মেলে। Austin Bradford Hill ১৯৬৫ সালে এমন বিবেচনার একটি তালিকা দেন, যা আজও আলোচিত, যদিও একে যান্ত্রিক চেকলিস্ট হিসেবে ব্যবহার নিয়ে সমালোচনা আছে।

বাজারঘরে একই প্রশ্ন ছোট আকারে আসে: coupon-এর প্রভাব কি আসলে আয়ের মতো কোনো অমাপা কারণের? তাই শুধু একটি সংখ্যা নয়, জিজ্ঞাসা করুন লুকানো কারণটি কত শক্তিশালী হলে সিদ্ধান্ত উল্টাত, আর তেমন কারণ বাস্তবে আছে কি না।

মূল উপলব্ধি: পর্যবেক্ষণের ডেটায় কারণ-দাবি টেকে একাধিক স্বাধীন প্রমাণ আর “কতটা শক্তিশালী confounder লাগত” প্রশ্নের সৎ উত্তর দিয়ে; একটি regression দিয়ে নয়।

প্র ০২পর্যবেক্ষণের পদ্ধতি কি randomised পরীক্ষার ফল ফিরিয়ে আনতে পারে? LaLonde-এর পরীক্ষা কী বলে?

১৯৭০-এর দশকে যুক্তরাষ্ট্রে National Supported Work নামের একটি চাকরি-প্রশিক্ষণ কর্মসূচি এলোমেলোভাবে অংশগ্রহণকারী বাছাই করে। তাই প্রশিক্ষণের প্রভাবের একটি বিশ্বাসযোগ্য মানদণ্ড ছিল। Robert LaLonde ১৯৮৬ সালে (American Economic Review) একটি চতুর কাজ করেন: randomised control দলটি সরিয়ে তার জায়গায় জাতীয় জরিপ থেকে নেওয়া সাধারণ মানুষ বসান, তারপর সে সময়ের প্রচলিত regression আর selection model দিয়ে প্রভাব মাপেন। অনেক অনুমান মানদণ্ড থেকে অনেক দূরে ছিল, কিছু ছিল উল্টো দিকে, আর কোন অনুমান ঠিক তা ডেটা দেখে বোঝার উপায় ছিল না।

১৯৯৯ সালে Dehejia আর Wahba propensity score matching দিয়ে একই ডেটায় মানদণ্ডের কাছাকাছি ফল দেখান, যা পদ্ধতিটিকে জনপ্রিয় করে। কিন্তু Smith আর Todd (২০০৫) দেখান, ফলটি নমুনা বাছাই আর specification-এর ওপর বেশ নির্ভরশীল। এখান থেকে দুটি শিক্ষা আসে। প্রথমত, overlap: সাধারণ জনসংখ্যার অনেকে প্রশিক্ষণ-প্রার্থীদের মতো নন, তাই তুলনীয় মানুষ কম; propensity score দেখালে তা ধরা পড়ে। দ্বিতীয়ত, কোন চলক মাপা হয়েছে সেটিই মূল: আগের বছরগুলোর আয় না থাকলে কোনো পদ্ধতি বাঁচায় না।

এই পর্বের simulation-এ matching আর IPW সত্যের কাছে এসেছে, কারণ সব confounder মাপা ছিল আর আমরা তা জানতাম। বাস্তবে এই নিশ্চয়তা থাকে না। তাই পর্যবেক্ষণের বিশ্লেষণকে সম্ভব হলে একটি ছোট randomised পরীক্ষা দিয়ে মিলিয়ে নেওয়া ভালো অভ্যাস।

মূল উপলব্ধি: পদ্ধতি শুধু মাপা চলকের ভারসাম্য আনে; সত্যের কাছে পৌঁছানো নির্ভর করে সঠিক চলক মাপা হয়েছে কি না আর তুলনীয় মানুষ আছে কি না তার ওপর।

প্র ০৩Card–Krueger-এর ন্যূনতম মজুরি গবেষণা থেকে DiD-এর শক্তি আর দুর্বলতা কী বোঝা যায়?

১৯৯২ সালে New Jersey ন্যূনতম মজুরি বাড়ায়, পাশের Pennsylvania-র পূর্বাঞ্চল বাড়ায়নি। David Card আর Alan Krueger দুই রাজ্যের কয়েকশো fast-food দোকানে বৃদ্ধির আগে ও পরে কর্মী সংখ্যা জরিপ করেন, আর ১৯৯৪ সালে (American Economic Review) জানান যে New Jersey-তে কর্মসংস্থান কমার প্রমাণ পাননি। সরল অর্থনৈতিক তত্ত্বের প্রত্যাশার বিপরীত এই ফল তীব্র বিতর্ক জাগায়। Neumark আর Wascher (২০০০) payroll রেকর্ড দিয়ে ভিন্ন ফল দাবি করেন; Card আর Krueger একই বছরে সরকারি ডেটা দিয়ে উত্তর দেন।

শক্তিটি এই পর্বের 2×2 টেবিলের মতোই: দুই রাজ্যের মাত্রার পার্থক্য আর একই সময়ের সাধারণ অর্থনৈতিক পরিবর্তন দুটিই বাদ যায়। দুর্বলতাও একই জায়গায়: parallel trends. দুই রাজ্যের অর্থনীতি কি সত্যিই একই পথে চলছিল? মাত্র দুটি “cluster” (দুই রাজ্য) থাকায় SE কতটা বিশ্বাসযোগ্য? মজুরি বাড়ার খবর আগে থেকে জানা থাকলে দোকানগুলো আগেই সমন্বয় করেছিল কি? এই প্রশ্নগুলো পরে DiD-এর পদ্ধতিগত উন্নতি এনেছে: event-study ছবি, বেশি cluster, synthetic control-এর মতো পদ্ধতি।

বাজারঘরে delivery fee-র বিশ্লেষণেও একই প্রশ্ন: fee কেন ওই ছয় জেলায়? যদি সেখানে চাহিদা আগে থেকেই নামছিল বলেই fee বাড়ানো হয়, তবে এই পর্বের “ভাঙা” জগতের মতো দ্বিগুণ বড় প্রভাব দেখা যাবে।

মূল উপলব্ধি: DiD-এর বিশ্বাসযোগ্যতা নির্ভর করে treatment কেন ওই দলে গেল তার গল্পের ওপর; pre-trend, placebo আর যথেষ্ট cluster সেই গল্পের যাচাই, প্রমাণ নয়।

প্র ০৪Pearl-এর DAG আর Rubin-এর potential outcomes: প্রতিদ্বন্দ্বী, না পরিপূরক?

দুটি ঐতিহ্য প্রায় আলাদা পথে বড় হয়েছে। Rubin-এর potential outcomes কাঠামো পরিসংখ্যান, অর্থনীতি আর চিকিৎসা-গবেষণায় প্রচলিত; এখানে মূল ভাষা estimand (ATE, ATT), treatment assignment-এর নিয়ম আর unconfoundedness. Pearl-এর DAG আর do-calculus কম্পিউটার বিজ্ঞান আর epidemiology-তে জনপ্রিয়; এখানে মূল ভাষা গ্রাফ, পথ আর d-separation. Pearl ২০১১ সালে Turing Award পান, আংশিকভাবে এই কাজের জন্য।

দুই পক্ষের মধ্যে প্রকাশ্য তর্কও হয়েছে। Pearl যুক্তি দিয়েছেন, শুধু “X ধরলে unconfoundedness সত্য” বলা যথেষ্ট নয়; কেন সত্য তা দেখাতে একটি গ্রাফ দরকার, নইলে collider-এর মতো ফাঁদ চোখ এড়ায়। Rubin ঘরানার অনেকে বলেছেন, বড় জটিল গ্রাফ অনেক যাচাই-অযোগ্য দাবি একসঙ্গে চাপায়, আর নকশা (randomisation, matching) নিয়ে চিন্তাই বেশি কাজের। Guido Imbens ২০২০ সালে দুটি কাঠামোর তুলনা করে একটি দীর্ঘ প্রবন্ধ লেখেন, যেখানে তিনি অর্থনীতির প্রয়োগে potential outcomes-কে বেশি কাজের বললেও DAG-এর উপযোগিতা স্বীকার করেন।

ব্যবহারিক দিক থেকে দুটি একসঙ্গে ভালো চলে, যেমন এই পর্বে: DAG দিয়ে ঠিক করা হয়েছে কোন চলক ধরতে হবে (L হ্যাঁ, M আর R না), আর potential outcomes দিয়ে ঠিক করা হয়েছে কী মাপছি (ATE না ATT) এবং selection bias কোথা থেকে আসে। গাণিতিকভাবে দুটির মধ্যে অনেক অনুবাদও সম্ভব।

মূল উপলব্ধি: DAG বলে কোন চলক ধরবেন; potential outcomes বলে ঠিক কোন সংখ্যা মাপছেন। ভালো বিশ্লেষণে দুটিই লিখিত থাকে।

প্র ০৫উচ্চ AUC-এর একটি churn model কেন বলে না coupon কাকে দিলে লাভ?

Kleinberg, Ludwig, Mullainathan আর Obermeyer ২০১৫ সালে (American Economic Review-এর Papers and Proceedings) “prediction policy problems” নামে একটি পার্থক্য স্পষ্ট করেন। কিছু সিদ্ধান্তে শুধু ভালো অনুমান লাগে: বৃষ্টির সম্ভাবনা বেশি হলে ছাতা নিন; ছাতা বৃষ্টি বদলায় না। কিছু সিদ্ধান্তে লাগে হস্তক্ষেপের প্রভাব: বৃষ্টির নাচ কি বৃষ্টি আনে? প্রথমটিতে machine learning দারুণ; দ্বিতীয়টিতে causal inference দরকার।

Coupon দ্বিতীয় ধরনের। একটি churn model বলে কে চলে যাবেন, কিন্তু coupon পেলে তিনি থাকবেন কি না, তা বলে না। কেউ coupon ছাড়াও থাকতেন, কেউ coupon দিয়েও যাবেন, আর কারও ক্ষেত্রে coupon উল্টো ফলও দিতে পারে। প্রশ্নটি তাই প্রতিটি মানুষের জন্য Y(১) − Y(০), অর্থাৎ CATE বা uplift; পর্ব ৬৫ (Customer analytics — RFM, retention, churn আর lifetime value) এর একটি প্রাথমিক রূপ দেখাবে। এই পর্বের LinearDML-এর রেখা (প্রভাব orders-এর সঙ্গে বাড়ে) ঠিক এই ধরনের প্রশ্নের উত্তর দেয়।

একই ভুলের আরেক রূপ SHAP বা feature importance-কে কারণ ভাবা। পর্ব ৫৭-এর SHAP বলে model কোন feature-এর ওপর ভর দিয়েছে; ডেটায় confounder থাকলে model একটি proxy-র ওপর ভর দিতে পারে, যা বদলালে ফল বদলাবে না। আর ডেটা যদি অতীতের coupon-নীতিতে তৈরি হয়, তবে model শেখে সেই নীতির ছাপ, coupon-এর প্রভাব নয়।

মূল উপলব্ধি: “কী ঘটবে” আর “আমি কিছু করলে কী বদলাবে” আলাদা প্রশ্ন; দ্বিতীয়টির উত্তর দেয় randomised পরীক্ষা বা স্পষ্ট অনুমানসহ causal পদ্ধতি, শুধু একটি ভালো predictive model নয়।

অনুশীলন

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

  1. হাতের হিসাব: (ক) তিনজন গ্রাহক: Y(০) = ১০০, ২০০, ৩০০; Y(১) = ১৫০, ২২০, ৩১০; শুধু তৃতীয়জন coupon পেয়েছেন। ATE, ATT আর সরল পার্থক্য কত? (খ) Control জেলায় অর্ডার ৮০ → ৮৪, treated জেলায় ৯০ → ৮৮. DiD কত? (গ) e = ০.৮-এর একজন treated আর e = ০.৮-এর একজন control-এর IPW ওজন কত? (ঘ) RR = ১.৫ হলে E-value কত?

    (ক) প্রভাব ৫০, ২০, ১০; ATE = ৮০ ÷ ৩ ≈ ২৬.৭. ATT = ১০ (শুধু তৃতীয়জন)। সরল পার্থক্য = ৩১০ − (১০০ + ২০০)/২ = ১৬০; এর মধ্যে selection bias = ৩০০ − ১৫০ = ১৫০, আর ১০ + ১৫০ = ১৬০.

    (খ) (৮৮ − ৯০) − (৮৪ − ৮০) = −২ − ৪ = −৬.

    (গ) Treated: ১/০.৮ = ১.২৫ (পাওয়ারই কথা ছিল, তাই হালকা)। Control: ১/(১ − ০.৮) = ৫ (না পাওয়া বিরল, তাই ভারী)।

    (ঘ) ১.৫ + √(১.৫ × ০.৫) = ১.৫ + ০.৮৬৬ ≈ ২.৩৭.

  2. সমালোচনা ও নকশা: একটি marketing রিপোর্ট: “যাঁরা coupon ব্যবহার করেছেন তাঁরা পরের মাসে ২.৭ গুণ বেশি কিনেছেন। আমরা বয়স, জেলা, গত মাসের অর্ডার, coupon-এর পরের app খোলার সংখ্যা আর review দেওয়া কি না, সব control করেছি; p < 0.001. তাই সবাইকে coupon দেওয়া উচিত।” কী কী সমস্যা, আর কীভাবে নতুন করে সাজাবেন?

    সমস্যা: (১) “ব্যবহার করেছেন” মানে নিজে বেছে নেওয়া: যাঁরা কিনতেই যাচ্ছিলেন, তাঁরাই বেশি ব্যবহার করেন। (২) App খোলা mediator; ধরলে প্রভাবের অংশ মুছে যায়। (৩) Review collider; ধরলে ভুয়া সম্পর্ক। (৪) p-value ছোট মানে এলোমেলোতা কম, bias নয়। (৫) “যাঁরা পেয়েছেন” তাঁদের ফল (ATT-ঘেঁষা) থেকে “সবাইকে দিলে” (ATE) সিদ্ধান্ত টানা। (৬) খরচের বিপরীতে লাভ মাপা হয়নি।

    নতুন নকশা: সবচেয়ে ভালো, একটি A/B test: এলোমেলো দলে coupon পাঠানো, পাঠানোর প্রভাব (intention-to-treat) মাপা। তা না হলে: estimand লেখা, DAG আঁকা, শুধু coupon-এর আগের চলক ধরা, propensity score-এ overlap আর SMD দেখা, IPW বা matching, bootstrap CI, sensitivity, আর লাভ-ক্ষতির হিসাব।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    t = np.array([1, 1, 0, 0, 0])
    y = np.array([10, 14, 6, 8, 4])
    e = np.array([0.8, 0.5, 0.5, 0.2, 0.2])
    w = np.where(t == 1, 1 / e, 1 / (1 - e))
    print(w)
    print(y[t == 1].mean() - y[t == 0].mean())
    print(round(np.average(y[t == 1], weights=w[t == 1]) - np.average(y[t == 0], weights=w[t == 0]), 2))
    [1.25 2.   2.   1.25 1.25]
    6.0
    6.46

    ওজন: treated ১/০.৮ = ১.২৫ আর ১/০.৫ = ২; control ১/০.৫ = ২, ১/০.৮ = ১.২৫ দুবার। সরল পার্থক্য ১২ − ৬ = ৬. ওজনসহ treated গড় (১২.৫ + ২৮) ÷ ৩.২৫ = ১২.৪৬; control (১২ + ১০ + ৫) ÷ ৪.৫ = ৬.০; পার্থক্য ৬.৪৬. আউটপুট একটি ডেস্কটপ run-এ যাচাই করা।

  4. কোড লিখুন: অংশ ৫-এর দুটি জগতে (fee_panel() আর fee_panel(trend_gap=-0.6)) district আর week-এর fixed effect-সহ DiD চালান: orders ~ treated:post + C(district) + C(week), জেলা ধরে cluster SE. তারপর treated-এর নিজস্ব রৈখিক ধারা (+ treated:week) যোগ করে আবার চালান। কী আশা করেন?
    import statsmodels.formula.api as smf
    from bazar_causal import fee_panel
    for name, p in [("parallel", fee_panel()), ("diverging", fee_panel(trend_gap=-0.6))]:
        for f in ["orders ~ treated:post + C(district) + C(week)",
                  "orders ~ treated:post + C(district) + C(week) + treated:week"]:
            fit = smf.ols(f, data=p).fit(cov_type="cluster", cov_kwds={"groups": p.district})
            print(name, "+trend" if "treated:week" in f else "TWFE  ",
                  round(fit.params["treated:post"], 2), round(fit.bse["treated:post"], 2))

    একটি ডেস্কটপ run-এ (Python 3.14.4, statsmodels 0.15.0) আউটপুট:

    parallel TWFE   -5.35 0.53
    parallel +trend -7.28 0.98
    diverging TWFE   -10.15 0.53
    diverging +trend -7.28 0.98

    সুষম panel আর এক সময়ে treatment-এ fixed effect-সহ অনুমান সরল 2×2-এর সমান (−৫.৩৫, −১০.১৫); SE সামান্য আলাদা। রৈখিক ধারা যোগ করলে দুই জগতেই −৭.২৮: ভাঙা জগতের ধারা সরে গেছে, কিন্তু আসল −৫ নয়। কারণ পুরো ১৬ সপ্তাহের ধারা অনুমানের সময় fee-র পরের হঠাৎ পতনের খানিকটাও “ধারা” হিসেবে গিলে নেয়; আর ঠিক জগতে অপ্রয়োজনীয় ধারা যোগে অনুমান বিগড়েছে, SE প্রায় দ্বিগুণ। ধারা অনুমান করা হয় শুধু আগের সপ্তাহ থেকে, বা event-study ছবি দেখে সিদ্ধান্ত নেওয়া হয়।

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

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

ক্রমপর্যবেক্ষণের ডেটা থেকে একটি treatment-এর প্রভাব মাপার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. প্রশ্ন থেকে estimand ঠিক করা (ATE না ATT)
  2. DAG এঁকে adjustment set বাছা
  3. Propensity score-এ overlap আর SMD balance যাচাই
  4. প্রভাব অনুমান আর তার CI
  5. Refutation, sensitivity আর যাচাই-অযোগ্য অনুমান রিপোর্টে লেখা

একটি DAG: L → T, L → Y, T → Y, T → M → Y, T → R ← Y. T-এর Y-এর ওপর মোট প্রভাব মাপতে কোন adjustment set বৈধ?

  • {L, M, R}
  • {L}
  • {L, M}
  • কিছুই না

একটি দোকানে coupon এলোমেলোভাবে দেওয়া হয়, আর delivery দেরি coupon থেকে স্বাধীন। দুটিই review লেখার সম্ভাবনা বাড়ায়। শুধু review লেখা গ্রাহকদের মধ্যে coupon আর দেরির সম্পর্ক কেমন দেখা যাবে?

  • শূন্য, কারণ দুটি আসলে স্বাধীন
  • ধনাত্মক
  • ঋণাত্মক, যদিও আসলে কোনো কারণ-সম্পর্ক নেই
  • বলা অসম্ভব

Control জেলায় সাপ্তাহিক অর্ডার ৪০ থেকে ৪৪ হয়েছে, আর fee বাড়ানো জেলায় ৫০ থেকে ৪৯. Difference-in-differences অনুমান কত?

  • −5
  • −1
  • +5
  • +3

IPW দিয়ে ATE অনুমানে একজন treated গ্রাহকের propensity score 0.25. তাঁর ওজন কত?

  • 0.25
  • 1.33
  • 0.75
  • 4

প্রতিটি coupon-পাওয়া গ্রাহকের জন্য propensity score-এ সবচেয়ে কাছের একজন না-পাওয়া গ্রাহক খুঁজে জোড়ার পার্থক্যের গড় নিলে কোন estimand পাওয়া যায়?

  • ATE
  • শুধু না-পাওয়াদের গড় প্রভাব
  • ATT
  • প্রতিটি মানুষের ব্যক্তিগত প্রভাব

একটি DiD বিশ্লেষণে treatment-এর আগের সপ্তাহগুলোতে দুই দলের ধারা প্রায় সমান্তরাল, আর placebo test-এর CI শূন্য ছোঁয়। কোন সিদ্ধান্তটি সবচেয়ে ঠিক?

  • Parallel trends প্রমাণিত
  • অনুমান ভাঙার দৃশ্যমান চিহ্ন নেই, তবু তা যাচাই-অযোগ্য অনুমান হিসেবেই থাকে
  • এখন সাধারণ OLS SE ব্যবহার করা যায়
  • আসল প্রভাবও তাই শূন্য

শব্দকোষ

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

মনে রাখুন

  1. দুটি সম্ভাব্য ফল, একটি দেখা: ATE সবার গড় প্রভাব, ATT পাওয়াদের; সরল পার্থক্য = ATT + selection bias. Randomisation bias সরায়।
  2. DAG দেখে adjust: backdoor পথ বন্ধ করুন (confounder ধরুন), কিন্তু treatment-এর পরের mediator বা collider ধরবেন না; সারি বাছাইও collider ধরা হতে পারে।
  3. Propensity score: matching-এ ATT, IPW-তে (১/e, ১/(১ − e)) ATE; শেষে SMD < ০.১ আর overlap যাচাই।
  4. DiD: (treated-এর পরিবর্তন) − (control-এর পরিবর্তন); parallel trends-এর ওপর দাঁড়ায়। Cluster SE, pre-trend ছবি আর placebo দিন।
  5. সৎ সীমা: unconfoundedness আর exclusion যাচাই-অযোগ্য; E-value, refuter আর সম্ভব হলে একটি A/B test দিয়ে দাবি মজবুত করুন।

পরের পর্বে: পর্ব ৬৫ (Customer analytics — RFM, retention, churn আর lifetime value): বাজারঘরের গ্রাহকদের ভাগ করা, ধরে রাখা আর কাকে coupon দিলে লাভ, তার প্রাথমিক uplift ভাবনা।

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

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