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

ডেটা সায়েন্সের প্রথম ধাপ — NumPy আর pandas

First steps in data science — asking questions of data with NumPy and pandas

Taught in Bangla, this lesson takes the first steps in data science with Python: the question-driven workflow, small analyses with csv and statistics, NumPy arrays, vectorization, broadcasting, boolean masks and axis, pandas Series and DataFrame, selecting, filtering, new columns and missing values, groupby, charts, Jupyter and data ethics.

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

  • ডেটা বিশ্লেষণের ছয়টি ধাপ, আর শুধু standard library দিয়ে একটি ছোট বিশ্লেষণ — যাতে বোঝেন লাইব্রেরি কী সমস্যা মেটায়
  • NumPy-র ndarray, dtype, shape; লুপ ছাড়া হিসাব, broadcasting, boolean mask আর axis-এর ধারণা
  • NumPy কেন দ্রুত — পাশাপাশি রাখা মেমরি আর C-তে চলা লুপ (পর্ব ৪৩-এর সাথে যোগ)
  • pandas-এ CSV পড়া, টেবিল দেখা, ছাঁকা, নতুন কলাম, groupby, sort, ফাঁকা মান সামলানো আর সেভ করা
  • একটি চার্টের ঝলক, Jupyter/Colab, ডেটার নৈতিকতা — আর AI-এর লেখা pandas কোড যাচাইয়ের চেকলিস্ট

১ · ডেটা সায়েন্স মানে ডেটাকে প্রশ্ন করা

একটি স্কুলের প্রধান শিক্ষক জানতে চান: “এবার কোন বিষয়ে ছাত্রছাত্রীরা সবচেয়ে দুর্বল? কতজন সব বিষয়ে পাস করেছে?” একটি দোকানের মালিক জানতে চান: “কোন জেলায় বিক্রি সবচেয়ে বেশি, আর গত মাসের চেয়ে বাড়ল না কমল?” একজন কৃষি কর্মকর্তা জানতে চান: “কোন বিভাগে বৃষ্টি সবচেয়ে কম, সেখানে সেচের জন্য আগে প্রস্তুতি লাগবে কি?” তিনটি প্রশ্নেরই উত্তর লুকিয়ে আছে কোনো টেবিলে — মার্কশিটে, বিক্রির খাতায়, আবহাওয়ার রেকর্ডে।

ডেটা সায়েন্স-এর মূল কথা এটুকুই: ডেটাকে প্রশ্ন করা, আর বিশ্বাসযোগ্য উত্তর বের করা. নামটা ভারী শোনালেও কাজের শুরু খুব সাধারণ — যোগ, গড়, সর্বোচ্চ, গণনা, দলে ভাগ। পার্থক্য হলো আকারে। ৫০ জন ছাত্রের নম্বর হাতে বা Excel-এ হিসাব করা যায়। কিন্তু সারা দেশের লাখো পরীক্ষার্থী, একটি সুপারশপের পাঁচ বছরের প্রতিটি বিক্রি, বা প্রতি ঘণ্টার আবহাওয়ার রেকর্ড — তখন প্রোগ্রাম লাগে। আর প্রোগ্রাম লিখতে হয় এমনভাবে, যাতে কাল নতুন ডেটা এলে একই বিশ্লেষণ আবার এক ক্লিকে চলে।

কাজের ছয় ধাপ

অভিজ্ঞ ডেটা বিশ্লেষকরা প্রায় সবসময় একই পথে হাঁটেন। পথটা সোজা লাইন নয় — প্রায়ই মাঝখান থেকে আগের ধাপে ফিরতে হয় — কিন্তু ধাপগুলো চেনা থাকলে কোথায় আটকে আছেন বোঝা সহজ হয়:

ধাপকী করেনমার্কশিটের উদাহরণে
১. প্রশ্নঠিক কী জানতে চান, স্পষ্ট করে লেখা“কোন বিষয়ের গড় সবচেয়ে কম? পাসের হার কত?”
২. ডেটা জোগাড়ফাইল, database, API বা জরিপ থেকে ডেটা আনাশিক্ষকদের Google Sheets থেকে CSV নামানো
৩. পরিষ্কার করাফাঁকা ঘর, ভুল বানান, ডুপ্লিকেট, ভুল ধরন ঠিক করাঅনুপস্থিত ছাত্রের ফাঁকা ঘর, “৭৮” বনাম “78”
৪. ঘুরে দেখাগড়, বিস্তার, দলভিত্তিক সারাংশ, অস্বাভাবিক মান খোঁজাবিষয়ভিত্তিক গড়, সর্বোচ্চ, কে কে ফেল
৫. চার্টসংখ্যাকে ছবিতে আনা, যাতে প্যাটার্ন চোখে পড়েবিষয়ভিত্তিক গড়ের একটি bar chart
৬. জানানোফল, সীমাবদ্ধতা আর পরামর্শ সহজ ভাষায় বলাপ্রধান শিক্ষকের জন্য এক পাতার সারাংশ

তৃতীয় ধাপটি, data cleaning, শুনতে সবচেয়ে কম আকর্ষণীয়, কিন্তু বাস্তবে সবচেয়ে বেশি সময় নেয়। চতুর্থ ধাপকে বলা হয় EDA — exploratory data analysis, ডেটার সাথে প্রথম পরিচয়। এই পর্বে আপনি দ্বিতীয় থেকে পঞ্চম ধাপের যন্ত্রপাতি দেখবেন। প্রথম আর ষষ্ঠ ধাপে কোনো লাইব্রেরি সাহায্য করে না — ভালো প্রশ্ন আর সৎ উপস্থাপন মানুষের কাজ।

ধরুন একটি গার্মেন্টস কারখানার মান-নিয়ন্ত্রণ বিভাগ। প্রতিদিন হাজারো শার্ট আসে। একজন পরিদর্শক প্রতিটি শার্ট হাতে তুলে মাপ নেন — কাজ হয়, কিন্তু ধীরে। আরেকটি লাইনে একটি যন্ত্র একসাথে পুরো একটি তাক মেপে ফেলে, কারণ তাকে বলা আছে “সব শার্ট একই মাপের, পাশাপাশি সাজানো”. NumPy সেই যন্ত্র। আর pandas হলো সেই খাতা, যেখানে প্রতিটি তাকের গায়ে লেবেল লাগানো — কোন লাইন, কোন দিন, কোন অর্ডার — যাতে “৩ নম্বর লাইনের মঙ্গলবারের ত্রুটি” এক কথায় খুঁজে পাওয়া যায়।

মূল ধারণা

লাইব্রেরি প্রশ্নের উত্তর দেয় না, উত্তর বের করার খরচ কমায়. NumPy কমায় সংখ্যার হিসাবের সময় আর মেমরি; pandas কমায় টেবিল নিয়ে কাজের কোডের দৈর্ঘ্য আর ভুলের সুযোগ। কোন প্রশ্ন করবেন, ডেটা বিশ্বাসযোগ্য কি না, আর ফলটা কী বোঝায় — এই তিনটি সিদ্ধান্ত সবসময় আপনার।

২ · আগে শুধু Python দিয়ে — csv আর statistics

লাইব্রেরি কী সমস্যা মেটায়, তা বুঝতে হলে আগে লাইব্রেরি ছাড়া কাজটা করে দেখা ভালো। পর্ব ২৬-এ csv.DictReader শিখেছেন, আর পর্ব ২৭-এ standard library-র ভ্রমণে statistics module-এর দেখা পেয়েছেন। এবার একটি স্কুলের পাঁচজন ছাত্রছাত্রীর মার্কশিট নিয়ে তিনটি প্রশ্নের উত্তর খুঁজি: প্রতিটি বিষয়ের গড়, মোট নম্বরে প্রথম কে, আর পাসের হার (এখানে প্রতিটি বিষয়ে ৩৩ বা তার বেশি পেলে পাস ধরা হয়েছে)। নম্বরগুলো শেখার জন্য বানানো। কোডটি এই পাতাতেই চালাতে পারবেন:

import csv
import io
import statistics

data = """name,bangla,english,math
Riya,78,85,91
Joy,65,58,72
Tania,88,92,95
Sakib,45,38,28
Nadia,82,79,85
"""
rows = list(csv.DictReader(io.StringIO(data)))
subjects = ["bangla", "english", "math"]
for s in subjects:
    print(s, statistics.mean(int(r[s]) for r in rows))
top = max(rows, key=lambda r: sum(int(r[s]) for s in subjects))
print("top:", top["name"])
passed = [r for r in rows if all(int(r[s]) >= 33 for s in subjects)]
print(f"pass rate: {len(passed) / len(rows):.0%}")

আউটপুট: bangla 71.6, english 70.4, math 74.2, তারপর top: Tania আর pass rate: 80%. ফাইল ছাড়াই চালানোর জন্য io.StringIO একটি string-কে ফাইলের মতো করে DictReader-কে দিয়েছে; বাস্তবে এখানে থাকবে open("marks.csv", newline="", encoding="utf-8-sig"). লক্ষ করুন কতবার int(r[s]) লিখতে হলো — CSV-র প্রতিটি ঘর str (পর্ব ২৬), তাই প্রতিটি হিসাবে রূপান্তর। সাকিব গণিতে ২৮ পেয়েছে, তাই পাঁচজনে চারজন পাস — ৮০%.

কাজ হলো, আর কোডও খারাপ নয়। তাহলে সমস্যা কোথায়? সমস্যা তিনটি, আর ডেটা বড় হলে তিনটিই বাড়ে।

🐢 গতি

প্রতিটি যোগ, প্রতিটি তুলনা Python-এর interpreter একটি একটি করে চালায় — পর্ব ০৫-এর bytecode নির্দেশ ধরে। পাঁচজনে টের পাবেন না; এক কোটি সারিতে মিনিট গড়াবে।

লুপ চলে interpreter-এ

🧳 মেমরি

Python list-এ প্রতিটি সংখ্যা একটি আলাদা int object (64-bit CPython-এ ছোট একটি int-ই প্রায় ২৮ byte), আর list রাখে তার ঠিকানা (আরও ৮ byte). ৮ byte-এর একটি সংখ্যার জন্য খরচ ৩৬ byte-এর মতো।

প্রতিটি সংখ্যা আলাদা object

🧩 কোডের দৈর্ঘ্য

“প্রতিটি বিষয়ের গড়”, “জেলাভিত্তিক মোট”, “ফাঁকা ঘর বাদ দিয়ে” — প্রতিটি প্রশ্নে নতুন লুপ, নতুন dict, নতুন রূপান্তর। প্রশ্ন বাড়লে কোড বাড়ে, আর ভুলের জায়গাও।

প্রতিটি প্রশ্নে নতুন লুপ

প্রথম দুটি সমস্যা মেটায় NumPy, তৃতীয়টি pandas. দুটিই third-party package — Python-এর সাথে আসে না, পর্ব ২৩-এর মতো একটি virtual environment-এ pip দিয়ে বসাতে হয়:

python -m venv .venv
# Windows: .venv\Scripts\activate    Linux/Mac: source .venv/bin/activate
pip install numpy pandas matplotlib

এই পাতার “চালান” বোতাম NumPy/pandas চালাতে পারে না। পাতার runner-এ শুধু Python-এর standard library আছে। তাই এখন থেকে NumPy আর pandas-এর কোডগুলো সাধারণ কোড ব্লক হিসেবে দেখানো হয়েছে, নিচে তাদের হুবহু আউটপুট (NumPy 2.x আর pandas 3.0-এ চালিয়ে মেলানো)। নিজে চালাতে উপরের মতো install করে নিজের কম্পিউটারে চালান, অথবা অংশ ৯-এর Jupyter বা Google Colab ব্যবহার করুন — Colab-এ সব আগে থেকেই বসানো।

