পাঠ ১০ · ২৫-এর মধ্যে · মডিউল ২
Home / AI Courses / AI-এর জন্য Python / Indexing & Slicing

NumPy indexing ও slicing

NumPy indexing & slicing
৬ মিনিট পড়া শুরু · Beginner ব্রাউজারে কোড চালান

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

  • 1D ও 2D array-এ basic indexing — positive ও negative
  • Slicing syntax — start:stop:step, default value, reverse
  • 2D slice — row, column, sub-matrix বের করা
  • Boolean indexing ও mask combination (& |)
  • Fancy indexing — list/array দিয়ে select
  • View vs copy — gotcha ও সমাধান
  • np.where, np.argmax, np.argsort — index-returning function
  • AI-তে indexing — label filter, top-k prediction

১ · Basic indexing — 1D array

একটি NumPy array থেকে একটি element নেওয়া — list-এর মতোই। arr[i] দিয়ে $i$-তম element পাওয়া যায়। Negative index সবসময় শেষ থেকে গণনা — arr[-1] মানে শেষ element।

Python · NumPy
import numpy as np

# 1D array
arr = np.array([10, 20, 30, 40, 50])

print(arr[0])        # 10  — প্রথম
print(arr[2])        # 30  — তৃতীয়
print(arr[-1])       # 50  — শেষ
print(arr[-2])       # 40  — শেষ থেকে দ্বিতীয়

# Out of range — IndexError
# print(arr[10])     # IndexError

# Assignment
arr[0] = 99
print(arr)           # [99 20 30 40 50]

    
Index ০ থেকে শুরু — Python convention। Negative index list-এর মতোই — arr[-1] = শেষ। Single element-এ assignment direct করা যায়।
ভাবুন একটি দোকানের তাকে সাজানো ৫টি বোতল। বাঁ থেকে গুনলে arr[0] প্রথম, arr[1] দ্বিতীয়। ডান থেকে গুনলে arr[-1] ডানতম। NumPy-তেও ঠিক তাই — শুধু কম্পিউটার শূন্য থেকে গোনা শুরু করে।

২ · Slicing — 1D array-এর অংশ

Slice syntax: arr[start:stop:step]। start include, stop exclude। Default — start=0, stop=len, step=1।

Python · NumPy
import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60, 70, 80])

# Basic slice
print(arr[2:5])       # [30 40 50]   — index 2,3,4
print(arr[:3])        # [10 20 30]   — start থেকে
print(arr[5:])        # [60 70 80]   — শেষ পর্যন্ত
print(arr[:])         # পুরো array (view)

# Step
print(arr[::2])       # [10 30 50 70]  — প্রতি ২য়
print(arr[1::2])      # [20 40 60 80]  — index 1 থেকে প্রতি ২য়

# Reverse
print(arr[::-1])      # [80 70 60 50 40 30 20 10]
print(arr[5:1:-1])    # [60 50 40 30] — back-to-front

# Slice-এ assignment — broadcast
arr[2:5] = 0
print(arr)            # [10 20  0  0  0 60 70 80]

    
arr[start:stop] — half-open interval। arr[::-1] idiomatic reverse। Slice-এ assignment করলে — সব position-এ একই value broadcast হয়।
Slice-এর তিন অংশ

১) start — শুরু (default ০)।
২) stop — শেষ (exclude, default len)।
৩) step — ধাপ (default ১, negative = reverse)।

৩ · 2D array indexing — row ও column

2D array — যেমন একটি গাণিতিক matrix। দু'টি index লাগে: row ও column। NumPy-তে preferred syntax — arr[i, j] (comma দিয়ে), যা arr[i][j]-এর চেয়ে দ্রুত (একবারই memory access)।

Python · NumPy
import numpy as np

# 3x4 matrix
mat = np.array([
    [1,  2,  3,  4],
    [5,  6,  7,  8],
    [9, 10, 11, 12],
])

print(mat.shape)        # (3, 4)

# Single element
print(mat[0, 0])        # 1   — top-left
print(mat[2, 3])        # 12  — bottom-right
print(mat[1, -1])       # 8   — দ্বিতীয় row-এর শেষ

# Whole row
print(mat[0])           # [1 2 3 4]
print(mat[1, :])        # [5 6 7 8] — equivalent

# Whole column
print(mat[:, 0])        # [1 5 9]   — প্রথম column
print(mat[:, -1])       # [4 8 12]  — শেষ column

