8  Deskriptive tabeller

library(tidyverse)
library(gtsummary)

Det kan være ulike grunner til å lage deskriptiv statistikk, og hva du skal bruke tabellene til kan ha betydning for hvordan du lager dem. Noen ganger skal du bare sjekke noen tall, og da er det ingen grunn til å bruke tid på å gjøre tabellen spesiell pen. Andre ganger skal tabellen publiseres i en rapport, på en nettside eller i en vitenskapelig artikkel - eller mest aktuelt på kort sikt: i en masteroppgave. Da må tabellene se ordentlige ut. Nedenfor skal vi se på begge mulighetene.

8.1 Quick-and-dirty oppsummeringer

Først og fremst har vi funksjonen summary(). Når denne brukes på et objekt vil hva slags output du får avhenge av objekttypen. Derfor vil summary() gi forskjellig output om det er en vektor, et datasett eller et regresjonsobjekt etc. Vi avgrenser oss til datasett her.

Her er output for hele datasettet.

summary(abu89)
     io_nr          time89             ed             age       
 Min.   :   3   Min.   : 25.00   Min.   : 0.00   Min.   :16.00  
 1st Qu.:1542   1st Qu.: 71.00   1st Qu.: 1.00   1st Qu.:30.00  
 Median :3093   Median : 83.33   Median : 3.00   Median :39.00  
 Mean   :3105   Mean   : 90.15   Mean   : 2.69   Mean   :39.65  
 3rd Qu.:4644   3rd Qu.:102.56   3rd Qu.: 3.00   3rd Qu.:48.00  
 Max.   :6258   Max.   :343.75   Max.   :11.00   Max.   :74.00  
                NA's   :368                                     
     female                           klasse89    promot          fexp       
 Min.   :0.0000   I Øvre serviceklasse    : 328   NEI:2568   Min.   :0.0000  
 1st Qu.:0.0000   II Nedre serviceklasse  :1181   JA :1559   1st Qu.:0.2000  
 Median :0.0000   III Rutinefunksjonærer  :1248              Median :0.7000  
 Mean   :0.4686   V-VI Faglærte arbeidere : 648              Mean   :0.9451  
 3rd Qu.:1.0000   VIIa Ufaglærte arbeidere: 637              3rd Qu.:1.4000  
 Max.   :1.0000   NA's                    :  85              Max.   :4.9000  
                                                                             
    private    
 Public :1602  
 Private:2525  
               
               
               
               
               

Merk at summary() rapporterer forskjellig basert på om variabelen er kontinuerlig eller kategorisk. For kontinuerlige variable gis min/max, kvartiler, median og gjennomsnitt. For kategoriske variable gis det antall i hver kategori. Hvis det er manglende verdier på en variabel står det oppført nederst som antall NA's.

Merk her at variabelen female er definert som kontinuerlig selv om det bare er to verdier. Det ville være mer hensiktsmessig å gjøre om denne variabelen til kategorisk.

Man kan også bruke summary() på enkeltvariable med bruk av $ som følger:

summary(abu89$time89)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  25.00   71.00   83.33   90.15  102.56  343.75     368 

Da får man altså bare tallene for den variabelen man har angitt etter dollartegnet.

8.1.1 Enkeltfunksjoner

Man kan hente ut hvert av disse tallene spesifikt fremfor å bruke summary(). Det er egne funksjoner for dette, og de kan også brukes når man gjør databearbeiding for litt andre formål. Vi ser her på de viktigste.

Hva om man vil ha en kvartil som ikke er oppgitt i forvalget? Da kan man bruke funksjonen quantile(). Argumentene i denne funksjonen er hvilken variabel og hvilket prosentil. Som vi ovenfor inneholder time89 noen NA. Vi må i tillegg bestemme hva vi ønsker å gjøre med NA i beregningen, og vi vil her se bort fra disse ved å angi na.rm = TRUE. Ellers får man feilmelding.

Her er eksempel med første kvartil som skal gi samme svar som ovenfor:

quantile(abu89$time89, .25, na.rm = TRUE)
25% 
 71 

Her er en variant der man ber om 95-prosentilen:

quantile(abu89$time89, .95, na.rm = TRUE)
     95% 
148.0362 

Man kan også be om flere prosentiler. Da listes disse opp innenfor en c() som følger. Her gis prosentilene for 5, 10, 90 og 95 prosent.

quantile(abu89$time89, c(.05, .10, .90, .95), na.rm = TRUE)
       5%       10%       90%       95% 
 54.91651  61.00000 127.77778 148.03618 

Gjennomsnittet av en variabel gis ved funksjonen mean():

mean(abu89$time89, na.rm = TRUE)
[1] 90.14948

Standardavviket gis ved sd():

sd(abu89$time89, na.rm = TRUE)
[1] 30.31473

