Use cultural dimension labels

This commit is contained in:
aseimel
2026-06-15 15:18:30 +02:00
parent 9a16f3232f
commit 7643658b15
8 changed files with 36 additions and 35 deletions
+3 -3
View File
@@ -5,7 +5,7 @@
## Executes the complete pipeline: data loading → preparation → model fitting
##
## Supports two model versions:
## - "2dim": 2D bipolar model (V1) - estimates economic_lr and galtan directly
## - "2dim": 2D bipolar model (V1) - estimates economic left-right and cultural cosmopolitan--traditionalist positions directly
## - "4dim": 4D unipolar model (V10) - estimates 4 traits, derives 2 scales
##
## Default is 2D model (better identification, faster convergence)
@@ -40,7 +40,7 @@ println(" Adapt delta: $(ADAPT_DELTA)")
println(" Max depth: $(MAX_DEPTH)")
println(" Start year: $(START_YEAR)")
if MODEL_VERSION == "2dim"
println("\n 2D MODEL: Estimates economic_lr and galtan directly")
println("\n 2D MODEL: Estimates economic left-right and cultural cosmopolitan--traditionalist positions directly")
println(" (Half the parameters, better convergence)")
else
println("\n 4D MODEL: Estimates 4 traits, derives 2 scales")
@@ -256,7 +256,7 @@ function run_model(;
println(" Expert dimension-specific observations: $(dat_4dim["N_exp_dim"])")
println(" Expert general L-R observations: $(dat_4dim["N_exp_lr"])")
if MODEL_VERSION == "2dim"
println(" Dimensions estimated: 2 (economic_lr, galtan)")
println(" Dimensions estimated: 2 (economic left-right, cultural cosmopolitan--traditionalist)")
println(" Theta parameters: $(2 * data_prep.R) (2 × R)")
else
println(" Dimensions estimated: 4 (pro_market, pro_welfare, cosmopolitan, traditional)")
+9 -9
View File
@@ -3,7 +3,7 @@
02_post_estimation.jl - Extract party position estimates from Stan model output
Supports both model versions:
- 2D model (V1): Extracts economic_lr and galtan directly
- 2D model (V1): Extracts economic left-right and cultural cosmopolitan--traditionalist positions directly
- 4D model (V10): Extracts 4 traits + 2 derived scales
V10/V1 UPDATE: Handles segment-based indexing
@@ -520,7 +520,7 @@ function extract_estimates(chains::DataFrame, segment_year_map::DataFrame, R::In
# Select quantities based on model version
if model_version == "2dim"
# 2D model: economic_lr and galtan are directly estimated
# 2D model: economic left-right and cultural cosmopolitan--traditionalist positions are directly estimated
# (general_lr is computed in Stan for anchoring but not extracted as output)
quantities = ["economic_lr", "galtan"]
test_col = "economic_lr.1"
@@ -703,7 +703,7 @@ function validate_output(output::DataFrame, segment_info::Union{DataFrame, Nothi
econ_marker = econ_ok ? "" : "*"
galtan_marker = galtan_ok ? "" : "*"
@printf(" %-15s econ=%.2f%s [%.2f-%.2f] galtan=%.2f%s [%.2f-%.2f] %s\n",
@printf(" %-15s economic=%.2f%s [%.2f-%.2f] cultural=%.2f%s [%.2f-%.2f] %s\n",
anchor.name, mean_econ, econ_marker, anchor.econ[1], anchor.econ[2],
mean_galtan, galtan_marker, anchor.galtan[1], anchor.galtan[2], status)
end
@@ -845,9 +845,9 @@ function save_output(output::DataFrame, metadata::Dict, segment_info::Union{Data
if is_2d
println(f, "Model: 2D Direct Bipolar")
println(f, "")
println(f, "Bipolar scales (0 = left/GAL, 1 = right/TAN):")
println(f, "Bipolar scales (0 = left/cosmopolitan, 1 = right/traditionalist):")
println(f, " economic_lr: Economic left-right position (directly estimated)")
println(f, " galtan: GAL-TAN cultural position (directly estimated)")
println(f, " galtan: Cultural cosmopolitan--traditionalist position (directly estimated)")
# Note: general_lr is computed internally for cross-dimensional anchoring
# but not reported as output (the two dimension-specific estimates are preferred)
else
@@ -856,12 +856,12 @@ function save_output(output::DataFrame, metadata::Dict, segment_info::Union{Data
println(f, "Dimensions (0 = low, 1 = high):")
println(f, " pro_market: Pro-market economic position")
println(f, " pro_welfare: Pro-welfare state position")
println(f, " cosmopolitan: Cosmopolitan/GAL cultural position")
println(f, " traditional: Traditional/TAN cultural position")
println(f, " cosmopolitan: Cosmopolitan cultural position")
println(f, " traditional: Traditionalist cultural position")
println(f, "")
println(f, "Derived bipolar scales (0 = left/GAL, 1 = right/TAN):")
println(f, "Derived bipolar scales (0 = left/cosmopolitan, 1 = right/traditionalist):")
println(f, " economic_lr: Economic left-right (derived from pro_market - pro_welfare)")
println(f, " galtan: GAL-TAN (derived from traditional - cosmopolitan)")
println(f, " galtan: Cultural cosmopolitan--traditionalist (derived from traditional - cosmopolitan)")
end
println(f, "")
println(f, "Uncertainty columns:")
+3 -3
View File
@@ -324,7 +324,7 @@ function validate_country_patterns(model::DataFrame)
sort!(country_stats, :n_obs, rev=true)
@printf("%-4s %6s %6s %8s %8s %8s %8s\n",
"CC", "Parties", "N", "Econ", "SD", "Galtan", "SD")
"CC", "Parties", "N", "Econ", "SD", "Cult", "SD")
println("-"^60)
for row in eachrow(country_stats[1:min(20, nrow(country_stats)), :])
@@ -349,9 +349,9 @@ function validate_country_patterns(model::DataFrame)
push!(issues, "right-skewed economy")
end
if row.mean_galtan < 0.35
push!(issues, "cultural-skewed")
push!(issues, "cosmopolitan-skewed")
elseif row.mean_galtan > 0.65
push!(issues, "TAN-skewed")
push!(issues, "traditionalist-skewed")
end
println(" $(row.country): $(join(issues, ", "))")
end
+11 -11
View File
@@ -201,13 +201,13 @@ function validate_economic_lr(model::DataFrame, expert::DataFrame)
end
function validate_galtan(model::DataFrame, expert::DataFrame)
"""Validate galtan against CHES galtan and V-Party/GPS libcons"""
"""Validate cultural cosmopolitan--traditionalist estimates against CHES and V-Party/GPS cultural measures"""
println("\n" * "="^60)
println("CONVERGENT VALIDITY: galtan")
println("CONVERGENT VALIDITY: cultural cosmopolitan--traditionalist")
println("="^60)
# Filter expert data for GAL-TAN dimension
# Filter expert data for cultural cosmopolitan--traditionalist dimension
galtan_vars = filter(v -> occursin("galtan", v) || occursin("libcon", v), unique(expert.var))
galtan_expert = filter(row -> row.var in galtan_vars, expert)
@@ -338,17 +338,17 @@ function validate_discriminant(model::DataFrame, expert::DataFrame)
@printf(" Model Economic × Expert Economic: r = %.3f (convergent, n=%d)\n", r, nrow(merged_ee))
end
# 2. Expert economic vs Model galtan (discriminant)
# 2. Expert economic vs model cultural dimension (discriminant)
merged_eg = innerjoin(econ_expert, model_gal, on=[:party, :year])
if nrow(merged_eg) >= 10
r = cor(merged_eg.val, merged_eg.galtan)
push!(results, (model_dim="galtan", expert_dim="economic",
r_pearson=r, r_spearman=corspearman(merged_eg.val, merged_eg.galtan),
n=nrow(merged_eg), type="discriminant"))
@printf(" Model GAL-TAN × Expert Economic: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_eg))
@printf(" Model Cultural × Expert Economic: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_eg))
end
# 3. Expert galtan vs Model galtan (convergent - already computed, include for matrix)
# 3. Expert cultural vs model cultural dimension (convergent - already computed, include for matrix)
galtan_vars = filter(v -> occursin("galtan", v) || occursin("libcon", v), unique(expert.var))
galtan_expert = filter(row -> row.var in galtan_vars, expert)
merged_gg = innerjoin(galtan_expert, model_gal, on=[:party, :year])
@@ -357,22 +357,22 @@ function validate_discriminant(model::DataFrame, expert::DataFrame)
push!(results, (model_dim="galtan", expert_dim="galtan",
r_pearson=r, r_spearman=corspearman(merged_gg.val, merged_gg.galtan),
n=nrow(merged_gg), type="convergent"))
@printf(" Model GAL-TAN × Expert GAL-TAN: r = %.3f (convergent, n=%d)\n", r, nrow(merged_gg))
@printf(" Model Cultural × Expert Cultural: r = %.3f (convergent, n=%d)\n", r, nrow(merged_gg))
end
# 4. Expert galtan vs Model economic (discriminant)
# 4. Expert cultural vs model economic dimension (discriminant)
merged_ge = innerjoin(galtan_expert, model_econ, on=[:party, :year])
if nrow(merged_ge) >= 10
r = cor(merged_ge.val, merged_ge.economic_lr)
push!(results, (model_dim="economic_lr", expert_dim="galtan",
r_pearson=r, r_spearman=corspearman(merged_ge.val, merged_ge.economic_lr),
n=nrow(merged_ge), type="discriminant"))
@printf(" Model Economic × Expert GAL-TAN: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_ge))
@printf(" Model Economic × Expert Cultural: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_ge))
end
println()
println("MTMM Matrix:")
println(" Expert Economic Expert GAL-TAN")
println(" Expert Economic Expert Cultural")
for r in results
if r.model_dim == "economic_lr" && r.expert_dim == "economic"
@printf(" Model Economic: %.3f ", r.r_pearson)
@@ -385,7 +385,7 @@ function validate_discriminant(model::DataFrame, expert::DataFrame)
end
for r in results
if r.model_dim == "galtan" && r.expert_dim == "economic"
@printf(" Model GAL-TAN: %.3f ", r.r_pearson)
@printf(" Model Cultural: %.3f ", r.r_pearson)
end
end
for r in results
+4 -3
View File
@@ -87,7 +87,7 @@ function load_chains_selective(run_dir::String, needed_rr::Set{Int}, K::Int)
# Build the set of column names we need
needed_cols = Set{String}()
# theta columns: economic_lr.{rr} and galtan.{rr} for each unique rr
# theta columns: economic_lr.{rr} and galtan.{rr} (cultural cosmopolitan--traditionalist) for each unique rr
for rr in needed_rr
push!(needed_cols, "economic_lr.$rr")
push!(needed_cols, "galtan.$rr")
@@ -384,6 +384,7 @@ function summarize_coverage(result::DataFrame, ci_level::Float64)
# Overall by dimension
dim_names = Dict(1 => "economic_lr", 2 => "galtan")
display_dim_names = Dict(1 => "economic left-right", 2 => "cultural cosmopolitan--traditionalist")
summary_rows = []
for dim in sort(unique(result.dim_idx))
@@ -394,8 +395,8 @@ function summarize_coverage(result::DataFrame, ci_level::Float64)
ci = wilson_ci(ppc, n)
dim_name = dim_names[dim]
println(@sprintf("\n %-15s: %.1f%% [%.1f%%, %.1f%%] (%d/%d)",
dim_name, 100*ppc, 100*ci.lower, 100*ci.upper, n_covered, n))
println(@sprintf("\n %-40s: %.1f%% [%.1f%%, %.1f%%] (%d/%d)",
display_dim_names[dim], 100*ppc, 100*ci.lower, 100*ci.upper, n_covered, n))
push!(summary_rows, (
dimension = dim_name,