1. Introducción

Row

Diego Sebastián Luyo León · Estefanía Diana Carolina Apaza Díaz — Estadística para el Análisis Político II, PUCP

Row

Eventos registrados

25,026

% que escaló a violencia

15.51%

Concentrados en Lima

47.58%

Periodo de mayor riesgo

17.1%

Row

Eventos y tasa de violencia por periodo político

25,026 eventos en 45 años: el riesgo de escalamiento no es constante, cambia con cada estructura de oportunidad política.

Row

Eventos de protesta y tasa de violencia por año

Usa el control deslizante de abajo para hacer zoom en cualquier tramo, y pasa el cursor sobre cualquier punto para ver el número exacto de eventos y la tasa de ese año.

2. Geografía

Row

Mapa: tasa de violencia por región

Pasa el cursor sobre cada región para ver el detalle. Lima concentra el mayor número de eventos, pero regiones como Loreto tienen tasas de violencia mucho más altas.

Row

Regiones con más eventos (top 10)

Lima concentra casi la mitad de los eventos nacionales, pero no es la región más violenta.

3. Actores

Row

Tasa de violencia según tipo de actor

La identidad del actor movilizado también predice el escalamiento — casi tanto como el periodo político.

Distribución por N° de eventos

El actor laboral tiene la mayor cantidad de eventos registrados, pero es el menos propenso a la violencia.

4. Interacción

Row

¿Los mismos actores se comportan igual en todos los periodos?

Cruce de tasa de violencia por tipo de actor y periodo político — cada celda es un subgrupo real de la base, no un promedio agregado.

Row

Lectura del cruce

Los estudiantes son volátiles en todo periodo, pero su pico ocurre en el actual autoritarismo parlamentario (2017–), coherente con la represión de las protestas de 2022–2023. Los actores territoriales/sociales mantienen tasas altas de forma más consistente entre periodos, lo que sugiere que su conflictividad responde más a la disputa por recursos extractivos que al ciclo político. Los actores laboral y económico son los más estables y menos propensos a la violencia en cualquier contexto — su capacidad organizativa formal parece operar como un amortiguador estructural.

5. Modelo

Row

Odds Ratios (IC 95%) — regresión logística, up-sampling

Un OR mayor a 1 (rojo) aumenta el riesgo de violencia frente a la categoría de referencia; menor a 1 (verde) lo reduce.

Efecto Marginal Promedio (puntos porcentuales)

Traduce el OR a un número directo: cuántos puntos porcentuales cambia la probabilidad de violencia.

Row

Matriz de confusión (umbral = 0.5)

Correlación entre variables del modelo

Diagnóstico de multicolinealidad: valores cercanos a ±1 (rojo/verde intenso) indicarían variables redundantes. Aquí no hay ninguna preocupante.

Row

Tabla completa del modelo (up-sampling)

Output crudo de R — summary(modelo_up)

El mismo resultado que verías corriendo el modelo en tu consola de R.


Call:
glm(formula = protesta_violenta ~ periodo_pre90 + periodo_90_00 + 
    periodo_01_16 + actor_laboral + actor_territorial_social + 
    actor_economico + actor_estudiantil + protesta_masiva + numero_eventos_previos, 
    family = binomial(), data = balanced_data)

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)              -0.2985352  0.0325298  -9.177  < 2e-16 ***
periodo_pre90             0.4654831  0.0333430  13.960  < 2e-16 ***
periodo_90_00            -0.1467255  0.0389344  -3.769 0.000164 ***
periodo_01_16             0.1127497  0.0290962   3.875 0.000107 ***
actor_laboral            -0.3870074  0.0296484 -13.053  < 2e-16 ***
actor_territorial_social  0.6035823  0.0297473  20.290  < 2e-16 ***
actor_economico          -0.1401897  0.0327409  -4.282 1.85e-05 ***
actor_estudiantil         1.2155287  0.0422537  28.767  < 2e-16 ***
protesta_masiva          -0.3603946  0.0571893  -6.302 2.94e-10 ***
numero_eventos_previos   -0.0015155  0.0005834  -2.598 0.009381 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 58624  on 42287  degrees of freedom
Residual deviance: 56008  on 42278  degrees of freedom
AIC: 56028

