Sesjon 9: Tabeller

R-workshop 10. oktober, OsloMet

R er utmerket for å lage tabeller. Dette foregår i to skritt:

  • Vi begynner med å lage en tabell i R. Resultatet er et lite datasett (et R-objekt). Til dette, er pakkene dplyr og tidyr veldig nyttige.

  • Deretter lager vi en tabell som siden kan eksporteres. Her finnes det mange pakker som kan hjelpe oss. En slik pakke er kableExtra.

Vi begynner med å laste inn pakkene i arbeidsområdet vårt.

library(kableExtra); library(dplyr); library(tidyr)

Vi laster så inn ESS-dataene fra forrige sesjon (med mindre vi fortsatt har dem).

load(
  url("https://siljehermansen.github.io/teaching/oslomet/kap7.rda")
)
df <- kap7

Krysstabeller

Vi husker fra forrige gang at vi kunne lage gruppegjennomsnitt med funksjonen group_by i dplyr.

Bivariat tabell

Her regner jeg ut gjennomsnitlig innvandringsskepsis i majoritetsbefolkningen og blant innvandrere, respektivt.

df %>%
  group_by(Innvandrer) %>%
  summarize(Innvandringsskepsis = mean(Skepsis, na.rm = TRUE))

Vi ser at huller i innvandrer-dataene (NA) blir til en egen kategori. Om jeg ikke ønsker dette, må jeg filtrere ut disse observasjonene ved hjelp av filter() før jeg foretar resten av operasjonene.

df %>%
  filter(!is.na(Innvandrer)) %>%
  group_by(Innvandrer) %>%
  summarize(Innvandringsskepsis = mean(Skepsis, na.rm = TRUE))

Jeg kan lage flere slike sammendrag. Merk deg hvordan jeg pakker inn de nye kolonnenavnene i hermetegn hvis jeg ønsker å bruke mellomrom i navnene.

df %>%
  filter(!is.na(Innvandrer)) %>%
  group_by(Innvandrer) %>%
  summarize("Innvandringsskepsis" = mean(Skepsis, na.rm = TRUE),
            "Prekær arbeidslivstilknytning" = mean(Prekaritet, na.rm = T))

Her ser vi at innvandrere jevnt over er mindre skeptiske til innvandring, men også mer oftere i en usikker arbeidssituasjon.

Trivariat tabell

Om vi tror at en respondents arbeidssituasjon har en innvirkning på holdningen deres til innvandring, vil vi være interessert i å lage en trivariat tabell.

En måte å gjøre dette på, er å legge til enda en grupperingskategori. Da får vi gruppegjennomsnitt med to grupperingsvariabler.

df %>%
  group_by(Innvandrer, Prekaritet) %>%
  summarize("Innvandringsskepsis" = mean(Skepsis, na.rm = TRUE))
## `summarise()` has grouped output by 'Innvandrer'. You can override using the
## `.groups` argument.

Om vi ønsker å fjerne NA-kategoriene, må vi filtrere igjen. Denne gangen legger jeg inn to betingelser: Jeg skal ikke ha NA i variabelen for Innvandrer (!is.na()) og (&) ikke i arbeidsvariabelen min.

df %>%
  filter(!is.na(Innvandrer) & !is.na(Prekaritet)) %>%
  group_by(Innvandrer, Prekaritet) %>%
  summarize("Innvandringsskepsis" = mean(Skepsis, na.rm = TRUE))
## `summarise()` has grouped output by 'Innvandrer'. You can override using the
## `.groups` argument.

Nå har jeg fire verdier for innvandringsskepsis som jeg er interessert i. Allerede her kan vi se at innvandringsskepsis blant majoritetsbefolkningen er høyere hos de med en prekær tilknytning til arbeidslivet (de to første linjene). Det motsatte er tilfelle for innvandrere!

Hvor mange observasjoner er det i hver kategori? Da kan jeg bruke n() for å telle antall observasjoner. Her lagrer jeg også tabellen i sitt eget objekt.

tabell <-
  df %>%
  filter(!is.na(Innvandrer) & !is.na(Prekaritet)) %>%
  group_by(Innvandrer, Prekaritet) %>%
  summarize("Innvandringsskepsis" = mean(Skepsis, na.rm = TRUE),
            "Antall" = n())
## `summarise()` has grouped output by 'Innvandrer'. You can override using the
## `.groups` argument.
tabell

Det er relativt krevende å lese denne tabellen. Vanligvis ønsker vi å foreta sammenlikningen kolonnevis, ikke per linje, slik som her.

