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

Missing data handling

Handling missing values
৬ মিনিট পড়া মধ্যম · Intermediate Pandas কোডসহ

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

  • Missing data কেন ঘটে — তিন উৎস
  • NaN, None, pd.NA — পার্থক্য ও mixed dtype
  • Detection — isna(), isna().sum(), percent missing
  • dropna — axis, how, thresh, subset
  • fillna — scalar, dict, ffill/bfill
  • Imputation — mean, median, mode, KNN, sklearn pipeline
  • MCAR, MAR, MNAR — তিন ধরনের missingness

১ · Missing data কেন ঘটে?

বাস্তব ডেটা কখনই perfect নয়। একটি Daraz order table-এ delivery_rating অনেক জায়গায় ফাঁকা — কারণ গ্রাহক rating দেননি। একটি IoT temperature sensor কখনো reading miss করে — বিদ্যুৎ গেলে। দুটি table merge করার পর — বাঁ table-এ key থাকলেও ডান table-এ না থাকলে — সমস্ত column NaN।

তিনটি উৎস

১) System fail: sensor crash, network drop, file truncate।
২) User skip: optional form field, refusal, "prefer not to say"।
৩) Join mismatch: left join-এ ডান side-এ row নেই → NaN।

২ · NaN, None, pd.NA — তিন রূপ

Pandas-এ "missing" তিনটি ভিন্ন object দিয়ে represent হয় — যা প্রায়শই বিভ্রান্তি তৈরি করে।

  • NaNNaN — Not a NumberIEEE 754 floating-point standard-এর special value। numpy.nan। পার্থক্য: NaN == NaN → False (অনন্য property)। Float column-এ default missing marker। — IEEE 754 float। Numeric column-এ default। অদ্ভুত: NaN == NaN → False।
  • None — Python-এর native null object। Object (string) column-এ থাকে। Pandas convert করে NaN-এ যখন numeric context।
  • pd.NA — Pandas 1.0+ এর nullable scalar। নতুন nullable dtypes (Int64, boolean, string)-এ ব্যবহৃত। Type-preserving — int column-এ NaN দিলে dtype float হয়ে যায়, কিন্তু pd.NA দিলে Int64 থাকে।
পরীক্ষায় না-উত্তর সামলানোর তিন উপায়: (১) ফাঁকা প্রশ্ন বাদ দিয়ে শুধু উত্তর-করা প্রশ্ন গণনা — drop। (২) সব ফাঁকা উত্তরে শূন্য বসানো — fill with 0। (৩) আগের পরীক্ষার গড় বসানো — mean impute। প্রতিটির trade-off আছে — প্রসঙ্গ অনুযায়ী বাছতে হয়।

৩ · Detection — কোথায় missing?

Pipeline-এর প্রথম কাজ — কোথায় কত missing গণনা।

Python · Pandas
import pandas as pd
import numpy as np

# একটি sample DataFrame — কিছু missing value সহ
df = pd.DataFrame({
    "name":   ["রহিম", "করিম", "সালমা", None,    "নাদিয়া"],
    "age":    [25,     np.nan, 32,     28,       np.nan],
    "city":   ["ঢাকা", "চট্টগ্রাম", None, "সিলেট", "খুলনা"],
    "salary": [50000,  45000,  np.nan, np.nan,   60000]
})

# কোথায় missing?
print(df.isna())

# প্রতিটি column-এ কত missing?
print("\nMissing count:")
print(df.isna().sum())

# শতকরা কত missing?
print("\nMissing %:")
print((df.isna().mean() * 100).round(2))

    
isna() → boolean DataFrame। .sum() column-wise গণনা। .mean() দিলে percent missing — কারণ True=1, False=0।

৪ · Visualization — missing pattern

Bar chart-এ প্রতিটি column-এর missing percent দেখলে দ্রুত বুঝতে পারবেন কোন column drop, কোনটা impute।

Python · Matplotlib
import matplotlib.pyplot as plt

# আগের df ব্যবহার করছি
missing_pct = df.isna().mean() * 100

ax = missing_pct.plot(kind="bar", color="#dc2626")
ax.set_ylabel("Missing %")
ax.set_title("প্রতি column-এ missing শতকরা")
plt.tight_layout()
plt.show()

