Introduction to dplyr
dplyr is the tidyverse package for manipulating tabular data in R, built around a small set of verbs—filter(), select(), mutate(), arrange(), summarise(), and group_by()—that each do one job well. Because every verb takes a data frame (or tibble) as its first argument and returns a data frame, you can chain them together with the pipe operator (%>% or the base |>) to express a multi-step data transformation as a readable left-to-right sequence rather than a nest of function calls.
Cricket analogy: Just as a cricket captain calls a sequence of field placements, bowling changes, and batting orders over an innings, dplyr chains filter(), mutate(), and summarise() into a readable over-by-over pipeline instead of one tangled instruction.
The Core Verbs: filter, select, arrange
filter() keeps only the rows that satisfy a logical condition, such as filter(df, age > 30 & country == 'India'), combining multiple conditions with & (AND) and | (OR). select() instead subsets columns, and supports helper functions like starts_with(), contains(), and everything() so you can grab groups of columns by name pattern rather than typing each one; arrange() then reorders the remaining rows by one or more columns, with desc() for descending order.
Cricket analogy: filter() is like a selector picking only players with a batting average above 40 and at least 50 ODIs, while select() is choosing which columns of the scorecard—runs, strike rate, not fielding position—to display.
library(dplyr)
starwars %>%
filter(species == 'Human', !is.na(height)) %>%
select(name, height, mass, homeworld) %>%
mutate(bmi = mass / (height / 100)^2) %>%
group_by(homeworld) %>%
summarise(
avg_bmi = mean(bmi, na.rm = TRUE),
n = n()
) %>%
arrange(desc(avg_bmi))Transforming Data: mutate and the Pipe
mutate() creates new columns or overwrites existing ones based on expressions applied to other columns, for example mutate(df, bmi = weight_kg / (height_m^2)), and can create several columns in one call, each able to reference columns defined earlier in the same mutate(). The pipe operator, whether magrittr's %>% or base R's native |>, takes the value on its left and inserts it as the first argument of the function on its right, so df %>% filter(...) %>% mutate(...) reads as 'take df, then filter it, then mutate it' instead of nested calls like mutate(filter(df, ...)).
Cricket analogy: mutate() is like a scorer adding a derived strike-rate column computed from runs and balls faced for every player, and the pipe reads like a match report narrating toss, then innings, then result in order.
The native pipe |> (introduced in R 4.1) works almost identically to magrittr's %>% for simple chains like df |> filter(...) |> mutate(...), and needs no package load, though %>% still offers extras such as the placeholder dot.
Grouped Summaries: group_by and summarise
group_by() splits a data frame into groups defined by one or more columns without changing its appearance, and every subsequent verb—especially summarise()—is then applied independently within each group; summarise(df, avg_score = mean(score), n = n()) collapses each group down to one row per group containing the requested aggregate statistics. Because grouping persists on the returned tibble, it is good practice to call ungroup() (or use .by = inside a single verb in modern dplyr) once you are done with grouped operations, so later steps in the pipeline don't silently operate group-wise when you expect them not to.
Cricket analogy: group_by(team) followed by summarise(avg_runs = mean(runs)) is like a scorer grouping every innings by team and computing each team's season batting average in one row per team, rather than one row per innings.
A grouped tibble stays grouped after summarise() (dropping only the last grouping variable), so chaining another summarise() or mutate() without ungroup() can silently aggregate within leftover groups instead of over the whole data frame.
- dplyr's core verbs—filter(), select(), mutate(), arrange(), summarise(), group_by()—each perform one focused transformation on a data frame.
- The pipe (%>% or |>) chains verbs left to right, avoiding deeply nested function calls.
- filter() subsets rows by logical conditions; select() subsets columns, including via helpers like starts_with().
- mutate() adds or overwrites columns and can reference columns created earlier in the same call.
- group_by() + summarise() is the standard pattern for computing per-group aggregate statistics.
- Always ungroup() (or scope with .by =) after grouped operations to avoid unexpected group-wise behavior downstream.
- arrange() reorders rows, with desc() for descending sort order.
Practice what you learned
1. Which dplyr verb would you use to keep only rows where a logical condition is TRUE?
2. What does group_by() do on its own, before any other verb is applied?
3. In df %>% filter(x > 0) %>% mutate(y = x * 2), what does the pipe operator do?
4. Why might summarise() after group_by(team, season) still behave unexpectedly in a later step?
5. Which function call correctly creates a new column bmi from existing weight_kg and height_m columns?
Was this page helpful?
You May Also Like
tidyr and Reshaping Data
Learn how to reshape messy or wide data into tidy form using pivot_longer(), pivot_wider(), separate_wider_delim(), and complete().
ggplot2 Basics
Learn ggplot2's grammar of graphics—aesthetics, geoms, facets, and themes—to build layered, publication-quality charts in R.
Handling Missing Data in R
Understand how R represents missing values with NA, how to detect and quantify missingness, and strategies for removal versus imputation.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics