Prof.: Joel Turco Quinto
Tema: Dashboard Gapminder - América (PIBpc)
Grupo 03 Integrantes:
El dashboard elaborado es una aplicación interactiva desarrollada bajo los paquetes de R: Shiny y la interfaz estandarizada de shinydashboard. Su propósito fundamental es guiar al usuario desde una vista animada de los datos, para conocer la evolución de los datos, hasta presentar resultados econométricos relacionados a la relación entre la Esperanza de Vida y el Crecimiento Económico.
library(shiny)
library(shinydashboard)
library(ggplot2)
library(gapminder)
library(dplyr)
library(plotly)
library(DT)
library(maps)
library(viridis)
library(stringr)
# 2. PREPARACIÓN DE DATOS ---------------------------------
DATA_PAC <- gapminder::gapminder %>%
filter(continent == "Americas", year >= 1970) %>%
mutate(
country = as.character(country),
continent = as.character(continent)
) %>%
rename(
País = country,
Continente = continent,
Año = year,
Esperanza_de_vida = lifeExp,
Población = pop,
PIBpc = gdpPercap
) %>%
mutate(
Continente = "América",
Subregion = case_when(
País %in% c("United States", "Canada") ~ "América del Norte",
País %in% c("Mexico", "Guatemala", "Honduras", "Nicaragua", "El Salvador",
"Costa Rica", "Panama") ~ "Centroamérica",
País %in% c("Brazil", "Argentina", "Chile", "Uruguay", "Paraguay", "Bolivia",
"Peru", "Colombia", "Venezuela", "Ecuador") ~ "América del Sur",
País %in% c("Cuba", "Dominican Republic", "Haiti", "Jamaica", "Trinidad and Tobago","Puerto Rico") ~ "El Caribe",
TRUE ~ "Otros"
),
iso_code = case_when(
País == "Argentina" ~ "ARG", País == "Bolivia" ~ "BOL", País == "Brazil" ~ "BRA",
País == "Canada" ~ "CAN", País == "Chile" ~ "CHL", País == "Colombia" ~ "COL",
País == "Costa Rica" ~ "CRI", País == "Cuba" ~ "CUB", País == "Dominican Republic" ~ "DOM",
País == "Ecuador" ~ "ECU", País == "El Salvador" ~ "SLV", País == "Guatemala" ~ "GTM",
País == "Haiti" ~ "HTI", País == "Honduras" ~ "HND", País == "Jamaica" ~ "JAM",
País == "Mexico" ~ "MEX", País == "Nicaragua" ~ "NIC", País == "Panama" ~ "PAN",
País == "Paraguay" ~ "PRY", País == "Peru" ~ "PER", País == "Trinidad and Tobago" ~ "TTO",
País == "United States" ~ "USA", País == "Uruguay" ~ "URY", País == "Venezuela" ~ "VEN",País == "Puerto Rico" ~ "PR",
TRUE ~ NA_character_
)
)
# Paleta de colores para diferenciar regiones
colores_sobres <- c(
"América del Norte" = "#00D2FF",
"Centroamérica" = "#00E676",
"América del Sur" = "#7C4DFF",
"El Caribe" = "#FF5252",
"Otros" = "#FF9100"
)
# 3. INTERFAZ DE USUARIO (UI) --------------------------------------------------
ui <- dashboardPage(
dashboardHeader(
title = tags$div(
tags$strong("Dashboard Gapminder-América", style = "color: #FFFFFF; font-size: 16px; letter-spacing: 0.5px;"),
tags$small("GRUPO 03", style = "color: #A0B2C6; font-size: 11px;")
),
titleWidth = 350
),
dashboardSidebar(
width = 280,
sidebarMenu(
menuItem("Desarrollo y Salud", tabName = "desarrollo", icon = icon("heartbeat")),
menuItem("Ranking y Competitividad", tabName = "ranking", icon = icon("trophy")),
menuItem("Mapa e Histórico", tabName = "mapa", icon = icon("map")),
menuItem("Estadísticas Descriptivas", tabName = "estadisticas", icon = icon("calculator")),
menuItem("Modelo de Regresión", tabName = "regresion", icon = icon("chart-line"))
),
tags$div(
style = "padding: 18px; color: #ECEFF1;",
tags$hr(style = "border-color: #37474F; margin-top: 5px; margin-bottom: 15px;"),
tags$label("Filtrar por Subregión:", style = "font-weight: 600; color: #B0BEC5; font-size: 12px;"),
selectInput("subregion", NULL, choices = c("Todas", sort(unique(DATA_PAC$Subregion))), selected = "Todas"),
tags$label("Filtrar por País:", style = "font-weight: 600; color: #B0BEC5; font-size: 12px;"),
uiOutput("selector_pais_ui"),
tags$label("Año de Análisis:", style = "font-weight: 600; color: #B0BEC5; font-size: 12px;"),
sliderInput("anio", NULL,
min = min(DATA_PAC$Año), max = max(DATA_PAC$Año),
value = 2007, step = 5, sep = "",
animate = animationOptions(interval = 1800, loop = FALSE)),
tags$label("Países en Ranking:", style = "font-weight: 600; color: #B0BEC5; font-size: 12px;"),
numericInput("top_n", NULL, value = 10, min = 1, max = 25)
)
),
dashboardBody(
tags$head(tags$style(HTML('
.skin-blue .main-header .logo { background-color: #0A1424; font-family: "Segoe UI", sans-serif; }
.skin-blue .main-header .navbar { background-color: #0F1E36; }
.skin-blue .main-sidebar { background-color: #16222F; font-family: "Segoe UI", sans-serif; }
.skin-blue .main-sidebar .sidebar .sidebar-menu .active a { border-left-color: #00D2FF; background-color: #0F1E36; }
.content-wrapper { background-color: #F8F9FA; }
.box { border-top: 3px solid #0F1E36 !important; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); margin-bottom: 20px; }
.box.box-solid.box-primary { border: 1px solid #0F1E36; }
.box.box-solid.box-primary>.box-header { background: #0F1E36; color: #fff; }
.box.box-solid.box-warning>.box-header { background: #FF9100; color: #fff; }
.small-box { border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.08); transition: transform .15s ease-in-out; }
.small-box:hover { transform: translateY(-2px); }
'))),
fluidRow(
valueBoxOutput("vbox_vida", width = 4),
valueBoxOutput("vbox_pib", width = 4),
valueBoxOutput("vbox_pop", width = 4)
),
tabItems(
# PESTAÑA 1: DESARROLLO Y SALUD
tabItem(tabName = "desarrollo",
fluidRow(
box(title = "Evolución del PIB per Cápita vs Esperanza de Vida (Animación)", plotlyOutput("plot_animacion", height = "500px"), width = 12, status = "primary", solidHeader = TRUE)
),
fluidRow(
box(title = "Datos Analizados", DTOutput("tabla_resumen"), width = 12, status = "primary")
)
),
# PESTAÑA 2: RANKING
tabItem(tabName = "ranking",
fluidRow(
box(title = "Top Países por PIB per Cápita (USD)", plotlyOutput("plot_ranking_gdp", height = "450px"), width = 6, status = "primary"),
box(title = "Dispersión entre PIBpc vs Salud", plotlyOutput("plot_matriz", height = "450px"), width = 6, status = "primary")
),
fluidRow(
box(title = "Distribución de la Esperanza de Vida", plotlyOutput("plot_boxplot_vida", height = "400px"), width = 12, status = "primary")
)
),
# PESTAÑA 3: MAPA E HISTÓRICO
tabItem(tabName = "mapa",
fluidRow(
box(title = "Distribución Geográfica de la Esperanza de Vida", plotlyOutput("mapa_plotly", height = "500px"), width = 12, status = "primary", solidHeader = TRUE)
),
fluidRow(
box(title = "Evolución Histórica del PIB per Cápita Promedio (USD)", plotlyOutput("plot_tendencias", height = "400px"), width = 12, status = "primary")
)
),
# PESTAÑA 4: ESTADÍSTICAS DESCRIPTIVAS
tabItem(tabName = "estadisticas",
fluidRow(
box(title = "Configuración del Análisis Descriptivo", status = "warning", solidHeader = TRUE, width = 12,
p("Los cálculos de las tablas se procesan automáticamente sobre la serie histórica completa."),
selectInput("var_analisis", "Variable a analizar:",
choices = c("PIB Per Cápita (USD)" = "PIBpc", "Esperanza de Vida (Años)" = "Esperanza_de_vida", "Población Total" = "Población"), selected = "PIBpc")),
box(title = "Métricas Agrupadas por Subregión", status = "primary", solidHeader = TRUE, width = 12, DTOutput("tabla_stats_subregion")),
box(title = "Métricas Históricas Detalladas por País", status = "primary", solidHeader = TRUE, width = 12, DTOutput("tabla_stats_pais"))
),
fluidRow(
box(title = "Matriz de Correlación de Pearson", status = "primary", solidHeader = TRUE, width = 12,
p(tags$em("Nota: Muestra el nivel de asociación lineal entre las variables numéricas segun los datos filtrados.")),
plotlyOutput("plot_correlacion", height = "450px"))
)
),
# PESTAÑA 5: MODELO DE REGRESIÓN LOG-LOG
tabItem(tabName = "regresion",
fluidRow(
valueBoxOutput("vbox_beta", width = 6),
valueBoxOutput("vbox_r2", width = 6)
),
fluidRow(
box(
title = "Modelo Log-Log Estimado: log(Esperanza de Vida) ~ log(PIB per Cápita)",
status = "primary", solidHeader = TRUE, width = 12,
p(tags$em("Nota: Ambas variables se encuentran transformadas logarítmicamente. La línea roja representa el ajuste lineal, interpretándose como la elasticidad (Variación Porcentual).")),
plotlyOutput("plot_regresion_log", height = "500px")
)
)
)
)
)
)
# 4. SERVIDOR (SERVER) ---------------------------------------------------------
server <- function(input, output, session) {
output$selector_pais_ui <- renderUI({
paises <- DATA_PAC
if(input$subregion != "Todas") paises <- paises %>% filter(Subregion == input$subregion)
selectInput("pais", NULL, choices = c("Todos", sort(unique(paises$País))), selected = "Todos")
})
datos_filtrados <- reactive({
res <- DATA_PAC %>% filter(Año == input$anio)
if(input$subregion != "Todas") res <- res %>% filter(Subregion == input$subregion)
if(!is.null(input$pais) && input$pais != "Todos") res <- res %>% filter(País == input$pais)
return(res)
})
datos_historicos_analisis <- reactive({
res <- DATA_PAC
if(input$subregion != "Todas") res <- res %>% filter(Subregion == input$subregion)
if(!is.null(input$pais) && input$pais != "Todos") res <- res %>% filter(País == input$pais)
return(res)
})
datos_regresion <- reactive({
if(!is.null(input$pais) && input$pais != "Todos") {
res <- DATA_PAC %>% filter(País == input$pais)
} else {
res <- DATA_PAC %>% filter(Año == input$anio)
if(input$subregion != "Todas") res <- res %>% filter(Subregion == input$subregion)
}
res <- res %>% filter(!is.na(PIBpc), PIBpc > 0,
!is.na(Esperanza_de_vida), Esperanza_de_vida > 0)
return(res)
})
output$vbox_vida <- renderValueBox({
df <- datos_filtrados()
val <- if(nrow(df) > 0) round(mean(df$Esperanza_de_vida, na.rm=TRUE), 0) else 0
valueBox(paste(val, "años"), "Esperanza de Vida Promedio", icon = icon("heart"), color = "teal")
})
output$vbox_pib <- renderValueBox({
df <- datos_filtrados()
val <- if(nrow(df) > 0) mean(df$PIBpc, na.rm=TRUE) else 0
valueBox(paste("$", scales::comma(round(val, 0))), "PIB per Cápita Promedio", icon = icon("money-bill-wave"), color = "light-blue")
})
output$vbox_pop <- renderValueBox({
df <- datos_filtrados()
val <- if(nrow(df) > 0) sum(as.numeric(df$Población), na.rm=TRUE) else 0
valueBox(
scales::number(val / 1e6, accuracy = 0.1, suffix = "Mill.", big.mark = ",", decimal.mark = "."),
"Población Total",
icon = icon("users"),
color = "blue"
)
})
output$plot_animacion <- renderPlotly({
df_anim <- DATA_PAC
if(input$subregion != "Todas") df_anim <- df_anim %>% filter(Subregion == input$subregion)
if(!is.null(input$pais) && input$pais != "Todos") df_anim <- df_anim %>% filter(País == input$pais)
p <- ggplot(df_anim, aes(x = PIBpc, y = Esperanza_de_vida, color = Subregion)) +
geom_point(aes(size = Población, frame = Año, ids = País, text = paste("País:", País, "<br>PIBpc: $", scales::comma(round(PIBpc, 0)), "USD", "<br>Esp. Vida:", round(Esperanza_de_vida, 1), "años")), alpha = 0.75) +
scale_x_log10(labels = scales::label_number(prefix = "$", big.mark = ",")) + scale_color_manual(values = colores_sobres) + scale_size_continuous(range = c(4, 18), guide = "none") +
labs(x = "PIB per Cápita (Escala en USD)", y = "Esperanza de Vida (Años)", color = "Subregión") +
theme_minimal() + theme(panel.grid.minor = element_blank())
ggplotly(p, tooltip = "text") %>% animation_opts(frame = 900, transition = 300, redraw = TRUE)
})
output$plot_ranking_gdp <- renderPlotly({
df <- datos_filtrados()
validate(need(nrow(df) > 0, "Sin datos disponibles"))
top_df <- df %>% arrange(desc(PIBpc)) %>% head(input$top_n)
p <- ggplot(top_df, aes(x = reorder(País, PIBpc), y = PIBpc, fill = Subregion, text = paste("País:", País, "<br>PIBpc: $", scales::comma(round(PIBpc, 0)), "USD"))) +
geom_col(alpha = 0.85, width = 0.7) + coord_flip() + scale_fill_manual(values = colores_sobres) + scale_y_continuous(labels = scales::label_number(prefix = "$", big.mark = ",")) +
labs(x = NULL, y = "PIB per Cápita (USD)") + theme_minimal() + theme(legend.position = "none")
ggplotly(p, tooltip = "text")
})
output$plot_matriz <- renderPlotly({
df <- datos_filtrados()
validate(need(nrow(df) > 0, "Sin datos disponibles"))
pib_m <- mean(df$PIBpc, na.rm = TRUE)
vida_m <- mean(df$Esperanza_de_vida, na.rm = TRUE)
df <- df %>% mutate(Cuadrante = case_when(PIBpc >= pib_m & Esperanza_de_vida >= vida_m ~ "Alto PBIpc / Alto EV", PIBpc < pib_m & Esperanza_de_vida >= vida_m ~ "Bajo PIBpc / Alta EV", PIBpc >= pib_m & Esperanza_de_vida < vida_m ~ "Alto PIBpc / Baja EV", TRUE ~ "Bajo PBIpc / Bajo EV"))
paleta_cuadrantes <- c("Alto PBIpc / Alto EV" = "#00E676", "Bajo PIBpc / Alta EV" = "#00D2FF", "Alto PIBpc / Baja EV" = "#FF9100", "Bajo PBIpc / Bajo EV" = "#FF5252")
p <- ggplot(df, aes(x = PIBpc, y = Esperanza_de_vida, color = Cuadrante, label = País)) +
geom_point(size = 3.5, alpha = 0.8, aes(text = paste("País:", País, "<br>PIBpc: $", scales::comma(round(PIBpc, 0)), "USD", "<br>Esp. Vida:", round(Esperanza_de_vida, 1), "años"))) +
geom_vline(xintercept = pib_m, linetype = "dashed", color = "#B0BEC5") + geom_hline(yintercept = vida_m, linetype = "dashed", color = "#B0BEC5") + scale_x_log10(labels = scales::label_number(prefix = "$", big.mark = ",")) +
scale_color_manual(values = paleta_cuadrantes) +
labs(x = "PIB per Cápita (Escala USD)", y = "Esperanza de Vida (Años)", color = "Cuadrante") + theme_minimal()
ggplotly(p, tooltip = "text")
})
output$plot_boxplot_vida <- renderPlotly({
df <- datos_filtrados()
validate(need(nrow(df) > 0, "Sin datos"))
p <- ggplot(df, aes(x = Subregion, y = Esperanza_de_vida, fill = Subregion)) +
geom_boxplot(outlier.shape = NA, alpha = 0.5, color = "#37474F") +
geom_jitter(width = 0.15, alpha = 0.7, aes(label = País, color = Subregion), size = 2) +
scale_fill_manual(values = colores_sobres) + scale_color_manual(values = colores_sobres) +
labs(x = NULL, y = "Años de Vida") + theme_minimal() + theme(legend.position = "none")
ggplotly(p)
})
output$mapa_plotly <- renderPlotly({
df <- datos_filtrados()
validate(need(nrow(df) > 0, "Sin datos para el mapa"))
plot_geo(df) %>%
add_trace(z = ~Esperanza_de_vida, color = ~Esperanza_de_vida, colors = "Purples",
text = ~paste(País, "<br>Esp. Vida:", round(Esperanza_de_vida, 1), "años"),
locations = ~iso_code, marker = list(line = list(color = toRGB("white"), width = 0.5))) %>%
layout(geo = list(scope = 'americas', projection = list(type = 'natural earth'), bgcolor = 'rgba(0,0,0,0)'))
})
output$plot_tendencias <- renderPlotly({
df_tend <- DATA_PAC
if(input$subregion != "Todas") df_tend <- df_tend %>% filter(Subregion == input$subregion)
if(!is.null(input$pais) && input$pais != "Todos") {
df_linea <- df_tend %>% filter(País == input$pais)
p <- ggplot(df_linea, aes(x = Año, y = PIBpc, group = País, text = paste("País:", País, "<br>Año:", Año, "<br>PIBpc: $", scales::comma(round(PIBpc, 0)), "USD"))) +
geom_line(linewidth = 1.2, color = "#7C4DFF") + geom_point(size = 2.5, color = "#0F1E36") + scale_y_continuous(labels = scales::label_number(prefix = "$", big.mark = ",")) + labs(title = paste("Trayectoria Histórica:", input$pais), x = "Año", y = "PIB per Cápita (USD)") + theme_minimal()
} else {
df_linea <- df_tend %>% group_by(Subregion, Año) %>% summarise(PIBpc_prom = mean(PIBpc, na.rm=TRUE), .groups = "drop")
p <- ggplot(df_linea, aes(x = Año, y = PIBpc_prom, color = Subregion, text = paste("Subregión:", Subregion, "<br>Año:", Año, "<br>PIBpc Promedio: $", scales::comma(round(PIBpc_prom, 0)), "USD"))) +
geom_line(linewidth = 1.2) + geom_point(size = 2) + scale_color_manual(values = colores_sobres) + scale_y_continuous(labels = scales::label_number(prefix = "$", big.mark = ",")) + labs(x = "Año", y = "PIB per Cápita Promedio (USD)", color = "Subregión") + theme_minimal() + theme(legend.position = "bottom")
}
ggplotly(p, tooltip = "text")
})
output$tabla_resumen <- renderDT({
datos_filtrados() %>% select(País, Subregion, PIBpc, Esperanza_de_vida, Población) %>% datatable(extensions = 'Buttons', options = list(pageLength = 5, dom = 'Bfrtip', buttons = c('csv', 'excel'), scrollX = TRUE), rownames = FALSE, colnames = c("País", "Subregión", "PIB per Cápita (USD)", "Esperanza de Vida (Años)", "Población")) %>% formatCurrency("PIBpc", currency = "$", digits = 0) %>% formatRound("Esperanza_de_vida", digits = 1) %>% formatRound("Población", digits = 0)
})
output$tabla_stats_subregion <- renderDT({
df_hist <- datos_historicos_analisis()
v_target <- input$var_analisis
df_stats <- df_hist %>% group_by(Subregion) %>% summarise(Promedio = mean(.data[[v_target]], na.rm = TRUE), Minimo = min(.data[[v_target]], na.rm = TRUE), Q25 = quantile(.data[[v_target]], 0.25, na.rm = TRUE), Q50_Med = median(.data[[v_target]], na.rm = TRUE), Q75 = quantile(.data[[v_target]], 0.75, na.rm = TRUE), Q100_Max = max(.data[[v_target]], na.rm = TRUE), Desv_Std = sd(.data[[v_target]], na.rm = TRUE), .groups = "drop") %>% mutate(Coef_Variacion = (Desv_Std / Promedio) * 100) %>% select(-Desv_Std)
datatable(df_stats, extensions = 'Buttons', options = list(dom = 'Bfrtip', buttons = c('csv', 'excel'), scrollX = TRUE, pageLength = 5), rownames = FALSE, colnames = c("Subregión", "Promedio", "Mínimo", "Cuartil 25%", "Cuartil 50% (Mediana)", "Cuartil 75%", "Máximo (Q100)", "Coef. Variación (%)")) %>% formatRound(columns = 2:8, digits = 2)
})
output$tabla_stats_pais <- renderDT({
df_hist <- datos_historicos_analisis()
v_target <- input$var_analisis
df_stats_pais <- df_hist %>% group_by(País, Subregion) %>% summarise(Promedio = mean(.data[[v_target]], na.rm = TRUE), Minimo = min(.data[[v_target]], na.rm = TRUE), Q25 = quantile(.data[[v_target]], 0.25, na.rm = TRUE), Q50_Med = median(.data[[v_target]], na.rm = TRUE), Q75 = quantile(.data[[v_target]], 0.75, na.rm = TRUE), Q100_Max = max(.data[[v_target]], na.rm = TRUE), Desv_Std = sd(.data[[v_target]], na.rm = TRUE), .groups = "drop") %>% mutate(Coef_Variacion = (Desv_Std / Promedio) * 100) %>% select(-Desv_Std)
datatable(df_stats_pais, extensions = 'Buttons', options = list(dom = 'Bfrtip', buttons = c('csv', 'excel'), scrollX = TRUE, pageLength = 10),
rownames = FALSE, colnames = c("País", "Subregión", "Promedio", "Mínimo", "Cuartil 25%", "Cuartil 50% (Mediana)", "Cuartil 75%", "Máximo (Q100)", "Coef. Variación (%)")) %>% formatRound(columns = 3:9, digits = 2)
})
output$plot_correlacion <- renderPlotly({
df <- datos_historicos_analisis()
vars_corr <- df %>%
select(
`PIB per Cápita` = PIBpc,
`Esp. Vida` = Esperanza_de_vida,
`Población` = Población
)
validate(need(nrow(vars_corr) >= 5, "Se requieren al menos 5 observaciones históricas acumuladas."))
matriz_cor <- cor(vars_corr, use = "complete.obs", method = "pearson")
df_cor <- as.data.frame(as.table(matriz_cor))
names(df_cor) <- c("Variable_X", "Variable_Y", "Coeficiente")
p <- ggplot(df_cor, aes(x = Variable_X, y = Variable_Y, fill = Coeficiente,
text = paste("Variables:", Variable_X, "vs", Variable_Y,
"<br>Correlación (r):", round(Coeficiente, 3)))) +
geom_tile(color = "white", lwd = 0.4) +
scale_fill_gradient2(low = "#FF5252", high = "#7C4DFF", mid = "#FFFFFF",
midpoint = 0, limit = c(-1, 1), name = "r") +
geom_text(aes(label = round(Coeficiente, 2)), color = "#263238", size = 4) +
theme_minimal() + labs(x = NULL, y = NULL) +
theme(
axis.text.x = element_text(angle = 25, vjust = 1, hjust = 1),
panel.grid.major = element_blank(), panel.grid.minor = element_blank()
)
ggplotly(p, tooltip = "text")
})
output$vbox_beta <- renderValueBox({
df <- datos_regresion()
validate(need(nrow(df) >= 3, "Insuficientes puntos de datos."))
modelo <- lm(log(Esperanza_de_vida) ~ log(PIBpc), data = df)
beta_val <- coef(modelo)[2]
valueBox(round(beta_val, 4), "Elasticidad β (variación porcentual)", icon = icon("calculator"), color = "purple")
})
output$vbox_r2 <- renderValueBox({
df <- datos_regresion()
validate(need(nrow(df) >= 3, ""))
modelo <- lm(log(Esperanza_de_vida) ~ log(PIBpc), data = df)
r2_val <- summary(modelo)$r.squared
valueBox(paste0(round(r2_val * 100, 1), "%"), "Bondad de Ajuste R² Explicado", icon = icon("percentage"), color = "teal")
})
output$plot_regresion_log <- renderPlotly({
df <- datos_regresion()
validate(need(nrow(df) >= 3, "Se requieren por lo menos 3 observaciones válidas."))
p <- ggplot(df, aes(x = PIBpc, y = Esperanza_de_vida)) +
geom_point(aes(color = Subregion, size = Población,
text = paste("País:", País, "<br>Año:", Año, "<br>PIBpc: $", scales::comma(round(PIBpc, 0)), "USD", "<br>Esp. Vida:", round(Esperanza_de_vida, 1), "años")), alpha = 0.75) +
stat_smooth(method = "lm", formula = y ~ x, color = "#FF5252", lwd = 1, se = TRUE, fill = "#FFCDD2") +
scale_x_log10(labels = scales::label_number(prefix = "$", big.mark = ",")) +
scale_y_log10(labels = scales::label_number(suffix = " años", accuracy = 1)) +
scale_color_manual(values = colores_sobres) + scale_size_continuous(range = c(3, 14), guide = "none") +
labs(x = "PIB per Cápita (Escala en USD)", y = "Esperanza de Vida (Escala en Años)", color = "Subregión") +
theme_minimal() + theme(panel.grid.minor = element_blank())
ggplotly(p, tooltip = "text")
})
}
# 5. EJECUCIÓN DE LA APLICACIÓN ------------------------------------------------
shinyApp(ui, server)
El proyecto también cuenta con una aplicación interactiva desarrollada en Shiny para explorar dinámicamente los indicadores económicos y sociales.