0% found this document useful (0 votes)
3 views45 pages

NumPy Pandas StudyGuide

The document is a comprehensive advanced study guide covering NumPy and Pandas, focusing on topics such as arrays, vectorization, data manipulation, and data cleaning. It includes sections on memory efficiency, applications, and various methods for creating and handling arrays. The guide is structured with questions categorized by difficulty level to facilitate learning and practice.

Uploaded by

Yaswanth Sai
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views45 pages

NumPy Pandas StudyGuide

The document is a comprehensive advanced study guide covering NumPy and Pandas, focusing on topics such as arrays, vectorization, data manipulation, and data cleaning. It includes sections on memory efficiency, applications, and various methods for creating and handling arrays. The guide is structured with questions categorized by difficulty level to facilitate learning and practice.

Uploaded by

Yaswanth Sai
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

NumPy & Pandas

Comprehensive Advanced Study Guide

Topics Covered: NumPy Arrays · Vectorization · Broadcasting · Indexing

Slicing · Universal Functions · Views vs Copies · Missing Values

Pandas Series · DataFrames · loc/iloc · Data Cleaning

Merging · Grouping · Lambda · Date Formatting

Difficulty Levels: Basic · Medium · Advanced Questions per Concept

Purpose: Syntax Reference + Conceptual Depth + Practice Questions


NumPy & Pandas — Advanced Study Guide

■ Table of Contents
PART 1 — NumPy
1.1 Python Array vs NumPy Array
1.2 Why NumPy is Memory Efficient
1.3 Applications and Uses
1.4 Creating NumPy Arrays (all types)
1.5 Vectorization & Broadcasting Intro
1.6 Data Types & Type Casting
1.7 Shapes & Reshaping
1.8 Arithmetic & Data-Type-Changing Operations
1.9 Conditional Operations
1.10 Universal Functions (ufuncs)
1.11 Indexing & Slicing
1.12 Multidimensional Slicing
1.13 Splitting Arrays
1.14 Broadcasting (deep dive)
1.15 Concatenation & Stacking
1.16 Aggregation
1.17 Masking & Boolean Indexing
1.18 Matrix Transpose
1.19 View vs Copy
1.20 Handling Missing Values

PART 2 — Pandas
2.1 Why Pandas?
2.2 Series — Creation, Indexing, Slicing, Modification, Sorting
2.3 Conditional Indexing in Series
2.4 DataFrames — Creation & Structure
2.5 loc & iloc
2.6 Broadcasting in DataFrames
2.7 Lambda Functions
2.8 Renaming Columns & Index
2.9 Dropping Rows/Columns
2.10 Importing Files (CSV, Excel, JSON)
2.11 Cleaning Data
2.12 Handling Duplicates
2.13 Date Reading & Formatting
2.14 Advanced Broadcasting in Pandas

Page 2
NumPy & Pandas — Advanced Study Guide

PART 1 — NumPy

1.1 Python List vs NumPy Array

Concept & Logic: A Python list is a collection of arbitrary Python objects, each carrying its own type metadata and
reference pointer (~28 bytes overhead per element). A NumPy array stores homogeneous raw binary values in a
contiguous block of memory, making element access O(1) with near-zero overhead.

Key Differences
Feature Python List NumPy Array

Memory Non-contiguous (pointers) Contiguous block (raw bytes)

Type Heterogeneous Homogeneous (single dtype)

Speed Slower (Python overhead) Fast (C-level operations)

Functionality Basic CRUD Math, linear algebra, FFT, …

Broadcasting Not supported Built-in

Size in memory ~56 bytes + 8 per int ~96 bytes + 8 per int (fixed)

Syntax & Code Example

import numpy as np

# Python list
py_list = [1, 2, 3, 4, 5]

# NumPy array from list


np_arr = [Link]([1, 2, 3, 4, 5])

# Check type
print(type(py_list)) # <class 'list'>
print(type(np_arr)) # <class '[Link]'>

# Memory comparison
import sys
print([Link](py_list)) # ~104 bytes for 5-element list
print(np_arr.nbytes) # 40 bytes (5 x 8-byte int64)

# Speed: element-wise multiplication


py_result = [x * 2 for x in py_list] # Python loop needed
np_result = np_arr * 2 # Vectorized — no loop!
BASIC LEVEL QUESTIONS

Q1. Create a Python list [10,20,30] and a NumPy array from it. Print the type and memory size of each.
Q2. What does [Link]([1,2,'three']) produce? Why? What dtype does it get?

MEDIUM LEVEL QUESTIONS

Q1. Write a function that takes a Python list of floats, converts it to a NumPy array, and returns the ratio of Python list
size to NumPy array byte size.

Page 3
NumPy & Pandas — Advanced Study Guide

Q2. Explain why np_arr[0] is faster than py_list[0] at the hardware level.

ADVANCED LEVEL QUESTIONS

Q1. Benchmark element-wise squaring for sizes 10^3, 10^5, 10^7 using both a list comprehension and NumPy. Plot the
results and explain the crossover point.
Q2. Describe how Python's garbage collector interacts with NumPy's memory model. When does NumPy free memory?

Page 4
NumPy & Pandas — Advanced Study Guide

1.2 Why NumPy is Memory Efficient

Concept & Logic: NumPy achieves efficiency through: (1) Homogeneous dtype — each element is exactly
[Link] bytes, no Python object wrapper. (2) Contiguous memory — elements sit side-by-side so the CPU
cache can prefetch them. (3) Strides — a compact integer tuple tells NumPy how many bytes to jump per
dimension, enabling views (reshapes, transposes) without copying data.
import numpy as np

a = [Link]([1, 2, 3, 4, 5, 6], dtype=np.int32)

print([Link]) # int32
print([Link]) # 4 (bytes per element)
print([Link]) # 24 (6 * 4)
print([Link]) # (4,) — move 4 bytes to get next element

# 2-D array strides


b = [Link]([[1,2,3],[4,5,6]], dtype=np.float64)
print([Link]) # (24, 8) — 24 bytes per row, 8 bytes per column

# dtype affects memory drastically


big = [Link](1_000_000, dtype=np.float64) # 8 MB
small = [Link](1_000_000, dtype=np.float32) # 4 MB
print([Link], [Link]) # 8000000 4000000
BASIC LEVEL QUESTIONS

Q1. Create a 1-D array of 100 zeros with dtype int16. Print its nbytes and compare with int64.
Q2. What are strides? Print the strides of a (3,4) array of float32.

MEDIUM LEVEL QUESTIONS

Q1. Create a 2-D array and take a slice (every other column). Show that the slice shares memory with the original using
np.shares_memory().
Q2. Why does changing dtype from float64 to float16 reduce memory 4x? What precision trade-off occurs?

ADVANCED LEVEL QUESTIONS

Q1. Manually compute the memory address of element [i,j] in a (M,N) int32 array given its base address and strides.
Write a Python function that validates this.
Q2. Explain how [Link].stride_tricks.as_strided() can create a sliding-window view of a 1-D array. Implement it and
discuss the risks.

Page 5
NumPy & Pandas — Advanced Study Guide

1.3 Applications & Uses of NumPy

Concept & Logic: NumPy is the backbone of the entire Python scientific stack. It powers: machine learning
(feature matrices in sklearn/TensorFlow), image processing (images as pixel arrays), signal processing (FFT),
financial modelling (vectorised price calculations), physics simulations, and genomics.

Domain How NumPy is Used

Machine Learning Feature matrices, weight tensors, activation maps

Image Processing Pixel arrays (H×W×C), colour transforms

Signal Processing [Link](), convolutions

Finance Vectorised returns, Monte Carlo simulation

Statistics Descriptive stats, random sampling

Linear Algebra [Link] — eigenvalues, SVD, matrix solve

Data Science Underlying engine of Pandas, SciPy, Matplotlib

# --- Image as array ---


import numpy as np
img = [Link](0, 256, size=(480, 640, 3), dtype=np.uint8)
gray = [Link](axis=2) # greyscale conversion

# --- FFT example ---


t = [Link](0, 1, 500)
sig = [Link](2 * [Link] * 50 * t)
freq = [Link](sig) # frequency domain

# --- Linear algebra ---


A = [Link]([[2,1],[5,3]])
b = [Link]([4, 7])
x = [Link](A, b) # solve Ax = b
print(x) # [5. -6.]
BASIC LEVEL QUESTIONS

Q1. Use NumPy to create a (224, 224, 3) random uint8 array representing a fake RGB image.
Q2. Use [Link]() to compute the Euclidean distance between two 3-D points.

MEDIUM LEVEL QUESTIONS

Q1. Simulate rolling two dice 100,000 times using [Link] and compute the probability distribution of their sum.
Q2. Normalise pixel values of a (H,W,3) image array to the range [0,1] in one line.

ADVANCED LEVEL QUESTIONS