Number of Fisher Scoring iterations: 4

6. Remuestreo

Row

Accuracy — Down-sampling

59.52%

Accuracy — Up-sampling (elegido)

60.23%

N final — Down-sampling

7,764

N final — Up-sampling

42,288

Row

Comparación de métricas de rendimiento

Por qué up-sampling: el down-sampling iguala clases eliminando ~69% de las protestas pacíficas originales, perdiendo heterogeneidad. El up-sampling conserva el 100% de la información original y muestra métricas de sensibilidad/especificidad igualmente estables.

7. Conclusiones

Row

H1 — Hipótesis de trabajo

“La probabilidad de que una protesta social escale hacia la violencia en el Perú responde a las condiciones estructurales y los incentivos institucionales determinados por cada periodo político y sus respectivos ejecutivos.”

Validada. Los nueve predictores del modelo son estadísticamente significativos (p < 0.001, salvo eventos previos p < 0.01).

Hallazgos principales

  • El periodo previo a 1990 es el de mayor riesgo estructural frente al periodo actual (2016 en adelante).
  • El fujimorismo (1990–2000) es el único periodo que reduce el riesgo base, consistente con un aparato autocrático que desmoviliza mediante control y cooptación.
  • Los actores territoriales/sociales tienen el mayor impacto agregado por su alta representatividad; los estudiantes muestran la mayor volatilidad relativa.
  • Los actores laborales y económicos, con mayor capacidad organizativa formal, actúan como factores de mitigación.
  • Limitación: la cobertura de prensa nacional subrepresenta eventos en la periferia rural, y el up-sampling duplica la varianza del grupo minoritario.
---
title: "Estructuras de oportunidad y acción colectiva"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
    theme:
      version: 4
      bootswatch: journal
    source_code: embed
---

```{r setup, include=FALSE}
# ============================================================
# Si es la primera vez que usas estos paquetes en Posit Cloud,
# corre esto UNA VEZ en la Consola (no en un chunk):
#
# install.packages(c("flexdashboard", "tidyverse", "plotly", "DT", "broom",
#                     "scales", "sf", "leaflet", "stringi"))
# ============================================================

library(flexdashboard)
library(tidyverse)
library(plotly)
library(DT)
library(broom)
library(scales)
library(sf)
library(leaflet)

knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)

# Paleta del proyecto (misma lógica que el panel HTML: verde-azulado = baja
# violencia, óxido = alta violencia)
color_ink    <- "#1c2b3a"
color_oxide  <- "#a23e28"
color_slate  <- "#2f6a63"
color_paper  <- "#e7e4da"
```

```{r carga-datos}
# Misma fuente que el .Rnw original (Base de Eventos de Protesta del Perú)
link_egpp <- "https://docs.google.com/spreadsheets/d/e/2PACX-1vR9hUSJf257WLVR3UUb50fPIXjLx_HlsNcp6rth5i5_fST9KYV1uPkfoDbU0MnfTrIIAs_SWYF_5TCB/pub?output=csv"

data <- read.csv(link_egpp)

# Dummies para tipo de actor
data$actor_laboral             <- ifelse(data$categoria_actor == "actor_laboral", 1, 0)
data$actor_territorial_social  <- ifelse(data$categoria_actor == "actor_territorial_social", 1, 0)
data$actor_economico           <- ifelse(data$categoria_actor == "actor_economico", 1, 0)
data$actor_estudiantil         <- ifelse(data$categoria_actor == "actor_estudiantil", 1, 0)
data$actor_politico_ciudadano  <- ifelse(data$categoria_actor == "actor_politico_ciudadano", 1, 0)

# Dummies para periodos políticos
data$periodo_pre90   <- ifelse(data$ano < 1990, 1, 0)
data$periodo_90_00   <- ifelse(data$ano >= 1990 & data$ano <= 2000, 1, 0)
data$periodo_01_16   <- ifelse(data$ano >= 2001 & data$ano <= 2016, 1, 0)
data$periodo_actual  <- ifelse(data$ano > 2016, 1, 0)

# Etiqueta legible de periodo, para los gráficos descriptivos
data <- data %>%
  mutate(periodo_label = case_when(
    ano <= 1989              ~ "Previo a 1990",
    ano >= 1990 & ano <= 2000 ~ "Fujimori (1990-2000)",
    ano >= 2001 & ano <= 2016 ~ "Regreso a la democracia (2001-2016)",
    ano >= 2017               ~ "Autoritarismo parlamentario (2017-)"
  )) %>%
  mutate(periodo_label = factor(periodo_label, levels = c(
    "Previo a 1990", "Fujimori (1990-2000)",
    "Regreso a la democracia (2001-2016)", "Autoritarismo parlamentario (2017-)"
  )))

# Dataframe para el modelo
Y <- data$violencia_y
X_model <- data %>%
  select(periodo_pre90, periodo_90_00, periodo_01_16,
         actor_laboral, actor_territorial_social, actor_economico, actor_estudiantil,
         protesta_masiva, numero_eventos_previos)
data_modelo <- cbind(protesta_violenta = Y, X_model)
```

