9  Tranformasjoner

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.2     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Når vi tar inn et datasett i SPSS er det vanligvis fordi vi vil transformere det på et vis. Vi gjør også en god del transformasjoner i Excel. Så klart skal vi også transformere verdier i R også. Nå vil vi merke fordelen ved at R er designa for å operere på vektorer. Når du gjør en transformasjon på en kolonne i et datasett (som du kanskje husker kan anses som en vektor i en liste), vil R utføre transformasjonen på hele lista. Hva betyr det? Vi lager en enkel kolonne i et datasett. Kolonnas verdi avhenger av en annen kolonne i datasettet.

starwars %>% 
  select(name, hair_color, eye_color) %>% 
  mutate(blond_blue_eyed = if_else(hair_color == "blond" & eye_color == "blue", TRUE, FALSE))
# A tibble: 87 × 4
   name               hair_color    eye_color blond_blue_eyed
   <chr>              <chr>         <chr>     <lgl>          
 1 Luke Skywalker     blond         blue      TRUE           
 2 C-3PO              <NA>          yellow    FALSE          
 3 R2-D2              <NA>          red       FALSE          
 4 Darth Vader        none          yellow    FALSE          
 5 Leia Organa        brown         brown     FALSE          
 6 Owen Lars          brown, grey   blue      FALSE          
 7 Beru Whitesun lars brown         blue      FALSE          
 8 R5-D4              <NA>          red       FALSE          
 9 Biggs Darklighter  black         brown     FALSE          
10 Obi-Wan Kenobi     auburn, white blue-gray FALSE          
# ℹ 77 more rows

Det vakre her er at vi slipper å iterere over alle radene i hver kolonne og sammenlikne dem med hverandre via f.eks. en loop. R gjør dette av seg sjøl.

Når vi transformerer tar vi en verdi i et datasett og endrer på den. Dette har vi allerede gjort mange ganger allerede iløpet av denne pamfletten, men la oss nå formelt introdusere arbeidshesten vår: mutate().

9.1 Mutate

mutate() kommer fra etc. etc. You know the drill. Den en enkel å bruke. Hvis vi vil lage en ny kolonne bare gir vi den et navn og definerer hvordan den skal se ut. Vi tar inn prognoseeksemplet vårt:

prognose <- tibble(
  plansone = rep(seq(5001001, 5001004), each = 2),
  kjonn = rep(c("M", "K"), 4),
  aar2023 = round(runif(8, 400, 800)),
  aar2024 = round(runif(8, 400, 800)),
  aar2025 = round(runif(8, 400, 800))
) %>% 
  rowwise() %>% 
  tibble(
    faktisk2023 = round(aar2023 + aar2023 * (rnorm(1, 0, 10)/100)),
    faktisk2024 = round(aar2024 + aar2024 * (rnorm(1, 0, 10)/100)),
    faktisk2025 = round(aar2025 + aar2025 * (rnorm(1, 0, 10)/100))
  )
prognose
# A tibble: 8 × 8
  plansone kjonn aar2023 aar2024 aar2025 faktisk2023 faktisk2024 faktisk2025
     <int> <chr>   <dbl>   <dbl>   <dbl>       <dbl>       <dbl>       <dbl>
1  5001001 M         592     534     663         610         509         602
2  5001001 K         786     538     631         810         512         573
3  5001002 M         589     769     755         607         732         685
4  5001002 K         699     529     714         720         504         648
5  5001003 M         734     547     411         756         521         373
6  5001003 K         519     685     466         535         652         423
7  5001004 M         464     722     491         478         688         446
8  5001004 K         727     630     654         749         600         594

Si at vi vil vite differansen mellom prognosen (“aarXXXX”) og faktisk befolkning (“faktiskXXXX”).

prognose %>% 
  mutate(diff2023 = aar2023 - faktisk2023,
         diff2024 = aar2024 - faktisk2024,
         diff2025 = aar2025 - faktisk2025)
# A tibble: 8 × 11
  plansone kjonn aar2023 aar2024 aar2025 faktisk2023 faktisk2024 faktisk2025
     <int> <chr>   <dbl>   <dbl>   <dbl>       <dbl>       <dbl>       <dbl>
1  5001001 M         592     534     663         610         509         602
2  5001001 K         786     538     631         810         512         573
3  5001002 M         589     769     755         607         732         685
4  5001002 K         699     529     714         720         504         648
5  5001003 M         734     547     411         756         521         373
6  5001003 K         519     685     466         535         652         423
7  5001004 M         464     722     491         478         688         446
8  5001004 K         727     630     654         749         600         594
# ℹ 3 more variables: diff2023 <dbl>, diff2024 <dbl>, diff2025 <dbl>

Dersom vi vil oppdatere verdien til en kolonne kan vi overskrive den i mutate ved å gi den nye variabelen samme navn som en eksisterende variabel.

# Vi oppdaterer prognosen for 2025 fordi vi forventer dobbelt så mange som vi 
# opprinnelig hadde tenkt.
prognose %>% 
  mutate(aar2025 = aar2025 * 2)
# A tibble: 8 × 8
  plansone kjonn aar2023 aar2024 aar2025 faktisk2023 faktisk2024 faktisk2025
     <int> <chr>   <dbl>   <dbl>   <dbl>       <dbl>       <dbl>       <dbl>
1  5001001 M         592     534    1326         610         509         602
2  5001001 K         786     538    1262         810         512         573
3  5001002 M         589     769    1510         607         732         685
4  5001002 K         699     529    1428         720         504         648
5  5001003 M         734     547     822         756         521         373
6  5001003 K         519     685     932         535         652         423
7  5001004 M         464     722     982         478         688         446
8  5001004 K         727     630    1308         749         600         594

