Lectura listado datasets del Banco Mundial (wb.datasets)

# LECTURA DATOS BÁSICOS DATASETS DISPONIBLES DEL WORLD BANK 
# Cargar la caché actual del paquete
# (Opcional: puedes actualizarla a la versión más reciente en línea con new_cache <- wb_cache())
new_cache <- wb_cache()
wb.datasets <- wb_cachelist$indicators |> as_tibble() |>
  as_tibble() |> 
  mutate(topics=unlist(lapply(topics, function(df) ifelse(is.null(df$value),NA,df$value)))) |>
  mutate(topics=sub(" $", "", topics)) |>
  select(Topic=topics,Id=indicator_id,Indicator=indicator,Indicator_desc=indicator_desc)

wb.datasets
## # A tibble: 16,643 × 4
##    Topic   Id                   Indicator                         Indicator_desc
##    <chr>   <chr>                <chr>                             <chr>         
##  1 Poverty 1.0.HCount.1.90usd   Poverty Headcount ($1.90 a day)   The poverty h…
##  2 Poverty 1.0.HCount.2.5usd    Poverty Headcount ($2.50 a day)   The poverty h…
##  3 Poverty 1.0.HCount.Mid10to50 Middle Class ($10-50 a day) Head… The poverty h…
##  4 Poverty 1.0.HCount.Ofcl      Official Moderate Poverty Rate-N… The poverty h…
##  5 Poverty 1.0.HCount.Poor4uds  Poverty Headcount ($4 a day)      The poverty h…
##  6 Poverty 1.0.HCount.Vul4to10  Vulnerable ($4-10 a day) Headcou… The poverty h…
##  7 Poverty 1.0.PGap.1.90usd     Poverty Gap ($1.90 a day)         The poverty g…
##  8 Poverty 1.0.PGap.2.5usd      Poverty Gap ($2.50 a day)         The poverty g…
##  9 Poverty 1.0.PGap.Poor4uds    Poverty Gap ($4 a day)            The poverty g…
## 10 Poverty 1.0.PSev.1.90usd     Poverty Severity ($1.90 a day)    The poverty s…
## # ℹ 16,633 more rows

Opciones del campo Topic de wb.datasets

unique(wb.datasets$Topic)
##  [1] "Poverty"                          NA                                
##  [3] "Education"                        "Social Protection & Labor"       
##  [5] "Economy & Growth"                 "Agriculture & Rural Development "
##  [7] "Climate Change"                   "Urban Development"               
##  [9] "External Debt"                    "Financial Sector"                
## [11] "Aid Effectiveness"                "Millenium development goals"     
## [13] "Environment"                      "Energy & Mining"                 
## [15] "Infrastructure"                   "Public Sector"                   
## [17] "Science & Technology"             "Private Sector"                  
## [19] "Trade"                            "Gender"                          
## [21] "Health"

DESCARGAMOS TODOS LOS DATASETS PARA OBTENER INFORMACIÓN ADICIONAL

# FUNCIÓN PARA DESCARGAR UN DATASET A PARTIR DE SU INDICADOR 
wb.data.download <- function(codigo_indicador, paises = "all", inicio = 1900, fin = 2100, max_intentos = 3, tiempo_espera_seg = 1) {
  
  for (intento in 1:max_intentos) {
    datos <- tryCatch({
      # Intento de descarga
      resultado <- wb_data(indicator = codigo_indicador, country = paises, start_date = inicio, end_date = fin)
      return(resultado) # Si tiene éxito, sale de la función y devuelve los datos
      
    }, error = function(e) {
      # Si hay un error:
      return(NULL) # Devuelve NULL temporalmente para permanecer en el bucle
    })
    
    # Si 'datos' no es NULL, significa que la descarga fue exitosa y ya se retornó dentro del tryCatch.
    
    # Si 'datos' es NULL (hubo un error) y no es el último intento, espera un momento.
    if (is.null(datos) && intento < max_intentos) {
      Sys.sleep(tiempo_espera_seg) # Pausa la ejecución
    }
  }
  
  # Si el bucle termina sin éxito después de todos los intentos:
  return(NULL)
}

# --- CONTROL DE CUELGUES ----------------------------------------------------
# El recorrido son miles de llamadas a la API y puede quedarse esperando
# indefinidamente en una de ellas. Dos medidas:
#
# 1) Se acota el tiempo de cualquier descarga, para que una llamada colgada
#    termine en error (que la lógica de reintentos ya sabe manejar) en vez de
#    bloquear el documento entero.
# 2) Se deja constancia del avance en un fichero .log con marca de tiempo. Al
#    compilar con quiet=TRUE no se ve NADA por consola -los print() van al
#    documento, que no existe hasta el final-, así que sin este log no hay forma
#    de saber si el proceso avanza o está colgado. El fichero se puede abrir
#    mientras compila: la última línea dice por qué dataset va y cuánto tardó.
options(timeout = 60)                                    # conexiones de R base
if(requireNamespace("httr", quietly = TRUE)){
  httr::set_config(httr::timeout(60))                    # llamadas de wbstats
}

