Hvordan gjøre en liste til en tabell (data.frame) i R?

Lister hoist() list() R tibble str() API

Denne posten beskriver hvordan man kan hente ut informasjonen fra en nestet liste i R der data er plassert på ulike nivåer. Først beskrives det hvordan vi får en oversikt over innholdet i lista med str(). Deretter brukes funksjonen hoist() for å få ut dataene i en data.frame eller en tibble. Dette er ofte nyttig når vi har data fra et API eller en nettjeneste.

Øyvind Bugge Solheim https://www.oyvindsolheim.com (C-REX - Center for Research on Extremism)https://www.sv.uio.no/c-rex/english/
2021-11-24

Lister

Data kommer ikke alltid i enkle tabeller. Ofte vil vi jobbe med data fra nettet som ikke er tabeller, men heller nestede trær med informasjon. Dette er for eksempel tilfellet om vi henter json-data fra et web-API. Heldigvis finnes det nyttige funskjoner for å hente ut informasjonen fra slike lister og gjøre dem om til mer nyttige tabeller slik som en tibble, data.frame eller data.table. Andre ganger får vi flere tabeller i en liste, for eksempel hvis vi har kjørt en loop som genererer mange tabeller. Denne posten omhandler ikke slike situasjoner. Da vil vi ofte kunne bruke for eksempel rbindlist() fra data.table.

Denne posten ble egentlig inspirert av data fra json, men jeg lager en kunstig tabell her til dette eksemplet. Json kan fint leses inn med pakka rjson.

Kunstig liste med mange nivåer

Vis hvordan lista ble lagd
#Starter med pakker:
library("tidyverse")
library("knitr")


# kunstig liste med mange nivåer

partiliste <- 
  list(
    list(
      parti="SV",
      partileder=list(etternavn="Lysbakken",
                      fornavn="Audun"),
      valgresultat= list(
        stortingsvalg=list(
          valg_21=list(
          prosent=7.6,
          mandater=13),
          valg_17=list(
            prosent=6,
          mandater=11)
          )
            
        )
      ),
    list(
      parti="Frp",
      partileder=list(fornavn="Sylvi",
                      etternavn="Listhaug"),
      valgresultat= list(
        stortingsvalg=list(
        valg_21=list(prosent=11.6,
                     mandater=21),
        valg_17=list(prosent=15.2,
                     mandater=27)
      )
    )
  )
  )

Over har jeg laget en liste med mange nivåer.1 Som regel har vi ikke helt oversikten over hva som ligger i en liste. Hvis vi printer den i sin helhet får vi ofte en litt vanskelig tolkbar oversikt:

Her kan du se outputen av print(partiliste)
print(partiliste)

[[1]]
[[1]]$parti
[1] "SV"

[[1]]$partileder
[[1]]$partileder$etternavn
[1] "Lysbakken"

[[1]]$partileder$fornavn
[1] "Audun"


[[1]]$valgresultat
[[1]]$valgresultat$stortingsvalg
[[1]]$valgresultat$stortingsvalg$valg_21
[[1]]$valgresultat$stortingsvalg$valg_21$prosent
[1] 7.6

[[1]]$valgresultat$stortingsvalg$valg_21$mandater
[1] 13


[[1]]$valgresultat$stortingsvalg$valg_17
[[1]]$valgresultat$stortingsvalg$valg_17$prosent
[1] 6

[[1]]$valgresultat$stortingsvalg$valg_17$mandater
[1] 11





[[2]]
[[2]]$parti
[1] "Frp"

[[2]]$partileder
[[2]]$partileder$fornavn
[1] "Sylvi"

[[2]]$partileder$etternavn
[1] "Listhaug"


[[2]]$valgresultat
[[2]]$valgresultat$stortingsvalg
[[2]]$valgresultat$stortingsvalg$valg_21
[[2]]$valgresultat$stortingsvalg$valg_21$prosent
[1] 11.6

[[2]]$valgresultat$stortingsvalg$valg_21$mandater
[1] 21