৩ · NumPy-র ndarray — একই ধরনের সংখ্যার ঘন সারি

NumPy-র কেন্দ্রে একটিমাত্র ধারণা: ndarray (n-dimensional array). দেখতে list-এর মতো, কিন্তু দুটি কঠোর শর্ত মানে। এক, সব উপাদান একই ধরনের — সেই ধরনের নাম dtype, যেমন int64 (৮ byte-এর পূর্ণসংখ্যা) বা float64. দুই, উপাদানগুলো মেমরিতে ফাঁক ছাড়া পাশাপাশি বসে — contiguous memory, ঠিক C-র array-র মতো। প্রতিটি মান আলাদা Python object নয়, কাঁচা ৮ byte. এই দুই শর্তের বিনিময়ে মেলে গতি।

import numpy as np

marks = np.array([78, 65, 88, 45, 82])
print(marks)
print(marks.dtype, marks.shape, marks.ndim)
print(marks + 5)
print(marks * 1.1)
print(np.zeros(3), np.arange(0, 10, 2))
print(np.linspace(0, 1, 5))
[78 65 88 45 82]
int64 (5,) 1
[83 70 93 50 87]
[85.8 71.5 96.8 49.5 90.2]
[0. 0. 0.] [0 2 4 6 8]
[0.   0.25 0.5  0.75 1.  ]

লাইন ধরে পড়ুন। np নামে import করা সর্বজনীন প্রথা — প্রায় সব বই, টিউটোরিয়াল আর AI-এর কোডে এটাই পাবেন। array ছাপা হয় কমা ছাড়া, এটাই list থেকে চেনার সহজ উপায়। dtype হলো int64; shape হলো (5,) — একটি tuple, যার প্রতিটি সংখ্যা একটি মাত্রার দৈর্ঘ্য; ndim মাত্রার সংখ্যা। তারপর আসল চমক: marks + 5 প্রতিটি নম্বরে ৫ যোগ করেছে — কোনো লুপ ছাড়া। list-এ [78, 65] + 5 লিখলে TypeError হতো, আর [78, 65] * 2 list-টিকেই দুবার জুড়ে দিত। NumPy-তে গাণিতিক চিহ্ন মানে প্রতিটি উপাদানে সেই হিসাব. * 1.1-এর ফল দশমিক, তাই নতুন array-র dtype নিজেই float64 হয়েছে। শেষ দুই লাইন array বানানোর আরও কয়েকটি উপায়: শূন্যে ভরা, range-এর মতো ধাপে ধাপে, আর দুই প্রান্তের মধ্যে সমান ভাগে।

Windows-এ পুরোনো NumPy (1.x) পূর্ণসংখ্যার ডিফল্ট dtype int32 দিত; NumPy 2.0 থেকে বেশিরভাগ 64-bit সিস্টেমে int64. dtype-এর আকার নির্দিষ্ট বলে সীমাও আছে: a = np.array([200, 100], dtype=np.uint8) (০ থেকে ২৫৫) হলে a + a-এর প্রথম ঘরে 200 + 200 হয় 144 — চুপচাপ ঘুরে যায়, Python int-এর মতো অসীম বড় হয় না (পর্ব ০৭)। আর np.array([1, "2"]) লিখলে সব উপাদান string হয়ে যায়, কারণ একটি array-তে একটিই dtype.

লুপটা আসলে কোথায় চলে?

পর্ব ৪৩-এ দেখেছেন, Python-এ দ্রুত হওয়ার বড় কৌশল হলো “লুপটা C-তে ঠেলে দেওয়া” — sum()-এর মতো built-in ব্যবহার করা। NumPy এই কৌশলকে পুরো একটি ভাষায় পরিণত করেছে। এর নাম vectorization: উপাদান ধরে ধরে লুপ না লিখে পুরো array-কে একটি নির্দেশ দেওয়া। ধাপে ধাপে দেখুন দুই পথে কী ঘটে:

  1. Python list[78, 65, …]ঠিকানার সারি
  2. for লুপbytecodeপ্রতি উপাদানে অনেক কাজ
  3. ndarrayint64 × nপাশাপাশি কাঁচা সংখ্যা
  4. marks + 5একটি ডাকলুপ C-তে
  5. ফলনতুন arrayবহুগুণ দ্রুত
Python list-এর মেমরিপ্রতিটি ঘরে একটি ঠিকানা
list:  [ ●  ●  ●  ●  … ]      # প্রতিটি ● = ৮ byte-এর ঠিকানা
         ↓  ↓  ↓  ↓
       int(78) int(65) int(88) …   # মেমরির নানা জায়গায় ছড়ানো
# প্রতিটি int object-এ ধরন, reference count আর মান
new = [m + 5 for m in marks]প্রতিটি উপাদানে interpreter-এর কাজ
১. পরের উপাদান আনো       # iterator থেকে
২. object-এর ধরন দেখো     # int? float? অন্য কিছু?
৩. ঠিক যোগটি খুঁজে ডাকো
৪. নতুন int object বানাও  # মেমরি বরাদ্দ
৫. list-এ ঠিকানা বসাও
# দশ লাখ উপাদান = এই চক্র দশ লাখ বার
ndarray-র মেমরিএকটানা একটি ব্লক
dtype=int64, shape=(1000000,)
| 78 | 65 | 88 | 45 | 82 | … |   # প্রতিটি ৮ byte, পাশাপাশি
# ধরন একবারই লেখা আছে — প্রতিটি উপাদানে নয়
# মোট ≈ ৮ MB; list-এ ≈ ৩৬ MB
new = marks + 5Python থেকে একটিই ডাক
Python:  marks.__add__(5)        # একবার
  ↓
C লুপ:   for (i = 0; i < n; i++)
             out[i] = in[i] + 5;   # ধরন দেখা নেই, object নেই
# CPU একসাথে কয়েকটি সংখ্যা যোগ করতে পারে (SIMD)
ফলএকই উত্তর, অনেক কম সময়
list comprehension:   প্রতি উপাদানে কয়েক ডজন ন্যানোসেকেন্ড
NumPy:                প্রতি উপাদানে এক ন্যানোসেকেন্ডের মতো
# সঠিক অনুপাত মেশিনভেদে আলাদা — পর্ব ৪৩-এর মতো নিজে মাপুন
  1. list রাখে ঠিকানা। Python list আসলে ঠিকানার একটি সারি; প্রতিটি ঠিকানা মেমরির অন্য কোথাও থাকা একটি পূর্ণাঙ্গ int object-কে দেখায় (পর্ব ০৬-এর “নাম আর object”). এই নমনীয়তার কারণেই একই list-এ সংখ্যা, string আর dict একসাথে রাখা যায়।
  2. লুপ চলে interpreter-এ। প্রতিটি উপাদানের জন্য interpreter কয়েকটি bytecode নির্দেশ চালায়: উপাদান আনা, তার ধরন দেখা, ঠিক যোগটি খোঁজা, ফলের জন্য নতুন object বানানো। একেকটি কাজ ছোট, কিন্তু দশ লাখবার — আর আসল যোগটা পুরো খরচের খুব ছোট অংশ।
  3. ndarray রাখে কাঁচা সংখ্যা। সব উপাদান int64 — এই তথ্য একবারই array-র মাথায় লেখা। সংখ্যাগুলো পাশাপাশি, তাই CPU-র cache একবারে অনেকগুলো টেনে আনে। মেমরিও চার ভাগের এক ভাগের মতো।
  4. Python থেকে একটিই ডাক। marks + 5 লিখলে Python শুধু একবার NumPy-র একটি function ডাকে। আসল লুপটি NumPy-র ভেতরের সংকলিত C কোডে চলে (পর্ব ০৫-এর C extension) — সেখানে ধরন যাচাই বা object বানানোর ঝামেলা নেই, আধুনিক CPU একসাথে কয়েকটি সংখ্যাও যোগ করতে পারে।
  5. ফল: বহুগুণ দ্রুত। বড় array-তে এমন সরল হিসাবে NumPy প্রায়ই দশ থেকে একশো গুণ দ্রুত হয়। তবে ছোট array-তে (কয়েকটি উপাদান) NumPy-র ডাকের নিজস্ব খরচ বেশি পড়ে, তখন list-ই সমান বা দ্রুত। নিয়ম পর্ব ৪৩-এর: অনুমান নয়, মাপুন।
Python লুপ: interpreter প্রতিটি উপাদানে ঘোরে
NumPy: Python একবার বলে, C লুপ ঘোরে

৪ · লুপ ছাড়া হিসাব — broadcasting, boolean mask আর সারাংশ

Broadcasting — একটি সংখ্যা পুরো array-তে

marks + 5-এ একটি জিনিস চুপচাপ ঘটেছে: একটি array (৫টি উপাদান) আর একটি একক সংখ্যা যোগ হয়েছে। দুটির আকার আলাদা, তবু কাজ হলো। NumPy-র এই নিয়মের নাম broadcasting — ছোট জিনিসটিকে বড়টির আকারে “ছড়িয়ে” দেওয়া, কিন্তু আসলে কপি না করে। একটি দোকানের দামের তালিকায় ১৫% VAT বসানো দিয়ে দেখি (১৫% বাংলাদেশে VAT-এর প্রচলিত হার; উদাহরণের দামগুলো বানানো):

1prices = np.array([120.0, 450.0, 80.0, 1250.0])2with_vat = prices * 1.153print(with_vat)
shape আর মান
prices
1.15
with_vat
  1. চারটি দাম, একটি array. সব দাম দশমিকসহ, তাই dtype float64, shape (4,).
  2. একটি সংখ্যা — shape খালি। 1.15 একক মান, তার কোনো মাত্রা নেই। দুটি আকার আলাদা; NumPy মিলিয়ে নেওয়ার নিয়ম খোঁজে।
  3. “যেন” চারটি কপি। broadcasting-এর নিয়মে একক মানটি যেন চারবার লেখা একটি array হয়ে যায় — কিন্তু মেমরিতে আসলে কোনো কপি তৈরি হয় না, C লুপ প্রতিবার একই 1.15 ব্যবহার করে। দশ লাখ দামেও বাড়তি মেমরি লাগে না।
  4. ঘর ধরে গুণ, নতুন array. ১২০ × ১.১৫ = ১৩৮, ৪৫০ × ১.১৫ = ৫১৭.৫ — চারটি ফল একসাথে। একই নিয়ম আরও বড় পরিসরে কাজ করে: একটি টেবিলের প্রতিটি সারি থেকে একটি সারি-আকারের array বিয়োগ করা যায় (যেমন প্রতিটি বিষয়ের গড় বাদ দেওয়া)। কোন আকার কোনটির সাথে মেলে, তার পুরো নিয়ম Python for AI কোর্সের broadcasting পর্বে আছে।

Boolean mask — শর্ত দিয়ে বাছাই

“৮০ বা তার বেশি পেয়েছে কারা?” list-এ লিখতেন [m for m in marks if m >= 80]. NumPy-তে এটি দুই ধাপের একটি সুন্দর ধারণা। প্রথমে তুলনা — marks >= 80 — যা নিজেই একটি vectorized হিসাব, আর ফল দেয় True/False-এর একটি array. একে বলে boolean mask. তারপর সেই mask দিয়ে মূল array-কে index করলে শুধু True জায়গার উপাদানগুলো আসে। ঘর ধরে দেখুন:

1marks = np.array([78, 65, 88, 45, 82])2mask = marks >= 803print(marks[mask], mask.sum())
index: 0 1 2 3 4
marks
এখন দেখছে
mask
  1. পাঁচজনের বাংলার নম্বর। একটি int64 array, পাঁচটি ঘর।
  2. প্রথম ঘর: 78 ≥ 80? না। তুলনার চিহ্নও vectorized — C লুপ প্রতিটি ঘরে একই তুলনা চালায় আর ফলের array-তে একটি bool লেখে। আমরা ধীরে দেখছি, আসলে সব একসাথে ঘটে।
  3. দ্বিতীয় ঘর: 65, আবার False. mask-এর দৈর্ঘ্য সবসময় মূল array-র সমান — প্রতিটি নম্বরের জন্য একটি হ্যাঁ/না।
  4. তৃতীয় ঘর: 88, True. এই ছাত্রটি বাছাইয়ে আসবে।
  5. বাকি দুটি: 45 False, 82 True. mask তৈরি: [False False True False True]. এটি নিজেই একটি array, তাই একে নাম দিয়ে রাখা, অন্য শর্তের সাথে জোড়া বা গোনা যায়।
  6. mask দিয়ে বাছাই, আর গোনা। marks[mask] শুধু True ঘরের মানগুলো নিয়ে নতুন array দেয়: [88 82]. আর True-কে 1, False-কে 0 ধরা হয় বলে mask.sum() সরাসরি গোনে কতজন — 2. একাধিক শর্তে & (এবং), | (অথবা), ~ (না) লাগে, প্রতিটি শর্ত বন্ধনীতে: marks[(marks >= 60) & (marks < 80)]. এখানে Python-এর and/or চলে না।

সারাংশ — যোগ, গড়, সর্বোচ্চ আর তার অবস্থান

অনেক মানকে একটি মানে নামিয়ে আনাকে বলে aggregation. NumPy array-র এর জন্য method আছে, সবই C-তে চলে:

marks = np.array([78, 65, 88, 45, 82])
print(marks[marks < 50])
print(marks.sum(), marks.mean(), marks.max(), marks.argmax())
[45]
358 71.6 88 2

শেষ মানটি লক্ষ করুন। argmax সর্বোচ্চ মানটি দেয় না, দেয় সেটি কোথায় — index 2. কেন এটা দরকারি? কারণ নামগুলো যদি আরেকটি array-তে একই ক্রমে থাকে (names[marks.argmax()]), তবে সর্বোচ্চ নম্বর পাওয়া ছাত্রের নাম পাওয়া যায়। এমন “দুটি array-কে index দিয়ে মেলানো” NumPy-তে খুব প্রচলিত — আর এই ঝামেলাই pandas পরে মিটিয়ে দেবে, নাম আর নম্বর একই টেবিলে রেখে। এ ছাড়া আছে min, argmin, std (standard deviation), np.median, cumsum (চলমান যোগফল)।

৫ · axis — দুই মাত্রার মার্কশিটে কোন দিকে হিসাব

বাস্তবের ডেটা সাধারণত টেবিল: সারি আর কলাম। NumPy-তে এটি একটি দুই মাত্রার array — list of lists থেকে বানানো যায়। অংশ ২-এর পুরো মার্কশিটটি নিই: পাঁচ সারি (ছাত্র), তিন কলাম (বাংলা, ইংরেজি, গণিত):

import numpy as np

# সারি = Riya, Joy, Tania, Sakib, Nadia; কলাম = bangla, english, math
table = np.array([[78, 85, 91],
                  [65, 58, 72],
                  [88, 92, 95],
                  [45, 38, 28],
                  [82, 79, 85]])
print(table.shape)
print(table.mean(axis=0))   # প্রতিটি বিষয়ের গড়
print(table.sum(axis=1))    # প্রতিটি ছাত্রের মোট
print(table.sum(axis=1).argmax())
print((table >= 33).all(axis=1).mean())
(5, 3)
[71.6 70.4 74.2]
[254 195 275 111 246]
2
0.8

অংশ ২-এর ১৫ লাইনের কাজ এখানে প্রায় এক লাইন করে — আর ফল হুবহু এক: বিষয়ভিত্তিক গড় ৭১.৬, ৭০.৪, ৭৪.২; সর্বোচ্চ মোট index 2-এর (Tania, ২৭৫); পাসের হার ০.৮, অর্থাৎ ৮০%. শেষ লাইনটি খুলে দেখার মতো: table >= 33 পুরো টেবিলের একটি boolean mask বানায় (৫ × ৩), .all(axis=1) প্রতিটি সারিতে জিজ্ঞেস করে “সব ঘর True কি?” — ফল ৫টি bool, আর তাদের mean() মানে True-র অনুপাত।

কিন্তু axis আসলে কী? এটি বলে দেয় কোন মাত্রাটি হিসাবের পর “হারিয়ে যাবে”. shape (5, 3)-এ মাত্রা 0 হলো সারি (৫টি), মাত্রা 1 হলো কলাম (৩টি)।

⬇️ axis=0 — সারিগুলো জুড়ে নিচের দিকে

প্রতিটি কলামের পাঁচটি সারি মিলিয়ে একটি মান। সারির মাত্রা (৫) হারায়, থাকে কলামের মাত্রা — ফলের shape (3,): প্রতিটি বিষয়ের গড়।

(5, 3) → (3,)

➡️ axis=1 — কলামগুলো জুড়ে পাশের দিকে

প্রতিটি সারির তিনটি কলাম মিলিয়ে একটি মান। কলামের মাত্রা (৩) হারায় — ফলের shape (5,): প্রতিটি ছাত্রের মোট।

(5, 3) → (5,)

পরীক্ষার হলের বসার ছক ভাবুন — সারিতে সারিতে বেঞ্চ, প্রতিটি বেঞ্চে তিনজন। “প্রতিটি কলামের গড় উচ্চতা” মাপতে আপনি প্রতিটি কলাম ধরে সামনে থেকে পেছনে (নিচের দিকে) হাঁটবেন — সেটা axis=0. “প্রতিটি বেঞ্চের মোট ওজন” মাপতে প্রতিটি বেঞ্চে বাঁ থেকে ডানে যাবেন — axis=1. যে দিকে হাঁটছেন, সেই দিকটাই এক সংখ্যায় গুটিয়ে যায়।

একটি সূক্ষ্ম কথা: ছাত্রের নম্বর বেরোল 2, নাম নয়। NumPy array-তে সারি বা কলামের কোনো নাম থাকে না — কোন সারি কে, কোন কলাম কোন বিষয়, তা আপনাকে মাথায় বা আলাদা list-এ রাখতে হয়। কলামের ক্রম ভুল করলে NumPy টেরও পাবে না। ঠিক এই ফাঁক পূরণ করতে এসেছে pandas.

C/C++ জানলে

NumPy-র ndarray আসলে C-র array-র মতোই একটানা একটি মেমরি ব্লক, সাথে dtype আর shape-এর তথ্য; 2D array ডিফল্টে সারি ধরে পরপর রাখা থাকে (row-major) — C-র int a[5][3]-এর মতো। পার্থক্য হলো, C-তে লুপ আপনি নিজে লেখেন আর সীমা পার হলে কেউ থামায় না; NumPy লুপ ভেতরে লিখে রেখেছে আর index-এর সীমা যাচাই করে IndexError দেয়। 2D array মেমরিতে কীভাবে বসে, দেখুন C কোর্সের বহুমাত্রিক array পর্বে।

৬ · pandas — নাম-লেখা টেবিল: Series আর DataFrame

pandas-কে সহজে ভাবুন প্রোগ্রাম দিয়ে চালানো spreadsheet হিসেবে। Excel-এ যা যা করেন — কলামের নাম দেখে কাজ, ফিল্টার, নতুন কলামে সূত্র, Pivot Table — pandas-এ সবই কোড, তাই পুনরাবৃত্তিযোগ্য: নতুন মাসের ফাইল এলে একই স্ক্রিপ্ট আবার চালালেই হলো। ভেতরে প্রতিটি কলাম আসলে একটি NumPy-ধাঁচের array, তাই গতিও আসে।

pandas-এর দুটি মূল ধরন। Series হলো এক মাত্রার array, যার প্রতিটি মানের একটি লেবেল আছে — সেই লেবেলগুলোর নাম index. আর DataFrame হলো অনেকগুলো Series পাশাপাশি — একটি টেবিল, যার প্রতিটি কলামের নাম আর নিজস্ব dtype আছে, আর সব কলাম একই index ভাগ করে।

import pandas as pd

s = pd.Series([78, 65, 88], index=["Riya", "Joy", "Tania"], name="bangla")
print(s)
print(s["Joy"], s.mean())
Riya     78
Joy      65
Tania    88
Name: bangla, dtype: int64
65 77.0

NumPy-র সমস্যাটা মিটে গেল: এখন s["Joy"] — নাম দিয়ে খোঁজা, dict-এর মতো — অথচ s.mean() চলে array-র গতিতে। pandas-কেও pd নামে import করাই প্রথা।

CSV থেকে DataFrame — read_csv

বাস্তবে DataFrame সাধারণত হাতে বানানো হয় না, ফাইল থেকে পড়া হয়। একটি দোকানের দুই মাসের বিক্রির খাতা নিই — জেলা, মাস, পণ্য, পরিমাণ আর একক দাম (সংখ্যাগুলো শেখার জন্য বানানো)। লক্ষ করুন, সিলেটের চায়ের দামের ঘরটি ফাঁকা:

district,month,item,qty,price
Dhaka,Jan,Rice,40,1300
Sylhet,Jan,Tea,25,
Chattogram,Jan,Rice,30,1300
Dhaka,Feb,Oil,50,950
Sylhet,Feb,Rice,12,1300
Chattogram,Feb,Tea,20,400
import pandas as pd

df = pd.read_csv("sales.csv", encoding="utf-8-sig")
print(df.head(3))
print(df.shape)
df.info()
     district month  item  qty   price
0       Dhaka   Jan  Rice   40  1300.0
1      Sylhet   Jan   Tea   25     NaN
2  Chattogram   Jan  Rice   30  1300.0
(6, 5)
<class 'pandas.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   district  6 non-null      str
 1   month     6 non-null      str
 2   item      6 non-null      str
 3   qty       6 non-null      int64
 4   price     5 non-null      float64
dtypes: float64(1), int64(1), str(3)
memory usage: 372.0 bytes

এক লাইনে অনেক কিছু ঘটেছে। পর্ব ২৬-এর মতো হাতে int() করতে হয়নি — read_csv প্রতিটি কলাম দেখে ধরন অনুমান করেছে: qty পূর্ণসংখ্যা, লেখার কলাম str. ফাঁকা ঘরটি হয়েছে NaN (পর্ব ০৭-এর NaN — “মান নেই” বোঝাতে pandas এটাই ব্যবহার করে)। আর NaN একটি float, তাই পুরো price কলামটিই float64 হয়ে গেছে — 1300 হয়েছে 1300.0. বাঁ পাশের 0, 1, 2 হলো ডিফল্ট index.

নতুন ডেটা হাতে পেলে প্রথম তিনটি কাজ প্রায় সবসময় এই: head() (প্রথম কয়েকটি সারি, চোখে দেখা), info() (কলাম, ধরন আর কোথায় কত ফাঁকা — price-এ ৬-এর মধ্যে ৫টি non-null), আর describe() — সংখ্যার কলামের descriptive statistics:

