Split-Apply-Combine in Action

Learn split-apply-combine workflows in action with this hands-on Data Analysis with Python tutorial — step-by-step guidance, troubleshooting, and next steps.

Focus: split-apply-combine workflows in action

Sponsored

You've cleaned your data, grouped it, and applied functions — but every time you need to answer a question like "what's the average sales per region?" or "which product category has the highest variance?", you find yourself writing the same repetitive loop. The pain is real: split-apply-combine workflows promise to collapse this boilerplate into a single, elegant chain, yet most tutorials only show toy examples. In this lesson, you'll move from understanding the pattern to applying it in action, using real-world-style data, and you'll discover how to debug and optimize these workflows so they become your default data-analysis tool.

The problem this lesson solves

Data analysis is rarely about running one operation on a whole dataset. More often, you need to break your data into meaningful groups, perform a computation on each group, and then combine the results back into a structured output. Without a systematic approach, you end up with:

  • Repetitive loops that are slow and hard to read.
  • Error-prone manual aggregation when you forget to handle a group or an edge case.
  • Inconsistent results when you try to combine multiple group-level calculations by hand.

Consider this common scenario: you have a DataFrame of sales transactions across stores and products, and you need to compute the average revenue per store, the total units sold per product, and the standard deviation of daily sales for each region. Doing this with for loops and manually constructing new DataFrames is not only tedious — it's a breeding ground for bugs.

This lesson gives you a structured, composable pattern — split-apply-combine — so you can express these operations declaratively and confidently, even on datasets with thousands of groups.

Core concept / mental model

Split-apply-combine is a data analysis pattern popularized by Hadley Wickham (in R) and deeply integrated into pandas. Think of it as a three-stage pipeline:

  1. Split: Divide your data into groups based on one or more keys (e.g., a column like Region or Product).
  2. Apply: Run a function or transformation on each group independently. This could be an aggregation (mean, sum), a transformation (z-score), or a filtration (keep groups that meet a condition).
  3. Combine: Merge the group-wise results into a single DataFrame or Series.

A useful analogy: imagine you're a teacher with a stack of exam papers. You split the papers by class, apply the same grading rubric to each class's papers, and then combine the class averages into a single report. The rubric is consistent; only the data changes.

In pandas, this pattern is most often realized through the groupby() method. When you call df.groupby('key'), pandas creates a lazy object — no computation happens yet. Only when you apply an aggregation or transformation does the magic occur. This laziness is a feature: it lets pandas optimize the operation and lets you chain multiple steps without computing intermediate results.

How it works step by step

Here's the mental sequence you'll follow every time you use split-apply-combine:

  1. Identify the grouping key(s) — the column(s) that define your groups. Usually categorical: Region, Product, Year, etc.
  2. Decide the operation type: - Aggregation: Reduces each group to a single value (e.g., mean, sum, max). - Transformation: Returns a result with the same shape as each group (e.g., z-score, fillna with group mean). - Filtration: Drops entire groups based on a condition (e.g., keep only groups with more than 10 observations).
  3. Write the chain: df.groupby('key').agg(...) or df.groupby('key').transform(...) or df.groupby('key').filter(...).
  4. Combine implicitly — pandas automatically aligns and concatenates results into a clean output.

Because groupby is lazy, you can also chain further operations after the aggregation, such as sorting or renaming columns. This makes your code both readable and efficient.

Hands-on walkthrough

Let's put split-apply-combine to work with a realistic dataset. We'll use a sample of sales transactions — you can easily adapt this to your own data.

Step 1: Setup and data