Medianen kan angis med quantile(), men enklere med median():

median(abu89$time89, na.rm = TRUE)
[1] 83.33333

Vi trenger også ofte antall. nrow() gir antall rader, dvs. antall observasjoner i datasettet

nrow(abu89)
[1] 4127

Tilsvarende gir ncol() antall kolonner, mens dim() gir begge deler:

ncol(abu89)
[1] 9
dim(abu89)
[1] 4127    9

8.2 Professjonelle tabeller med gtsummary

For å lage ordentlig professjonelle tabeller kreves det mer. For det første skal de se ordentlige ut, men de skal også kunne eksporteres til andre formater på en hensiktsmessig måte.

I R finnes det en hel rekke slike funksjoner. Her har vi vektlagt pakken gtsummary fordi den gir gode tabeller fra helt enkle til ganske avanserte relativt lett. Det er også mange muligheter for å justere tabellene slik du vil. Dessuten kan resultatene eksporteres lett til de fleste aktuelle formater (Word, html, pdf, Excel, latex).

Avanserte brukere vil muligens se begrensningene i denne pakken og foretrekke noe annet. De fleste vil kunne lage det aller meste med denne pakken.

Vi starter med en enkel oversiktstabell med alle variablene i datasettet. Men vi fjerner løpenummeret for person, nemlig variabelen io_nr fordi den ikke inneholder noe analyserbar informasjon.

abu89 %>% 
  select(-io_nr) %>% 
  tbl_summary()
Characteristic N = 4,1271
Gjennomsnittlig timelønn 1989 83 (71, 103)
    Unknown 368
År utdanning
    0 839 (20%)
    1 1,156 (28%)
    3 1,121 (27%)
    5 483 (12%)
    7 308 (7.5%)
    9 205 (5.0%)
    11 15 (0.4%)
Alder 39 (30, 48)
Respondentens kjønn 1,934 (47%)
Goldthorpe klasse 1989
    I Øvre serviceklasse 328 (8.1%)
    II Nedre serviceklasse 1,181 (29%)
    III Rutinefunksjonærer 1,248 (31%)
    V-VI Faglærte arbeidere 648 (16%)
    VIIa Ufaglærte arbeidere 637 (16%)
    Unknown 85
Noen gang forfremmet
    NEI 2,568 (62%)
    JA 1,559 (38%)
Bedriftserfaring 0.70 (0.20, 1.40)
Privat sektor
    Public 1,602 (39%)
    Private 2,525 (61%)
1 Median (IQR); n (%)

Legg merke til at tbl_summary gjør en del ting automatisk. Først og fremst er bruker den variabel label og factor levels i sidespalten. Ofte vil ikke variable ha slike labler, og da vil det vises variabelnavnene. Variabelen kjønn har ikke angitt factor levels, og variabelen har bare verdiene 0 og 1, og da rapporteres kun den ene kategorien (dvs. verdien 1). Vi kan legge til annen tekst hvis vi ønsker.

Dernest er det en forhåndsinnstilling som angir at det for kontinuerlige variable skal rapporteres median og interquartile range (IQR), dvs. nedre og øvre kvartil i parentes. Det gir en god beskrivelse av variablene, men vi skal endre dette nedenfor. For kategoriske variable rapporteres det antall observasjoner og andelen i prosent i parentes.

Men merk at for antall år utdanning og kjønn, så er det rapportert som kategoriske variable selv om variabeltypen er kontinuerlig. tbl_summary gjør dette fordi det er relativt få kategorier slik at median og IQR ikke er så interessant uansett.

La oss først endre slik at det rapporteres gjennomsnitt og standardavvik i stedet. Det er mer vanlig å gjøre selv om det ikke er noen regel for dette. Funksjonen theme_gtsummary_mean_sd() endrer standardvalget for tbl_summary i alle etterfølgende tabeller. Dermed slipper du endre neste gang. Flere themes finner du på pakkens hjemmeside. For å gå tilbake til opprinnelig theme brukes funksjonen reset_gtsummary_theme().

Vi kan endre andre ting ved tabellen med noen enkle grep. Alle variable kan endre navn i forspalten med å legge til argumentet label =. Nedenfor er to variable endret for å vise hvordan man endrer flere variable. Når det er flere variable må de spesifiseres innenfor argumentet list() som nedenfor. Her endrer vi også label for variabelen female og klasse89.

Noen ganger kan man også ønske å endre hvordan en variabel presenteres. Et vanlig behov er å presisere hvilken type en variabel er. I dette tilfellet er utdanning antall år etter obligatorisk skolenivå, så det er egentlig en kontinuerlig variabel selv om antall verdier er få. Vi kan velge å presisere at denne er av typen continuous. Nedenfor presiserer vi også at female er kategorisk, dichotomous, selv om denne ble presentert riktig uansett. Vi bruker argumentet type = og flere variable må oppgis innenfor list().

