Data Analyst

Module 8 of 12

Module 8: Advanced Data Processing

5 min read826 words
What you'll learn
Optimize Pandas operations for large datasets (vectorization, eval, query)Manage memory efficiently with appropriate dtypes and chunked readingWrite clean, readable code using method chainingBuild analyses in Polars with lazy evaluation and expressionsBenchmark Pandas vs. Polars and choose the right tool for the job

"Fast enough isn't. When your data grows from thousands to millions of rows, the techniques that 'worked fine' will bring your machine to its knees."

Learning Objectives

By the end of this module, you will be able to:

  • Optimize Pandas operations for large datasets (vectorization, eval, query)
  • Manage memory efficiently with appropriate dtypes and chunked reading
  • Write clean, readable code using method chaining
  • Build analyses in Polars with lazy evaluation and expressions
  • Benchmark Pandas vs. Polars and choose the right tool for the job

Part 1: Pandas Performance Optimization

Why Your Pandas Code Is Slow

Three usual suspects: Python loops over rows (especially .iterrows()), accidental copies, and fat dtypes (object, float64 everywhere).

SmellFix direction
.iterrows()Vectorize, np.select, apply only if needed
Giant intermediatesChain, reuse views, drop unused columns
object columnsCategoricals, smaller numerics

Rule 1: Vectorize Everything

np.where / np.select for branching on whole columns beats row-wise Python. Think in arrays, not records.

Rule 2: Use eval() and query() for Complex Operations

DataFrame.eval and .query push work through numexpr-style paths for large frames — handy for arithmetic-heavy filters on many columns.

Rule 3: Optimize Data Types

Downcast integers, use category for low-cardinality strings, float32 if precision allows. Halving width ≈ halving memory.

Rule 4: Chunked Reading for Huge Files

read_csv(chunksize=...) or iterator=True lets you aggregate in passes without loading everything at once.

Method Chaining — Clean, Readable Pipelines

.pipe for custom steps; assign with lambdas; keep a linear story: load → clean → feature → aggregate. Easier to test and profile.

Try This! Print df.memory_usage(deep=True).sum() before and after dtype tuning on a sample file.

Part 2: Introduction to Polars

Why Polars?

Polars is a columnar DataFrame library built for multi-core and lazy query optimization. Often much faster than Pandas on big workloads; API is expression-based.

Pandas habitPolars spirit
In-place df["x"]=...Expressions + with_columns
Eager everythinglazy() then collect()
Mixed index tricksExplicit columns

Polars Expressions

.select, .filter, .with_columns with pl.col("a").mean() etc. — composable and parallel-friendly.

Lazy Evaluation — Polars Superpower

Scan parquet/csv → build lazy plan → collect() once. The engine can push down predicates and projection before reading full data.

Scan — Read Files Lazily

pl.scan_parquet, pl.scan_csv for out-of-core style workflows.

Performance Comparison: Pandas vs. Polars

Benchmark on your data: groupbys, joins, filters. Polars often wins on wide tables and big aggregations; Pandas still wins on ecosystem familiarity and stack overflow depth.

When to Use Pandas vs. Polars

Choose PandasChoose Polars
Team knows it coldSpeed/memory critical
Library needs pandas objectETL on huge parquet
Quick one-offsLazy pipelines on big files

Chunked reads (Pandas reminder): pd.read_csv(path, chunksize=100_000) returns an iterator — aggregate per chunk (running sums, filters) to stay under RAM. Pair with explicit dtype and usecols.

Polars mental modelOne-liner
Add columnwith_columns
Filterfilter
Lazy planscan_parquet(...).filter(...).select(...).collect()

Key Example: Same idea in Pandas (vectorized) vs Polars (expressions) — categorizing revenue tiers without Python row loops.

python
[object Object], numpy ,[object Object], np
,[object Object], pandas ,[object Object], pd
,[object Object], polars ,[object Object], pl

pdf = pd.DataFrame({,[object Object],: [,[object Object],, ,[object Object],, ,[object Object],, ,[object Object],]})
pdf[,[object Object],] = np.select(
    [pdf[,[object Object],] > ,[object Object],, pdf[,[object Object],] > ,[object Object],],
    [,[object Object],, ,[object Object],],
    default=,[object Object],,
)

ldf = pl.DataFrame({,[object Object],: [,[object Object],, ,[object Object],, ,[object Object],, ,[object Object],]})
ldf = ldf.with_columns(
    pl.when(pl.col(,[object Object],) > ,[object Object],)
    .then(pl.lit(,[object Object],))
    .when(pl.col(,[object Object],) > ,[object Object],)
    .then(pl.lit(,[object Object],))
    .otherwise(pl.lit(,[object Object],))
    .alias(,[object Object],),
)

At-a-glance: slow vs fast patterns

Slow patternFaster pattern
for i, row in df.iterrows()Vectorized ops, itertuples only if needed
Repeated df.copy() in a loopOne copy before loop or chain without copies
object dtype for 50-level categorycategory dtype
Reading 10 GB CSV in one gochunksize or Polars scan_csv lazy
Giant .apply(lambda x: ...)NumPy ufuncs or Polars expressions
Multiple .merge on wide framesPush joins to SQL/warehouse when possible

Profiling quick wins: %%time / time.perf_counter() around the suspect cell; memory_profiler if RAM spikes. Often one groupby or one bad join dominates — fix that before micro-optimizing syntax.

When Polars wins most: Parquet-heavy pipelines, parallel reads, complex expression chains with lazy optimization. When Pandas wins: Stack Overflow answers, sklearn input, quick notebooks, legacy team codebases.

Practice Exercises

Exercise 1: Optimize Slow Code (Beginner)

Replace iterrows categorization with vectorized np.select; time both.

Exercise 2: Memory Optimization (Intermediate)

Shrink dtypes on a wide sample; report memory before/after.

Exercise 3: Method Chaining Challenge (Intermediate)

Rewrite a multi-step clean + aggregate as one chain.

Exercise 4: Polars Conversion (Advanced)

Port a Pandas notebook section to Polars lazy scan + collect.

Exercise 5: Lazy vs. Eager Benchmark (Advanced)

Same query eager vs lazy; discuss plan optimizations.

Mini-Project: Data Pipeline Optimizer

Take a slow script: profile it, vectorize, fix dtypes, optional Polars path for the hottest step, short README with timings.

Key Takeaways

  1. Vectorize; avoid .iterrows() for compute-heavy work.
  2. Dtypes and columns you drop matter as much as “the algorithm.”
  3. Chunk huge CSVs; prefer Parquet for repeated analytics loads.
  4. Polars expressions + lazy mode target big data friction points.
  5. Pick tool by team, ecosystem, and measured bottleneck.

Resources for Further Learning

Key Takeaway

  • Profile before optimizing; target the hot path.
  • Vectorize and tighten dtypes instead of micro-tuning syntax alone.
  • Stream or chunk when data doesn’t fit comfortably in RAM.
  • Chain Pandas steps for clarity and fewer stray temporaries.
  • Try Polars when scans, joins, and aggregations dominate runtime.

Next up: Module 9 — Business Intelligence Tools — turning your analysis into dashboards stakeholders actually use.