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.
$TaJobber+df$TaSkatt+df$Belastning df
Om vi ønsker å beholde min-max verdiene, deler vi på antall variabler (kolonner). Merk deg parentesen du ville satt om du skrev dette matematisk.
$TaJobber+df$TaSkatt+df$Belastning)/3 (df
… 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.
$indeks = rowMeans(df[,c("TaJobber", "TaSkatt", "Belastning")],
dfna.rm = T)
Skal du snu skalaretningen? Gang med -1 og pluss på maksverdien
$indeks*-1) + max(df$indeks,
(dfna.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!
$indeks <- (df$indeks*-1) + max(df$indeks,
dfna.rm = T)
$indeks*-1) + max(df$indeks,
(dfna.rm = T)
Gi alltid nytt navn.
$indeks_snudd <- (df$indeks*-1) + max(df$indeks,
dfna.rm = T)
$indeks*-1) + max(df$indeks,
(dfna.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.
$indeks/10 df
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
$Inntekt < 3 df
Her setter jeg to
$Inntekt > 3 & df$Inntekt < 7 df
Lag en ny variabel
$Inntekt_kat = NA df
Lav inntekt
$Inntekt.kat[df$Inntekt <= 3] = "Lav" df
$Inntekt.kat[df$Inntekt > 3 & df$Inntekt < 7 ] = "Middels" df
$Inntekt.kat[df$Inntekt >= 7 ] = "Hoy" df
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?
$Inntekt.kat <- factor(df$Inntekt.kat,
dflevels = c("Lav", "Middels", "Hoy"))
… eller ordinal
$Inntekt.kat <- factor(df$Inntekt.kat,
dflevels = 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”?
$Inntekt.kat[grep("dd", df$Inntekt.kat)] df
Funksjonen grepl()
gir TRUE/FALSE-svar. Det er nyttig
når du skal lage en ny variabel.
$rik <- grepl("Hoy", df$Inntekt.kat) df
R leser TRUE som 1 og FALSE som 0.
sum(df$rik)
## [1] 495
Du kan eksplisitt omkode om du ønsker
$rik <- grepl("Hoy", df$Inntekt.kat) %>%
dfas.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
$Inntekt > 3,] df[df
#dplyr
%>%
df filter(Inntekt > 3)
Spesifikke variabler: da selekterer du variablene.
#baseR
"Inntekt"]
df[,#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:
$Inntekt_omk %>% table df
## .
## 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
<- kap6b df2
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.
$Parti %>% unique df
## [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
$party_name %>% unique df2
## [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å?
$Parti[df$Parti %in% df2$party_name] %>% unique df
## [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å?
$Parti[!df$Parti %in% df2$party_name] %>% unique df
## [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()
ellertoupper()
. - 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.
<- match(tolower(df$Parti),
id 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.
$skepsis[id]
df2$Skepsis_parti <- df2$skepsis[id] df
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 avabs()
; f.eksabs(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))