En siste ting vi kan endre er å ikke rapportere NA. Det er ikke oppgitt timelønn for alle, så antall NA er rapportert for seg. Det kan være fint, men kan også hende vi ikke ønsker det. Nedenfor er det derfor også lagt til missing = "no".

theme_gtsummary_mean_sd()
abu89 %>% 
  select(-io_nr) %>% 
  tbl_summary(label = list(female ~ "Kjønn", klasse89 = "Klasse"), 
              type = list(ed ~ "continuous", female ~ "dichotomous"), 
              missing = "no")
Characteristic N = 4,1271
Gjennomsnittlig timelønn 1989 90 (30)
År utdanning 2.69 (2.56)
Alder 40 (12)
Kjønn 1,934 (47%)
Klasse
    I Øvre serviceklasse 328 (8.1%)
    II Nedre serviceklasse 1,181 (29%)
    III Rutinefunksjonærer 1,248 (31%)
    V-VI Faglærte arbeidere 648 (16%)
    VIIa Ufaglærte arbeidere 637 (16%)
Noen gang forfremmet
    NEI 2,568 (62%)
    JA 1,559 (38%)
Bedriftserfaring 0.95 (0.91)
Privat sektor
    Public 1,602 (39%)
    Private 2,525 (61%)
1 Mean (SD); n (%)

Ofte vil vi ha en tabell som ikke bare viser univariat fordeling, men bi-variate, altså fordelt på to eller flere grupper. Det er f.eks. ganske vanlig å vise tabeller fordelt på kjønn. Det kan vi også gjøre her ved å legge til argumentet by = female. Nedenfor er det også forenklet argumentene for label = og type =. I slike tilfeller vil vi ofte ha totalen i tillegg til per gruppe, og det gjør vi ved å legge til funksjonen add_overall().

For de kontinuerlige variablene får vi ikke antallet som inngår i beregningene. Vi vil gjerne vise antall ikke-missing verdier - særlig fordi vi tok vekk NA som egen rad ovenfor. Dette gjør vi ved å legge til funksjonen add_n().

abu89 %>% 
  select(-io_nr) %>% 
  mutate(female = ifelse(female == 0, "Menn", "Kvinner")) %>% 
    tbl_summary(by = female, 
                label = list(klasse89 = "Klasse"), 
              type = list(ed ~ "continuous"), 
              missing = "no") %>% 
  add_overall() %>% 
  add_n()
Characteristic N Overall, N = 4,1271 Kvinner, N = 1,9341 Menn, N = 2,1931
Gjennomsnittlig timelønn 1989 3,759 90 (30) 79 (24) 100 (32)
År utdanning 4,127 2.69 (2.56) 2.38 (2.40) 2.96 (2.66)
Alder 4,127 40 (12) 40 (13) 40 (12)
Klasse 4,042
    I Øvre serviceklasse 328 (8.1%) 74 (3.9%) 254 (12%)
    II Nedre serviceklasse 1,181 (29%) 555 (29%) 626 (29%)
    III Rutinefunksjonærer 1,248 (31%) 986 (52%) 262 (12%)
    V-VI Faglærte arbeidere 648 (16%) 46 (2.4%) 602 (28%)
    VIIa Ufaglærte arbeidere 637 (16%) 244 (13%) 393 (18%)
Noen gang forfremmet 4,127
    NEI 2,568 (62%) 1,308 (68%) 1,260 (57%)
    JA 1,559 (38%) 626 (32%) 933 (43%)
Bedriftserfaring 4,127 0.95 (0.91) 0.83 (0.81) 1.05 (0.97)
Privat sektor 4,127
    Public 1,602 (39%) 1,016 (53%) 586 (27%)
    Private 2,525 (61%) 918 (47%) 1,607 (73%)
1 Mean (SD); n (%)

Men vi kan lage mer kompliserte tabeller også. La oss si at vi ønsker å lage den samme tabellen som over, men fordelt på to grupper. Det kan være relevant å sammenligne offentlig og privat sektor. En mulighet er å lage en ny grupperingsvariabel ved å slå sammen kjønn og sektor slik at vi får fire kategorier. Men vi får et bedre resultat ved å lage en stratifisert tabell med funksjonen tbl_strata(). Det er litt kryptisk syntaks, men det viktige er å angi hvilken variabel det skal stratifiseres etter med argumentet strata = etterfulgt av .tbl_fun = ~ .x %>%, så kommer tble_summary etter dette. Her er det også lagt til en ekstra header med antall observasjoner.