Jeg begyner med å transposere tabellen: Jeg flipper tabellen slik at kolonner og linjer bytter plass med t().

tabell <- t(tabell)
tabell
##                           [,1]      [,2]       [,3]      [,4]
## Innvandrer            0.000000   0.00000   1.000000  1.000000
## Prekaritet            0.000000   1.00000   0.000000  1.000000
## Innvandringsskepsis   4.388755   4.98325   4.303855  4.007576
## Antall              849.000000 204.00000 151.000000 47.000000

Nå kan jeg velge med de linjene jeg ønsker å bruke videre. He er det linje 3 og 4, som rapporterer innvandringsskepsis og antall observasjoner.

tabell <- tabell[3:4,]

Jeg bruker kableExtra- pakken for a pimpe denne tabellen. Basisfunksjonen er kbl() som etablerer en tabell.

kbl(tabell)
Innvandringsskepsis 4.388755 4.98325 4.303855 4.007576
Antall 849.000000 204.00000 151.000000 47.000000

Tabellen dukker opp i “Viewer”-vinduet i RStudio. Foreløpig er den vanskelig å lese.

Her ber jeg om maks to desimaler i resultatene. Siden dette er en norsk tabell, endrer jeg også desimalskilletegnet til komma ved et formateringsargument. Merk deg at svaret må presiseres i en liste.

I tillegg gir jeg tabellen min en tittel.

tab_trivariat <- 
  kbl(tabell,
      #Desimaler
      digits = 2,
      #Komma i stedet for punktum
      format.args = list(decimal.mark = ","),
      #Tittel
      caption = "Innvandringsskepsis og okonomisk utrygghet")
tab_trivariat
Innvandringsskepsis og okonomisk utrygghet
Innvandringsskepsis 4,39 4,98 4,3 4,01
Antall 849,00 204,00 151,0 47,00

Siden jeg er fornøyd med resultatet, tar jeg vare på det i et nytt objekt.

Nå er det på tide å gi noen kolonnenavn til tabellen. Jeg begynner med arbeidssituasjonen.

tab_trivariat <-
  tab_trivariat %>%
  #Kolonneoverskrifter: Antall kolonner + radnavn. Her spenner de to siste over to kolonner hver.
  add_header_above(header = c("",
                              "Ikke-prekær", "Prekaer",
                              "Ikke-prekær", "Prekaer")) 
tab_trivariat
Ikke-prekær
Prekaer
Ikke-prekær
Prekaer
Innvandringsskepsis og okonomisk utrygghet
Innvandringsskepsis 4,39 4,98 4,3 4,01
Antall 849,00 204,00 151,0 47,00

Dette ser lovende ut, men jeg har nå fire kolonner, med like kolonnenavn. Det er fordi vi ikke har lagt til den siste grupperingsvariabelen: Innvandrerstatus. Det gjør jeg nå.

Rekkefølgen i kodene er viktige. Det siste kolonnenavnet kommer øverst i tabellen.

kableExtra gjør det mulig å slå sammen kolonnenavn. Det er hva jeg er interessert i her.

tab_trivariat <-
  tab_trivariat %>%
  #Kolonneoverskrifter: Antall kolonner + radnavn. Her spenner de to siste over to kolonner hver.
  add_header_above(header = c("" , 
                              "Norskfødt" = 2 , 
                              "Innvandrer" = 2)) 
tab_trivariat
Norskfødt
Innvandrer
Ikke-prekær
Prekaer
Ikke-prekær
Prekaer
Innvandringsskepsis og okonomisk utrygghet
Innvandringsskepsis 4,39 4,98 4,3 4,01
Antall 849,00 204,00 151,0 47,00

Jeg presiserer at den første kolonnen (med variabler) ikke trenger en overskrift. Deretter sier jeg at de to neste kolonnene skal ha én felles overskrift (“Norskfødt”), osv.

Nå kan jeg “style” tabellen. Her har jeg valgt en funksjon for “klassiske” design.

På dette tidspunktet, er det på tide å eksportere tabellen. Her presiserer jeg at jeg ønsker en html-tabell som skal skrives ut til arbeidsområdet mitt. Denne gangen lagerer jeg ikke utputtet i et objekt.

Jeg kan nå gå til arbeidsområdet mitt, og åpne tabellen i nettleseren min.

Om du har glemt hvor arbeidsområdet ditt er, kan du spørre R.

getwd()
## [1] "C:/Users/ssherman/Dropbox/Teaching/OsloMET"

