# ============================================================ # process_manifesto.R - Manifesto Project Data Processing # ============================================================ # Processes Manifesto Project data for the two-dimensional party-position model # Input: $PARTY2D_RAW_DATA_DIR/manifesto/MPDataset_MPDS2025a.csv # Output: manifesto_data.csv # ============================================================ library(tidyverse) library(countrycode) library(purrr) # Set working directory (works both in RStudio and command line) if (interactive() && requireNamespace("rstudioapi", quietly = TRUE)) { try(setwd(dirname(rstudioapi::getActiveDocumentContext()$path)), silent = TRUE) } cat("Processing Manifesto Project data...\n") raw_data_dir <- Sys.getenv( "PARTY2D_RAW_DATA_DIR", unset = file.path("..", "..", "_local", "raw") ) manifesto_raw_path <- file.path(raw_data_dir, "manifesto", "MPDataset_MPDS2025a.csv") partyfacts_path <- file.path(raw_data_dir, "partyfacts", "partyfacts-external-parties.csv") # ============================================================ # PartyFacts Linkage # ============================================================ partyfacts_raw <- read_csv(partyfacts_path, show_col_types = FALSE) manifesto_link <- partyfacts_raw %>% filter(dataset_key == "manifesto") %>% transmute(id = dataset_party_id, country = countrycode(country, origin = 'iso3c', destination = "iso2c"), party = partyfacts_id, party = ifelse(party == 622, 604, party)) # ============================================================ # Load Manifesto Data # ============================================================ manifesto_data <- read_csv(manifesto_raw_path, show_col_types = FALSE) # ============================================================ # CMP Code Mapping to 4 Dimensions # ============================================================ vars <- tribble( ~type, ~subtype, ~per_var, ~stance, ~label, # pro_market "pro_market", "Market Regulation", "per401", "Positive", "Free Market Economy", "pro_market", "Economic Liberalization","per402", "Positive", "Incentives: Positive", "pro_market", "Market Regulation", "per407", "Positive", "Protectionism: Negative", "pro_market", "Economic Liberalization","per414", "Positive", "Economic Orthodoxy", "pro_market", "Economic Liberalization","per505", "Positive", "Welfare State Limitation", "pro_market", "Economic Liberalization","per507", "Positive", "Education Limitation", "pro_market", "Economic Liberalization","per702", "Positive", "Labour Groups: Negative", "pro_market", "Market Regulation", "per406", "Negative", "Protectionism: Positive", "pro_market", "Market Regulation", "per412", "Negative", "Controlled Economy", "pro_market", "Economic Liberalization","per504", "Negative", "Welfare State Expansion", # pro_welfare "pro_welfare", "Economic Intervention", "per403", "Positive", "Market Regulation", "pro_welfare", "Economic Intervention", "per404", "Positive", "Economic Planning", "pro_welfare", "Economic Intervention", "per412", "Positive", "Controlled Economy", "pro_welfare", "Economic Intervention", "per413", "Positive", "Nationalisation", "pro_welfare", "Social Services", "per504", "Positive", "Welfare State Expansion", "pro_welfare", "Social Services", "per506", "Positive", "Education Expansion", "pro_welfare", "Economic Intervention", "per701", "Positive", "Labour Groups: Positive", "pro_welfare", "Economic Intervention", "per401", "Negative", "Free Market Economy", "pro_welfare", "Social Services", "per505", "Negative", "Welfare State Limitation", # cosmopolitan "cosmopolitan", "Internationalism", "per107", "Positive", "Internationalism: Positive", "cosmopolitan", "Internationalism", "per108", "Positive", "European Community/Union: Positive", "cosmopolitan", "Multiculturalism", "per607", "Positive", "Multiculturalism: Positive", "cosmopolitan", "Multiculturalism", "per201", "Positive", "Freedom and Human Rights", "cosmopolitan", "Multiculturalism", "per604", "Positive", "traditional Morality: Negative", "cosmopolitan", "Internationalism", "per109", "Negative", "Internationalism: Negative", "cosmopolitan", "Multiculturalism", "per601", "Negative", "National Way of Life: Positive", # traditional "traditional", "National Identity", "per109", "Positive", "Internationalism: Negative", "traditional", "Conservative Morality", "per110", "Positive", "European Community/Union: Negative", "traditional", "National Identity", "per601", "Positive", "National Way of Life: Positive", "traditional", "Conservative Morality", "per603", "Positive", "traditional Morality: Positive", "traditional", "Conservative Morality", "per608", "Positive", "Multiculturalism: Negative", "traditional", "Conservative Morality", "per605", "Positive", "Law and Order: Positive", "traditional", "National Identity", "per107", "Negative", "Internationalism: Positive", "traditional", "Conservative Morality", "per607", "Negative", "Multiculturalism: Positive" ) # ============================================================ # Process Manifesto Data # ============================================================ manifesto <- vars %>% pmap_dfr(~ manifesto_data %>% transmute(country = countrycode(countryname, origin = 'country.name', destination = 'iso2c'), year = as.numeric(format(as.Date(edate, format = "%d/%m/%Y"), "%Y")), id = as.character(party), count = round(.data[[..3]]), var = ..3, label = ..5, type = ..1, subtype = ..2, stance = ..4, project = 'Manifesto Project') %>% left_join(manifesto_link, by = c("id", "country")) %>% select(-id)) %>% group_by(party, country, year, subtype) %>% summarise( positive = sum(count[stance == "Positive"], na.rm = TRUE), sample = sum(count, na.rm = TRUE), type = first(type), project = first(project), .groups = "drop" ) %>% na.omit() %>% rename(var = subtype) %>% # Convert to bipolar bridge structure (type_high/type_low) mutate( type_high = case_when( type == "pro_welfare" ~ "pro_welfare", type == "pro_market" ~ "pro_market", type == "cosmopolitan" ~ "cosmopolitan", type == "traditional" ~ "traditional" ), type_low = case_when( type %in% c("pro_welfare", "pro_market") ~ ifelse(type == "pro_welfare", "pro_market", "pro_welfare"), type %in% c("cosmopolitan", "traditional") ~ ifelse(type == "cosmopolitan", "traditional", "cosmopolitan") ) ) %>% select(-type) %>% # Add _manifesto suffix to variable names mutate(var = paste0(tolower(gsub(" ", "_", var)), "_manifesto")) # ============================================================ # NOTE: Temporal continuity filter moved to the data setup orchestrator. # This allows exempting parties that appear in parliamentary data # (parties in parliament are by definition not fringe parties) # ============================================================ cat("Skipping temporal filter (applied in 02_build_model_inputs.R after combining with other text data)\n") cat(sprintf(" Parties: %d\n", n_distinct(manifesto$party))) # ============================================================ # Write Output # ============================================================ write_csv(manifesto, "manifesto_data.csv") cat(sprintf("Output: manifesto_data.csv (%d rows, %d parties)\n", nrow(manifesto), n_distinct(manifesto$party))) # ============================================================ # Election Data Extraction (vote shares) # ============================================================ cat("\nExtracting election data (pervote)...\n") election_data <- manifesto_data %>% transmute( country = countrycode(countryname, origin = 'country.name', destination = 'iso2c'), year = as.numeric(format(as.Date(edate, format = "%d/%m/%Y"), "%Y")), id = as.character(party), pervote = pervote ) %>% left_join(manifesto_link, by = c("id", "country")) %>% select(-id) %>% filter(!is.na(party), !is.na(pervote)) %>% # Keep one row per (party, country, year) — take max pervote if duplicates group_by(party, country, year) %>% summarise(pervote = max(pervote, na.rm = TRUE), .groups = "drop") %>% arrange(country, party, year) write_csv(election_data, "election_data.csv") cat(sprintf("Output: election_data.csv (%d rows, %d parties)\n", nrow(election_data), n_distinct(election_data$party))) # Export manifesto_link for use by other scripts # (poldem also needs it for CMP linkage)