Data Wrangling with dplyr
x |> f(y) is f(x, y).
library(dplyr)
library(palmerpenguins) # penguins dataset18.1 The pipe
x |> f(y) is f(x, y). The native pipe supports a _ placeholder for named arguments (R 4.2+): df |> lm(y ~ x, data = _).
penguins |> filter(species == "Adelie") |> summarise(mean_mass = mean(body_mass_g, na.rm = TRUE))
penguins %>% head() # magrittr pipe (supports the . placeholder)18.2 Core verbs
keep rows matching conditions choose/rename columns create/modify columns reduce to summary rows group_by() / .by count() / tally() rename() / relocate() rename / reorder columns rows by position (slice_max, slice_sample,...) extract a column as a vector
penguins |>
filter(!is.na(sex), island %in% c("Biscoe", "Dream"), body_mass_g > 3500) |> # rows
select(species, island, sex, bill_length_mm, body_mass_g) |> # columns
mutate(mass_kg = body_mass_g / 1000, # new columns
big = if_else(mass_kg > 4.5, "big", "small")) |>
arrange(desc(mass_kg)) |> # sort
slice_head(n = 10)18.3 Grouped summaries
penguins |>
group_by(species, sex) |>
summarise(
n = n(),
mean_mass = mean(body_mass_g, na.rm = TRUE),
sd_mass = sd(body_mass_g, na.rm = TRUE),
max_bill = max(bill_length_mm, na.rm = TRUE),
.groups = "drop"
)
penguins |> summarise(mean_mass = mean(body_mass_g, na.rm = TRUE), .by = species) # per-operation grouping
penguins |> count(species, island, sort = TRUE)
penguins |> group_by(species) |> mutate(z = (body_mass_g - mean(body_mass_g, na.rm = TRUE)) / sd(body_mass_g, na.rm = TRUE))18.4 Selection helpers & across()
select(penguins, starts_with("bill"), ends_with("_mm"), contains("mass"), where(is.numeric))
select(penguins, -year, species:island, any_of(c("sex", "nope")))
penguins |> summarise(across(where(is.numeric), \(x) mean(x, na.rm = TRUE)))
penguins |> mutate(across(c(bill_length_mm, bill_depth_mm), ~ .x / 10, .names = "{.col}_cm"))
penguins |> filter(if_any(ends_with("_mm"), \(x) x > 50))
penguins |> rowwise() |> mutate(total = sum(c_across(bill_length_mm:flipper_length_mm)))18.5 Useful helpers
if_else(x > 0, "pos", "neg")
case_when(x < 0 ~ "neg", x == 0 ~ "zero", .default = "pos")
case_match(code, 1 ~ "one", 2 ~ "two")
coalesce(x, 0) # replace NA
na_if(x, -99)
lag(x); lead(x); cumsum(x); row_number(); min_rank(desc(x)); ntile(x, 4); percent_rank(x)
between(x, 1, 10); near(0.1 + 0.2, 0.3)
n_distinct(x); first(x); last(x); nth(x, 2)
consecutive_id(x)18.6 Joins
band <- tibble(name = c("Mick", "John", "Paul"), band = c("Stones", "Beatles", "Beatles"))
plays <- tibble(name = c("John", "Paul", "Keith"), plays = c("guitar", "bass", "guitar"))
left_join(band, plays, by = "name") # all rows of band
inner_join(band, plays, by = "name") # matches only
full_join(band, plays, by = "name") # all rows of both
right_join(band, plays, by = "name")
semi_join(band, plays, by = "name") # band rows with a match (filtering join)
anti_join(band, plays, by = "name") # band rows without a match
left_join(x, y, by = join_by(id == key, date >= start)) # different names, inequality/rolling joins
cross_join(x, y)
bind_rows(df1, df2, .id = "source"); bind_cols(df1, df2)
union(), intersect(), setdiff() # set operations on rows18.7 dplyr backends
The same dplyr code runs on databases (dbplyr), data.table (dtplyr), Arrow (arrow), DuckDB (duckplyr), and Spark (sparklyr) — translated to SQL or native operations.