Introduction to gerda

library(gerda)

Overview

The gerda package provides functions to access and work with GERDA datasets. The German Election Database (GERDA) provides data on federal elections, state (Landtag) elections, local (Kommunal) elections, mayoral (Bürgermeister) elections, Landrat elections, European Parliament elections, and county (Kreistag) elections. Federal data include municipality-, county-, and constituency-level files; state data include municipality- and constituency-level files. GERDA also supplies geographically harmonized datasets that account for changes in municipal boundaries and mail-in voting districts.

In addition to election results, the package provides county-level socioeconomic covariates from INKAR, municipality-level data from the German Census 2022, and a party crosswalk that maps GERDA party names to standardized ParlGov attributes.

GERDA was compiled by Vincent Heddesheimer, Florian Sichart, Andreas Wiedemann and Hanno Hilbig. For additional information, see also the GERDA website (www.german-elections.com) and the accompanying publication: doi.org/10.1038/s41597-025-04811-5

This vignette will introduce you to the main functions of the package and demonstrate how to use them.

For a stricter workflow intended for unattended coding agents, including snapshot provenance, key audits, guarded joins, and machine-readable join diagnostics, see vignette("agent-workflow").

Available Datasets

To see a list of all available GERDA electoral result datasets, you can use the gerda_data_list() function:

gerda_data_list()
#> municipal_unharm                 Local elections at the municipal level (1984-2026, unharmonized; includes council seat counts (seats_*) where the state source reports them).
#> municipal_harm                   Local elections at the municipal level (1990-2026, harmonized).
#> municipal_harm_25                Local elections at the municipal level (1990-2026, harmonized to 2025 boundaries).
#> state_unharm                     State elections at the municipal level (2006-2019, unharmonized).
#> state_harm                       State elections at the municipal level (2006-2019, harmonized).
#> state_harm_21                    State elections at the municipal level, harmonized to 2021 boundaries.
#> state_harm_23                    State elections at the municipal level, harmonized to 2023 boundaries.
#> state_harm_25                    State elections at the municipal level, harmonized to 2025 boundaries.
#> ltw_wkr_unharm                   State (Landtag) elections at the Wahlkreis (constituency) level (1990-2026, unharmonized).
#> ltw_wkr_unharm_long              State (Landtag) elections at the Wahlkreis level (1990-2026), long format with vote counts.
#> federal_muni_raw                 Federal elections at the municipal level (1980-2025, raw data).
#> federal_muni_unharm              Federal elections at the municipal level (1980-2025, unharmonized).
#> federal_muni_harm_21             Federal elections at the municipal level (1990-2025, harmonized to 2021 boundaries).
#> federal_muni_harm_25             Federal elections at the municipal level (1990-2025, harmonized to 2025 boundaries).
#> federal_cty_unharm               Federal elections at the county level (1953-2021, unharmonized).
#> federal_cty_harm                 Federal elections at the county level (1990-2021, harmonized).
#> federal_wkr_unharm               Federal elections at the Wahlkreis (constituency) level (2002-2025, unharmonized).
#> federal_wkr_unharm_long          Federal elections at the Wahlkreis level (2002-2025), long format with vote counts.
#> federal_wkr_2021_on_2025         Federal 2021 result recomputed onto the 2025 Wahlkreis boundaries (official recomputation).
#> county_elec_unharm               County (Kreistag) elections at the municipal level, unharmonized.
#> county_elec_harm_21_cty          County (Kreistag) elections aggregated to county level, harmonized to 2021 boundaries.
#> county_elec_harm_21_muni         County (Kreistag) elections at the municipal level, harmonized to 2021 boundaries.
#> county_council_seats             County council (Kreistag) seat composition, yearly panel on fixed current county boundaries (2008-2025).
#> european_muni_unharm             European Parliament elections at the municipal level, unharmonized.
#> european_muni_harm               European Parliament elections at the municipal level, harmonized.
#> mayoral_unharm                   Mayoral election results at the municipal level, unharmonized.
#> mayoral_harm                     Mayoral election results at the municipal level, harmonized.
#> mayoral_candidates               Mayoral candidates (person-level).
#> mayor_panel                      Mayor panel (person-level, one row per mayor-term).
#> mayor_panel_harm                 Mayor panel (person-level, harmonized to current boundaries).
#> mayor_panel_annual               Mayor panel at annual frequency (one row per municipality-year).
#> mayor_panel_annual_harm          Mayor panel at annual frequency, harmonized to current boundaries.
#> landrat_unharm                   Landrat (county executive) elections at the county level (1945-2026, unharmonized).
#> landrat_candidates               Landrat candidates (person-level, includes Stichwahl and candidate attributes).
#> ags_crosswalks                   Crosswalks for municipalities (1990-2025).
#> cty_crosswalks                   Crosswalks for counties (1990-2025).
#> wkr_2021_to_2025_crosswalk       Bundestag Wahlkreis crosswalk: 2021 to 2025 (unchanged / redrawn / new).
#> ags_1990_to_2023_crosswalk       Municipality crosswalk: 1990 boundaries to 2023 boundaries.
#> ags_1990_to_2025_crosswalk       Municipality crosswalk: 1990 boundaries to 2025 boundaries.
#> crosswalk_ags_2021_to_2023       Municipality crosswalk: AGS 2021 to AGS 2023 (targeted).
#> crosswalk_ags_2021_2022_to_2023  Municipality crosswalk: AGS 2021 and 2022 to AGS 2023 (targeted).
#> crosswalk_ags_2023_to_2025       Municipality crosswalk: AGS 2023 to AGS 2025 (targeted; RDS only).
#> crosswalk_ags_2023_24_to_2025    Municipality crosswalk: AGS 2023 and 2024 to AGS 2025 (targeted; RDS only).
#> crosswalk_ags_2024_to_2025       Municipality crosswalk: AGS 2024 to AGS 2025 (targeted; RDS only).
#> ags_area_pop_emp                 Crosswalk covariates (area, population, employment) for municipalities (1990-2025).
#> ags_area_pop_emp_2023            Crosswalk covariates (area, population, employment) for municipalities, harmonized to 2023 boundaries.
#> cty_area_pop_emp                 Crosswalk covariates (area, population, employment) for counties (1990-2025).

