Denne posten baserer seg på en presentasjon jeg gjorde av data.table
-pakka. Den beskriver hvordan man kan bruke data.table
i R for å jobbe med store datasett slik som registerdata. Posten går blant annet gjennom de ulike posisjonene i klammene [i,j,by]
og viser hvordan man kan lage nye variabler i data.table
med :=
.
Nyhet:
ConciseR
.Jeg har testet ut chatGPT sin nye GPT-bygger til å lage en data.table-spesifikk GPT. Alle som betaler for chatGPT får tilgang med denne lenken.
data.table
?1Raske oppslag i indekserte tabeller
Mindre kopiering gir raskere kode
Effektiv syntaks for grupperte utregninger
Fordelene med data.table
knytter seg primært til hastighet og effektivisering. Når man indekserer en data.table
får man veldig raske oppslag i tabellen. data.table
reduserer også mengden kopiering som skjer i R. I standard R overskrives objekter hver gang du endrer dem. Hver gang du lagrer med <-
lages det en ny kopi av datasettet. Dette skjer også når du beholder det samme navnet for datasettet. I data.table
reduseres denne typen kopier til et minimum noe som øker gjør det mye mer effektivt å bruke med store datasett. data.table
har i tillegg effektiv kode for å gjennomføre grupperte utregninger.
Vi begynner med å laste inn data.table
-pakka.
# install.packages("data.table")
library("data.table")
Laster inn csv-er med fread
. fread
er data.tables versjon av read.csv
og laster veldig raskt inn store csv-filer.
Husk as.data.table(df)
hvis dere bruker andre måter å laste inn datasettet på.
Eventuelt kan du bruke setDT(df)
hvis du bare vil endre type fra data.frame til data.table uten å kopiere.
data.table
Likner data.frame
med noen unntak
Utregning skjer inne i klammene
Klammene har tre hovedposisjoner: dt[i, j, by]
data.table
dt[ i ] velger og organiserer rader
dt[, j ] velger variabler, regner ut og lager nye variabler
dt[,, by ] sier hvordan datasettet skal være gruppert og organiserer datasettet etter grupperinger (keyby
)
NB: rekkefølge er viktig. Ofte bruker vi komma uten noe innhold: dt[,mean(variabel)]
Som i vanlig (base) R forholder data.table
seg til ulike deler av klammene, men i data.table
har klammene flere posisjoner som kan brukes til ulike ting. I tillegg kjører vi som regel koden inne i klammene istedenfor rundt datasettet (base) eller kjedet med piper (tidyverse).
Klammene til en data.table
har tre “posisjoner”: DT[i, j, by]
. i
brukes som i vanlige data.frame
til å velge eller organisere tabellen. j
viser som i data.frame til hvilke variabler man ønsker, men her kan man også lage nye variabler og bruke funksjoner på en variabel. by
viser til grupperingen eller organiseringen (keyby
) som vi ønsker å regne ut i fra.
NB: siden rekkefølgen er viktig jobber vi ofte med tomme felt. Dette gjelder særlig i
som ofte bare står tom. For eksempel dt[,mean(variabel)]
dt[i]
Kan skrive variabelnavn direkte inne i klammene.
Dermed kan vi bruke større enn eller mindre inn direkte i i
for å subsette tabellen.
Så kan vi regne ut en verdi for den subsatte gruppa
Inne i en data.table
kan vi omtale variablene i tabellen direkte uten gåseøyne. Dette er veldig praktisk. Vi kan subsette ved å omtale variablene direkte inne i i
. Vi kan også regne ut for eksempel gjennomsnitt i j
.
Kan subsette med ==
inntekt_kjonn[kjonn==1]
FALSE inntekt kjonn idnr
FALSE 1: 32596 1 463599
FALSE 2: 12711 1 1386844
FALSE ---
FALSE 999999: 1625864 1 1215391
FALSE 1000000: 1296593 1 557819
Kan regne ut gjennomsnitt for den subsatte gruppa:
inntekt_kjonn[kjonn==0,mean(inntekt,na.rm=T)]
FALSE [1] 142562
Kan organisere tabellen:
inntekt_kjonn[order(-inntekt)]
FALSE inntekt kjonn idnr
FALSE 1: 1999962 1 792542
FALSE 2: 1999955 1 131002
FALSE ---
FALSE 1999999: NA 0 1519811
FALSE 2000000: NA 0 1240485
dt[,j]
med .(var1,var2)
Hvis vi vil se flere variabler samtidig kan vi bruke .(var1,var2)
inne i j
: dt[,.()]
(Dette er det samme som list()
)
Vi kan lage nye midlertidige navn inne i dt[,.()]
med dt[,.(ny_var=gammel_var)]
Vi kan hente ut to variabler og variablene imellom med :
NB: j
finnes etter i
og vi må ha komma
Den andre posisjonen i klammene er j
. Der kan vi spesifisere hvilke variabler vi ønsker å se. Hvis vi vil se mer enn en variabel kan vi bruke .()
som er en data.table-versjon av list()
. Begge virker inne i en data.table
. Vi kan også lage nye navn midlertidig inne i .()
. Husk å bruke komma først for å komme til j
.
Skriver ut flere variabler med .()
inntekt_kjonn[,.(inntekt,kjonn)]
FALSE inntekt kjonn
FALSE 1: 32596 1
FALSE 2: 12711 1
FALSE ---
FALSE 1999999: 1822257 0
FALSE 2000000: 695376 0
Lager midlertidige variabler med .()
og =
inntekt_kjonn[,.("Månedlig inntekt"=inntekt,"Kjønn numerisk"=kjonn)][1:2]
FALSE Månedlig inntekt Kjønn numerisk
FALSE 1: 32596 1
FALSE 2: 12711 1
Henter ut variabler med alle mellom med :
inntekt_kjonn[,inntekt:idnr]
FALSE inntekt kjonn idnr
FALSE 1: 32596 1 463599
FALSE 2: 12711 1 1386844
FALSE ---
FALSE 1999999: 1822257 0 1964207
FALSE 2000000: 695376 0 1168915
:=
i j
Bruker :=
til å lage variabler inne i en data.table
dt[,ny_var:=gammel_var]
Dette erstatter <-
i base R
Mer effektivt fordi det ikke kopierer tabellen
I data.table
lager vi nye variabler med :=
. Dette legger til en variabel uten å kopiere datasettet. Dette er dermed mer effektivt enn for eksempel dt <- dt %>% mutate(x=0)
fordi dplyr::mutate
kopierer datasettet med <-
. Når vi har store datasett er kopiering ineffektivt.
Lager første inntektsdesil:
inntekt_kjonn[inntekt<=32480,desil_1:=1]
inntekt_kjonn[inntekt>32480,desil_1:=0]
# Alternativ med fifelse
inntekt_kjonn[,desil_1_2:=fifelse(inntekt>32480,0,1)]
# Likt resultat: inntekt_kjonn[,table(desil_1,desil_1_2,useNA="ifany")]
by
Rask utregning per gruppe med by=
inne i den tredje posisjonen
dt[,,by=gruppe]
keyby=
gjør at gruppene er ordnet for eksempel etter størrelse eller etter faktornivå (level)
En sentral fordel med data.table
er at det er veldig raskt å regne ut estimater for grupper i datasettet. Vi kan definere grupper både før vi bruker datasettet med setkey()
og inne i klammene ved å legge gruppevariabelen i den tredje posisjonen. Hvis vi vil at gruppene skal komme i en viss rekkefølge kan vi bruke en ordnet faktor og bruke keyby=
inne i den tredje posisjonen i klammene. Hvis vi vil regne ut gjennomsnittlig inntekt per kjønn kan vi gjøre det slik:
Gjennomsnittsinntekt etter kjønn:
inntekt_kjonn[,mean(inntekt,na.rm=T),kjonn]
FALSE kjonn V1
FALSE 1: 1 147790.8
FALSE 2: 0 142562.0
Eller slik med navn:
inntekt_kjonn[,.("inntekt_snitt"=mean(inntekt,na.rm=T)), keyby=kjonn]
FALSE kjonn inntekt_snitt
FALSE 1: 0 142562.0
FALSE 2: 1 147790.8
Vi kan også regne ut nye variabler slik som gruppesentrert inntekt per kjønn:
inntekt_kjonn[,inntekt_sent:=inntekt-mean(inntekt,na.rm=T),kjonn]
data.table
gir litt ulike typer print med .() og uten:
#Ny data.table:
#inntekt_kjonn[,.(inntekt_sent)]
#Bare verdier:
#inntekt_kjonn[,inntekt_sent][1:20] #Hvis ikke veldig mye
data.table
har flere ulike spesialkoder
.N
gir antallet observasjoner i datasettet eller gruppa
.GRP
gir hvilket nummer gruppa er av gruppene
.I
er hvilket nummer observasjonen er
data.table har en del praktiske spesialkoder som man kan bruke. .N
gir antallet observasjoner, .GRP
viser hvilket gruppenummer man er i, .I
er hvilket nummer observasjonen er.
inntekt_kjonn[,.N]
FALSE [1] 2000000
Eller per gruppe:
inntekt_kjonn[,.N,keyby=kjonn]
FALSE kjonn N
FALSE 1: 0 1000000
FALSE 2: 1 1000000
Eller per gruppe under en viss inntekt:
# antall observasjoner under en viss inntekt per gruppe:
inntekt_kjonn[inntekt<10000,.N,keyby=kjonn]
FALSE kjonn N
FALSE 1: 0 30800
FALSE 2: 1 29129
Mest relevant for andre utregninger eller loops:
# gruppenummer
inntekt_kjonn[,.GRP,keyby=kjonn]
FALSE kjonn GRP
FALSE 1: 0 1
FALSE 2: 1 2
Minste observasjonsnummer per gruppe:
inntekt_kjonn[,min(.I),keyby=kjonn]
FALSE kjonn V1
FALSE 1: 0 1000001
FALSE 2: 1 1
.SD
er nyttig og gir tilgang til flere kolonnerVi kan gjøre en handling på alle kolonnene
Vi kan velge kolonner med tekst-input
Vi kan bruke map()
eller lapply
til å regne ut verdier for alle variablene vi spesifiserer i .SDcols=
inntekt_kjonn[,.SD*2,.SDcols = c("inntekt","kjonn")]
FALSE inntekt kjonn
FALSE 1: 65192 2
FALSE 2: 25422 2
FALSE ---
FALSE 1999999: 3644514 0
FALSE 2000000: 1390752 0
Enkel utregning av snitt for inntekt og inntekt_sent etter kjønn
inntekt_kjonn[,map(.SD,mean,na.rm=T),.SDcols = c("inntekt","inntekt_sent"),by=kjonn]
FALSE kjonn inntekt inntekt_sent
FALSE 1: 1 147790.8 -1.827140e-10
FALSE 2: 0 142562.0 8.630197e-10
fcase()
gjør det lett å omkode variabler
%chin%
er en rask måte å søke etter tekststrenger
%between%
gjør det mulig å finne observasjoner mellom (og inkludert) to verdier
Data.table har noen egne funksjoner som er praktiske når man omkoder et datasett.fcase
kan omkode variabler. %chin%
er rask spesialversjon av %in%
for character-variabler. %between%
gjør det mulig å finne rader mellom to verdier (NB: inkludert verdiene).
fcase()
inntekt_kjonn[,kjonn_b:=fcase(
kjonn == 0, "Kvinne",
kjonn > 0, "Mann")]
%chin%
:FALSE kjonn kjonn_b
FALSE 1: 1 Mann
FALSE 2: 1 Mann
FALSE ---
FALSE 999999: 1 Mann
FALSE 1000000: 1 Mann
%between%
:inntekt_kjonn[inntekt %between% c(10000,20000),":="(nobs=.N,nobs2=.N-5),by=kjonn_b]
inntekt_kjonn[!is.na(nobs)]
FALSE inntekt kjonn idnr desil_1 desil_1_2 inntekt_sent kjonn_b
FALSE 1: 12711 1 1386844 1 1 -135079.8 Mann
FALSE 2: 12235 1 54966 1 1 -135555.8 Mann
FALSE ---
FALSE 60331: 17965 0 1345531 1 1 -124597.0 Kvinne
FALSE 60332: 17546 0 961034 1 1 -125016.0 Kvinne
FALSE nobs nobs2
FALSE 1: 29481 29476
FALSE 2: 29481 29476
FALSE ---
FALSE 60331: 30851 30846
FALSE 60332: 30851 30846
inntekt_kjonn[!is.na(inntekt),.N,
keyby=.(kjonn_b,inntekt<800000)][,
.(inntekt,kjonn_b,Prosent=round(100*N/sum(N),2))]
FALSE inntekt kjonn_b Prosent
FALSE 1: FALSE Kvinne 3.10
FALSE 2: TRUE Kvinne 46.90
FALSE 3: FALSE Mann 3.14
FALSE 4: TRUE Mann 46.87
Litt mer avansert:
inntekt_kjonn[!is.na(inntekt),.N,
keyby=.("Kjonn"=kjonn_b,"Hoy inntekt"=inntekt<700000)][,
.(`Hoy inntekt`,`Kjonn`,Prosent=round(100*N/sum(N),2))][,
.SD,keyby=.(`Hoy inntekt`,`Kjonn`)]
FALSE Hoy inntekt Kjonn Prosent
FALSE 1: FALSE Kvinne 3.36
FALSE 2: FALSE Mann 3.40
FALSE 3: TRUE Kvinne 46.64
FALSE 4: TRUE Mann 46.60
Eventuelt med piper og (den litt rare) funksjonen "["()
"["()
bare fungerer med data.table
-tabeller, ikke med data.frame
eller tibble
inntekt_kjonn[!is.na(inntekt),.N,
keyby=.("Kjønn"=kjonn_b,"Høy inntekt"=inntekt<700000)] %>%
"["(,.(`Høy inntekt`,`Kjønn`,Prosent=round(100*N/sum(N),2))) %>%
"["(,.SD,keyby=.(`Høy inntekt`,`Kjønn`))
FALSE Høy inntekt Kjønn Prosent
FALSE 1: FALSE Kvinne 3.36
FALSE 2: FALSE Mann 3.40
FALSE 3: TRUE Kvinne 46.64
FALSE 4: TRUE Mann 46.60
data.table
bruker key
til å slå sammen to datasett
Det er ikke nødvendig at key
-ene har samme navn
Vi bruker setkey
til å spesifisere hvilken key
vi har
Ev. kan vi bruk on=
hvis datasettene ikke har key
, men like variabler
Vi putter datasettene inne i hverandre dt[dt2]
Dette gir alle observasjoner i dt
som er i dt2
Når dt
ikke har observasjonene til dt2
får variablene fra dt
verdien NA
NB: Observasjoner i dt
som ikke er i dt2
blir ikke med
Når to data.table
-r har samme key
vil de kunne slås sammen enkelt. Da brukes keyen til å slå sammen observasjoner raskt. Variablene i de to datasettene trenger ikke å ha samme navn.
To datasett med samme navn, men uten key
alder_arbeid<- alder[arbeid,on="idnr"]
Lager en ny variabel og sletter den gamle:
inntekt_kjonn[,IDen:= idnr]
inntekt_kjonn[,idnr:=NULL]
Lager grupper med fcase()
.
fcase()
bruker syntaksen: fcase(betingelse1, verdi1, betingelse2, verdi2)
inntekt_kjonn_alder_arbeid[,alder_grupper:=fcase(
alder < 30, "Under 30",
alder >= 30&alder<45, "30 til 45",
alder >= 45&alder <60, "45 til 60",
alder >= 60&alder <70, "60 til 70",
alder >= 70, "Over 70")]
Regner ut gjennomsnittlig inntekt etter aldersgruppe og kjønn:
FALSE alder_grupper kjonn_b V1
FALSE 1: 30 til 45 Kvinne NA
FALSE 2: 30 til 45 Mann NA
FALSE 3: 45 til 60 Kvinne NA
FALSE 4: 45 til 60 Mann NA
FALSE 5: 60 til 70 Kvinne NA
FALSE 6: 60 til 70 Mann NA
FALSE 7: Over 70 Kvinne NA
FALSE 8: Over 70 Mann NA
FALSE 9: Under 30 Kvinne NA
FALSE 10: Under 30 Mann NA
by=
gjør at vi kan regne ut det samme estimatet for ulike grupper av datasettet for eksempel med lineær regresjon
Vi kan bruke lm()
for å regne ut betydning av kjønn for inntekt for alle aldersgrupper og arbeidstyper
I lm()
og de fleste typene regresjon i R spesifiserer vi modellen med en formula
En fomula
har oppsettet: Avhengig~Uavhengig+Uavhengig: inntekt~kjonn_b
Vi må også spesifisere datasettet med data=
For å få ut en tabell bruker vi coeftable()
knitr::kable()
lager en tabell som ser fin ut på nett.
library(fixest)
coeftable(lm(inntekt~kjonn_b,data=inntekt_kjonn_alder_arbeid)) %>%
knitr::kable(digits=c(0,1,1,2))
Estimate | Std. Error | t value | Pr(>|t|) | |
---|---|---|---|---|
(Intercept) | 142562 | 345.9 | 412.1 | 0 |
kjonn_bMann | 5229 | 489.2 | 10.7 | 0 |
data=.SD
inntekt_kjonn_alder_arbeid[,coeftable(lm(inntekt~kjonn_b,data = .SD))] %>%
knitr::kable(digits=c(0,1,1,2))
Estimate | Std. Error | t value | Pr(>|t|) | |
---|---|---|---|---|
(Intercept) | 142562 | 345.9 | 412.1 | 0 |
kjonn_bMann | 5229 | 489.2 | 10.7 | 0 |
Siden vi bare er interessert i estimatet for “Mann” subsetter vi coeftable
med [2,]
for å få den andre raden
data.table
er mest glad i lister så vi bruker as.list()
i tillegg.
keyby=
gjør at vi kan gjøre dette for hvert subsett av aldersgruppe og arbeidstype
inntekt_kjonn_alder_arbeid[,
as.list(coeftable(lm(inntekt~kjonn_b,data = .SD))[2,]),
keyby=list(alder_grupper,arbeid)] %>%
kable(digits =c(0,0,0,0,1,2))
alder_grupper | arbeid | Estimate | Std. Error | t value | Pr(>|t|) |
---|---|---|---|---|---|
30 til 45 | Arbeid | 5974 | 1217 | 4.9 | 0.00 |
30 til 45 | Student | 7698 | 2917 | 2.6 | 0.01 |
30 til 45 | Trygd | 6690 | 2239 | 3.0 | 0.00 |
45 til 60 | Arbeid | 6214 | 1188 | 5.2 | 0.00 |
45 til 60 | Student | 4605 | 4203 | 1.1 | 0.27 |
45 til 60 | Trygd | 4280 | 2058 | 2.1 | 0.04 |
60 til 70 | Arbeid | 2396 | 1748 | 1.4 | 0.17 |
60 til 70 | Pensjon | 4897 | 2244 | 2.2 | 0.03 |
60 til 70 | Student | -6910 | 5968 | -1.2 | 0.25 |
60 til 70 | Trygd | 8661 | 3012 | 2.9 | 0.00 |
Over 70 | Pensjon | 4876 | 1172 | 4.2 | 0.00 |
Under 30 | Arbeid | 4601 | 1590 | 2.9 | 0.00 |
Under 30 | Student | 5084 | 1585 | 3.2 | 0.00 |
lista <- list()
# looper over alder
for(i in unique(inntekt_kjonn_alder_arbeid$alder_grupper)){
#looper over arbeid
for(j in unique(inntekt_kjonn_alder_arbeid$arbeid)){
#Sjekker at det finnes observasjoner med alder*arbeid
if(any(inntekt_kjonn_alder_arbeid[,alder_grupper==i&arbeid==j])){
#Regner ut:
lista[[i]] <- flatten(list(Aldersgruppe=i,Arbeid=j,
as.list(coeftable(lm(inntekt~kjonn_b,
data=inntekt_kjonn_alder_arbeid[alder_grupper==i&arbeid==j,]))[2,])))
}
}
}
Gjør om til tabell
Aldersgruppe | Arbeid | Estimate | Std. Error | t value | Pr(>|t|) |
---|---|---|---|---|---|
30 til 45 | Trygd | 6690 | 2239 | 2.99 | 0.003 |
Under 30 | Student | 5084 | 1585 | 3.21 | 0.001 |
45 til 60 | Trygd | 4280 | 2058 | 2.08 | 0.038 |
Over 70 | Pensjon | 4876 | 1172 | 4.16 | 0.000 |
60 til 70 | Pensjon | 4897 | 2244 | 2.18 | 0.029 |
Alle illustrasjonene er lagd av OpenAIs chatgpt og Dall-e.↩︎
For attribution, please cite this work as
Solheim (2023, Nov. 9). Solheim: Introduksjon til `data.table`. Retrieved from https://www.oyvindsolheim.com/code/data.table/
BibTeX citation
@misc{solheim2023introduksjon, author = {Solheim, Øyvind Bugge}, title = {Solheim: Introduksjon til `data.table`}, url = {https://www.oyvindsolheim.com/code/data.table/}, year = {2023} }