```{r datos-mapa}
# Polígonos de departamentos del Perú (fuente pública: juaneladio/peru-geojson)
peru_geo <- st_read(
  "https://raw.githubusercontent.com/juaneladio/peru-geojson/master/peru_departamental_simple.geojson",
  quiet = TRUE
)

normalize_name <- function(x) {
  x <- toupper(trimws(x))
  stringi::stri_trans_general(x, "Latin-ASCII")
}

peru_geo$region_norm <- normalize_name(peru_geo$NOMBDEP)

region_map_data <- data %>%
  group_by(region) %>%
  summarise(n = n(), tasa = mean(violencia_y) * 100) %>%
  mutate(region_norm = normalize_name(region))

peru_geo <- peru_geo %>%
  left_join(region_map_data, by = "region_norm")
```

```{r resampleo-y-modelo}
# ---- Up-sampling (método elegido en el trabajo final) ----
pacificas <- data_modelo[data_modelo$protesta_violenta == 0, ]
violentas <- data_modelo[data_modelo$protesta_violenta == 1, ]

set.seed(123)
replicated_violentas <- violentas[sample(1:nrow(violentas), nrow(pacificas), replace = TRUE), ]
balanced_data <- rbind(pacificas, replicated_violentas)

modelo_up <- glm(
  protesta_violenta ~ periodo_pre90 + periodo_90_00 + periodo_01_16 +
    actor_laboral + actor_territorial_social + actor_economico + actor_estudiantil +
    protesta_masiva + numero_eventos_previos,
  data = balanced_data, family = binomial()
)

pred_prob_up  <- predict(modelo_up, type = "response")
pred_class_up <- ifelse(pred_prob_up > 0.5, 1, 0)
actual_up     <- balanced_data$protesta_violenta
accuracy_up    <- mean(pred_class_up == actual_up) * 100
sensitivity_up <- sum(pred_class_up == 1 & actual_up == 1) / sum(actual_up == 1) * 100
specificity_up <- sum(pred_class_up == 0 & actual_up == 0) / sum(actual_up == 0) * 100

# ---- Down-sampling (comparación) ----
wins_down   <- data_modelo[data_modelo$protesta_violenta == 1, ]
losses_down <- data_modelo[data_modelo$protesta_violenta == 0, ]

set.seed(123)
replicated_losses <- losses_down[sample(1:nrow(losses_down), nrow(wins_down), replace = FALSE), ]
balanced_data_down <- rbind(replicated_losses, wins_down)

modelo_down <- glm(
  protesta_violenta ~ periodo_pre90 + periodo_90_00 + periodo_01_16 +
    actor_laboral + actor_territorial_social + actor_economico + actor_estudiantil +
    protesta_masiva + numero_eventos_previos,
  data = balanced_data_down, family = binomial()
)

pred_prob_down  <- predict(modelo_down, type = "response")
pred_class_down <- ifelse(pred_prob_down > 0.5, 1, 0)
actual_down     <- balanced_data_down$protesta_violenta
accuracy_down    <- mean(pred_class_down == actual_down) * 100
sensitivity_down <- sum(pred_class_down == 1 & actual_down == 1) / sum(actual_down == 1) * 100
specificity_down <- sum(pred_class_down == 0 & actual_down == 0) / sum(actual_down == 0) * 100

# ---- Odds Ratios + IC 95% (modelo up-sampled) ----
or_table <- tidy(modelo_up, conf.int = TRUE, exponentiate = TRUE) %>%
  filter(term != "(Intercept)")

# ---- Efectos Marginales Promedio (AME), calculados a mano ----
ame_dummy <- function(var){
  d1 <- balanced_data; d1[[var]] <- 1
  d0 <- balanced_data; d0[[var]] <- 0
  mean(predict(modelo_up, newdata = d1, type = "response") -
       predict(modelo_up, newdata = d0, type = "response")) * 100
}
ame_continua <- function(var){
  d1 <- balanced_data; d1[[var]] <- balanced_data[[var]] + 1
  mean(predict(modelo_up, newdata = d1, type = "response") -
       predict(modelo_up, newdata = balanced_data, type = "response")) * 100
}

dummy_vars <- c("periodo_pre90", "periodo_90_00", "periodo_01_16",
                 "actor_laboral", "actor_territorial_social", "actor_economico", "actor_estudiantil",
                 "protesta_masiva")

ame_values <- sapply(dummy_vars, ame_dummy)
ame_values["numero_eventos_previos"] <- ame_continua("numero_eventos_previos")
ame_df <- tibble(term = names(ame_values), ame = as.numeric(ame_values))

# Etiquetas legibles para el modelo
label_map <- c(
  periodo_pre90 = "Periodo: Pre-1990",
  periodo_90_00 = "Periodo: 1990-2000 (Fujimori)",
  periodo_01_16 = "Periodo: 2001-2016",
  actor_laboral = "Actor: Laboral",
  actor_territorial_social = "Actor: Territorial/social",
  actor_economico = "Actor: Económico",
  actor_estudiantil = "Actor: Estudiantil",
  protesta_masiva = "Control: Protesta masiva",
  numero_eventos_previos = "Control: Eventos previos (+1)"
)

modelo_final <- or_table %>%
  left_join(ame_df, by = "term") %>%
  mutate(label = label_map[term]) %>%
  arrange(estimate)
```