Q1. Implement a Monte Carlo estimation of pi using NumPy random points in a unit square. How many samples do you
need for 4 significant figures?
Q2. Solve the least-squares regression y = Xβ for a 1000×10 feature matrix X using [Link]. Discuss numerical
stability.

Page 6
NumPy & Pandas — Advanced Study Guide

1.4 Creating NumPy Arrays — All Types

Concept & Logic: NumPy provides a rich set of constructors. Knowing which one to reach for saves you from
explicit loops. Choose from: literal conversion, range-based, fill-based (zeros/ones/full), identity, random, and
linspace/logspace.
import numpy as np

# ■■ From Python sequences ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a1 = [Link]([1, 2, 3]) # 1-D int64
a2 = [Link]([[1,2],[3,4]], dtype=float) # 2-D float64
a3 = [Link]([1,2,3], dtype=np.complex128) # complex

# ■■ Range-based ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
r1 = [Link](0, 10, 2) # [0 2 4 6 8] (start, stop, step)
r2 = [Link](0, 1, 5) # [0. 0.25 0.5 0.75 1. ] (5 points incl ends)
r3 = [Link](0, 2, 3) # [ 1. 10. 100.] (log scale)

# ■■ Fill-based ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
z = [Link]((3, 4)) # 3×4 zeros float64
o = [Link]((2, 3), dtype=int) # 2×3 ones int64
f = [Link]((2, 2), 7.0) # 2×2 filled with 7.0
e = [Link]((3, 3)) # uninitialised (fast!)

# ■■ Identity / diagonal ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


I = [Link](4) # 4×4 identity
d = [Link]([1, 2, 3]) # diagonal matrix

# ■■ Random ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
rng = [Link].default_rng(42) # reproducible seed (new API)
u = [Link]((3, 3)) # uniform [0,1)
n = [Link](0, 1, (3, 3)) # standard normal
i = [Link](0, 10, (3,)) # random ints

# ■■ Special ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
flat = [Link](lambda i,j: i+j, (3,3), dtype=int) # index-based
itr = [Link]((x**2 for x in range(5)), dtype=int) # from generator
BASIC LEVEL QUESTIONS

Q1. Create: (a) 1-D array 0–9, (b) 4×4 identity, (c) 3×3 zeros float32, (d) 10 linearly spaced points from 0 to 100.
Q2. What is the difference between [Link]() and [Link]()? When would you choose empty?

MEDIUM LEVEL QUESTIONS

Q1. Create a 5×5 checkerboard (0/1) array without using any explicit loops. Hint: use slicing.
Q2. Use [Link] to build an n×n multiplication table.

ADVANCED LEVEL QUESTIONS

Q1. Create a structured array with fields 'name' (unicode, 10 chars), 'age' (int32), 'score' (float32) and populate it with 3
records. Demonstrate field-based access.
Q2. Explain the difference between [Link].default_rng(seed) and the legacy [Link](). Why is the new
Generator API preferred for reproducibility?

Page 7
NumPy & Pandas — Advanced Study Guide

1.5 Vectorization & Embedding

Concept & Logic: Vectorization means expressing batch operations as array-level expressions rather than explicit
Python loops. NumPy delegates the loop to optimised C/Fortran code (BLAS/LAPACK). Embedding refers to how
multi-dimensional arrays are stored in a flat 1-D buffer and how strides map multi-D indices to that buffer.
import numpy as np
import time

# ■■ Vectorisation vs loop ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


n = 1_000_000
arr = [Link](n, dtype=np.float64)

# Loop (slow)
t0 = time.perf_counter()
result = [x**2 for x in arr]
print(f'Loop: {time.perf_counter()-t0:.4f}s')

# Vectorised (fast)
t0 = time.perf_counter()
result = arr ** 2
print(f'NumPy: {time.perf_counter()-t0:.4f}s') # ~50-200x faster

# ■■ Avoiding loops with vectorised conditionals ■■■■■■■■■■■■■■■■■■■


grades = [Link]([45, 72, 88, 55, 91])
labels = [Link](grades >= 60, 'Pass', 'Fail') # no loop needed
print(labels)

# ■■ Embedding: flat index calculation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a = [Link](12).reshape(3, 4)
# Element a[1,2] lives at flat index: 1*4 + 2 = 6
print([Link][6]) # same as a[1,2]
print([Link]()) # returns flat view
BASIC LEVEL QUESTIONS

Q1. Replace the loop result = []; for x in arr: [Link](x*3+1) with a vectorised NumPy one-liner.
Q2. What does [Link]() return? How does it differ from [Link]()?

MEDIUM LEVEL QUESTIONS

Q1. Given two (10000,) arrays x and y, compute the cosine distance (1 - dot(x,y)/(||x|| * ||y||)) without any Python loops.
Q2. Vectorise a sigmoid function σ(x) = 1 / (1 + e^-x) for a (1000, 1000) matrix.

ADVANCED LEVEL QUESTIONS

Q1. Implement a vectorised batch matrix multiplication of two (B, M, K) and (B, K, N) arrays without [Link]. Use
[Link] and explain the notation.
Q2. Why does [Link]() sometimes return a view and sometimes a copy? Under what stride conditions can ravel return
a view?

Page 8
NumPy & Pandas — Advanced Study Guide

1.6 Data Types & Type Casting

Concept & Logic: Every NumPy array has exactly one dtype. Knowing dtypes prevents silent overflow bugs (e.g.,
int8 max is 127). Type casting (astype) creates a new array; in-place casting is not possible without reassignment.

Category NumPy dtype Range / Precision

int8 -128 … 127

int16 -32768 … 32767

Integer int32 ~-2.1B … 2.1B

int64 ~-9.2e18 … 9.2e18

uint8 0 … 255

uint16 / uint32 / uint64 0 … 2^n - 1

float16 ~±65504, 3 decimal digits

float32 ~±3.4e38, 7 decimal digits

float64 ~±1.8e308, 15 decimal digits

complex64 / complex128 Two float32 / float64 parts

Boolean bool_ True / False (1 byte each)

String U10 / S10 10 Unicode chars / 10 bytes

import numpy as np

# ■■ Inspect dtype ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a = [Link]([1, 2, 3])
print([Link]) # int64

# ■■ Specify dtype on creation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


b = [Link]([1.5, 2.5], dtype=np.float32)
c = [Link](5, dtype=np.uint8)

# ■■ Type casting (astype) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


d = [Link]([1.9, 2.7, 3.1])
d_int = [Link](np.int32) # truncates: [1 2 3]
d_str = [Link](str) # ['1.9' '2.7' '3.1']

# ■■ Overflow example — know your dtype! ■■■■■■■■■■■■■■■■■■■■■■■■■


x = [Link]([200], dtype=np.uint8)
print(x + 100) # [300] is NOT correct — wraps to 44 (uint8 overflow!)

# ■■ Safe upcast ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


x_safe = [Link](np.uint16) + 100
print(x_safe) # [300] correct

# ■■ Casting rules ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(np.result_type(np.int32, np.float64)) # float64 (upcasting)
BASIC LEVEL QUESTIONS

Q1. Create an array [1,2,3,4] and cast it to float32, then to bool. What values do you get?
Q2. What happens when you add a float64 array to an int32 array? What is the result dtype?

Page 9
NumPy & Pandas — Advanced Study Guide

MEDIUM LEVEL QUESTIONS

Q1. You have a uint8 image array (values 0–255). Normalise it to float32 in [0,1] using astype and arithmetic.
Q2. Explain safe vs unsafe casting in astype(). When does astype raise an error?

ADVANCED LEVEL QUESTIONS

Q1. Design a function that automatically up-casts two arrays to a common safe dtype before performing arithmetic,
using np.result_type and np.can_cast.
Q2. What is a structured dtype? Create one for a student record and demonstrate vectorised field access.

Page 10
NumPy & Pandas — Advanced Study Guide

1.7 Shapes & Reshaping

Concept & Logic: shape is a tuple (d0, d1, …, dn-1) describing the size along each axis. reshape() returns a view
when possible (contiguous memory) and a copy otherwise. The total number of elements ([Link](shape)) must
remain constant. Use -1 as a wildcard dimension to let NumPy infer it.
import numpy as np

a = [Link](24)
print([Link]) # (24,)

# ■■ reshape ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
b = [Link](4, 6) # 4 rows × 6 cols (view)
c = [Link](2, 3, 4) # 3-D (view)
d = [Link](6, -1) # -1 inferred as 4 → (6,4)

# ■■ Verify it is a view ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


b[0, 0] = 99
print(a[0]) # 99 — same memory!

# ■■ flatten vs ravel ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


e = [Link]() # always a copy
f = [Link]() # view if possible

# ■■ Adding / removing axes ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


