Applying a custom function or a built-in function to every single cell (element) in a Pandas DataFrame is a common requirement for data transformation, cleaning, or feature engineering. Pandas provides methods to perform these element-wise operations efficiently.
This guide explains how to use DataFrame.map() (the successor to the deprecated DataFrame.applymap()) and alternatives like DataFrame.apply() with numpy.vectorize() to apply a function to each cell in a DataFrame.
The Goal: Element-wise Operations on a DataFrame
We want to take a function that operates on a single scalar value and apply it individually to every element within a Pandas DataFrame, producing a new DataFrame of the same shape with the transformed values.
Example DataFrame
import pandas as pd
import numpy as np # For np.nan and np.vectorize
import math # For math.sqrt
data = {
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.5, 2.5, 4.5, 6.5, 8.5],
'C': [0.0, 1.0, -2.0, 3.0, np.nan]
}
df_original = pd.DataFrame(data)
print("Original DataFrame:")
print(df_original)Output:
Original DataFrame:
A B C
0 1.0 0.5 0.0
1 4.0 2.5 1.0
2 9.0 4.5 -2.0
3 NaN 6.5 3.0
4 16.0 8.5 NaNMethod 1: Using DataFrame.map() (Recommended - Replaces applymap)
As of Pandas version 2.1.0, DataFrame.applymap() has been deprecated and renamed to DataFrame.map(). The DataFrame.map(func, na_action=None) method applies a function func that accepts and returns a scalar to every element of the DataFrame.
Applying a Built-in Function (e.g., math.sqrt)
The function passed to map() must work on individual scalar values. math.sqrt is an example.
import pandas as pd
import numpy as np
import math
df = pd.DataFrame({
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.25, 2.25, 4.0, 6.25, 9.0], # Values suitable for sqrt
'C': [0.0, 1.0, 1.0, np.nan, 4.0] # Added another NaN for C
})
try:
df_sqrt = df.map(math.sqrt) # This will error if a cell is negative or unhandled NaN
print("DataFrame after df.map(math.sqrt) - if no errors:")
print(df_sqrt)
print()
except TypeError as e: # math.sqrt can not handle NaN directly
print(f"Error with df.map(math.sqrt) due to NaN/negative: {e}")
print("Using na_action='ignore' or pre-filtering/filling NaNs is needed for math.sqrt on NaNs.")
print()
def custom_scalar_sqrt(x):
if pd.isna(x) or x < 0:
return np.nan # Handle NaN and negative numbers
return math.sqrt(x)
df_custom_sqrt = df.map(custom_scalar_sqrt)
print("DataFrame after df.map(custom_scalar_sqrt):")
print(df_custom_sqrt)Output:
DataFrame after df.map(math.sqrt) - if no errors:
A B C
0 1.0 0.5 0.0
1 2.0 1.5 1.0
2 3.0 2.0 1.0
3 NaN 2.5 NaN
4 4.0 3.0 2.0
DataFrame after df.map(custom_scalar_sqrt):
A B C
0 1.0 0.5 0.0
1 2.0 1.5 1.0
2 3.0 2.0 1.0
3 NaN 2.5 NaN
4 4.0 3.0 2.0Applying a Custom Function
You can pass any Python function (including a lambda function) that takes a single scalar argument and returns a single scalar value.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.5, 2.5, 4.5, 6.5, 8.5],
'C': [0.0, 1.0, -2.0, 3.0, np.nan]
})
def transform_value(x):
if pd.isna(x):
return x # Keep NaN as is
if x > 5:
return x * 10
elif x < 1:
return x / 2
else:
return x # Keep values between 1 and 5 (inclusive) as is
df_transformed = df.map(transform_value)
print("DataFrame after df.map(custom_function):")
print(df_transformed)Output:
DataFrame after df.map(custom_function):
A B C
0 1.0 0.25 0.0
1 4.0 2.50 1.0
2 90.0 4.50 -1.0
3 NaN 65.00 3.0
4 160.0 85.00 NaNHandling NaN Values with na_action
The na_action parameter in df.map() controls how NaN values are handled:
None(default):NaNvalues are passed to the functionfunc. Iffunccan not handleNaN(likemath.sqrt), it might raise an error.'ignore':NaNvalues are propagated. The functionfuncis not called forNaNcells; they remainNaNin the output.
import pandas as pd
import numpy as np
import math
df_with_nan = pd.DataFrame({
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.25, 2.25, np.nan, 6.25, 9.0],
'C': [0.0, 1.0, 1.0, np.nan, 4.0]
})
df_sqrt_ignore_nan = df_with_nan.map(lambda x: math.sqrt(x) if x >= 0 else np.nan, na_action='ignore')
print("DataFrame after df.map(lambda_sqrt, na_action='ignore'):")
print(df_sqrt_ignore_nan)Output:
DataFrame after df.map(lambda_sqrt, na_action='ignore'):
A B C
0 1.0 0.5 0.0
1 2.0 1.5 1.0
2 3.0 NaN 1.0
3 NaN 2.5 NaN
4 4.0 3.0 2.0Method 2: Using DataFrame.apply() with numpy.vectorize()
While DataFrame.map() is preferred for element-wise operations on the whole DataFrame, you can also achieve this using DataFrame.apply() in conjunction with numpy.vectorize(). np.vectorize is primarily a convenience for functions not designed to work on arrays.
How It Works
numpy.vectorize(pyfunc): Takes a Python functionpyfuncthat works on scalars and returns a new "vectorized" function that can operate on NumPy arrays (element-wise).DataFrame.apply(vectorized_func): Applies the vectorized function. Whenapplyis used on a DataFrame withoutaxisspecified oraxis=0(default), it applies the function to each column (Series). If the vectorized function is then applied to a Series, it works element-wise. Ifaxis=1, it applies to rows. For element-wise on the whole DataFrame, you usually apply it to the result of selecting all data (df[:]).
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.5, 2.5, 4.5, 6.5, 8.5],
'C': [0.0, 1.0, -2.0, 3.0, np.nan]
})
def transform_value_np(x): # Same custom function as before
if pd.isna(x): return x
if x > 5: return x * 10
elif x < 1: return x / 2
else: return x
vectorized_transform = np.vectorize(transform_value_np)
df_transformed_np_apply = df.apply(vectorized_transform)
print("DataFrame after df.apply(np.vectorize(custom_function)):")
print(df_transformed_np_apply)Output: (Same as df.map(transform_value))
DataFrame after df.apply(np.vectorize(custom_function)):
A B C
0 1.0 0.25 0.0
1 4.0 2.50 1.0
2 90.0 4.50 -1.0
3 NaN 65.00 3.0
4 160.0 85.00 NaNDirect Application of Vectorized Function
You can also apply the vectorized function directly to the DataFrame (or its underlying NumPy array).
import pandas as pd
import numpy as np
def transform_value_np(x): # Same custom function as before
if pd.isna(x): return x
if x > 5: return x * 10
elif x < 1: return x / 2
else: return x
df_copy = pd.DataFrame({
'A': [1.0, 4.0, 9.0, np.nan, 16.0],
'B': [0.5, 2.5, 4.5, 6.5, 8.5],
'C': [0.0, 1.0, -2.0, 3.0, np.nan]
})
vectorized_transform = np.vectorize(transform_value_np)
df_copy[:] = vectorized_transform(df_copy)
print("DataFrame after vectorized_function(df) and reassignment:")
print(df_copy)Output: (Same as df.map(transform_value))
DataFrame after vectorized_function(df) and reassignment:
A B C
0 1.0 0.25 0.0
1 4.0 2.50 1.0
2 90.0 4.50 -1.0
3 NaN 65.00 3.0
4 160.0 85.00 NaNImportant Note: DataFrame.applymap() is Deprecated
If you see older code or tutorials using DataFrame.applymap(func), be aware that this method was deprecated in Pandas 2.1.0 and replaced by DataFrame.map(func). They serve the same purpose: applying a scalar function element-wise to a DataFrame. You should update your code to use DataFrame.map().
Choosing the Right Method
DataFrame.map(func, na_action=...): This is the recommended and modern method for applying a scalar function to every element of a DataFrame. It's clear, concise, and provides control overNaNhandling.DataFrame.apply(np.vectorize(func)): A viable alternative, especially if you already have a scalar function and want to make it work element-wise on a DataFrame (or its columns/rows). However,df.map()is generally more direct for this specific task.- Native Pandas/NumPy vectorized operations: If your transformation can be expressed directly using arithmetic operators (
+,-,*,/) or NumPy universal functions (np.sqrt,np.log,np.sin, etc.) on the DataFrame or specific columns, this is usually the most performant approach (e.g.,df * 2,np.sqrt(df)).df.map()is for more arbitrary scalar functions.
Conclusion
To apply a function to each cell (element-wise) in a Pandas DataFrame:
- Use
DataFrame.map(your_function). This is the current standard, replacing the deprecatedapplymap(). Ensureyour_functiontakes a single scalar value and returns a single scalar value. Use thena_actionparameter to control howNaNs are treated. - As an alternative,
DataFrame.apply(numpy.vectorize(your_scalar_function))can be used, butDataFrame.map()is generally more idiomatic for this purpose.
For optimal performance, always try to use built-in Pandas/NumPy vectorized operations if your transformation allows. If you need to apply a custom scalar function, DataFrame.map() is your primary tool.