100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

apply, map, and applymap

A practical comparison of pandas' three element-wise transformation tools — Series.map, DataFrame.apply, and the deprecated DataFrame.applymap — and when to reach for each.

Data TransformationIntermediate9 min readJul 8, 2026
Analogies

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.

python
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 NaN

DataFrame.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.

python
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

Was this page helpful?

Topics covered

#Python#PandasNumPyStudyNotes#DataScience#ApplyMapAndApplymap#Apply#Map#Applymap#Series#StudyNotes#SkillVeris