1. Introducción
=======================================================================

Row {data-height=40}
-----------------------------------------------------------------------

### 
```{r}
htmltools::tags$p("Diego Sebastián Luyo León · Estefanía Diana Carolina Apaza Díaz — Estadística para el Análisis Político II, PUCP",
                   style = "color:#6b675c; font-size:13px; margin:0;")
```

Row {data-height=130}
-----------------------------------------------------------------------

### Eventos registrados
```{r}
valueBox(comma(nrow(data)), icon = "fa-list", color = color_ink)
```

### % que escaló a violencia
```{r}
pct_viol <- round(mean(data$violencia_y) * 100, 2)
valueBox(paste0(pct_viol, "%"), icon = "fa-fire", color = color_oxide)
```

### Concentrados en Lima
```{r}
pct_lima <- round(mean(data$region == "Lima") * 100, 2)
valueBox(paste0(pct_lima, "%"), icon = "fa-map-marker-alt", color = color_ink)
```

### Periodo de mayor riesgo
```{r}
riskiest <- data %>% group_by(periodo_label) %>%
  summarise(rate = mean(violencia_y) * 100) %>%
  arrange(desc(rate)) %>% slice(1)
valueBox(paste0(round(riskiest$rate,1), "%"), icon = "fa-exclamation-triangle", color = color_oxide)
```

Row {data-height=450}
-----------------------------------------------------------------------

### Eventos y tasa de violencia por periodo político

*25,026 eventos en 45 años: el riesgo de escalamiento no es constante, cambia con cada estructura de oportunidad política.*

```{r}
periodo_summary <- data %>%
  group_by(periodo_label) %>%
  summarise(n = n(), tasa = mean(violencia_y) * 100)

p <- ggplot(periodo_summary, aes(x = periodo_label, y = n, fill = tasa,
            text = paste0(periodo_label, "<br>N = ", comma(n), "<br>Tasa violencia = ", round(tasa,1), "%"))) +
  geom_col() +
  geom_text(aes(label = comma(n)), vjust = -0.6, size = 3.6, color = color_ink) +
  scale_fill_gradient(low = color_slate, high = color_oxide, name = "% violencia") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.18))) +
  labs(x = NULL, y = "N° de eventos") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 15, hjust = 1))

ggplotly(p, tooltip = "text") %>% layout(margin = list(t = 30))
```