Beskrivende statistikk

Vi ønsker ofte å se en beskrivende statistikk i forkant av en regresjonsanalyse. Dette er nyttig for å bli kjent med dataene, men også i tolkningen av resultatene.

Her finnes det en lett og en mer omstendelig måte å gjøre det på.

Enkle tabeller med beskrivende statistikk.

stargazer-pakken genererer automatisk tabeller med beskrivende statistikk for oss.

Om jeg ikke har hentet den fra biblioteket ennå, gjør jeg det nå.

library(stargazer)
## 
## Please cite as:
##  Hlavac, Marek (2022). stargazer: Well-Formatted Regression and Summary Statistics Tables.
##  R package version 5.2.3. https://CRAN.R-project.org/package=stargazer

Tabellen bruker alle variablene i datasettet med mindre jeg oppgir noe annet. De fleste datasett er så store at vi ikke ønsker å bruke alt som finnes. Her velger jeg ut variablene jeg ønsker først og lagerer dem i et eget objekt.

tabell_data <-
  df %>%
  dplyr::select(Skepsis, Prekaritet,
         Innvandrer, Inntekt,
         SubjektivInntekt)

Deretter putter jeg det nye dataobjektet direkte inn i stargazer. Funksjonen vil gi meg en latex-kode om jeg ikke presiserer annet, så her har jeg bedt om å se resultatet som tekst.

stargazer(tabell_data,
          type = "text")
Statistic N Mean St. Dev. Min Max
Skepsis 1,396 4.452 1.506 0.000 9.333
Prekaritet 1,253 0.200 0.400 0 1
Innvandrer 1,434 0.158 0.364 0 1
Inntekt 1,371 5.322 2.830 1 10
SubjektivInntekt 1,432 2.554 0.666 0 3

Jeg kan velge hvilken statistikk jeg ønsker å rapportere. Her har jeg valgt å se kvartilene, samt gjennomsntittet og antall observasjoner i hver variabel.

Statistic Min Pctl(25) Mean Median Pctl(75) Max N
Skepsis 0.000 3.333 4.452 4.333 5.333 9.333 1,396
Prekaritet 0 0 0.200 0 0 1 1,253
Innvandrer 0 0 0.158 0 0 1 1,434
Inntekt 1 3 5.322 5 8 10 1,371
SubjektivInntekt 0 2 2.554 3 3 3 1,432

Hjemmelagede tabeller

Man kan også gjøre disse tingene “for hånd” med piper. Det er en god unskyldning for å lære å gå tur-retur fra bredt til langt dataformat.

I denne tabellen ønsker jeg å rapportere statistikk for hver variabel langs hver linje. Jeg begynner med å definere hvilke variabler og statistikk jeg skal bruke. Noter deg at jeg lager en vektor med variabelnavn, men en liste med funksjoner. Det er på grunn av hvordan funksjonen min vil lese informasjonen.

variabler <- c("Skepsis", 
               "Innvandrer", 
               "Prekaritet",
               "Inntekt")

funksjoner <- list(min, mean, median, max)

Vanligvis vil summarize() gi statistikk kun for én variabel av gangen. Her finner jeg gjennomsnittet av innvandringsskepsis.

df %>%
  summarize(Skepsis = mean(Skepsis, na.rm = T))

Om vi ønsker statistikk for to variabler, vil vi vanligvis måtte gjenta koden ved hjelp av litt “copy-paste”.

df %>%
  summarize(Skepsis = mean(Skepsis, na.rm = T),
            Innvandrer = mean(Innvandrer, na.rm = T))

Det blir tungvint i lengden.

Sammendrag for flere variabler Vi kan gjøre den samme operasjonen på flere variabler samtidig ved hjelp av across(). Den krever at vi forteller hvilke variabler som skal behandles (.col =), samt hvilken funksjon som skal brukes (.fun =). I dette tilfellet, må jeg også komme med enda et tilleggsargument: Hvordan NA skal behandles.

df %>% 
  summarize(across(.cols = c(Skepsis, Innvandrer), 
                   mean, 
                   na.rm = T))

I stedet for å liste opp variablene, kan jeg bruke objektet jeg lagde med alle variabelnavnene, samt objektet med alle funksjonene jeg skal bruke.

Siden jeg er fornøyd med resultatet, tar jeg vare på det i et objekt. Om du heller ønsker å fortsette pipen, kan du det.

tabell <- 
  df %>% 
  summarize(across(.cols = variabler, 
                   funksjoner, 
                   na.rm = T))
