# ============================================================ # process_poldem.R - PolDem Media Data Processing # ============================================================ # Processes PolDem (Political Deliberation in the Media) data # for the two-dimensional party-position model # # Input: $PARTY2D_RAW_DATA_DIR/poldem/poldem-election_all.csv (sentence-level) # Output: poldem_data.csv (party-year-var aggregates) # ============================================================ library(tidyverse) library(countrycode) # 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 PolDem media data...\n") raw_data_dir <- Sys.getenv( "PARTY2D_RAW_DATA_DIR", unset = file.path("..", "..", "_local", "raw") ) poldem_raw_path <- file.path(raw_data_dir, "poldem", "poldem-election_all.csv") partyfacts_path <- file.path(raw_data_dir, "partyfacts", "partyfacts-external-parties.csv") # ============================================================ # PartyFacts Linkage (via CMP party IDs) # ============================================================ partyfacts_raw <- read_csv(partyfacts_path, show_col_types = FALSE) manifesto_link <- partyfacts_raw %>% filter(dataset_key == "manifesto") %>% transmute(cmp = as.numeric(dataset_party_id), # Convert to numeric for join country_pf = countrycode(country, origin = 'iso3c', destination = "iso2c"), party = partyfacts_id, party = ifelse(party == 622, 604, party)) # ============================================================ # Issue Category Mapping to 4 Dimensions # ============================================================ # For positive direction: type_high is the active trait # For negative direction: we flip (same data, just contributes to the opposite trait) poldem_mapping <- tribble( ~issue_cat, ~dimension, ~type_high, ~type_low, # Economic dimension "ecolib", "economic", "pro_market", "pro_welfare", # Economic liberalization "welfare", "economic", "pro_welfare", "pro_market", # Welfare state # Final exclusion: the PolDem economic-reform category is intentionally # omitted because item-response diagnostics showed that it did not load # substantively onto the economic latent trait. # Cultural dimension "immig", "cultural", "cosmopolitan", "traditional", # Immigration (pro = cosmopolitan) "cultlib", "cultural", "cosmopolitan", "traditional", # Cultural liberalism "nationalism", "cultural", "traditional", "cosmopolitan", # Nationalism (pro = traditional) "europe", "cultural", "cosmopolitan", "traditional", # EU integration (pro = cosmopolitan) "euro", "cultural", "cosmopolitan", "traditional", # Euro currency (pro = cosmopolitan) "defense", "cultural", "traditional", "cosmopolitan", # Defense (pro = traditional) "security", "cultural", "traditional", "cosmopolitan" # Security/law-order (pro = traditional) ) cat(sprintf(" Using %d issue categories\n", nrow(poldem_mapping))) # ============================================================ # Load and Clean PolDem Data # ============================================================ poldem_raw <- read_csv(poldem_raw_path, show_col_types = FALSE) cat(sprintf(" Raw PolDem data: %d rows\n", nrow(poldem_raw))) poldem <- poldem_raw %>% # Fix country codes mutate(country = case_when( iso2code == "AU" ~ "AT", # Austria (PolDem uses AU instead of AT) iso2code == "UK" ~ "GB", # United Kingdom TRUE ~ iso2code )) %>% # Extract year from article date (format: YYYY-MM-DD) mutate(year = suppressWarnings(as.numeric(substr(date_art, 1, 4)))) %>% # Filter to valid issue categories only filter(issue_cat %in% poldem_mapping$issue_cat) %>% # Convert direction to numeric and filter out neutral/NA mutate(direction = as.numeric(direction)) %>% filter(!is.na(direction) & direction != 0) %>% # Remove rows with invalid years filter(!is.na(year)) cat(sprintf(" After filtering: %d rows (valid issues, non-neutral)\n", nrow(poldem))) # ============================================================ # Link to PartyFacts via CMP codes # ============================================================ poldem <- poldem %>% mutate(cmp = as.numeric(cmp)) %>% left_join(manifesto_link, by = "cmp") %>% filter(!is.na(party)) # Report linkage n_linked <- nrow(poldem) n_unlinked <- nrow(poldem_raw %>% filter(issue_cat %in% poldem_mapping$issue_cat) %>% mutate(direction = as.numeric(direction)) %>% filter(!is.na(direction) & direction != 0)) - n_linked cat(sprintf(" Linked to PartyFacts: %d rows\n", n_linked)) if (n_unlinked > 0) { cat(sprintf(" Warning: %d rows could not be linked (missing CMP mapping)\n", n_unlinked)) } # ============================================================ # Aggregate to Party-Year-Issue Level # Using round(sum()) for weak direction values (0.5, -0.5) # ============================================================ poldem_agg <- poldem %>% left_join(poldem_mapping, by = "issue_cat") %>% group_by(party, country, year, issue_cat, type_high, type_low) %>% summarise( # Sum positive directions (0.5 and 1), then round positive = round(sum(direction[direction > 0])), # Sum absolute directions for sample (all non-neutral), then round sample = round(sum(abs(direction))), n_obs = n(), .groups = "drop" ) %>% # Minimum 3 observations per group filter(n_obs >= 3) %>% select(-n_obs) cat(sprintf(" After aggregation: %d party-year-issue observations\n", nrow(poldem_agg))) # ============================================================ # Format Output (matching manifesto structure) # ============================================================ poldem_data <- poldem_agg %>% mutate( var = paste0(issue_cat, "_poldem"), project = "PolDem" ) %>% select(party, country, year, var, positive, sample, type_high, type_low, project) # ============================================================ # Write Output # ============================================================ write_csv(poldem_data, "poldem_data.csv") cat(sprintf("\nOutput: poldem_data.csv\n")) cat(sprintf(" Total rows: %d\n", nrow(poldem_data))) cat(sprintf(" Unique parties: %d\n", n_distinct(poldem_data$party))) cat(sprintf(" Countries: %s\n", paste(sort(unique(poldem_data$country)), collapse = ", "))) cat(sprintf(" Year range: %d-%d\n", min(poldem_data$year, na.rm = TRUE), max(poldem_data$year, na.rm = TRUE))) cat("\n Rows by issue category:\n") poldem_data %>% group_by(var) %>% summarise(n = n(), .groups = "drop") %>% arrange(desc(n)) %>% print()