Row {data-height=480}
-----------------------------------------------------------------------

### Eventos de protesta y tasa de violencia por año

*Usa el control deslizante de abajo para hacer zoom en cualquier tramo, y pasa el cursor sobre cualquier punto para ver el número exacto de eventos y la tasa de ese año.*

```{r}
year_summary <- data %>%
  group_by(ano) %>%
  summarise(n = n(), viol = sum(violencia_y), tasa = mean(violencia_y) * 100)

plot_ly(year_summary, x = ~ano) %>%
  add_bars(y = ~n, name = "Eventos", marker = list(color = color_ink, opacity = 0.75),
           hovertemplate = "<b>Año %{x}</b><br>Eventos: %{y}<extra></extra>") %>%
  add_trace(y = ~tasa, name = "Tasa de violencia (%)", yaxis = "y2",
            type = "scatter", mode = "lines+markers",
            line = list(color = color_oxide, width = 2.5),
            marker = list(color = color_oxide, size = 5),
            hovertemplate = "<b>Año %{x}</b><br>Tasa: %{y:.1f}%<extra></extra>") %>%
  layout(
    hovermode = "x unified",
    yaxis = list(title = "N° de eventos"),
    yaxis2 = list(overlaying = "y", side = "right", title = "% violencia", rangemode = "tozero"),
    xaxis = list(title = NULL, rangeslider = list(visible = TRUE, thickness = 0.08)),
    legend = list(orientation = "h", y = 1.14),
    margin = list(t = 40),
    shapes = list(
      list(type = "line", x0 = 1990, x1 = 1990, y0 = 0, y1 = 1, yref = "paper",
           line = list(color = "#a49f8c", dash = "dot", width = 1)),
      list(type = "line", x0 = 2001, x1 = 2001, y0 = 0, y1 = 1, yref = "paper",
           line = list(color = "#a49f8c", dash = "dot", width = 1)),
      list(type = "line", x0 = 2017, x1 = 2017, y0 = 0, y1 = 1, yref = "paper",
           line = list(color = "#a49f8c", dash = "dot", width = 1))
    ),
    annotations = list(
      list(x = 1990, y = 1.06, yref = "paper", text = "Fujimori →", showarrow = FALSE, font = list(size = 9.5, color = "#6b675c")),
      list(x = 2001, y = 1.06, yref = "paper", text = "Democracia →", showarrow = FALSE, font = list(size = 9.5, color = "#6b675c")),
      list(x = 2017, y = 1.06, yref = "paper", text = "Actual →", showarrow = FALSE, font = list(size = 9.5, color = "#6b675c"))
    )
  )
```

2. Geografía
=======================================================================

Row {data-height=520}
-----------------------------------------------------------------------

### Mapa: tasa de violencia por región

*Pasa el cursor sobre cada región para ver el detalle. Lima concentra el mayor número de eventos, pero regiones como Loreto tienen tasas de violencia mucho más altas.*

```{r}
pal <- colorNumeric(
  palette = c(color_slate, "#f2f0e7", color_oxide),
  domain = peru_geo$tasa,
  na.color = "#eeeeee"
)

leaflet(peru_geo, options = leafletOptions(zoomControl = TRUE, minZoom = 4.5, maxZoom = 8)) %>%
  addProviderTiles("CartoDB.Positron") %>%
  setView(lng = -75.5, lat = -9.5, zoom = 5) %>%
  addPolygons(
    fillColor = ~pal(tasa),
    fillOpacity = 0.87,
    color = "white",
    weight = 1.2,
    label = ~paste0(coalesce(region, NOMBDEP), ": ", round(tasa,1), "% violencia (N=", comma(n), ")"),
    labelOptions = labelOptions(style = list("font-family" = "sans-serif", "font-size" = "12px")),
    highlightOptions = highlightOptions(weight = 3, color = color_ink, bringToFront = TRUE)
  ) %>%
  addLegend(pal = pal, values = ~tasa, title = "% violencia", position = "bottomright")
```

Row {data-height=420}
-----------------------------------------------------------------------

### Regiones con más eventos (top 10)

*Lima concentra casi la mitad de los eventos nacionales, pero no es la región más violenta.*

```{r}
region_summary <- data %>%
  group_by(region) %>%
  summarise(n = n(), tasa = mean(violencia_y) * 100) %>%
  arrange(desc(n)) %>%
  slice(1:10)

p3 <- ggplot(region_summary, aes(x = reorder(region, n), y = n,
             text = paste0(region, "<br>N = ", comma(n), "<br>Tasa violencia = ", round(tasa,1), "%"))) +
  geom_col(fill = color_ink, alpha = 0.85) +
  geom_text(aes(label = comma(n)), hjust = 1.08, color = "white", fontface = "bold", size = 3.3) +
  coord_flip() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.06))) +
  labs(x = NULL, y = "N° de eventos") +
  theme_minimal(base_size = 11)

ggplotly(p3, tooltip = "text") %>% layout(margin = list(r = 20, l = 10))
```

3. Actores
=======================================================================

Row
-----------------------------------------------------------------------

### Tasa de violencia según tipo de actor

*La identidad del actor movilizado también predice el escalamiento — casi tanto como el periodo político.*

```{r}
actor_labels <- c(
  actor_laboral = "Actor laboral",
  actor_territorial_social = "Actor territorial/social",
  actor_economico = "Actor económico",
  actor_estudiantil = "Actor estudiantil",
  actor_politico_ciudadano = "Actor político-ciudadano"
)

actor_summary <- data %>%
  filter(categoria_actor %in% names(actor_labels)) %>%
  group_by(categoria_actor) %>%
  summarise(n = n(), tasa = mean(violencia_y) * 100) %>%
  mutate(actor = actor_labels[categoria_actor]) %>%
  arrange(tasa)

p2 <- ggplot(actor_summary, aes(x = reorder(actor, tasa), y = tasa, fill = tasa,
             text = paste0(actor, "<br>N = ", comma(n), "<br>Tasa = ", round(tasa,1), "%"))) +
  geom_col() +
  geom_text(aes(label = paste0(round(tasa,1), "%")), hjust = -0.15, size = 3.3, color = color_ink) +
  coord_flip() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.28))) +
  scale_fill_gradient(low = color_slate, high = color_oxide, guide = "none") +
  labs(x = NULL, y = "% de eventos violentos") +
  theme_minimal(base_size = 11)

ggplotly(p2, tooltip = "text") %>% layout(margin = list(r = 55, l = 10))
```

### Distribución por N° de eventos

*El actor laboral tiene la mayor cantidad de eventos registrados, pero es el menos propenso a la violencia.*

```{r}
p2b <- ggplot(actor_summary, aes(x = reorder(actor, n), y = n, fill = tasa,
             text = paste0(actor, "<br>N = ", comma(n), "<br>Tasa = ", round(tasa,1), "%"))) +
  geom_col() +
  geom_text(aes(label = comma(n)), hjust = -0.15, size = 3.3, color = color_ink) +
  coord_flip() +
  scale_y_continuous(expand = expansion(mult = c(0, 0.28))) +
  scale_fill_gradient(low = color_slate, high = color_oxide, guide = "none") +
  labs(x = NULL, y = "N° de eventos") +
  theme_minimal(base_size = 11)

ggplotly(p2b, tooltip = "text") %>% layout(margin = list(r = 55, l = 10))
```

4. Interacción
=======================================================================

Row
-----------------------------------------------------------------------

### ¿Los mismos actores se comportan igual en todos los periodos?

*Cruce de tasa de violencia por tipo de actor y periodo político — cada celda es un subgrupo real de la base, no un promedio agregado.*

```{r}
heat_data <- data %>%
  filter(categoria_actor %in% names(actor_labels)) %>%
  group_by(periodo_label, categoria_actor) %>%
  summarise(n = n(), tasa = mean(violencia_y) * 100, .groups = "drop") %>%
  mutate(actor = actor_labels[categoria_actor])

p7 <- ggplot(heat_data, aes(x = periodo_label, y = actor, fill = tasa,
             text = paste0(actor, " · ", periodo_label, "<br>N = ", comma(n), "<br>Tasa = ", round(tasa,1), "%"))) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = paste0(round(tasa,0), "%")), color = "white", size = 3.8, fontface = "bold") +
  scale_fill_gradient(low = color_slate, high = color_oxide, guide = "none") +
  labs(x = NULL, y = NULL) +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 12, hjust = 1), panel.grid = element_blank())

ggplotly(p7, tooltip = "text") %>% layout(margin = list(b = 60))
```

