apply, map, and applymap
Pandas gives you several ways to run custom logic over your data that don't have a built-in vectorized equivalent. The three most common are Series.map, which transforms every value in a Series using a function, dict, or another Series; DataFrame.apply, which runs a function along an axis (rows or columns) of a DataFrame or over every value of a Series; and DataFrame.applymap (now DataFrame.map as of pandas 2.1, with applymap deprecated), which applies a function element-wise across an entire DataFrame. Choosing correctly matters for both readability and performance — a function that could be vectorized with NumPy will always outperform apply on large data, since apply is essentially a disguised Python loop under the hood.
Cricket analogy: Recoding a Series of team abbreviations with Series.map is like translating 'IND' to 'India' value by value, while DataFrame.apply running across a row of a scorecard to compute strike rate needs multiple columns at once, and it's slower than a vectorized run rate formula.
Series.map: lookup and transform
map is the right tool when you're translating each value in a single column independently — recoding categories, joining against a lookup dictionary, or applying a simple scalar function. It accepts three kinds of arguments: a function, a dict, or another Series (used as a lookup table, where the index of the Series being mapped aligns against the index of the mapping Series). Values not found in a dict or lookup Series become NaN by default, which is a common source of silent data loss if you forget to check for it.
Cricket analogy: Using map with a dict like {'IND':'India','AUS':'Australia'} recodes a team column value by value; any code not in the dict, like an unlisted associate nation, silently becomes NaN if you forget to check.
import pandas as pd
orders = pd.DataFrame({
'order_id': [101, 102, 103, 104],
'status_code': ['A', 'P', 'S', 'X']
})
status_map = {'A': 'Approved', 'P': 'Pending', 'S': 'Shipped'}
orders['status'] = orders['status_code'].map(status_map)
print(orders)
# order_id status_code status
# 0 101 A Approved
# 1 102 P Pending
# 2 103 S Shipped
# 3 104 X NaN <- 'X' had no match, silently becomes NaNDataFrame.apply: row-wise and column-wise logic
apply is the workhorse for logic that needs to see more than one column at once — computing a value per row from several fields, or reducing each column to a summary statistic. The axis parameter controls direction: axis=1 passes each row to your function as a Series (indexed by column name), while axis=0 (the default) passes each column as a Series. Because apply calls your Python function once per row or column, it is considerably slower than a vectorized NumPy expression on large DataFrames, so it should be a fallback rather than a first instinct.
Cricket analogy: apply with axis=1 passes each scorecard row (batter, runs, balls) to your function to compute strike rate per player, while axis=0 passes each column like the runs column as a whole Series, and it's slower than a vectorized formula on a huge database.
import pandas as pd
sales = pd.DataFrame({
'unit_price': [12.5, 8.0, 25.0],
'quantity': [3, 10, 2],
'discount_pct': [0.0, 0.1, 0.05]
})
def line_total(row):
gross = row['unit_price'] * row['quantity']
return round(gross * (1 - row['discount_pct']), 2)
sales['total'] = sales.apply(line_total, axis=1)
print(sales['total'].tolist())
# [37.5, 72.0, 47.5]
# Equivalent, faster vectorized version:
sales['total_vectorized'] = (sales['unit_price'] * sales['quantity']) * (1 - sales['discount_pct'])applymap / DataFrame.map: element-wise across the whole frame
When you need to apply a scalar function to every single cell of a DataFrame regardless of column, applymap (renamed DataFrame.map in pandas 2.1+, with applymap kept as a deprecated alias) is the tool. A typical use case is formatting — rounding every numeric cell, converting every string to uppercase, or masking values that meet a condition — without caring which column they came from. It is functionally equivalent to calling apply(lambda col: col.map(func)) on every column, but reads more directly for this use case.
Cricket analogy: applymap (now DataFrame.map) rounds every numeric cell in a full scoreboard at once, regardless of whether it's runs, overs, or economy rate, applying the same formatting function uniformly across the whole grid without caring which column it came from.
A useful mnemonic: map works on a Series (one column, one value at a time), apply works on a Series or DataFrame and can see whole rows/columns (one axis at a time), and applymap/DataFrame.map works on an entire DataFrame cell by cell (every value at a time, no cross-column awareness).
Because apply and applymap both fall back to Python-level iteration, avoid them for anything with a native vectorized equivalent (arithmetic, comparisons, string methods via .str, numpy ufuncs). On a DataFrame with a million rows, a NumPy-vectorized calculation can be 50-100x faster than the equivalent row-wise apply.
- Series.map transforms one column using a function, dict, or Series lookup; unmatched keys become NaN.
- DataFrame.apply runs a function along an axis — axis=1 for row-wise logic that spans multiple columns, axis=0 for column-wise reductions.
- applymap (now DataFrame.map in pandas 2.1+) applies a scalar function to every cell in a DataFrame independently of column.
- All three are slower than genuinely vectorized NumPy/pandas operations because they iterate in Python under the hood.
- Prefer vectorized arithmetic, boolean masks, and .str accessor methods whenever a built-in equivalent exists.
- map with a dict is a fast, readable way to recode categorical values without writing a custom function.
Practice what you learned
1. Which method is best suited for recoding a single categorical column using a dictionary of old-value-to-new-value mappings?
2. In `df.apply(func, axis=1)`, what is passed to `func` on each call?
3. What happens when Series.map() encounters a value that has no corresponding key in the mapping dict?
4. Why is applymap generally slower than a vectorized pandas operation on the same DataFrame?
5. As of pandas 2.1, what is the recommended replacement for the deprecated DataFrame.applymap?
Was this page helpful?
You May Also Like
DataFrame Basics
Understand the structure of a pandas DataFrame — rows, columns, index, and dtypes — and the core operations for creating, inspecting, and selecting from tabular data.
Vectorized Operations and Broadcasting
Vectorization applies operations across whole arrays without explicit loops, and broadcasting extends this to arrays of different but compatible shapes.
String Methods in Pandas
Explore the vectorized .str accessor that applies Python string operations — case conversion, splitting, pattern matching, extraction — across entire Series.
Aggregation Functions
Explore the built-in and custom aggregation functions available after groupby, including agg() for applying multiple statistics at once.