Skip to content

সমাধান — অধ্যায় ০.৬ · Python On-ramp for Scientific Computing

এই ফাইলে ০.৬ অধ্যায়ের সব অনুশীলনীর পূর্ণ সমাধান। প্রতিটি কোড runnable; নিচে প্রকৃত output দেওয়া।


Conceptual

সমাধান ১ — vectorization বনাম for-loop [easy]

মূল পার্থক্য: Python for-loop প্রতিটি উপাদান এক-এক করে interpreted কোডে প্রক্রিয়া করে; প্রতিটি iteration-এ interpreter type যাচাই, object তৈরি, reference গণনা ইত্যাদি overhead সামলায়। Vectorization-এ পুরো array-র উপর operation চালায় আগে থেকে compile করা C কোড, যেখানে এই per-element overhead নেই এবং উপাদানগুলো memory-তে পাশাপাশি থাকায় CPU দক্ষভাবে (cache-friendly, প্রায়ই SIMD-সহ) কাজ করে।

কেন দ্রুত (সংক্ষেপে): 1. interpreter loop-overhead বাদ যায়, 2. data সন্নিহিত (contiguous) memory-তে, 3. নিম্নস্তরে optimized C/Fortran routine চলে।

ফল: একই গণনা প্রায়ই ১০–১০০ গুণ দ্রুত (অধ্যায়ের benchmark-এ ~৩৪ গুণ)। তাই বড় data-তে loop যথাসম্ভব এড়িয়ে array operation ব্যবহার করা উচিত।

সমাধান ২ — seed না দিলে কী সমস্যা [medium]

seed না দিলে প্রতিবার কোড চালালে ভিন্ন এলোমেলো সংখ্যা আসে, ফলে: - গবেষণা: আপনার রিপোর্ট করা ফলাফল (গড়, p-value, plot) অন্য কেউ — এমনকি আপনি নিজেও — হুবহু পুনরুৎপাদন করতে পারবেন না; বৈজ্ঞানিক যাচাই ভেঙে পড়ে। - Debugging: কোনো বাগ যদি কেবল নির্দিষ্ট random মানে দেখা দেয়, seed ছাড়া তা প্রতিবার ফিরিয়ে আনা যায় না — সমস্যা খুঁজে বের করা কঠিন। - তুলনা: দুটি method একই random data-তে তুলনা করতে হলে উভয়কে একই seed দিতে হবে; নাহলে পার্থক্য আসলটা method-জনিত নাকি randomness-জনিত তা বলা যায় না।

সমাধান (default_rng(0)): pseudo-random generator একটি নির্ধারক algorithm; নির্দিষ্ট seed (এখানে 0) মানে সবসময় একই শুরুর অবস্থা → একই সংখ্যা-ক্রম। ফলে ফলাফল পুনরুৎপাদনযোগ্য, যাচাইযোগ্য ও debuggable হয়।


Computational

সমাধান ৩ — z-score রূপান্তর [easy]

import numpy as np

x = np.array([10, 12, 14, 16, 18])
z = (x - x.mean()) / x.std()        # vectorized: পুরো array-তে একসাথে

print("z =", z.round(4))
print("z.mean() =", round(float(z.mean()), 10))
print("z.std()  =", round(float(z.std()), 6))

Output:

z = [-1.4142 -0.7071  0.      0.7071  1.4142]
z.mean() = 0.0
z.std()  = 1.0

ব্যাখ্যা: \(\bar{x}=14\), \(\sigma\approx 2.83\)। প্রতিটি মান থেকে গড় বিয়োগ ও \(\sigma\) দিয়ে ভাগ — সবই broadcasting/vectorization-এ এক লাইনে। গঠনগতভাবে z-score-এর গড় সর্বদা \(0\) ও std সর্বদা \(1\) হয়, যা output নিশ্চিত করে।

সমাধান ৪ — column-centered matrix [medium]

import numpy as np

rng = np.random.default_rng(99)          # reproducibility
M = rng.integers(1, 10, size=(4, 3))
print("M =\n", M)
print("column means =", M.mean(axis=0))

col_centered = M - M.mean(axis=0)        # (4,3) - (3,) → broadcasting
print("new column means (≈0) =", col_centered.mean(axis=0).round(10))

Output:

M =
 [[9 5 7]
 [6 2 5]
 [9 9 9]
 [6 3 6]]
column means = [7.5  4.75 6.75]
new column means (≈0) = [0. 0. 0.]