abu89 %>% 
  select(-io_nr) %>% 
  mutate(female = ifelse(female == 0, "Menn", "Kvinner")) %>% 
  tbl_strata(strata = private, 
             .tbl_fun = 
               ~ .x %>%
               tbl_summary(by = female, 
              label = list(klasse89 = "Klasse"), 
              type = list(ed ~ "continuous"), 
              missing = "no") %>%
               add_n(),
    .header = "**{strata}**, N = {n}"
    )
Characteristic Public, N = 1602 Private, N = 2525
N Kvinner, N = 1,0161 Menn, N = 5861 N Kvinner, N = 9181 Menn, N = 1,6071
Gjennomsnittlig timelønn 1989 1,403 82 (23) 100 (28) 2,356 76 (24) 100 (33)
År utdanning 1,602 2.88 (2.67) 4.22 (3.12) 2,525 1.82 (1.92) 2.50 (2.31)
Alder 1,602 42 (12) 43 (11) 2,525 37 (13) 39 (12)
Klasse 1,592 2,450
    I Øvre serviceklasse 55 (5.4%) 150 (26%) 19 (2.1%) 104 (6.7%)
    II Nedre serviceklasse 340 (34%) 196 (34%) 215 (24%) 430 (28%)
    III Rutinefunksjonærer 507 (50%) 64 (11%) 479 (54%) 198 (13%)
    V-VI Faglærte arbeidere 5 (0.5%) 114 (20%) 41 (4.6%) 488 (31%)
    VIIa Ufaglærte arbeidere 104 (10%) 57 (9.8%) 140 (16%) 336 (22%)
Noen gang forfremmet 1,602 2,525
    NEI 696 (69%) 318 (54%) 612 (67%) 942 (59%)
    JA 320 (31%) 268 (46%) 306 (33%) 665 (41%)
Bedriftserfaring 1,602 0.93 (0.85) 1.15 (0.94) 2,525 0.72 (0.74) 1.01 (0.98)
1 Mean (SD); n (%)

Det går også an å lage langt mer avanserte tabeller enn dette, og alle deler kan modifiseres. Men vi går ikke inn på dette her. Ved behov finner du instruksjoner på pakkens hjemmeside.

8.2.1 Eksport av tabeller

Du skal aldri bruke “klipp og lim” for å få en tabell over i et tekstbehandlingsprogram. Trikset er å konvertere tabellen til gt-format som har en eksportfunksjon til MS Word.

Først lagres tabellen i et eget objekt.

fintabell <- abu89 %>% 
  select(-io_nr) %>% 
  mutate(female = ifelse(female == 0, "Menn", "Kvinner")) %>% 
  tbl_strata(strata = private, 
             .tbl_fun = 
               ~ .x %>%
               tbl_summary(by = female, 
              label = list(klasse89 = "Klasse"), 
              type = list(ed ~ "continuous"), 
              missing = "no") %>%
               add_n(),
    .header = "**{strata}**, N = {n}"
    )

Så kan tabellen eksporteres til Word, og evt. redigeres videre der hvis det trengs. På dette nivået kan det være mer tidsbesparende å gjøre siste justeringer i Word fremfor å lære alle triks for å lage tabellen fiks ferdig i R. (Skal du lage mange tabeller kan det likevel lønne seg å gjøre mest mulig i R).

fintabell %>% 
  as_gt() %>% 
  gt::gtsave(filename = "output/fintabell.docx")

Merk at eksport til docx-formatet krever at du har en relativt ny installasjon av pakkene {gt} og {gtsummary}. Filhalen “.docx” innebærer at filen lagres i dette formatet. Tilsvarende kan du lagre i .html, .pdf, .rtf, .png, .tex eller .ltex bare ved å endre filhalen.

En tilsvarende variant som noen av dere har lært på sosgeo1120 er å bruke as_flextable og en tilsvarende eksportfunksjon. Det er selvsagt også helt ok. En tidligere versjon av {gt} kunne som sagt ikke eksportere til Word, så da var {flextable} beste løsning. Men pakken {flextable} har vist seg å være litt trøblete å installere på noen pc’er, så da er det bedre å bruke {gt}.

8.3 Manuelle tabeller

Noen ganger trenger man å lage ganske spesifikke ting.

8.3.1 For datasettet totalt

8.3.2 Grupperte statistikker

8.4 Oppgaver

Slå opp i boken R for data science hvis du står fast eller ikke skjønner hva koden betyr.

Exercise 8.1 Bruk datasettet abu89 og lag de samme tabellene som vist her, gjør noen endringer på kodene for å endre utseendet på tabellene. Det er et mål at du skal forstå hva hver enkelt kommando gjør.

Exercise 8.2 Last inn datasettet NorLAG i R. Velg noen variable som du selv tenker kan være informative å se nærmere på. Bruk de samme teknikkene på disse variablene.