# alternative: missingno library
# import missingno as msno
# msno.matrix(df)   # heatmap-like view

    

৫ · dropna — সরিয়ে ফেলা

সবচেয়ে সরল strategy — যেখানে missing, সেই row বা column বাদ। কিন্তু সাবধান — আপনি ডেটা হারাচ্ছেন।

Python · Pandas
# যেকোনো row-এ একটিও NaN থাকলে drop
df.dropna()

# শুধু সেই row drop যেখানে সব column NaN
df.dropna(how="all")

# অন্তত ৩টি non-NaN value থাকতে হবে
df.dropna(thresh=3)

# নির্দিষ্ট column-এ NaN হলেই drop
df.dropna(subset=["age", "salary"])

# Column drop — যেকোনো column-এ NaN থাকলে
df.dropna(axis=1)

    
Rule of thumb: যদি একটি column-এর >৫০% missing — drop। যদি একটি row-এর >৫০% missing — drop। মাঝামাঝি হলে — impute।

৬ · fillna — পূরণ করা

Python · Pandas
# সব NaN → 0
df.fillna(0)

# Column-wise dict — প্রতি column-এ আলাদা value
df.fillna({
    "age": 30,
    "salary": df["salary"].median(),
    "city": "অজানা",
    "name": "Unknown"
})

# Forward fill — আগের value দিয়ে পূরণ
df.fillna(method="ffill")    # time series-এ চমৎকার

# Backward fill — পরের value দিয়ে
df.fillna(method="bfill")

# Limit — কতগুলো consecutive NaN পূরণ হবে
df.fillna(method="ffill", limit=2)

    
Time series-এ ffillForward Fillআগের valid observation দিয়ে পরের NaN পূরণ। Stock price, temperature, sensor reading — যেখানে "শেষ পরিচিত value" বর্তমানে valid assumption — সেখানে পারফেক্ট। পারফেক্ট: stock price, temperature, sensor reading — যেখানে "শেষ পরিচিত value" এখনও বৈধ বলে ধরে নেওয়া যায়।

৭ · Imputation strategies — mean, median, mode

ImputationImputationMissing value-এর জায়গায় estimated value বসানো। সরল: mean/median/mode। জটিল: KNN, regression, MICE। লক্ষ্য: distribution যতটা সম্ভব preserve করা। মানে — missing value-র জায়গায় একটি অনুমান বসানো। কোন statistic ব্যবহার করবেন তা data-র ধরনের উপর নির্ভর।

Python · Pandas
# Numeric → mean (symmetric data) বা median (skewed data)
df["age"] = df["age"].fillna(df["age"].mean())
df["salary"] = df["salary"].fillna(df["salary"].median())

# Categorical → mode (সবচেয়ে common value)
df["city"] = df["city"].fillna(df["city"].mode()[0])

# Group-wise imputation — শ্রেণিভিত্তিক mean
df["salary"] = df.groupby("city")["salary"].transform(
    lambda s: s.fillna(s.median())
)

# String column-এ "Unknown" placeholder
df["name"] = df["name"].fillna("Unknown")

    
সতর্কতা: Mean impute সহজ, কিন্তু variance কমিয়ে দেয় — সব missing-এ একই value বসলে distribution কৃত্রিমভাবে narrow হয়। Downstream ML model "অতিরিক্ত confident" হতে পারে। Production-এ KNNKNN ImputerK nearest neighbors দিয়ে imputation — যে rows-এর অন্য features কাছাকাছি, তাদের value-র গড় বসানো। Variance better preserve করে। sklearn-এ KNNImputer। imputer বা MICE বেশি সঠিক।

৮ · sklearn SimpleImputer — production pipeline

Pandas-এ inline imputation prototype-এ ভাল, কিন্তু production-এ আপনাকে train ও test-এ একই rule apply করতে হবে। sklearn-এর SimpleImputer এই purpose-এ designed।

Python · scikit-learn
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
import numpy as np

# Numeric imputer — train-এ fit, test-এ transform
num_imputer = SimpleImputer(strategy="median")
X_train_num = num_imputer.fit_transform(df[["age", "salary"]])

# Categorical imputer — most frequent
cat_imputer = SimpleImputer(strategy="most_frequent")
X_train_cat = cat_imputer.fit_transform(df[["city"]])

