পাঠ ১৫ · ২৫-এর মধ্যে · মডিউল ২

groupby, merge ও pivot

groupby, merge, pivot
৮ মিনিট পড়া মধ্যম · Intermediate Pandas কোডসহ

এই পাঠে যা শিখবেন

  • split-apply-combine — কেন এটি ডেটা বিশ্লেষণের সর্বজনীন pattern
  • single ও multi-column groupby + multiple aggregation
  • transform বনাম agg বনাম apply — তিনটির পার্থক্য
  • merge — চার ধরনের join, on/left_on/right_on, suffixes
  • concat বনাম merge — কখন কোনটা
  • pivot_table, crosstab, melt — wide ↔ long রূপান্তর
  • AI feature engineering — group-based features

১ · groupby intuition — split-apply-combine

কল্পনা করুন আপনার কাছে বাংলাদেশের ৬৪ জেলার ১০ লক্ষ বিক্রয়-রেকর্ড আছে। প্রশ্ন — "প্রতিটি জেলার মোট বিক্রয় কত?" উত্তর পেতে আপনি ৬৪ বার একই কাজ করবেন: ফিল্টার → যোগ → ফলাফল। এটাই groupbygroupbyPandas-এর সবচেয়ে শক্তিশালী operation। ডেটা একটি column-এর মান অনুসারে ভাগ করে, প্রতিটি ভাগে function চালায়, তারপর ফলাফল একত্রিত করে। SQL-এর GROUP BY-এর সরাসরি analog।-এর কাজ — কিন্তু একটি লাইনে।

তিন ধাপ — split-apply-combine

১) Split: একটি কলামের মান অনুসারে DataFrame কে ছোট ছোট গ্রুপে ভাগ।
২) Apply: প্রতিটি গ্রুপে একটি function (sum, mean, count, custom) চালান।
৩) Combine: সব ফলাফল আবার একটি ফলাফল-DataFrame-এ জোড়া।

groupby = ক্লাসে বিভাগ-ভিত্তিক গড়। শিক্ষক সব ছাত্রের নম্বর-শীট হাতে নিয়ে — প্রথমে বিভাগ অনুসারে স্তূপ করেন (Science, Commerce, Arts), তারপর প্রতিটি স্তূপের গড় বের করেন, শেষে একটি সারাংশ লেখেন। Pandas এই তিন কাজ একটি লাইনে করে।

২ · single-column groupby — হাতে-কলমে

চলুন বাস্তব বাংলাদেশী ডেটা দিয়ে দেখি — শহর-ভিত্তিক বিক্রয়।

Python · Pandas
import pandas as pd

# বাংলাদেশী shop-এর বিক্রয় ডেটা
sales = pd.DataFrame({
    "city":  ["Dhaka", "Chittagong", "Dhaka", "Sylhet", "Dhaka", "Chittagong", "Sylhet"],
    "sales": [12000, 8500, 9000, 4200, 15000, 7300, 5100],
    "qty":   [10, 7, 8, 3, 12, 6, 4],
})
print(sales)

# শহর-ভিত্তিক মোট বিক্রয়
print("\n--- প্রতি শহরের মোট বিক্রয় ---")
print(sales.groupby("city")["sales"].sum())

# একাধিক aggregate একসাথে
print("\n--- describe() — সব stat একসাথে ---")
print(sales.groupby("city")["sales"].describe())

    
groupby("city") তিনটি গ্রুপ তৈরি করে — Dhaka, Chittagong, Sylhet। তারপর ["sales"].sum() প্রতিটিতে যোগ চালায়। ফলাফল — একটি Series যেখানে index = শহরের নাম।

৩ · multi-column groupby — দুই বা তিন স্তরের

একাধিক column-এ একসাথে group করা যায় — তখন result-এ MultiIndex তৈরি হয়।

Python · Pandas
import pandas as pd

orders = pd.DataFrame({
    "city":      ["Dhaka", "Dhaka", "Dhaka", "Chittagong", "Chittagong", "Sylhet"],
    "category":  ["fashion", "electronics", "fashion", "fashion", "electronics", "fashion"],
    "amount":    [2300, 18000, 1800, 1500, 22000, 1200],
    "month":     ["Jan", "Jan", "Feb", "Jan", "Feb", "Feb"],
})