# arr[i][j] vs arr[i, j]
print(mat[1][2])        # 7  — কাজ করে কিন্তু slow
print(mat[1, 2])        # 7  — preferred

    
mat[i, j] = row $i$, column $j$। mat[i, :] = পুরো row $i$। mat[:, j] = পুরো column $j$। : মানে "সব" (এই dimension-এ)।
একটি ক্লাসরুমের বসার বিন্যাস ভাবুন। mat[2, 3] = ৩য় row, ৪র্থ column-এ বসা ছাত্র। mat[2] = ৩য় row-এর সব ছাত্র। mat[:, 3] = সব row-এর ৪র্থ column-এর ছাত্ররা — অর্থাৎ ৪র্থ column-এ বসা সবাই।

৪ · 2D slicing — sub-matrix

2D slice দু'টি dimension-এই কাজ করে। arr[r1:r2, c1:c2] দিয়ে rectangular sub-matrix পাওয়া যায়।

Python · NumPy
import numpy as np

mat = np.array([
    [ 1,  2,  3,  4],
    [ 5,  6,  7,  8],
    [ 9, 10, 11, 12],
    [13, 14, 15, 16],
])

# Sub-matrix
print(mat[1:3, 0:2])
# [[ 5  6]
#  [ 9 10]]

# All rows, first 2 columns
print(mat[:, :2])
# [[ 1  2]
#  [ 5  6]
#  [ 9 10]
#  [13 14]]

# Last row, all columns
print(mat[-1, :])    # [13 14 15 16]

# Reverse rows (flip vertically)
print(mat[::-1])
# [[13 14 15 16]
#  [ 9 10 11 12]
#  [ 5  6  7  8]
#  [ 1  2  3  4]]

# Every other row & column
print(mat[::2, ::2])
# [[ 1  3]
#  [ 9 11]]

# 2D slice-এ assignment
mat[0:2, 0:2] = 0
print(mat)
# [[ 0  0  3  4]
#  [ 0  0  7  8]
#  [ 9 10 11 12]
#  [13 14 15 16]]

    
Comma-র দু'পাশে independent slice। mat[1:3, 0:2] মানে — row 1-2, column 0-1। ::2 দিয়ে downsample (প্রতি ২য়) — image processing-এ খুব কাজে আসে।

৫ · Boolean indexing — শর্তভিত্তিক select

একটি comparison operator NumPy array-তে apply করলে — boolean array পাওয়া যায়। সেই maskMask (Boolean array)একটি boolean array — যেখানে True/False দিয়ে প্রতিটি position চিহ্নিত। NumPy-তে এই array দিয়ে indexing করলে — শুধু True position-গুলোর element ফেরে। SQL-এর WHERE clause-এর মতো। দিয়ে original array index করলে — শুধু শর্ত মানা element ফেরে।

Python · NumPy
import numpy as np

arr = np.array([3, 7, 1, 9, 4, 6, 2, 8])

# Comparison → boolean array
mask = arr > 5
print(mask)        # [False True False True False True False True]

# Boolean indexing
print(arr[mask])           # [7 9 6 8]
print(arr[arr > 5])        # একই — inline syntax

# Combining masks — & (and), | (or), ~ (not)
print(arr[(arr > 3) & (arr < 8)])    # [7 4 6]
print(arr[(arr < 3) | (arr > 7)])    # [1 9 2 8]
print(arr[~(arr > 5)])               # [3 1 4 2]

# np.where — index ফেরায়
indices = np.where(arr > 5)
print(indices)             # (array([1, 3, 5, 7]),)
print(arr[indices])        # [7 9 6 8]

# Conditional assignment
arr[arr > 5] = 0
print(arr)                 # [3 0 1 0 4 0 2 0]

# 2D boolean mask
mat = np.array([[1, 2, 3], [4, 5, 6]])
print(mat[mat > 3])        # [4 5 6] — flattened

    
Boolean indexing = SQL-এর WHERE clause-এর সমতুল্য। সাবধান: and/or keyword কাজ করে না — bitwise &/| ব্যবহার করুন এবং প্রতিটি condition বন্ধনীতে রাখুন।
arr[arr > 3 & arr < 8] ভুল — operator precedence-এ & আগে চলে। সঠিক: arr[(arr > 3) & (arr < 8)] — বন্ধনী mandatory।

৬ · Fancy indexing — list দিয়ে select

Fancy indexingFancy Indexingএকটি integer array/list দিয়ে multiple position একসাথে select করার কৌশল। Result সবসময় copy (view নয়)। Order arbitrary — আপনি যে order দেবেন সেই order-এই ফেরে। — index হিসেবে list বা array পাস করা। Result সবসময় copy (slice-এর মতো view নয়)।

