Wire local-only data setup workflow

This commit is contained in:
aseimel
2026-06-15 14:38:47 +02:00
parent b5ca9370f1
commit d9c648bd54
21 changed files with 2442 additions and 110 deletions
+5 -78
View File
@@ -1,20 +1,16 @@
#!/usr/bin/env julia
#=
02_enrich_output.jl - Enrich party positions CSV with metadata
02_enrich_output.jl - Enrich party positions CSV with model-input-derived metadata
Fast post-processing script that adds party names, union membership status,
and election results to the model output CSV. Operates purely on CSV files
(no chain loading). Takes seconds, not minutes.
Fast post-processing script that adds union membership status from the model-ready
inputs. Operates purely on CSV files (no chain loading). Takes seconds, not minutes.
Usage:
julia 02_enrich_output.jl # enriches latest party_positions_*.csv
julia 02_enrich_output.jl somefile.csv # enriches a specific file
Adds columns:
party_name_english - Full English party name (from data/party_names.csv)
party_name_short - Standard abbreviation
in_union - 1 if party's union had a joint manifesto that year, 0 otherwise
pervote - Vote share (%) at election years; missing for non-election years
=#
using CSV
@@ -40,32 +36,7 @@ function enrich(input_file::String)
output = CSV.read(input_file, DataFrame)
println(" Rows: $(nrow(output)), Columns: $(ncol(output))")
# --- Party names ---
party_names_file = joinpath("data", "party_names.csv")
if isfile(party_names_file)
names_df = CSV.read(party_names_file, DataFrame)
name_lookup = Dict{Int, Tuple{String, String}}()
for row in eachrow(names_df)
short = ismissing(row.party_name_short) ? "" : string(row.party_name_short)
name_lookup[row.partyfacts_id] = (string(row.party_name_english), short)
end
output.party_name_english = [
haskey(name_lookup, pid) ? name_lookup[pid][1] : ""
for pid in output.party_id
]
output.party_name_short = [
haskey(name_lookup, pid) ? name_lookup[pid][2] : ""
for pid in output.party_id
]
n_named = count(x -> x != "", output.party_name_english)
println(" Party names: $n_named / $(nrow(output)) rows matched")
else
output.party_name_english = fill("", nrow(output))
output.party_name_short = fill("", nrow(output))
println(" WARNING: data/party_names.csv not found")
end
# --- Union mapping (for in_union + pervote fallback) ---
# --- Union mapping (for in_union) ---
union_mapping_file = joinpath("data", "union_mapping.csv")
constituent_to_union = Dict{Int, Int}()
if isfile(union_mapping_file)
@@ -126,49 +97,6 @@ function enrich(input_file::String)
println(" WARNING: Could not compute in_union (missing files)")
end
# --- Election results (pervote) ---
election_file = joinpath("data", "election_data.csv")
output.pervote = Vector{Union{Float64, Missing}}(missing, nrow(output))
if isfile(election_file)
election_df = CSV.read(election_file, DataFrame)
# Build lookup: (party_id, year) -> pervote
election_lookup = Dict{Tuple{Int, Int}, Float64}()
for row in eachrow(election_df)
election_lookup[(row.party, row.year)] = row.pervote
end
n_filled = 0
for i in 1:nrow(output)
pid = output.party_id[i]
yr = output.year[i]
# Direct match (standalone party)
if haskey(election_lookup, (pid, yr))
output.pervote[i] = election_lookup[(pid, yr)]
n_filled += 1
elseif hasproperty(output, :union_party_id) && !ismissing(output.union_party_id[i])
# Union constituent: look up by union PF ID
uid = output.union_party_id[i]
if haskey(election_lookup, (uid, yr))
output.pervote[i] = election_lookup[(uid, yr)]
n_filled += 1
end
elseif haskey(constituent_to_union, pid)
# Fallback: use union mapping even if union_party_id column not populated
uid = constituent_to_union[pid]
if haskey(election_lookup, (uid, yr))
output.pervote[i] = election_lookup[(uid, yr)]
n_filled += 1
end
end
end
println(" pervote: $n_filled / $(nrow(output)) rows filled")
else
println(" WARNING: election_data.csv not found")
end
# --- Reorder columns ---
estimate_cols = Symbol[]
for base in ["economic_lr", "galtan", "pro_market", "pro_welfare", "cosmopolitan", "traditional"]
@@ -185,8 +113,7 @@ function enrich(input_file::String)
output.country = replace(output.country, "MO" => "MK")
col_order = vcat(
[:party_id, :party_name_english, :party_name_short, :country, :year, :segment_num,
:union_party_id, :in_union, :pervote],
[:party_id, :country, :year, :segment_num, :union_party_id, :in_union],
estimate_cols
)
col_order = filter(c -> hasproperty(output, c), col_order)