# শহর + ক্যাটেগরি — দুই স্তরের group
g = orders.groupby(["city", "category"])["amount"].sum()
print(g)

# unstack() দিয়ে wide-format
print("\n--- unstack করে table ---")
print(g.unstack(fill_value=0))

    
Result-এর index = (city, category) tuple। .unstack() দিয়ে দ্বিতীয় index level কে columns-এ রূপান্তর — Excel-এর মতো cross-tabulation দেখা যায়।

৪ · একাধিক aggregation — agg() ও named aggregation

একই গ্রুপে — একটি column-এ sum, আরেকটিতে mean, আরেকটিতে count — সব একসাথে।

Python · Pandas
import pandas as pd

txn = pd.DataFrame({
    "customer": ["Karim", "Karim", "Salma", "Salma", "Salma", "Rahim"],
    "amount":   [500, 1200, 800, 300, 950, 2100],
    "items":    [2, 4, 3, 1, 3, 5],
})

# Method 1: dict-form agg
res = txn.groupby("customer").agg({"amount": "sum", "items": "mean"})
print(res)

# Method 2: named aggregation (Pandas 0.25+) — পরিচ্ছন্ন column নাম
res2 = txn.groupby("customer").agg(
    total_spend=("amount", "sum"),
    avg_items=("items", "mean"),
    visits=("amount", "count"),
)
print("\n--- named aggregation ---")
print(res2)

# Method 3: একই column-এ একাধিক function
res3 = txn.groupby("customer")["amount"].agg(["sum", "mean", "max"])
print("\n--- একই column, একাধিক agg ---")
print(res3)

    
Named aggregation সবচেয়ে পরিষ্কার — column name আপনি নিজে দিচ্ছেন, MultiIndex column ঝামেলা নেই। Production কোডে এটাই preferred।

৫ · transform — group-aware feature এবং filter

agg() গ্রুপ-প্রতি একটি মান দেয় (shape ছোট হয়)। কিন্তু transformtransformপ্রতিটি গ্রুপে function চালিয়ে — মূল DataFrame-এর shape অপরিবর্তিত রেখে — group-aware মান প্রতিটি row-এ broadcast করে। "প্রতিটি ক্রেতার গড় তার নিজের প্রতি লেনদেনে" — এই ধরনের feature বানাতে অপরিহার্য। মূল shape অপরিবর্তিত রাখে — প্রতিটি row পায় তার গ্রুপের মান। ML feature engineering-এ অপরিহার্য।

Python · Pandas
import pandas as pd

df = pd.DataFrame({
    "city":  ["Dhaka", "Dhaka", "Chittagong", "Chittagong", "Sylhet"],
    "sales": [100, 300, 200, 400, 250],
})

# Group total — প্রতিটি row পাবে তার শহরের মোট
df["city_total"] = df.groupby("city")["sales"].transform("sum")

# Percent of group total — অসাধারণ feature!
df["pct_of_city"] = df["sales"] / df["city_total"] * 100

# Z-score within group
df["z_in_city"] = df.groupby("city")["sales"].transform(
    lambda x: (x - x.mean()) / x.std() if x.std() else 0
)
print(df)

# filter — পুরো group রাখা/ফেলা একটি condition-এ
big_cities = df.groupby("city").filter(lambda g: g["sales"].sum() > 300)
print("\n--- শুধু সেই শহর যেখানে মোট > 300 ---")
print(big_cities)

    
transform-এর shape = মূল df-এর shape, কিন্তু মান group-aware। "এই গ্রাহক তার শহরের গড় থেকে কতটা ভিন্ন" — এই ধরনের feature ML মডেলকে অসাধারণ signal দেয়।

৬ · merge — দু'টি DataFrame জোড়া (SQL JOIN)

বাস্তবে ডেটা প্রায়ই একাধিক টেবিলে — ক্রেতা info এক table-এ, লেনদেন আরেকটায়। mergemergeদু'টি DataFrame কে এক বা একাধিক common column ("key") অনুসারে জোড়া। SQL-এর JOIN-এর Pandas-equivalent। চার ধরনের: inner (intersection), outer (union), left, right। Default = inner। এদের key-column অনুসারে জোড়ে।

Python · Pandas
import pandas as pd

customers = pd.DataFrame({
    "cust_id": [1, 2, 3, 4],
    "name":    ["Karim", "Salma", "Rahim", "Mitu"],
    "city":    ["Dhaka", "Sylhet", "Dhaka", "Chittagong"],
})

