Sesjon 7-8: Tilrettelegge data

R-workshop 30. og 31. august, OsloMet

Når vi har utforsket datasettet, kan vi begynne med å tilrettelegge dataene.

Dette kan vi gjøre i baseR eller i tidyverse. Vi begynner med base, slik at dere blir kjent med matriselogikken i R, så gjør vi større operasjoner i dplyr-pakken (tidyverse).

Vi omkoder:

  • fra lavt til høyere målenivå: indekser
  • rette kategorier
  • aggregere

Last inn data

#Fra lokal fil
load("kap6.rda")
#Rett ned fra nettet
load(url("https://siljehermansen.github.io/teaching/oslomet/kap6.rda"))

Additive indekser: kontinuerlige variabler

Vi benyttet oss av en indeks allerede da vi analyserte respondentenes holdning til {}konomiske argumenter mot innvandring (Skepsis). Indeksen består av tre tellevariabler (TaJobber, TaSkatt og Belastning med svarkategorier fra 0 til ) som jeg har satt sammen til en additiv indeks.

Den mest banale måten å gjøre dette på er med plusstegnet mellom hver variabel.

df$TaJobber+df$TaSkatt+df$Belastning

Om vi ønsker å beholde min-max verdiene, deler vi på antall variabler (kolonner). Merk deg parentesen du ville satt om du skrev dette matematisk.

(df$TaJobber+df$TaSkatt+df$Belastning)/3

… men det finnes en funksjon som summerer rader for deg også. Da bruker jeg matriseindeksering.

rowSums(df[,c("TaJobber", "TaSkatt", "Belastning")])

… gjennomsnittet…

rowMeans(df[,c("TaJobber", "TaSkatt", "Belastning")])

… og her er første NA imputeringstriks. Har du observasjoner på 1 eller 2 av 3 variabler? Legg til na.rm = T og regn gjennomsnittet av de observerte variablene for å tette hullene.

rowMeans(df[,c("TaJobber", "TaSkatt", "Belastning")]) %>%
  is.na() %>%
  sum()
## [1] 40

Hvor mange NA har jeg nå?

rowMeans(df[,c("TaJobber", "TaSkatt", "Belastning")],
         na.rm = T) %>%
  is.na() %>%
  sum()
## [1] 3

Det var en kjapt tur til tannlegen.

Husk å lagre resultatet i en ny, omkodet variabel.

df$indeks = rowMeans(df[,c("TaJobber", "TaSkatt", "Belastning")],
                     na.rm = T) 

Skal du snu skalaretningen? Gang med -1 og pluss på maksverdien

(df$indeks*-1) + max(df$indeks, 
                     na.rm = T)

NB: Denne koden kan du ikke kjøre i to omganger, siden maksverdien endrer seg. Et triks er å gjøre alle koder robuste; slik at du kan kjøre den samme koden flere ganger uten å få ulikt svar. Måten å gjøre det på, er å alltid lagre nye variabler i nye variabelnavn

Nei, nei, nei!

df$indeks <- (df$indeks*-1) + max(df$indeks, 
                                  na.rm = T)
(df$indeks*-1) + max(df$indeks, 
                   na.rm = T)

Gi alltid nytt navn.

df$indeks_snudd <- (df$indeks*-1) + max(df$indeks, 
                                        na.rm = T)
(df$indeks*-1) + max(df$indeks, 
                   na.rm = T)

Du kan bruke de fleste matematiske operasjoner på variabelen din, så slå deg løs. Her omkoder jeg slik at skalaen går fra 0 til 1 i stedet for 1 til 10.

df$indeks/10

Sjekk resultatet

Jeg sjekker alltid omkodingene mine. Da kommer kodene fra forrige kapittel godt med.

Her plotter jeg indikatorene mine mot hverandre og mot indeksen.

plot(df[,c("TaJobber", "TaSkatt", "Belastning", "indeks")])

Hvordan gikk snuoperasjonen?

plot(df$indeks, df$indeks_snudd)