# Production pipeline — fit একবার, যেকোনো নতুন data-তে apply
num_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler())
])
X_processed = num_pipeline.fit_transform(df[["age", "salary"]])

# KNN imputer — neighbors থেকে শেখে (বেশি সঠিক, বেশি ধীর)
knn = KNNImputer(n_neighbors=3)
X_knn = knn.fit_transform(df[["age", "salary"]])
print(X_knn)

    
Pipeline-এ fit_transform train-এ — তারপর সেই fitted object দিয়ে transform test-এ। এতে data leakage এড়ানো যায় — test-এর statistic train-এ "leak" করে না।

৯ · Missingness pattern — MCAR, MAR, MNAR

Statistician Donald Rubin (১৯৭৬) তিনটি মৌলিক pattern আলাদা করেছিলেন। কোন imputation strategy সঠিক — তা এই pattern-এর উপর নির্ভর।

  • MCARMissing Completely At RandomMissingness-এর সাথে কোনো observed বা unobserved variable-এর সম্পর্ক নেই। সবচেয়ে "নিরাপদ" — drop বা mean impute তেমন bias আনে না। বাস্তবে দুর্লভ। (Missing Completely At Random): Missing-এর কারণ random — কোনো variable-এর সাথে সম্পর্ক নেই। যেমন: random sensor failure। নিরাপদে drop বা mean impute চলে।
  • MAR (Missing At Random): Missing-এর কারণ অন্য observed variable-এর উপর নির্ভর — missing value নিজে নয়। যেমন: পুরুষরা salary disclose করতে কম রাজি। gender জানা থাকলে এটি model করা যায়।
  • MNAR (Missing Not At Random): Missing-এর কারণ unobserved value-এর উপরই — সবচেয়ে কঠিন। যেমন: উচ্চ-আয়ের লোক salary disclose করতে কম রাজি। Naive impute serious bias আনে।

১০ · Risk — naive imputation-এর বিপদ

একটি সরল রিস্ক — সব missing-এ 0 বসিয়ে দিলে — মডেল "0 = ছোট value" শিখে, যেটা missing-এর সঠিক প্রতিনিধিত্ব নয়। সমাধান: একটি অতিরিক্ত boolean column "was_missing" — যা মডেলকে missing-এর pattern নিজে শিখতে দেয়।

Missing data — কী করব? decision tree Missing detected কত % missing? isna().mean() < 5% Drop rows df.dropna() 5–30% Fill / impute median / ffill > 30% Model impute / drop col KNNImputer / MICE + "was_missing" boolean column missingness নিজেই একটি signal
Missing data সামলানোর simple decision tree — শতাংশ অনুযায়ী strategy বাছুন।
Missing pattern — column বনাম row red = missing, green = present name age city salary rating row 0 row 1 row 2 row 3 row 4 20% 40% 20% 40% 40%
Heatmap-style view — column-wise missing percentage একনজরে। row 2 ও row 3-এ একসাথে multiple missing — সম্ভবত system fail।

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

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

প্র ০১ NaN, None, এবং pd.NA — তিনটি missing marker-এর internal পার্থক্য কী? কেন একই DataFrame-এ এরা মিশে থাকতে পারে? float NaN-এর IEEE 754 origin বুঝুন।

এটি প্রায়শই junior data scientist-দের বিভ্রান্ত করে — তিনটি object একই কাজ করে মনে হলেও — internal মেকানিজম আলাদা।

(১) numpy.nan — IEEE 754 float:

  • NaN-এর জন্ম ১৯৮৫ সালের IEEE 754 floating-point standard-এ। যেকোনো undefined float operation (০/০, sqrt(-1)) → NaN।
  • Type: float64। অর্থাৎ — শুধু float column-এই থাকতে পারে।
  • অদ্ভুত property: NaN == NaN → False। কারণ — IEEE define করেছে "NaN-এর সাথে কোনো comparison-ই True নয়"। তাই detection-এর জন্য np.isnan() বা pd.isna() ব্যবহার করতে হয়।
  • একটি integer column-এ NaN ঢোকালে — pandas সম্পূর্ণ column float-এ convert করে। কারণ int-এ NaN representation নেই।

