Skip to contents

Introductie

Het grondgebied van Nederland kan op diverse manieren worden opgedeeld. De opdeling waar het in deze repository over gaat is de opsplitsing in gemeenten, wijken en buurten. Daarbij geldt dat buurten optellen tot wijken en wijken optellen tot gemeenten.

Deze indeling ligt niet vast. Het aantal gemeenten neemt de laatste jaren af door fusies. Een grotere gemeente kan taken die worden toegewezen aan gemeenten gemakkelijker oppakken. Het aantal wijken neemt geleidelijk aan toe. Een gemeente kan zelf bepalen hoe zij hun grondgebied willen opdelen in wijken en buurten. Wellicht doordat het aantal gemeenten af neemt, is er behoefte een gemeente op te delen in meer wijken.

Door deze ontwikkelingen wijzigen de grenzen van gemeenten en wijken regelmatig . Dat heeft consequenties als men de huidige wijkkenmerken wil vergelijken met het verleden. Immers, de huidige wijk bestond mogelijk nog niet in voorgaande jaren. Daarmee is het lastig om trends in de ontwikkelingen in wijkkenmerken scherp te ontwaren. Bij wijkkenmerken kan men bijvoorbeeld denken aan het aandeel 65-plussers, de gemiddelde huishoudgrootte of de gemiddelde huizenprijs in een wijk.

Het package Grenswijzigen helpt om te gaan met deze problematiek.

Achtergrond

Bij het CBS is veel data te vinden. Voor dit onderwerp zijn we met name geinteresseerd in cijfers rondom wijken en gemeenten. Binnen het CBS betreft het bijvoorbeeld onderstaande tabellen:


library(cbsodataR)

df.catalog <- cbsodataR::cbs_get_datasets(catalog="CBS")
df.catalog <- df.catalog[
  grepl("Kerncijfers wijken en buurten \\d{4}$", df.catalog$Title), 
  c("Identifier", "Title")
]
df.catalog$Jaar <- as.numeric(
  regmatches(df.catalog$Title, regexpr("\\d{4}", df.catalog$Title))
)
df.catalog
#>      Identifier                              Title Jaar
#> 1080   85984NED Kerncijfers wijken en buurten 2024 2024
#> 1081   85618NED Kerncijfers wijken en buurten 2023 2023
#> 1082   85318NED Kerncijfers wijken en buurten 2022 2022
#> 1083   85039NED Kerncijfers wijken en buurten 2021 2021
#> 1084   84799NED Kerncijfers wijken en buurten 2020 2020
#> 1085   84583NED Kerncijfers wijken en buurten 2019 2019
#> 1086   84286NED Kerncijfers wijken en buurten 2018 2018
#> 3843   83765NED Kerncijfers wijken en buurten 2017 2017
#> 3844   83487NED Kerncijfers wijken en buurten 2016 2016
#> 3845   83220NED Kerncijfers wijken en buurten 2015 2015
#> 3846   82931NED Kerncijfers wijken en buurten 2014 2014
#> 3847   82339NED Kerncijfers wijken en buurten 2013 2013
#> 3850   80868ned Kerncijfers wijken en buurten 2003 2003
#> 3851   70139NED Kerncijfers wijken en buurten 2001 2001
#> 3852      37681 Kerncijfers wijken en buurten 1999 1999
#> 3853      37515 Kerncijfers wijken en buurten 1997 1997
#> 3854      37108 Kerncijfers wijken en buurten 1995 1995

Het package Grenswijzigen is te gebruiken voor de jaren 2016 en later. Laten we daarom alle data ophalen van 2016 tot en met 2024:


library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

# find all relevant identifiers from 2016 onwards
ids <- df.catalog$Identifier[df.catalog$Jaar >= 2016]

# create empty data-frame to be filled
df <- data.frame()

# loop over all id's 
for (id in ids) {
  df <- rbind(
    df,
    # download data, filter on 'Gemeente' and select relevant columns
    cbsodataR::cbs_get_data(
      id=id,
      select=c("WijkenEnBuurten", "AantalInwoners_5", "k_65JaarOfOuder_12"),
      WijkenEnBuurten = has_substring("GM") | has_substring("WK")
    ) %>%
      cbs_add_label_columns() %>%
      # add a 'Year' column to keep track of the year the data is from
      mutate(
        Year = as.numeric(
          gsub("\\D", "", df.catalog[df.catalog["Identifier"]==id, "Title"])
        )
      )
  )
}

