0Pricing
R Academy · Lesson

Grouped Summaries and group_by()

Aggregate data by groups and compute per-group statistics.

Grouped Summaries and group_by() is a free R 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 R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Group Data?

Grouping data is one of the most powerful operations in data analysis. Instead of computing statistics over an entire dataset, you compute them within each group. The dplyr package makes this intuitive with group_by() followed by summarize().

library(dplyr)

# Sample sales data
sales <- data.frame(
  region = c('North','North','South','South','East'),
  revenue = c(120, 95, 200, 175, 88),
  units = c(10, 8, 15, 12, 7)
)

print(sales)

group_by() Basics

group_by(col) marks a data frame so that subsequent operations happen per group. It does not change the data itself — it adds grouping metadata. You can group by one or multiple columns.

library(dplyr)

sales <- data.frame(
  region = c('North','North','South','South','East'),
  revenue = c(120, 95, 200, 175, 88),
  units = c(10, 8, 15, 12, 7)
)

# Group by region
grouped <- sales %>% group_by(region)
print(grouped)
print(group_keys(grouped))

summarize() After group_by()

summarize() (or summarise()) collapses each group into a single row. You specify new column names and the summary functions to apply. Common functions: n(), mean(), sum(), min(), max().

library(dplyr)

sales <- data.frame(
  region = c('North','North','South','South','East'),
  revenue = c(120, 95, 200, 175, 88),
  units = c(10, 8, 15, 12, 7)
)

sales %>%
  group_by(region) %>%
  summarize(
    n = n(),
    mean_revenue = mean(revenue),
    total_units = sum(units)
  )

Counting with n()

n() inside summarize() returns the number of rows in each group. It requires no arguments. For counting rows matching a condition, use sum(condition). n_distinct(col) counts unique values.

library(dplyr)

employees <- data.frame(
  dept = c('HR','HR','Eng','Eng','Eng','Sales'),
  salary = c(55000, 60000, 95000, 105000, 90000, 70000),
  senior = c(FALSE, TRUE, TRUE, TRUE, FALSE, FALSE)
)

employees %>%
  group_by(dept) %>%
  summarize(
    count = n(),
    senior_count = sum(senior),
    avg_salary = mean(salary)
  )

Grouping by Multiple Columns

You can pass multiple columns to group_by(). The groups are formed by every combination of values present in the data. This is useful for cross-tabulation and hierarchical summaries.

library(dplyr)

orders <- data.frame(
  region = c('North','North','South','South','North','South'),
  product = c('A','B','A','B','A','A'),
  sales = c(100, 200, 150, 250, 120, 180)
)

orders %>%
  group_by(region, product) %>%
  summarize(
    total_sales = sum(sales),
    orders = n(),
    .groups = 'drop'
  )

The .groups Argument

After summarize(), the result is still grouped by all but the last grouping variable. The .groups argument controls this:

  • 'drop_last' — default, drops last group level
  • 'drop' — removes all grouping
  • 'keep' — retains all grouping
library(dplyr)

df <- data.frame(
  year = c(2022,2022,2023,2023),
  quarter = c('Q1','Q2','Q1','Q2'),
  revenue = c(100, 120, 130, 150)
)

# .groups = 'drop' ensures ungrouped result
result <- df %>%
  group_by(year, quarter) %>%
  summarize(total = sum(revenue), .groups = 'drop')

print(result)
print(is.grouped_df(result))  # FALSE

ungroup() Explicitly

You can always call ungroup() to remove grouping metadata from a data frame. This is important when you want subsequent operations to act on the full data, not per-group.

library(dplyr)

df <- data.frame(
  dept = c('HR','HR','Eng','Eng'),
  salary = c(55000, 60000, 95000, 105000)
)

# Without ungroup: rank is within group
df %>%
  group_by(dept) %>%
  mutate(rank_in_dept = rank(salary)) %>%
  ungroup() %>%
  mutate(rank_overall = rank(salary))

group_keys() and n_groups()

group_keys() returns a data frame showing the unique group combinations. n_groups() tells you how many groups exist. These are handy for introspection before performing grouped operations.

library(dplyr)

df <- data.frame(
  country = c('US','US','UK','UK','DE'),
  category = c('A','B','A','A','B'),
  value = c(10, 20, 15, 25, 30)
)

grouped <- df %>% group_by(country, category)

cat('Number of groups:', n_groups(grouped), '\n')
print(group_keys(grouped))

Summarizing with Multiple Stats

You can compute many summary statistics in one summarize() call. Combine count, mean, median, standard deviation, min, max, and custom calculations all at once.

library(dplyr)

scores <- data.frame(
  class = c('A','A','A','B','B','B','C','C'),
  score = c(85, 90, 78, 92, 88, 95, 70, 75)
)

scores %>%
  group_by(class) %>%
  summarize(
    n = n(),
    mean_score = round(mean(score), 1),
    median_score = median(score),
    sd_score = round(sd(score), 2),
    min_score = min(score),
    max_score = max(score)
  )

Group-Aware mutate()

mutate() after group_by() computes values within each group but keeps all rows (unlike summarize()). This is perfect for computing group-level metrics that you want attached to each row.

library(dplyr)

sales <- data.frame(
  region = c('East','East','West','West','West'),
  rep = c('Alice','Bob','Carol','Dave','Eve'),
  revenue = c(150, 200, 120, 180, 160)
)

sales %>%
  group_by(region) %>%
  mutate(
    region_total = sum(revenue),
    pct_of_region = round(100 * revenue / sum(revenue), 1)
  ) %>%
  ungroup()

Filtering Within Groups

filter() after group_by() filters rows based on group-level conditions. For example, keep only the top performer per region or rows where the value exceeds the group mean.

library(dplyr)

sales <- data.frame(
  region = c('East','East','West','West','North','North'),
  rep = c('Alice','Bob','Carol','Dave','Eve','Frank'),
  revenue = c(150, 200, 120, 180, 95, 130)
)

# Keep the top rep per region
sales %>%
  group_by(region) %>%
  filter(revenue == max(revenue)) %>%
  ungroup()

Quick Check

What does the .groups = 'drop' argument do in summarize()?

Recap: Grouped Summaries

Key takeaways from grouped summaries:

  • group_by(col) adds grouping — data is unchanged but operations become group-aware
  • summarize() collapses groups into one row each — use n(), mean(), sum(), etc.
  • .groups = 'drop' ensures the result is ungrouped
  • ungroup() removes grouping at any point
  • group_keys() inspects group levels; n_groups() counts them
  • mutate() + group_by() adds group-level columns without collapsing rows
library(dplyr)

# Full pipeline example
data.frame(
  dept = c('Eng','Eng','HR','HR','Sales'),
  salary = c(95000, 110000, 60000, 65000, 75000)
) %>%
  group_by(dept) %>%
  summarize(
    headcount = n(),
    avg_salary = mean(salary),
    .groups = 'drop'
  ) %>%
  arrange(desc(avg_salary))

Frequently asked questions

Is the “Grouped Summaries and group_by()” lesson free?

Yes — the full text of “Grouped Summaries and group_by()” is free to read here on the web, and the R 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 R Academy course, upgrade to CoddyKit PRO.

What will I learn in “Grouped Summaries and group_by()”?

Aggregate data by groups and compute per-group statistics. You practise R 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 R Academy?

No prior experience is required. R 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 “Grouped Summaries and group_by()” 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 R Academy lesson?

Yes. Every R 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. Grouped Summaries and group_by()
  2. Window Functions: lag, lead, cumsum
  3. Multi-table Joins in dplyr
  4. Tidy Evaluation: {{ }} and .data
← Back to R Academy