EDS 221 Day 6 AM

Data wrangling


August 17th, 2026

Meet the data: penguins


Today we’ll wrangle a data frame of penguin measurements from the Palmer Archipelago, Antarctica.

library(tidyverse)
library(palmerpenguins)

penguins
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           NA            NA                  NA          NA
 5 Adelie  Torgersen           36.7          19.3               193        3450
 6 Adelie  Torgersen           39.3          20.6               190        3650
 7 Adelie  Torgersen           38.9          17.8               181        3625
 8 Adelie  Torgersen           39.2          19.6               195        4675
 9 Adelie  Torgersen           34.1          18.1               193        3475
10 Adelie  Torgersen           42            20.2               190        4250
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>

Getting an overview: glimpse()


glimpse() shows every column, its type, and a preview of its values — a fast way to see what you’re working with.

glimpse(penguins)
Rows: 344
Columns: 8
$ species           <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
$ island            <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
$ bill_length_mm    <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, …
$ bill_depth_mm     <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, …
$ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186…
$ body_mass_g       <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, …
$ sex               <fct> male, female, female, NA, female, male, female, male…
$ year              <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…

Working with rows: filter()


filter() keeps only the rows that match a condition. The first argument is the data frame; the rest are conditions that must be TRUE.

filter(penguins, species == "Chinstrap")
# A tibble: 68 × 8
   species   island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>     <fct>           <dbl>         <dbl>             <int>       <int>
 1 Chinstrap Dream            46.5          17.9               192        3500
 2 Chinstrap Dream            50            19.5               196        3900
 3 Chinstrap Dream            51.3          19.2               193        3650
 4 Chinstrap Dream            45.4          18.7               188        3525
 5 Chinstrap Dream            52.7          19.8               197        3725
 6 Chinstrap Dream            45.2          17.8               198        3950
 7 Chinstrap Dream            46.1          18.2               178        3250
 8 Chinstrap Dream            51.3          18.2               197        3750
 9 Chinstrap Dream            46            18.9               195        4150
10 Chinstrap Dream            51.3          19.9               198        3700
# ℹ 58 more rows
# ℹ 2 more variables: sex <fct>, year <int>

filter(): combining conditions


, or &both must be true

filter(
  penguins,
  sex == "female",
  flipper_length_mm > 190
)
# A tibble: 100 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           40.3          18                 195        3250
 2 Adelie  Torgersen           36.7          19.3               193        3450
 3 Adelie  Torgersen           38.7          19                 195        3450
 4 Adelie  Dream               36.4          17                 195        3325
 5 Adelie  Biscoe              35.5          16.2               195        3350
 6 Adelie  Torgersen           39.6          17.2               196        3550
 7 Adelie  Torgersen           40.9          16.8               191        3700
 8 Adelie  Dream               37.3          17.8               191        3350
 9 Adelie  Dream               35.7          18                 202        3550
10 Adelie  Biscoe              35            17.9               192        3725
# ℹ 90 more rows
# ℹ 2 more variables: sex <fct>, year <int>

|either can be true

filter(
  penguins,
  species == "Chinstrap" |
    body_mass_g < 3000
)
# A tibble: 75 × 8
   species   island   bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>     <fct>             <dbl>         <dbl>             <int>       <int>
 1 Adelie    Dream              37.5          18.9               179        2975
 2 Adelie    Biscoe             34.5          18.1               187        2900
 3 Adelie    Biscoe             36.5          16.6               181        2850
 4 Adelie    Biscoe             36.4          17.1               184        2850
 5 Adelie    Dream              33.1          16.1               178        2900
 6 Adelie    Biscoe             37.9          18.6               193        2925
 7 Adelie    Torgers…           38.6          17                 188        2900
 8 Chinstrap Dream              46.5          17.9               192        3500
 9 Chinstrap Dream              50            19.5               196        3900
10 Chinstrap Dream              51.3          19.2               193        3650
# ℹ 65 more rows
# ℹ 2 more variables: sex <fct>, year <int>

This | is not the same as the || from last week’s conditionals — | compares element-by-element across an entire column.

Working with rows: arrange()


arrange() sorts rows by one or more columns, smallest to largest by default. Wrap a column in desc() to sort largest to smallest.

arrange(penguins, bill_depth_mm)
# A tibble: 344 × 8
   species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>           <dbl>         <dbl>             <int>       <int>
 1 Gentoo  Biscoe           42.9          13.1               215        5000
 2 Gentoo  Biscoe           46.1          13.2               211        4500
 3 Gentoo  Biscoe           44.9          13.3               213        5100
 4 Gentoo  Biscoe           43.3          13.4               209        4400
 5 Gentoo  Biscoe           46.5          13.5               210        4550
 6 Gentoo  Biscoe           42            13.5               210        4150
 7 Gentoo  Biscoe           44            13.6               208        4350
 8 Gentoo  Biscoe           40.9          13.7               214        4650
 9 Gentoo  Biscoe           45.5          13.7               214        4650
10 Gentoo  Biscoe           42.6          13.7               213        4950
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
arrange(penguins, desc(bill_depth_mm))
# A tibble: 344 × 8
   species   island   bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>     <fct>             <dbl>         <dbl>             <int>       <int>
 1 Adelie    Torgers…           46            21.5               194        4200
 2 Adelie    Torgers…           38.6          21.2               191        3800
 3 Adelie    Dream              42.3          21.2               191        4150
 4 Adelie    Torgers…           34.6          21.1               198        4400
 5 Adelie    Dream              39.2          21.1               196        4150
 6 Adelie    Biscoe             41.3          21.1               195        4400
 7 Chinstrap Dream              54.2          20.8               201        4300
 8 Adelie    Torgers…           42.5          20.7               197        4500
 9 Adelie    Biscoe             39.6          20.7               191        3900