head(df)
#> # A tibble: 6 × 5
#>   WijkenEnBuurten WijkenEnBuurten_label AantalInwoners_5 k_65JaarOfOuder_12
#>   <chr>           <fct>                            <int>              <int>
#> 1 "GM1680    "    Aa en Hunze                      25845               7368
#> 2 "WK168000  "    Wijk 00 Annen                     3590               1015
#> 3 "WK168001  "    Wijk 01 Eext                      1460                355
#> 4 "WK168002  "    Wijk 02 Anloo                      390                100
#> 5 "WK168003  "    Wijk 03 Gasteren                   390                140
#> 6 "WK168004  "    Wijk 04 Anderen                    260                 65
#> # ℹ 1 more variable: Year <dbl>

Op basis van de opgehaalde data is het vrij eenvoudig inzichtelijk te maken hoe het aantal gemeenten afneemt in de laatste jaren:


# explore the number of municipalities
library(ggplot2)

ggplot2::ggplot(
  data = df %>% filter(grepl("^GM", WijkenEnBuurten)),
  mapping = ggplot2::aes(x=as.factor(Year))
) +
  ggplot2::geom_bar(fill="dodgerblue") +
  ggplot2::theme_classic() +
  ggplot2::labs(
    title = "Aantal Nederlandse gemeenten per jaar",
    y = "Aantal gemeenten",
    x = "Jaren"
  ) +
    geom_text(
    aes(y = after_stat(count + 2), label = after_stat(count)),
    stat = "count",
    vjust=2
  )

En, zoals al eerder opgemerkt, neemt het aantal wijken de laatste jaren toe. Dit heeft vermoedelijk te maken met de behoefte om binnen grotere gemeenten toch zinvolle gebiedsopdelingen te kunnen blijven maken.


ggplot2::ggplot(
  data = df %>% filter(grepl("^WK", WijkenEnBuurten)),
  mapping = ggplot2::aes(x=as.factor(Year))
) +
  ggplot2::geom_bar(fill="dodgerblue") +
  ggplot2::theme_classic() +
  ggplot2::labs(
    title = "Aantal Nederlandse wijken per jaar",
    y = "Aantal wijken",
    x = "Jaren"
  ) +
    geom_text(
    aes(y = after_stat(count + 2), label = after_stat(count)),
    stat = "count",
    vjust=2
  )

Als een gevolg van deze gemeentelijke herindelingen zijn plotselinge sprongen te zien in de demografische gegevens, zoals weergegeven in de grafiek hieronder. Het aantal inwoners in Vught lijkt plotseling te vermeerderen met circa 5000 vanwege de gedeeltelijke overname van de verdwenen gemeente Haaren. Ook voor nieuw gevormde gemeenten zoals Eemsdelta zijn geen historische gegevens beschikbaar. Het is duidelijk dat dergelijke interrupties het moeilijk maken om data over de jaren heen met elkaar te vergelijken. Trends in mogelijk demografische ontwikkelingen zijn moeilijk te ontwaren.


# show population of selected municipalities
selected_municipalities = c("Vught", "Eemsdelta", "Wageningen", "Montferland", "Gorinchem")

ggplot2::ggplot(
  data = df %>%
    filter(WijkenEnBuurten_label %in% selected_municipalities),
  mapping = ggplot2::aes(
    x=Year, 
    y=AantalInwoners_5, 
    color=WijkenEnBuurten_label
  )
) + 
  ggplot2::geom_line(size=1.5) +
  ggplot2::labs(
    title = "Inwoners per jaar",
    y = "Inwoneraantal",
    color = "Gemeente"
  ) +
  ggplot2::theme_bw() 
#> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
#>  Please use `linewidth` instead.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.

Oplossing

Installatie

Het pakket van grenswijzigingen kan geïnstalleerd worden vanuit GitHub als:

# Install the development version from GitHub
devtools::install_github("reidhin/grenswijzigen") 