## Note: Using an external vector in selections is ambiguous.
## ℹ Use `all_of(variabler)` instead of `variabler` to silence this message.
## ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This message is displayed once per session.
tabell

Den første kolonnen rapporterer resultatet fra den første funksjonen (min()) for den første variabelen (Skepsis), deretter den andre funksjonen (mean()) for den første variabelen osv.

Dette er et datasett i bredt format. Ideelt sett har jeg lyst til å ha alle gjennomsnittene i en variabel, osv. Det vil si at jeg ønsker et datasett i langt format.

Her kommer tidyr-pakken godt med: Den inneholder en funksjon nettopp til dette: pivot_longer(). Som et minimum, må jeg oppgi hvilke variabler som skal settes sammen. Her har jeg valgt alle variabler.

tabell <-
  tabell %>%
  pivot_longer(everything())
tabell 

Resultatet er en tabell med to kolonner. R har selv valgt å legge alle variabelnavn inn i en ny variabel (name) hvor verdiene for hver observasjon er i value.

Nå kan jeg lage to nye variabler. En som presiserer hvilken funksjon jeg brukte for de ulike kategoriene, og en som forteller hvilken variabel jeg har laget sammendrag av.

Til dette bruker jeg gsub() funksjonen: Første argument oppgir hvilket regelmessig uttrykk som flagger funksjonen (her er det et tall), deretter oppgir jeg hva jeg ønsker å erstatte verdien med.

tabell <-
  tabell %>%
  mutate(stat = gsub(".*_1", "Minimum", name),
         stat = gsub(".*_2", "Gjennomsnitt", stat),
         stat = gsub(".*_3", "Median", stat),
         stat = gsub(".*_4", "Maksimum", stat),
         Variabel = gsub("_.*", "", name))
tabell

Jeg plukker ut de variablene jeg ønsker å beholde, så er det på tide å reformatere til et bredt format igjen. Jeg ønsker en ny linje per variabel i datasettet, men en ny kolonne per nye funksjon jeg har brukt.

pivot_wider() hjelper meg med dette. Denne gangen kan jeg opplyse om hvilken variabel som har kategorier som også egner seg som kolonner. I dette tilfellet, er det stat- variabelen.

tabell <- 
  tabell %>%
  select(Variabel, value, stat) %>%
  pivot_wider(names_from = stat)

tabell

Voilà! Nå har jeg en tabell med statistikken jeg ønsker, og jeg kan begynne med de estetiske tweaksene.

Jeg begynner med å omdefinere beskrivelsen av variablene.

tabell$Variabel <- c("Innvandringsskepsis (0-10)",
                     "Innvandrer (0 eller 1)",
                     "Utrygg tilknytning til arbeidslivet (0 eller 1)",
                     "Inntekt (desiler; 1-10)")
tabell
kbl(tabell,
    digits = 1,
    format.args = list(decimal.mark = ","),
    caption = "Beskrivende statistikk") %>%
  kable_classic(full_width = F, 
                html_font = "Cambria") %>%
  column_spec(1, 
              italic = T) 
Beskrivende statistikk
Variabel Minimum Gjennomsnitt Median Maksimum
Innvandringsskepsis (0-10) 0 4,5 4,3 9,3
Innvandrer (0 eller 1) 0 0,2 0,0 1,0
Utrygg tilknytning til arbeidslivet (0 eller 1) 0 0,2 0,0 1,0
Inntekt (desiler; 1-10) 1 5,3 5,0 10,0

Om jeg er fornøyd med hva jeg har gjort, kan jeg nå skrive ut tabellen.

kbl(tabell,
    digits = 1,
    format.args = list(decimal.mark = ","),
    caption = "Beskrivende statistikk") %>%
  kable_classic(full_width = F, 
                html_font = "Cambria") %>%
  column_spec(1, 
              italic = T) %>%
  save_kable(file = "trivariat_tabell.html")

Her er hele pipa

df %>% 
  summarise(across(variabler, 
                   (funksjoner), 
                        na.rm = T)) %>%
  pivot_longer(everything(),
               values_to = c("value"),
               names_to = c("name")) %>%
  mutate(stat = gsub(".*_1", "Min", name),
         stat = gsub(".*_2", "Gjennomsnitt", stat),
         stat = gsub(".*_3", "Median", stat),
         stat = gsub(".*_4", "Maks", stat),
         Variabel = gsub("_.*", "", name)) %>%
  select(Variabel, value, stat) %>%
  pivot_wider(names_from = stat)