Hvordan ser fordelingen ut? i.e. fikk jeg egentlig et høyere målenivå?

hist(df$indeks)

Betinget omkoding: kategoriske variabler

For hånd med indeksering

Vi har allerede lekt gjetteleken noen ganger. Den kommer godt med når vi skal omkode.

Her setter jeg én betingelse

df$Inntekt < 3

Her setter jeg to

df$Inntekt > 3 & df$Inntekt < 7 

Lag en ny variabel

df$Inntekt_kat = NA

Lav inntekt

df$Inntekt.kat[df$Inntekt <= 3] = "Lav"
df$Inntekt.kat[df$Inntekt > 3 & df$Inntekt < 7 ] = "Middels"
df$Inntekt.kat[df$Inntekt >= 7 ] = "Hoy"

Har det fungert?

table(df$Inntekt.kat)
## 
##     Hoy     Lav Middels 
##     495     432     444

Hva slags målenivå er dette?

class(df$Inntekt.kat)
## [1] "character"

Skal vi omkode til kategorisk?

df$Inntekt.kat <- factor(df$Inntekt.kat, 
                         levels = c("Lav", "Middels", "Hoy"))

… eller ordinal

df$Inntekt.kat <- factor(df$Inntekt.kat, 
                         levels = c("Lav", "Middels", "Hoy"),
                         ordered = TRUE)

Regelmessige uttrykk (Superkraften!)

Når vi bruker erlik-tegnet, må vi skrive innholdet (verdien) helt korrekt. Men vi kan få R til å kjenne igjen tekstuttryk.

Kjærnefunksjonene i baseR er gsub() og grep().

Ta grep

Her griper jeg bokstavene “dd”. Svaret er linjenummeret i datasettet.

grep("dd", df$Inntekt.kat)

Dette kan jeg bruke for omkoding ved hjelp av indeksering. Hvilke inntektsverdier inneholder “dd”?

df$Inntekt.kat[grep("dd", df$Inntekt.kat)]

Funksjonen grepl() gir TRUE/FALSE-svar. Det er nyttig når du skal lage en ny variabel.

df$rik <- grepl("Hoy", df$Inntekt.kat)

R leser TRUE som 1 og FALSE som 0.

sum(df$rik)
## [1] 495

Du kan eksplisitt omkode om du ønsker

df$rik <- grepl("Hoy", df$Inntekt.kat) %>%
  as.numeric()
table(df$rik)
## 
##   0   1 
## 941 495

Bytt ut

Du kan substituere bokstaver med andre.

gsub(pattern = ". problem", 
     replacement = "n utfordring", 
     x = "vi har et problem")
## [1] "vi har en utfordring"

Merket du bruken min av “.”?

Din tur!

Det finnes egne regex-koder for å finne fram regelmessige uttrykk.

Lek deg rundt med kodene fra tabellen!

Mitt forslag:

grep("^noe .*",
     c("ikkenoe", "noe annet", "noe mer", "nei,nei!", "Noe"))
## [1] 2 3
#enda mer inkluderende
grep("^(N|n)oe .*",
     c("ikkenoe", "noe annet", "noe mer", "nei,nei!", "Noe"))
## [1] 2 3

Dette er grunnsteinen jeg bruker når jeg samler data automatisk (“data scraping”). Men det er også nyttig for å finne alle relevante observasjoner selv om jeg ikke husker/det er skrivefeil i verdinavnene.

Ferdigfunksjoner

Man kan alltids copy-paste med indeksering og endre teksten vi trenger. Dere kommer dere til mål, men det finnes en del snarveier.

Hvis/hvis ikke En praktisk funksjon er hvis/hvis-ikke.

ifelse(df$Inntekt <= 3, 
       yes = "Lav",
       no = NA)

Denne kan nøstes

ifelse(df$Inntekt <= 3, 
       yes = "Lav",
       no = ifelse(df$Inntekt > 7,
                   yes = "Hoy",
                   no = "Middels"))

