Written and maintained by CASRAI Editorial Board
Last updated
Reader task: you have a pandas DataFrame or Series and need to run your own function over its rows, columns or values, and know which arguments change the output shape. apply in pandas calls a function you supply on each column or row of a DataFrame (DataFrame.apply) or on each element of a Series (Series.apply) and combines the results into a Series or DataFrame.
Command forms below were checked against the pandas 3.1.1 documentation (read 2026-11-07), and the worked example was run on pandas 3.0.5.
What apply is used for in research
Analysts use apply when a derived variable needs logic that no built-in column operation expresses: a score computed from several fields in one record, a recode with custom rules, or a per-column summary function. The documentation describes DataFrame.apply as applying “a function along an axis of the DataFrame”. The objects passed to your function are Series: with axis=0 the function receives each column, with axis=1 each row. For clean-up steps that come before this, see data wrangling and Python for exploratory data analysis.
Install or access (official source only)
The pandas installation page lists these official routes: pip install pandas, or conda install -c conda-forge pandas. To create an isolated conda environment: conda create -c conda-forge -n name_of_my_env python pandas, then conda activate name_of_my_env. The page recommends working in a virtual environment and notes that pandas distributed through the Anaconda channel is not officially managed by the pandas development team. Source: pandas.pydata.org/docs/getting_started/install.html.
Step-by-step: apply a function in pandas
The data below are a small illustrative table (three made-up participants with pre and post scores), not real study data.
-
Build the data.
import pandas as pd import numpy as np df = pd.DataFrame({"participant": ["P1", "P2", "P3"], "pre": [12, 15, 9], "post": [18, 14, 13]}) -
Apply a function to each row (axis=1). The function receives one row as a Series, so you index it by column name.
df.apply(lambda r: r["post"] - r["pre"], axis=1)Output values: 6, -1, 4.
-
Apply a function to each column (axis=0, the default).
df[["pre", "post"]].apply(lambda c: c.max() - c.min())Output: pre 6, post 5 (one value per column).
-
Return several values per row. Return a Series from the function and the result becomes a DataFrame whose columns are the Series index.
def summarise(r): return pd.Series({"change": r["post"] - r["pre"], "improved": r["post"] > r["pre"]}) df.apply(summarise, axis=1)Output: columns
change(6, -1, 4) andimproved(True, False, True). -
Pass extra arguments. Positional arguments go in
args=(...); keyword arguments are passed straight through.def pct(r, base): return round(100 * (r["post"] - r["pre"]) / base, 1) df.apply(pct, axis=1, base=10)Output values: 60.0, -10.0, 40.0.
-
Apply to one column (Series.apply). This is the usual form for if-else recodes with a lambda.
df["pre"].apply(lambda x: "high" if x >= 12 else "low")Output: high, high, low. Per the documentation,
Series.applypasses each element to a callablefunc, asSeries.mapdoes.
Apply with a lambda over multiple columns
Use axis=1 and read the columns you need from the row, as in step 2. Select a subset of columns first (df[["pre", "post"]].apply(...)) when you only want those passed in.
Signature and arguments
DataFrame.apply(func, axis=0, raw=False, result_type=None, args=(), by_row='compat', engine=None, engine_kwargs=None, **kwargs). Series.apply(func, args=(), *, by_row='compat', **kwargs).
axis: 0 or ‘index’ applies the function to each column; 1 or ‘columns’ to each row. In the function, the Series.nameis the column label (axis=0) or row label (axis=1).raw: False passes Series; True passes ndarrays. The docs say that if you are only applying a NumPy reduction function, True “will achieve much better performance”.argsand**kwargs: extra positional and keyword arguments forfunc.by_row(added in pandas 2.1.0): only matters whenfuncis list-like or dict-like of functions. ForSeries.apply, “compat” passes each element to a callable; False passes the whole Series.engine(added in 2.2.0): ‘python’, ‘numba’ or a JIT decorator. The docs say JIT engines are only recommended for functions that take a significant amount of time.
Note: convert_dtype does not appear in the current Series.apply signature; older tutorials that use it were written for earlier versions.
Output and how to read it
The return value is a Series or a DataFrame. result_type (DataFrame.apply, for list-like returns) decides the shape. The outputs below are the ones in the official examples, for df = pd.DataFrame([[4, 9]] * 3, columns=["A", "B"]):
| result_type | Documented meaning | Official example result |
|---|---|---|
None (default) |
List-likes are returned as a Series; a returned Series is expanded to columns | df.apply(lambda x: [1, 2], axis=1) gives a Series of [1, 2] objects (dtype object) |
'expand' |
List-like results become columns | Same call with result_type="expand" gives a DataFrame with columns 0 and 1, values 1 and 2 |
'reduce' |
Returns a Series if possible rather than expanding list-likes | The opposite of expand |
'broadcast' |
Results keep the original shape, index and columns | Columns A and B with values 1 and 2 |
When the function returns a Series with a labelled index, the result columns take those labels (the documentation example gives columns foo and bar). result_type has no effect when func is a NumPy ufunc such as np.sqrt; wrap it in a lambda to use it.
Troubleshooting
Unexpected results or errors when the function changes its input
The pandas user guide (“Mutating with User Defined Function (UDF) methods”, which covers apply, aggregate, transform and filter) warns that mutating the object being iterated gives unexpected behavior. The documented fix is to copy first inside the function (s = s.copy() before an operation such as s.pop("a")). The DataFrame.apply notes also say functions that mutate the passed object are not supported.
Lists inside cells
The same guide notes that lists stored in cells produce object dtype and no vectorised code paths.
Wrong axis
If a row-wise formula fails with a key error or returns one value per column, check axis: the default 0 passes columns, not rows.
Exceptions inside the function
Put a try/except inside your own function and return a missing value on failure so one bad record does not stop the whole call. Converting strings to numbers is a separate job from apply and belongs to pd.to_numeric; see the pandas documentation for its errors argument.
Parallel apply
The speed-related options documented in DataFrame.apply itself are raw=True and the engine argument. This page makes no benchmark claims.
Free alternatives within pandas and NumPy
| Option | What the documentation says |
|---|---|
Pass a NumPy ufunc directly, e.g. df.apply(np.sqrt) |
Official example: applies the function to every cell (4 becomes 2.0, 9 becomes 3.0) |
Series.map |
Listed in the Series.apply “See also” as the element-wise counterpart |
Series.agg and Series.transform |
Listed in the Series.apply “See also” as related methods |
raw=True with a NumPy reduction |
Documented as giving much better performance for that case |
Related CASRAI how-tos: how to drop columns in pandas and dropna in pandas.
FAQ
What does apply do in pandas?
It runs a function you supply along an axis of a DataFrame (each column or each row) or over each element of a Series, and returns the combined result as a Series or DataFrame.
What is the difference between axis=0 and axis=1 in apply?
axis=0 (default) applies the function to each column; axis=1 applies it to each row.
How do I use apply with lambda and if-else?
Write the conditional expression inside the lambda, for example df["pre"].apply(lambda x: "high" if x >= 12 else "low").
How do I return multiple columns from apply?
Return a pd.Series with a labelled index from the function and use axis=1, or set result_type="expand" for list-like returns.
Is there a top-level pandas.apply?
No; apply is a method on DataFrame and Series, so call df.apply(...) or s.apply(...).