v = [Link]([1,2,3]) # shape (3,)
col = v[:, [Link]] # shape (3,1) — column vector
row = v[[Link], :] # shape (1,3) — row vector

# using expand_dims
x = [Link]((3,4))
x3 = np.expand_dims(x, axis=0) # (1,3,4)
x3b = np.expand_dims(x, axis=2) # (3,4,1)

# squeeze: remove size-1 axes


y = [Link]((1,3,1,4))
print([Link]().shape) # (3,4)
BASIC LEVEL QUESTIONS

Q1. Reshape [Link](30) into (5,6), then into (2,3,5). Confirm total elements are the same.
Q2. What does [Link](-1) do? When does it return a view vs a copy?

MEDIUM LEVEL QUESTIONS

Q1. You have a (100,) array of pixel brightness. Reshape it to (10,10), then add a channel axis to make it (10,10,1) as
required by a CNN.
Q2. Explain the difference between C-order and Fortran-order in reshape. Demonstrate with a (2,3) array.

ADVANCED LEVEL QUESTIONS

Q1. Without using reshape, use [Link].stride_tricks.as_strided to re-interpret a (12,) int32 array as a (3,4) view. What are
the required strides?
Q2. Why can reshape return a copy when the array is non-contiguous (e.g. after transposing)? How can you force a
contiguous copy first?

Page 11
NumPy & Pandas — Advanced Study Guide

1.8 Arithmetic, Type-Changing & Conditional Operations

Concept & Logic: NumPy arithmetic is element-wise by default. Operations follow broadcasting rules when
shapes differ. [Link] is the vectorised ternary operator. [Link] handles multi-condition logic without loops.
import numpy as np

a = [Link]([10, 20, 30, 40])


b = [Link]([ 1, 2, 3, 4])

# ■■ Basic arithmetic ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(a + b) # [11 22 33 44]
print(a - b) # [ 9 18 27 36]
print(a * b) # [10 40 90 160]
print(a / b) # [10. 10. 10. 10.] → float
print(a // b) # [10 10 10 10] integer division
print(a % b) # [0 0 0 0] modulo
print(a ** 2) # [100 400 900 1600] power

# ■■ In-place (modifies a!) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a += 5 # a is now [15 25 35 45]

# ■■ Comparison (returns bool array) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(a > 20) # [False True True True]
print(a == 25) # [False True False False]

# ■■ [Link] (vectorised if/else) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


arr = [Link]([5, 15, 25, 35, 45])
result = [Link](arr > 20, 'High', 'Low')
print(result) # ['Low' 'Low' 'High' 'High' 'High']

# Replace negatives with 0


data = [Link]([-3, 1, -1, 4, -2, 6])
clipped = [Link](data < 0, 0, data)
print(clipped) # [0 1 0 4 0 6]

# ■■ [Link] (multi-condition) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


conditions = [arr < 10, arr < 30, arr >= 30]
choices = ['low', 'mid', 'high']
labels = [Link](conditions, choices, default='unknown')
print(labels)

# ■■ [Link] ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
scores = [Link]([30, 110, 85, -5, 100])
print([Link](scores, 0, 100)) # [30 100 85 0 100]
BASIC LEVEL QUESTIONS

Q1. Given arr = [Link]([4,9,16,25]), compute its square root and integer floor using NumPy.
Q2. Write one line using [Link] to replace all values > 50 with 50 in a random 10-element array.

MEDIUM LEVEL QUESTIONS

Q1. Implement ReLU (max(0, x)) and Leaky ReLU (x if x>0 else 0.01*x) using [Link] on a (-5,5) linspace of 100
points.
Q2. Normalise an array to zero mean and unit variance (z-score normalisation) in a single NumPy expression.

Page 12
NumPy & Pandas — Advanced Study Guide

ADVANCED LEVEL QUESTIONS

Q1. Implement a piecewise function: f(x)= x^2 if x<0, sqrt(x) if 0<=x<=1, 1 otherwise — for a 1000-element array using
[Link] or [Link].
Q2. Explain why a *= 2 is faster than a = a * 2 for a large array. Under what conditions can in-place fail?

Page 13
NumPy & Pandas — Advanced Study Guide

1.9 Universal Functions (ufuncs)

Concept & Logic: A ufunc is a vectorised wrapper around a C-level function that operates element-wise on
arrays. They support broadcasting, type promotion, and optional output buffers. All standard math ops (+, *, sin,
exp, …) are ufuncs. You can create custom ufuncs with [Link] or numba @vectorize.
import numpy as np

x = [Link](-[Link], [Link], 6)

# ■■ Trigonometric ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print([Link](x))
print([Link](x))
print([Link](x))

# ■■ Exponential / log ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]([Link]([0, 1, 2]))) # [1. 2.71828 7.38906]
print([Link]([Link]([1, np.e, np.e**2]))) # [0. 1. 2.]
print(np.log2([1, 2, 4, 8])) # [0. 1. 2. 3.]
print(np.log10([1, 10, 100])) # [0. 1. 2.]

# ■■ Rounding ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
a = [Link]([1.2, 1.5, 1.7, 2.5])
print([Link](a)) # [1. 1. 1. 2.]
print([Link](a)) # [2. 2. 2. 3.]
print([Link](a)) # [1. 2. 2. 2.] banker's rounding

# ■■ Comparison ufuncs ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]([1,5,3], [4,2,6])) # [4 5 6] element-wise max
print([Link]([1,5,3], [4,2,6])) # [1 2 3]

# ■■ Reduce / accumulate ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]([1,2,3,4])) # 10 (sum)
print([Link]([1,2,3,4])) # [1 2 6 24]

# ■■ Custom ufunc from Python function ■■■■■■■■■■■■■■■■■■■■■■■■■■■■


def my_relu(x):
return x if x > 0 else 0.0

relu_ufunc = [Link](my_relu, 1, 1)
print(relu_ufunc([Link]([-2, -1, 0, 1, 2])))
BASIC LEVEL QUESTIONS

Q1. Compute sin(x) and cos(x) for 100 evenly spaced values between 0 and 2π.
Q2. What is the difference between [Link]() and Python's built-in abs() when applied to an array?

MEDIUM LEVEL QUESTIONS

Q1. Use [Link] to create a 5×5 multiplication table.


Q2. Implement log-sum-exp (numerically stable softmax denominator) using [Link] and [Link] for a 1-D array.

ADVANCED LEVEL QUESTIONS

Q1. Create a ufunc for the Gaussian function f(x) = exp(-x^2/2) / sqrt(2π) using [Link]. Compare its speed to a
vectorised NumPy expression.

Page 14
NumPy & Pandas — Advanced Study Guide

Q2. Explain how [Link]() differs from [Link](). Use [Link] to implement a scatter-add (histogram binning)
without [Link].

Page 15
NumPy & Pandas — Advanced Study Guide

1.10 Indexing & Slicing

Concept & Logic: NumPy supports four indexing modes: basic integer, basic slice (returns view), fancy (integer
array — returns copy), and boolean (returns copy). Slices follow Python's [start:stop:step] syntax; negative indices
wrap around.
import numpy as np

a = [Link](10) # [0 1 2 3 4 5 6 7 8 9]

# ■■ Basic integer indexing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(a[3]) # 3
print(a[-1]) # 9 (last element)

# ■■ Slicing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print(a[2:7]) # [2 3 4 5 6]
print(a[::2]) # [0 2 4 6 8] every 2nd
print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0] reversed
print(a[1:8:3]) # [1 4 7]

# ■■ 2-D Indexing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


m = [Link](12).reshape(3, 4)
# [[0 1 2 3]
# [4 5 6 7]
# [8 9 10 11]]
print(m[1, 2]) # 6
print(m[1]) # [4 5 6 7] entire row
print(m[:, 2]) # [2 6 10] entire column
print(m[0:2, 1:3]) # [[1 2][5 6]] sub-matrix

# ■■ Fancy indexing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


idx = [Link]([0, 2, 4])
print(a[idx]) # [0 2 4] — copy, not view

# 2-D fancy: select rows


rows = m[[0, 2]] # rows 0 and 2
print(rows)

# ■■ Step slicing on 2-D ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(m[::2, ::2]) # every other row and column
BASIC LEVEL QUESTIONS

Q1. Given a = [Link](20), extract: (a) elements 5–10 inclusive, (b) every 3rd element, (c) last 4 elements.
Q2. How do you extract the 2nd column from a (4,5) 2-D array?

MEDIUM LEVEL QUESTIONS

Q1. Using fancy indexing, extract elements at positions [1,4,7,9] from a 1-D array and double them, then put them back
into the original array.
Q2. Explain why a_slice = a[2:5] shares memory with a but a_fancy = a[[2,3,4]] does not.

ADVANCED LEVEL QUESTIONS

Q1. Implement a function extract_diagonal(m) that returns the diagonal of any 2-D array using only fancy indexing (no
[Link]).