This function displays a formatted table with the names and descriptions of all available datasets. Use gerda_data_list(print_table = FALSE) to obtain the structured catalog, then pass a value from its data_name column to load_gerda_web().

Loading Data

The main function for loading GERDA data is load_gerda_web(). This function allows you to load a specific dataset from a web source. Here’s an example of how to use it:

# Load the municipal harmonized dataset
municipal_harm_data <- load_gerda_web(
  "municipal_harm",
  verbose = TRUE,
  file_format = "rds",
  on_error = "stop",
  cache = TRUE
)

The load_gerda_web() function takes the following parameters:

Choosing the Right Dataset

The dataset variant is a substantive choice:

The structured catalog supports programmatic filtering:

catalog <- gerda_data_list(print_table = FALSE)
subset(catalog,
       election_type == "federal" &
       geographic_level == "municipality" &
       boundary == "2025")
#> # A tibble: 1 × 9
#>   data_name       description election_type geographic_level year_start year_end
#>   <chr>           <chr>       <chr>         <chr>                 <int>    <int>
#> 1 federal_muni_h… Federal el… federal       municipality           1990     2025
#> # ℹ 3 more variables: boundary <chr>, formats <chr>, candidate_info <lgl>

Example Workflow

Here’s an example of a typical workflow using the gerda package:

  1. List available datasets:
gerda_data_list()
#> municipal_unharm                 Local elections at the municipal level (1984-2026, unharmonized; includes council seat counts (seats_*) where the state source reports them).
#> municipal_harm                   Local elections at the municipal level (1990-2026, harmonized).
#> municipal_harm_25                Local elections at the municipal level (1990-2026, harmonized to 2025 boundaries).
#> state_unharm                     State elections at the municipal level (2006-2019, unharmonized).
#> state_harm                       State elections at the municipal level (2006-2019, harmonized).
#> state_harm_21                    State elections at the municipal level, harmonized to 2021 boundaries.
#> state_harm_23                    State elections at the municipal level, harmonized to 2023 boundaries.
#> state_harm_25                    State elections at the municipal level, harmonized to 2025 boundaries.
#> ltw_wkr_unharm                   State (Landtag) elections at the Wahlkreis (constituency) level (1990-2026, unharmonized).
#> ltw_wkr_unharm_long              State (Landtag) elections at the Wahlkreis level (1990-2026), long format with vote counts.
#> federal_muni_raw                 Federal elections at the municipal level (1980-2025, raw data).
#> federal_muni_unharm              Federal elections at the municipal level (1980-2025, unharmonized).
#> federal_muni_harm_21             Federal elections at the municipal level (1990-2025, harmonized to 2021 boundaries).
#> federal_muni_harm_25             Federal elections at the municipal level (1990-2025, harmonized to 2025 boundaries).
#> federal_cty_unharm               Federal elections at the county level (1953-2021, unharmonized).
#> federal_cty_harm                 Federal elections at the county level (1990-2021, harmonized).
#> federal_wkr_unharm               Federal elections at the Wahlkreis (constituency) level (2002-2025, unharmonized).
#> federal_wkr_unharm_long          Federal elections at the Wahlkreis level (2002-2025), long format with vote counts.
#> federal_wkr_2021_on_2025         Federal 2021 result recomputed onto the 2025 Wahlkreis boundaries (official recomputation).
#> county_elec_unharm               County (Kreistag) elections at the municipal level, unharmonized.
#> county_elec_harm_21_cty          County (Kreistag) elections aggregated to county level, harmonized to 2021 boundaries.
#> county_elec_harm_21_muni         County (Kreistag) elections at the municipal level, harmonized to 2021 boundaries.
#> county_council_seats             County council (Kreistag) seat composition, yearly panel on fixed current county boundaries (2008-2025).
#> european_muni_unharm             European Parliament elections at the municipal level, unharmonized.
#> european_muni_harm               European Parliament elections at the municipal level, harmonized.
#> mayoral_unharm                   Mayoral election results at the municipal level, unharmonized.
#> mayoral_harm                     Mayoral election results at the municipal level, harmonized.
#> mayoral_candidates               Mayoral candidates (person-level).
#> mayor_panel                      Mayor panel (person-level, one row per mayor-term).
#> mayor_panel_harm                 Mayor panel (person-level, harmonized to current boundaries).
#> mayor_panel_annual               Mayor panel at annual frequency (one row per municipality-year).
#> mayor_panel_annual_harm          Mayor panel at annual frequency, harmonized to current boundaries.
#> landrat_unharm                   Landrat (county executive) elections at the county level (1945-2026, unharmonized).
#> landrat_candidates               Landrat candidates (person-level, includes Stichwahl and candidate attributes).
#> ags_crosswalks                   Crosswalks for municipalities (1990-2025).
#> cty_crosswalks                   Crosswalks for counties (1990-2025).
#> wkr_2021_to_2025_crosswalk       Bundestag Wahlkreis crosswalk: 2021 to 2025 (unchanged / redrawn / new).
#> ags_1990_to_2023_crosswalk       Municipality crosswalk: 1990 boundaries to 2023 boundaries.
#> ags_1990_to_2025_crosswalk       Municipality crosswalk: 1990 boundaries to 2025 boundaries.
#> crosswalk_ags_2021_to_2023       Municipality crosswalk: AGS 2021 to AGS 2023 (targeted).
#> crosswalk_ags_2021_2022_to_2023  Municipality crosswalk: AGS 2021 and 2022 to AGS 2023 (targeted).
#> crosswalk_ags_2023_to_2025       Municipality crosswalk: AGS 2023 to AGS 2025 (targeted; RDS only).
#> crosswalk_ags_2023_24_to_2025    Municipality crosswalk: AGS 2023 and 2024 to AGS 2025 (targeted; RDS only).
#> crosswalk_ags_2024_to_2025       Municipality crosswalk: AGS 2024 to AGS 2025 (targeted; RDS only).
#> ags_area_pop_emp                 Crosswalk covariates (area, population, employment) for municipalities (1990-2025).
#> ags_area_pop_emp_2023            Crosswalk covariates (area, population, employment) for municipalities, harmonized to 2023 boundaries.
#> cty_area_pop_emp                 Crosswalk covariates (area, population, employment) for counties (1990-2025).
  1. Load a dataset (in this case, the federal elections at the county level, harmonized):