Row {data-height=180}
-----------------------------------------------------------------------

### Lectura del cruce

Los **estudiantes** son volátiles en todo periodo, pero su pico ocurre en el **actual autoritarismo parlamentario** (2017–), coherente con la represión de las protestas de 2022–2023. Los **actores territoriales/sociales** mantienen tasas altas de forma más consistente entre periodos, lo que sugiere que su conflictividad responde más a la disputa por recursos extractivos que al ciclo político. Los actores **laboral** y **económico** son los más estables y menos propensos a la violencia en cualquier contexto — su capacidad organizativa formal parece operar como un amortiguador estructural.

5. Modelo
=======================================================================

Row {data-height=550}
-----------------------------------------------------------------------

### Odds Ratios (IC 95%) — regresión logística, up-sampling

*Un OR mayor a 1 (rojo) aumenta el riesgo de violencia frente a la categoría de referencia; menor a 1 (verde) lo reduce.*

```{r}
p4 <- ggplot(modelo_final, aes(x = reorder(label, estimate), y = estimate,
             ymin = conf.low, ymax = conf.high, color = estimate > 1,
             text = paste0(label, "<br>OR = ", round(estimate,2),
                            "<br>IC 95%: [", round(conf.low,2), ", ", round(conf.high,2), "]"))) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "grey60") +
  geom_pointrange() +
  coord_flip() +
  scale_y_log10() +
  scale_color_manual(values = c(`TRUE` = color_oxide, `FALSE` = color_slate), guide = "none") +
  labs(x = NULL, y = "Odds Ratio (escala log)") +
  theme_minimal(base_size = 12)

ggplotly(p4, tooltip = "text")
```

### Efecto Marginal Promedio (puntos porcentuales)

*Traduce el OR a un número directo: cuántos puntos porcentuales cambia la probabilidad de violencia.*

```{r}
p5 <- ggplot(modelo_final, aes(x = reorder(label, ame), y = ame, fill = ame > 0,
             text = paste0(label, "<br>AME = ", round(ame,2), " pp"))) +
  geom_col() +
  geom_hline(yintercept = 0, color = "grey40") +
  coord_flip() +
  scale_fill_manual(values = c(`TRUE` = color_oxide, `FALSE` = color_slate), guide = "none") +
  labs(x = NULL, y = "AME (puntos porcentuales)") +
  theme_minimal(base_size = 12)

ggplotly(p5, tooltip = "text")
```

Row {data-height=340}
-----------------------------------------------------------------------

### Matriz de confusión (umbral = 0.5)

```{r}
conf_df <- as.data.frame(table(Real = actual_up, Predicho = pred_class_up)) %>%
  mutate(Real = ifelse(Real == 1, "Violenta", "Pacífica"),
         Predicho = ifelse(Predicho == 1, "Violenta", "Pacífica"))

p6 <- ggplot(conf_df, aes(x = Real, y = Predicho, fill = Freq,
             text = paste0("Real: ", Real, "<br>Predicho: ", Predicho, "<br>N = ", comma(Freq)))) +
  geom_tile(color = "white", linewidth = 1) +
  geom_text(aes(label = comma(Freq)), color = "white", size = 5, fontface = "bold") +
  scale_fill_gradient(low = color_slate, high = color_oxide, guide = "none") +
  labs(x = "Valor real", y = "Valor predicho") +
  theme_minimal(base_size = 12) +
  theme(panel.grid = element_blank())

ggplotly(p6, tooltip = "text")
```

### Correlación entre variables del modelo

*Diagnóstico de multicolinealidad: valores cercanos a ±1 (rojo/verde intenso) indicarían variables redundantes. Aquí no hay ninguna preocupante.*