Page 16
NumPy & Pandas — Advanced Study Guide

Q2. Using advanced indexing, assign 0 to all elements in a (5,5) array that are on even rows and odd columns —
without any loops.

Page 17
NumPy & Pandas — Advanced Study Guide

1.11 Multidimensional Slicing

Concept & Logic: For N-D arrays, supply N comma-separated slice/index expressions. Ellipsis (...) expands to as
many ':' as needed to fill remaining axes. [Link] (alias for None) inserts a new size-1 dimension.
import numpy as np

# 3-D array: shape (2, 3, 4)


t = [Link](24).reshape(2, 3, 4)

# ■■ Full slice notation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(t[0]) # first 'block' → shape (3,4)
print(t[0, 1]) # first block, second row → shape (4,)
print(t[0, 1, 2]) # scalar: element [0][1][2] = 6

print(t[:, 0, :]) # first row of every block → shape (2,4)


print(t[:, :, -1]) # last column of every block → shape (2,3)

# ■■ Ellipsis ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# For 5-D array shape (a,b,c,d,e)
data = [Link]((2, 3, 4, 5, 6))
print(data[0, ..., 0].shape) # (3,4,5) ellipsis fills axes 1-3
print(data[..., 2].shape) # (2,3,4,5) last axis indexed

# ■■ [Link] / None ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


v = [Link]([1, 2, 3]) # (3,)
col = v[:, [Link]] # (3,1) — column vector
row = v[[Link], :] # (1,3) — row vector

# Outer product via broadcasting


outer = col * row # (3,3)
print(outer)
BASIC LEVEL QUESTIONS

Q1. Create a (4,4,4) array and extract: (a) the first 2×2×2 corner, (b) all elements along axis 1 at index 2.
Q2. What does t[..., 0] mean for a 4-D array? Write an equivalent explicit slice.

MEDIUM LEVEL QUESTIONS

Q1. Given an image batch of shape (32, 224, 224, 3) (batch, H, W, C), extract the red channel for all images in one
slice.
Q2. Use [Link] to compute the Euclidean distance matrix between n points in d dimensions without loops.

ADVANCED LEVEL QUESTIONS

Q1. Implement a function rolling_window(a, window) using multidimensional slicing and strides that returns a (n-w+1, w)
view of a 1-D array.
Q2. Describe how the ellipsis interacts with numpy's advanced (fancy) indexing. Does t[..., [0,2]] return a view or copy?
Justify.

Page 18
NumPy & Pandas — Advanced Study Guide

1.12 Splitting · Broadcasting · Concatenation & Stacking

Concept & Logic: Splitting divides an array into sub-arrays. Broadcasting enables operations between arrays of
different (but compatible) shapes by virtually expanding dimensions. Concatenation joins arrays along an existing
axis; stacking adds a new axis.

Broadcasting Rules (applied left-to-right on reversed shape tuples):


1. Prepend 1s to the shorter shape. 2. Sizes must match OR one of them must be 1. 3. Output shape is the
element-wise maximum.

import numpy as np

# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# SPLITTING
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
a = [Link](12)
parts = [Link](a, 3) # [0-3], [4-7], [8-11]
print(parts)

# Split at specific indices


p2 = [Link](a, [2, 7]) # [:2], [2:7], [7:]

# 2-D split
m = [Link](16).reshape(4,4)
rows = [Link](m, 2) # split into top/bottom halves
cols = [Link](m, 2) # split into left/right halves

# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# BROADCASTING
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
A = [Link]((3, 4))
v = [Link]([10, 20, 30, 40]) # shape (4,)
print(A + v) # v broadcasts over rows → (3,4)

# Column broadcast
col = [Link]([[1],[2],[3]]) # shape (3,1)
print(A + col) # col broadcasts over cols → (3,4)

# Batch normalisation pattern


batch = [Link](100, 10) # 100 samples, 10 features
mean = [Link](axis=0) # shape (10,)
std = [Link](axis=0) # shape (10,)
norm = (batch - mean) / std # broadcasting: (100,10) - (10,)

# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# CONCATENATION & STACKING
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
x = [Link]([[1,2],[3,4]])
y = [Link]([[5,6],[7,8]])

print([Link]([x, y], axis=0)) # (4,2) — vertical


print([Link]([x, y], axis=1)) # (2,4) — horizontal

# vstack / hstack shortcuts

Page 19
NumPy & Pandas — Advanced Study Guide

print([Link]([x, y])) # same as concatenate axis=0


print([Link]([x, y])) # same as concatenate axis=1

# [Link] — NEW axis


print([Link]([x, y], axis=0).shape) # (2,2,2) — stacks along axis 0
print([Link]([x, y], axis=2).shape) # (2,2,2) — stacks along axis 2

# dstack — depth stack (axis 2)


print([Link]([x, y]).shape) # (2,2,2)
BASIC LEVEL QUESTIONS

Q1. Split [Link](18) into 3 equal parts. Then split it into parts of size 5, 7, 6.
Q2. What is the output shape of [Link]([[1],[2],[3]]) + [Link]([10,20,30,40])? Explain why.

MEDIUM LEVEL QUESTIONS

Q1. Stack 5 grayscale images each of shape (64,64) to create a (5,64,64) batch tensor. Then add a channel dimension
to get (5,64,64,1).
Q2. Subtract the row-wise mean from a (100,20) matrix using broadcasting in one line.

ADVANCED LEVEL QUESTIONS


Q1. Explain why (3,) and (3,1) are not the same for broadcasting. Give an example where this causes a wrong result if
you forget to reshape.
Q2. Use np.broadcast_to() to create a (100,100) array where each row is [0,1,2,...,99] without allocating full memory.
Confirm with nbytes.

Page 20
NumPy & Pandas — Advanced Study Guide

1.13 Aggregation

Concept & Logic: Aggregation functions reduce an array along one or more axes. Specifying axis=None
collapses all dimensions to a scalar. keepdims=True preserves the rank, enabling broadcasting of the result.
import numpy as np

m = [Link]([[4, 7, 2],
[1, 8, 3],
[9, 5, 6]])

# ■■ Global aggregation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link](m)) # 45
print([Link](m)) # 5.0
print([Link](m)) # 2.494
print([Link](m)) # 6.222
print([Link](m)) # 1
print([Link](m)) # 9
print([Link](m)) # 5.0

# ■■ Along an axis ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link](m, axis=0)) # column sums [14 20 11]
print([Link](m, axis=1)) # row sums [13 12 20]

# ■■ keepdims ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
row_mean = [Link](m, axis=1, keepdims=True) # shape (3,1)
centered = m - row_mean # broadcasts back to (3,3)

# ■■ Cumulative ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print([Link](m, axis=1)) # running row sums
print([Link]([Link]([1,2,3,4]))) # [1 2 6 24]

# ■■ Arg-reductions ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print([Link](m)) # 3 (flat index of minimum)
print([Link](m, axis=0)) # [2 1 2] row index of max per column
print([Link](m, axis=1)) # sort indices per row

# ■■ percentile / quantile ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


data = [Link](0, 100, 1000)
print([Link](data, [25, 50, 75])) # Q1, median, Q3
BASIC LEVEL QUESTIONS

Q1. Given a (5,4) array, compute the sum of each column and the max of each row.
Q2. What does keepdims=True do? Show how the result shape differs from keepdims=False.

MEDIUM LEVEL QUESTIONS

Q1. Standardise (z-score) each column of a (1000,20) matrix using only [Link], [Link], and broadcasting.
Q2. Find the row index and column index of the global maximum in a 2-D array without np.unravel_index, then use it.

ADVANCED LEVEL QUESTIONS

Q1. Implement a weighted mean function using [Link] and verify it with a manually computed value.
Q2. Compute the running 5-point moving average of a 100-element signal using [Link], then implement it with
[Link] for O(n) complexity.

Page 21
NumPy & Pandas — Advanced Study Guide

1.14 Masking & Boolean Indexing

Concept & Logic: Boolean indexing uses a bool array of the same shape as a selector. It always returns a copy.
Combine masks with & (AND), | (OR), ~ (NOT) — NOT Python 'and/or/not' which do not work element-wise. [Link]
provides masked arrays that propagate NaN-like behaviour.
import numpy as np

data = [Link]([3, -1, 7, -5, 2, 8, -3, 6])

# ■■ Boolean mask ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


mask = data > 0
print(mask) # [T F T F T T F T]
print(data[mask]) # [ 3 7 2 8 6] — only positives

# ■■ Combined masks ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


m2 = (data > 0) & (data < 7)
print(data[m2]) # [3 2 6]

m3 = (data < 0) | (data > 5)


print(data[m3]) # [-1 7 -5 8 -3 6]

# ■■ Assignment with mask ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