orders = pd.DataFrame({
    "cust_id": [1, 1, 2, 3, 5],
    "amount":  [500, 1200, 800, 300, 999],
    "month":   ["Jan", "Feb", "Jan", "Mar", "Feb"],
})

# inner join — দুই tableই থাকতে হবে (default)
inner = pd.merge(customers, orders, on="cust_id", how="inner")
print("--- INNER ---")
print(inner)

# left join — customers-এর সব row রাখো
left = pd.merge(customers, orders, on="cust_id", how="left")
print("\n--- LEFT (Mitu-র order নেই, NaN পাবে) ---")
print(left)

# outer join — দুই table-এর সব row, missing হলে NaN
outer = pd.merge(customers, orders, on="cust_id", how="outer", indicator=True)
print("\n--- OUTER (cust_id=5 customers-এ নেই, _merge দেখুন) ---")
print(outer)

# ভিন্ন key নাম হলে — left_on / right_on
products = pd.DataFrame({"pid": [10, 11], "title": ["Shirt", "Phone"]})
order_lines = pd.DataFrame({"product_id": [10, 11], "qty": [2, 1]})
joined = pd.merge(products, order_lines, left_on="pid", right_on="product_id")
print("\n--- left_on / right_on ---")
print(joined)

    
Default merge = inner — অর্থাৎ যে rows দুই table-এই আছে শুধু সেগুলো থাকবে। Full join চাইলে how="outer" দিন। indicator=True দিলে _merge column পাবেন — কোন row কোথা থেকে এসেছে দেখায় ("left_only", "right_only", "both")।

৭ · concat বনাম merge — কখন কোনটা

দু'টি প্রায়ই গুলিয়ে যায়। মূল পার্থক্য — concat "চাপিয়ে রাখে" (vertical/horizontal stacking), merge "key দিয়ে যোগ করে"।

Python · Pandas
import pandas as pd

jan = pd.DataFrame({"city": ["Dhaka", "Sylhet"], "sales": [100, 50]})
feb = pd.DataFrame({"city": ["Dhaka", "Sylhet"], "sales": [120, 60]})

# concat — উপর-নিচে চাপিয়ে রাখা
stacked = pd.concat([jan, feb], keys=["Jan", "Feb"])
print("--- concat (vertical) ---")
print(stacked)

# concat horizontal
side = pd.concat([jan, feb], axis=1)
print("\n--- concat (horizontal) ---")
print(side)

# merge — key দিয়ে যোগ
m = pd.merge(jan, feb, on="city", suffixes=("_jan", "_feb"))
print("\n--- merge on city ---")
print(m)

    
concat = same-shape DataFrames stack। merge = different-shape DataFrames-কে key-relation দিয়ে যোগ। suffixes দিয়ে duplicate column-এ tag লাগান।
Cartesian explosion সাবধান! দু'টি table-এ key duplicate থাকলে — merge পরে rows সংখ্যা বিস্ফোরিত। ১০০-row × ১০০-row → ১০,০০০ row। সর্বদা merge-এর আগে df.duplicated(subset=["key"]).sum() দিয়ে check করুন। key dtype-ও মেলান (int বনাম str — silent bug)।

৮ · pivot_table — Excel-এর pivot, Pandas-এ

pivotpivot_tablelong-format ডেটাকে wide-format-এ রূপান্তর — index একটি column, columns আরেকটি, values তৃতীয়, এবং duplicate combination থাকলে aggfunc দিয়ে summarize। Excel-এর pivot table-এর সরাসরি Pandas-equivalent।_table — long-format ডেটাকে wide-format cross-tabulation-এ পরিণত করে।

Python · Pandas
import pandas as pd

sales = pd.DataFrame({
    "city":     ["Dhaka", "Dhaka", "Chittagong", "Chittagong", "Sylhet", "Dhaka"],
    "month":    ["Jan", "Feb", "Jan", "Feb", "Jan", "Feb"],
    "category": ["fashion", "fashion", "electronics", "fashion", "fashion", "electronics"],
    "amount":   [1000, 1500, 2200, 800, 600, 3000],
})

# index = সারি, columns = কলাম, values = সেল
pt = sales.pivot_table(
    index="city",
    columns="month",
    values="amount",
    aggfunc="sum",
    fill_value=0,
)
print(pt)