Indien the pakket grenswijzigingen samen met de vignettes moet worden geïnstalleerd vanuit Github dan kan het volgende commando gebruikt worden:

# Install the development version from GitHub, explicitly requesting vignettes
devtools::install_github(
  "reidhin/grenswijzigen", 
  dependencies = TRUE, 
  build_vignettes = TRUE
)

Indien gewenst kan de gehele code ook vanuit GitHub gecloned worden.

De hoofdfunctie voor het uitvoeren van de code is de functie wrapper_vertaal_naar_peiljaar in het bestand grenswijzigingen.R. Indicatoren die bestaan uit aantallen (bijvoorbeeld aantal inwoners in de wijk of aantal 65-plussers in de wijk) worden intern op een andere manier behandeld dan indicatoren die bestaan uit percentages of aandelen (bijvoorbeeld gemiddelde huishoudgrootte of aandeel arbeidsongeschikten per wijk). De functie wrapper_vertaal_naar_peiljaar probeert op basis van kolomnamen zelf in te schatten of het om aantallen of aandelen gaat.

Visualisatie wijzigingen

Als voorbeeld is hieronder de verandering in gemeenten voor de jaren 2020 naar 2021 weergegeven. Het totaal aantal gemeenten neemt af van 355 in het jaar 2020 naar 352 in het jaar 2021. De drie noordelijke gemeenten Appingedam, Loppersum en Delfzijl gaan op in een nieuwe gemeente met de naam Eemsdelta. De gemeente Haaren verdwijnt van 2020 naar 2021 in zijn geheel en gaat op in de vier gemeenten Boxtel, Oisterwijk, Tilburg, en Vught. In het Sankey diagram hieronder is grafisch weergegeven hoe de adressen verschuiven tussen de gemeenten van 2020 naar 2021. Naast de bovengenoemde gemeenten gaat er een enkel adres van de gemeente Noordenveld naar de buurgemeente Westerveld, vermoedelijk vanwege een grenscorrectie.

library(grenswijzigen)
library(networkD3)

mat <-grenswijziging_toevoeging_gemeente_van_2020_naar_2021
df.links <- data.frame(
  value=as.vector(mat), 
  target=rep(0:(nrow(mat)-1), ncol(mat)), 
  source=rep(nrow(mat):(nrow(mat)+ncol(mat)-1), each=nrow(mat))
)
df.nodes <- merge(
  data.frame(
    name=c(rownames(mat), colnames(mat)),
    Year=c(rep(2021, nrow(mat)), rep(2020, ncol(mat)))
  ),
  df %>% mutate(name=trimws(gsub("(^GM)", "", WijkenEnBuurten))),
  sort=FALSE
)
df.links <- df.links[df.links$value > 0, ]

sankeyNetwork(
  Links = df.links, 
  Nodes = df.nodes, 
  Source = 'source',
  Target = 'target', 
  Value = 'value', 
  NodeID = 'WijkenEnBuurten_label',
  fontSize = 12
)

Als tweede voorbeeld is hieronder de verandering in de Wageningse wijken van 2017 naar 2018 weergegeven. In 2017 heeft Wageningen slechts twee wijken: Stedelijk gebied en Landelijk gebied. In 2018 heeft Wageningen de wijkindeling verfijnd naar twaalf wijken. In het Sankey diagram hieronder is grafisch weergegeven hoe de adressen verschuiven tussen de wijken van 2017 naar 2018. De wijzigingen zijn tamelijk complex, in de zin dat de nieuwe wijken zowel uit adressen uit stedelijk als uit landelijk gebied kunnen zijn samengesteld.

library(grenswijzigen)
library(networkD3)

mat <-grenswijziging_toevoeging_wijk_van_2017_naar_2018
# take only Wageningen (gwb_code begint met 0289)
mat <- mat[grepl("^0289", rownames(mat)), grepl("^0289", colnames(mat))]
df.links <- data.frame(
  value=as.vector(mat), 
  target=rep(0:(nrow(mat)-1), ncol(mat)), 
  source=rep(nrow(mat):(nrow(mat)+ncol(mat)-1), each=nrow(mat))
)
df.nodes <- merge(
  data.frame(
    name=c(rownames(mat), colnames(mat)),
    Year=c(rep(2018, nrow(mat)), rep(2017, ncol(mat)))
  ),
  df %>% mutate(name=trimws(gsub("(^WK)", "", WijkenEnBuurten))),
  sort=FALSE
)
df.links <- df.links[df.links$value > 0, ]