10 Chinstrap Dream              52            20.7               210        4800
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>

Working with columns: mutate()


mutate() adds a new column, computed from existing ones. The argument name becomes the new column name.

mutate(penguins, body_mass_kg = body_mass_g / 1000)
# A tibble: 344 × 9
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           NA            NA                  NA          NA
 5 Adelie  Torgersen           36.7          19.3               193        3450
 6 Adelie  Torgersen           39.3          20.6               190        3650
 7 Adelie  Torgersen           38.9          17.8               181        3625
 8 Adelie  Torgersen           39.2          19.6               195        4675
 9 Adelie  Torgersen           34.1          18.1               193        3475
10 Adelie  Torgersen           42            20.2               190        4250
# ℹ 334 more rows
# ℹ 3 more variables: sex <fct>, year <int>, body_mass_kg <dbl>

mutate(): multiple new columns


You can create several columns in one call — later columns can even use ones you just created.

mutate(
  penguins,
  body_mass_kg = body_mass_g / 1000,
  bill_length_cm = bill_length_mm / 10
)
# A tibble: 344 × 10
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           NA            NA                  NA          NA
 5 Adelie  Torgersen           36.7          19.3               193        3450
 6 Adelie  Torgersen           39.3          20.6               190        3650
 7 Adelie  Torgersen           38.9          17.8               181        3625
 8 Adelie  Torgersen           39.2          19.6               195        4675
 9 Adelie  Torgersen           34.1          18.1               193        3475
10 Adelie  Torgersen           42            20.2               190        4250
# ℹ 334 more rows
# ℹ 4 more variables: sex <fct>, year <int>, body_mass_kg <dbl>,
#   bill_length_cm <dbl>

Working with columns: select()


select() keeps only the columns you name, in the order you name them — everything else is dropped.

select(penguins, species, island, body_mass_g)
# A tibble: 344 × 3
   species island    body_mass_g
   <fct>   <fct>           <int>
 1 Adelie  Torgersen        3750
 2 Adelie  Torgersen        3800
 3 Adelie  Torgersen        3250
 4 Adelie  Torgersen          NA
 5 Adelie  Torgersen        3450
 6 Adelie  Torgersen        3650
 7 Adelie  Torgersen        3625
 8 Adelie  Torgersen        4675
 9 Adelie  Torgersen        3475
10 Adelie  Torgersen        4250
# ℹ 334 more rows

select(): helpers


: — a range of columns

select(penguins, species:bill_length_mm)
# A tibble: 344 × 3
   species island    bill_length_mm
   <fct>   <fct>              <dbl>
 1 Adelie  Torgersen           39.1
 2 Adelie  Torgersen           39.5
 3 Adelie  Torgersen           40.3
 4 Adelie  Torgersen           NA  
 5 Adelie  Torgersen           36.7
 6 Adelie  Torgersen           39.3
 7 Adelie  Torgersen           38.9
 8 Adelie  Torgersen           39.2
 9 Adelie  Torgersen           34.1
10 Adelie  Torgersen           42  
# ℹ 334 more rows

starts_with() — match column names

There are lots of these helper functions!

select(penguins, starts_with("bill"))
# A tibble: 344 × 2
   bill_length_mm bill_depth_mm
            <dbl>         <dbl>
 1           39.1          18.7
 2           39.5          17.4
 3           40.3          18  
 4           NA            NA  
 5           36.7          19.3
 6           39.3          20.6
 7           38.9          17.8
 8           39.2          19.6
 9           34.1          18.1
10           42            20.2
# ℹ 334 more rows

The problem with nesting


Wrangling usually takes several steps. Nesting function calls works, but it reads inside-out and gets hard to follow.

filter(
  mutate(
    penguins,
    body_mass_kg = body_mass_g / 1000
  ),
  body_mass_kg < 2.8
)
# A tibble: 1 × 9
  species   island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>     <fct>           <dbl>         <dbl>             <int>       <int>
1 Chinstrap Dream            46.9          16.6               192        2700
# ℹ 3 more variables: sex <fct>, year <int>, body_mass_kg <dbl>

The pipe: |>


|> takes what’s on its left and passes it in as the first argument of the function on its right. Steps now read top-to-bottom, in the order they happen.

penguins |>
  mutate(body_mass_kg = body_mass_g / 1000) |>
  filter(body_mass_kg < 2.8)
# A tibble: 1 × 9
  species   island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>     <fct>           <dbl>         <dbl>             <int>       <int>
1 Chinstrap Dream            46.9          16.6               192        2700
# ℹ 3 more variables: sex <fct>, year <int>, body_mass_kg <dbl>

Summarizing groups: summarize()


summarize() collapses many rows into one summary value. Most summary functions need na.rm = TRUE to ignore missing values.

penguins |>
  summarize(avg_size = mean(body_mass_g, na.rm = TRUE))
# A tibble: 1 × 1
  avg_size
     <dbl>
1    4202.

summarize(): grouped with .by


The .by argument runs the summary separately for each group, returning one row per group.

penguins |>
  summarize(
    avg_size = mean(body_mass_g, na.rm = TRUE),
    biggest_bill = max(bill_length_mm, na.rm = TRUE),
    .by = species
  )
# A tibble: 3 × 3
  species   avg_size biggest_bill
  <fct>        <dbl>        <dbl>
1 Adelie       3701.         46  
2 Gentoo       5076.         59.6
3 Chinstrap    3733.         58  

What to memorize


Data wrangling with the tidyverse involves far more functions than you’ve seen so far. You should memorize a few fundamentals now, then pick up the rest as you gain experience.

Rows

  • filter()
  • arrange()

Columns

  • select()
  • mutate()

Groups

  • summarize()
  • n()
  • The .by parameter