CBS Open data downloaden voor data analyse in R

In de ranglijst open data inventory (ODIN) van Open Data Watch staat Nederlands sinds 2018 wereldwijd op de derde plek, ruim boven onze buurlanden België, Duitsland en Frankrijk. We scoren dus goed met het aanbod van open data! Inmiddels zijn er meer dan 4200 datasets bij het CBS beschikbaar.

Wat is het OData protocol?

Het Open Data (OData) protocol is ontwikkeld voor het publiceren van data via webservices, RESTful APIs, die online publiekelijk bevraagd worden. Deze standaard is sinds december 2016 ISO gecertificeerd (ISO/IEC 20802-1:2016) en daarmee een internationale standaard voor het online publiceren van open data.

Het CBS heeft voor gebruikers van R het package ‘cbsodatR’ ontwikkeld. Dit package maakt het voor data analisten een eenvoudiger om code te schrijven waarmee open data van het CBS wordt opgevraagd.

Ben je een Python gebruiker?

Natuurlijk kan je dit artikel over R verder lezen en inspiratie op doen. Inmiddels heb ik ook een artikel geschreven over open data van CBS met Python: een verkennende data analyse naar correlatie tussen bijstand en werkloosheidscijfers.

In zes stappen met R data downloaden vanaf CBS Open Data Statline

In dit artikel beschrijf ik hoe je in slechts zes eenvoudige stappen open data van het CBS kan bevragen met R, waarna je de data kan gebruiken voor data analyse en het verrijken van andere data bronnen. Als voorbeeld nemen we Kerncijfers wijken en buurten 2017. Dit zijn demografische en sociaaleconomische cijfers over gemeenten, wijken en buurten.

Stap 1: Kies één of meerdere datasets uit de datacatalogus

Het CBS heeft haar datacatalogus beschikbaar gemaakt en ingedeeld per thema. Daarnaast kan je vrij zoeken door een zoekopdracht op te geven. Elke dataset heeft een unieke ‘Identifier’, bijvoorbeeld ‘83765NED’ voor ‘Kerncijfers wijken en buurten 2017’.

Een goed alternatief is om de inhoudsopgave met R op te halen, eventueel voorzien van een zoekopdracht om de data te filteren. Dit doe je zo:

require('cbsodataR')
require('tidyverse')
Inhoudsopgave <- cbs_get_toc("Language" = "nl") %>% 
  filter(grepl('buurt|wijk|gemeente', ShortDescription))
View(Inhoudsopgave)

De bestanden, die nodig zijn voor data analyse, of het verrijken van andere datasets, download ik graag met één opdracht. Hiervoor neem ik ze eerst op een lijst. De data gaan we daarna inlezen als Dataframe. Deze lijst bevat per dataset de gewenste Dataframe naam en de ‘identifier’ van het CBS.

cbs.datasets <- list(
  # Gebieden in Nederland 2017, bijvoorbeeld 'Zaanstreek/Waterland'
  "Gebieden" = "83553NED", 
  # Woonplaatsen in Nederland 2017
  "Woonplaatsen" = "83689NED",
  # Kerncijfers wijken en buurten 2017 
  "Kerncijfers" = "83765NED"
) 

Stap 2: Download data

De datasets worden dus via de OData services door het CBS aangeboden. De ‘Untyped’ datasets bevatten de oorspronkelijke data, waarin symbolen zijn opgenomen om aan te geven dat gegevens ontbreken, nihil of geheim zijn. We kiezen daarom voor de ‘Typed’ versie van de datasets. Deze zijn namelijk geconverteerd om rekenkundige bewerkingen mogelijk te maken.

De code hieronder loopt één voor één door de lijst en downloadt de datasets om deze vervolgens als CSV bestand op te slaan in je werkdirectory.

for (item in cbs.datasets){
  cbs.id <- item
  path <- file.path(getwd(),cbs.id)
  cbs_download_table(id=cbs.id, dir = path, typed = TRUE, cache = TRUE, verbose = FALSE)
  print(item)
}

Stap 3: CSV bestanden importeren in R en metadata raadplegen