First, create a sample DataFrame. (In a real scenario, you'd load data from a CSV or database.)

import pandas as pd
import numpy as np

# Reproducible sample data
df = pd.DataFrame({
    'Region': ['North', 'North', 'South', 'South', 'East', 'East', 'West', 'West', 'North', 'South'],
    'Product': ['A', 'B', 'A', 'C', 'B', 'A', 'C', 'B', 'A', 'B'],
    'Units':   [10, 14, 12, 8, 9, 15, 7, 11, 13, 6],
    'Price':   [100, 150, 120, 80, 140, 110, 90, 130, 105, 75]
})

print(df.head())

Output:

  Region Product  Units  Price
0  North       A     10    100
1  North       B     14    150
2  South       A     12    120
3  South       C      8     80
4  East        B      9    140

Step 2: Basic aggregation with groupby

Now compute average price per region:

# Aggregation: average price per region
avg_price_by_region = df.groupby('Region')['Price'].mean()
print(avg_price_by_region)

Output:

Region
East     125.0
North    118.333333
South     91.666667
West     110.0
Name: Price, dtype: float64

Notice the result is a Series indexed by Region. But often you want a DataFrame with multiple aggregates at once. Use .agg() with a list or dict:

# Multiple aggregations per group
summary = df.groupby('Region').agg(
    total_units=('Units', 'sum'),
    avg_price=('Price', 'mean')
)
print(summary)

Output:

        total_units  avg_price
Region                       
East             24     125.0
North            37     118.333333
South            26      91.666667
West             18     110.0

Step 3: Transformation to keep row-level detail

Aggregation collapses groups. What if you need to add a column with the group mean, keeping every row? Use transform:

# Add column with mean price per region (same length as df)
df['region_avg_price'] = df.groupby('Region')['Price'].transform('mean')
print(df.head())

Output:

  Region Product  Units  Price  region_avg_price
0  North       A     10    100        118.333333
1  North       B     14    150        118.333333
2  South       A     12    120         91.666667
3  South       C      8     80         91.666667
4  East        B      9    140        125.000000

Step 4: Filtration to remove entire groups

Sometimes you only want groups that meet a threshold. For example, keep only regions with total units above 20:

# Filter groups: keep regions with total units > 20
filtered = df.groupby('Region').filter(lambda x: x['Units'].sum() > 20)
print(filtered)

Output (rows from East, North, South — West is dropped):

  Region Product  Units  Price  region_avg_price
0  North       A     10    100        118.333333
1  North       B     14    150        118.333333
2  South       A     12    120         91.666667
3  South       C      8     80         91.666667
4  East        B      9    140        125.000000
6  East        A     15    110        125.000000

Pro tip: groupby.filter() accepts a function that returns a boolean, and it works on the group DataFrame, not just a single column. Use it to filter based on any group-level statistic.

Step 5: Chaining for a complete workflow

Now combine everything into a single, clean pipeline — from raw data to a final report:

# Full split-apply-combine workflow
result = (
    df.assign(Revenue=df['Units'] * df['Price'])   # compute revenue
      .groupby(['Region', 'Product'])             # split by region and product
      .agg(total_revenue=('Revenue', 'sum'),      # apply multiple aggregations
           avg_price=('Price', 'mean'),
           transaction_count=('Revenue', 'size'))
      .reset_index()                               # combine into a flat table
)
print(result)

Output:

  Region Product  total_revenue  avg_price  transaction_count
0   East       A           1650      110.0                  1
1   East       B           1260      140.0                  1
2  North       A           2050      102.5                  2
3  North       B           2100      150.0                  1
4  South       A           1440      120.0                  1
5  South       B            450       75.0                  1
6  South       C            640       80.0                  1
7   West       B           1430      130.0                  1
8   West       C            630       90.0                  1

This example demonstrates the full power of split-apply-combine: you split by two keys, apply multiple aggregations, and combine into a tidy DataFrame with a single chain.

Compare options / when to choose what

Not every group-wise operation is best done with groupby. Here's a comparison of common approaches in pandas:

Approach Best for Pros Cons
groupby().agg() Aggregation to a summary Concise, powerful, flexible with multiple functions Can be verbose with many columns
groupby().transform() Add group-level statistics to rows Preserves row count, easy to use in feature engineering Limited to transformations that return same shape
groupby().filter() Drop whole groups Intuitive for group-level conditioons Function can be slow on huge data
pd.pivot_table() Cross-tabulation with multiple indices/columns Great for reporting, handles multiple aggfuncs Less flexible for custom functions
Manual for loops When you need full control Fine for tiny data Slow, error-prone, not idiomatic

When to choose what:

  • Use groupby().agg() for most summary statistics — it's the workhorse.
  • Use transform when you need to keep the original rows, like adding a group mean for normalization.
  • Use filter when you want to exclude groups entirely, not rows.
  • Use pivot_table when you need a matrix-like view (e.g., regions as rows, products as columns).
  • Avoid loops unless you absolutely must — pandas' vectorized operations are much faster and cleaner.

Troubleshooting & edge cases

1. KeyError on groupby column — Make sure the column name exists exactly (case-sensitive). Debug with df.columns.tolist().

2. Unexpected aggregation results — If a group produces NaN, you likely have missing values in the aggregating column. Use skipna=False in .agg() to see them, or handle missing values before grouping. For example:

# Handle missing values before aggregation
df_clean = df.dropna(subset=['Price'])
df.groupby('Region')['Price'].mean()  # now works reliably

3. transform returns a different length — This happens when the grouping key is not unique or you accidentally selected multiple columns. transform must return the same number of rows as the input group; double-check your function.

4. filter drops all rows — Verify your condition. For instance, lambda x: x['Units'].sum() > 20 will remove any group whose total units is 20 or less. Print group sums first to debug:

print(df.groupby('Region')['Units'].sum())

5. Performance issues on large data — If your dataset is huge (millions of rows), avoid Python-level functions in .agg(). Use built-in pandas functions (e.g., 'mean' instead of lambda x: np.mean(x)). For very heavy transformations, consider using numba or splitting the data manually.

Pro tip: Always inspect the intermediate result after each step — use .head() or print the group keys with df.groupby('Region').groups to ensure your split is correct.

What you learned & what's next

In this lesson, you've mastered split-apply-combine workflows in action — the core pattern for group-wise data analysis. You can now:

  • Explain the split-apply-combine pattern and its three stages.
  • Use groupby().agg() for efficient summarization.
  • Apply transform() to add group-level statistics while preserving rows.
  • Filter entire groups with filter().
  • Chain operations into a single, readable pipeline.
  • Avoid common pitfalls like missing values and performance bottlenecks.

You've also seen how this pattern integrates with the rest of your data analysis toolkit — cleaning, transforming, and now aggregating. As you move forward, you'll combine these workflows with more advanced features like custom aggregation functions and multi-level grouping, and you'll learn to visualize group-level insights using pandas built-in plots. Mastering split-apply-combine today gives you the foundation for efficient, reproducible analyses that scale with your data.

Next step: Check the next lesson in the track — you'll build on these workflows to tackle more complex data manipulation and visualization tasks.

Practice recap

Try this on a dataset of your own: pick a categorical column and a numeric column, then write a single chain that groups by the category and computes the mean, median, and count of the numeric column. Then use transform to add the group mean as a new column, and finally, use filter to keep only groups with more than 5 observations. Practice with different grouping keys and multiple aggregations to build confidence.

Common mistakes

  • Using groupby().transform() on multiple columns without specifying the column — the result may be a DataFrame, causing alignment issues.
  • Forgetting that .agg() drops the grouping column by default; use reset_index() or as_index=False to keep it as a column.
  • Applying a custom Python function inside .agg() on large data, which slows down performance dramatically — prefer built-in pandas aggregations.
  • Using filter() without understanding that it evaluates the function on the entire group DataFrame, not just one column, leading to unexpected group drops.

Variations

  1. Use pd.pivot_table instead of groupby().agg() when you need a cross-tabulated view with rows as groups and columns as another key.
  2. For very large datasets, consider using pandas with dask or modin to apply split-apply-combine in parallel.

Real-world use cases

  • E-commerce: Compute monthly revenue and average order value per product category to identify top performers and guide inventory.
  • Finance: Calculate daily portfolio volatility (standard deviation) per asset class and filter out classes with too few trading days.
  • Healthcare: Aggregate patient readmission rates by hospital and region, then standardize (z-score) within each hospital for fair comparison.

Key takeaways

  • Split-apply-combine is a three-stage pattern: split by group keys, apply a function per group, and combine results into a structured output.
  • Use groupby().agg() for group-level summaries, transform() for row-level additions, and filter() to exclude entire groups.
  • Chaining groupby with other pandas methods produces clean, readable, and efficient data pipelines.
  • Always handle missing values before grouping to avoid NaN aggregation results.
  • Prefer built-in pandas aggregation functions over custom Python lambdas for better performance on large data.
  • Mastering split-apply-combine is a foundational skill for advanced data manipulation and visualization.

Sponsored

Sponsored