[[2]]$valgresultat$stortingsvalg$valg_17
[[2]]$valgresultat$stortingsvalg$valg_17$prosent
[1] 15.2

[[2]]$valgresultat$stortingsvalg$valg_17$mandater
[1] 27

For å få en brukbar oversikt over innholdet i lista kan vi heller bruke str(). str()gir oss en kompakt oversikt over strukturen i et R-objekt.2 Vi kan spesifisere hvor mange nivåer vi ønsker å printe med max.level, men siden vi er interessert i å se hele lista lar vi str() printe alt, noe som også er standard.

str(partiliste)
List of 2
 $ :List of 3
  ..$ parti       : chr "SV"
  ..$ partileder  :List of 2
  .. ..$ etternavn: chr "Lysbakken"
  .. ..$ fornavn  : chr "Audun"
  ..$ valgresultat:List of 1
  .. ..$ stortingsvalg:List of 2
  .. .. ..$ valg_21:List of 2
  .. .. .. ..$ prosent : num 7.6
  .. .. .. ..$ mandater: num 13
  .. .. ..$ valg_17:List of 2
  .. .. .. ..$ prosent : num 6
  .. .. .. ..$ mandater: num 11
 $ :List of 3
  ..$ parti       : chr "Frp"
  ..$ partileder  :List of 2
  .. ..$ fornavn  : chr "Sylvi"
  .. ..$ etternavn: chr "Listhaug"
  ..$ valgresultat:List of 1
  .. ..$ stortingsvalg:List of 2
  .. .. ..$ valg_21:List of 2
  .. .. .. ..$ prosent : num 11.6
  .. .. .. ..$ mandater: num 21
  .. .. ..$ valg_17:List of 2
  .. .. .. ..$ prosent : num 15.2
  .. .. .. ..$ mandater: num 27

Vi ser at informasjonen vi er interessert i er lagret på ulike steder i lista. Liste består av to underlister, en for hvert parti. For hver av disse listene finnes det en variabel for hvilket parti (kalt parti..), men også informasjon om partiledere3 fornavn og etternavn under partileder og så fornavn eller etternavn og det er en liste med valgresultatet i to valg under valgresultat, stortingsvalg, valg_21 eller valg_17 og så til slutt prosent eller mandater. Vi kunne selvfølgelig henta ut denne informasjonen med ulike subsettinger. I vårt eksempel kunne både subsetting med tall og med tekst vært mulig fordi vi har så liten liste. Men listene vil ofte være mye lenger og større. Da vil en slik framgangsmåte være lite hensiktsmessig.

Valgresultat SV 2021:

partiliste[[1]]$valgresultat$stortingsvalg$valg_21$prosent
[1] 7.6

Tall-subsetting er også ganske kronglete og vanskelig å lese her:

Valgresultat Fremskrittspartiet 2021:

partiliste[[2]][[3]][[1]][[1]][[1]]
[1] 11.6

Løsningen er hoist()

Som vanlig har noen andre hatt dette problemet før og funnet en god måte å løse det på. Ikke overraskende er en av løsningene tilgjengelig i tidyverse og i pakka tidyr. Funksjonen hoist() lar oss hente enkeltelementer i lista og lage en tibble med informasjonen.

hoist() fungerer bare med tibble som input. Vi lagrer lista i en tibble og sier at den skal hete datasett.

parti_tibble<- partiliste %>%
  # Hoist() klarer bare å jobbe med tibbler. Men tibbler fungerer likt som lister.
  
  # Punktumet viser her til partiliste:
  tibble(datasett = .)

Når vi nå har en tibble kan vi bruke hoist() til å hente ut data. Hadde vi bare vært interessert i data fra det øverste nivået kan vi bare spesifisere hva dataene heter. Her finnes for eksempel partinavnet på det øverste nivået.

Øverste nivå