(২) None — Python's null:

  • Python-এর singleton object। Type: NoneType।
  • Object dtype column-এ থাকে — সাধারণত string column-এ।
  • None == None → True (NaN-এর বিপরীত)।
  • Numeric context-এ pandas auto-convert করে NaN-এ।

(৩) pd.NA — pandas 1.0+ nullable scalar:

  • 2020-এ introduced। লক্ষ্য: type-preserving missing।
  • Nullable dtypes-এ ব্যবহৃত: Int64, Float64, boolean, string।
  • pd.NA == pd.NA → pd.NA (propagating, NaN-এর মতই, কিন্তু নিজস্ব semantics)।
  • Boolean operation-এ "three-valued logic" — True / False / NA।

কেন এক DataFrame-এ মিশে থাকে:

  • CSV read-এ — empty cell numeric column-এ NaN, string column-এ None হিসেবে আসে।
  • Mixed-type column (object dtype) — উভয়ই থাকতে পারে।
  • Legacy code NaN ব্যবহার করে, modern code pd.NA। Pipeline-এ মিশ্রণ স্বাভাবিক।

Best practice: Detection-এ সবসময় pd.isna() বা .isna() ব্যবহার করুন — এটি তিনটিকেই uniformly ধরে। == np.nan বা is None avoid করুন।

মূল উপলব্ধি: "Missing" একটি concept — কিন্তু Python-এ implementation একাধিক, কারণ Pandas-এর evolution numpy float-এর সীমাবদ্ধতা থেকে শুরু করে আজকের nullable type-এ পৌঁছেছে। এই history না জানলে subtle bug এসে যায়।

প্র ০২ MCAR, MAR, MNAR — তিন ধরনের missingness pattern। প্রতিটিতে imputation strategy কেন আলাদা? প্রত্যেকটির real-world example দিন।

১৯৭৬-এ statistician Donald Rubin এই taxonomy দেন। আজও missing data theory-র ভিত্তি। কোন strategy "correct" — তা pattern-এর উপর directly নির্ভর।

(১) MCAR — Missing Completely At Random:

  • সংজ্ঞা: Missing-এর probability কোনো observed বা unobserved variable-এর উপর নির্ভর করে না। সম্পূর্ণ random।
  • Real example: ল্যাব-এ একটি pH meter randomly fail করে — কোনো sample-এর বৈশিষ্ট্যের সাথে সম্পর্ক নেই।
  • Strategy: Listwise deletion (dropna) safe — sample size কমে কিন্তু bias আসে না। Mean impute তুলনামূলক নিরাপদ।
  • Test: Little's MCAR test দিয়ে statistically check করা যায়।

(২) MAR — Missing At Random:

  • সংজ্ঞা: Missing-এর probability অন্য observed variable-এর উপর নির্ভর — কিন্তু missing value নিজে নয়।
  • Real example: পুরুষরা mental-health survey-তে কম response দেন (gender একটি observed variable)। missing-এর কারণ gender, depression score নয়।
  • Strategy: Conditional imputation — gender-অনুযায়ী মান। Multiple imputation (MICE) সঠিক estimate দেয়। Naive mean impute bias আনবে।
  • আধুনিক ML-এ অধিকাংশ missingness MAR বলে ধরে নেওয়া হয়।

(৩) MNAR — Missing Not At Random:

  • সংজ্ঞা: Missing-এর probability missing value নিজের উপর নির্ভর। Unobserved।
  • Real example: উচ্চ-আয়ের ব্যক্তি income disclose করতে অনিচ্ছুক। বা — যাদের HIV positive, তারা test result শেয়ার করেন না। missing-এর কারণই value-টি।
  • Strategy: সবচেয়ে কঠিন। Standard imputation systematically biased estimate দেবে। Selection model বা pattern-mixture model দরকার — domain knowledge ছাড়া অসম্ভব।
  • "Missingness নিজে একটি feature" — boolean indicator যোগ করা একটি pragmatic solution।

কেন strategy আলাদা:

  • MCAR-এ data-র distribution missing/non-missing-এ identical। যেকোনো imputation নিরাপদ।
  • MAR-এ subgroup-অনুযায়ী distribution আলাদা। Conditional imputation দরকার।
  • MNAR-এ missing data fundamentally observed data থেকে আলাদা। Imputation মানেই assumption — যা ভুল হতে পারে।