sankeyNetwork(
  Links = df.links, 
  Nodes = df.nodes, 
  Source = 'source',
  Target = 'target', 
  Value = 'value', 
  NodeID = 'WijkenEnBuurten_label',
  fontSize = 12
)

Toepassen grenscorrecties

Het CBS rapporteert de wijzigingen in gemeentegrenzen en hoe gemeentekenmerken over de jaren heen vergeleken kunnen worden. Voor de wijkgrenzen is er geen jaarlijkse publicatie over hoe wijkkenmerken over de jaren heen vergeleken kunnen worden. Het CBS geeft enkel aan of de cijfers vergeleken mogen worden met het jaar daarvoor. Als de cijfers niet vergelijkbaar zijn met het vorige jaar wordt niet aangegeven hoe deze getransformeerd moeten worden.

In principe kan het CBS of de gemeente op basis van zogenaamde microdata exact uitrekenen wat de wijkkenmerken van vorige jaren zijn voor de huidige wijkgrenzen. Deze microdata bestaat uit kenmerken op persoons- of huishoudensniveau. Voor het rekenen met microdata moeten de privacy regels goed gewaarborgd worden.

Indien er geen beschikking is over microdata, of als men niet met privacy gevoelige data wil of kan rekenen, kunnen er toch schattingen worden gemaakt van de wijkkenmerken van vorige jaren met de huidige wijkgrenzen. Deze repository bevat R-scripts waarmee dergelijke schattingen gemaakt kunnen worden.

Zie deze link (voor het laatst bijgewerkt in 2022) voor een dashboard die grenswijzigingen inzichtelijk maakt https://datamonitoringvng.shinyapps.io/grenswijzigingen/

De code hieronder geeft aan hoe de data uit verschillende jaren met verschillende gemeentelijke indelingen omgezet kan worden naar een bepaald peiljaar. Hieronder is voor het peiljaar 2024 gekozen. Er moet worden aangegeven welk model wordt gekozen voor de omzetting. In dit geval is voor model.2 gekozen. Ook het regionale niveau en of de variabelen als aantallen of percentages moeten worden behandeld kan worden aangegeven.


# Transform the historic data to 2024
df_transformed <- wrapper_vertaal_naar_peiljaar(
  
  # the package requires 'wijkcode' and 'jaar' as column names
  as.data.frame(
    df %>% rename(
      wijkcode = WijkenEnBuurten,
      gemeentenaam = WijkenEnBuurten_label,
      jaar = Year
    ) %>%
      filter(grepl("^GM", wijkcode)) %>%
      mutate(wijkcode=trimws(wijkcode))
  ),
  peiljaar = 2024,
  model="model.2",
  regionaalniveau = "gemeente",
  kolommen_aantal = c("AantalInwoners_5", "k_65JaarOfOuder_12")
) 
#> ----------------------------
#> De volgende kolommen worden niet meegenomen in de grensomzetting
#> wijkcode
#> gemeentenaam
#> jaar
#> gwb_code
#> ----------------------------
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"
#> [1] "Aantal rijen omgezette data-frame: 342"

# show that we have equal numbers of municipalities each year
print(table(df_transformed$jaar))
#> 
#> 2016 2017 2018 2019 2020 2021 2022 2023 2024 
#>  342  342  342  342  342  342  342  342  342

De omzetting van de dat leidt tot een dataset die over de jaren 2016 tot en met 2024 met elkaar vergeleken kan worden. Voor de jaren waarbij de gemeente nog niet bestond in de huidige vorm, worden de getallen ingeschat volgens model.2 zoals in de code hierboven gerealiseerd wordt. Dit alles leidt tot de grafiek hieronder. Het is duidelijk dat demografische trends nu beter waarneembaar zijn.