# একাধিক aggfunc + margins (grand total)
pt2 = sales.pivot_table(
    index="city",
    columns="category",
    values="amount",
    aggfunc=["sum", "mean"],
    fill_value=0,
    margins=True,
    margins_name="মোট",
)
print("\n--- multi-agg + margins ---")
print(pt2)

    
fill_value=0 — যে combination data-তে নেই (যেমন Sylhet-Feb), সেখানে 0 বসাবে। margins=True — সারি/কলামের মোট দেখাবে।

৯ · crosstab ও melt — দ্রুত উল্লেখ

pd.crosstab — pivot_table-এর simpler form, frequency count-এর জন্য বিশেষ।
meltmeltpivot-এর বিপরীত — wide-format ডেটাকে long-format-এ রূপান্তর। Tidy data principle-এর জন্য অপরিহার্য। Visualization library (seaborn, plotly) প্রায়ই long format চায়। — pivot-এর বিপরীত: wide → long, "tidy" format। Visualization-এ অপরিহার্য।

Python · Pandas
import pandas as pd

# crosstab — frequency
df = pd.DataFrame({
    "city":   ["Dhaka", "Dhaka", "Sylhet", "Sylhet", "Chittagong"],
    "gender": ["M", "F", "M", "F", "M"],
})
print(pd.crosstab(df["city"], df["gender"]))

# melt — wide → long
wide = pd.DataFrame({
    "city":  ["Dhaka", "Sylhet"],
    "Jan":   [100, 50],
    "Feb":   [120, 60],
    "Mar":   [130, 70],
})
long = wide.melt(id_vars="city", var_name="month", value_name="sales")
print("\n--- melt: wide → long ---")
print(long)

    

১০ · AI use case — per-customer feature engineering

ML মডেল training-এর আগে — প্রতিটি ক্রেতার behavioral summary বানাতে হয়। groupby + transform + merge — তিনটি একসাথে।

Python · Pandas — feature engineering
import pandas as pd

# কাঁচা লেনদেন
txn = pd.DataFrame({
    "cust_id": [1, 1, 1, 2, 2, 3, 3, 3, 3],
    "amount":  [500, 1200, 800, 300, 950, 2100, 50, 75, 1800],
    "category":["food", "fashion", "food", "food", "fashion",
                "electronics", "food", "food", "electronics"],
})

# প্রতি ক্রেতার feature summary (model-ready)
features = txn.groupby("cust_id").agg(
    total_spend=("amount", "sum"),
    avg_spend=("amount", "mean"),
    n_txn=("amount", "count"),
    max_spend=("amount", "max"),
    n_categories=("category", "nunique"),
).reset_index()

# Customer master table-এর সাথে merge
master = pd.DataFrame({
    "cust_id": [1, 2, 3, 4],
    "name":    ["Karim", "Salma", "Rahim", "Mitu"],
    "city":    ["Dhaka", "Sylhet", "Dhaka", "Chittagong"],
})

ml_ready = pd.merge(master, features, on="cust_id", how="left").fillna(0)
print(ml_ready)

# এই ml_ready DataFrame সরাসরি sklearn-এ X হিসেবে ব্যবহার যোগ্য

    
এটাই production ML pipeline-এর প্রথম ধাপ — raw transactional data → per-entity feature matrix। groupby-merge-এর এই pattern Daraz, bKash, Pathao — সব বাংলাদেশী tech কোম্পানির ML-এ চলে।
Split → Apply → Combine df.groupby("city")["sales"].sum() মূল DataFrame city | sales Dhaka | 12000 Ctg | 8500 Dhaka | 9000 Sylhet | 4200 Dhaka | 15000 Ctg | 7300 Sylhet | 5100 ৭ rows split Split (3 গ্রুপ) Dhaka 12000 9000 15000 Chittagong 8500 7300 Sylhet 4200 5100 apply .sum() Combine (ফলাফল) city sales Dhaka 36000 Chittagong 15800 Sylhet 9300 ৩ rows · index = city dtype: int64 Merge — দুই table-এ key-column দিয়ে যোগ customers cust_id | name 1 | Karim 2 | Salma ⨝ on=cust_id orders cust_id | amount 1 | 1200 2 | 800 merged (inner) cust_id | name | amount 1 | Karim| 1200 2 | Salma| 800
উপরে: split-apply-combine — groupby-র অন্তর্নিহিত pattern। নিচে: merge — দু'টি table-কে key-column অনুসারে জোড়া।

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

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