data[data < 0] = 0 # clip negatives to zero
print(data) # [3 0 7 0 2 8 0 6]

# ■■ [Link] (masked arrays) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


arr = [Link]([1, 2, -999, 4, -999, 6])
masked = [Link].masked_where(arr == -999, arr)
print(masked) # [1 2 -- 4 -- 6]
print([Link]()) # 3.25 ignores masked values

# ■■ [Link] / [Link] ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a = [Link]([0, 3, 0, 0, 5, 0, 7])
print([Link](a)) # (array([1, 4, 6]),)
print([Link](a > 0)) # [[1],[4],[6]]
BASIC LEVEL QUESTIONS

Q1. Create a 10-element random integer array (0–100) and extract all values above 50 using boolean indexing.
Q2. Set all values equal to 0 in a (4,4) array to -1 using boolean masking.

MEDIUM LEVEL QUESTIONS

Q1. Given a student scores array, mask all failing scores (<40) and compute the mean of passing scores only.
Q2. Use [Link] to mask outliers (values more than 3 standard deviations from mean) and compute the cleaned mean.

ADVANCED LEVEL QUESTIONS

Q1. Implement a function multi_mask(arr, conditions) that accepts a list of (condition_array, replacement_value) tuples
and applies them sequentially.
Q2. Explain how boolean indexing with a 2-D mask on a 2-D array works. Does it return a 2-D result? Why?

Page 22
NumPy & Pandas — Advanced Study Guide

1.15 Matrix Transpose

Concept & Logic: arr.T or [Link](arr) reverses the axis order (for N-D). For 2-D this is the classic matrix
transpose. T always returns a view — it only changes strides, not data. For general axis permutation use
[Link](arr, axes=(…)).
import numpy as np

# ■■ 2-D transpose ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


A = [Link]([[1,2,3],
[4,5,6]])
print([Link]) # (2,3)
print([Link]) # (3,2)
print(A.T)
# [[1 4]
# [2 5]
# [3 6]]

# T is a view
A.T[0, 0] = 99
print(A[0, 0]) # 99 — same memory

# ■■ 3-D transpose (axis permutation) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■


T3 = [Link](24).reshape(2, 3, 4)
print([Link](1, 0, 2).shape) # (3,2,4) axes reordered
print([Link](T3, 0, -1).shape) # (3,4,2) move axis 0 to last

# ■■ Matrix multiplication ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


B = [Link](4, 3)
C = B.T @ B # (3,3) covariance-like matrix
print([Link])

# ■■ [Link] ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
img = [Link]((100, 200, 3)) # HWC format
chw = [Link](img, 0, 2) # → (3,200,100) roughly CHW
BASIC LEVEL QUESTIONS

Q1. Transpose a (4,6) matrix. What is the new shape? Verify with .shape.
Q2. Does m.T create a new array in memory? Prove it using np.shares_memory().

MEDIUM LEVEL QUESTIONS

Q1. Convert an image batch from NHWC (N,H,W,C) to NCHW (N,C,H,W) format using [Link]. Provide the axes
argument.
Q2. Use the transpose to compute the dot product of each pair of rows in a (5,10) matrix (i.e., compute the Gram
matrix).

ADVANCED LEVEL QUESTIONS

Q1. Explain why transposing a C-contiguous array produces a Fortran-contiguous result. How does this interact with
BLAS calls in [Link]?
Q2. You have a tensor of shape (B,T,H,D) (batch, time, heads, depth). Rearrange it to (B,H,T,D) using a single
[Link] call. Show the axes argument.

Page 23
NumPy & Pandas — Advanced Study Guide

1.16 View vs Copy

Concept & Logic: A view shares memory with the original — changes propagate both ways. A copy is
independent. Views are created by basic slicing and T; copies are created by fancy indexing, boolean indexing,
[Link](), and .flatten(). Use np.shares_memory() or [Link] is not None to test.
import numpy as np

original = [Link](12).reshape(3, 4)

# ■■ View (basic slice) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


view = original[0:2, :]
view[0, 0] = 99
print(original[0, 0]) # 99 — original changed!
print(np.shares_memory(view, original)) # True
print([Link] is original) # True

# ■■ Copy (fancy index) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


copy_ = original[[0, 1], :]
copy_[0, 0] = 0
print(original[0, 0]) # still 99 — original NOT changed
print(np.shares_memory(copy_, original)) # False

# ■■ Explicit copy ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


explicit = [Link]()
explicit[:] = -1
print(original[0,0]) # 99 — unaffected

# ■■ Reshape view vs copy ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


a = [Link](6)
r = [Link](2, 3) # view (contiguous)
print(np.shares_memory(a, r)) # True

b = original[:, ::2] # non-contiguous slice


try:
r2 = [Link](3, 2) # requires copy (non-contiguous)
print('copy made:', not np.shares_memory(b, r2))
except: pass

# ■■ .flags attribute ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]['OWNDATA']) # True (owns its data)
print([Link]['OWNDATA']) # False (is a view)
BASIC LEVEL QUESTIONS

Q1. Create an array a, slice it as b = a[1:4], modify b, and show a has changed. Confirm with np.shares_memory.
Q2. What is [Link]? What does it return for an original array vs a view?

MEDIUM LEVEL QUESTIONS

Q1. Write a function safe_modify(arr, idx, val) that modifies a copy of arr at index idx, leaving the original unchanged.
Q2. When would accidentally modifying a view cause a hard-to-find bug in a data pipeline? Give a realistic example.

ADVANCED LEVEL QUESTIONS

Q1. Explain the WRITEABLE flag. How can you use [Link] = False to protect a shared array from accidental
modification?

Page 24
NumPy & Pandas — Advanced Study Guide

Q2. Does np.broadcast_to() return a view? Try to write to it and explain the error. How is this implemented using
strides?

Page 25
NumPy & Pandas — Advanced Study Guide

1.17 Handling Missing Values in NumPy

Concept & Logic: NumPy's float arrays use [Link] (IEEE 754 NaN) as a sentinel for missing values. Integer
arrays cannot store NaN — use float conversion or [Link] (masked arrays). NaN propagates through arithmetic;
use nan-safe functions ([Link], [Link], …) to ignore them.
import numpy as np

data = [Link]([1.0, [Link], 3.0, [Link], 5.0])

# ■■ Detecting NaN ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link](data)) # [F T F T F]
print([Link]([Link](data))) # True
print([Link]([Link](data))) # 2 (count of NaNs)

# ■■ NaN propagation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]()) # nan — NaN contaminates sum
print([Link]()) # nan

# ■■ Nan-safe functions ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link](data)) # 9.0
print([Link](data)) # 3.0
print([Link](data)) # 1.632
print([Link](data)) # 1.0
print([Link](data)) # 5.0

# ■■ Fill NaN ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


filled_mean = [Link]([Link](data), [Link](data), data)
filled_zero = np.nan_to_num(data, nan=0.0)
filled_inf = np.nan_to_num(data, nan=0.0, posinf=1e9, neginf=-1e9)

# ■■ Drop NaN rows from 2-D ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


m = [Link]([[1, [Link], 3],
[4, 5, 6],
[[Link], 8, 9]])
clean_rows = m[~[Link]([Link](m), axis=1)]
print(clean_rows) # [[4 5 6]] only fully complete rows

# ■■ Masked array approach ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


ma = [Link].masked_invalid(data) # masks NaN and inf
print([Link]()) # 3.0 (ignores masked)
BASIC LEVEL QUESTIONS

Q1. Create an array with three NaN values. Use [Link] to count them and [Link] to compute the mean.
Q2. What does np.nan_to_num() do? Replace NaN with 0 and inf with 999 in an example array.

MEDIUM LEVEL QUESTIONS

Q1. Write a function impute_mean(arr) that replaces NaN in each column of a 2-D array with that column's mean.
Q2. An integer array cannot hold NaN. How would you represent missing integers? Demonstrate with a masked array.

ADVANCED LEVEL QUESTIONS

Q1. Implement forward-fill (propagate last valid value forward) for a 1-D float array with NaNs using only NumPy (no
Pandas).

Page 26
NumPy & Pandas — Advanced Study Guide

Q2. Explain why [Link] != [Link] evaluates to True. How does this affect hashing and set membership, and how does
[Link]() work correctly?

Page 27
NumPy & Pandas — Advanced Study Guide

PART 2 — Pandas

2.1 Why Do We Need Pandas?

Concept & Logic: NumPy is great for homogeneous numerical arrays, but real-world data is heterogeneous (ints,
strings, dates, booleans) and needs labels (column names, row indices). Pandas provides DataFrame (labeled 2-D
table) and Series (labeled 1-D array) built on NumPy, adding: missing value handling, SQL-like operations, time
series, and file I/O.

Need NumPy Pandas

Mixed types in one object No Yes (per column)

Column / row labels No Yes

