Pandas
Intermediate 14 min read Pandas

Applying Functions and Vectorization

Per-row logic vs C-speed vector math

df.apply pipes row/column functions over a frame, while true vectorization (column algebra, np.select, pd.cut) runs in C — pick the fast path whenever the task is expressible element-wise.

applyapplymapvectorizenp.selectpd.cutlambda
Before we start

What you will learn

  • Use apply() and applymap() correctly
  • Replace slow row-wise lambdas with vectorized expressions
  • Use np.select / pd.cut for branching and binning
Introduction

What is this?

df.apply(fn, axis=1) runs fn on each row, and .applymap runs an element function over a frame — both iterate in Python, so they're slow. Whenever the logic can be expressed as column arithmetic or a vectorized function (np.where, np.select, pd.cut, .map), that path executes in C across whole columns. Knowing which tool matches which job is a performance and reliability skill.

Motivation

Why do you need it?

  • row-wise apply on 1M rows can take minutes; the vectorized equivalent, milliseconds.
  • Many 'apply' patterns are one-liner true vectorizations — an easy 1000× win.
  • Branch-heavy logic belongs to np.select / np.where, offloading the loop to C.
  • Senior-performers read apply as a code smell and reach for the vector version first.
Think about it

Mental model

Imagine

Having a manager give each employee hand-written instructions

Think of

Row-wise apply

Works, but each instruction costs time. Batching would be faster — as a supervisor's memo, vectorized ops broadcast one instruction to the whole column. apply is the hand-delivered memo; ufuncs are the group email.

Mechanics

How it works

  • df.apply(fn, axis=1) passes each row to fn — slow, but fine for genuinely row-shaped logic.
  • df['col'].apply(fn) maps a function element-wise — slower than df['col'].map(dict) for lookup maps.
  • df['new'] = df['a'] * 2 + df['b'] runs pure C column algebra — the fast path.
  • np.where(cond, a, b) and np.select([c1,c2], [r1,r2], default) vectorize branching.
  • pd.cut(x, bins, labels=) creates categorical buckets in C.

Pick the fastest tool.

Column algebra
Branch?
Bins?
Lookup table
Row-specific logic
Hands-on

Examples

The three speeds

The three speeds
import pandas as pd, numpy as np
df = pd.DataFrame({"age": [23, 41, 67], "spend": [10, 500, 25]})
# slow: apply
df["status_apply"] = df.apply(lambda r: "loyal" if r.spend > 100 else "normal", axis=1)
# fast: vectorized np.where
df["status_vec"] = np.where(df["spend"] > 100, "loyal", "normal")
# fast: pd.cut for 3 bins
df["age_band"] = pd.cut(df["age"], bins=[0, 35, 65, 99], labels=["young", "mid", "senior"])
print(df[["status_apply", "status_vec", "age_band"]])

np.where and np.where-based branches run in C; apply runs one Python function call per row. Both produce identical output, so the vector version wins whenever expressible.

Real world

In production

  • A feature pipeline rewrote 40 row-wise applies into np.select/np.where — 30× faster nightly.
  • An eligibility flow mapped SKU→category through .map(dict) over .apply with a lookup.
  • A monitoring score bins latency with pd.cut before feeding a classifier.
Guidelines

Best practices

  • Profile before optimizing; apply on small frames is irrelevant — it's big data where it bites.
  • Express branching with np.select at any length (condition lists stay readable).
  • Keep .map for categorical lookup tables — faster and skips lambda overhead.
  • When apply is unavoidable, vectorize the per-row helper and call it once.
Watch out

Common mistakes

  • Handing apply a function already vectorized (like np.log) — np.log on the whole column is faster.
  • apply(axis=1) dropping dtype performance when a column expr suffices.
  • Using apply to mutate in place and relying on side effects.
When things break

Debugging tips

  • Compare apply output vs the vectorized expression with df.equals/np.allclose on a sample.
  • If apply is slow, the fix is usually rewriting, not parallelizing — check the per-element cost first.
  • applymap vs column apply — use the narrowest scope you need.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/2
1

Which is generally slowest for element-wise transforms on a big column?

2

np.where is a vectorized branching substitute for apply(lambda ...).

Go deeper

Official documentation