প্র ০১ groupby-র split-apply-combine pattern এত powerful কেন? SQL-এর GROUP BY-র সাথে comparison — কোথায় এক, কোথায় ভিন্ন?

Hadley Wickham (R-এর dplyr-এর স্রষ্টা) ২০১১-তে "split-apply-combine" নামে এই pattern-কে formalize করেন। কিন্তু এটি প্রায় সব ডেটা বিশ্লেষণের অন্তর্নিহিত pattern — SQL, Spark, MapReduce, এমনকি Excel pivot — সবই এই pattern-এর variant।

কেন এত powerful:

  • Universality: ৯০% analytical question — "X-প্রতি Y-এর Z" — এই pattern-এ পড়ে। "শহর-প্রতি বিক্রয়ের গড়", "মাস-প্রতি গ্রাহকের সংখ্যা", "ক্যাটেগরি-প্রতি refund-হার"।
  • Composability: ফলাফল আবার একটি DataFrame — তার উপর আবার groupby করা যায়। চেইন তৈরি হয়।
  • Parallelism-friendly: প্রতিটি গ্রুপ স্বাধীন — তাই Spark, Dask সহজেই parallel-এ চালাতে পারে। এই কারণেই "group by" বিগ-ডেটা processing-এর মেরুদণ্ড।
  • Mental model: মানুষ এভাবেই চিন্তা করে — "এটাকে এই category-অনুসারে ভাগ করি, প্রতিটিতে এটা করি, তারপর মিলাই।"

SQL GROUP BY-র সাথে তুলনা:

  • মিল: দু'টোই split-apply-combine। SELECT city, SUM(sales) FROM t GROUP BY city ≡ df.groupby("city")["sales"].sum()।
  • SELECT clause-এর সীমাবদ্ধতা: SQL-এ GROUP BY-র পর শুধু grouped column বা aggregate-ই SELECT করা যায়। Pandas-এ কম restrictive।
  • HAVING vs filter: SQL-এর HAVING = Pandas-এর .filter()।
  • Window function: SQL-এর OVER (PARTITION BY) = Pandas-এর transform। দু'টোই — group-aware মান কিন্তু shape অপরিবর্তিত।
  • Custom function: Pandas-এ .apply(lambda) — যেকোনো Python function। SQL-এ user-defined function লিখতে হয়, ভিন্ন syntax।
  • Lazy বনাম eager: SQL সাধারণত query optimizer দিয়ে চলে — execution plan optimize হয়। Pandas eager — যেমন লিখবেন তেমন চলবে। বড় ডেটায় Pandas চেয়ে DuckDB/Polars দ্রুত।

কখন Pandas, কখন SQL:

  • ডেটা DB-তে আছে — SQL-এ যা possible তা DB-তেই করুন (network transfer কম)।
  • Iterative exploration — Pandas/Notebook সহজ।
  • ১০ million+ row — Polars / DuckDB / Spark বিবেচনা করুন।

মূল উপলব্ধি: split-apply-combine শুধু একটি API নয় — এটি ডেটা সম্পর্কে চিন্তা করার একটি ভাষা। এই pattern একবার আয়ত্ত হলে — SQL, Pandas, Spark — সব tool-ই familiar মনে হবে।

প্র ০২ agg, apply, transform — তিনটির subtle পার্থক্য কী? কোনটি কখন? Performance-এ কে এগিয়ে?

নতুন Pandas user-এর সবচেয়ে confusing চারটি method — agg, apply, transform, filter। তিনটি একই দেখায় কিন্তু ভিন্ন কাজ করে। Production কোডে সঠিকটি বাছাই important।

agg (aggregate):

  • Output shape: গ্রুপ-প্রতি একটি মান। Result-এ rows = unique groups সংখ্যা।
  • Function signature: Series → scalar (sum, mean, max, count)।
  • Use case: Summary statistics — "শহর-প্রতি মোট"।
  • Performance: দ্রুততম — Pandas built-in agg-এ Cython-optimized path।