print(df["qty"].describe())
count     6.0000
mean     29.5000
std      13.7659
min      12.0000
25%      21.2500
50%      27.5000
75%      37.5000
max      50.0000
Name: qty, dtype: float64

গণনা, গড়, বিস্তার (std), সর্বনিম্ন-সর্বোচ্চ আর quartile — ৫০% সারিটি median. বড় ডেটায় এই এক লাইন প্রায়ই প্রথম ভুল ধরিয়ে দেয়: বয়সের কলামে min −1, বা নম্বরের কলামে max 780 (কেউ 78-এর পরে একটি 0 বেশি লিখেছে)।

বাংলা CSV আর encoding. পর্ব ২৬-এর নিয়ম pandas-এও খাটে। Excel থেকে “CSV UTF-8” হিসেবে সেভ করা বাংলা ফাইল encoding="utf-8-sig" দিয়ে পড়ুন, নইলে প্রথম কলামের নামের সাথে অদৃশ্য BOM লেগে থাকে আর df["district"] দেয় KeyError. pandas ডিফল্টে UTF-8 ধরে নেয়; পুরোনো কোনো encoding-এ সেভ করা ফাইলে UnicodeDecodeError এলে ফাইলটি আবার UTF-8-এ সেভ করানোই সবচেয়ে নিরাপদ সমাধান। আর লেখার সময় Excel-এ খোলার জন্য to_csv(..., encoding="utf-8-sig").

version-এর একটি কথা। উপরের আউটপুট pandas 3.0-এর। ২০২৬ সালে আসা এই version-এ লেখার কলামের ডিফল্ট dtype str; pandas 2.x-এ একই কলাম দেখাত object. টিউটোরিয়াল বা AI-এর উত্তরে object দেখলে অবাক হবেন না — বেশিরভাগ লেখা পুরোনো version-এ।

৭ · বাছাই, ছাঁকা, নতুন কলাম আর ফাঁকা ঘর

কলাম বাছাই আর শর্ত দিয়ে সারি ছাঁকা

একটি কলাম চাইলে df["qty"] — ফল একটি Series. কয়েকটি কলাম চাইলে নামের একটি list: df[["district", "qty"]] (দুই জোড়া বন্ধনী — ভেতরেরটি list) — ফল আরেকটি DataFrame. আর সারি ছাঁকা হয় ঠিক NumPy-র boolean mask দিয়ে, শুধু এবার কলামের নাম ধরে:

print(df[["district", "qty"]].head(2))
big = df[(df["qty"] >= 25) & (df["month"] == "Jan")]
print(big)
  district  qty
0    Dhaka   40
1   Sylhet   25
     district month  item  qty   price
0       Dhaka   Jan  Rice   40  1300.0
1      Sylhet   Jan   Tea   25     NaN
2  Chattogram   Jan  Rice   30  1300.0

df["qty"] >= 25 একটি True/False Series বানায়, & দুটি শর্ত জোড়ে (প্রতিটি শর্ত বন্ধনীতে — অংশ ৪-এর নিয়ম), আর df[mask] শুধু True সারিগুলো রাখে। লক্ষ করুন index (0, 1, 2) মূল টেবিলের মতোই থেকে গেছে — প্রতিটি সারি তার পরিচয় সাথে নিয়ে চলে। নির্দিষ্ট সারি ও কলাম একসাথে বাছতে আছে df.loc[শর্ত, "কলাম"] (লেবেল দিয়ে) আর df.iloc[0:3, 1] (অবস্থান দিয়ে); বিস্তারিত Python for AI কোর্সের pandas পর্বে।

ফাঁকা মান — খোঁজা, বাদ দেওয়া, ভরা

বাস্তবের প্রায় প্রতিটি ডেটাসেটে missing value থাকে: কেউ জরিপের প্রশ্নের উত্তর দেননি, কোনো দিনের রেকর্ড হারিয়েছে, কেউ দাম লিখতে ভুলে গেছেন। এগুলোকে চুপচাপ উপেক্ষা করা বিপজ্জনক — তাই আগে গুনুন, তারপর সিদ্ধান্ত নিন:

print(df.isna().sum())
print(df.dropna().shape)
df["price"] = df["price"].fillna(400)
df["revenue"] = df["qty"] * df["price"]
print(df[["district", "item", "revenue"]])
district    0
month       0
item        0
qty         0
price       1
dtype: int64
(5, 5)
     district  item  revenue
0       Dhaka  Rice  52000.0
1      Sylhet   Tea  10000.0
2  Chattogram  Rice  39000.0
3       Dhaka   Oil  47500.0
4      Sylhet  Rice  15600.0
5  Chattogram   Tea   8000.0

তিনটি অস্ত্র: isna() প্রতিটি ঘরের জন্য “ফাঁকা কি?” বলে, আর .sum() কলাম ধরে গোনে — price-এ একটি। dropna() যেকোনো ফাঁকা ঘরসহ সারি বাদ দেয় — ৬ থেকে ৫ সারি। fillna(মান) ফাঁকা ঘর ভরে। কোনটি বেছে নেবেন, তা প্রোগ্রামিংয়ের নয়, বিচারের প্রশ্ন. এখানে দোকান থেকে জেনে নেওয়া গেছে যে চায়ের দাম ৪০০, তাই 400 বসানো ঠিক। কিন্তু অনুপস্থিত ছাত্রের নম্বরে 0 বসালে গড় অন্যায়ভাবে কমে যাবে, আর গড় দিয়ে ভরলে সে “গড় ছাত্র” হয়ে যাবে — যা সত্য নয়। কী করেছেন, সেটা রিপোর্টে লিখে রাখাই সততা।

শেষ দুই লাইনে আরেকটি জিনিস ঘটেছে: নতুন হিসাব করা কলাম. df["qty"] * df["price"] দুটি কলামকে সারি ধরে গুণ করেছে — vectorized, লুপ ছাড়া — আর ফল revenue নামে নতুন কলাম হয়ে বসেছে। Excel-এ একটি কলামে সূত্র লিখে নিচ পর্যন্ত টেনে দেওয়ার মতো, শুধু টানতে হয় না।

৮ · groupby — ভাগ করুন, হিসাব করুন, জোড়া দিন

“জেলাভিত্তিক মোট বিক্রি কত?” — ডেটা বিশ্লেষণের সবচেয়ে প্রচলিত প্রশ্নের ধাঁচ এটি: প্রতিটি দলের সারাংশ। প্রতিটি বিষয়ের গড়, প্রতিটি মাসের মোট, প্রতিটি বিভাগের বৃষ্টিপাত। আগে শুধু Python দিয়ে করে দেখি, পর্ব ২৭-এর defaultdict দিয়ে:

from collections import defaultdict

sales = [
    ("Dhaka", "Jan", 52000), ("Sylhet", "Jan", 10000),
    ("Chattogram", "Jan", 39000), ("Dhaka", "Feb", 47500),
    ("Sylhet", "Feb", 15600), ("Chattogram", "Feb", 8000),
]
groups = defaultdict(list)                     # ১. ভাগ (split)
for district, month, revenue in sales:
    groups[district].append(revenue)

summary = {d: (sum(v), len(v)) for d, v in groups.items()}   # ২. হিসাব (apply)
ranked = sorted(summary.items(), key=lambda kv: kv[1][0], reverse=True)
for d, (total, orders) in ranked:              # ৩. জোড়া (combine)
    print(f"{d:<11} total={total:>7} orders={orders}")

আউটপুট তিন লাইন: Dhaka total= 99500 orders=2, Chattogram total= 47000 orders=2, Sylhet total= 25600 orders=2. এগুলো অংশ ৭-এর revenue কলামের মানই। কোডের তিনটি মন্তব্য তিনটি ধাপ চিহ্নিত করেছে: জেলা ধরে মানগুলো আলাদা list-এ ভাগ, প্রতিটি list-এ যোগফল আর গণনা, তারপর ফলগুলো সাজিয়ে একটি টেবিল। sorted(..., reverse=True) মোটের বড় থেকে ছোট ক্রমে সাজিয়েছে; :<11 আর :>7 পর্ব ১০-এর format spec, কলাম সোজা রাখতে।

এই তিন ধাপের একটি নাম আছে: split-apply-combine. পরিসংখ্যানের R ভাষার জন্য Hadley Wickham ২০১১ সালের একটি গবেষণাপত্রে নামটি জনপ্রিয় করেন, কিন্তু ধারণাটা SQL-এর GROUP BY আর Excel-এর Pivot Table-এরও মূল কথা। pandas-এ পুরো কাজটি করে groupby, একটি শিকলে:

Dhaka · 52000
Sylhet · 10000
Chattogram · 39000
Dhaka · 47500
Sylhet · 15600
Chattogram · 8000
Dhaka: 52000, 47500
Chattogram: 39000, 8000
Sylhet: 10000, 15600
sum = 99500count = 2
sum = 47000count = 2
sum = 25600count = 2
নতুন DataFrameindex = district
Dhaka 99500 · Chattogram 47000 · Sylhet 25600sort_values দিয়ে সাজানো
  1. মূল টেবিল — ছয় সারি। প্রতিটি সারিতে একটি জেলা আর তার আয়। রং দিয়ে জেলা চেনানো হয়েছে, কিন্তু টেবিলে তারা এলোমেলো ক্রমে আছে।
  2. Split — জেলা ধরে ভাগ। df.groupby("district") সারিগুলোকে district কলামের মান ধরে তিনটি দলে ভাগ করে। পর্ব ৩৮-এর itertools.groupby-র মতো আগে sort করতে হয় না — pandas নিজেই সব সমান মান এক দলে আনে। এই ধাপে এখনো কোনো হিসাব হয়নি; groupby অলস, সে শুধু দলের তালিকা মনে রাখে।
  3. Apply — প্রতিটি দলে একই হিসাব। .agg(total=("revenue", "sum"), orders=("revenue", "count")) বলে: প্রতিটি দলের revenue-র যোগফলকে total নাম দাও, গণনাকে orders. হিসাবগুলো ভেতরে vectorized চলে।
  4. Combine — ফল একটি নতুন টেবিল। প্রতিটি দলের ফল জুড়ে নতুন DataFrame, যার index এখন জেলার নাম। .sort_values("total", ascending=False) বড় থেকে ছোট সাজায়। বিশুদ্ধ Python-এর পুরো কোডটা এখন একটি পড়তে-সহজ বাক্য।
summary = (df.groupby("district")
             .agg(total=("revenue", "sum"), orders=("revenue", "count"))
             .sort_values("total", ascending=False))
print(summary)
print(df.groupby("month")["revenue"].sum())
summary.to_csv("district_summary.csv", encoding="utf-8-sig")
              total  orders
district
Dhaka       99500.0       2
Chattogram  47000.0       2
Sylhet      25600.0       2
month
Feb     71100.0
Jan    101000.0
Name: revenue, dtype: float64

প্রথম টেবিল বিশুদ্ধ Python-এর ফলের সাথে হুবহু মেলে (float হয়েছে, কারণ price কলাম float ছিল)। দ্বিতীয়টি দেখায় একটি কলামে groupby করে একটি মাত্র কলামের যোগফল নিলে ফল একটি Series — index হিসেবে মাসের নাম, বর্ণানুক্রমে সাজানো (Feb আগে, Jan পরে; ক্যালেন্ডারের ক্রম চাইলে আলাদা করে বলতে হয়)। শেষ লাইন ফলটিকে নতুন CSV-তে সেভ করে — index (জেলা) প্রথম কলাম হিসেবে লেখা হয়; না চাইলে index=False. Excel-এ খোলার জন্য utf-8-sig.

