Intro to NumPy and pandas
The bridge from core Python into data work — arrays, DataFrames, and why these libraries exist at all.
The Problem NumPy Exists to Solve
A plain Python list can hold numbers, but every arithmetic operation on it requires an explicit loop — and each iteration carries real overhead, since Python objects (even a simple integer) are far heavier than the raw numeric values a lower-level language works with directly.
numbers = list(range(1_000_000))
doubled = [n * 2 for n in numbers] # a full Python-level loop, one iteration at a timeimport numpy as np
numbers = np.arange(1_000_000)
doubled = numbers * 2 # no explicit loop — operates on the WHOLE array at onceThe NumPy version is not just shorter — it is typically 10-100x faster for numeric work at this scale. The reason is structural: a NumPy array stores its numbers as a single contiguous block of raw memory (much closer to how C or Java store an array of numbers) rather than as a list of individually boxed Python objects, and operations like * 2 run as a single, highly optimised loop written in C — this is called vectorisation, and it is the entire reason NumPy exists.
The ndarray — NumPy's Core Data Structure
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a) # [1 2 3 4 5]
print(a.shape) # (5,) — a 1-dimensional array of 5 elements
print(a.dtype) # int64 — every element shares ONE data type, unlike a Python list
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(matrix.shape) # (2, 3) — 2 rows, 3 columnsThe single most important structural difference from a Python list: every element of a NumPy array shares exactly one dtype — you cannot freely mix an int and a str in the same array the way a Python list allows. This uniformity is precisely what makes the contiguous-memory, vectorised-operation model possible in the first place.
prices = np.array([19.99, 29.99, 9.99, 49.99])
with_tax = prices * 1.08 # every element multiplied at once
print(with_tax) # [21.5892 32.3892 10.7892 53.9892]
print(prices[prices > 20]) # [29.99 49.99] — boolean indexing: filter by a condition, no loopBroadcasting — operating on arrays of different shapes
Broadcasting is the rule set NumPy uses to apply an operation between arrays of different shapes without requiring you to manually resize either one — in the example above, the single number 1.08 is automatically "stretched" to apply to every element of the prices array. Broadcasting extends to combining full arrays of compatible shapes too, and is genuinely central to writing idiomatic, loop-free NumPy code.
Rows, Columns, and Labels — Built on Top of NumPy
NumPy arrays are excellent for pure numeric data, but real-world data is usually tabular — rows and named columns, often with mixed types (text, numbers, dates) in the same table. pandas is built on top of NumPy specifically to handle this shape of data, centred on two core structures: Series (a single labelled column of data) and DataFrame (a full table — a collection of aligned Series).
import pandas as pd
df = pd.DataFrame({
"name": ["Keyboard", "Mouse", "Monitor"],
"price": [79.99, 24.99, 249.99],
"in_stock": [True, True, False],
})
print(df)
# name price in_stock
# 0 Keyboard 79.99 True
# 1 Mouse 24.99 True
# 2 Monitor 249.99 Falseprint(df["price"]) # a single column, as a Series
print(df[df["in_stock"]]) # rows where in_stock is True — boolean filtering, same idea as NumPy
print(df["price"].mean()) # 118.32333... — built-in aggregate methods
print(df["price"].sum()) # 354.97
df["price_with_tax"] = df["price"] * 1.08 # adding a new computed column, vectorised, no looppandas vs the csv Module for Real Files
The CSV and JSON module covered earlier in this track handles files with plain Python data structures — lists of dicts. For genuinely tabular analysis work — filtering, aggregating, joining, computing statistics across thousands or millions of rows — pandas is usually the better tool, since it was purpose-built for exactly this.
import csv
import pandas as pd
# The csv module — you get a list of dicts, and write your own loops for everything
with open("sales.csv") as f:
rows = list(csv.DictReader(f))
total = sum(float(row["amount"]) for row in rows)
# pandas — one line to load, built-in vectorised aggregation
df = pd.read_csv("sales.csv")
total = df["amount"].sum()The csv module remains the right choice for simple row-by-row processing, especially in a script with no other pandas dependency — pulling in pandas for a five-line script that reads one small file is unnecessary weight. pandas earns its place once the work genuinely involves analysis: grouping, joining multiple sources, computing statistics, or handling data too large to comfortably reason about with manual loops.
Grouping and aggregating — a genuinely common real task
df = pd.DataFrame({
"category": ["electronics", "electronics", "office", "office"],
"amount": [79.99, 249.99, 4.99, 12.99],
})
print(df.groupby("category")["amount"].sum())
# category
# electronics 329.98
# office 17.98Two Warnings Every pandas Beginner Eventually Hits
SettingWithCopyWarning is pandas' most infamous warning, and it confuses nearly everyone the first time they see it — it appears when pandas cannot tell for certain whether you are modifying the original DataFrame or a temporary copy of a slice of it.
electronics = df[df["category"] == "electronics"]
electronics["amount"] = electronics["amount"] * 1.1
# SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame# If you intend to modify the ORIGINAL df:
df.loc[df["category"] == "electronics", "amount"] *= 1.1
# If you intend to work on an independent COPY:
electronics = df[df["category"] == "electronics"].copy()
electronics["amount"] = electronics["amount"] * 1.1 # no warning — pandas knows this is intentionaldf[condition]["column"] = value — is the root cause of most SettingWithCopyWarning cases. Whether the first bracket returns a view or a copy of the original data is not always guaranteed, which is exactly why the warning exists. Use a single .loc[row_condition, "column"] call instead whenever you intend to modify the original DataFrame.Replacing a 40-Minute Report Script With a 3-Second One, at a San Diego Marketing Analytics Company
A weekly campaign-performance report reads a 2-million-row CSV using the standard csv module, computing per-campaign totals and averages with hand-written Python loops and dictionaries. The script takes 40 minutes to run, and every new metric requested by the marketing team means writing another manual loop.
totals = {}
counts = {}
with open("campaign_data.csv") as f:
for row in csv.DictReader(f):
campaign = row["campaign_id"]
totals[campaign] = totals.get(campaign, 0) + float(row["spend"])
counts[campaign] = counts.get(campaign, 0) + 1
averages = {c: totals[c] / counts[c] for c in totals}df = pd.read_csv("campaign_data.csv")
summary = df.groupby("campaign_id")["spend"].agg(["sum", "mean", "count"])Why the difference was dramatic, not just stylistic
The pandas version runs in roughly 3 seconds instead of 40 minutes — the vectorised groupby/agg operations are implemented in optimised C code operating on contiguous memory, exactly as described in Part 01, instead of 2 million individual Python-level dictionary lookups and updates. Just as significantly for the team's day-to-day work, adding a new requested metric became a one-line change to the .agg([...]) call instead of writing and testing an entirely new manual accumulation loop.
Four Misconceptions About NumPy and pandas
5 Interview Questions — With Complete Answers
NumPy & pandas Mistakes Beginners Make Constantly
Errors You Will Hit With NumPy & pandas — And Exactly Why
🎯 Key Takeaways
- ✓NumPy arrays store uniformly-typed data in contiguous memory, enabling vectorised operations (a single optimised C loop) that are typically 10-100x faster than an equivalent Python-level loop.
- ✓pandas is built on top of NumPy, adding labelled rows/columns and mixed-type tabular data support via Series (one column) and DataFrame (a full table).
- ✓Prefer the standard library csv module for small, simple row-by-row scripts; reach for pandas once real analysis — grouping, aggregating, joining — or larger data volumes are involved.
- ✓groupby()/.agg() is pandas' equivalent of a SQL GROUP BY — computing per-group aggregates without writing manual accumulation loops.
- ✓SettingWithCopyWarning signals ambiguity about whether an assignment targets the original data or a copy — resolve it with .loc[...] for an intentional original-data edit, or .copy() for an intentional independent copy.
- ✓Vectorisation means avoiding slow Python-level looping via optimised C operations on contiguous memory — a distinct concept from multi-core parallelism, even though some operations can also benefit from both.
What comes next
Module 44 puts everything from this phase together in a full, real project — building a complete command-line tool from scratch with argparse.
Module 44 → Building a CLI ToolDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.