transform:

  • Output shape: মূল DataFrame-এর shape অপরিবর্তিত। প্রতিটি row পায় তার গ্রুপের মান (broadcast)।
  • Function signature: Series → Series (same length) বা scalar (যা broadcast হবে)।
  • Use case: Group-aware feature বানানো — "এই row-এর মান গ্রুপের গড় থেকে কতটা ভিন্ন"। ML feature engineering-এ অপরিহার্য।
  • Performance: agg-এর চেয়ে কিছুটা ধীর (broadcast cost) কিন্তু apply-এর চেয়ে দ্রুত।

apply:

  • Output shape: flexible — scalar, Series, বা DataFrame; Pandas নিজে ঠিক করে কীভাবে combine করবে।
  • Function signature: পুরো sub-DataFrame → যা খুশি।
  • Use case: Complex per-group logic — "প্রতিটি গ্রাহকের top-3 ক্যাটেগরি", "প্রতিটি শহরের sales-এর time-series decomposition"।
  • Performance: সবচেয়ে ধীর — pure Python loop চলে। বড় ডেটায় bottleneck।

Decision tree:

  • একক summary (sum, mean) চাই → agg।
  • প্রতিটি row-তে group-aware মান চাই → transform।
  • পুরো গ্রুপ access করতে হবে complex logic-এ → apply। তবু আগে ভাবুন — agg/transform-এ করা যায় কিনা।
  • পুরো গ্রুপ keep/drop করতে হবে → filter।

Performance চিত্র (১০ লক্ষ rows-এ approximate):

  • groupby.sum() → ~৫০ ms (Cython path)
  • groupby.transform("sum") → ~১৫০ ms
  • groupby.apply(lambda x: x.sum()) → ~২ সেকেন্ড (Python loop!)

সাধারণ ভুল:

  • apply ব্যবহার সেখানে যেখানে agg বা transform সম্ভব — ১০x-৫০x ধীর।
  • transform-এ shape না মেলা function — Pandas error দেবে।
  • apply-এর return type inconsistent — কখনো Series, কখনো DataFrame — debug কঠিন।

মূল উপলব্ধি: তিনটি method-এর পার্থক্য — output shape এবং function signature-এ। নাম মুখস্থ না করে — "shape কী হবে?" প্রশ্ন করুন। সঠিকটি স্বয়ংক্রিয়ভাবে আসবে।

প্র ০৩ Merge কেন silent bugs-এর প্রধান উৎস? Duplicate key, mismatched dtype, accidental cartesian — কীভাবে detect ও prevent?

Production data pipeline-এর সবচেয়ে dangerous bug — merge-জনিত। কোনো error throw হয় না, ডেটার size সামান্য বদলায় (বা চুপচাপ বহুগুণ বাড়ে), এবং downstream metrics ভুল হয়। গ্রাহকের কাছে ভুল রিপোর্ট গেলেই ধরা পড়ে। তখন debug কঠিন।

(১) Duplicate key — cartesian explosion:

  • Scenario: customers table-এ cust_id unique হওয়ার কথা — কিন্তু data quality issue-এ একই ID দু'বার (e.g., manual entry ভুল)। Merge-এর পর — সেই গ্রাহকের প্রতিটি order দু'বার appear করবে।
  • Detect:
    • df.duplicated(subset=["key"]).sum() — merge-এর আগে।
    • pd.merge(... validate="one_to_many") — Pandas নিজে check করবে। validate options: "one_to_one", "one_to_many", "many_to_one", "many_to_many"।
    • merge-এর আগে-পরে len(df) compare করুন — সন্দেহজনক ভাবে বড় হলে red flag।
  • Prevent: Master tables-এ primary key constraint enforce করুন (DB-তে); ETL-এ validate parameter সবসময় দিন।

(২) Mismatched dtype — silent miss:

  • Scenario: এক table-এ cust_id = int (1, 2, 3), অন্যটিতে string ("1", "2", "3")। Merge হবে — কিন্তু কোনো match হবে না। Result empty বা partial।
  • Detect: df1["key"].dtype, df2["key"].dtype compare করুন। Result রহস্যজনকভাবে ছোট হলে — dtype check করুন।
  • Prevent: Merge-এর আগে explicit cast — df["key"] = df["key"].astype(int)। CSV load-এর পর সর্বদা dtype verify।

(৩) Accidental cartesian (many-to-many):

  • দু'টি table-এ key duplicate। Merge-এ — প্রতিটি combination row হবে। ১০০-row × ১০০-row → ১০,০০০ row।
  • Memory explode করতে পারে; ফলাফল গাণিতিকভাবে ভুল।
  • Prevent: validate="one_to_one" বা "one_to_many" সেট — Pandas auto-throw করবে।