Python · NumPy
import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60, 70])

# Multiple indices একসাথে
print(arr[[0, 2, 4]])         # [10 30 50]
print(arr[[6, 0, 3]])         # [70 10 40]  — যেকোনো order
print(arr[[0, 0, 1, 1]])      # [10 10 20 20] — repeat OK

# 2D fancy indexing
mat = np.array([
    [ 1,  2,  3],
    [ 4,  5,  6],
    [ 7,  8,  9],
    [10, 11, 12],
])

# Specific rows
print(mat[[0, 2]])
# [[1 2 3]
#  [7 8 9]]

# Specific (row, col) pairs
rows = [0, 1, 2, 3]
cols = [0, 1, 2, 0]
print(mat[rows, cols])         # [1 5 9 10] — diagonal-ish

# Fancy + slice combine
print(mat[[0, 2], :2])
# [[1 2]
#  [7 8]]

    
Fancy indexing — non-contiguous selection-এর জন্য। ML-এ batch sampling, train/test split-এ অপরিহার্য। mat[[0, 2]] — ০ ও ২ নম্বর row নেয়।

৭ · View vs Copy — সবচেয়ে বড় gotcha

Slice একটি viewViewএকই memory-তে আলাদা "window"। Slice modify করলে original array-ও বদলায়। Memory efficient — কিন্তু unintended mutation-এর কারণ। আলাদা data চাইলে .copy()। ফেরায় — মানে original array-এর সাথে memory share করে। View modify করলে — original-ও বদলায়। এটি memory efficient, কিন্তু bug-এর সবচেয়ে বড় কারণ।

Python · NumPy
import numpy as np

# Slice = view (memory shared)
original = np.array([1, 2, 3, 4, 5])
view = original[1:4]
view[0] = 99

print(view)         # [99  3  4]
print(original)     # [ 1 99  3  4  5]   ← original বদলেছে!

# is shared?
print(view.base is original)   # True

# Copy — আলাদা memory
original = np.array([1, 2, 3, 4, 5])
copy = original[1:4].copy()
copy[0] = 99

print(copy)         # [99  3  4]
print(original)     # [1 2 3 4 5]   ← অপরিবর্তিত

# Boolean ও fancy indexing — সবসময় copy
arr = np.array([10, 20, 30, 40])
sub = arr[[0, 2]]      # fancy
sub[0] = 999
print(arr)             # [10 20 30 40] — অপরিবর্তিত

mask_sub = arr[arr > 15]   # boolean
mask_sub[0] = 999
print(arr)                 # [10 20 30 40] — অপরিবর্তিত

    
Common bug: def normalize(x): x[:] -= x.mean(); return x — caller-এর array modify করে! সমাধান: function-এর শুরুতে x = x.copy()।
মনে রাখবেন: Slice → view (shared memory)। Boolean/fancy → copy (আলাদা memory)। Doubt হলে — arr.flags.owndata চেক করুন বা .copy() call করুন।

৮ · Index-returning functions

কখনো value নয়, position দরকার — তখন np.wherenp.whereদু'ভাবে কাজ করে — একটি argument-এ True position-এর index ফেরায়; তিন argument-এ where(cond, a, b) — cond True হলে a, না হলে b। NumPy-র ternary operator।, np.argmax, np.argsort ব্যবহার হয়।

Python · NumPy
import numpy as np

scores = np.array([72, 85, 91, 68, 79, 95, 88])

# argmax / argmin — index of max/min
print(np.argmax(scores))    # 5  — index of 95
print(np.argmin(scores))    # 3  — index of 68

# argsort — sorted order-এর index
order = np.argsort(scores)
print(order)                # [3 0 4 1 6 2 5]
print(scores[order])        # [68 72 79 85 88 91 95]
print(scores[order[::-1]])  # descending: [95 91 88 85 79 72 68]

# Top-3 highest
top3_idx = np.argsort(scores)[-3:][::-1]
print(top3_idx)             # [5 2 6]
print(scores[top3_idx])     # [95 91 88]

# np.where (1-arg) — index
high = np.where(scores >= 80)
print(high)                 # (array([1, 2, 5, 6]),)