federal_cty_harm <- load_gerda_web(
  "federal_cty_harm",
  verbose = TRUE,
  on_error = "stop",
  cache = TRUE
)

Joining GERDA Datasets

If you are using add_gerda_covariates() or add_gerda_census(), you can skip this section: the helpers detect the level of your data and use the correct join keys automatically. If you are writing a manual left_join() or merging against other sources, the table below shows which identifier and time columns each family carries.

Dataset family Geographic identifier and additional row keys Time column
municipal_*, state_*, federal_muni_*, european_muni_* ags (8-digit municipality) election_year (+ election_date where available)
mayoral_unharm, mayoral_harm ags; round distinguishes first and runoff rounds election_year, election_date
mayoral_candidates ags + candidate; first- and second-round results are stored in separate columns election_year, election_date, election_date_sw
mayor_panel, mayor_panel_harm ags + person_id election_date
mayor_panel_annual, mayor_panel_annual_harm ags + person_id year
landrat_unharm ags (8-digit county AGS ending in 000); round distinguishes election rounds election_year, election_date
landrat_candidates ags (8-digit county AGS ending in 000) + candidate election_year, election_date, election_date_sw
federal_cty_harm county_code (5-digit county) election_year
federal_cty_unharm county_code (renamed from upstream ags on load) election_year (renamed from upstream year on load)
county_elec_*_cty county_code (5-digit county) election_year
county_elec_unharm, county_elec_*_muni ags (8-digit municipality) election_year
county_council_seats county (5-digit county) year (yearly panel; seat vectors carried forward between elections)
ltw_wkr_unharm state + wkr_nr + stimme election_year, election_date
ltw_wkr_unharm_long state + wkr_nr + stimme + party election_year, election_date
federal_wkr_unharm wkr_nr + stimme election_year, election_date
federal_wkr_unharm_long wkr_nr + stimme + party election_year, election_date
federal_wkr_2021_on_2025 wkr_nr (2025 boundaries) + stimme Fixed 2021 result; no time column
wkr_2021_to_2025_crosswalk wkr_nr (2025) + prior_2021_wkr_nr Boundary vintages are encoded in column names
gerda_covariates() (INKAR, county-level) county_code (5-digit county) year (not election_year)
gerda_census() (Zensus 2022, municipality-level) ags (8-digit municipality) Time-invariant (2022)
ags_crosswalks, ags_1990_to_2023_crosswalk, ags_1990_to_2025_crosswalk, crosswalk_ags_* Pair of AGS codes at source and target vintages Vintage is encoded in column names
cty_crosswalks Pair of 5-digit county codes at source and target vintages Vintage is encoded in column names

Three things to watch for when joining manually:

County-Level Covariates

The gerda package includes county-level socioeconomic and demographic covariates from INKAR (Indikatoren und Karten zur Raum- und Stadtentwicklung). These covariates can be easily merged with GERDA election data to enrich your analyses. INKAR data is available from 1995 to 2022, so covariates can be matched to federal elections from 1998 onwards (earlier elections fall outside the INKAR coverage window).

Quick Start

The easiest way to add covariates to your election data is using the add_gerda_covariates() function:

library(dplyr)

# Load election data and add covariates
merged <- load_gerda_web(
  "federal_cty_harm",
  on_error = "stop",
  cache = TRUE
) %>%
  add_gerda_covariates(unmatched = "error")

join_report <- gerda_join_diagnostics(merged)

Under the hood, add_gerda_covariates() merges on county code and election year. It automatically:

The default unmatched = "warn" retains unexpected unmatched rows with missing joined values and reports exact row and unit counts. Use "error" in automated pipelines or "ignore" only when unmatched keys have already been audited. Retrieve the machine-readable report with gerda_join_diagnostics().

Available Covariates

The covariates dataset includes 30 variables across 10 categories (for the full list of variable names, units, and descriptions, see gerda_covariates_codebook()):

Viewing the Codebook

To see detailed information about each covariate, including units and missing data patterns:

# Get the codebook
codebook <- gerda_covariates_codebook()
print(codebook)

# Find variables with good coverage
library(dplyr)
codebook %>%
  filter(missing_pct < 10) %>%
  select(variable, label, category)

Advanced Usage

For more control, you can access the raw covariates data:

# Get raw covariate data
covs <- gerda_covariates()

# Inspect before merging
summary(covs$unemployment_rate)

# Custom merge
elections <- load_gerda_web("federal_cty_harm")
merged <- elections %>%
  left_join(covs, by = c("county_code" = "county_code", "election_year" = "year"))

Data Coverage

Coverage varies by variable: core indicators (demographics, economy, labor market) are available for all 7 federal election years (1998-2021). Newer INKAR indicators (e.g., childcare, some healthcare variables) are available for 2-3 recent elections only. Consult the codebook’s missing_pct column to check per-variable availability before analysis.

Census 2022 Data

The gerda package includes municipality-level data from the German Census 2022 (Zensus 2022). This cross-sectional snapshot covers approximately 10,800 municipalities and can be merged with any GERDA election dataset.

The main advantage of this covariate data is that it is observed at the municipal level (unlike the county-level INKAR data). This allows for more fine-grained analyses of local election outcomes. However, the census is a single time point (2022), so it does not vary across election years. This means that the resulting merged dataset will have time-invariant covariates, i.e. each municipality receives the same census values for all election years. Users should not conduct analyses that rely on over-time variation in these covariates.

Quick Start

library(gerda)

# Add census data to municipal-level elections
muni_merged <- load_gerda_web(
  "federal_muni_harm_21",
  on_error = "stop",
  cache = TRUE
) |>
  add_gerda_census(unmatched = "error")

# Also works with county-level data (aggregated from municipalities)
county_merged <- load_gerda_web(
  "federal_cty_harm",
  on_error = "stop",
  cache = TRUE
) |>
  add_gerda_census(unmatched = "error")

gerda_join_diagnostics(muni_merged)

Available Indicators

The census data includes 14 indicators across four categories:

Since the census is a 2022 snapshot, the same values are attached to all election years (see also the note above).

The Destatis source publishes a combined 60-74 age bin. GERDA therefore exposes share_50to59_census22 and share_60plus_census22; the source cannot support separate 50-64 and 65+ measures.

Viewing the Codebook

# Get the census codebook
census_cb <- gerda_census_codebook()
print(census_cb)

Data Coverage

Most census variables have >95% municipality coverage. avg_household_size_census22 has approximately 12.5% missing values because Destatis suppresses data for small municipalities under its disclosure rules.

Party Crosswalk Function

The party_crosswalk() function provides a mapping between GERDA party names and standardized party information from the ParlGov database. This is particularly useful for linking GERDA data with other political science datasets or for obtaining standardized party characteristics.

Usage

The function takes two main parameters:

Available Mapping Options

You can map GERDA party names to various standardized party characteristics, including:

Example

# Map GERDA party names to left-right positions
parties <- c("cdu", "spd", "linke_pds", "fdp")
left_right_scores <- party_crosswalk(parties, "left_right")
print(left_right_scores)

# Map to English party names
english_names <- party_crosswalk(parties, "party_name_english")
print(english_names)

This function is especially useful when you want to:

Conclusion

The gerda package provides easy access to a wide range of German election and related data. By using the gerda_data_list() function to explore available datasets and load_gerda_web() to load them, you can quickly incorporate this data into your research or analysis projects.

For more information or to provide feedback, please contact or visit the GitHub repository at https://github.com/hhilbig/gerda.