parti_tibble %>%
  
  # Vi har en tibble hoist() kan jobbe med:
  hoist(
    #Vi kalte tibblen datasett:
    datasett,
    
    #her følger alle enkeltelementene du ville ha:
    
    #Når de er på det øverste nivået kan du bare skrive dem:
    "parti") %>% 
  kable()
parti datasett
SV Lysbakken, Audun , 7.6 , 13 , 6 , 11
Frp Sylvi , Listhaug, 11.6 , 21 , 15.2 , 27

For å få informasjon fra lavere nivåer må vi spesifisere hele grenen Dette kan bli litt kronglete, men koden er i hvert fall veldig grei å skrive. Vi bruker c()til å spesifisere alle nivåene innover til informasjonen vi er interessert i. Her er for eksempel partiledernes navn lagt under partileder og så fornavn eller etternavn. Vi skriver dermed at variabelen partileder_fornavn skal være c("partileder", "fornavn"). Tilsvarende lager vi en lengre remse for å hente ut valgresultatene.

Henter data fra flere nivåer

parti_tabell<- parti_tibble %>%
  
  # Vi har en tibble hoist() kan jobbe med:
  hoist(
    #Vi kalte tibblen datasett:
    datasett,
    
    #her følger alle enkeltelementene du ville ha:
    
    #Når de er på det øverste nivået kan du bare skrive dem:
    "parti",
    # Når de er lenger ned kan du skrive de overordnede nivåene inne i en c() og så det du vil ha:
    partileder_fornavn = c("partileder", "fornavn"),
    partileder_etternavn = c("partileder", "etternavn"),
    valgresultat_21 = c("valgresultat", "stortingsvalg",
                        "valg_21", "prosent"),
    mandater_21 = c("valgresultat", "stortingsvalg",
                    "valg_21", "mandater"),
    valgresultat_17 = c("valgresultat", "stortingsvalg",
                        "valg_17", "prosent"),
    mandater_17 = c("valgresultat", "stortingsvalg",
                    "valg_17", "mandater")
  )

Vi så i det første eksempelet over at hoist() automatisk legger all den andre informasjonen i lista/tibblen til slutt i tabellen. Dette er vi ikke interessert i. Dermed fjerner vi variabelen datasett (navnet vi spesifiserte da vi lagde den første tibblen) fra tabellen. Vi bruker grep() til å finne ut hvilken kolonne som heter “datasett”.

rest_kolonnenummer <- grep("datasett",colnames(parti_tabell))

Når vi har funnet hvilket nummer denne kolonnen er, kan vi subsette part_tabell slik at denne kolonna blir fjerna. Det gjør vi ved å bruke minustegnet før tallet (her -rest_kolonnenummer). Da fjerner vi kolonne nummer 8 i dette tilfelle. Dette skal spesifiseres etter kommaet siden det er en kolonne og ikke en rad.

parti_tabell <- parti_tabell[,-rest_kolonnenummer]

Ferdig tabell:

Show code
kable(parti_tabell)
parti partileder_fornavn partileder_etternavn valgresultat_21 mandater_21 valgresultat_17 mandater_17
SV Audun Lysbakken 7.6 13 6.0 11
Frp Sylvi Listhaug 11.6 21 15.2 27

Vi ser at dette er en tabell det går an å jobbe med.


  1. Dataene er hentet fra valgresultat.no↩︎

  2. Fritt etter R-dokumentasjonen her: str()↩︎

  3. Jeg blei lei av å lage liste ganske raskt, men her burde selvfølgelig også vært spesifisert at Siv Jensen var partileder i 2017-valget.↩︎

Citation

For attribution, please cite this work as

Solheim (2021, Nov. 24). Solheim: Hvordan gjøre en liste til en tabell (data.frame) i R?. Retrieved from www.oyvindsolheim.com/code/Hente data fra lister/

BibTeX citation

@misc{solheim2021hvordan,
  author = {Solheim, Øyvind Bugge},
  title = {Solheim: Hvordan gjøre en liste til en tabell (data.frame) i R?},
  url = {www.oyvindsolheim.com/code/Hente data fra lister/},
  year = {2021}
}