# np.where (3-arg) — ternary
grade = np.where(scores >= 80, "Pass", "Fail")
print(grade)                # ['Fail' 'Pass' 'Pass' 'Fail' 'Fail' 'Pass' 'Pass']

    
argmax/argmin — classification-এ predicted class বের করে। argsort — top-k recommendation-এ। np.where(cond, a, b) — vectorized if-else।
2D Indexing — slice, column, boolean mask arr.shape = (4, 4) arr[1:3, :2] sub-matrix 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [[5, 6], [9, 10]] rows 1-2, cols 0-1 arr[:, 1] column extract 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [2, 6, 10, 14] all rows, col 1 arr[arr > 8] boolean mask 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [9,10,...,16] flattened, > 8 তিন রকম indexing — তিন ফলাফল slice → 2D sub-matrix column → 1D vector mask → flat 1D filtered image processing → ছবিও এভাবেই index হয়
2D indexing — তিন রকম pattern: rectangular slice (হলুদ), column extraction (নীল), boolean mask filter (সবুজ)। AI ডেটা manipulation-এর তিন pillar।

৯ · AI use case — label filter ও top-k prediction

Indexing AI workflow-এ সর্বত্র। দু'টি সাধারণ pattern — class-wise data filter ও model output থেকে top-k prediction।

Python · NumPy
import numpy as np

# ── Use case 1: label-wise data filter ──
# 6 sample, 3 feature
X = np.array([
    [1.2, 0.5, 3.1],   # class 0
    [0.8, 1.1, 2.9],   # class 1
    [1.5, 0.3, 3.5],   # class 0
    [0.9, 1.4, 2.7],   # class 1
    [1.7, 0.2, 3.8],   # class 0
    [1.0, 1.2, 2.8],   # class 1
])
y = np.array([0, 1, 0, 1, 0, 1])

# Class 0-এর সব sample
X_class0 = X[y == 0]
print("Class 0 samples:")
print(X_class0)

# প্রতি class-এর mean feature
mean_0 = X[y == 0].mean(axis=0)
mean_1 = X[y == 1].mean(axis=0)
print("Mean class 0:", mean_0)
print("Mean class 1:", mean_1)

# ── Use case 2: top-k prediction ──
# Model output — 5 class-এর probability
probs = np.array([0.05, 0.42, 0.18, 0.27, 0.08])
class_names = np.array(["cat", "dog", "horse", "bird", "fish"])

# Top-1 prediction
top1 = np.argmax(probs)
print(f"Top-1: {class_names[top1]} ({probs[top1]:.2f})")

# Top-3 prediction
top3 = np.argsort(probs)[-3:][::-1]
print(f"Top-3: {class_names[top3]}")
print(f"Probs: {probs[top3]}")

# ── Use case 3: image crop ──
# RGB image — 100x100x3
img = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)

# Center crop — 50x50
crop = img[25:75, 25:75]
print(f"Crop shape: {crop.shape}")     # (50, 50, 3)

# Red channel only
red = img[:, :, 0]
print(f"Red channel shape: {red.shape}")  # (100, 100)

# Downsample 2x
small = img[::2, ::2]
print(f"Downsampled shape: {small.shape}")  # (50, 50, 3)

    
Indexing-ই dataset prep + post-processing-এর পুরো backbone। Label filter (boolean), top-k (argsort), image crop (slice), channel split (last-axis index), downsample (step) — সব এক পাঠেই পেলেন।
পাঁচটি indexing pattern

১) arr[i] — single element।
২) arr[a:b:c] — slice (view)।
৩) arr[mask] — boolean (copy)।
৪) arr[[i, j, k]] — fancy (copy)।
৫) arr[np.argsort(...)] — index reorder।

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

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

প্র ০১ NumPy slice কেন default-এ view ফেরায়, copy নয়? Performance ও memory perspective থেকে design rationale কী? কখন .copy() অপরিহার্য — এবং কেন এই behavior সবচেয়ে বড় bug-source?

NumPy-র view-by-default behavior — তার সবচেয়ে strong design choice এবং সবচেয়ে controversial পয়েন্ট। বুঝলে — performance + correctness দু'টোই hand-in-hand চলে।

কেন view (memory perspective)?

  • একটি ১GB array slice করলেন arr[:500_000_000] — যদি copy হয় তবে আরেকটি 500MB allocate। Memory ১.৫× explosion।
  • Real ML pipeline-এ — হাজার হাজার slice operation chain হয়। প্রতি operation copy → memory blow-up।
  • View-এ — শুধু metadata (offset, stride, shape) বদলায়। data buffer অপরিবর্তিত। O(১) memory cost।