(৪) NULL/NaN keys:

  • NaN ≠ NaN — তাই NaN-key rows কখনো match করে না, silently drop হয় (inner join-এ)।
  • Prevent: Merge-এর আগে df["key"].isna().sum() check। প্রয়োজনে fill বা drop।

(৫) Whitespace / case mismatch:

  • "Dhaka" vs "dhaka " (trailing space) — match হবে না।
  • Prevent: df["key"] = df["key"].str.strip().str.lower() standardize।

Defensive merge checklist (production code-এ):

  1. Both DataFrames-এ .shape log করুন।
  2. Key column-এ duplicate, NaN, dtype check।
  3. indicator=True, validate="one_to_many" দিন।
  4. Merge-এর পর _merge column distribution log করুন — "left_only" / "right_only" / "both"।
  5. Expected size assertion — assert len(merged) == expected।

মূল উপলব্ধি: Merge ৩-লাইন code, কিন্তু production-এ ৩০-লাইন defensive wrapper দরকার। যিনি এই discipline শেখেন — তার pipeline নির্ভরযোগ্য। যিনি শেখেন না — মাসে একবার "কেন total revenue ২x দেখাচ্ছে?" এই debug-এ রাত কাটান।

প্র ০৪ Wide format vs long format — কোনটি ML-এ ভাল, কোনটি visualization-এ? melt/pivot-এর philosophical role কী?

২০১৪-তে Hadley Wickham "Tidy Data" পেপারে formalize করেন: প্রতিটি variable একটি column, প্রতিটি observation একটি row, প্রতিটি observational unit একটি table। এটাই "long format" বা "tidy" format। কিন্তু বাস্তব ডেটা প্রায়ই wide-format-এ আসে (Excel-এর কারণে)। দু'টোর মধ্যে নিরন্তর রূপান্তর — Pandas-এর daily কাজ।

Wide format (Excel-style):

  • প্রতিটি unique মান-এর জন্য আলাদা column। যেমন: city | Jan | Feb | Mar — তিন মাস তিন কলাম।
  • সুবিধা: মানুষের পড়তে সহজ; spreadsheet-এ visually compact।
  • অসুবিধা: নতুন মাস যোগ করতে হলে — schema বদলাতে হয় (নতুন column)। SQL-এ কঠিন। ML মডেল-এ feature mismatch ঝুঁকি।

Long format (tidy):

  • প্রতিটি observation এক row। যেমন: city | month | sales — মাস একটি কলাম, মান আরেকটি।
  • সুবিধা: Schema stable; নতুন মাস → শুধু আরও row। groupby-friendly; visualization library-friendly।
  • অসুবিধা: মানুষের চোখে কম পরিচ্ছন্ন; row সংখ্যা বেশি।

ML-এ কোনটি?

  • Training matrix (X) — wide: প্রতিটি row = একটি sample, প্রতিটি column = একটি feature। sklearn এই format-ই চায়।
  • Storage / ETL — long: Raw events (txn, log, sensor) সবই long। ETL pipeline-এর প্রতিটি ধাপে long থাকে।
  • রূপান্তর: ETL শেষে — pivot করে wide ML-matrix বানান। তারপর train।
  • Time-series: sklearn-এ wide (lag features); deep learning sequence model-এ long (timestamp ordered)।

Visualization-এ কোনটি?

  • Long format universally জিতে। matplotlib (pyplot OO), seaborn, plotly, ggplot — সবাই long চায়।
  • Seaborn-এ sns.lineplot(data=df_long, x="month", y="sales", hue="city") — automatic legend, color-coding।
  • Wide format-এ multi-series plot manually loop করতে হয় — code দীর্ঘ, error-prone।

melt ও pivot — দু'টি দিকের সেতু:

  • pivot_table: long → wide। Reporting, ML-matrix building।
  • melt: wide → long। Visualization preparation, normalize for storage।
  • দু'টি inverse operation — যেকোনো ডেটা scientist-এর daily-use tool।