মূল উপলব্ধি: "যেকোনো missing → mean impute" — এটি statistically illiterate behavior। Missingness pattern সম্পর্কে hypothesis ছাড়া কোনো imputation justify করা যায় না।

প্র ০৩ Mean impute সবচেয়ে সহজ ও জনপ্রিয়। কিন্তু কেন risky? Variance underestimation, distribution distortion, এবং downstream model bias — তিনটি ব্যাখ্যা করুন।

Mean impute — beginner tutorial-এর প্রিয় strategy। কিন্তু statistically এটি বহু সমস্যা তৈরি করে — যা production model-এ silent bias হয়ে দাঁড়ায়।

(১) Variance underestimation:

  • Statistical formula: $\text{Var}(X) = \frac{1}{n} \sum (x_i - \bar{x})^2$।
  • সব missing-এ $\bar{x}$ বসালে — সেই terms-এর contribution exactly 0।
  • ফলে computed variance original-এর চেয়ে কম। যত % missing — তত বড় underestimation।
  • Standard error, confidence interval — সবই artificially narrow হয়ে যায়। হাইপোথিসিস টেস্ট-এ false positive বাড়ে।

(২) Distribution distortion:

  • Original data normal distribution-এ। Mean impute-এর পর — distribution-এ একটি spike তৈরি হয় mean-এর জায়গায়।
  • Bimodal বা skewed distribution-এ এই spike ভয়ংকর misleading।
  • Histogram, density estimation — সবই বিকৃত। Visual inspection-এ "অস্বাভাবিক" দেখাবে।
  • Categorical mode-impute-এ similar issue: minority class-এর representation হারিয়ে যায়।

(৩) Downstream model bias:

  • Linear regression: coefficient estimate biased। Variable-এর "true effect" underestimated।
  • Tree models: imputed value-এ artificial split তৈরি হয়। Decision tree "mean exactly" এ split করার tendency develop করে।
  • Distance-based (k-NN, clustering): imputed point আসল pattern থেকে দূরে — neighborhood বিকৃত।
  • Feature importance: imputed feature artificially "stable" দেখায় — true importance underestimated।

কখন mean impute তবু গ্রহণযোগ্য:

  • Missing < 5% এবং MCAR — impact minimal।
  • Quick prototype বা baseline model।
  • Downstream model robust (যেমন regularized regression)।

উন্নত alternatives:

  • Median impute: skewed data-এ better। Outlier-এর প্রভাব কম।
  • Stochastic imputation: mean নয়, একটি sample distribution থেকে — variance preserve।
  • Multiple imputation (MICE): একই dataset-এ একাধিক imputation, multiple model train, results pool — uncertainty সংরক্ষণ।
  • Model-based (KNN, regression): অন্য features ব্যবহার করে predict।

মূল উপলব্ধি: Mean impute = "convenient lie"। Quick এবং safe-looking, কিন্তু statistical foundation দুর্বল। Production-এ — অন্তত median, ideally MICE বা KNN imputer ব্যবহার করুন।

প্র ০৪ আধুনিক approach — KNN imputer, MICE (multiple imputation by chained equations), missingness-as-feature। তিনটির trade-off ও কখন কোনটা ব্যবহার করবেন?

সাধারণ mean/median impute beyond — তিনটি প্রধান modern technique। প্রত্যেকটির নিজস্ব assumption ও cost আছে।

(১) KNN Imputer (sklearn):

  • আইডিয়া: প্রতিটি missing row-এর জন্য — K nearest neighbors খুঁজে — তাদের value-র average বসানো।
  • Distance metric: Euclidean (default), অন্য features-এর মান অনুযায়ী।
  • সুবিধা: Local pattern capture। Variance তুলনামূলকভাবে preserve। Implementation সরল (KNNImputer(n_neighbors=5))।
  • অসুবিধা: Curse of dimensionality — high-D-এ neighbors meaningless। Computation $O(n^2)$। বড় dataset-এ slow।
  • কখন: মাঝারি dataset (< ১ লাখ row), < ৫০ features, mixed numeric data।

