163 lines
6.7 KiB
R
163 lines
6.7 KiB
R
# ============================================================
|
|
# 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()
|