ব্যাখ্যা: M.mean(axis=0)-এর shape (3,) (প্রতি কলামের গড়)। M - M.mean(axis=0)-এ broadcasting এই (3,) array-কে প্রতিটি সারির সাথে মিলিয়ে নেয়, ফলে প্রতিটি কলাম থেকে তার নিজস্ব গড় বিয়োগ হয়। তাই নতুন কলাম-গড় ঠিক \(0\)


Debugging / coding

সমাধান ৫ — row-centering error ঠিক করা [medium]

সমস্যা: M-এর shape (4, 3), কিন্তু M.mean(axis=1)-এর shape (4,)। broadcasting ডান দিক থেকে shape মেলায়: (4, 3) বনাম (4,) → শেষ মাত্রায় 3 বনাম 4 — মেলে না, তাই ValueError। (লক্ষণীয়: axis=0-এর ক্ষেত্রে (3,) ঠিকই মিলত, কিন্তু axis=1-এ নয়।)

ঠিক করা (keepdims=True): গড় নেওয়ার সময় মাত্রা ধরে রাখলে shape হয় (4, 1), যা (4, 3)-এর সাথে broadcasting-এ মেলে।

import numpy as np

rng = np.random.default_rng(99)
M = rng.integers(1, 10, size=(4, 3))

row_centered = M - M.mean(axis=1, keepdims=True)   # (4,3) - (4,1) ✓
print("row_centered =\n", row_centered)
print("new row means (≈0) =", row_centered.mean(axis=1).round(10))

Output:

row_centered =
 [[ 2.         -2.          0.        ]
 [ 1.66666667 -2.33333333  0.66666667]
 [ 0.          0.          0.        ]
 [ 1.         -2.          1.        ]]
new row means (≈0) = [0. 0. 0. 0.]

বিকল্প fix: M.mean(axis=1).reshape(-1, 1) বা M.mean(axis=1)[:, None] — তিনটিই shape (4,1) দেয়।


Mini-project

সমাধান ৬ — কৃত্রিম exam dataset workflow [hard]

import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

rng = np.random.default_rng(2025)        # reproducibility
n = 150

# --- build ---
df = pd.DataFrame({
    "section": rng.choice(["A", "B", "C"], size=n),
    "hours":   rng.uniform(1, 10, size=n).round(1),
})
section_bonus = df["section"].map({"A": 0, "B": 5, "C": -3})
df["score"] = (40 + 4 * df["hours"] + section_bonus
               + rng.normal(0, 6, n)).clip(0, 100).round(1)

print("shape:", df.shape)
print(df.head())

# --- (ক) per-section summary ---
print("\nper-section summary:")
print(df.groupby("section")["score"].agg(["mean", "std", "count"]).round(2))

# --- (খ) correlation ---
print("\ncorr(hours, score) =", round(df["hours"].corr(df["score"]), 3))

# --- (গ) scatter plot ---
fig, ax = plt.subplots(figsize=(6.4, 4.2))
ax.scatter(df["hours"], df["score"], s=20, alpha=0.6, color="#4C72B0")
ax.set_xlabel("Study hours"); ax.set_ylabel("Score")
ax.set_title("Score vs study hours (simulated)")
fig.tight_layout()
fig.savefig("0-6-solution-scatter.png", dpi=150)   # local; assets নয়

Output:

shape: (150, 3)
  section  hours  score
0       B    4.7   72.9
1       C    9.9   73.0
2       C    8.7   68.9
3       B    2.4   49.5
4       C    7.7   66.5

per-section summary:
          mean    std  count
section
A        62.19  12.63     47
B        66.25  11.05     53
C        58.69  12.20     50

corr(hours, score) = 0.849

ব্যাখ্যা: - build: score = 40 + 4·hours + (section bonus) + noise, তারপর .clip(0,100) দিয়ে বাস্তবসম্মত সীমায় রাখা। section B-তে +৫, C-তে −৩ — তাই গড়ে B > A > C। - (ক) groupby summary section-ভিত্তিক গড় ঠিক সেই pattern দেখায় (B≈66, A≈62, C≈59)। - (খ) correlation ≈ 0.85 — দৃঢ় ধনাত্মক, কারণ score সরাসরি hours-এর উপর নির্ভরশীল (এর সাথে noise ও section effect)। - (গ) scatter-এ বিন্দুগুলো উপরের দিকে ঢালু মেঘের মতো — hours বাড়লে score বাড়ে।

নোট: এই workflow-ই (build → summarize → visualize) Part I থেকে পরবর্তী প্রতিটি অধ্যায়ের code lab-এর মূল কাঠামো। seed নির্দিষ্ট থাকায় উপরের সংখ্যাগুলো হুবহু পুনরুৎপাদনযোগ্য।