Met de onderstaande code gaan we de ontvangen CSV bestanden importeren in R. Voor elk geladen bestand wordt een dataframe aangemaakt, zodat de data in R kan worden bewerkt, samengevoegd met andere data en natuurlijk, daar was het om te doen, in analyses worden gebruikt.

Handig is dat we van het CBS ook de metadata bestanden hebben ontvangen. Metadata beschrijft de eigenschappen van de data. De dataframes met metadata herken je aan de toegevoegde suffix. De oorspronkelijke CSV bestanden met metadata heten ‘DataProperties.csv’.

Een stuk minder handig is dat het CBS spaties heeft toegevoegd aan gemeentenamen en regiocodes. Dit maakt filteren en combineren van data een stuk lastiger: ‘GM0439 ‘ is namelijk niet gelijk aan ‘ GM0439’. Volgens mij gebeurt dit alleen in de beschrijvende kolommen en niet in de meetwaarden.

De functie hieronder verwijderd de spaties.

DataCleansing <- function(x){
  x <- str_trim(x)  
  return(x)
}

Het volgende code snippet loopt één voor één door de lijst, importeert de CSV bestanden in R en maakt de dataframes aan met de opgegeven naam. We zijn daarna bijna zover dat we de data in R kunnen analyseren.

for (name in names(cbs.datasets)){
  # Bestandslocatie, elk bestand heet data.csv
  path.csv <- file.path(getwd(),cbs.datasets[name],"data.csv")
  # CSV bestanden inlezen, maakt dataframes met opgegeven naam.
  assign(name, 
         read_csv(file = path.csv, col_types = cols(.default = "c"), locale = readr::locale(encoding = "windows-1252")) # alle kolommen als tekst laden, daarna bewerken
  )
  # CSV bestanden met metadata importeren, maakt dataframes met suffix meta.
  path.csv <- file.path(getwd(),cbs.datasets[name],"DataProperties.csv")
  assign(paste(name,'meta',sep = '_'), 
         read_csv(file = path.csv, col_types = cols(.default = "c"), locale = readr::locale(encoding = "windows-1252")) # alle kolommen als tekst laden, daarna bewerken
  )
  assign(name,
         get(name) %>% mutate_all(DataCleansing)) # spaties links en rechts verwijderen
  print(name)
}
# Metadata bekijken.
View(Kerncijfers_meta)

Hieronder een voorbeeld uit de metadata. De metadata beschikt over datadefinities en een beschrijvingen van de onderwerpen in de kerncijfers,

metadata

Stap 4: Kolommen met meetwaarden converteren naar numeriek

Bij het importeren van CSV bestanden kies ik er vaak voor om alle kolommen in te lezen als tekst. Dit opent de mogelijkheid om daarna bewerkingen op tekstuele waarden uit te voeren en te kiezen welke kolommen je wilt converteren naar factor formaat (categoriale data). Het betekent echter ook dat het data type van numerieke kolommen omgezet moet worden voordat we kunnen gaan rekenen.

Hieronder zie je een voorbeeld van hoe je dit kan doen zonder de data conversie voor elke kolom uit te coderen.

Kerncijfers <- Kerncijfers %>% mutate_at(c(6:ncol(Kerncijfers)), as.numeric)

Natuurlijk kan het ook voor specifieke kolommen:

Gebieden <- Gebieden %>% mutate_at(c('Inwonertal_48'), as.numeric)

Stap 5: GeoDetail, de dimensie met geografische kenmerken

De kerncijfers dataset ontvang je samen met een bestand met de bijbehorende wijken en buurten. Deze indeling kan namelijk in de loop van de tijd van naam of samenstelling wijzigen. Het bestand heet ‘WijkenEnBuurten.csv’.

path.csv <- file.path(getwd(),cbs.datasets["Kerncijfers"],"WijkenEnBuurten.csv")
GeoDetail <- read_csv(file = path.csv, col_types = cols(.default = "c"), locale = readr::locale(encoding = "windows-1252"))

Erg handig, maar er zijn wel een paar bewerkingen nodig om ervoor te zorgen dat je op wijknaam kan zoeken om de bijbehorende buurten te selecteren. Ook is het patroon ‘Wijk ##’ toegevoegd aan het begin van de wijknaam. Soms bestaat dit patroon niet, mist een spatie tussen de prefix ‘Wijk’ en het wijknummer of heeft het wijknummer geen voorloop nul. Je kan de naam ongewijzigd laten maar ik heb ervoor gekozen om de prefix te verwijden.

