// ============================================================================= // 2D BIPOLAR LATENT TRAIT MODEL V6 - Hierarchical L-R Weights // ============================================================================= // // V6 CHANGE: Hierarchical L-R weights varying by country, source, and decade // - Replaces global simplex[2] lr_weights with additive logit-scale model // - logit_weight[n] = global + country_offset[c] + source_offset[k] + decade_offset[d] // - All offsets use non-centered parameterization with estimated sigma hyperparameters // - Data-poor contexts shrink toward global mean (~55 new scalar parameters) // // PRESERVED FROM V5: // - Beta-Binomial expert likelihood with K-scaling // - V-Party cultural expansion (5 native items + v2pawelf) // - Mean-constituent model (individual party estimates for union members) // - Segment-based indexing (S segments, R segment-years) // - 3-level hierarchical variance (global -> country -> family) // - Random walk dynamics within segments // - CDU anchor for scale identification (CDU=1375, not CDU/CSU=211) // - Non-centered parameterization for efficiency // - Zero-inflation model for manifesto data // - Country-item intercepts (coding convention differences across countries) // - Binomial-logit likelihood for text data (unchanged) // // ============================================================================= data { // Segment structure int S; // Number of segments int P; // Number of countries int R; // Total unique segment-year combinations int T_year; // Total number of years array[S] int len_theta_ts; // Number of years per segment // Country membership for each segment array[S] int segment_country; // Segment family data int F; // Number of party families array[S] int segment_family; // Family index for each segment // ========================================================================= // Text data (manifesto + PolDem) // ========================================================================= int N_man; // Number of text observations int K_man; // Number of unique text items array[N_man] int kk_man; // Text item index array[N_man] int ss_man; // Segment index (first constituent for unions) array[N_man] int pp_man; // Country index array[N_man] int positive; // Positive mentions array[N_man] int sample; // Total sample size array[N_man] int year_for_man; // Year index // Dimension and direction for text data array[N_man] int dim_idx_man; // 1=economic, 2=galtan array[N_man] int direction_man; // +1=right/TAN, -1=left/GAL // V4: Constituent structure for manifesto observations int N_const_man_total; // Total entries in const_rr_man array[N_man] int n_const_man; // Number of constituents per obs array[N_man] int const_offset_man; // Offset into const_rr_man array[N_const_man_total] int const_rr_man; // Constituent rr indices // Country-item-year data (used by zero-inflation model) int N_ciy; // Number of unique country-item-year combinations array[N_man] int ciy_idx; // ========================================================================= // Expert dimension-specific data (V5: integer observations + scale size) // ========================================================================= int N_exp_dim; // Number of dimension-specific expert observations int K_exp_dim; // Number of unique expert items array[N_exp_dim] int kk_exp_dim; array[N_exp_dim] int ss_exp_dim; array[N_exp_dim] int pp_exp_dim; array[N_exp_dim] int val_dim_int; // V5: rounded sum = round(mean * K * n_scale) array[N_exp_dim] int n_total_exp_dim; // V5: K * n_scale (total trials) array[N_exp_dim] int n_experts_exp_dim; // V5: K (number of experts) // Dimension index for expert data array[N_exp_dim] int dim_idx_exp; // 1=economic, 2=galtan // V4: Constituent structure for expert dim observations int N_const_exp_dim_total; array[N_exp_dim] int n_const_exp_dim; array[N_exp_dim] int const_offset_exp_dim; array[N_const_exp_dim_total] int const_rr_exp_dim; // ========================================================================= // Expert general L-R data (V5: integer observations + scale size) // ========================================================================= int N_exp_lr; int K_exp_lr; array[N_exp_lr] int kk_exp_lr; array[N_exp_lr] int ss_exp_lr; array[N_exp_lr] int pp_exp_lr; array[N_exp_lr] int val_lr_int; // V5: rounded sum = round(mean * K * n_scale) array[N_exp_lr] int n_total_exp_lr; // V5: K * n_scale (total trials) array[N_exp_lr] int n_experts_exp_lr; // V5: K (number of experts) // V6: Decade indexing for hierarchical L-R weights int D_lr; // Number of decades array[N_exp_lr] int dd_exp_lr; // Decade index per LR obs // V4: Constituent structure for expert L-R observations int N_const_exp_lr_total; array[N_exp_lr] int n_const_exp_lr; array[N_exp_lr] int const_offset_exp_lr; array[N_const_exp_lr_total] int const_rr_exp_lr; // Prior information real mn_resp_log_man; real mn_resp_log_exp_dim; real mn_resp_log_exp_lr; // Identification anchor int anchor_segment; // CDU segment (1375 with unions, 211 without) // V3 compatibility: these are still passed but not used in V4+ likelihood // (kept so older data dicts work without modification for backwards compat) array[N_man] int rr_man; // Segment-year index (unused in V4+ likelihood) array[N_exp_dim] int rr_exp_dim; array[N_exp_lr] int rr_exp_lr; } transformed data { real eps = 1e-6; real one_minus_eps = 1 - eps; // Count zero and non-zero samples int N_man_zero = 0; int N_man_nonzero = 0; for (n in 1:N_man) { if (sample[n] == 0) { N_man_zero += 1; } else { N_man_nonzero += 1; } } // Create index arrays for zero/nonzero split array[N_man_zero > 0 ? N_man_zero : 1] int idx_zero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int idx_nonzero; { int pos_zero = 1; int pos_nonzero = 1; for (n in 1:N_man) { if (sample[n] == 0) { idx_zero[pos_zero] = n; pos_zero += 1; } else { idx_nonzero[pos_nonzero] = n; pos_nonzero += 1; } } } // V4: Pre-compute constituent info for nonzero manifesto obs array[N_man_nonzero > 0 ? N_man_nonzero : 1] int kk_man_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int orig_idx_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int direction_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int pp_man_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int dim_idx_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int n_const_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int const_offset_nonzero; { for (i in 1:N_man_nonzero) { int n = idx_nonzero[i]; orig_idx_nonzero[i] = n; kk_man_nonzero[i] = kk_man[n]; direction_nonzero[i] = direction_man[n]; pp_man_nonzero[i] = pp_man[n]; dim_idx_nonzero[i] = dim_idx_man[n]; n_const_nonzero[i] = n_const_man[n]; const_offset_nonzero[i] = const_offset_man[n]; } } // Pre-compute segment start positions array[S + 1] int segment_start; segment_start[1] = 1; for (s in 1:S) { segment_start[s + 1] = segment_start[s] + len_theta_ts[s]; } // Extract sample and positive for nonzero observations array[N_man_nonzero > 0 ? N_man_nonzero : 1] int sample_nonzero; array[N_man_nonzero > 0 ? N_man_nonzero : 1] int positive_nonzero; for (i in 1:N_man_nonzero) { sample_nonzero[i] = sample[idx_nonzero[i]]; positive_nonzero[i] = positive[idx_nonzero[i]]; } // Pre-compute family-to-country mapping array[F] int family_country; { for (f in 1:F) { family_country[f] = 0; } for (s in 1:S) { int f = segment_family[s]; if (family_country[f] == 0) { family_country[f] = segment_country[s]; } } for (f in 1:F) { if (family_country[f] == 0) { family_country[f] = 1; } } } } parameters { // ========================================================================= // Latent position parameters - 2 dimensions // ========================================================================= matrix[2, R] theta_ncp; // Non-centered: [1]=economic_lr, [2]=galtan matrix[2, S] theta_init_raw; // Initial traits per segment vector[2] sigma_theta_init; // SD of initial theta per dimension // ========================================================================= // Three-level hierarchical variance // ========================================================================= vector[2] mu_sigma_global_raw; // Global mean RW variance vector[2] tau_sigma_country; // Country deviation scale matrix[2, P] sigma_country_raw; // Non-centered country deviations vector[2] tau_sigma_family; // Family deviation scale matrix[2, F] sigma_family_raw; // Non-centered family deviations // ========================================================================= // Country-item intercepts (coding convention differences) // ========================================================================= matrix[P, K_man] country_item_raw; real sigma_country_item; // ========================================================================= // Zero-sample parameters // ========================================================================= real alpha_zs; vector[T_year] year_effect_raw; real sigma_year_effect; vector[S] segment_zs_raw; real sigma_segment_zs; vector[N_ciy] ciy_zs_raw; real sigma_ciy_zs; // ========================================================================= // Item parameters // ========================================================================= // Text data: intercept + single positive loading (direction handled in data) vector[K_man] gamma_man_intercept_raw; vector[K_man] gamma_man_loading; // Positive loading // Expert dimension-specific: intercept + slope (direct mapping to dimension) vector[K_exp_dim] gamma_exp_intercept_raw; vector[K_exp_dim] gamma_exp_slope; // Expert general L-R: intercept + slope vector[K_exp_lr] gamma_lr_intercept_raw; vector[K_exp_lr] gamma_lr_slope; // V6: Hierarchical L-R weights (replace simplex[2] lr_weights) real lr_weight_global; // Global logit-scale weight vector[P] lr_country_offset_raw; // Country offsets (non-centered) vector[K_exp_lr] lr_source_offset_raw; // Source offsets (non-centered) vector[D_lr] lr_decade_offset_raw; // Decade offsets (non-centered) real sigma_lr_country; // SD of country offsets real sigma_lr_source; // SD of source offsets real sigma_lr_decade; // SD of decade offsets // Precision parameters real phi_exp_dim; // Precision for dimension-specific (now: only measurement noise) real phi_exp_lr; // Precision for general L-R (now: only measurement noise) // Scale parameters real sigma_intercept_man; real sigma_loading_man; real sigma_intercept_exp; real sigma_slope_exp; real sigma_intercept_lr; real sigma_slope_lr; real mu_lambda_man; real mu_lambda_exp_dim; real mu_lambda_exp_lr; } transformed parameters { matrix[2, R] theta; // [1]=economic_lr, [2]=galtan matrix[2, S] theta_init; // Three-level hierarchical variance (2D) matrix[2, P] sigma_theta_country; for (d in 1:2) { for (p in 1:P) { sigma_theta_country[d, p] = log1p_exp( mu_sigma_global_raw[d] + tau_sigma_country[d] * sigma_country_raw[d, p] ); } } matrix[2, F] sigma_theta_family; for (d in 1:2) { for (f in 1:F) { int c = family_country[f]; sigma_theta_family[d, f] = log1p_exp( log(sigma_theta_country[d, c]) + tau_sigma_family[d] * sigma_family_raw[d, f] ); } } // Non-centered parameterization for theta_init for (dim in 1:2) { theta_init[dim, :] = sigma_theta_init[dim] * theta_init_raw[dim, :]; } // Country-item intercepts matrix[P, K_man] country_item_intercept = sigma_country_item * country_item_raw; // Zero-sample components vector[T_year] year_effect = sigma_year_effect * year_effect_raw; vector[S] segment_zs = sigma_segment_zs * segment_zs_raw; vector[N_ciy] ciy_zs = sigma_ciy_zs * ciy_zs_raw; // Zero-sample probability vector[N_man] zero_sample_logit = alpha_zs + year_effect[year_for_man] + segment_zs[ss_man] + ciy_zs[ciy_idx]; vector[N_man] zero_sample_prob = inv_logit(zero_sample_logit); zero_sample_prob = fmax(fmin(zero_sample_prob, one_minus_eps), eps); // Construct theta using family-specific random walk variance (2D) for (dim in 1:2) { for (s in 1:S) { int start = segment_start[s]; int Ts = len_theta_ts[s]; int fam = segment_family[s]; real sigma_s = sigma_theta_family[dim, fam]; theta[dim, start] = theta_init[dim, s] + sigma_s * theta_ncp[dim, start]; if (Ts > 1) { theta[dim, start + 1 : start + Ts - 1] = theta[dim, start] + cumulative_sum(sigma_s * theta_ncp[dim, start + 1 : start + Ts - 1]); } } } // Item parameters vector[K_man] gamma_man_intercept = mu_lambda_man + sigma_intercept_man * gamma_man_intercept_raw; vector[K_exp_dim] gamma_exp_intercept = mu_lambda_exp_dim + sigma_intercept_exp * gamma_exp_intercept_raw; vector[K_exp_lr] gamma_lr_intercept = mu_lambda_exp_lr + sigma_intercept_lr * gamma_lr_intercept_raw; } model { // ========================================================================= // PRIORS // ========================================================================= // Three-level hierarchical variance priors (2D) mu_sigma_global_raw ~ normal(-0.8, 0.5); tau_sigma_country ~ normal(0, 0.3); to_vector(sigma_country_raw) ~ std_normal(); tau_sigma_family ~ normal(0, 0.2); to_vector(sigma_family_raw) ~ std_normal(); // Other variance priors sigma_theta_init ~ normal(0, 0.5); to_vector(theta_init_raw) ~ std_normal(); to_vector(theta_ncp) ~ std_normal(); // Country-item intercept priors to_vector(country_item_raw) ~ std_normal(); sigma_country_item ~ normal(0, 0.3); // Zero-sample priors alpha_zs ~ normal(-1, 1); year_effect_raw ~ std_normal(); sigma_year_effect ~ normal(0, 0.5); segment_zs_raw ~ std_normal(); sigma_segment_zs ~ normal(0, 0.3); ciy_zs_raw ~ std_normal(); sigma_ciy_zs ~ normal(0, 0.3); // ========================================================================= // Item parameter priors // ========================================================================= // Text data item priors mu_lambda_man ~ normal(mn_resp_log_man, 0.5); sigma_intercept_man ~ normal(0, 1); sigma_loading_man ~ normal(0, 0.5); gamma_man_intercept_raw ~ std_normal(); gamma_man_loading ~ normal(1.0, sigma_loading_man); // Expert dimension item priors mu_lambda_exp_dim ~ normal(mn_resp_log_exp_dim, 0.5); sigma_intercept_exp ~ normal(0, 1); sigma_slope_exp ~ normal(0, 0.5); gamma_exp_intercept_raw ~ std_normal(); gamma_exp_slope ~ normal(1.0, sigma_slope_exp); // Expert L-R item priors mu_lambda_exp_lr ~ normal(mn_resp_log_exp_lr, 0.5); sigma_intercept_lr ~ normal(0, 1); sigma_slope_lr ~ normal(0, 0.5); gamma_lr_intercept_raw ~ std_normal(); gamma_lr_slope ~ normal(1.0, sigma_slope_lr); // V6: Hierarchical L-R weight priors lr_weight_global ~ normal(0, 1); lr_country_offset_raw ~ std_normal(); lr_source_offset_raw ~ std_normal(); lr_decade_offset_raw ~ std_normal(); sigma_lr_country ~ normal(0, 0.5); sigma_lr_source ~ normal(0, 0.5); sigma_lr_decade ~ normal(0, 0.5); // Expert data precision priors phi_exp_dim ~ gamma(50, 0.5); phi_exp_lr ~ gamma(10, 0.5); // ========================================================================= // CDU ANCHOR CONSTRAINT // With unions: anchors CDU (1375) at moderate center-right position // Without unions: anchors CDU/CSU (211) as before // ========================================================================= target += normal_lpdf(theta_init[1, anchor_segment] | 0.2, 0.2); // economic_lr target += normal_lpdf(theta_init[2, anchor_segment] | 0.2, 0.2); // galtan // ========================================================================= // LIKELIHOOD 1: Text data (binomial with zero-inflation) // V4: Mean-constituent averaging for union observations // ========================================================================= // Zero-sample observations if (N_man_zero > 0) { target += sum(log(zero_sample_prob[idx_zero])); } // Non-zero observations with constituent averaging if (N_man_nonzero > 0) { vector[N_man_nonzero] lin_man; for (i in 1:N_man_nonzero) { int nc = n_const_nonzero[i]; int off = const_offset_nonzero[i]; int dim = dim_idx_nonzero[i]; real avg_pos; if (nc == 1) { // Fast path: single party (>95% of observations) avg_pos = theta[dim, const_rr_man[off]]; } else { // Union: average over constituent thetas avg_pos = 0; for (c in 0:(nc-1)) { avg_pos += theta[dim, const_rr_man[off + c]]; } avg_pos /= nc; } lin_man[i] = gamma_man_intercept[kk_man_nonzero[i]] + direction_nonzero[i] * gamma_man_loading[kk_man_nonzero[i]] * avg_pos + country_item_intercept[pp_man_nonzero[i], kk_man_nonzero[i]]; } for (i in 1:N_man_nonzero) { target += log1m(zero_sample_prob[orig_idx_nonzero[i]]) + binomial_logit_lpmf(positive_nonzero[i] | sample_nonzero[i], lin_man[i]); } } // ========================================================================= // LIKELIHOOD 2: Expert dimension-specific data (V5: beta-binomial likelihood) // V4: Constituent averaging for union-level expert obs // ========================================================================= { vector[N_exp_dim] pos; for (n in 1:N_exp_dim) { int nc = n_const_exp_dim[n]; int off = const_offset_exp_dim[n]; int dim = dim_idx_exp[n]; if (nc == 1) { pos[n] = theta[dim, const_rr_exp_dim[off]]; } else { pos[n] = 0; for (c in 0:(nc-1)) { pos[n] += theta[dim, const_rr_exp_dim[off + c]]; } pos[n] /= nc; } } vector[N_exp_dim] lin_exp_dim; for (n in 1:N_exp_dim) { lin_exp_dim[n] = gamma_exp_intercept[kk_exp_dim[n]] + gamma_exp_slope[kk_exp_dim[n]] * pos[n]; } vector[N_exp_dim] mu_exp_dim = inv_logit(lin_exp_dim); mu_exp_dim = fmax(fmin(mu_exp_dim, one_minus_eps), eps); // K-scaling: phi * K corrects for independent expert perceptions vector[N_exp_dim] alpha_exp_dim = phi_exp_dim * to_vector(n_experts_exp_dim) .* mu_exp_dim; vector[N_exp_dim] beta_exp_dim = phi_exp_dim * to_vector(n_experts_exp_dim) .* (1 - mu_exp_dim); val_dim_int ~ beta_binomial(n_total_exp_dim, alpha_exp_dim, beta_exp_dim); } // ========================================================================= // LIKELIHOOD 3: Expert general L-R data (V6: hierarchical per-obs weights) // V4: Constituent averaging + weighted combination of both dimensions // V6: Per-observation weights via country + source + decade offsets // ========================================================================= { // V6: Compute per-observation economic weight on logit scale vector[N_exp_lr] logit_w; for (n in 1:N_exp_lr) { logit_w[n] = lr_weight_global + sigma_lr_country * lr_country_offset_raw[pp_exp_lr[n]] + sigma_lr_source * lr_source_offset_raw[kk_exp_lr[n]] + sigma_lr_decade * lr_decade_offset_raw[dd_exp_lr[n]]; } vector[N_exp_lr] w_econ = inv_logit(logit_w); vector[N_exp_lr] combined_pos; for (n in 1:N_exp_lr) { int nc = n_const_exp_lr[n]; int off = const_offset_exp_lr[n]; if (nc == 1) { int r = const_rr_exp_lr[off]; combined_pos[n] = w_econ[n] * theta[1, r] + (1 - w_econ[n]) * theta[2, r]; } else { // Average the combined position across constituents combined_pos[n] = 0; for (c in 0:(nc-1)) { int r = const_rr_exp_lr[off + c]; combined_pos[n] += w_econ[n] * theta[1, r] + (1 - w_econ[n]) * theta[2, r]; } combined_pos[n] /= nc; } } vector[N_exp_lr] lin_exp_lr; for (n in 1:N_exp_lr) { lin_exp_lr[n] = gamma_lr_intercept[kk_exp_lr[n]] + gamma_lr_slope[kk_exp_lr[n]] * combined_pos[n]; } vector[N_exp_lr] mu_exp_lr = inv_logit(lin_exp_lr); mu_exp_lr = fmax(fmin(mu_exp_lr, one_minus_eps), eps); // K-scaling: phi * K corrects for independent expert perceptions vector[N_exp_lr] alpha_exp_lr = phi_exp_lr * to_vector(n_experts_exp_lr) .* mu_exp_lr; vector[N_exp_lr] beta_exp_lr = phi_exp_lr * to_vector(n_experts_exp_lr) .* (1 - mu_exp_lr); val_lr_int ~ beta_binomial(n_total_exp_lr, alpha_exp_lr, beta_exp_lr); } } generated quantities { // ========================================================================= // Direct outputs (no derivation needed) // ========================================================================= // Two bipolar scales on [0,1] via inv_logit vector[R] economic_lr = inv_logit(to_vector(theta[1, :])); // 0=left, 1=right vector[R] galtan = inv_logit(to_vector(theta[2, :])); // 0=GAL, 1=TAN // General left-right combining both dimensions (using global weight only) // Per-R country/source/decade info not available in GQ, so use global mean real lr_w_econ_global = inv_logit(lr_weight_global); vector[R] general_lr = inv_logit( lr_w_econ_global * to_vector(theta[1, :]) + (1 - lr_w_econ_global) * to_vector(theta[2, :]) ); // V6: Expose hierarchical L-R weight diagnostics real lr_weight_econ_global = lr_w_econ_global; real lr_sigma_country = sigma_lr_country; real lr_sigma_source = sigma_lr_source; real lr_sigma_decade = sigma_lr_decade; // Expose hierarchical variance diagnostics (2D) vector[2] mean_sigma_global; vector[2] mean_sigma_country; vector[2] mean_sigma_family; for (d in 1:2) { mean_sigma_global[d] = log1p_exp(mu_sigma_global_raw[d]); mean_sigma_country[d] = mean(sigma_theta_country[d, :]); mean_sigma_family[d] = mean(sigma_theta_family[d, :]); } }