SQL-like groupby, merge Manual Built-in

File I/O (CSV, Excel, JSON) No Yes

Time series support Limited Rich (DatetimeIndex)

Missing value handling NaN only NaN + [Link] + [Link]

import pandas as pd
import numpy as np

# Pandas sits on top of NumPy


df = [Link]({'age':[25,30,22], 'name':['Alice','Bob','Carol']})
print(df['age'].values) # underlying NumPy array
print(type(df['age'].values)) # <class '[Link]'>

Page 28
NumPy & Pandas — Advanced Study Guide

2.2 Series — Creation, Indexing, Slicing, Modification, Sorting

Concept & Logic: A Series is a 1-D labeled array. The index can be integers, strings, or dates. Values are
homogeneous (one dtype). Series is the building block of DataFrames (each column is a Series sharing the
DataFrame index).
import pandas as pd
import numpy as np

# ■■ Creation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
s1 = [Link]([10, 20, 30, 40]) # default int index 0-3
s2 = [Link]([10,20,30], index=['a','b','c']) # custom index
s3 = [Link]({'x':1, 'y':2, 'z':3}) # from dict (keys=index)
s4 = [Link](5, index=range(4)) # scalar broadcast

# ■■ Basic attributes ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]) # Index(['a', 'b', 'c'], dtype='object')
print([Link]) # array([10, 20, 30])
print([Link]) # int64
print([Link]) # None (can set: [Link] = 'scores')

# ■■ Indexing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print(s2['b']) # 20 — label access
print(s2[1]) # 20 — integer positional (deprecated for ambiguous cases)
print([Link][1]) # 20 — always positional (safe)
print([Link]['b']) # 20 — always label (safe)

# ■■ Slicing ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print(s2['a':'b']) # inclusive of both ends with label slicing
print([Link][0:2]) # positional: rows 0 and 1

# ■■ Modification ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
s2['a'] = 100
[Link][2] = 999

# ■■ Sorting ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
unsorted = [Link]([30,10,20], index=['c','a','b'])
print(unsorted.sort_values()) # sorted by value
print(unsorted.sort_index()) # sorted by label
print(unsorted.sort_values(ascending=False)) # descending

# ■■ Arithmetic & alignment ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


s_a = [Link]([1,2,3], index=['a','b','c'])
s_b = [Link]([10,20,30], index=['b','c','d'])
print(s_a + s_b) # NaN for non-matching indices (a, d)
BASIC LEVEL QUESTIONS

Q1. Create a Series of 5 city names with a custom integer index starting at 1. Access the 3rd element using both label
and position.
Q2. Sort a Series of random floats first by value (descending), then by index (ascending).

MEDIUM LEVEL QUESTIONS

Q1. Add two Series with partially overlapping indices. Handle the resulting NaN values using .fillna(0).
Q2. Given a Series with string index labels, use boolean masking to extract all values greater than the median.

Page 29
NumPy & Pandas — Advanced Study Guide

ADVANCED LEVEL QUESTIONS

Q1. Create a Series with a DatetimeIndex (daily frequency for 2024). Resample to monthly averages and explain what
resampling does under the hood.
Q2. Explain the difference between .values (returns NumPy array), .array (returns ExtensionArray), and .to_numpy().
When does each matter?

Page 30
NumPy & Pandas — Advanced Study Guide

2.3 Conditional Indexing in Pandas

Concept & Logic: Boolean indexing in Pandas works like NumPy but respects the label-based index. Use &
(AND), | (OR), ~ (NOT) with parentheses around each condition. .query() provides a SQL-like string syntax. .isin(),
.between(), and .[Link]() are vectorised convenience methods.
import pandas as pd
import numpy as np

df = [Link]({
'name': ['Alice','Bob','Carol','Dave','Eve'],
'age': [25, 30, 22, 35, 28],
'score': [88, 72, 91, 65, 79],
'dept': ['IT','HR','IT','Finance','HR']
})

# ■■ Single condition ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df[df['age'] > 27])

# ■■ AND / OR conditions ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df[(df['age'] > 25) & (df['score'] > 75)])
print(df[(df['dept'] == 'IT') | (df['score'] > 85)])

# ■■ NOT condition ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df[~(df['dept'] == 'HR')])

# ■■ .query() ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print([Link]('age > 25 and score > 75'))
threshold = 80
print([Link]('score > @threshold')) # @ for external var

# ■■ .isin() ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print(df[df['dept'].isin(['IT', 'Finance'])])

# ■■ .between() ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print(df[df['age'].between(25, 30)])

# ■■ String conditions ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df[df['name'].[Link]('A')])
print(df[df['name'].[Link]('e', case=False)])
BASIC LEVEL QUESTIONS

Q1. Filter a DataFrame to rows where salary > 50000 and department == 'Engineering'.
Q2. Use .isin() to select rows where a 'category' column contains 'A', 'B', or 'C'.

MEDIUM LEVEL QUESTIONS

Q1. Using .query(), filter a large DataFrame for rows where price is between 10 and 50 and quantity > 100. Compare
speed with boolean indexing.
Q2. Select all rows where a 'description' column contains a regex pattern (e.g., any two consecutive digits) using
.[Link](regex=True).

ADVANCED LEVEL QUESTIONS

Q1. Write a function dynamic_filter(df, **kwargs) that accepts column-value pairs and applies all conditions using
Boolean indexing, supporting both equality and range conditions.

Page 31
NumPy & Pandas — Advanced Study Guide

Q2. How does Pandas evaluate df[df['a'] > 5] internally? Trace through the __getitem__ and boolean Series operations.

Page 32
NumPy & Pandas — Advanced Study Guide

2.4 DataFrames — Creation & Structure

Concept & Logic: A DataFrame is a 2-D labeled table with potentially different dtypes per column. Internally it is a
dict of Series sharing one common Index. Columns are accessible as attributes ([Link]) or keys (df['col']); the latter
is always safe. Use [Link]() and [Link]() for quick EDA.
import pandas as pd
import numpy as np

# ■■ From dict ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df = [Link]({
'id': [1, 2, 3, 4],
'name': ['Alice','Bob','Carol','Dave'],
'score': [88.5, 72.0, 91.3, 65.7],
'passed': [True, True, True, False]
})

# ■■ From list of dicts ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


records = [{'x':1,'y':2}, {'x':3,'y':4}, {'x':5,'y':6}]
df2 = [Link](records)

# ■■ From NumPy array ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


arr = [Link](0,100,(3,4))
df3 = [Link](arr, columns=['A','B','C','D'])

# ■■ Inspection ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
print([Link]) # (4, 4)
print([Link]) # per-column dtype
print([Link]) # Index(['id','name','score','passed'])
print([Link]) # RangeIndex(start=0, stop=4, step=1)
[Link]() # memory, dtypes, non-null counts
print([Link]()) # stats for numeric columns

# ■■ Adding columns ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['grade'] = [Link](df['score'], bins=[0,60,75,90,100],
labels=['F','C','B','A'])

# ■■ Removing columns ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df_clean = [Link](columns=['id']) # does not modify in place
[Link](columns=['id'], inplace=True) # modifies df

# ■■ Setting index ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df.set_index('name', inplace=True)
print([Link]['Alice']) # access by name now
BASIC LEVEL QUESTIONS

Q1. Create a DataFrame with columns 'product', 'price', 'stock'. Add a new column 'total_value' = price * stock.
Q2. What is the difference between df['col'] and [Link]? When can the latter fail?

MEDIUM LEVEL QUESTIONS

Q1. Merge two DataFrames sharing a 'user_id' column using [Link](). Use all four join types: inner, left, right, outer.
Show the shape of each result.
Q2. Use pd.pivot_table to summarise total sales by month and category from a transactions DataFrame.

ADVANCED LEVEL QUESTIONS

Page 33
NumPy & Pandas — Advanced Study Guide

Q1. Explain the MultiIndex. Create a DataFrame with a (Year, Month) MultiIndex and demonstrate .loc, .xs, and
.unstack on it.
Q2. How does Pandas copy-on-write (CoW) introduced in Pandas 2.0 change the behaviour of chained assignment?
Give a before/after example.

Page 34
NumPy & Pandas — Advanced Study Guide

2.5 loc & iloc

Concept & Logic: .loc is label-based: rows and columns are selected by their actual index/column names. Label
slices are INCLUSIVE of both endpoints. .iloc is integer position-based (like NumPy indexing): EXCLUSIVE of
stop. Both accept scalar, list, slice, or boolean array selectors.
import pandas as pd

df = [Link]({
'name': ['Alice','Bob','Carol','Dave'],
'age': [25, 30, 22, 35],
'score': [88, 72, 91, 65]
}, index=['r0','r1','r2','r3'])