Philosophical গুরুত্ব:

  • "Tidy data" একটি data philosophy — relational database-এর normalization-এর সাথে গভীর সম্পর্কিত।
  • Long format = third normal form-এর কাছাকাছি — schema flexibility, query power।
  • Wide format = denormalized — read-friendly, কিন্তু update/extend কঠিন।
  • Modern data engineering — storage-এ long (Parquet, Delta), serving-এ wide (feature store) — সবচেয়ে সাধারণ pattern।

একটি সাবধানতা: Wide → long রূপান্তরে — মূল data lossless থাকে। কিন্তু long → wide-এ duplicate combination থাকলে — aggregation দরকার (pivot_table-এর aggfunc)। Information loss possible।

মূল উপলব্ধি: "এই ডেটা কোন format-এ?" — এই প্রশ্ন দিয়ে যেকোনো analysis শুরু করুন। সঠিক format = সঠিক tool = সহজ code = কম bug।

অনুশীলন

  1. groupby + agg: নিচের bKash-style লেনদেন ডেটায় — প্রতিটি গ্রাহকের মোট পাঠানো টাকা, লেনদেন সংখ্যা ও সর্বোচ্চ একক লেনদেন বের করুন (named aggregation ব্যবহার করুন)।
    txn = pd.DataFrame({
        "sender": ["A", "A", "B", "B", "B", "C"],
        "amount": [200, 1500, 300, 800, 50, 5000],
    })
    import pandas as pd
    txn = pd.DataFrame({
        "sender": ["A", "A", "B", "B", "B", "C"],
        "amount": [200, 1500, 300, 800, 50, 5000],
    })
    res = txn.groupby("sender").agg(
        total=("amount", "sum"),
        n_txn=("amount", "count"),
        max_one=("amount", "max"),
    )
    print(res)
    #         total  n_txn  max_one
    # sender
    # A        1700      2     1500
    # B        1150      3      800
    # C        5000      1     5000
  2. merge দু'টি table: নিচের products ও orders table merge করুন (inner)। তারপর প্রতিটি ক্রেতার মোট খরচ বের করুন।
    products = pd.DataFrame({
        "pid":   [1, 2, 3],
        "name":  ["Shirt", "Phone", "Book"],
        "price": [800, 25000, 350],
    })
    orders = pd.DataFrame({
        "cust":   ["Karim", "Karim", "Salma", "Rahim"],
        "pid":    [1, 2, 1, 3],
        "qty":    [2, 1, 3, 5],
    })
    import pandas as pd
    products = pd.DataFrame({
        "pid": [1, 2, 3],
        "name": ["Shirt", "Phone", "Book"],
        "price": [800, 25000, 350],
    })
    orders = pd.DataFrame({
        "cust": ["Karim", "Karim", "Salma", "Rahim"],
        "pid":  [1, 2, 1, 3],
        "qty":  [2, 1, 3, 5],
    })
    
    merged = pd.merge(orders, products, on="pid", how="inner")
    merged["line_total"] = merged["qty"] * merged["price"]
    
    per_cust = merged.groupby("cust")["line_total"].sum()
    print(per_cust)
    # cust
    # Karim    26600
    # Rahim     1750
    # Salma     2400
  3. pivot_table তৈরি: নিচের ডেটায় — শহর × মাস ভিত্তিক মোট বিক্রয়ের cross-table বানান। Missing combination-এ 0।
    sales = pd.DataFrame({
        "city":  ["Dhaka","Dhaka","Chittagong","Chittagong","Sylhet"],
        "month": ["Jan","Feb","Jan","Feb","Jan"],
        "amount":[1000, 1500, 2200, 800, 600],
    })
    import pandas as pd
    sales = pd.DataFrame({
        "city":  ["Dhaka","Dhaka","Chittagong","Chittagong","Sylhet"],
        "month": ["Jan","Feb","Jan","Feb","Jan"],
        "amount":[1000, 1500, 2200, 800, 600],
    })
    pt = sales.pivot_table(
        index="city",
        columns="month",
        values="amount",
        aggfunc="sum",
        fill_value=0,
    )
    print(pt)
    # month        Feb   Jan
    # city
    # Chittagong   800  2200
    # Dhaka       1500  1000
    # Sylhet         0   600

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন — Google-এর ফ্রি অনলাইন Python পরিবেশ, শুধু Gmail অ্যাকাউন্ট লাগে। Pandas, NumPy preinstalled।
পূর্ববর্তী পাঠ
পাঠ ১৪ · CSV পড়া ও পরিষ্কার করা