pandas তৈরি করেন Wes McKinney, ২০০৮ সালে, যুক্তরাষ্ট্রের আর্থিক প্রতিষ্ঠান AQR Capital Management-এ কাজ করার সময় — তাঁর দরকার ছিল Python-এ R-এর data frame-এর মতো কিছু। ২০০৯ সালে এটি open source হয়। নামটি এসেছে অর্থনীতির “panel data” শব্দ থেকে, সাথে “Python data analysis”-এর ইঙ্গিত। NumPy আর pandas দুটিই আজ অলাভজনক প্রতিষ্ঠান NumFOCUS-এর সহায়তাপ্রাপ্ত প্রকল্প।

৯ · চার্ট, Jupyter আর ডেটার নৈতিকতা

একটি চার্টের ঝলক — matplotlib

সংখ্যার টেবিল থেকে প্যাটার্ন খোঁজার চেয়ে একটি চার্টে চোখ বোলানো অনেক দ্রুত। Python-এর সবচেয়ে পুরোনো ও প্রচলিত চার্ট লাইব্রেরি matplotlib; pandas-এর .plot ভেতরে এটিই ডাকে। আট বিভাগের বার্ষিক বৃষ্টিপাতের একটি অনুভূমিক bar chart দেখি। সংখ্যাগুলো আনুমানিক, শুধু শেখার জন্য — সাধারণ প্যাটার্নটা (সিলেটে সবচেয়ে বেশি, রাজশাহীতে সবচেয়ে কম) বাস্তবের মতো, কিন্তু আসল কাজে বাংলাদেশ আবহাওয়া অধিদপ্তরের মতো নির্ভরযোগ্য উৎসের ডেটা ব্যবহার করবেন:

import matplotlib.pyplot as plt
import pandas as pd

rain = pd.Series({"Sylhet": 4000, "Chattogram": 2900, "Barishal": 2500,
                  "Mymensingh": 2500, "Rangpur": 2200, "Dhaka": 2100,
                  "Khulna": 1800, "Rajshahi": 1500})     # মিমি, আনুমানিক
ax = rain.sort_values().plot.barh(color="steelblue")
ax.set_xlabel("Annual rainfall (mm, illustrative)")
ax.set_title("Rainfall by division")
plt.tight_layout()
plt.show()                       # Jupyter/Colab-এ চার্টটি নিচেই দেখাবে
print(rain.mean(), rain.idxmax(), rain.idxmin())
2437.5 Sylhet Rajshahi

চার্টটি দেখতে এমন: আটটি আড়াআড়ি নীল দণ্ড, নিচে সবচেয়ে ছোট রাজশাহী (১,৫০০) থেকে ওপরে সবচেয়ে লম্বা সিলেট (৪,০০০) পর্যন্ত। sort_values() আগে সাজিয়ে দিয়েছে বলে চোখ এক নজরে ক্রম ধরে ফেলে — সিলেটের দণ্ড রাজশাহীর আড়াই গুণেরও বেশি লম্বা। অনুভূমিক দণ্ড বেছে নেওয়ার কারণ: লম্বা নামগুলো (Chattogram, Mymensingh) বাঁ পাশে আরামে আঁটে। দুটি ছোট অভ্যাস চার্টকে সৎ রাখে — অক্ষের নামে একক (mm) লেখা, আর bar chart-এ অক্ষ শূন্য থেকে শুরু করা, যাতে দণ্ডের দৈর্ঘ্য সত্যিকারের অনুপাত দেখায়। ভালো চার্টের নিয়ম নিয়ে পুরো আলোচনা আছে ডেটা সায়েন্স কোর্সের visualization পর্বে।

কোথায় চালাবেন — Jupyter আর Google Colab

ডেটা নিয়ে কাজ সাধারণ স্ক্রিপ্টের মতো “লিখে, চালিয়ে, শেষ” নয় — বরং প্রশ্ন-উত্তরের মতো: একটু দেখি, একটি গড় বের করি, চার্ট আঁকি, তারপর নতুন প্রশ্ন। এর জন্য প্রচলিত পরিবেশ Jupyter notebook, যার কথা পর্ব ০৩-এর সেটআপে এসেছিল: কোড লেখা হয় ঘরে ঘরে (cell), প্রতিটি ঘর আলাদা চালানো যায়, আর ফল — টেবিল বা চার্ট — ঠিক নিচেই দেখা যায়। ডেটা একবার পড়ে মেমরিতে রেখে বারবার প্রশ্ন করা যায়।

Google Colab হলো ব্রাউজারে চলা বিনামূল্যের Jupyter. একটি Google অ্যাকাউন্ট দিয়ে colab.research.google.com খুলে নতুন notebook নিন — NumPy, pandas, matplotlib আগে থেকেই বসানো। মোবাইল বা দুর্বল কম্পিউটার থেকেও চলে। তবে কোড চলে Google-এর সার্ভারে, তাই কারও ব্যক্তিগত ডেটা সেখানে তোলার আগে ভাবুন (নিচের নৈতিকতা অংশ)।

পর্ব ২৩-এর মতো একটি virtual environment বানিয়ে install করুন, তারপর JupyterLab খুলুন — ব্রাউজারে একটি নতুন ট্যাব খুলবে। VS Code-এও Python আর Jupyter extension দিয়ে সরাসরি .ipynb ফাইল খোলা যায়।

pip install numpy pandas matplotlib jupyterlab
jupyter lab

notebook-এর একটি ফাঁদ: ঘরগুলো যেকোনো ক্রমে চালানো যায়, তাই ওপরের একটি ঘর বদলে নিচেরটি আবার না চালালে পর্দার ফল আর কোড আর মেলে না। কাজ শেষে “Restart and Run All” দিয়ে ওপর থেকে নিচে একবার চালিয়ে নিশ্চিত হোন — আর বারবার চালাতে হবে এমন বিশ্লেষণ শেষে সাধারণ .py স্ক্রিপ্টে সরিয়ে নিন।

ডেটার নৈতিকতা — মানুষের ডেটা মানে মানুষ

মার্কশিট, রোগীর রেকর্ড, bKash লেনদেন, চাকরির আবেদন — এগুলোর প্রতিটি সারি একজন মানুষ। নাম, ফোন নম্বর, NID, ঠিকানা বা রোলের সাথে নম্বর হলো personal data — যা দিয়ে একজনকে চেনা যায়। তিনটি সহজ নিয়ম মনে রাখুন:

অনেক দেশে ব্যক্তিগত তথ্য সুরক্ষার আইন আছে (যেমন ইউরোপের GDPR, ২০১৮ থেকে কার্যকর), আর আইন না থাকলেও দায়িত্ব থাকে। আরেকটি দিক: ডেটা কাদের নিয়ে আর কাদের বাদ দিয়ে তৈরি, সেটাও ফলকে বাঁকাতে পারে — শুধু শহরের স্কুলের ডেটা দিয়ে “সারা দেশের ছাত্ররা” নিয়ে সিদ্ধান্ত টানা যায় না।

এরপর কোথায়

এই পর্ব ছিল একটি সেতু। NumPy-র indexing, reshape, linear algebra আর pandas-এর গভীর কাজ (merge, pivot, সময়ভিত্তিক ডেটা) শিখতে ABCL TECH-এর Python for AI কোর্স দেখুন — সেখানে NumPy আর pandas নিয়ে আলাদা আলাদা পর্ব আছে, মেশিন লার্নিংয়ের দিকে এগিয়ে। আর পরিসংখ্যান, EDA, ফাঁকা ডেটা সামলানোর কৌশল, চার্ট আর ড্যাশবোর্ড — পুরো ডেটা বিশ্লেষকের পথ — আছে ডেটা সায়েন্স কোর্সে।

পরীক্ষা ও ইন্টারভিউতে
  • NumPy array আর Python list-এর মূল পার্থক্য কী? — array-র সব উপাদান একই dtype, মেমরিতে পাশাপাশি, আর গাণিতিক চিহ্ন প্রতিটি উপাদানে কাজ করে (vectorized); list যেকোনো object-এর ঠিকানা রাখে, আর + মানে জোড়া লাগানো।
  • shape (4, 3)-এর array-তে sum(axis=0)-এর ফলের shape কী? — (3,); axis 0 (সারি) গুটিয়ে প্রতিটি কলামের একটি যোগফল।
  • Series আর DataFrame-এর পার্থক্য? — Series এক মাত্রার লেবেলযুক্ত array; DataFrame একই index ভাগ করা অনেক Series-এর টেবিল, প্রতিটি কলাম একটি Series.
  • pandas-এ ফাঁকা মান কীভাবে খুঁজবেন ও সামলাবেন? — isna().sum() দিয়ে গোনা, তারপর প্রেক্ষাপট বুঝে dropna() বা fillna(মান); কী করলেন তা লিখে রাখা।
  • pandas-এ iterrows() দিয়ে লুপ কেন এড়ানো উচিত? — প্রতিটি সারিকে Python object বানিয়ে interpreter-এ চলে, তাই খুব ধীর; কলামভিত্তিক vectorized হিসাব একই কাজ C-তে করে।

১০ · AI-এর যুগে — AI-এর লেখা pandas কোড যাচাই

“এই CSV থেকে জেলাভিত্তিক বিক্রি বের করো” — AI এমন pandas কোড মুহূর্তে লেখে, আর প্রায়ই চলেও। কিন্তু pandas-এর কোডে ভুল খুব কমই error দেয়; বেশিরভাগ সময় দেয় ভুল সংখ্যা, যা দেখতে একদম স্বাভাবিক। নিচের ধাঁচগুলো বারবার দেখা যায়:

AI প্রায়ই যা লেখেসমস্যাভালো রূপ
df[df.qty > 30]["price"] = 0chained indexing: মাঝের অংশটি কপি, মূল টেবিল বদলায় না। pandas 2.x-এ SettingWithCopyWarning; pandas 3.0-এ Copy-on-Write ডিফল্ট, তাই এটি কখনো কাজ করে না আর ChainedAssignmentError সতর্কবার্তা আসেdf.loc[df.qty > 30, "price"] = 0
for i, row in df.iterrows(): দিয়ে প্রতিটি সারিতে হিসাবপ্রতিটি সারি একটি Series object হয়ে interpreter-এ চলে — বড় ডেটায় খুব ধীর; dtype-ও হারাতে পারেকলাম ধরে: df["qty"] * df["price"]; শর্তে np.where
ফাঁকা মান নিয়ে কিছুই না বলাmean()/sum() NaN চুপচাপ বাদ দেয়; গণনার ভিত্তি বদলে যায়, কেউ টের পায় নাআগে isna().sum(), তারপর সচেতন সিদ্ধান্ত
df["Total_Sales"], df["Region"] — যে কলাম ফাইলে নেইAI ফাইল দেখেনি, কলামের নাম বানিয়ে লিখেছে; KeyError, বা মিলে গেলে ভুল কলামprompt-এ df.columns আর head()-এর আউটপুট দেওয়া
pd.read_csv("file.csv"), encoding ছাড়াExcel-এর বাংলা ফাইলে BOM বা UnicodeDecodeErrorencoding="utf-8-sig"
পুরোনো API: df.append(...), object dtype ধরে নেওয়াappend pandas 2.0-তে সরানো হয়েছে; AI-এর প্রশিক্ষণ-ডেটা পুরোনোpd.concat([df, new]); নিজের pandas version বলে দেওয়া

AI-কে ডেটা বিশ্লেষণের কোড লিখতে বলার সময় ডেটার আসল চেহারা আর শর্তগুলো আগেই দিন:

আমি Python 3.12 আর pandas 3.0 ব্যবহার করি (Jupyter-এ)। sales.csv নামে একটি ফাইল আছে, Excel থেকে "CSV UTF-8" হিসেবে সেভ করা। df.columns-এর আউটপুট: ['district', 'month', 'item', 'qty', 'price']. df.head() আর df.isna().sum() নিচে দিলাম: price কলামে কিছু ঘর ফাঁকা। [এখানে head() আর isna().sum()-এর আউটপুট পেস্ট করো] কাজ: প্রতিটি জেলার মোট আয় (qty × price) আর অর্ডারের সংখ্যা বের করো, বড় থেকে ছোট সাজাও, আর ফলটি district_summary.csv-তে সেভ করো (Excel-এ খোলা হবে)। শর্ত: ১. শুধু উপরের কলামের নাম ব্যবহার করবে — নতুন কোনো কলামের নাম ধরে নেবে না। ২. iterrows বা সারি ধরে লুপ নয়; কলামভিত্তিক vectorized হিসাব। ৩. মান বসাতে হলে df.loc ব্যবহার করো, chained indexing নয়। ৪. price-এর ফাঁকা ঘর নিয়ে নিজে সিদ্ধান্ত নেবে না: কয়টি ফাঁকা তা ছাপাও, আর বাদ দেওয়া বনাম ভরার দুটি বিকল্প আলাদা করে দেখাও। ৫. read_csv আর to_csv-তে encoding="utf-8-sig" দাও। ৬. প্রতিটি ধাপের পর ফলের shape ছাপাও, যাতে আমি মিলিয়ে নিতে পারি।

AI-এর লেখা ডেটা-কোড পেলে — যাচাইয়ের চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১NumPy একদিনে আসেনি — এর আগে ছিল Numeric আর Numarray নামে দুটি প্রতিদ্বন্দ্বী লাইব্রেরি। এই বিভাজন কেন হয়েছিল, আর ২০০৬ সালের NumPy 1.0 কীভাবে তা মেটাল?

১৯৯০-এর দশকের মাঝামাঝি বিজ্ঞানী ও প্রকৌশলীরা Python-এর সরলতা পছন্দ করছিলেন, কিন্তু সংখ্যার বড় সারি নিয়ে কাজের জন্য তাঁরা ব্যবহার করতেন MATLAB, IDL বা Fortran. Python-এ দরকার ছিল একটি দক্ষ array ধরন। ১৯৯৫ সালের দিকে একটি আলোচনা-গোষ্ঠী (matrix-sig) থেকে জন্ম নেয় Numeric; এর প্রথম বড় বাস্তবায়ন করেন Jim Hugunin, পরে আরও অনেকে যোগ দেন। Numeric-এর প্রভাব Python ভাষাতেও পড়েছিল — বহুমাত্রিক slicing-এর সুবিধার মতো কিছু বৈশিষ্ট্য বৈজ্ঞানিক ব্যবহারকারীদের অনুরোধে ভাষায় এসেছিল।

সময়ের সাথে Numeric-এর সীমা দেখা দিল, বিশেষ করে খুব বড় array-তে। টেলিস্কোপের বিশাল ছবি নিয়ে কাজ করা Space Telescope Science Institute-এর একটি দল (Hubble টেলিস্কোপের ডেটা যাদের হাতে) ২০০০-এর দশকের শুরুতে লিখল Numarray — বড় ডেটায় ভালো, আরও নমনীয়, কিন্তু ছোট array-তে ধীর। ফলে সম্প্রদায় দুই ভাগ হয়ে গেল: কিছু লাইব্রেরি Numeric-এর ওপর, কিছু Numarray-এর ওপর। একটি প্রকল্পে দুটি লাইব্রেরি একসাথে ব্যবহার করতে গেলে array রূপান্তরের ঝামেলা। বিজ্ঞানীদের জন্য এটি ছিল বড় অপচয় — একই কাজের দুটি অসংগত ভিত।

২০০৫ সালে Travis Oliphant, তখন একজন বিশ্ববিদ্যালয়-শিক্ষক এবং SciPy-র অন্যতম প্রতিষ্ঠাতা, দুই পক্ষের সেরা ধারণা মিলিয়ে একটি নতুন লাইব্রেরি লিখতে বসেন — Numeric-এর কোডভিত্তি থেকে শুরু করে Numarray-এর বৈশিষ্ট্যগুলো যোগ করে। প্রায় একা, দীর্ঘ পরিশ্রমে লেখা সেই কাজ ২০০৬ সালের অক্টোবরে NumPy 1.0 হিসেবে প্রকাশিত হয়। এতে এল আজকের ndarray, নমনীয় dtype ব্যবস্থা, broadcasting-এর পূর্ণ নিয়ম। ধীরে ধীরে Numeric আর Numarray-এর ব্যবহারকারীরা NumPy-তে চলে এলেন, আর পুরোনো দুটির রক্ষণাবেক্ষণ বন্ধ হলো।

এই একীকরণের ফল সুদূরপ্রসারী। পরের প্রায় দুই দশকে যত বৈজ্ঞানিক Python লাইব্রেরি এসেছে — SciPy, matplotlib, pandas, scikit-learn, আরও শত শত — সবাই একই ndarray-কে সাধারণ ভাষা হিসেবে নিয়েছে। একটি লাইব্রেরির ফল সরাসরি আরেকটিতে দেওয়া যায়, কোনো রূপান্তর ছাড়া। ২০২৪ সালে NumPy 2.0 এল — প্রায় আঠারো বছর পর প্রথম বড় version, কিছু পুরোনো আচরণ পরিষ্কার করতে।

মূল উপলব্ধি: একটি ইকোসিস্টেমে সবচেয়ে মূল্যবান জিনিস প্রায়ই সবচেয়ে চমকপ্রদ লাইব্রেরি নয়, বরং সবার মেনে নেওয়া একটি সাধারণ ডেটা ধরন. দুটি ভালো কিন্তু অসংগত মানদণ্ডের চেয়ে একটি গ্রহণযোগ্য মানদণ্ড অনেক বেশি শক্তিশালী।

প্র ০২pandas এসেছে একটি আর্থিক প্রতিষ্ঠানের ভেতর থেকে, একজন মানুষের হাতে। একটি কোম্পানির ভেতরের টুল কীভাবে বিশ্বের ডেটা বিশ্লেষণের প্রধান লাইব্রেরি হয়ে উঠল?

২০০৮ সালে Wes McKinney যুক্তরাষ্ট্রের AQR Capital Management-এ আর্থিক ডেটা বিশ্লেষণের কাজ করছিলেন। তাঁর প্রতিদিনের কাজ ছিল সময়ভিত্তিক ডেটা — শেয়ারের দাম, সূচক — জোড়া লাগানো, সারিবদ্ধ করা, ফাঁকা দিন সামলানো। তখনকার টুলগুলো তাঁকে সন্তুষ্ট করছিল না: Excel বড় ডেটায় ভাঙে আর পুনরাবৃত্তিযোগ্য নয়; R-এ data frame ছিল, কিন্তু R একটি সাধারণ-উদ্দেশ্যের ভাষা হিসেবে বড় সিস্টেম বানানোর জন্য তাঁর পছন্দ ছিল না; আর NumPy-তে array ছিল, কিন্তু লেবেল ছিল না — কোন সারি কোন তারিখ, নিজে মনে রাখতে হতো। তিনি NumPy-র ওপর লেবেলযুক্ত, ফাঁকা মান সহনশীল একটি টেবিল-ধরন লিখতে শুরু করলেন।

pandas-এর কেন্দ্রীয় ধারণা ছিল সারিবদ্ধকরণ (alignment): দুটি Series যোগ করলে pandas অবস্থান নয়, index-এর লেবেল মিলিয়ে যোগ করে — যে তারিখ একটিতে আছে অন্যটিতে নেই, সেখানে NaN. আর্থিক ডেটায় এটি ছিল বিপ্লবী, কারণ হাতে তারিখ মেলানোর ভুলই ছিল সবচেয়ে প্রচলিত বাগ। ২০০৯ সালে McKinney প্রতিষ্ঠানকে রাজি করিয়ে কোডটি open source করেন — একটি আর্থিক প্রতিষ্ঠানের জন্য তখন এটি অস্বাভাবিক সিদ্ধান্ত ছিল।

এরপর তিনটি জিনিস একসাথে ঘটল। প্রথমত, McKinney প্রতিষ্ঠান ছেড়ে পুরো সময় pandas-এ দিলেন, আর অন্য অবদানকারীরা যোগ দিলেন। দ্বিতীয়ত, ২০১২ সালে তাঁর বই Python for Data Analysis প্রকাশিত হলো, যা হাজারো মানুষের কাছে pandas-এর প্রবেশপথ হয়ে উঠল। তৃতীয়ত, ঠিক সেই সময়ে “ডেটা সায়েন্স” শব্দটি জনপ্রিয় হচ্ছিল, আর IPython notebook (পরে Jupyter) আর scikit-learn একই সময়ে পরিণত হচ্ছিল। pandas হয়ে গেল সেই পথের “প্রথম মাইল” — ডেটা পড়া, পরিষ্কার করা আর সাজানো, মডেলে দেওয়ার আগে।

সাফল্যের সাথে এসেছে কিছু ভার। প্রথম দিকের তাড়াহুড়োর নকশার কিছু সিদ্ধান্ত — যেমন একটি টেবিলের অংশ কখন কপি আর কখন আসল ডেটার দৃশ্য, যা থেকে বিখ্যাত SettingWithCopyWarning — বছরের পর বছর ব্যবহারকারীদের বিভ্রান্ত করেছে। McKinney নিজেই ২০১৭ সালে একটি লেখায় pandas-এর কিছু নকশাগত দুর্বলতা স্বীকার করেন, আর পরে Apache Arrow প্রকল্পে যুক্ত হন — বিভিন্ন টুলের মধ্যে কলামভিত্তিক ডেটা ভাগ করার একটি সাধারণ মেমরি-ফরম্যাট। pandas 3.0-এ Copy-on-Write ডিফল্ট হওয়া সেই পুরোনো বিভ্রান্তির অবসান ঘটানোর চেষ্টা।

মূল উপলব্ধি: বড় টুল প্রায়ই জন্মায় একজন মানুষের নিজের প্রতিদিনের যন্ত্রণা থেকে, আর টিকে থাকে open source, ভালো ডকুমেন্টেশন আর ঠিক সময়ের কারণে। তবে দ্রুত জনপ্রিয় হওয়া নকশার ভুল পরে ঠিক করা খুব কঠিন — লাখো মানুষের কোড পুরোনো আচরণের ওপর দাঁড়িয়ে থাকে।

প্র ০৩R ভাষা পরিসংখ্যানের জন্যই বানানো, MATLAB ছিল প্রকৌশলীদের প্রিয় — তবু আজ ডেটা সায়েন্স আর AI-এর প্রধান ভাষা Python. একটি সাধারণ-উদ্দেশ্যের ভাষা কীভাবে এই জায়গা দখল করল?

২০০০-এর দশকের শুরুতে বৈজ্ঞানিক হিসাবের জগতে Python ছিল প্রান্তিক। পরিসংখ্যানবিদরা ব্যবহার করতেন R (১৯৯০-এর দশকে নিউজিল্যান্ডে Ross Ihaka ও Robert Gentleman শুরু করেন), প্রকৌশলীরা MATLAB, আর ভারী সিমুলেশনে Fortran ও C. Python-এর সুবিধা ছিল অন্যখানে: পড়তে সহজ, “আঠা” হিসেবে দারুণ — C বা Fortran-এ লেখা দ্রুত কোডকে সহজে ডাকা যায় (পর্ব ০৫-এর C extension) — আর বিনামূল্যের, যেখানে MATLAB-এর লাইসেন্স ছিল ব্যয়বহুল।

