Skip to content
AgileLogics
7 min read

dplyr: mutate, summarise, and group_by

Prerequisites: dplyr: filter, select, and arrange

These verbs create new columns and summaries — and group_by makes them work per group, which answers most everyday data questions.

mutate — add or change columns

library(dplyr, warn.conflicts = FALSE)

staff <- tibble(
  name = c("Ada", "Linus", "Grace", "Alan", "Margaret"),
  team = c("Data", "Infra", "Data", "Research", "Infra"),
  salary = c(72000, 65000, 91000, 58000, 83000),
  years = c(5, 3, 12, 2, 8)
)

staff |>
  mutate(
    monthly = round(salary / 12),
    level = if_else(years >= 5, "senior", "junior")
  ) |>
  select(name, monthly, level)
# A tibble: 5 × 3
  name     monthly level
  <chr>      <dbl> <chr>
1 Ada         6000 senior
2 Linus       5417 junior
3 Grace       7583 senior
4 Alan        4833 junior
5 Margaret    6917 senior

if_else() is dplyr's stricter ifelse(). For several conditions, use case_when():

library(dplyr, warn.conflicts = FALSE)

tibble(score = c(95, 84, 72, 55)) |>
  mutate(grade = case_when(
    score >= 90 ~ "A",
    score >= 80 ~ "B",
    score >= 70 ~ "C",
    .default = "F"
  ))
# A tibble: 4 × 2
  score grade
  <dbl> <chr>
1    95 A
2    84 B
3    72 C
4    55 F

summarise — reduce to one row

library(dplyr, warn.conflicts = FALSE)

staff <- tibble(
  team = c("Data", "Infra", "Data", "Research", "Infra"),
  salary = c(72000, 65000, 91000, 58000, 83000)
)

staff |> summarise(people = n(), avg_salary = mean(salary))
# A tibble: 1 × 2
  people avg_salary
   <int>      <dbl>
1      5      73800

group_by — summaries per group

library(dplyr, warn.conflicts = FALSE)

staff <- tibble(
  team = c("Data", "Infra", "Data", "Research", "Infra"),
  salary = c(72000, 65000, 91000, 58000, 83000)
)

staff |>
  group_by(team) |>
  summarise(people = n(), avg_salary = mean(salary), top = max(salary)) |>
  arrange(desc(avg_salary))
# A tibble: 3 × 4
  team     people avg_salary   top
  <chr>     <int>      <dbl> <dbl>
1 Data          2      81500 91000
2 Infra         2      74000 83000
3 Research      1      58000 58000

"Split the data into groups, summarise each group, combine the results" — this pattern answers questions like average sales per region, number of customers per month, or highest score per class.

count(team) is a handy shortcut for group_by(team) |> summarise(n = n()).

Common mistakes

  • Forgetting na.rm = TRUE inside summarise, so one missing value makes the whole group's mean NA.
  • Leaving data grouped after group_by with mutate — later operations stay grouped. Use .by = team inside a single verb (summarise(..., .by = team)) or call ungroup().

Practice

Using the staff data from the previous lesson, find the total salary cost and the average years of experience for each team.

Show solution
library(dplyr, warn.conflicts = FALSE)

staff <- tibble(
  team = c("Data", "Infra", "Data", "Research", "Infra"),
  salary = c(72000, 65000, 91000, 58000, 83000),
  years = c(5, 3, 12, 2, 8)
)

staff |>
  summarise(total_salary = sum(salary), avg_years = mean(years), .by = team)
# A tibble: 3 × 3
  team     total_salary avg_years
  <chr>           <dbl>     <dbl>
1 Data           163000       8.5
2 Infra          148000       5.5
3 Research        58000       2