Piper og dplyr ( tidyverse)

dplyr er en fantastisk pakke for tilrettelegging av data; særlig når vi begynner å aggregere.

Indeksering er ikke nødvendig

I tidyverse, indekserer vi egentlig ikke, men vi kan hente frem spesifikke variabler og observasjoner. Derimot bruker vi ikke disse funksjonene når vi omkoder.

Spesifikke observasjoner

#base
df[df$Inntekt > 3,]
#dplyr
df %>%
  filter(Inntekt > 3)

Spesifikke variabler: da selekterer du variablene.

#baseR
df[,"Inntekt"]
#dplyr
df %>%
  select(Inntekt)

Du vil skjelden bruke select() når du koder om. I stedet vil du legge til nye variabler direkte i datasettet. Derimot kan du hente ut variabler når du ser på dataene eller ønsker å dele et “rent” datasett.

Omkoding: i ett skritt

Når vi lager nye variabler, er mutate() gull. Som andre funksjoner i denne serien, kan du legge inn egne funksjoner inn i funksjonen når du oppretter ny variabel. Husk dog å lagre datasettet (her: ved å overskrive).

df <- df %>%
  mutate(Inntekt_omk = ifelse(Inntekt <= 3, "Lav",
                              ifelse(Inntekt > 7, "Hoy", "Middels")))

Noter deg hvordan jeg ikke indekserer. Jeg jobber hele tiden med hele datasettet, men presiserer hvilke variabler og variabelverdier som skal brukes for å opprette den nye variabelen.

Sjekk:

df$Inntekt_omk %>% table
## .
##     Hoy     Lav Middels 
##     378     432     561

Slå sammen data

Ofte vil vi slå sammen informasjon (aggregere) eller datasett.

Vi begynner med å laste inn datasettet fra Chapel Hill-undersøkelsen.