এর ওপর ধীরে ধীরে গড়ে উঠল যাকে বলা হয় SciPy stack. ২০০১ সালে Travis Oliphant, Eric Jones ও Pearu Peterson শুরু করেন SciPy — বৈজ্ঞানিক অ্যালগরিদমের সংগ্রহ। একই বছর Fernando Pérez, তখন পদার্থবিজ্ঞানের স্নাতকোত্তর ছাত্র, লেখেন IPython — একটি উন্নত interactive shell. ২০০৩ সালে নিউরোবায়োলজিস্ট John D. Hunter MATLAB-এর মতো চার্ট আঁকতে লেখেন matplotlib. ২০০৬-এ NumPy সবাইকে একটি সাধারণ array দিল (প্রশ্ন ০১)। ২০০৮-০৯ সালে pandas, আর ২০০৭ সালে Google Summer of Code-এর একটি প্রকল্প হিসেবে শুরু হয়ে ২০১০ সালে প্রথম বড় প্রকাশ পায় scikit-learn — মেশিন লার্নিংয়ের অ্যালগরিদম, একটি সুসংগত ইন্টারফেসে।

২০১৪ সালে IPython notebook ভাষা-নিরপেক্ষ Jupyter প্রকল্পে রূপ নেয় (নামটি Julia, Python আর R-এর ইঙ্গিত)। notebook হয়ে উঠল ডেটা সায়েন্সের প্রধান কর্মক্ষেত্র — কোড, ফল, চার্ট আর ব্যাখ্যা এক পাতায়। তারপর এল গভীর শিক্ষার যুগ: TensorFlow (২০১৫) আর PyTorch (২০১৬) দুটিই Python-কে প্রধান ইন্টারফেস বানাল। AI গবেষণা যত বাড়ল, Python-এর অবস্থান তত পোক্ত হলো।

মূল কারণটা সম্ভবত এটি: R পরিসংখ্যানে অসাধারণ, কিন্তু বিশ্লেষণ শেষে সেটিকে একটি ওয়েব সার্ভিসে, একটি অটোমেশন স্ক্রিপ্টে বা একটি বড় সফটওয়্যারে বসাতে হলে Python-এর মতো সাধারণ-উদ্দেশ্যের ভাষা সুবিধাজনক। একই ভাষায় ডেটা পড়া, মডেল বানানো, API বানানো (পর্ব ৪৮), আর সার্ভারে চালানো যায়। আর শিক্ষার্থীদের জন্য প্রথম ভাষা হিসেবে Python-এর জনপ্রিয়তা নতুন প্রজন্মকে এই পথে এনেছে। তবু R আজও পরিসংখ্যান ও জীববিজ্ঞানের গবেষণায় শক্তিশালী, আর Julia-র মতো নতুন ভাষা গতির প্রশ্নে চ্যালেঞ্জ দিচ্ছে।

মূল উপলব্ধি: একটি ভাষা কোনো ক্ষেত্রে আধিপত্য পায় শুধু ভাষার গুণে নয়, বরং তার চারপাশের লাইব্রেরি, টুল আর মানুষের সম্মিলিত শক্তিতে। Python-এর “ধীর” হওয়া সমস্যা হয়নি, কারণ ভারী কাজ C-তে লেখা লাইব্রেরিগুলো করে — Python শুধু তাদের পরিচালনা করে।

প্র ০৪২০২০ সালে বিজ্ঞান সাময়িকী Nature-এ NumPy নিয়ে একটি গবেষণাপত্র প্রকাশিত হয়। একটি সফটওয়্যার লাইব্রেরি নিয়ে শীর্ষ বিজ্ঞান সাময়িকীতে লেখা কেন গুরুত্বপূর্ণ ছিল?

২০২০ সালের সেপ্টেম্বরে Nature-এ প্রকাশিত হয় “Array programming with NumPy” — লেখক Charles R. Harris, K. Jarrod Millman, Stéfan van der Walt সহ NumPy-র দীর্ঘদিনের অবদানকারীদের একটি বড় দল (Travis Oliphant-ও লেখকদের মধ্যে)। লেখাটি নতুন কোনো আবিষ্কারের ঘোষণা নয়; এটি ব্যাখ্যা করে array programming কী — vectorization, broadcasting, indexing, view বনাম copy — আর কীভাবে NumPy পুরো বৈজ্ঞানিক Python জগতের ভিত হয়ে উঠেছে।

কেন এটি গুরুত্বপূর্ণ? বিজ্ঞানের জগতে স্বীকৃতির মুদ্রা হলো গবেষণাপত্র আর তার উদ্ধৃতি (citation). হাজারো গবেষণা NumPy ব্যবহার করে হিসাব করে, কিন্তু বহু বছর ধরে NumPy-কে উদ্ধৃত করার মতো একটি প্রামাণিক লেখাই ছিল না। ফলে যাঁরা এই সফটওয়্যার রক্ষণাবেক্ষণ করেন, তাঁদের কাজ একাডেমিক চাকরি বা অনুদানের হিসাবে প্রায় অদৃশ্য থাকত। Nature-এর মতো সাময়িকীতে প্রকাশ হওয়া মানে বৈজ্ঞানিক সফটওয়্যারকে একটি গবেষণা-অবদান হিসেবে স্বীকার করা।

লেখাটিতে একটি অস্বস্তিকর সত্যও উঠে আসে: বিশাল ব্যবহারের তুলনায় NumPy দীর্ঘদিন চলেছে অল্প কয়েকজন রক্ষণাবেক্ষণকারীর ওপর, অনেকে স্বেচ্ছায়, ছুটির সময়ে। পূর্ণকালীন অর্থায়ন এসেছে অনেক পরে, কিছু ফাউন্ডেশন ও প্রতিষ্ঠানের অনুদানে। এটি open source-এর একটি পরিচিত সমস্যা — পুরো পৃথিবী যে ভিতের ওপর দাঁড়িয়ে, তার দেখাশোনা করেন হাতে গোনা কয়েকজন।

লেখাটি NumPy-র ভবিষ্যতের দিকও দেখায়। আজ array শুধু CPU-র মেমরিতে থাকে না — GPU-তে (CuPy, PyTorch), বহু কম্পিউটারে ভাগ করে (Dask), বা অলসভাবে হিসাব করে (JAX). NumPy তাই কিছু “প্রোটোকল” তৈরি করেছে, যাতে অন্য লাইব্রেরির array-ও NumPy-র function বুঝতে পারে। অর্থাৎ NumPy একটি লাইব্রেরি থেকে একটি সাধারণ API-র মানদণ্ডে পরিণত হচ্ছে — আজ NumPy-র ধাঁচে লেখা কোড অনেক সময় অল্প বদলে GPU-তে চালানো যায়। লেখাটিতে উদাহরণ হিসেবে প্রশ্ন ০৫-এর দুটি বৈজ্ঞানিক সাফল্যে NumPy-র ভূমিকার কথাও আছে।

মূল উপলব্ধি: আধুনিক বিজ্ঞান সফটওয়্যারের ওপর দাঁড়িয়ে, আর সেই সফটওয়্যারকে টিকিয়ে রাখা নিজেই একটি বৈজ্ঞানিক কাজ। যে লাইব্রেরি আপনি বিনামূল্যে ব্যবহার করেন, তার পেছনে মানুষের শ্রম আছে — উদ্ধৃতি দেওয়া, বাগ রিপোর্ট করা বা অবদান রাখা সেই ঋণ শোধের উপায়।

প্র ০৫২০১৯ সালে কৃষ্ণগহ্বরের প্রথম ছবি আর ২০১৫ সালে মহাকর্ষ তরঙ্গের প্রথম শনাক্তকরণ — দুটি ঐতিহাসিক বৈজ্ঞানিক সাফল্যেই Python-এর বৈজ্ঞানিক টুলের ভূমিকা ছিল। এই ভূমিকা ঠিক কতটুকু, আর এর থেকে কী শেখার আছে?

২০১৫ সালের ১৪ সেপ্টেম্বর যুক্তরাষ্ট্রের LIGO-র দুটি যন্ত্র প্রথমবারের মতো সরাসরি মহাকর্ষ তরঙ্গ শনাক্ত করে — দুটি কৃষ্ণগহ্বরের মিলনে স্থান-কালের সূক্ষ্ম কাঁপুনি, যার ভবিষ্যদ্বাণী আইনস্টাইনের তত্ত্বে ছিল। ঘোষণা আসে ২০১৬ সালের ফেব্রুয়ারিতে, আর ২০১৭ সালে Rainer Weiss, Barry Barish ও Kip Thorne পদার্থবিজ্ঞানে নোবেল পান। ২০১৯ সালের এপ্রিলে Event Horizon Telescope (EHT) প্রকাশ করে M87 ছায়াপথের কেন্দ্রের কৃষ্ণগহ্বরের প্রথম ছবি — পৃথিবীর নানা প্রান্তের রেডিও টেলিস্কোপ মিলিয়ে একটি পৃথিবী-আকারের “ভার্চুয়াল টেলিস্কোপ” বানিয়ে।

দুটি ক্ষেত্রেই বিশাল পরিমাণ ডেটা বিশ্লেষণ করতে হয়েছিল, আর গবেষকদের প্রকাশিত কাজে ব্যবহৃত সফটওয়্যারের তালিকায় NumPy, SciPy, matplotlib-এর মতো Python টুলের নাম আছে। LIGO তাঁদের প্রথম শনাক্তকরণের ডেটা প্রকাশ্যে উন্মুক্ত করেছিল, আর সেই ডেটা বিশ্লেষণের Python টিউটোরিয়ালও দিয়েছিল — যে কেউ নিজের কম্পিউটারে সেই সংকেত দেখতে পারে। EHT-র ছবি তৈরিতে একাধিক স্বাধীন পদ্ধতি ব্যবহার হয়েছিল, যাতে একটি পদ্ধতির পক্ষপাত ফলকে বাঁকাতে না পারে; তার অন্তত একটি (eht-imaging) Python-এ লেখা লাইব্রেরি। ২০২০ সালের NumPy-র Nature গবেষণাপত্রেও (প্রশ্ন ০৪) এই দুটি সাফল্যকে NumPy-র ব্যবহারের উদাহরণ হিসেবে উল্লেখ করা হয়েছে।

তবে সাবধানে বলা দরকার: “Python কৃষ্ণগহ্বরের ছবি তুলেছে” — এমন দাবি ভুল। এগুলো শত শত বিজ্ঞানী আর প্রকৌশলীর বহু বছরের কাজ — যন্ত্র নির্মাণ, পদার্থবিজ্ঞানের তত্ত্ব, সূক্ষ্ম পরিমাপ। সফটওয়্যারের সব অংশও Python নয়; অনেক ভারী হিসাব C, C++ বা অন্য ভাষায় লেখা, আর Python সেগুলোকে জোড়া লাগানো, বিশ্লেষণ ও চিত্র তৈরিতে ব্যবহৃত হয়েছে। সঠিক কথা হলো: বিনামূল্যের, open source Python টুলগুলো এই কাজের হাতিয়ারের একটি গুরুত্বপূর্ণ অংশ ছিল।