(২) MICE — Multiple Imputation by Chained Equations:

  • আইডিয়া: প্রতিটি missing variable-কে অন্য variables-এর regression model দিয়ে predict। Iteratively সব variable পূরণ। M বার (যেমন ১০ বার) imputation — M sets of complete data।
  • Statistical foundation: Rubin (১৯৮৭)। MAR-এ unbiased estimate দেয়।
  • সুবিধা: Uncertainty quantification — প্রতিটি imputation আলাদা, downstream model M বার train করে result pool — confidence interval ঠিকভাবে আসে। Variance underestimation নেই।
  • অসুবিধা: Computationally expensive। Implementation জটিল (sklearn-এ IterativeImputer, R-এ mice package)। Convergence guarantee সবসময় নেই।
  • কখন: Statistical inference critical (research, healthcare, social science)। MAR assumption justify করা যাচ্ছে।

(৩) Missingness-as-feature:

  • আইডিয়া: Imputation করুন (যেকোনো method-এ) + একটি অতিরিক্ত boolean column "was_missing_X"। মডেল নিজে শিখবে — missingness নিজেই signal কিনা।
  • সুবিধা: MNAR-এ চমৎকার — যেখানে missing pattern নিজে predictive। Tree-based model (XGBoost, RandomForest) এই pattern সহজে exploit করে।
  • অসুবিধা: Feature dimensionality দ্বিগুণ। Linear model-এ collinearity issue হতে পারে।
  • কখন: Production tabular ML (Kaggle, business analytics)। Missing-এর কারণ informative হতে পারে এমন domain — credit scoring, medical, churn prediction।

Decision matrix:

  • Quick prototype: SimpleImputer (median)।
  • Tabular ML production: SimpleImputer + missingness flag (XGBoost natively handles)।
  • Statistical research: MICE।
  • Image/sensor time series: ffill / interpolation।
  • Mid-size mixed data: KNNImputer।

আধুনিক LLM-যুগে: Tabular foundation models (TabPFN, ২০২২+) missingness internally handle করতে পারে — pretraining-এ শেখা। ভবিষ্যতে hand-crafted imputation কম দরকার হবে। কিন্তু interpretability ও audit-এর জন্য classical approach এখনও essential।

মূল উপলব্ধি: "Best imputation" বলে কিছু নেই — context, dataset size, downstream model, এবং statistical assumption সব মিলিয়ে decision। Missing data handling-এ অভিজ্ঞতা — production data scientist-এর সবচেয়ে valuable skill-গুলোর একটি।

অনুশীলন

  1. Detect missing: নিচের DataFrame-এ প্রতিটি column-এ কত শতাংশ missing — গণনা করুন।
    df = pd.DataFrame({
        "a": [1, np.nan, 3, np.nan, 5],
        "b": [np.nan, np.nan, 3, 4, 5],
        "c": [1, 2, 3, 4, 5]
    })
    print((df.isna().mean() * 100).round(2))
    # a    40.0
    # b    40.0
    # c     0.0

    Column c-তে কোনো missing নেই। a ও b-তে ৪০% করে missing।

  2. Fill with median per group: একটি city column ও salary column আছে। প্রতি city-র median salary দিয়ে missing salary পূরণ করুন।
    df["salary"] = df.groupby("city")["salary"].transform(
        lambda s: s.fillna(s.median())
    )

    transform group size preserve করে — অর্থাৎ result-এর shape original df-এর সমান। প্রতিটি group-এর missing শুধু সেই group-এর median দিয়েই পূরণ।

  3. Drop rows with > 50% missing: যেসব row-এ অর্ধেকের বেশি column missing — drop করুন।
    # thresh = কতগুলো non-NaN value থাকতে হবে
    min_non_na = df.shape[1] // 2 + 1   # > 50%
    df_clean = df.dropna(thresh=min_non_na)
    
    # বিকল্প — direct ratio দিয়ে
    mask = df.isna().mean(axis=1) <= 0.5
    df_clean = df[mask]

    thresh parameter "অন্তত কতগুলো non-NaN value চাই" specify করে। আমরা যদি >৫০% non-NaN চাই — column count-এর অর্ধেকের বেশি।

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন — Google-এর ফ্রি অনলাইন Python পরিবেশ, শুধু Gmail অ্যাকাউন্ট লাগে।
পূর্ববর্তী পাঠ
পাঠ ১৫ · groupby, merge ও pivot