# ■■ .loc — label based ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]['r0']) # entire row r0 as Series
print([Link]['r0', 'score']) # scalar: 88
print([Link]['r0':'r2', 'age':'score']) # INCLUSIVE slice
print([Link][['r0','r2'], :]) # multiple rows

# ■■ Boolean with .loc ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link][df['age'] > 25, ['name','score']])

# ■■ .loc assignment ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[Link]['r0', 'score'] = 95
[Link][df['age'] > 30, 'score'] += 5 # conditional assignment

# ■■ .iloc — integer position ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link][0]) # first row
print([Link][0, 2]) # row 0, col 2 (score)
print([Link][0:2, 1:3]) # EXCLUSIVE stop
print([Link][[0,2], :]) # rows 0 and 2
print([Link][-1, :]) # last row

# ■■ Difference summary ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


# [Link]['r0':'r2'] → includes r2
# [Link][0:2] → excludes row at position 2
BASIC LEVEL QUESTIONS

Q1. Use .loc to select rows where age > 25 and only return 'name' and 'score' columns.
Q2. Use .iloc to select the last 3 rows and the first 2 columns of any DataFrame.

MEDIUM LEVEL QUESTIONS

Q1. Assign a value of 100 to the 'score' column for all rows where 'grade' == 'A' using .loc. Why is .loc preferred over
chained indexing here?
Q2. Demonstrate a SettingWithCopyWarning scenario (with chained indexing) and fix it with .loc.

ADVANCED LEVEL QUESTIONS

Q1. Explain how .loc with a boolean array works when the index is not the default RangeIndex — specifically the
index-alignment behaviour.
Q2. Use .iloc inside a custom function that accepts row batches (for chunked processing) and applies a transformation.

Page 35
NumPy & Pandas — Advanced Study Guide

2.6 Broadcasting & Lambda Functions in Pandas

Concept & Logic: Pandas broadcasts scalar and Series operations across a DataFrame column-wise (axis=0 by
default) or row-wise (axis=1). .apply() runs a function along an axis; .applymap()/.map() on Series run
element-wise. Lambda functions provide concise inline transformations.
import pandas as pd
import numpy as np

df = [Link]({
'math': [80, 65, 90, 72],
'science': [75, 88, 70, 85],
'english': [90, 60, 85, 78]
})

# ■■ Scalar broadcasting ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df + 10) # adds 10 to every element
print(df / 100) # normalise all scores

# ■■ Series broadcasting (column-wise, axis=0) ■■■■■■■■■■■■■■■■■■■■


weights = [Link]([0.4, 0.3, 0.3], index=['math','science','english'])
weighted = df * weights # each column multiplied by its weight
print([Link](axis=1)) # weighted total per student

# ■■ .apply() on a Series (element-wise) ■■■■■■■■■■■■■■■■■■■■■■■■■


df['math_grade'] = df['math'].apply(lambda x: 'A' if x>=85 else 'B' if x>=70 else 'C')

# ■■ .apply() row-wise ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['total'] = df[['math','science','english']].apply(lambda row: [Link](), axis=1)
df['max_subject'] = df[['math','science','english']].apply(lambda row: [Link](),
axis=1)

# ■■ .map() (Series only, element-wise) ■■■■■■■■■■■■■■■■■■■■■■■■■■■


grade_map = {80:'B', 65:'D', 90:'A', 72:'C'}
df['math_letter'] = df['math'].map(grade_map)

# ■■ .applymap() / .map() on DataFrame (element-wise) ■■■■■■■■■■■■


# Pandas 2.1+: use .map() instead of .applymap()
numeric = df[['math','science','english']]
scaled = [Link](lambda x: round(x/10)*10) # round to nearest 10

# ■■ assign() for chaining ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


result = (df
.assign(avg = lambda d: d[['math','science','english']].mean(axis=1))
.assign(pass_ = lambda d: d['avg'] >= 75)
)
print(result[['avg','pass_']])
BASIC LEVEL QUESTIONS

Q1. Add a 'bonus' column equal to 5% of each row's 'salary' column using a lambda in .assign().
Q2. Use .apply() with a lambda to convert a 'temperature_C' column to Fahrenheit (F = C * 9/5 + 32).

MEDIUM LEVEL QUESTIONS

Q1. Row-wise broadcast: normalise each row of a numeric DataFrame to sum to 1 using .apply(axis=1).

Page 36
NumPy & Pandas — Advanced Study Guide

Q2. Use .map() to replace string categories ('cat'/'dog'/'bird') with integer codes (0/1/2) in a Series.

ADVANCED LEVEL QUESTIONS

Q1. Compare the performance of .apply(lambda), .map(), and a vectorised NumPy expression for transforming a
1M-row column. Profile all three.
Q2. Implement a custom aggregation function using .agg() that returns multiple statistics (mean, std, skewness) in a
single .groupby().agg() call.

Page 37
NumPy & Pandas — Advanced Study Guide

2.7 Renaming & Dropping

Concept & Logic: .rename() changes column or index labels without modifying data. .drop() removes rows
(axis=0) or columns (axis=1). Both return new DataFrames by default (inplace=True modifies in place). Use
errors='ignore' to silently skip non-existent labels.
import pandas as pd

df = [Link]({'A':[1,2,3], 'B':[4,5,6], 'C':[7,8,9]})

# ■■ Rename columns ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df2 = [Link](columns={'A':'alpha','B':'beta','C':'gamma'})
[Link](columns=[Link], inplace=True) # all lowercase

# ■■ Rename using function ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[Link](columns=lambda c: [Link]().replace(' ','_'), inplace=True)

# ■■ Rename index ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[Link](index={0:'first', 1:'second', 2:'third'}, inplace=True)

# ■■ Drop columns ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df_no_c = [Link](columns=['c'])
[Link]('c', axis=1, inplace=True) # same with axis=

# ■■ Drop rows ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[Link](index='first', inplace=True)
[Link]([0, 2], axis=0, inplace=True) # drop rows by position if int index

# ■■ errors='ignore' ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
[Link](columns=['nonexistent'], errors='ignore') # no KeyError

# ■■ Rename entire index / columns object ■■■■■■■■■■■■■■■■■■■■■■■■■


[Link] = ['x', 'y'] # direct reassignment
[Link] = range(len(df)) # reset to RangeIndex

# ■■ set_axis ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
df.set_axis(['col1','col2'], axis='columns', inplace=True)
BASIC LEVEL QUESTIONS

Q1. Rename columns 'fname' → 'first_name' and 'lname' → 'last_name'. Remove the 'temp' column.
Q2. Drop all rows with index 0, 3, or 5 from a DataFrame using .drop().

MEDIUM LEVEL QUESTIONS

Q1. Write a clean_columns(df) function that strips whitespace, lowercases, and replaces spaces with underscores in all
column names using .rename().
Q2. Drop all columns that contain more than 40% missing values from a DataFrame.

ADVANCED LEVEL QUESTIONS

Q1. Explain the Copy-on-Write implications of inplace=True in Pandas 2.x. Why is the Pandas team recommending
against inplace?
Q2. Use [Link]() and [Link]() to identify columns present in one DataFrame but not another,
then perform targeted drops.

Page 38
NumPy & Pandas — Advanced Study Guide

2.8 Importing Files & Cleaning Data

Concept & Logic: pd.read_csv / read_excel / read_json are the primary file I/O functions. Data cleaning involves:
handling missing values, fixing dtypes, removing unwanted characters, normalising strings, and validating ranges.
import pandas as pd
import numpy as np

# ■■ Reading files ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df = pd.read_csv('[Link]')
df = pd.read_csv('[Link]', index_col=0) # first col as index
df = pd.read_csv('[Link]', usecols=['a','b']) # only specific cols
df = pd.read_csv('[Link]', dtype={'age':int}) # specify dtype
df = pd.read_csv('[Link]', na_values=['N/A','–','null']) # extra NaN markers

df_xl = pd.read_excel('[Link]', sheet_name='Sheet1')


df_js = pd.read_json('[Link]')

# ■■ Writing files ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df.to_csv('[Link]', index=False)
df.to_excel('[Link]', index=False)
df.to_json('[Link]', orient='records')

# ■■ Inspecting missing values ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]().sum()) # NaN count per column
print([Link]().mean() * 100) # % missing per column
print([Link]().any(axis=1).sum()) # rows with at least one NaN

# ■■ Filling missing values ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['age'].fillna(df['age'].median(), inplace=True)
df['city'].fillna('Unknown', inplace=True)
[Link](method='ffill', inplace=True) # forward fill
[Link](method='bfill', inplace=True) # backward fill

# ■■ Dropping missing values ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[Link](inplace=True) # drop any row with NaN
[Link](subset=['age','score']) # only if those cols are NaN
[Link](axis=1, thresh=len(df)*0.6) # drop cols missing >40%