# show how the transformed data looks like
ggplot2::ggplot(
  data = df_transformed %>%
    filter(gemeentenaam %in% selected_municipalities),
  mapping = ggplot2::aes(
    x=jaar, 
    y=AantalInwoners_5, 
    color=gemeentenaam,
    linetype=berekend
  )
) + 
  ggplot2::geom_line(size=1.5, linetype="dotted") +
  ggplot2::geom_line(size=1.5) +
  ggplot2::labs(
    title = "Inwoners per jaar",
    y = "Inwoneraantal",
    color = "Gemeente",
    linetype = "Oorsprong gegevens"
  ) + 
  ggplot2::scale_linetype_manual(values=c("solid", "dotted"), labels=c("Daadwerkelijk", "Geschat")) +
  ggplot2::theme_bw()

Modellen voor grenswijzigen

Alle modellen zijn gebaseerd op het volgen van adressen door de tijd heen. Hierdoor kan worden achterhaalt bij welke wijk een adres hoort op in elk jaar. Vervolgens zijn er verschillende manieren om deze informatie te benutten. In deze repository zijn er drie modellen geïmplementeerd voor het uitvoeren van grenswijzigingen.

  • Model.0 gaat uit van een uniforme verdeling van wijkkenmerken over de wijk heen. Dat wil zeggen dat wordt aangenomen dat kenmerken, bijvoorbeeld het aantal 65-plussers, gelijkelijk verdeeld zijn over de wijk. Bij het delen van de wijk in kleinere stukken kunnen deze kenmerken dan evenredig met het aantal adressen in deze stukken worden toegekend. In Model.0 zijn adressen gedefinieerd als postcode + huisnummer. Huisnummertoevoegingen en gebruiksfunctie van adressen worden niet meegenomen. Model.0 is daarmee een heel toegankelijk model dat een redelijk goede grenswijziging uitvoerd, maar dat faalt in geval van wijken met veel huisnummertoevoegingen (bijvoorbeeld hoogbouw of flats) en wijken met veel adressen zonder woonfunctie (bijvoorbeeld winkels, scholen en fabrieken).

  • Model.1 werkt gelijk aan Model.0 maar neemt daarentegen wel de huisnummertoevoeging en gebruiksfunctie van het adres mee. In Model.1 worden alleen adressen met een woonfunctie beschouwt. Daarmee presteert Model.1 aanzienlijk beter dan Model.0 voor wijken met veel huisnummertoevoegingen en andere gebruiksfuncties dan wonen. Model.1 gaat echter nog wel altijd uit van een uniforme verdeling van de wijkkenmerken over de wijk heen. Daarnaast wordt er een VNG api gebruikt om de huisnummertoevoegingen en gebruiksfunctie te achterhalen Dat kost het tamelijk veel tijd om uit te voeren voor alle adressen in Nederland. Daarom zijn ook vooraf berekende omzetmatrices beschikbaar gesteld in deze repository.

  • Model.2 neemt een andere benadering. Hier worden adressen opgedeeld in blokken gedefinieerd door de overlappingen tussen wijken in het ene jaar en de wijken in het volgende jaar. Voor elk blok wordt een variabele gedefinieerd die het gemiddelde van een wijkkenmerk in dat blok weergeeft. Voor deze variabelen wordt een stelsel formules opgesteld zodat de sommen van de adressen maal de gemiddelden gelijk zijn aan de wijkkenmerken zoals gepubliceerd door het CBS. Daarnaast wordt aangenomen dat deze gemiddelden van jaar tot jaar nauwelijks wijzigen. Dat lijkt een redelijke aanname: bijvoorbeeld het gemiddelde aantal 65-plussers per adres zal van jaar tot jaar ongeveer gelijk zijn. Dit stelsel vergelijkingen wordt opgelost wat tot grensgewijzigde data leidt. In dit model wordt eveneens uitgegaan van adressen met huisnummertoevoegingen en woonfunctie.

Beperkingen

In de gebruikte benadering wordt aangenomen dat de demografische kenmerken van jaar op jaar op een groepje adressen relatief constant is. Op het moment dat een wijk of gemeente onder sterke ontwikkeling onderhevig is, zal deze benadering minder goed werken. Daarbij kan gedacht worden aan grootschalige nieuwbouw of herbestemming van bestaande panden.