R包gbmt轨迹模型指标计算的函数

发布于:2025-05-19 ⋅ 阅读:(19) ⋅ 点赞:(0)
calculate_OCC <- function(model) {
  num_groups <- length(model$appa)

  # Si el modelo tiene solo un grupo, devolver 999.0
  if (num_groups == 1) {
    return(Inf)
  }

  # Encontrar el grupo asignado para cada observación según la máxima probabilidad posterior
  assigned_groups <- apply(model$posterior, 1, which.max)

  # Calcular la proporción de población estimada para cada grupo
  population_proportion <- table(assigned_groups) / length(assigned_groups)

  # Calcular el OCC para cada grupo
  OCC <- sapply(1:num_groups, function(j) {
    # Si APPA es igual a 1, devolver 999.0
    if (model$appa[j] == 1) {
      return(999.0)
    }

    odds_correct <- model$appa[j] / (1 - model$appa[j])
    odds_random <- population_proportion[j] / (1 - population_proportion[j])
    return(odds_correct / odds_random)
  })

  # Devolver el promedio de OCC
  return(mean(OCC))
}

calculate_mismatch <- function(model) {
  # Calcular las proporciones de cada grupo estimadas por el modelo
  group_proportions <- model$prior

  # Calcular las proporciones reales de cada grupo en la muestra
  real_proportions <- table(factor(model$assign, levels = 1:length(group_proportions))) / length(model$assign)

  # Calcular el Mismatch para cada grupo
  mismatch <- group_proportions - real_proportions

  # Devolver el promedio de los valores absolutos de Mismatch de todos los grupos
  return(mean(abs(mismatch)))
}

calculate_SD_GMP <- function(model) {
  # Inicializar un vector para almacenar el SD-GMP para cada grupo
  sd_gmp <- numeric(length(model$prior))

  # Calcular el SD-GMP para cada grupo
  for (j in 1:length(model$prior)) {
    assigned_indices <- which(model$assign == j)
    group_probs <- model$posterior[assigned_indices, j]
    sd_gmp[j] <- sd(group_probs)
  }

  # Devolver la media de los SD-GMP de todos los grupos
  return(mean(sd_gmp))
}
---
title: "Trajectory Analysis"
author:
  - name: Brian Peña-Calero
    email: brian.pena@upch.pe
    affiliation: Innovar, UPCH
date: "`r Sys.Date()`"
output: 
  html_document: 
    toc: yes
    toc_depth: 3
    toc_float: yes
    number_sections: yes
    code_folding: show
    code_download: yes
    theme: 
      bootswatch: flatly
    highlight: kate
    highlight_downlit: true
editor_options: 
  chunk_output_type: console
---


