Análisis de votaciones usando el paquete bcall.
B-Call es un método para analizar comportamiento legislativo que integra dos dimensiones:
¿Por qué B-Call? Modelos tradicionales como W-NOMINATE funcionan bien en sistemas estables con partidos disciplinados. Pero en contextos fragmentados (como Chile o Brasil), legisladores con baja cohesión aparecen artificialmente como "moderados" cuando en realidad son volátiles. B-Call corrige esto al medir explícitamente esa volatilidad como una segunda dimensión.
# Instalar bcall
install.packages("devtools")
devtools::install_github("bcallr/bcall")
library(bcall)
Clona el repositorio para obtener todos los archivos:
git clone https://github.com/Alcatruz/bcall-example.git
cd bcall-example
O descarga manualmente estos archivos en una carpeta data/:
Luego carga los datos en R:
rollcall <- read.csv("data/CHL-rollcall-2025.csv", row.names = 1)
clustering_original <- read.csv("data/CHL-clustering-2025.csv", row.names = 1)
clustering_corregido <- read.csv("data/CHL-clustering-2025-CORREGIDO.csv", row.names = 1)
El análisis automático detecta grupos ideológicos sin necesidad de clasificación previa:
El pivot es un legislador de referencia que define la orientación del eje d1 (izquierda-derecha). Si conoces el contexto político, puedes especificar un legislador de derecha como pivot. Si no lo especificas, B-Call lo elige automáticamente. El pivot solo afecta la dirección (valores positivos vs negativos en d1), no altera el análisis.
rollcall <- read.csv("data/CHL-rollcall-2025.csv", row.names = 1)
resultado_auto <- bcall_auto(
rollcall = rollcall,
distance_method = 1,
pivot = "Alessandri_Vergara_Jorge", # Opcional: si conoces el contexto, especifica un legislador de derecha
# Si no lo incluyes, se elige automáticamente (solo cambia la dirección del eje)
threshold = 0.1,
verbose = TRUE
)
# Visualizar
plot_bcall_analysis(
resultado_auto,
title = "Cámara de Diputados Chile 2025 - Clustering Automático",
show_names = FALSE
)
Cuando usamos clustering manual, el análisis depende de la clasificación que proporcionamos.
clustering_original <- read.csv("data/CHL-clustering-2025.csv", row.names = 1)
resultado_original <- bcall(
rollcall = rollcall,
clustering = clustering_original,
pivot = "Alessandri_Vergara_Jorge",
threshold = 0.1,
verbose = TRUE
)
plot_bcall_analysis(resultado_original, show_names = FALSE)
clustering_corregido <- read.csv("data/CHL-clustering-2025-CORREGIDO.csv", row.names = 1)
resultado_corregido <- bcall(
rollcall = rollcall,
clustering = clustering_corregido,
pivot = "Alessandri_Vergara_Jorge",
threshold = 0.1,
verbose = TRUE
)
plot_bcall_analysis(resultado_corregido, show_names = FALSE)
Izquierda: clustering original con errores (coherencia ~85%). Derecha: clustering corregido (coherencia ~98%).
El análisis B-Call manual depende de la clasificación que proporcionas.
Recomendación: Usa bcall_auto() cuando no tengas clasificación previa. Usa bcall() cuando tengas clasificación verificada.
Primera dimensión: ambos modelos correlacionan (r=0.995). Segunda dimensión: B-Call captura cohesión, W-NOMINATE no.
# Cargar datos W-NOMINATE
library(readxl)
library(dplyr)
library(ggplot2)
wnominate <- read_excel("data/df_wnominate.xlsx")
# Ejecutar B-Call
resultado_bcall <- bcall(
rollcall = rollcall,
clustering = clustering_corregido,
pivot = "Alessandri_Vergara_Jorge",
threshold = 0.1
)
# Matchear legisladores
comparacion <- resultado_bcall$results %>%
left_join(wnominate %>% select(legislator, coord1D, coord2D), by = "legislator")
# Correlación primera dimensión
cor(comparacion$d1, comparacion$coord1D) # r = 0.995
# Gráfico correlación d1
ggplot(comparacion, aes(x = coord1D, y = d1, color = cluster)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "black", linetype = "dashed") +
scale_color_manual(values = c("izquierda" = "#E74C3C", "derecha" = "#3498DB")) +
labs(title = "Primera Dimensión: B-Call vs W-NOMINATE",
x = "W-NOMINATE coord1D", y = "B-Call d1") +
theme_minimal()
# Detectar zonas de volatilidad
# ZONA ROJA: d2 alto + coord2D centrado
outliers_roja <- comparacion %>%
filter(d2 > 0.9, abs(coord2D) < 0.2) %>%
mutate(zona = "Roja")
# ZONA AMARILLA: d1 centrado + d2 alto
outliers_amarilla <- comparacion %>%
filter(abs(d1) < 0.3, d2 > 0.9) %>%
mutate(zona = "Amarilla")
outliers <- bind_rows(outliers_roja, outliers_amarilla)
# Gráfico zonas
ggplot(comparacion, aes(x = d1, y = d2, color = cluster)) +
geom_point(size = 3, alpha = 0.6) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 0.9, ymax = Inf),
fill = "red", alpha = 0.05, inherit.aes = FALSE) +
geom_rect(aes(xmin = -0.3, xmax = 0.3, ymin = 0.9, ymax = Inf),
fill = "yellow", alpha = 0.1, inherit.aes = FALSE) +
geom_text(data = outliers, aes(label = legislator), size = 2.5, vjust = -0.5) +
scale_color_manual(values = c("izquierda" = "#E74C3C", "derecha" = "#3498DB")) +
labs(title = "B-Call detecta volatilidad legislativa que W-NOMINATE no ve",
x = "d1 (ideología)", y = "d2 (cohesión: alto = volátil)") +
theme_minimal()
Correlación r=0.995. Ambos modelos capturan ideología.
B-Call detecta dos zonas de volatilidad que W-NOMINATE no captura.
d2 > 0.9 y |coord2D| < 0.2. Legisladores volátiles que W-NOMINATE ve centrados.
|d1| < 0.3 y d2 > 0.9. Legisladores centrados ideológicamente pero sin cohesión en votación.