এখান থেকে শেখার বিষয় তিনটি। প্রথমত, পুনরুৎপাদনযোগ্যতা: ডেটা আর বিশ্লেষণের কোড উন্মুক্ত হলে অন্যরা যাচাই করতে পারে — বিজ্ঞানের বিশ্বাসযোগ্যতার ভিত। দ্বিতীয়ত, আপনি এই পর্বে যে NumPy আর matplotlib শিখলেন, ঠিক সেই টুলই এমন গবেষণায় ব্যবহৃত হয় — শেখার আর পেশাদার কাজের টুলের মধ্যে দূরত্ব খুব কম। তৃতীয়ত, বড় দাবি করার আগে উৎস যাচাই — ডেটা সায়েন্সের মূল নৈতিকতা নিজের কথাতেও খাটে।

মূল উপলব্ধি: বড় বৈজ্ঞানিক সাফল্য একটি টুলের নয়, একটি পুরো ইকোসিস্টেম আর অসংখ্য মানুষের। open source টুল সেই ইকোসিস্টেমের সাধারণ ভাষা — আর তা ব্যবহার করে ঢাকা বা রাজশাহীর একজন শিক্ষার্থীও LIGO-র আসল ডেটায় হাত দিতে পারেন।

অনুশীলন

এগুলোর বেশিরভাগে নতুন কোড লিখতে হবে না — অনুমান করা, হাতে চালানো আর মিলিয়ে নেওয়াই আসল কাজ। NumPy/pandas-এর উত্তর মেলাতে নিজের কম্পিউটার বা Colab ব্যবহার করুন।

  1. হাতে চালান: চালানোর আগে লিখুন কী ছাপা হবে — (ক) np.array([1, 2.5, 3]).dtype; (খ) np.array([1, 2, 3]) * 2 আর [1, 2, 3] * 2; (গ) a = np.array([12, 7, 30, 18]); print(a[a % 2 == 0], (a > 10).sum()).

    (ক) float64 — একটি array-তে একটিই dtype, আর 2.5 রাখতে হলে সবাইকে float হতে হয়। (খ) array-তে [2 4 6] (প্রতিটি উপাদান গুণ), list-এ [1, 2, 3, 1, 2, 3] (list দুবার জোড়া)। (গ) [12 30 18] 3 — জোড় সংখ্যাগুলো mask দিয়ে বাছা, আর ১০-এর বড় তিনটি (12, 30, 18) True, যাদের যোগফল 3.

  2. axis ভাবুন: একটি দোকানের চার জেলার ১২ মাসের বিক্রি একটি array-তে আছে, shape (4, 12) — সারি জেলা, কলাম মাস। (ক) sales.sum(axis=1)-এর shape আর অর্থ কী? (খ) sales.sum(axis=0)? (গ) কোন মাসে চার জেলা মিলিয়ে বিক্রি সবচেয়ে বেশি, তার index কীভাবে পাবেন? (ঘ) প্রতিটি জেলার সেরা মাস?

    (ক) shape (4,) — মাসের মাত্রা গুটিয়ে প্রতিটি জেলার বার্ষিক মোট। (খ) shape (12,) — জেলার মাত্রা গুটিয়ে প্রতিটি মাসের সারা-দেশ মোট। (গ) sales.sum(axis=0).argmax() — ০ থেকে ১১-এর একটি সংখ্যা (০ = জানুয়ারি)। (ঘ) sales.argmax(axis=1) — shape (4,), প্রতিটি জেলার জন্য সর্বোচ্চ বিক্রির মাসের index. মনে রাখার কৌশল: যে axis দেন, ফলের shape থেকে সেই সংখ্যাটি মুছে যায়।

  3. বাগ খুঁজুন: একটি AI এই কোড দিয়েছে প্রতিটি ছাত্রের ফল লিখতে: df = pd.read_csv("marks.csv"), তারপর for i, row in df.iterrows(): লুপের ভেতরে if row["Total_Marks"] >= 33: df[df.index == i]["result"] = "Pass", শেষে print(df["result"].value_counts()). ফাইলটি Excel থেকে সেভ করা বাংলা নামসহ, কলাম name, bangla, english, math, আর কয়েকজনের কিছু নম্বর ফাঁকা। অন্তত চারটি সমস্যা খুঁজুন।

    (১) Total_Marks নামে কোনো কলাম নেই — AI বানিয়ে নিয়েছে; আগে df["total"] = df[["bangla", "english", "math"]].sum(axis=1) লাগবে, আর পাসের শর্ত আসলে প্রতিটি বিষয়ে ৩৩, মোটে নয়। (২) df[df.index == i]["result"] = ... chained indexing — মূল টেবিল বদলায় না (pandas 3.0-এ কখনোই নয়), তাই result কলাম তৈরিই হয় না আর শেষ লাইনে KeyError; ঠিক রূপ df.loc[...]. (৩) iterrows লুপ অপ্রয়োজনীয় ও ধীর — vectorized রূপ: passed = (df[["bangla", "english", "math"]] >= 33).all(axis=1), তারপর df["result"] = np.where(passed, "Pass", "Fail"). (৪) ফাঁকা নম্বর (NaN) নিয়ে সিদ্ধান্ত নেই — NaN-এর সাথে তুলনা সবসময় False, তাই অনুপস্থিত ছাত্র চুপচাপ “Fail” হয়ে যাবে; আলাদা করে চিহ্নিত করা উচিত। (৫) Excel-এর বাংলা ফাইলে encoding="utf-8-sig" নেই।

  4. ছোট কোড লিখুন: rain.csv-এ কলাম division, month, rain_mm, কিছু rain_mm ফাঁকা। pandas দিয়ে লিখুন: (ক) প্রতিটি কলামে কয়টি ফাঁকা, ছাপান; (খ) ফাঁকা সারিগুলো বাদ দিয়ে প্রতিটি বিভাগের মোট বৃষ্টিপাত, বড় থেকে ছোট; (গ) সবচেয়ে বেশি বৃষ্টির বিভাগের নাম; (ঘ) ফল rain_summary.csv-তে সেভ।
    import pandas as pd
    
    df = pd.read_csv("rain.csv", encoding="utf-8-sig")
    print(df.isna().sum())                              # (ক)
    clean = df.dropna(subset=["rain_mm"])
    print("বাদ গেল:", len(df) - len(clean), "সারি")
    total = (clean.groupby("division")["rain_mm"]
                  .sum()
                  .sort_values(ascending=False))        # (খ)
    print(total)
    print("সবচেয়ে বেশি:", total.idxmax())                # (গ)
    total.to_csv("rain_summary.csv", encoding="utf-8-sig")   # (ঘ)

    dropna(subset=[...]) শুধু ওই কলাম ফাঁকা হলে সারি বাদ দেয়। কয়টি সারি বাদ গেল তা ছাপানো সৎ অভ্যাস — কোনো বিভাগের অর্ধেক মাস ফাঁকা থাকলে তার “মোট” অন্যদের সাথে তুলনীয় নয়, আর সেটি রিপোর্টে বলা উচিত। এমন ক্ষেত্রে মোটের বদলে মাসিক গড় (.mean()) বেশি ন্যায্য হতে পারে।

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

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

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

  1. ❓ স্পষ্ট প্রশ্ন ঠিক করা
  2. 📥 ডেটা জোগাড় করা (ফাইল, database, API)
  3. 🧹 ডেটা পরিষ্কার করা — ফাঁকা ঘর, ভুল ধরন, ডুপ্লিকেট
  4. 🔍 ঘুরে দেখা — গড়, দলভিত্তিক সারাংশ
  5. 📊 চার্ট বানানো
  6. 📢 ফল ও সীমাবদ্ধতা জানানো

NumPy-তে np.array([1, 2, 3]) * 2-এর ফল কী?

  • [2 4 6]
  • [1 2 3 1 2 3]
  • TypeError
  • [1, 2, 3, 1, 2, 3]

NumPy-তে shape (5, 3)-এর একটি array (৫ জন ছাত্র × ৩ বিষয়)-এ table.mean(axis=0) কী দেয়?

  • ৫টি মান — প্রতিটি ছাত্রের গড়
  • একটি মাত্র মান — পুরো টেবিলের গড়
  • ৩টি মান — প্রতিটি বিষয়ের গড়
  • একই shape (5, 3)-এর একটি array

NumPy-তে a = np.array([78, 65, 88, 45, 82]) হলে a[a >= 80]-এর ফল কী?

  • [False False True False True]
  • [88 82]
  • [2 4]
  • 2

pandas-এ একটি DataFrame df-এর প্রতিটি কলামে কয়টি ফাঁকা (missing) মান আছে, তা জানার সঠিক উপায় কোনটি?

  • df.dropna()
  • df.fillna(0)
  • (df == None).sum()
  • df.isna().sum()

দশ লাখ সংখ্যার যোগফল বের করতে Python list-এ লুপের চেয়ে NumPy array-র sum() অনেক দ্রুত হওয়ার মূল কারণ কী?

  • সংখ্যাগুলো একই ধরনে পাশাপাশি মেমরিতে থাকে, আর লুপটি C-তে চলে
  • NumPy সব হিসাব স্বয়ংক্রিয়ভাবে GPU-তে পাঠায়
  • NumPy সবসময় সব CPU core একসাথে ব্যবহার করে
  • NumPy আনুমানিক হিসাব করে, পুরো যোগ করে না

pandas-এ df.groupby("district")["revenue"].sum()-এর ফল কী?

  • পুরো টেবিলের revenue-র একটি মাত্র যোগফল
  • মূল টেবিলের সমান সারির একটি নতুন কলাম
  • জেলার নাম index করা একটি Series, প্রতিটি জেলার মোট revenue
  • একটি error, কারণ আগে district ধরে sort করা হয়নি

শব্দকোষ

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

মনে রাখুন

  1. ডেটা সায়েন্স = প্রশ্ন → ডেটা → পরিষ্কার → ঘুরে দেখা → চার্ট → জানানো. লাইব্রেরি মাঝের ধাপ সহজ করে; প্রশ্ন আর বিচার আপনার।
  2. NumPy-র ndarray: একই dtype, পাশাপাশি মেমরি, shape. গাণিতিক চিহ্ন প্রতিটি উপাদানে কাজ করে, লুপ চলে C-তে (vectorization); একক মান broadcasting-এ পুরো array-তে ছড়ায়।
  3. শর্ত দিয়ে বাছাই boolean mask-এ: a[a >= 80], শর্ত জোড়ে &/|, বন্ধনীসহ। axis=0 সারি গুটিয়ে প্রতিটি কলামের ফল, axis=1 প্রতিটি সারির।
  4. pandas: Series (লেবেলযুক্ত array) আর DataFrame (নাম-লেখা টেবিল)। read_csv (বাংলা Excel ফাইলে utf-8-sig) → head/info/describe → ছাঁকা → নতুন কলাম → groupby().agg() → sort_values → to_csv.
  5. ফাঁকা মান আগে গুনুন (isna().sum()), তারপর বুঝে dropna/fillna. মান বসাতে df.loc, iterrows নয়। মানুষের ব্যক্তিগত ডেটা প্রকাশের আগে anonymize করুন।

পরের পর্বে: এই পর্বে ডেটা এসেছিল ফাইল থেকে। কিন্তু আবহাওয়া, মুদ্রার বিনিময় হার বা কোনো ওয়েবসাইটের তথ্য আসে ইন্টারনেট থেকে। পরের পর্বে HTTP কীভাবে কাজ করে, API থেকে JSON ডেটা কীভাবে আনবেন, আর ওয়েবপাতা থেকে তথ্য সংগ্রহের (scraping) সময় কোন নিয়ম আর দায়িত্ব মানতে হয়।

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

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