```{r}
cor_mat <- balanced_data %>%
  select(protesta_violenta, periodo_pre90, periodo_90_00, periodo_01_16,
         actor_laboral, actor_territorial_social, actor_economico, actor_estudiantil,
         protesta_masiva, numero_eventos_previos) %>%
  cor() %>%
  as.data.frame() %>%
  rownames_to_column("Var1") %>%
  pivot_longer(-Var1, names_to = "Var2", values_to = "r")

p8 <- ggplot(cor_mat, aes(x = Var1, y = Var2, fill = r, text = paste0(Var1, " × ", Var2, "<br>r = ", round(r,2)))) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = color_slate, mid = "#f2f0e7", high = color_oxide, midpoint = 0, guide = "none") +
  labs(x = NULL, y = NULL) +
  theme_minimal(base_size = 9) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(p8, tooltip = "text") %>% layout(margin = list(b = 100, l = 100))
```

Row {data-height=280}
-----------------------------------------------------------------------

### Tabla completa del modelo (up-sampling)

```{r}
modelo_final %>%
  transmute(Variable = label, OR = round(estimate, 3),
            `IC 2.5%` = round(conf.low, 3), `IC 97.5%` = round(conf.high, 3),
            `AME (pp)` = round(ame, 3), `p-valor` = signif(p.value, 3)) %>%
  datatable(options = list(dom = "t", pageLength = 10), rownames = FALSE)
```

### Output crudo de R — `summary(modelo_up)`

*El mismo resultado que verías corriendo el modelo en tu consola de R.*

```{r comment=""}
summary(modelo_up)
```

6. Remuestreo
=======================================================================

Row {data-height=130}
-----------------------------------------------------------------------

### Accuracy — Down-sampling
```{r}
valueBox(paste0(round(accuracy_down,2), "%"), icon = "fa-balance-scale", color = color_slate)
```

### Accuracy — Up-sampling (elegido)
```{r}
valueBox(paste0(round(accuracy_up,2), "%"), icon = "fa-check-circle", color = color_oxide)
```

### N final — Down-sampling
```{r}
valueBox(comma(nrow(balanced_data_down)), icon = "fa-database", color = color_slate)
```

### N final — Up-sampling
```{r}
valueBox(comma(nrow(balanced_data)), icon = "fa-database", color = color_oxide)
```

Row
-----------------------------------------------------------------------

### Comparación de métricas de rendimiento

```{r}
tibble(
  Métrica = c("Precisión global (accuracy)", "Sensibilidad", "Especificidad", "N final"),
  `Down-sampling` = c(paste0(round(accuracy_down,2),"%"), paste0(round(sensitivity_down,2),"%"),
                       paste0(round(specificity_down,2),"%"), comma(nrow(balanced_data_down))),
  `Up-sampling (elegido)` = c(paste0(round(accuracy_up,2),"%"), paste0(round(sensitivity_up,2),"%"),
                               paste0(round(specificity_up,2),"%"), comma(nrow(balanced_data)))
) %>%
  datatable(options = list(dom = "t"), rownames = FALSE)
```

> **Por qué up-sampling:** el down-sampling iguala clases eliminando ~69% de las protestas pacíficas originales, perdiendo heterogeneidad. El up-sampling conserva el 100% de la información original y muestra métricas de sensibilidad/especificidad igualmente estables.

7. Conclusiones
=======================================================================

Row
-----------------------------------------------------------------------

### H1 — Hipótesis de trabajo

> "La probabilidad de que una protesta social escale hacia la violencia en el Perú responde a las condiciones estructurales y los incentivos institucionales determinados por cada periodo político y sus respectivos ejecutivos."

**Validada.** Los nueve predictores del modelo son estadísticamente significativos (p < 0.001, salvo eventos previos p < 0.01).

### Hallazgos principales

- El **periodo previo a 1990** es el de mayor riesgo estructural frente al periodo actual (2016 en adelante).
- El **fujimorismo (1990–2000)** es el único periodo que reduce el riesgo base, consistente con un aparato autocrático que desmoviliza mediante control y cooptación.
- Los **actores territoriales/sociales** tienen el mayor impacto agregado por su alta representatividad; los **estudiantes** muestran la mayor volatilidad relativa.
- Los actores **laborales y económicos**, con mayor capacidad organizativa formal, actúan como factores de mitigación.
- **Limitación:** la cobertura de prensa nacional subrepresenta eventos en la periferia rural, y el up-sampling duplica la varianza del grupo minoritario.