9.1.1 Noen nyttige funksjoner til mutate

Her er noen nyttige funksjoner som jeg ofte bruker sammen med mutate().

Varianter av if else: ifelse(), if_else og case_when.

# Vi lager et datasett med noen personer som vi veit kjønn og alder til.
set.seed(123)
folk <- tibble(
  kjonn = rep(c("M", "K"), 25),
  alder = round(runif(50, 10, 80))
)
folk
# A tibble: 50 × 2
   kjonn alder
   <chr> <dbl>
 1 M        30
 2 K        65
 3 M        39
 4 K        72
 5 M        76
 6 K        13
 7 M        47
 8 K        72
 9 M        49
10 K        42
# ℹ 40 more rows
# Vi kan lage en ny variabel som forteller oss hvem som er myndig (fylt 18):
# Til dette bruker vi `if_else()`. 
folk %>% 
  mutate(myndig = if_else(alder > 17, TRUE, FALSE))
# A tibble: 50 × 3
   kjonn alder myndig
   <chr> <dbl> <lgl> 
 1 M        30 TRUE  
 2 K        65 TRUE  
 3 M        39 TRUE  
 4 K        72 TRUE  
 5 M        76 TRUE  
 6 K        13 FALSE 
 7 M        47 TRUE  
 8 K        72 TRUE  
 9 M        49 TRUE  
10 K        42 TRUE  
# ℹ 40 more rows
# Verdiene til `if_else()` kan være noe annet også:
folk %>% 
  mutate(myndig = if_else(alder > 17, "myndig", "barn"))
# A tibble: 50 × 3
   kjonn alder myndig
   <chr> <dbl> <chr> 
 1 M        30 myndig
 2 K        65 myndig
 3 M        39 myndig
 4 K        72 myndig
 5 M        76 myndig
 6 K        13 barn  
 7 M        47 myndig
 8 K        72 myndig
 9 M        49 myndig
10 K        42 myndig
# ℹ 40 more rows

Hva er forskjellen på ifelse() og if_else()? Sistnevnte kommer fra dplyr og er en kjappere og strengere versjon av ifelse(). Alle argumenta må være av samme type, så du henter du får feilmelding når du bruker denne.

case_when() er en nyttig utvidelse av if else-tankegangen når vi har mer enn to muligheter. For eksempel hvis vi skal lage en kjapp alderskategorisering. case_when() følger en struktur hvor du definerer betingelsen på venstre side av ~ og resultatet på høyre side. Bruken av tilde (~) indikerer at dette et et formel-objekt. Vi har ikke snakka noe særlig om formel-objekter hittil, og jeg tenker at vi ikke trenger å gå inn på det her heller. Men hvis du noen gang skal gjøre noe fancy med case_when() kan det være greit å vite at den bruker formler i koden sin.

folk %>% 
  mutate(alders_gruppe = case_when(
    alder < 18 ~ 1, 
    alder >= 18 & alder < 30 ~ 2,
    alder >= 30 & alder < 40 ~ 3,
    alder >= 40 & alder < 50 ~ 4,
    alder > 50 ~ 5,
    TRUE ~ NA_real_)
  )
# A tibble: 50 × 3
   kjonn alder alders_gruppe
   <chr> <dbl>         <dbl>
 1 M        30             3
 2 K        65             5
 3 M        39             3
 4 K        72             5
 5 M        76             5
 6 K        13             1
 7 M        47             4
 8 K        72             5
 9 M        49             4
10 K        42             4
# ℹ 40 more rows

Noen ting å merke seg med case_when():

  • Logikken arbeider seg nedover og for hver rad velger den ut den første betingelsen som stemmer.
  • Det er smart å ha en catch-all på slutten av case_when(). Du ser det i eksemplet mitt over, i form av det siste argumentet som er TRUE ~ NA_real_. Dersom ingen av betingelsene over stemmer, vil raden få verdien NA. Hvorfor skriver jeg NA_real_? case_when(), lik if_else() er streng med å holde seg til samme type verdier. Derfor lar den meg ikke uten videre bruke NA uten å definere om dette er en numerisk NA eller en streng-NA. Hadde jeg brukt strenger som gruppenavn istedenfor tall ville jeg i siste linje oppgitt NA_character_ istedenfor.
  • Dersom du arbeider på en case_when() som begynner å bli lang og komplisert er det ofte bedre å heller gjøre det om til en funksjon.

9.2 Transmute

Tvillingen til mutate() heter transmute(). De opererer likt, bortsett fra at transmute() kun beholder de variabelene som blir laga. Noen ganger er dette nyttig. Merk at det gjør disse to kodebrokkene lik

# Dette
prognose %>% 
  mutate(aar2025 = aar2025 * 2) %>% 
  select(aar2025)
# A tibble: 8 × 1
  aar2025
    <dbl>
1    1326
2    1262
3    1510
4    1428
5     822
6     932
7     982
8    1308
# Er det samme som dette
prognose %>% 
  transmute(aar2025 = aar2025 * 2)
# A tibble: 8 × 1
  aar2025
    <dbl>
1    1326
2    1262
3    1510
4    1428
5     822
6     932
7     982
8    1308

9.3 Summarise

Også nært beslekta er summarise(). Vi har prata om summarise() tidligere (sec-oppsummering), og du vil lett se at disse likner på hverandre. summarise() bruker vi når vi vil lage en oversikt over enkelte deler av, eller hele, datasettet. Til forskjell fra mutate() summerer den opp alle radene på hver kolonne, eventuelt gruppert etter grupper om man kombinerer den med group_by().