Datasettet inneholder tre variabler: id rapporterer ekspertens anonymiserte id-nummer. party_name identifiserer partiet eksperten uttaler seg om, og `immigrate_policy´ rapporterer hvordan hvert parti rangeres på en skala fra 0 (negativ til restriktiv innvandringspolitikk) til 10 (positiv).

Jeg begynner med å laste ned dataene fra nettet og inn i R.

#Fra lokal fil
load("kap6b.rda")
#eller rett ned fra nettet
load(url("https://siljehermansen.github.io/teaching/oslomet/kap6b.rda"))
#endre navn
df2 <- kap6b

Sjekk ut

head(df2)

Datastrukturen består av flere eksperter som uttaler seg om samme parti; et klassisk eksempl på noe jeg ønsker å aggregere til parti-nivå.

Aggregere data

group_by() revolusjonerte mitt liv. Her tar jeg gjennomsnitlig holdning til innvandring for hvert parti. Da får jeg rangert partiene etter holdning.

df2 <- df2 %>%
  group_by(party_name) %>%
  mutate(skepsis = mean(immigrate_policy))

Det er viktig å gi funksjonene til R i riktig rekkefølge: Du ønsker å gruppere dataene først, så omkode; ikke omvent!

Hvordan ser norske partiers holdninger ut?

df2 %>%
  #Velg de to variablene jeg er interessert i
  select(party_name, skepsis) %>%
  #Fjern alle duplikater
  unique()

… ooops… jeg hadde hull i datasettet. Rykk to skritt bakover og legg til na.rm = T (den gode, gamle R-problematikken).

df2 <- df2 %>%
  group_by(party_name) %>%
  mutate(skepsis = mean(immigrate_policy, na.rm = T))
#Gikk det nå?
df2 %>%
  #Velg de to variablene jeg er interessert i
  select(party_name, skepsis) %>%
  #Fjern alle duplikater
  unique()

Jeg kan organisere dataene

df2 %>%
  #Velg de to variablene jeg er interessert i
  select(party_name, skepsis) %>%
  #Fjern alle duplikater
  unique() %>%
  #sorter fra lav til høy
  arrange(skepsis)

Her skal jeg legge informasjon om innnvandringskepsis på partinivå til ESS dataene på respondentnivå.

Aller først må jeg sjekke om partinavnene er de samme.

df$Parti %>% unique  
##  [1] FRP   A     KRF   H     <NA>  MDG   SV    SP    Other V     RODT  KYST 
## Levels: A FRP H KRF KYST MDG Other RODT SP SV V
df2$party_name %>% unique
## [1] A   FrP H   SV  Sp  KrF V   MDG
## Levels: A FrP H KrF MDG SV Sp V

Jeg bør bruke øynene, men jeg kan også sjekke på andre måter. Her spør jeg om alle partiene fra ESS (df) er tilstede i CHES (df2).

all(df$Parti %in% df2$party_name)
## [1] FALSE

Det er de visst ikke.

Hvilke observasjoner har jeg data på?

df$Parti[df$Parti %in% df2$party_name] %>% unique
## [1] A   H   MDG SV  V  
## Levels: A FRP H KRF KYST MDG Other RODT SP SV V

Hvilke observasjoner har jeg ikke data på?

df$Parti[!df$Parti %in% df2$party_name] %>% unique
## [1] FRP   KRF   <NA>  SP    Other RODT  KYST 
## Levels: A FRP H KRF KYST MDG Other RODT SP SV V

Din tur!

Her må vi kode om: noen av partiene inneholder små bokstaver. Kod om CHES dataene (df2) slik at alle partier korresponderer med ESS (df).

  • kan du komme på en løsning selv?
  • forsøk denne løsningen: omkod begge variabler til små bokstaver ved hjelp av tolower() eller toupper().
  • bruk %in% for å sjekke om du fikk det riktig.

Legge til variabler: nøkler

Personlig legger jeg ofte variablene til hoveddatasettet mitt én etter én. Da har jeg kontroll.

Jeg begynner med match(). Funksjonen kommer fra baseR og rapporterer observasjonsnummeret til variabel2 i rekkefølgen til variabel1.

match(tolower(df$Parti),
            tolower(df2$party_name))[1:10]
##  [1]  5  1 21  9  5  1  9 NA  1  9

Her er første parti i ESS dataene (df) det femte partiet i CHES (df2). Det kan jeg bruke til å indeksere. For enkeltheltsskyld tar jeg vare på indeksen i et objekt og bruker den.

id <- match(tolower(df$Parti),
            tolower(df2$party_name))

Jeg ønsker å overføre innvandringsskepsis fra CHESS til ESS. Indeksvariabelen min id sørger for at df2$skepsis[id] er like lang som df. Dermed kan jeg ganske enkelt kopiere variabelen over.

df2$skepsis[id]
df$Skepsis_parti  <- df2$skepsis[id]

Ha tunga rett i munn når du gjør dette; det blir snart en (u)vane. R gir deg beskjed om de to vektorene er av ulik lengde. Da har du indeksert feil.

Din tur

Nå kan vi sjekke om respondentene og partiene de stemte på har omtrent samme holdning til innvandring! Hvilke partier har størst avstand fra velgerne sine?

  • korreler de to ved hjelp av cor.test()
cor.test(df$Skepsis, df$Skepsis_parti)
## 
##  Pearson's product-moment correlation
## 
## data:  df$Skepsis and df$Skepsis_parti
## t = 10.848, df = 1052, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.2618089 0.3704663
## sample estimates:
##       cor 
## 0.3171781
  • aggreger/regn ut den gjennomsnitlige differansen mellom velgerne og partiet ved hjelp av group_by() i dplyr. Da kan det være nyttig å ta den absolutte differensen ved hjelp av abs(); f.eks abs(1-2)
df %>%
  group_by(Parti) %>%
  summarize(distanse = mean(abs(Skepsis - Skepsis_parti),
                            na.rm = T)) %>%
  #Sorterer i nedadgående (descending) verdier
  arrange(desc(distanse))

Slå sammen hele datasett