কেন view (performance perspective)?

  • Allocation cost — malloc/free system call expensive।
  • Cache locality — view শুধু stride update; data already in CPU cache।
  • Copy = O(n) memcpy। Hot loop-এ unacceptable।
  • NumPy-র C internal — view creation প্রায় free।

View internals — strides:

  • প্রতিটি ndarray-এর .strides tuple — প্রতি dimension-এ পরবর্তী element-এ যেতে কত byte।
  • Slice arr[::2] — শুধু stride দ্বিগুণ। data untouched।
  • Transpose arr.T — strides reverse। data untouched।
  • Reshape contiguous-এ — view, না হলে copy।

কখন .copy() অপরিহার্য:

  • Function input mutation এড়াতে: def normalize(x): x = x.copy(); x -= x.mean(); return x — caller-এর data নিরাপদ।
  • Slice independent করা: training validation split — পরে modification একে অপরের সাথে interfere না করে।
  • Long-lived sub-array: ১GB array থেকে ১MB slice ধরে রাখলে — পুরো ১GB GC হয় না (view base reference রাখে)। .copy() করলে original free।
  • Multi-threading: shared mutable state এড়াতে।

সবচেয়ে common bug — silent mutation:

def shuffle_first_half(arr):
    half = arr[:len(arr)//2]
    np.random.shuffle(half)    # original-ও shuffled!
    return half

original = np.array([1, 2, 3, 4, 5, 6])
shuffle_first_half(original)
# original এখন corrupt — caller-এর knowledge ছাড়াই

Diagnostic tools:

  • arr.base — যদি view হয়, parent ফেরায়; copy হলে None।
  • arr.flags.owndata — own data নাকি borrow।
  • np.shares_memory(a, b) — explicit check।

Rule of thumb: Library-quality code লিখলে — input array কখনো mutate করবেন না। Defensive copy at boundary, view inside। এই discipline-ই PyTorch/NumPy-এর success-এর মূল।

মূল উপলব্ধি: View-by-default = performance-first design। Cost: programmer-কে ownership track করতে হয়। ROI-তে worth it — কারণ AI scale-এ ১.৫× memory blow-up unacceptable। এই trade-off accept করাই NumPy-mastery-র প্রবেশদ্বার।

প্র ০২ Boolean indexing internally কীভাবে কাজ করে? arr > 5 থেকে arr[arr > 5] পর্যন্ত — vectorized comparison, mask array, gather operation — এই তিন stage-এ Python-এর জন্য কী trade-off?

Boolean indexing দেখতে magic, ভেতরে তিনটি well-defined stage। প্রতিটি stage NumPy-র C-extension-এ optimized — তাই Python loop-এর চেয়ে ১০-১০০× দ্রুত।

Stage 1: Vectorized comparison

  • arr > 5 — প্রতিটি element-এ comparison।
  • Python loop নয় — NumPy-র C-level ufunc (universal function)।
  • SIMD instruction (AVX, SSE) — একসাথে ৪-৮ element compare।
  • Output: same shape boolean array (dtype bool, ১ byte/element)।
  • একটি ১M-element float64 array → 8MB; mask → 1MB। ৮× smaller।

Stage 2: Mask array — কী store করে?

  • Position-wise True/False — original array-এর সমান shape।
  • Memory: ১ byte/element (NumPy bool, না bit-packed)।
  • np.packbits() দিয়ে bit-packed করা যায় — তবে indexing-এ unpack লাগে।
  • Mask intermediate object — combine করতে free ((a>5)&(b<3) — temporary mask)।

Stage 3: Gather operation

  • arr[mask] — দু'pass operation।
  • Pass 1: True count → output size determine।
  • Pass 2: True position থেকে value copy → contiguous output।
  • Output সবসময় 1D (flattened) — কারণ True positions arbitrary।
  • Output সবসময় copy — view সম্ভব না (positions non-contiguous)।

Trade-offs (Python vs C):

  • Python loop: [x for x in arr if x > 5] — readable কিন্তু slow। প্রতি element-এ Python object creation, comparison, list append।
  • NumPy boolean: ১০-১০০× দ্রুত। কিন্তু intermediate mask memory খায়।
  • Numba/Cython: No intermediate mask, fused loop। সবচেয়ে দ্রুত যদি condition complex।

Performance benchmark (১M elements):

  • Python list comprehension: ~১০০ms।
  • arr[arr > 5]: ~২ms (50× দ্রুত)।
  • np.compress(arr > 5, arr): same as above, alternative API।
  • Numba JIT: ~০.৫ms (200× Python)।

Memory cost:

  • Original: 8MB (float64 × 1M)।
  • Mask: 1MB (bool × 1M)।
  • Output: variable (depends on True count)।
  • Peak: 8MB + 1MB + output। প্রায় ২× original।

Combine masks — operator precedence trap:

# ভুল!
arr[arr > 3 & arr < 8]    # & আগে চলে — TypeError

# সঠিক
arr[(arr > 3) & (arr < 8)]

# কেন? — Python operator precedence
# & is bitwise, higher precedence than >
# & evaluates first → "3 & arr" — শুরুতেই type mismatch

Vectorized vs imperative — design philosophy:

  • NumPy = "what you want", not "how to do it"।
  • "arr[arr > 5]" intent-revealing — SQL WHERE-এর মতো।
  • C internals JIT compile/SIMD optimize করতে পারে।
  • Pandas, PyTorch, JAX — সবই এই philosophy carry করেছে।

মূল উপলব্ধি: Boolean indexing ৩-stage pipeline: compare → mask → gather। প্রতিটি C-optimized। Memory cost intermediate mask। Performance ১০-১০০× Python loop। Vectorization NumPy-র "ম্যাজিক" নয় — well-engineered SIMD + cache-friendly layout-এর ফল।

প্র ০৩ Fancy indexing বনাম boolean masking — দু'টোই subset select করে। কোনটা কখন বাছবেন? Performance, readability, ও memory trade-off-এর light-এ practical guideline কী?

দু'টি technique প্রায় interchangeable মনে হলেও — semantics, performance, ও use case ভিন্ন। ভুল choice ১০× slowdown বা bug দিতে পারে।

Semantic difference:

  • Boolean mask: "প্রতিটি position-এ যাব কি যাব না।" শর্তভিত্তিক — order automatic (original order preserved)।
  • Fancy indexing: "এই specific position-গুলো — এই order-এ।" Position list-এর order respected, repetition allowed।

কখন boolean mask:

  • Condition-based filter: arr[arr > threshold]।
  • Multi-condition: arr[(arr > a) & (arr < b)]।
  • Element-wise condition থেকে subset।
  • Order matter না — কোন position match জানা নেই।
  • Pandas-এ df[df.age > 18] — same idea।

কখন fancy indexing:

  • Specific known indices: arr[[0, 5, 10]]।
  • Random sampling: arr[np.random.choice(n, k)]।
  • Train/test split: shuffled indices।
  • Reorder/permute: arr[np.argsort(scores)]।
  • Repetition needed: arr[[0, 0, 1]] — boolean পারে না।
  • Custom order: arr[[3, 1, 2]] — boolean original order ফেরায়।

Performance comparison:

  • Boolean — small selection (1%): overhead high (full mask traverse)।
  • Boolean — large selection (50%): efficient (single pass)।
  • Fancy — small selection: very efficient (only k lookups)।
  • Fancy — large selection: slower than boolean (random access pattern)।
  • Rule: < ১০% select → fancy; > ১০% select → boolean।

Memory comparison:

  • Boolean mask: O(n) memory always (full-size mask)।
  • Fancy indices: O(k) memory (k = selected count)।
  • Tiny selection from huge array → fancy clearly winner।

Cache behavior:

  • Boolean: sequential scan — cache-friendly।
  • Fancy: random access — cache miss potential বেশি।
  • Sorted indices fancy-এও sequential — দ্রুত।

Readability:

# Intent: "৬৫+ score-এর student"
# Boolean — clear
high_scorers = students[scores > 65]

# Fancy — extra step, less clear
high_idx = np.where(scores > 65)[0]
high_scorers = students[high_idx]

# Intent: "specific 3 students by ID"
# Fancy — natural
selected = students[[101, 205, 309]]

# Boolean — awkward
selected = students[np.isin(ids, [101, 205, 309])]

2D context:

  • Boolean 2D: result flattened (loses shape)।
  • Fancy 2D: shape preserved more cleanly।
  • Row select: mat[[0, 2]] (fancy) vs mat[mask] (flat)।

Combining — best of both:

# Step 1: boolean → indices
indices = np.where(scores > 65)[0]

# Step 2: random sample
sample = np.random.choice(indices, size=10, replace=False)

# Step 3: fancy index
selected = students[sample]

Mutable assignment — both work, with differences:

  • Boolean: arr[arr<0] = 0 — clip negative to 0।
  • Fancy: arr[[1,3,5]] = 99 — specific positions।
  • Fancy duplicate: arr[[0,0,0]] = [1,2,3] — undefined order, last wins (typically 3)।

মূল উপলব্ধি: Boolean = "condition-based, full scan, large subset"। Fancy = "specific positions, small subset, custom order"। Right tool choosing comes with practice — শুরুতে readability prefer করুন, hot path-এ profile করে optimize।

প্র ০৪ Image processing-এ slicing-এর বাস্তব ব্যবহার — crop, channel split, downsample, padding, augmentation। Computer vision pipeline-এ slicing কেন এত central, এবং কোন pitfall এড়াতে হয়?

Computer vision = NumPy slicing-এর masterclass। প্রতিটি image transform — slicing-এর creative ব্যবহার। PIL/OpenCV-র অনেক operation আসলে এই pattern-গুলোর wrapper।

Image = 3D ndarray:

  • Grayscale: (H, W) — height × width।
  • RGB: (H, W, 3) — শেষ axis = R, G, B channel।
  • RGBA: (H, W, 4) — alpha channel যোগ।
  • Batch: (N, H, W, C) — N ছবি একসাথে (TF order)।
  • PyTorch: (N, C, H, W) — channel আগে।

(১) Crop — slice on H, W:

# Center crop 224x224 from larger image
h, w = img.shape[:2]
top = (h - 224) // 2
left = (w - 224) // 2
crop = img[top:top+224, left:left+224]

# Random crop — data augmentation
top = np.random.randint(0, h - 224)
left = np.random.randint(0, w - 224)
crop = img[top:top+224, left:left+224]

(২) Channel split:

r = img[:, :, 0]      # Red channel
g = img[:, :, 1]      # Green
b = img[:, :, 2]      # Blue

# Grayscale conversion
gray = 0.299*r + 0.587*g + 0.114*b

# Drop alpha
rgb = rgba[:, :, :3]

# Reorder BGR → RGB (OpenCV convention)
rgb = bgr[:, :, ::-1]    # last axis reverse

(৩) Downsample — step slicing:

# 2x downsample (nearest)
small = img[::2, ::2]

# 4x downsample
tiny = img[::4, ::4]

# Quick thumbnail — quality poor কিন্তু fast
thumb = img[::8, ::8]

# Production: cv2.resize / PIL.thumbnail
# (anti-aliasing, interpolation)

(৪) Flip — reverse slice:

flipped_h = img[:, ::-1]       # horizontal flip
flipped_v = img[::-1, :]       # vertical flip
both = img[::-1, ::-1]         # 180° rotate

(৫) Padding — assignment slicing:

# Black border 10px
h, w, c = img.shape
padded = np.zeros((h+20, w+20, c), dtype=img.dtype)
padded[10:10+h, 10:10+w] = img

# Better: np.pad — fewer error
padded = np.pad(img, ((10,10), (10,10), (0,0)))

(৬) Patch extraction (CNN, ViT input):

# Non-overlapping 16x16 patches
patches = []
for i in range(0, h, 16):
    for j in range(0, w, 16):
        patches.append(img[i:i+16, j:j+16])

# Vectorized — reshape trick
patches = img.reshape(h//16, 16, w//16, 16, 3)
patches = patches.transpose(0, 2, 1, 3, 4)
# shape: (n_h, n_w, 16, 16, 3)

(৭) Mask-based segmentation:

# Foreground mask (boolean H×W)
foreground = mask > 0.5

# Apply to image — broadcast
result = img.copy()
result[~foreground] = 0    # background black

Pitfalls:

  • View vs copy: crop = img[100:200, 100:200] — view! crop modify করলে img-ও বদলায়। Augmentation pipeline-এ disaster।
  • Bound check: img[1000:1200] — image যদি ৮০০ row, no error, just empty array। Silent bug।
  • Channel order confusion: OpenCV BGR, PIL/Matplotlib RGB। Color wrong হলে — debug nightmare।
  • HWC vs CHW: NumPy/TF HWC, PyTorch CHW। Wrong shape model crash।
  • dtype issue: uint8 (০-২৫৫) vs float (০-১)। Slice keep dtype, কিন্তু arithmetic-এ overflow।
  • Negative stride performance: img[::-1] view but reverse stride — কিছু operation slow।

Vectorized augmentation pipeline:

def random_augment(img):
    # Random crop
    h, w = img.shape[:2]
    new_h, new_w = 224, 224
    top = np.random.randint(0, h - new_h)
    left = np.random.randint(0, w - new_w)
    img = img[top:top+new_h, left:left+new_w].copy()  # ← copy দরকার!

    # Random horizontal flip
    if np.random.rand() < 0.5:
        img = img[:, ::-1].copy()

    # Random channel shuffle (rare)
    if np.random.rand() < 0.1:
        perm = np.random.permutation(3)
        img = img[:, :, perm]

    return img

Production tools:

  • OpenCV — C++ optimized, geometric ops fast।
  • PIL/Pillow — convenient, slower।
  • Albumentations — augmentation library, NumPy-based।
  • Kornia — GPU augmentation (PyTorch tensor)।
  • tf.image — TensorFlow native।

মূল উপলব্ধি: Image processing = "ndarray gymnastics"। Slicing প্রায় সব transform-এর foundation। View/copy gotcha augmentation pipeline-এ disaster — সবসময় .copy() at boundary। Vectorized slicing GPU-এর age-এও relevant — কারণ PyTorch/JAX-এর tensor indexing identical syntax follow করে। NumPy ভাল বুঝলে — সব deep learning framework-এর data manipulation আপনার কাছে natural।

অনুশীলন

  1. Slice practice: একটি 5×5 matrix বানান (১ থেকে ২৫)। (ক) মাঝের 3×3 sub-matrix বের করুন। (খ) দ্বিতীয় column বের করুন। (গ) প্রতি ২য় row নিয়ে নতুন matrix বানান।
    import numpy as np
    mat = np.arange(1, 26).reshape(5, 5)
    print(mat)
    # [[ 1  2  3  4  5]
    #  [ 6  7  8  9 10]
    #  [11 12 13 14 15]
    #  [16 17 18 19 20]
    #  [21 22 23 24 25]]
    
    # (ক) মাঝের 3x3
    print(mat[1:4, 1:4])
    # [[ 7  8  9]
    #  [12 13 14]
    #  [17 18 19]]
    
    # (খ) দ্বিতীয় column (index 1)
    print(mat[:, 1])     # [ 2  7 12 17 22]
    
    # (গ) প্রতি ২য় row
    print(mat[::2])
    # [[ 1  2  3  4  5]
    #  [11 12 13 14 15]
    #  [21 22 23 24 25]]
  2. Boolean & fancy: arr = np.array([3, 7, 1, 9, 4, 6, 2, 8, 5])। (ক) ৪-৭ range-এ যেগুলো — boolean দিয়ে। (খ) সবচেয়ে বড় ৩টি element-এর index — argsort দিয়ে। (গ) ০, ৩, ৬ index-এর element — fancy দিয়ে।
    import numpy as np
    arr = np.array([3, 7, 1, 9, 4, 6, 2, 8, 5])
    
    # (ক) ৪-৭ range
    print(arr[(arr >= 4) & (arr <= 7)])
    # [7 4 6 5]
    
    # (খ) top-3 index
    top3 = np.argsort(arr)[-3:][::-1]
    print(top3, arr[top3])
    # [3 7 1] [9 8 7]
    
    # (গ) fancy
    print(arr[[0, 3, 6]])
    # [3 9 2]
  3. View vs copy bug: নিচের code-এ কী bug? কীভাবে fix করবেন?
    def zero_first_half(a):
        half = a[:len(a)//2]
        half[:] = 0
        return half

    Bug: half = a[:len(a)//2] view ফেরায় — তাই half[:] = 0 লিখলে original array a-ও modify হয়। Caller-এর data corrupt!

    # প্রমাণ
    import numpy as np
    arr = np.array([1, 2, 3, 4, 5, 6])
    result = zero_first_half(arr)
    print(arr)        # [0 0 0 4 5 6]   ← original বদলে গেছে!
    
    # Fix 1 — copy at start
    def zero_first_half_fixed(a):
        half = a[:len(a)//2].copy()    # ← copy
        half[:] = 0
        return half
    
    # Fix 2 — explicit intent (caller আগে copy দেয়)
    def zero_first_half_v2(a):
        """Note: returns view; modifies input."""
        a = a.copy()
        a[:len(a)//2] = 0
        return a
    
    # Lesson: function-এর শুরুতে input mutate করা avoid।
    # Library-quality code-এ defensive copy বা document mutation।

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন — Google-এর ফ্রি অনলাইন Python পরিবেশ, শুধু Gmail অ্যাকাউন্ট লাগে।
পূর্ববর্তী পাঠ
পাঠ ০৯ · NumPy পরিচিতি — ndarray