Wat ook opvalt is dat de regiocode van regio ‘Nederland’ ontbreekt. Vreemd, maar snel opgelost.

Het is handig om data met regiosoort te kunnen filteren. Deze kolom zit ook in het kerncijfers bestand. In onderstaande code zie je hoe je regiosoort kan herleiden uit de regiocode. Gewoon omdat het kan en wellicht noodzakelijk als je met andere bestanden aan het werk bent.

De laatste handeling is het combineren van het bestand met zichzelf om zo de buurten te voorzien van de wijk- en gemeentenaam en wijken van gemeentenaam. Dat vereenvoudigd o.a. het selecteren van alle buurten uit een bepaalde wijk.

pattern.wijknaam.1 <- "^Wijk [0123456789] "
pattern.wijknaam.2 <- "^Wijk [0123456789][0123456789] "
pattern.wijknaam.3 <- "^Wijk[0123456789][0123456789] "

GeoDetail$DetailRegionCode[GeoDetail$Key=="NL00"] <- 'NL00'

GeoDetail <- GeoDetail %>% 
  mutate(
    SoortRegio = case_when(str_sub(GeoDetail$DetailRegionCode,1,2)=='BU' ~ "Buurt",
                          str_sub(GeoDetail$DetailRegionCode,1,2)=='WK' ~ "Wijk",
                          str_sub(GeoDetail$DetailRegionCode,1,2)=='GM' ~ "Gemeente",
                          str_sub(GeoDetail$DetailRegionCode,1,4)=='NL00' ~ "Nederland",
                          TRUE ~ "Anders"),
    RegioNaam = case_when(str_detect(Title, pattern.wijknaam.1)==TRUE ~ str_sub(Title,8,100),
                          str_detect(Title, pattern.wijknaam.2)==TRUE ~ str_sub(Title,9,100),
                          str_detect(Title, pattern.wijknaam.3)==TRUE ~ str_sub(Title,8,100),
                          TRUE ~ Title),
    WijkCode  = if_else(str_sub(GeoDetail$DetailRegionCode,1,2)=='BU',
                        paste('WK',str_sub(GeoDetail$DetailRegionCode,3,8), sep = ""),
                        "")
    )

GeoDetail <- GeoDetail %>%
  left_join(select(GeoDetail,DetailRegionCode, WijkNaam = RegioNaam),
                    by=c("WijkCode"="DetailRegionCode")) %>% 
  select(Municipality,DetailRegionCode,SoortRegio,RegioNaam,WijkCode,WijkNaam)
View(GeoDetail)

Stap 6: Eindelijk, CBS data analyseren!

Het klonk kort en bondig: zes stappen. Toch is het best een lang artikel geworden. Je kunt nu beginnen met jouw data analyse in R. Mag ik je nog één tip geven?

R kent verschillende methoden om kolommen te selecteren. Het CBS voegt een kolom index toe aan elke kolom, die niet behoort tot de identificerende kolommen. In kerncijfers zijn de eerste twee kolommen identificerend en kolom drie heeft dus indexnummer 1 als suffix in de naam gekregen. Kerncijfers 2017 heeft in totaal 110 kolommen met soms erg lange kolomnamen.

Hieronder de laatste code van de dit artikel. Het toont het selecteren van kolommen met kolom indexnummers en combineert dataframes Kerncijfers en GeoDetail tot één dataset.

kolom.index <- c(1:4,37,49,109) 
DataSelectie <- Kerncijfers %>%
  select(kolom.index) %>%
  left_join(select(GeoDetail,DetailRegionCode, RegioNaam, WijkNaam),
            by=c("WijkenEnBuurten"="DetailRegionCode"))
View(DataSelectie)

What’s next?

CBS data bevat enorm veel statistieken die op zich zelf waardevol zijn voor analyses. Echt leuk wordt het als je CBS data kan combineren met andere data om een exploratieve data analyse uit te voeren op samenhang en groeperingen. Voorbeelden: correlatie en clusteren.