# ■■ Fixing dtypes ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['price'] = df['price'].[Link]('$','').astype(float)
df['date'] = pd.to_datetime(df['date'])
df['code'] = df['code'].astype('category')

# ■■ String cleaning ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['name'] = df['name'].[Link]().[Link]()
df['email']= df['email'].[Link]()
df['phone']= df['phone'].[Link](r'[^0-9]', '', regex=True)
BASIC LEVEL QUESTIONS

Q1. Read a CSV with pd.read_csv(). Print shape, dtypes, and number of missing values per column.
Q2. Fill NaN in a numeric column with its median and NaN in a string column with 'Unknown'.

MEDIUM LEVEL QUESTIONS

Page 39
NumPy & Pandas — Advanced Study Guide

Q1. Write a pipeline that: reads a CSV, strips whitespace from all string columns, converts a 'price' column with '$' signs
to float, and saves to a new CSV.
Q2. Use pd.read_csv with chunksize=1000 to process a large file in chunks and count total non-null rows.

ADVANCED LEVEL QUESTIONS

Q1. Build a generic data_quality_report(df) function that returns: % missing per column, dtype per column, number of
duplicates, and outlier counts (IQR method) in a formatted DataFrame.
Q2. Explain the difference between [Link], [Link], None, and [Link]. When does each appear and how do they
interact with arithmetic?

Page 40
NumPy & Pandas — Advanced Study Guide

2.9 Handling Duplicates

Concept & Logic: .duplicated() returns a boolean Series marking duplicate rows. .drop_duplicates() removes
them. Use subset= to consider only specific columns; keep= controls which occurrence to retain ('first','last',False).
import pandas as pd

df = [Link]({
'id': [1, 2, 2, 3, 4, 4, 4],
'name': ['A','B','B','C','D','D','D'],
'val': [10,20,20,30,40,40,99]
})

# ■■ Detect duplicates ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]()) # based on ALL columns
print([Link](subset='id')) # based on 'id' only
print([Link]().sum()) # total duplicate row count

# ■■ View duplicate rows ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print(df[[Link](keep=False)]) # show ALL copies

# ■■ Drop duplicates ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df_clean = df.drop_duplicates() # keep first
df_clean = df.drop_duplicates(keep='last') # keep last
df_clean = df.drop_duplicates(keep=False) # remove all copies
df_clean = df.drop_duplicates(subset=['id','name']) # partial key

# ■■ Deduplicate keeping highest value row ■■■■■■■■■■■■■■■■■■■■■■■■■


df_top = (df.sort_values('val', ascending=False)
.drop_duplicates(subset='id', keep='first'))
print(df_top)
BASIC LEVEL QUESTIONS

Q1. Create a DataFrame with 3 duplicate rows and show: total count, which rows are duplicates, and the cleaned
DataFrame.
Q2. How does keep='last' differ from keep='first' in drop_duplicates()?

MEDIUM LEVEL QUESTIONS

Q1. For each duplicate group (same 'email'), keep only the most recent row based on a 'timestamp' column.
Q2. Identify columns that have only one unique value across the entire DataFrame (constant columns) and drop them.

ADVANCED LEVEL QUESTIONS

Q1. Implement a fuzzy deduplication: use difflib to find rows where 'name' is >80% similar and flag them for manual
review.
Q2. Explain how drop_duplicates() handles NaN values. Do two NaN values in the same column count as duplicates?

Page 41
NumPy & Pandas — Advanced Study Guide

2.10 Date Reading & Formatting

Concept & Logic: pd.to_datetime() converts strings/ints to Timestamp objects. DatetimeIndex unlocks .dt
accessor for year/month/day extraction, and resampling. pd.date_range() generates regular frequency ranges.
strftime/strptime format codes control string conversion.
import pandas as pd
import numpy as np

# ■■ Parsing dates ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df = [Link]({'date_str': ['2024-01-15','2024-02-28','2024-03-10']})
df['date'] = pd.to_datetime(df['date_str'])
print(df['date'].dtype) # datetime64[ns]

# Parse non-standard format explicitly


df['date2'] = pd.to_datetime(df['date_str'], format='%Y-%m-%d')
df['date3'] = pd.to_datetime(['15/01/2024','28/02/2024'], format='%d/%m/%Y')

# ■■ .dt accessor ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['year'] = df['date'].[Link]
df['month'] = df['date'].[Link]
df['day'] = df['date'].[Link]
df['weekday'] = df['date'].dt.day_name()
df['quarter'] = df['date'].[Link]
df['week'] = df['date'].[Link]().week

# ■■ Formatting (back to string) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['formatted'] = df['date'].[Link]('%B %d, %Y')
print(df['formatted']) # e.g. 'January 15, 2024'

# ■■ Date arithmetic ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['days_since'] = ([Link]() - df['date']).[Link]
df['next_month'] = df['date'] + [Link](months=1)

# ■■ pd.date_range ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
idx = pd.date_range('2024-01-01', periods=12, freq='MS') # month start
ts = [Link]([Link](12), index=idx)

# ■■ Resampling ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
daily_ts = [Link]([Link](365),
index=pd.date_range('2024-01-01', periods=365))
monthly = daily_ts.resample('ME').mean() # monthly average
weekly = daily_ts.resample('W').sum() # weekly sum

# ■■ Timezone handling ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


ts_utc = [Link]('2024-06-15', tz='UTC')
ts_ist = ts_utc.tz_convert('Asia/Kolkata')
print(ts_ist) # IST is UTC+5:30
BASIC LEVEL QUESTIONS

Q1. Parse a column of date strings in 'DD-MM-YYYY' format using pd.to_datetime with the format argument.
Q2. Extract year, month, and day of week from a datetime column using .dt accessor.

MEDIUM LEVEL QUESTIONS

Q1. Compute the number of business days between two date columns using np.busday_count.

Page 42
NumPy & Pandas — Advanced Study Guide

Q2. Resample a daily time series of stock prices to weekly frequency, keeping the last price of each week.

ADVANCED LEVEL QUESTIONS

Q1. Build a function that flags rows where a transaction date falls on a weekend or national holiday (using a custom
calendar).
Q2. Explain the difference between 'ME', 'MS', 'M' frequency aliases in pd.date_range. How did these change from
Pandas 2.2?

Page 43
NumPy & Pandas — Advanced Study Guide

2.11 Advanced Broadcasting & GroupBy in Pandas

Concept & Logic: GroupBy implements the split-apply-combine pattern. .transform() returns a result with the
same shape as the input (useful for broadcasting group statistics back to original rows). .agg() applies multiple
functions; .apply() handles complex custom logic per group.
import pandas as pd
import numpy as np

df = [Link]({
'dept': ['IT','IT','HR','HR','IT','HR'],
'name': ['Alice','Bob','Carol','Dave','Eve','Frank'],
'salary': [80000,75000,60000,65000,90000,55000],
'years': [3,5,2,7,1,4]
})

# ■■ Basic groupby ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


print([Link]('dept')['salary'].mean())
print([Link]('dept')[['salary','years']].agg(['mean','std','count']))

# ■■ Named aggregation ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


stats = [Link]('dept')['salary'].agg(
avg_sal='mean', max_sal='max', count='count'
)
print(stats)

# ■■ transform — broadcast back to original shape ■■■■■■■■■■■■■■■■■■


df['dept_avg_salary'] = [Link]('dept')['salary'].transform('mean')
df['salary_vs_avg'] = df['salary'] - df['dept_avg_salary']
df['salary_pct_rank'] = [Link]('dept')['salary'].transform('rank', pct=True)

# ■■ filter — keep groups satisfying a condition ■■■■■■■■■■■■■■■■■■■


high_earning_depts = [Link]('dept').filter(lambda g: g['salary'].mean() > 70000)
print(high_earning_depts)

# ■■ apply — arbitrary per-group function ■■■■■■■■■■■■■■■■■■■■■■■■■


def top2(group):
return [Link](2, 'salary')

print([Link]('dept').apply(top2, include_groups=False))

# ■■ Cumulative per group ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


df['cum_salary'] = [Link]('dept')['salary'].cumsum()
BASIC LEVEL QUESTIONS

Q1. Group a sales DataFrame by 'region' and compute the total and average revenue per region.
Q2. What does .transform('mean') return compared to .agg('mean')? Show the shape difference.

MEDIUM LEVEL QUESTIONS

Q1. Compute a within-group z-score for each employee's salary using .groupby() + .transform().
Q2. Use .filter() to remove all groups where any member's salary is below 40000.

ADVANCED LEVEL QUESTIONS

Q1. Implement a rolling 3-month revenue moving average per product using .groupby() + .rolling() + .mean(). Handle
the warm-up period correctly.

Page 44
NumPy & Pandas — Advanced Study Guide

Q2. Explain the performance implications of .apply() vs .transform() for large DataFrames. When should you vectorise
instead?

Page 45

You might also like