Data Analysis for Engineers/Module 2

Module 2 · Section 8 of 12

Lesson 2.7 - GroupBy & Aggregation

Target: ~10 min read - 20 min hands-on

Overview

.groupby() splits a DataFrame into groups (by site, by month), .agg() computes one or more summary statistics per group, and pivot tables reshape grouped results into a wide, report-ready format. This is arguably the single most powerful pandas pattern for engineering reporting.

We also build a synthetic sector demand dataset here - the dataset used in Mini-Project 2.

Why This Matters (Engineering Context)

"Monthly energy by site" or "average demand by sector by year" are exactly the summary tables that appear in engineering and utility reports - GroupBy + pivot produces them in three lines instead of a manual spreadsheet pivot.

Code-Along

# --- GroupBy + agg: summary stats per (site, month) ---

monthly_by_site = (
    energy_df
    # add a "month" column: dt.to_period("M") collapses each date to its
    # year-month (e.g. 2021-03), so every day in the same month shares a value
    .assign(month=energy_df["date"].dt.to_period("M"))
    # split the data into one group per unique (site, month) pair...
    .groupby(["site", "month"])["energy_kwh"]
    # ...and compute three summary statistics of energy_kwh within each group
    .agg(["sum", "mean", "max"])
    # groupby puts "site" and "month" into the index; reset_index() moves them
    # back to ordinary columns so the result is a plain DataFrame
    .reset_index()
)
print(monthly_by_site.head())

# --- Multiple named aggregations at once, one row per sector ---

# The "name=(column, function)" form aggregates different columns with
# different functions in a single call and names each output:
#   total_energy_kwh = sum of energy_kwh
#   avg_ambient_c    = mean of ambient_c
#   n_readings       = count of non-null energy_kwh values (rows that had data)
sector_summary = energy_df.groupby("sector").agg(
    total_energy_kwh=("energy_kwh", "sum"),
    avg_ambient_c=("ambient_c", "mean"),
    n_readings=("energy_kwh", "count"),
)
print("\nSector summary:")
print(sector_summary)

# --- Pivot table: reshape into a site x year grid of average ambient temp ---

# First derive a plain "year" column from the date.
energy_df["year"] = energy_df["date"].dt.year

# pivot_table spreads one variable across rows and another across columns:
#   index="site"    -> one row per site
#   columns="year"  -> one column per year
#   values="ambient_c", aggfunc="mean" -> each cell is the mean ambient_c
#                                         for that site in that year
# .round(1) just trims the displayed precision.
pivot = energy_df.pivot_table(values="ambient_c", index="site", columns="year", aggfunc="mean").round(1)
print("\nAvg ambient temp by site and year:")
pivot
# --- Build a synthetic sector electricity-demand dataset for Mini-Project 2 ---

np.random.seed(7)   # fixed seed -> everyone generates the same numbers

sectors = ["Chemical", "Semiconductor", "Data Center", "Manufacturing", "Logistics"]
years = [2021, 2022, 2023]
months = range(1, 13)

# Starting-point average demand (kW) per sector, before growth / seasonality / noise
sector_base = {"Chemical": 2600, "Semiconductor": 4200, "Data Center": 5200,
               "Manufacturing": 3100, "Logistics": 1400}

demand_rows = []
for sector in sectors:
    base = sector_base[sector]
    for year in years:
        # a steady year-on-year growth factor applied to the base demand
        growth = 1 + 0.04 * (year - 2021)
        for month in months:
            # bump demand in the hot months (Apr-Jun), trim it around year-end
            seasonal = 1.15 if month in (4, 5, 6) else (0.92 if month in (12, 1) else 1.0)
            # base * growth * seasonal, then +/- ~3% random noise
            demand_kw = base * growth * seasonal * (1 + np.random.normal(0, 0.03))
            demand_rows.append((sector, year, month, round(demand_kw, 1)))

# one row per (sector, year, month) = 5 x 3 x 12 = 180 rows
sector_demand = pd.DataFrame(demand_rows, columns=["sector", "year", "month", "avg_demand_kw"])
print(sector_demand.shape)
sector_demand.head()

Practice Exercises

  1. Using sector_demand, find peak (max) monthly demand per sector across all years with a single .groupby().agg() call.
  2. Build a pivot table of avg_demand_kw with sector as rows and year as columns, aggregated by mean.
  3. Which sector shows the largest percentage growth in average demand from 2021 to 2023?
# Try the practice exercises here

Knowledge Check

  1. What does .groupby("sector") do before you call .agg()?
  2. What's the difference between .agg() with multiple functions and a pivot table?
  3. Why build the sector-demand dataset here rather than waiting for Mini-Project 2?
Answer key
  1. Splits the DataFrame into groups sharing the same sector value, ready for per-group aggregation
  2. .agg() returns one row per group with a column per statistic; a pivot table reshapes with one dimension as rows and another as columns
  3. So the dataset and GroupBy skills are practiced together before the mini-project asks you to apply them independently

18 / 63 sections · Course home · Join the coaching cohort