0Pricing
Pandas & NumPy Academy · Lesson

apply() on Columns and Rows

Use DataFrame.apply() with axis=0 and axis=1 to run a custom function over each column or each row.

apply() on Columns and Rows is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

When to Use apply()

DataFrame.apply() runs a custom Python function over each column (axis=0) or each row (axis=1). It is the tool of last resort — slower than built-in vectorised operations — but essential when a computation cannot be expressed with NumPy arithmetic, boolean indexing, or built-in aggregation functions. Use it when you need complex conditional logic, custom string parsing, or multi-step calculations that operate on a single row or column at a time.

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'price': [10.5, 25.0, 8.3, 100.0],
    'quantity': [3, 1, 5, 2],
    'tax_rate': [0.05, 0.10, 0.05, 0.15]
})
print(df)

apply() Along Columns (axis=0)

With axis=0 (the default), apply() passes each column as a Series to the function. The function receives one column at a time and should return a scalar (for aggregation) or a Series (for transformation). This is useful for applying a custom normalisation or cleaning step uniformly to all numeric columns in one call.

# Custom range normalisation (0 to 1) for each column
def min_max_scale(col):
    return (col - col.min()) / (col.max() - col.min())

df_scaled = df[['price', 'quantity']].apply(min_max_scale, axis=0)
print(df_scaled)

apply() Along Rows (axis=1)

With axis=1, apply() passes each row as a Series to the function. The row's index is the column name, so you can access values by name. This is ideal for row-level calculations that depend on multiple columns, such as computing total revenue after tax where each row has its own tax rate.

def revenue_after_tax(row):
    subtotal = row['price'] * row['quantity']
    return subtotal * (1 + row['tax_rate'])

df['revenue_after_tax'] = df.apply(revenue_after_tax, axis=1)
print(df)

Returning Multiple Values with apply()

A function passed to apply(axis=1) can return a pd.Series with named entries, which Pandas expands into multiple new columns. This is cleaner than calling apply() twice for two new columns. Alternatively, the function can return a dict, which Pandas also expands into columns.

def compute_totals(row):
    subtotal = row['price'] * row['quantity']
    tax = subtotal * row['tax_rate']
    return pd.Series({'subtotal': subtotal, 'tax': tax, 'total': subtotal + tax})

result = df.apply(compute_totals, axis=1)
print(result)

apply() for Custom Aggregation on Columns

Use apply(func, axis=0) to compute a custom statistic for each column and return a summary Series. For example, compute the coefficient of variation (standard deviation divided by mean) for every numeric column in one call. The result is a Series indexed by column name, which is useful for quick per-column diagnostics.

def coeff_of_variation(col):
    return col.std() / col.mean() if col.mean() != 0 else np.nan

cv = df[['price', 'quantity']].apply(coeff_of_variation, axis=0)
print('Coefficient of variation per column:')
print(cv)

Comparing apply() to Vectorised Alternatives

Before using apply(), always check whether a vectorised alternative exists. For the revenue-after-tax calculation, df['price'] * df['quantity'] * (1 + df['tax_rate']) does the same thing as the apply(axis=1) version but runs 10–100x faster because it uses NumPy C-level loops. Reserve apply() for cases where vectorised logic would be unreadably complex.

import time

start = time.time()
df['vectorised'] = df['price'] * df['quantity'] * (1 + df['tax_rate'])
print('Vectorised:', time.time() - start, 's')

start = time.time()
df['apply_result'] = df.apply(revenue_after_tax, axis=1)
print('apply():', time.time() - start, 's')

apply() with a Lambda

For short one-line transformations, pass a lambda directly to apply() instead of defining a named function. Lambdas are concise for simple operations but should be avoided for complex logic where a named function with comments is easier to understand and test. Use lambdas sparingly — they cannot be easily unit-tested or profiled by name.

# Clip revenue between 0 and 200, then round to nearest 10
df['revenue_clean'] = df['revenue_after_tax'].apply(lambda x: round(min(max(x, 0), 200) / 10) * 10)
print(df[['revenue_after_tax', 'revenue_clean']])

Passing Extra Arguments to apply()

Pass additional arguments to your function using the args tuple or keyword arguments in apply(func, args=(val,), axis=1). This avoids using global variables inside the function and makes the function reusable with different parameter values. In Python 3.8+, you can also use functools.partial to create a partially applied version of the function.

def discount_price(row, discount_pct, threshold):
    if row['quantity'] >= threshold:
        return row['price'] * (1 - discount_pct)
    return row['price']

df['discounted_price'] = df.apply(
    discount_price, axis=1,
    args=(0.1, 3)  # 10% discount for quantity >= 3
)
print(df[['price', 'quantity', 'discounted_price']])

apply() for Type Conversion

When a column contains mixed types — some integers, some floats, some strings — astype() raises an error. Use apply(pd.to_numeric, errors='coerce') to attempt conversion row by row and return NaN for values that cannot be converted. This is a safe pattern for columns that should be numeric but have occasional non-numeric entries from data entry errors.

messy = pd.Series(['10', '20.5', 'N/A', '100', '--'])
clean = messy.apply(pd.to_numeric, errors='coerce')
print(clean)

Performance: apply() vs np.vectorize

When a function that applies to scalars must be applied element-wise, np.vectorize(func)(array) is typically faster than Series.apply(func) because NumPy's vectorize dispatches through C rather than Python's function call overhead. However, np.vectorize is still slower than true broadcasting — it is only useful when no broadcasting expression captures the logic.

def classify_size(price):
    if price < 15:
        return 'small'
    elif price < 50:
        return 'medium'
    return 'large'

# np.vectorize approach
classify_v = np.vectorize(classify_size)
df['size_class'] = classify_v(df['price'].values)
print(df[['price', 'size_class']])

When apply() Is the Right Choice

Apply is the right tool when: (1) the logic requires branching on multiple column values simultaneously; (2) the function calls an external API or database per row (unavoidably sequential); (3) the function parses a complex string like a JSON blob stored in a cell; or (4) the function returns a variable-length object like a list. In all other cases, prefer vectorised operations for speed and readability.

import json

# Parse a JSON metadata column row by row
df['metadata'] = ['{"color": "red"}', '{"color": "blue"}', '{}', '{"color": "green"}']
df['color'] = df['metadata'].apply(lambda s: json.loads(s).get('color', 'unknown'))
print(df[['metadata', 'color']])

Quick Check

Test your understanding of Data Analysis concepts from this lesson.

Lesson Recap

In this lesson you learned: using apply(axis=0) to compute column-level custom statistics, using apply(axis=1) to compute row-level calculations with multiple column inputs, and recognising when to prefer vectorised alternatives for performance. Next up we explore apply() with GroupBy for complex group-level aggregations.

Frequently asked questions

Is the “apply() on Columns and Rows” lesson free?

Yes — the full text of “apply() on Columns and Rows” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.

What will I learn in “apply() on Columns and Rows”?

Use DataFrame.apply() with axis=0 and axis=1 to run a custom function over each column or each row. You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Pandas & NumPy Academy?

No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “apply() on Columns and Rows” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Pandas & NumPy Academy lesson?

Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. apply() on Columns and Rows
  2. apply() with GroupBy
  3. map() and applymap() for Element-Wise Operations
  4. Method Chaining with pipe()
← Back to Pandas & NumPy Academy