LOG <- "WORLD_BANK_TableDatasetGeneration.log"
cat(format(Sys.time()), "- inicio del recorrido\n", file = LOG)
log_avance <- function(k, n, id, seg){
  cat(sprintf("%s  %5d/%5d  %-28s %6.1f s%s\n",
              format(Sys.time()), k, n, id, seg,
              if(seg > 30) "   <-- LENTO" else ""),
      file = LOG, append = TRUE)
}

K <- as.numeric()
N.reg <- as.numeric()
DIM <- as.character()
dateStart <- as.character()
dateEnd <- as.character()
Ndates <- as.numeric()
Ncountries <- as.numeric()
last_updated <- as.character()

for(k in 1:nrow(wb.datasets)){
#for(k in 1:100){
  Id <- wb.datasets$Id[k]
  #print(paste(k,Id))
  t.inicio <- Sys.time()
  data <- wb.data.download(Id)
  log_avance(k, nrow(wb.datasets), Id,
             as.numeric(difftime(Sys.time(), t.inicio, units = "secs")))
  # if (!"iso3c" %in% names(data)) stop("El campo 'iso3c' no existe en el tibble")
  # if (!"country" %in% names(data)) stop("El campo 'country' no existe en el tibble")
  # if (!"date" %in% names(data)) stop("El campo 'date' no existe en el tibble")
  for(col in colnames(data)){
    if(is.numeric(data[[col]])){
      data <- data |> filter(! is.na(!!sym(col)))
    }
  }
  if(is.null(data)){
    N.reg <- c(N.reg,0)
    DIM <- c(DIM,"*")
    K <- c(K,k)
    dateStart <- c(dateStart, NA)
    dateEnd <- c(dateEnd, NA)
    Ndates <- c(Ndates,0)
    Ncountries <- c(Ncountries,0)
    last_updated <- c(last_updated, NA)
    next
  }
  N.reg <- c(N.reg,nrow(data))
  K <- c(K,k)
  dim <- ""
  dateStart <- c(dateStart, NA)
  dateEnd <- c(dateEnd, NA)
  Ndates <- c(Ndates,0)
  Ncountries <- c(Ncountries,0)
  last_updated <- c(last_updated, NA)
  for(col in colnames(data)){
    if(is.numeric(data[[col]]) & col!="date") next
    if(col=="date"){
      dateStart[length(dateStart)] <- min(data[[col]])
      dateEnd[length(dateStart)] <- max(data[[col]])
      Ndates[length(dateStart)] <- length(unique(data[[col]]))
      next
    }
    if(col=="country"){
      Ncountries[length(dateStart)] <- length(unique(data[[col]]))
      next
    }
    if(col=="last_updated"){
      last_updated[length(dateStart)] <-  as.character(data[[col]][1])
      next
    }
    
    N <- length(unique(data[[col]]))
    dim <- paste0(dim,substr(col,1,20)," ",N, " - ")
  }
  #print(paste(nrow(data),dim))
  DIM <- c(DIM,substr(dim,1,nchar(dim)-3))
  if(k %% 100 == 0){
    tibble(
      Nº = K,
      Ncountries = Ncountries,
      N.reg = N.reg,
      dateStart=dateStart,
      dateEnd=dateEnd,
      Ndates=Ndates,
      last_updated=last_updated,
      DIM = DIM
    ) |> write.xlsx("wb.datasets.temporal.xlsx")
    
  }
  
  
}

tibble(
      Nº = K,
      Ncountries = Ncountries,
      N.reg = N.reg,
      dateStart=dateStart,
      dateEnd=dateEnd,
      Ndates=Ndates,
      last_updated=last_updated,
      DIM = DIM
    ) |> write.xlsx("wb.datasets.temporal.xlsx")

# El recorrido ha llegado al final, así que el guardado parcial ya no sirve para
# nada: se borra para no dejar en la carpeta un fichero a medias que se pueda
# confundir con el catálogo bueno. Si el recorrido se interrumpe, este borrado no
# se ejecuta y el .temporal sobrevive, que es justo para lo que está.
if(file.exists("wb.datasets.temporal.xlsx")) file.remove("wb.datasets.temporal.xlsx")
## [1] TRUE
wb.datasets <- wb.datasets |> 
  cbind(
  tibble(
        Nº = K,
        Ncountries = Ncountries,
        N.reg = N.reg,
        dateStart=dateStart,
        dateEnd=dateEnd,
        Ndates=Ndates,
        last_updated=last_updated,
        DIM = DIM
      )
  ) |>
  relocate(Nº) |>
  relocate(Ndates,.after=Indicator)|>
  relocate(dateEnd,.after=Indicator)|>
  relocate(dateStart,.after=Indicator)|>
  relocate(N.reg,.after=Indicator)|>
  relocate(Ncountries,.after=Indicator)|>
  rename(N.obs=N.reg)|>
  rename(ANOTHER.DIMENSIONS=DIM)

Escritura metadatos de los datasets antes de filtrar en wb.datasets.raw.xlsx

wb.datasets |>
  write.xlsx("wb.datasets.raw.xlsx")

Escritura metadatos de los datasets filtrados en wb.datasets.xlsx

wb.datasets.filtered <- wb.datasets |>
  filter(Ncountries>=100,Ndates>=10) |>
  filter(as.numeric(substr(dateEnd,1,4))>=2022) 

wb.datasets.filtered|>
  mutate(Nº=1:nrow(wb.datasets.filtered)) |> 
  write.xlsx("wb.datasets.xlsx")