5.2 dplyr: case_when() and Distinct Rows

DATA 412/612 · Week 5 · PDF · Source (.qmd)

case_when() replaces nested if_else(). distinct() and rownames_to_column() clean identifiers.

1. case_when()

case_when() is a vectorized chain of if / else. Each argument is condition ~ value. Conditions are tried in order. End with TRUE ~ ... as the default. If nothing matches, you get NA.

x <- 1:16
case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  TRUE ~ as.character(x)
)
##  [1] "1"    "2"    "3"    "4"    "fizz" "6"    "buzz" "8"    "9"    "fizz"
## [11] "11"   "12"   "13"   "buzz" "fizz" "16"

Put the specific cases first. A leading TRUE ~ catches everything and the later lines never run.

NA in x is not special. Handle it with is.na(x) ~ ....

Inside mutate() it is the usual way to build a category from several columns:

starwars |>
  select(name:mass, gender, species) |>
  mutate(
    type = case_when(
      height > 200 | mass > 200 ~ "large",
      species == "Droid" ~ "robot",
      TRUE ~ "other"
    )
  )
## # A tibble: 87 × 6
##    name               height  mass gender    species type 
##    <chr>               <int> <dbl> <chr>     <chr>   <chr>
##  1 Luke Skywalker        172    77 masculine Human   other
##  2 C-3PO                 167    75 masculine Droid   robot
##  3 R2-D2                  96    32 masculine Droid   robot
##  4 Darth Vader           202   136 masculine Human   large
##  5 Leia Organa           150    49 feminine  Human   other
##  6 Owen Lars             178   120 masculine Human   other
##  7 Beru Whitesun Lars    165    75 feminine  Human   other
##  8 R5-D4                  97    32 masculine Droid   robot
##  9 Biggs Darklighter     183    84 masculine Human   other
## 10 Obi-Wan Kenobi        182    77 masculine Human   other
## # ℹ 77 more rows

All right-hand sides must have the same type.


2. Row names and distinct()

Tibbles do not use row names. A matrix such as state.x77 or mtcars does. has_rownames() checks. rownames_to_column(mtcars, var = "car") then as_tibble() turns the names into a real column.

has_rownames(mtcars)
## [1] TRUE
mtcars |>
  rownames_to_column(var = "car") |>
  as_tibble()
## # A tibble: 32 × 12
##    car           mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
##    <chr>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Mazda RX4    21       6  160    110  3.9   2.62  16.5     0     1     4     4
##  2 Mazda RX4 …  21       6  160    110  3.9   2.88  17.0     0     1     4     4
##  3 Datsun 710   22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
##  4 Hornet 4 D…  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
##  5 Hornet Spo…  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
##  6 Valiant      18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
##  7 Duster 360   14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
##  8 Merc 240D    24.4     4  147.    62  3.69  3.19  20       1     0     4     2
##  9 Merc 230     22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
## 10 Merc 280     19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
## # ℹ 22 more rows

distinct() keeps unique rows, in the original order. You can name columns, or use across():

starwars |>
  distinct(across(contains("color"))) |>
  arrange(hair_color, skin_color, eye_color)
## # A tibble: 67 × 3
##    hair_color    skin_color eye_color
##    <chr>         <chr>      <chr>    
##  1 auburn        fair       blue     
##  2 auburn, grey  fair       blue     
##  3 auburn, white fair       blue-gray
##  4 black         blue, grey yellow   
##  5 black         brown      brown    
##  6 black         dark       brown    
##  7 black         dark       dark     
##  8 black         fair       brown    
##  9 black         light      brown    
## 10 black         tan        brown    
## # ℹ 57 more rows

3. Programming note (aside)

dplyr verbs use data masking (filter(month == 1) means the column, not an object in the console) or tidy selection (select(starts_with("dep"))). That is why interactive work is short. Inside a function, when the column name is stored in an ordinary R object, you need extra tools. We will stay with typed column names this week.


4. This week, in one place

  • rowwise() and c_across() for a summary across columns in one row
  • across() to repeat a function on many columns
  • case_when() instead of nested if_else()
  • distinct() and rownames_to_column() when you need them

5. Practice

  1. Check whether mtcars has row names. If it does, make a tibble with a car column.
  2. Min and max of every numeric column except birth_year, by species and gender, plus n().