```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)

Carga de paquetes

library(tidyverse)
library(gbmt)
library(multidplyr)

Crear carpetas de trabajo:

fs::dir_create("01_data/raw/")
fs::dir_create("01_data/processed/")

Importar datos

library(sf)
salid1_pubsalid1 <- readr::read_csv("01_data/raw/SALID1_PUBSALID1.csv")
gdb_path <- "01_data/raw/Data Request MS242_09222023/MS242_L1.gdb"
gdb_salurbal <- st_layers(gdb_path)

cities_l1ad <- st_read(
  gdb_path,
  gdb_salurbal$name[[1]]
) %>%
  as_tibble() %>%
  st_as_sf() %>%
  left_join(
    salid1_pubsalid1
  ) %>%
  relocate(SALID1, .after = PUBSALID1)

cities_l1ad_noislands <- st_read(
  gdb_path,
  gdb_salurbal$name[[2]]
) %>%
  as_tibble() %>%
  st_as_sf() %>%
  left_join(
    salid1_pubsalid1
  ) %>%
  relocate(SALID1, .after = PUBSALID1)

Datos Crecimiento Urbano

Los datos de crecimiento urbano actuales solo tienen la columna ISO2 que identifica al país, y una codificación propia SALID1 para las ciudades, que es con lo que necesitamos realizar el match, para tener los datos de ciudad en cuanto a su crecimiento poblacional y a la vez en cuánto a su crecimiento urbano.

bec_backcast_l1ux <- read_csv("01_data/raw/BEC_BACKCAST_L1UX_20230302.csv")

Se adiciona la columna SALID0 calculado como los 3 primeros dígitos de SALID1. Además se agrega PUBSALID1.

bec_backcast_l1ux <- bec_backcast_l1ux %>%
  mutate(
    SALID0 = str_sub(SALID1, 1, 3),
    .before = SALID1
  ) %>%
  left_join(
    salid1_pubsalid1
  ) %>%
  relocate(PUBSALID1, .before = SALID1)

Datos Poblacionales

Para el caso de los datos poblacionales, se tiene la identificación en nombre del país y la ciudad. Se agrega el identificador ISO2, para fines de matching.

population <- st_read(
  gdb_path,
  gdb_salurbal$name[[3]]
) %>%
  as_tibble() %>%
  left_join(
    salid1_pubsalid1
  ) %>%
  relocate(SALID1, .after = PUBSALID1) %>%
  drop_na(PUBSALID1)
population <- population %>%
  mutate(
    ISO2 = case_match(
      country,
      "argentina" ~ "AR",
      "brazil" ~ "BR",
      "chile" ~ "CL",
      "colombia" ~ "CO",
      "costa rica" ~ "CR",
      "el salvador" ~ "SV",
      "guatemala" ~ "GT",
      "mexico" ~ "MX",
      "nicaragua" ~ "NI",
      "panama" ~ "PA",
      "peru" ~ "PE"
    ),
    .before = country
  ) %>%
  left_join(
    bec_backcast_l1ux %>%
      select(ISO2, SALID0) %>%
      distinct()
  ) %>%
  relocate(ISO2, SALID0) %>%
  arrange(SALID0)
population

Se aplica una pequeña corrección a los datos numéricos poblacionales que contienen caracteres como - y " " (espacios) para representar valores perdidos.

population <- population %>%
  mutate(
    across(
      F1945:F2022,
      as.numeric
    )
  )

population

Se crea este objeto temporal a partir del formato en el objeto population que contiene la información de ISO2, SALID0, SALID1, country, City. Aquí debería reemplazarse con el una data que brinde una información equivalente.

city_info <- population %>%
  select(
    ISO2:City, PUBSALID1:SALID1
  )
saveRDS(
  city_info,
  file = "01_data/processed/city_info.rds"
)

#city_info <- readRDS("01_data/processed/city_info.rds")

Imputación de datos

Debido a que hay una gran cantidad de valores perdidos a través de los años en distintas ciudades, se realiza un proceso de imputación de datos tanto para los de crecimiento urbano como datos poblacionales.

El análisis de imputación se realiza para cada ciudad, por lo que se usará el enfoque de tidy nested para organizar los datos de tal manera que cada fila sea una ciudad; y su columna, sea un conjunto de datos que contenga la información de interés a través de los años. Para el proceso de imputación se usará el paquete imputeTS wrappeado en la función safely (paquete purrr) para evitar la detención de errores debido a problemas de convergencia o insuficiencia de datos en algunas ciudades.

library(imputeTS)
na_kalman_safe <- safely(.f = na_kalman)

Crecimiento Urbano

Los datos de crecimiento urbano proporcionado tienen información cada 5 años, por lo que se usará la misma técnica de imputación para tener información anual. Para ello primero se completará los años faltantes en el data.frame:

bectuareal_tidy <- bec_backcast_l1ux %>%
  distinct(PUBSALID1) %>%
  crossing(YEAR = 1985:2015) %>%
  left_join(
    bec_backcast_l1ux %>%
      select(PUBSALID1, YEAR, BECTUAREAL1UX)
  ) %>%
  janitor::clean_names()

Ahora se procede a crear el formato nest:

bectuareal_imp <- bectuareal_tidy %>%
  group_nest(pubsalid1) %>%
  mutate(
    ts_data = map(
      data,
      ~ ts(.x$bectuareal1ux, 1985, 2015)
    ),
    kalman_data = map(
      ts_data,
      na_kalman_safe
    )
  )

En este caso también se tiene algunos errores (1)

bectuareal_imp_errors <- bectuareal_imp %>%
  filter(map_lgl(.$kalman_data, ~ !is.null(.x$error)))

Aunque en la mayoría de ciudades se logra imputar (370 ciudades).

bectuareal_imp_clean <- bectuareal_imp %>% 
  filter(map_lgl(.$kalman_data, ~ is.null(.x$error))) %>% 
  mutate(kalman_data = map(kalman_data, 'result'))

Finalmente, se añadirá la información sobre la cantidad original de crecimiento urbano existente para fines de futuras validaciones:

bectuareal_imp_merge <- bectuareal_imp_clean %>%
  mutate(
    kalman_data = map(
      kalman_data,
      ~ as_tibble(.x) %>%
        mutate(
          year = 1985:2015
        )
    ),
    data_imputation = map2(
      data, kalman_data,
      ~ .x %>%
        left_join(.y,
          by = join_by(year)
        )
    )
  )

bectuareal_imp_merge <- bectuareal_imp_merge %>% 
  select(pubsalid1, data_imputation) %>% 
  unnest(cols = c(data_imputation)) %>% 
  rename(bectuareal1ux_imp = x)

Datos poblacionales

Necesitamos ordenar los datos y darle un formato nested. Observamos que los datos poblacionales con los que se trabaja tiene 648 ciudades.

population_nested <- population %>%
  janitor::clean_names() %>%
  rename_with(
    ~ str_replace(., "f", "x"),
    f1945:f2015
  ) %>%
  select(pubsalid1, x1945:x2015) %>%
  pivot_longer(
    cols = x1945:x2015,
    names_to = "year",
    values_to = "population"
  ) %>%
  group_nest(pubsalid1)
population_nested

Se tiene una función que verifica una desviación máxima de valores poblacionales en una ciudad en particular a partir de su mediana a lo largo de los años. Por defecto, se usa el criterio de 20% máximo de variación. En caso algún valor poblacional supere este valor, se ingresará un NA en lugar del valor reportado. Así, la imputación podrá también estimar estos valores anómalos.

fix_population <- function(data, desviacion_max = 0.2) {
  data_wo_na <- na.omit(data)
  
  if (length(data_wo_na) < 2) {
    return(data)
  }
  
  median_population <- median(data_wo_na)
  
  upper_limit <- median_population * (1 + desviacion_max)
  lower_limit <- median_population * (1 - desviacion_max)
  
  data_fix <- ifelse(
    !is.na(data) & (data > upper_limit | data < lower_limit), 
    NA, 
    data
  )
  return(data_fix)
}
population_imp <- population_nested %>%
  mutate(
    # Aplicación de la corrección
    pop_fix = map(
      data, 
      ~ fix_population(.x$population) %>%
        enframe(
          name = "year",
          value = "population"
        ) %>%
        mutate(
          year = paste0("x", 1945:2015)
        )
    ),
    # Transformar a objeto `time series`
    ts_pop_fix = map(
      pop_fix,
      ~ ts(.x$population, 1945, 2015) %>%
        log()
    ),
    # Aplicación de la imputación
    kalman_pop_fix = map(
      ts_pop_fix, 
      na_kalman_safe
    )
  )

Debido a que algunas ciudades tienen valores perdidos en todos los años de análisis, no en todos los casos la imputación es factible. En este caso hubo un total de 208 ciudades con problemas de imputación.

population_imp_errors <- population_imp %>%
  filter(map_lgl(.$kalman_pop_fix, ~ !is.null(.x$error)))

En tanto, la cantidad de ciudades con las que se podrá trabajar con información poblacional será de 440.

population_imp_clean <- population_imp %>%
  filter(map_lgl(.$kalman_pop_fix, ~ is.null(.x$error))) %>%
  mutate(kalman_pop_fix = map(kalman_pop_fix, "result"))

Finalmente, se añadirá la información sobre la cantidad original de dato poblacional existente para fines de futuras validaciones:

population_imp_merge <- population_imp_clean %>%
  mutate(
    kalman_pop_fix = map(
      kalman_pop_fix,
      ~ as_tibble(.x) %>%
        mutate(
          x = exp(x), 
          year = paste0("x", 1945:2015)
        )
    ),
    data_imputation = map2(
      pop_fix, kalman_pop_fix,
      ~ .x %>%
        left_join(.y,
          by = join_by(year)
        )
    )
  )

population_imp_merge <- population_imp_merge %>%
  select(pubsalid1, data_imputation) %>%
  unnest(cols = c(data_imputation)) %>%
  rename(population_imp = x) %>%
  mutate(
    year = str_remove(year, "x"),
    year = as.numeric(year)
  )

population_imp_merge

Unificación de datos

En este punto, ya se tiene los datos poblacionales y de crecimiento urbano limpios y ordenados. Se realiza la unificación en base al código salid1 que deberían tener ambos objetos.

Se usa la función full_join para tener la posibilidad de analizar los datos poblacionales y de crecimiento juntos y separados.

# bectuareal_imp_merge %>%
# filter(year == 1985)

# pubsalid1_nopopulation <- anti_join(
#     bectuareal_imp_merge,
#     population_imp_merge,
#     by = join_by(pubsalid1, year)
#   ) %>%
#   filter(year == 1985) %>%
#   pull(pubsalid1)

# pubsalid1_nobectu <- anti_join(
#   population_imp_merge,
#   bectuareal_imp_merge,
#   by = join_by(pubsalid1, year)
# ) %>%
#   filter(year == 1985) %>%
#   pull(pubsalid1)

# anti_join(
#   bectuareal_imp_merge,
#   population_imp_merge,
#   by = join_by(pubsalid1, year)
# ) %>%
# filter(year == 1985)
pop_bectuareal <- full_join(
  population_imp_merge,
  bectuareal_imp_merge,
  by = join_by(pubsalid1, year)
)

pop_bectuareal %>%
filter(pubsalid1 == 514651)

Grouped-based Model Trajectory (GBMT)

Formateo de datos

Se crearán 3 objetos:

  • pop_bectu_gbmt_df: Contiene a las coincidencias completas de datos poblacionales y crecimiento poblacional en las ciudades.
  • pop_gbmt_df: Contiene solo a los valores imputados completos de las poblaciones en ciudades.
  • bectu_gbmt_df: Contiene solo a los valores imputados completos de los crecimientos poblacionales en ciudades.
pop_bectu_gbmt_df <- pop_bectuareal %>%
  select(
    pubsalid1, year,
    population_imp, bectuareal1ux_imp
  ) %>%
  drop_na()

pop_gbmt_df <- pop_bectuareal %>%
  select(pubsalid1, year, population_imp) %>%
  drop_na(population_imp)

bectu_gbmt_df <- pop_bectuareal %>%
  select(pubsalid1, year, bectuareal1ux_imp) %>%
  drop_na(bectuareal1ux_imp) 

Estimación

Configuración de uso multinúcleo para el análisis:

cluster <- new_cluster(parallel::detectCores())
cluster_library(cluster, c("dplyr", "purrr", "gbmt"))
gbmt_safe <- safely(.f = gbmt)
cluster_copy(cluster, "gbmt_safe")

Se crea una pseudo-matriz que contiene los parámetros de análisis y los conjuntos de datos para analizar en cada fila.

gbmt_matrix <- expand_grid(
  d = 2,
  ng = 1:10,
  scale = c(2, 4),
  tibble(
    data_gbmt = list(
      pop_bectu_gbmt_df,
      pop_gbmt_df,
      bectu_gbmt_df
    ),
    x_names = list(
      c("population_imp", "bectuareal1ux_imp"),
      c("population_imp"),
      c("bectuareal1ux_imp")
    )
  )  
) 

Este siguiente bloque realiza las estimaciones especificadas arriba usando todos los núcleos del procesador.

gbmt_outputs <- gbmt_matrix %>%
  partition(cluster) %>%
  mutate(
    gbmt_out = pmap(
      list(
        data_gbmt, x_names,
        d, ng, scale
      ), 
      ~ gbmt_safe(
        x.names = ..2,
        unit = "pubsalid1",
        time = "year",
        data = as.data.frame(..1),
        d = ..3,
        ng = ..4,
        scaling = ..5
      )
    )
  )

gbmt_collect <- gbmt_outputs %>%
  collect()
fs::dir_create("01_data/processed/")

saveRDS(gbmt_collect,
  file = "01_data/processed/gbmt_collect_pop_bectu.rds"
)

#gbmt_collect <- readRDS("01_data/processed/gbmt_collect_pop_bectu.rds")
gbmt_collect_errors <- gbmt_collect %>% 
  filter(map_lgl(.$gbmt_out, ~ !is.null(.x$error)))

gbmt_collect_clean <- gbmt_collect %>% 
  filter(map_lgl(.$gbmt_out, ~ is.null(.x$error))) %>% 
  mutate(gbmt_out = map(gbmt_out, 'result'))

Índices de validación

source("fit-index-gbmt.r")
gbmt_fit_total <- gbmt_collect_clean %>%
  mutate(
    AIC = map_dbl(gbmt_out, AIC),
    BIC = map_dbl(gbmt_out, BIC),
    L = map_dbl(gbmt_out, logLik),
    APPA = map_dbl(gbmt_out, ~ mean(.$appa)),
    Mismatch = map_dbl(gbmt_out, calculate_mismatch),
    SD_GMP = map_dbl(gbmt_out, calculate_SD_GMP),
    OCC = map_dbl(gbmt_out, calculate_OCC),
    smallest_group = map2_dbl(
      gbmt_out, ng,
      ~ min(table(factor(.x$assign, levels = 1:.y))) /
        sum(table(factor(.x$assign, levels = 1:.y)))
    ),
    scale_name = case_match(
      scale,
      2 ~ "standardization",
      4 ~ "logarithmic"
    ),
    city_info = list(city_info)
  ) %>%
  relocate(scale_name, .after = "scale") %>%
  arrange(ng, d, scale)

Exportar la información sobre los índices de ajuste:

fs::dir_create("02_output/tables/")

gbmt_fit_total %>%
  select(d:scale, scale_name, x_names, 
         AIC:smallest_group) %>%
  openxlsx::write.xlsx(
    file = "02_output/tables/fit_measures_models_gbmt.xlsx"
  )

Exportar el output de las regresiones:

# Esta función procesa un único conjunto de regresiones
process_regressions <- function(regressions, ng, scale) {
  result_list <- list()

  for (group in names(regressions)) {
    for (var_name in names(regressions[[group]])) {
      tmp <- broom::tidy(regressions[[group]][[var_name]])
      tmp$variable <- var_name
      tmp$ng <- ng
      tmp$scale <- scale
      tmp$group <- as.integer(group)

      result_list[[length(result_list) + 1]] <- tmp
    }
  }

  bind_rows(result_list)
}

# Procesa todas las regresiones en gbmt_fit_total$gbmt_out
results_all <- lapply(1:length(gbmt_fit_total$gbmt_out), function(i) {
  process_regressions(
    regressions = gbmt_fit_total$gbmt_out[[i]]$reg,
    ng = gbmt_fit_total$ng[i],
    scale = gbmt_fit_total$scale_name[i]
  )
})

# Convierte los resultados en un formato más manejable
resultados_tidy <- bind_rows(results_all)
saveRDS(
  resultados_tidy, 
  file = "02_output/tables/resultados_tidy_reg.rds"
)

Re-ingreso de información

gbmt_fit_total <- gbmt_fit_total %>%
  mutate(
    data_gbmt_extract = map(
      gbmt_out,
      ~ .x$data.norm %>%
        rename_with(
          ~ paste0(., "_norm"),
          contains("_imp")
        ) %>%
        left_join(
          .x$data.orig,
          by = join_by(pubsalid1, year)
        ) %>%
        as_tibble()
    ),
    assign_list = map(
      gbmt_out,
      ~ enframe(.x$assign.list) %>%
      unnest(cols = c(value)) %>%
      mutate(
        pubsalid1 = as.factor(value)
      ) %>%
      select(pubsalid1, group = name)
    )
  )

En este punto se unificará la información de los datos poblacionales, crecimiento urbano, grupo al que pertenece debido al análisis gbmt y la infiormación de las ciudades que está almacenada en city_info. Si esa información es incompleta vamos a tener problemas de NA’s.

gbmt_fit_final <- gbmt_fit_total %>%
  mutate(
    gbmt_fit_total = pmap(
      list(
        data_gbmt_extract,
        assign_list,
        city_info
      ),
      ~ left_join(
        ..1,
        ..2,
        by = join_by(pubsalid1)
      ) %>%
      # mutate(
      #   SALID0 = str_sub(salid1, 1, 3)
      # ) %>%
      left_join(
        ..3 %>%
          rename(pubsalid1 = PUBSALID1) %>%
          mutate(pubsalid1 = factor(pubsalid1)),
        by = join_by(pubsalid1)
      )
    )
  )
saveRDS(gbmt_fit_final,
  file = "01_data/processed/gbmt_fit_final.rds"
)

#gbmt_fit_final <- readRDS("01_data/processed/gbmt_fit_final.rds")

La estructura en este punto estaría quedando así:

Generación de Gráficos

Gráficos de barra y líneas

library(patchwork)

gg_line_plot <- function(datos, y_variable, y_label, type = "standardized") {
  gg <- ggplot(datos, aes(x = year, y = !!sym(y_variable))) +
    geom_line(aes(group = pubsalid1), color = "grey80") +
    geom_smooth(method = "lm", formula = y ~ I(x^1) + I(x^2)) +
    scale_x_continuous(breaks = c(seq(1945, 2015, 15), 2015)) +
    labs(x = NULL, y = y_label) +
    theme_bw()

  if (type == "standardized") {
    gg +
      facet_wrap(vars(group))
  } else if (type == "non-standardized") {
    gg +
      scale_y_continuous(
        labels = scales::label_number()
      ) +
      facet_wrap(vars(group), scales = "free")
  }
}

gg_stacked_bar <- function(datos) {
  datos %>%
    count(country, group) %>%
    group_by(country) %>%
    mutate(
      percent = n / sum(n),
      country = str_to_title(country)
    ) %>%
    ungroup() %>%
    ggplot(aes(x = country, y = percent)) +
    geom_col(aes(fill = group)) +
    innovar::scale_fill_innova("ccvi") +
    labs(
      fill = "Cluster",
      y = NULL,
      x = NULL
    ) +
    geom_label(
      aes(
        group = group,
        label = scales::percent(percent, accuracy = 1)
      ),
      position = position_stack(vjust = 0.5)
    ) +
    scale_y_continuous(
      labels = scales::percent_format()
    ) +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 20, hjust = 1))
}

gg_bar <- function(datos, year_filter = 2015) {
  datos %>%
    filter(year == year_filter) %>%
    count(country, group) %>%
    group_by(country) %>%
    mutate(
      percent = n / sum(n),
      country = str_to_title(country)
    ) %>%
    ungroup() %>%
    ggplot(aes(x = country, y = n)) +
    geom_col(aes(fill = group)) +
    innovar::scale_fill_innova("ccvi") +
    labs(
      fill = "Cluster",
      y = NULL,
      x = NULL
    ) +
    geom_label(
      aes(
        group = group,
        label = n
      ),
      position = position_stack(vjust = 0.5)
    ) +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 20, hjust = 1))
}

Generación de los gráficos de línea estandarizados, sin estandarizar, stacked bar y barras (frecuencia).

gbmt_fit_plots <- gbmt_fit_final %>%
  mutate(
    line_plot_std = map2(
      gbmt_fit_total,
      x_names,
      ~ {
        if (all(.y == c("population_imp", "bectuareal1ux_imp"))) {
          p1 <- gg_line_plot(.x, "population_imp_norm", "Standardized Population")
          p2 <- gg_line_plot(.x, "bectuareal1ux_imp_norm", "Standardized Bectuareal1ux")
          p1 / p2
        } else if (all(.y == c("population_imp"))) {
          gg_line_plot(.x, "population_imp_norm", "Standardized Population")
        } else {
          gg_line_plot(.x, "bectuareal1ux_imp_norm", "Standardized Bectuareal1ux")
        }
      }
    ),
    line_plot = map2(
      gbmt_fit_total,
      x_names,
      ~ {
        if (all(.y == c("population_imp", "bectuareal1ux_imp"))) {
          p1 <- gg_line_plot(.x, "population_imp", "Population", "non-standardized")
          p2 <- gg_line_plot(.x, "bectuareal1ux_imp", "Bectuareal1ux", "non-standardized")
          p1 / p2
        } else if (all(.y == c("population_imp"))) {
          gg_line_plot(.x, "population_imp", "Population", "non-standardized")
        } else {
          gg_line_plot(.x, "bectuareal1ux_imp", "Bectuareal1ux", "non-standardized")
        }
      }
    ),
    stacked_bar_plot = map(
      gbmt_fit_total,
      gg_stacked_bar
    ),
    bar_plot = map(
      gbmt_fit_total,
      gg_bar
    )
  )

Finalmente guardamos los gráficos generados:

# Función para guardar una lista de gráficos
save_plots <- function(plots, prefix, x_names, cluster, scale_name = NULL, std = FALSE) {
  walk2(plots, seq_along(plots), function(plot, i) {
    # Crear el nombre base con las variables
    var_names <- str_replace_all(x_names[[i]], "_imp", "")
    var_string <- str_c(var_names, collapse = "-")

    # Crear el nombre del archivo usando el número de cluster
    filename <- paste0(prefix, "-", cluster[[i]], "-", var_string)

    if (!is.null(scale_name[[i]])) {
      filename <- paste0(filename, "-", scale_name[[i]])
    }

    if (std) {
      filename <- paste0(filename, "-std")
    }

    filename <- paste0(filename, ".png")

    ggsave(
      filename,
      plot,
      dpi = 300,
      bg = "white"
    )
  })
}

fs::dir_create("02_output/plots/line_plots_std/")
fs::dir_create("02_output/plots/line_plots/")
fs::dir_create("02_output/plots/stacked_bar_plots/")
fs::dir_create("02_output/plots/bar_plots/")

# Aplicar la función a cada una de las últimas cuatro columnas
save_plots(
  gbmt_fit_plots$line_plot_std, 
  "02_output/plots/line_plots_std/lineplot",
  gbmt_fit_final$x_names, 
  gbmt_fit_final$ng, 
  gbmt_fit_final$scale_name, 
  std = TRUE
)

save_plots(
  gbmt_fit_plots$line_plot, 
  "02_output/plots/line_plots/lineplot", 
  gbmt_fit_final$x_names, 
  gbmt_fit_final$ng, 
  gbmt_fit_final$scale_name
)

save_plots(
  gbmt_fit_plots$stacked_bar_plot,
   "02_output/plots/stacked_bar_plots/stackedbarplot", 
   gbmt_fit_final$x_names, 
   gbmt_fit_final$ng,
   gbmt_fit_final$scale_name
)

save_plots(
  gbmt_fit_plots$bar_plot,
  "02_output/plots/bar_plots/barplot",
  gbmt_fit_final$x_names, 
  gbmt_fit_final$ng,
  gbmt_fit_final$scale_name
)

Generación de mapas

En esta sección se unificarán las geometrías de las ciudades con la información de la columna City que ahora mismo viene de los datos poblacionales. Actualmente se está usando geometrías proporcionadas por geodata::gadm() sin embargo, no se está teniendo coincidencias adecuadas en las ciudades. Esto debería modificarse de acuerdo a como tengan las geometrías almacenadas.

cities_l1ad_noislands %>% plot()

cities_l1ad_noislands 

cities_l1ad %>% plot()
library(sf)
get_shp <- function(ISO2) {
  # Obtener shp_1
  shp_0 <- geodata::gadm(
    ISO2,
    path = "01_data/raw/shp/",
    level = 0
  ) %>%
    st_as_sf()
  
  # Obtener shp_1
  shp_1 <- geodata::gadm(
    ISO2,
    path = "01_data/raw/shp/",
    level = 1
  ) %>%
    st_as_sf()

  # Obtener shp_2 y aplicar las transformaciones
  shp_2 <- cities_l1ad_noislands %>%
    filter(COUNTRYAB == ISO2) %>%
    janitor::clean_names()

  # shp_2 <- geodata::gadm(
  #   ISO2,
  #   path = "01_data/raw/shp/",
  #   level = 2
  # ) %>%
  #   st_as_sf() %>%
  #   mutate(
  #     NAME_2 = str_to_lower(NAME_2),
  #     NAME_2 = case_when(
  #       str_detect(NAME_2, "capital") ~ str_to_lower(NAME_1),
  #       TRUE ~ NAME_2
  #     )
  #   )

  # Devolver la lista con shp_1 y shp_2
  list(
    shp_0 = shp_0,
    shp_1 = shp_1,
    shp_2 = shp_2
  )
}


shp_list <- unique(gbmt_fit_final$gbmt_fit_total[[1]]$ISO2) %>%
  append(
    c("GUY", "SUR", "BOL", "PRY",
      "URY", "ECU", "BLZ", "HND",
      "TTO", "VEN")
  ) %>% 
  set_names() %>%
  map(get_shp)

Creamos una función para unr los datos y los shapefiles

join_data_shp <- function(data, shp_list) {
  # Proceso de unión con shapefiles
  data %>%
    filter(year == 2015) %>%
    mutate(
      pubsalid1 = as.numeric(as.character(pubsalid1)),
      # City = str_to_lower(City),
      group = factor(group)
    ) %>%
    group_nest(ISO2, country) %>%
    drop_na(ISO2) %>%
    mutate(
      country = str_to_title(country),
      shp_1 = map(ISO2, ~ shp_list[[.x]]$shp_1),
      shp_2 = map(ISO2, ~ shp_list[[.x]]$shp_2),
      shp_inner = map2(
        data,
        shp_2,
        ~ inner_join(
          .x,
          .y,
          by = join_by(pubsalid1)
        ) %>%
          st_as_sf()
      )
    )
}

El cual usaremos a continuación:

gbmt_shp <- map(
  gbmt_fit_final$gbmt_fit_total,
  ~ join_data_shp(.x, shp_list)
)

Se espera que gbmt_shp sea una lista de n tamaño cantidad de modelos se hayan estimado. Por ej. gbmt_shp[[1]] tendría la siguiente estructura:

Y ahora procederemos con la función encargada de la generación de los mapas que tienen a su vez correcciones específicas con fines de visualización en algunos países:

plot_country_map <- function(country_data) {
  p <- ggplot(country_data$shp_inner[[1]]) +
    geom_sf(
      data = country_data$shp_1[[1]],
      lwd = 0.10
    ) +
    geom_sf(aes(fill = group)) +
    scale_fill_discrete(drop = FALSE) +
    labs(
      title = country_data$country,
      fill = "Cluster"
    ) +
    theme_minimal()

  # Si el país es Chile, ajustar los límites del gráfico
  if (country_data$country == "Chile") {
    xlim <- c(-90, -55) # Longitud
    ylim <- c(-56, -17) # Latitud
    p <- p + coord_sf(xlim = xlim, ylim = ylim) +
      scale_x_continuous(breaks = seq(-90, -50, by = 10))
  } else if (country_data$country == "Costa Rica") {
    xlim <- c(-86, -82.5) # Longitud
    ylim <- c(11.5, 8) # Latitud
    p <- p + coord_sf(xlim = xlim, ylim = ylim) +
      scale_x_continuous(breaks = seq(-86, -80, by = 10))
  } else if (country_data$country == "Colombia") {
    xlim <- c(-80, -65) # Longitud
    ylim <- c(15, -5) # Latitud
    p <- p + coord_sf(xlim = xlim, ylim = ylim) +
      scale_x_continuous(breaks = seq(-80, -65, by = 5))
  } else if (country_data$country == "Argentina") {
    xlim <- c(-82, -45) # Longitud
    p <- p + coord_sf(xlim = xlim) +
      scale_x_continuous(breaks = seq(-80, -40, by = 10))
  } else if (country_data$country == "Peru") {
    xlim <- c(-82, -68) # Longitud
    p <- p + coord_sf(xlim = xlim) +
      scale_x_continuous(breaks = seq(-80, -68, by = 4))
  }

  return(p)
}

# Función para mapear sobre los países de un modelo específico
plot_model_countries <- function(model_data) {
  map(
    model_data$ISO2,
    ~ plot_country_map(
      subset(model_data, ISO2 == .)
    )
  )
}

Y se aplica las funciones para cada uno de los modelos de gbmt_shp:

maps_models_countries <- map(
  gbmt_shp,
  plot_model_countries
)

Finalmente con la ayuda del paquete patchwork se unifican los gráficos por modelos:

final_plots <- list()

# Iterar sobre cada modelo en maps_models_countries
for (j in 1:length(maps_models_countries)) {
  # Obtener los gráficos del modelo actual
  current_graphs <- maps_models_countries[[j]]

  # Combinar los gráficos del modelo actual
  combined_plot <- current_graphs[[1]]
  for (i in 2:length(current_graphs)) {
    combined_plot <- combined_plot + current_graphs[[i]]
  }

  # Especificar el diseño y unificar las leyendas
  final_plot <- combined_plot +
    plot_layout(
      ncol = 4,
      nrow = 3,
      guides = "collect",
      widths = c(1, 1, 1, 1),
      heights = c(1, 1, 1)
    )

  # Almacenar el final_plot en la lista final_plots
  final_plots[[j]] <- final_plot
}

Se crea una función para guardar los gráficos de los modelos en función del nombre de su análisis:

# Función para guardar cada gráfico
save_maps <- function(plot, index, cluster, x_names, scale_name) {
  # Crear el nombre base con las variables
  var_names <- str_replace_all(x_names[[index]], "_imp", "")
  var_string <- str_c(var_names, collapse = "-")


  filename <- paste0(
    "02_output/plots/maps/mapa-cluster-",
    cluster[[index]],
    "-",
    var_string,
    "-",
    scale_name[[index]],
    ".png"
  )

  ggsave(
    filename,
    plot,
    dpi = 600,
    bg = "white",
    width = 16,
    height = 8,
    device = grDevices::png
  )
}

Y por último, aplicamos la función a cada uno de los gráficos de final_plots:

fs::dir_create("02_output/plots/maps/")

walk2(
  final_plots,
  seq_along(final_plots),
  save_maps,
  cluster = gbmt_fit_final$ng,
  x_names = gbmt_fit_final$x_names,
  scale_name = gbmt_fit_final$scale_name
)

Mapa con puntos

join_data_shp2 <- function(data, shp_list) {
  # Proceso de unión con shapefiles
  data %>%
    mutate(
      pubsalid1 = as.numeric(as.character(pubsalid1)),
      # City = str_to_lower(City),
      group = factor(group)
    ) %>%
    group_nest(ISO2, country) %>%
    drop_na(ISO2) %>%
    mutate(
      country = str_to_title(country),
      shp_1 = map(ISO2, ~ shp_list[[.x]]$shp_1),
      shp_2 = map(ISO2, ~ shp_list[[.x]]$shp_2),
      shp_inner = map2(
        data,
        shp_2,
        ~ inner_join(
          .x,
          .y,
          by = join_by(pubsalid1)
        ) %>%
          st_as_sf()
      )
    )
}
gbmt_interest <- gbmt_fit_final %>%
  filter(
    scale_name == "logarithmic",
    ng == 4
  ) %>%
  slice(1)

# Calcula el delta año tras año para cada ciudad
gbmt_interest$gbmt_fit_total[[1]] <- gbmt_interest$gbmt_fit_total[[1]] %>%
  arrange(pubsalid1, year) %>%
  group_by(ISO2, country, group, pubsalid1) %>%
  mutate(
    perc_population_imp = (population_imp - lag(population_imp))/lag(population_imp),
    perc_bectuareal1ux_imp = (bectuareal1ux_imp - lag(bectuareal1ux_imp))/lag(bectuareal1ux_imp),
    perc_population_imp_norm = (population_imp_norm - lag(population_imp_norm))/lag(population_imp_norm),
    perc_bectuareal1ux_imp_norm = (bectuareal1ux_imp_norm - lag(bectuareal1ux_imp_norm))/lag(bectuareal1ux_imp_norm)
  ) %>%
  summarise(
    mean_perc_population = mean(perc_population_imp, na.rm = TRUE),
    mean_perc_bectuareal = mean(perc_bectuareal1ux_imp, na.rm = TRUE),
    mean_perc_population_norm = mean(perc_population_imp_norm, na.rm = TRUE),
    mean_perc_bectuareal_norm = mean(perc_bectuareal1ux_imp_norm, na.rm = TRUE)
  ) %>%
  ungroup() %>%
  mutate(
    tercile_perc_population = ntile(mean_perc_population, 3),
    tercile_perc_bectuareal = ntile(mean_perc_bectuareal, 3),
    tercile_perc_population_norm = ntile(mean_perc_population_norm, 3),
    tercile_perc_bectuareal_norm = ntile(mean_perc_bectuareal_norm, 3)
  )
 
gbmt_shp_interest <- map(
  gbmt_interest$gbmt_fit_total,
  ~ join_data_shp2(.x, shp_list)
)
country_data <- map(shp_list, 1) %>% 
  bind_rows() %>% 
  mutate(
    study = c(rep(TRUE, 11), rep(FALSE, 10))
  )

countries_by_study <- country_data %>% 
  as_tibble() %>% 
  select(GID_0, study)

level1_data <- map(shp_list, 2) %>% 
  bind_rows() %>% 
  left_join(countries_by_study)
# country_data <- gbmt_shp_interest[[1]] %>%
#   select(ISO2, country, shp_1) %>%
#   unnest(cols = c(shp_1)) %>%
#   st_as_sf()

city_data <- gbmt_shp_interest[[1]] %>%
  select(ISO2, country, shp_inner) %>%
  unnest(cols = shp_inner) %>%
  st_as_sf() %>%
  st_centroid()
library(biscale)
city_data_biscale <- bi_class(city_data,
  x = mean_perc_population,
  y = mean_perc_bectuareal,
  style = "quantile", dim = 3
)

custom_pal_teal <- c(
  "1-1" = "#b2d8d8", # teal green for low x, low y
  "2-1" = "#ba8890",
  "3-1" = "#9e3547",
  "1-2" = "#8aa6c2",
  "2-2" = "#7a6b84",
  "3-2" = "#682a41",
  "1-3" = "#4279b0",
  "2-3" = "#3a4e78",
  "3-3" = "#311e3b"
)

ggmap <- level1_data %>%
  ggplot() +
  geom_sf(
    aes(fill = study),
    linewidth = 0.05
  ) +
  scale_fill_manual(
    values = c("#bdbdbd", "#e5e5e5"),
    guide = "none"
  ) + 
  geom_sf(
    data = country_data,
    linewidth = 0.35, 
    color = "#3f3f3f",
    fill = NA
  ) +
  geom_sf(
    data = city_data_biscale,
    mapping = aes(color = bi_class),
    #color = "white", 
    size = 2.5,
    show.legend = FALSE
  ) +
  bi_scale_color(pal = custom_pal_teal, dim = 3) +
  # bi_scale_color(pal = "GrPink", dim = 3) +
  bi_theme()

legend <- bi_legend(
  pal = custom_pal_teal,
  dim = 3,
  xlab = "Higher Δ% Population",
  ylab = "Higher Δ% Urban Area",
  size = 6
)

library(cowplot)

finalPlot <- ggdraw(ggmap) +
  # draw_plot(ggmap, 0, 0, 1.5, 1.5) +
  draw_plot(legend, 0.15, 0.25, 0.25, 0.25)

ggsave("ggmap.png",
       finalPlot,
       height = 7,
       width = 7,
       dpi = 600)
ggmap2 <- country_data %>%
  group_by(country) %>%
  summarise() %>%
  ggplot() +
  geom_sf(fill = "white") +
  geom_sf(
    data = city_data_biscale,
    mapping = aes(color = bi_class),
    # color = "white",
    size = 2.5,
    show.legend = FALSE
  ) +
  bi_scale_color(pal = "GrPink", dim = 3) +
  bi_theme()

legend <- bi_legend(
  pal = "GrPink",
  dim = 3,
  xlab = "Higher Δ Population",
  ylab = "Higher Δ Urban Area",
  size = 6
)

library(cowplot)

finalPlot <- ggdraw() +
  draw_plot(ggmap, 0, 0, 1, 1) +
  draw_plot(legend, 0.1, .28, 0.25, 0.25)

ggsave("ggmap.png",
  finalPlot,
  dpi = 600
)

网站公告

今日签到

点亮在社区的每一天
去签到