1. Instalación de Paquetes

Se inició usando el paquete pacman para poder gestionar mejor las librerias al momento de cargas los paquetes. Este paquete resultó ser una herramienta esencial para mantener un sistema actualizado de manera eficiente y sencilla.

if (!require("pacman")) install.packages("pacman")
pacman::p_load(
  # estos son usados en la manipulación de datos
  dplyr, tidyr, purrr, data.table,
  # los paquetes para Visualizar los datos
  ggplot2, corrplot, visdat, naniar, patchwork,
  # análisis 
  outliers, EnvStats, moments,
  # imputación
  mice, VIM,
  # lectura de datos
  readr, httr
)

2. Función para carga de Datos

Para descargar y procesar los datos de temperatura global directamente de la NASA y asegurando una completa compatibilidad ante posibles cambios en el formato, se definió la base de datos como una función y utilizando el comando (tryCatch) se manejaron los errores.

get_nasa_temp_data <- function() {
  url <- "https://data.giss.nasa.gov/gistemp/tabledata_v4/T_AIRS/GLB.Ts+dSST.txt"
  
  tryCatch({
    temp_file <- tempfile()
    download.file(url, temp_file, mode = "wb", quiet = TRUE)
    all_lines <- readLines(temp_file)
    data_lines <- all_lines[grep("^\\s*[0-9]{4}", all_lines)]
    
    temp_data <- data.table::fread(
      text = paste(data_lines, collapse = "\n"),
      header = FALSE,
      na.strings = c("****", "***", "NA", ""),
      colClasses = list(character = 1)
    )
    
    month_names <- c("Jan", "Feb", "Mar", "Apr", "May", "Jun", 
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
    
    if (ncol(temp_data) == 1) {
      names(temp_data) <- "Year"
    } else if (ncol(temp_data) <= 13) {
      names(temp_data) <- c("Year", month_names[1:(ncol(temp_data)-1)])
    } else {
      names(temp_data) <- c("Year", month_names, paste0("V", 14:ncol(temp_data)))
    }
    
    temp_data <- as.data.frame(temp_data) %>%
      select(-starts_with("V")) %>%
      mutate(Year = as.integer(Year)) %>%
      filter(!is.na(Year)) %>%
      select(where(~!all(is.na(.))))
    
    return(temp_data)
    
  }, error = function(e) {
    message("Error en método principal: ", e$message)
    message("Intentando alternativa...")
    
    tryCatch({
      response <- httr::GET(url)
      content <- httr::content(response, "text", encoding = "UTF-8")
      lines <- unlist(strsplit(content, "\n"))
      data_lines <- lines[grepl("^\\s*[0-9]{4}", lines)]
      
      temp_data <- read_table(
        paste(data_lines, collapse = "\n"),
        col_names = FALSE,
        na = c("****", "***", "NA", ""),
        col_types = cols(X1 = col_integer(), .default = col_double())
      )
      
      names(temp_data) <- c("Year", month.abb[1:(ncol(temp_data)-1)])
      return(temp_data)
      
    }, error = function(e) {
      message("Error en método alternativo: ", e$message)
      return(data.frame(Year = integer()))
    })
  })
}

3. Carga y verificación de datos

Se tienen que comprobar que los datos hayan cargado correctamente y que el rango y columnas sean coherentes.

  • Validación de que haya registros (nrow(temp_data) > 0)
cat("Cargando datos de la NASA")
## Cargando datos de la NASA
temp_data <- get_nasa_temp_data()

if (nrow(temp_data) == 0) {
  stop("No se pudieron cargar los datos. Verifique conexión o formato.")
}

cat(" Datos fueron cargados correctamente")
##  Datos fueron cargados correctamente
cat("Periodo:", range(temp_data$Year), "\n")
## Periodo: 2002 2025
cat("Columnas:", names(temp_data), "\n\n")
## Columnas: Year Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

4. Analisis exploratorio

4.1 Estadisticas descriptivas

El objetivo de este analisis exploratorio es obtener una visión mas generalizada de la distribución de temperaturas por mes, para esto se realizó lo siguiente:

  • Cálculo de media, desviación estándar, mediana, mínimo, máximo, asimetría (skewness) y cantidad de valores faltantes (NA) por mes.

  • Uso de summarise(across(...)) para aplicar las métricas a todas las columnas de temperatura.

desc_stats <- temp_data %>%
  select(-Year) %>%
  summarise(across(
    everything(),
    list(
      Mean = ~mean(., na.rm = TRUE),
      SD = ~sd(., na.rm = TRUE),
      Median = ~median(., na.rm = TRUE),
      Min = ~min(., na.rm = TRUE),
      Max = ~max(., na.rm = TRUE),
      Skew = ~moments::skewness(., na.rm = TRUE),
      NAs = ~sum(is.na(.))
    ),
    .names = "{col}_{fn}"
  ))

print(t(desc_stats))
##                   [,1]
## Jan_Mean    10.2714286
## Jan_SD      21.6379440
## Jan_Median  10.0000000
## Jan_Min    -43.0000000
## Jan_Max     64.0000000
## Jan_Skew    -0.1099284
## Jan_NAs      2.0000000
## Feb_Mean    12.5000000
## Feb_SD      25.2367055
## Feb_Median  12.0000000
## Feb_Min    -34.0000000
## Feb_Max     76.0000000
## Feb_Skew     0.3879957
## Feb_NAs      2.0000000
## Mar_Mean    11.5571429
## Mar_SD      23.9405276
## Mar_Median   9.0000000
## Mar_Min    -40.0000000
## Mar_Max     60.0000000
## Mar_Skew     0.2900997
## Mar_NAs      2.0000000
## Apr_Mean     6.9714286
## Apr_SD      19.1386788
## Apr_Median   7.0000000
## Apr_Min    -27.0000000
## Apr_Max     57.0000000
## Apr_Skew     0.4306791
## Apr_NAs      2.0000000
## May_Mean     3.8571429
## May_SD      17.6645766
## May_Median   5.5000000
## May_Min    -45.0000000
## May_Max     43.0000000
## May_Skew    -0.2055614
## May_NAs      2.0000000
## Jun_Mean     7.0857143
## Jun_SD      17.2280046
## Jun_Median   3.0000000
## Jun_Min    -25.0000000
## Jun_Max     53.0000000
## Jun_Skew     0.4446120
## Jun_NAs      2.0000000
## Jul_Mean     6.8571429
## Jul_SD      19.8452606
## Jul_Median   3.0000000
## Jul_Min    -40.0000000
## Jul_Max     54.0000000
## Jul_Skew     0.3169704
## Jul_NAs      2.0000000
## Aug_Mean     4.7164179
## Aug_SD      18.6870608
## Aug_Median   3.0000000
## Aug_Min    -33.0000000
## Aug_Max     58.0000000
## Aug_Skew     0.5365766
## Aug_NAs      5.0000000
## Sep_Mean     5.9275362
## Sep_SD      19.4318711
## Sep_Median   2.0000000
## Sep_Min    -25.0000000
## Sep_Max     76.0000000
## Sep_Skew     1.1149175
## Sep_NAs      3.0000000
## Oct_Mean     6.5507246
## Oct_SD      18.1355486
## Oct_Median   7.0000000
## Oct_Min    -24.0000000
## Oct_Max     59.0000000
## Oct_Skew     0.7130196
## Oct_NAs      3.0000000
## Nov_Mean     2.0724638
## Nov_SD      20.8455940
## Nov_Median   0.0000000
## Nov_Min    -58.0000000
## Nov_Max     64.0000000
## Nov_Skew     0.2054810
## Nov_NAs      3.0000000
## Dec_Mean     7.4057971
## Dec_SD      21.1467692
## Dec_Median   7.0000000
## Dec_Min    -25.0000000
## Dec_Max     69.0000000
## Dec_Skew     0.6798749
## Dec_NAs      3.0000000

4.2 Visualización de datos faltantes

En esta sección se identifican los valores ausentes:

  • Uso de vis_miss() para generar un gráfico que muestra la distribución de NA por variable y observación.
vis_miss(temp_data) + 
  ggtitle("Distribución de Valores Faltantes") +
  theme(plot.title = element_text(hjust = 0.5))

4.3 Distribucion por mes

Se observa la forma de distribución de anomalias de temperatura por mes:

  • Gráficos de densidad (geom_density) clasificados por mes para comparar las distribuciones.
temp_data %>%
  pivot_longer(-Year, names_to = "Month", values_to = "Temp") %>%
  ggplot(aes(x = Temp, fill = Month)) +
  geom_density(alpha = 0.6) +
  facet_wrap(~Month) +
  labs(title = "Distribución de Temperaturas por Mes",
       x = "Anomalía de Temperatura (°C)") +
  theme_minimal() +
  theme(legend.position = "none")

5. Detección de valores atípicos

En esta sección se detectan los posibles outliers o registros extremos que puedan influir en el analisis realizando:

  • Creación de detect_outliers() que:
    • Aplica test de Grubbs para detectar un outlier extremo.

    • Calcula límites de Hampel para detectar valores fuera de rango (±3 MAD).

    • Registra el número y porcentaje de valores fuera de estos límites.

detect_outliers <- function(data) {
  outliers_list <- list()
  
  for (col in names(data)[-1]) {
    x <- data[[col]]
    x <- x[!is.na(x)]
    
    grubbs_test <- tryCatch(grubbs.test(x), error = function(e) NULL)
    
    hampel_lim <- median(x) + 3 * mad(x) * c(-1, 1)
    outliers <- x < hampel_lim[1] | x > hampel_lim[2]
    
    outliers_list[[col]] <- list(
      Grubbs = grubbs_test,
      Hampel_Limits = hampel_lim,
      Outlier_Count = sum(outliers),
      Outlier_Pct = mean(outliers) * 100
    )
  }
  return(outliers_list)
}

outliers_analysis <- detect_outliers(temp_data)
walk(names(outliers_analysis), ~{
  cat(.x, ":\n")
  print(outliers_analysis[[.x]][1:3])
  cat("\n")
})
## Jan :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.48307, U = 0.90935, p-value = 0.3961
## alternative hypothesis: highest value 64 is an outlier
## 
## 
## $Hampel_Limits
## [1] -38.9258  58.9258
## 
## $Outlier_Count
## [1] 3
## 
## 
## Feb :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.51618, U = 0.90691, p-value = 0.3578
## alternative hypothesis: highest value 76 is an outlier
## 
## 
## $Hampel_Limits
## [1] -61.3887  85.3887
## 
## $Outlier_Count
## [1] 0
## 
## 
## Mar :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.15355, U = 0.93181, p-value = 1
## alternative hypothesis: lowest value -40 is an outlier
## 
## 
## $Hampel_Limits
## [1] -75.5082  93.5082
## 
## $Outlier_Count
## [1] 0
## 
## 
## Apr :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.61400, U = 0.89954, p-value = 0.2628
## alternative hypothesis: highest value 57 is an outlier
## 
## 
## $Hampel_Limits
## [1] -59.717  73.717
## 
## $Outlier_Count
## [1] 0
## 
## 
## May :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.76583, U = 0.88753, p-value = 0.1589
## alternative hypothesis: lowest value -45 is an outlier
## 
## 
## $Hampel_Limits
## [1] -52.3214  63.3214
## 
## $Outlier_Count
## [1] 0
## 
## 
## Jun :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.66510, U = 0.89557, p-value = 0.2226
## alternative hypothesis: highest value 53 is an outlier
## 
## 
## $Hampel_Limits
## [1] -32.5824  38.5824
## 
## $Outlier_Count
## [1] 4
## 
## 
## Jul :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.37552, U = 0.91703, p-value = 0.5461
## alternative hypothesis: highest value 54 is an outlier
## 
## 
## $Hampel_Limits
## [1] -57.0453  63.0453
## 
## $Outlier_Count
## [1] 0
## 
## 
## Aug :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.85136, U = 0.87495, p-value = 0.1115
## alternative hypothesis: highest value 58 is an outlier
## 
## 
## $Hampel_Limits
## [1] -41.478  47.478
## 
## $Outlier_Count
## [1] 1
## 
## 
## Sep :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 3.60606, U = 0.80596, p-value = 0.005242
## alternative hypothesis: highest value 76 is an outlier
## 
## 
## $Hampel_Limits
## [1] -51.3736  55.3736
## 
## $Outlier_Count
## [1] 2
## 
## 
## Oct :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.89207, U = 0.87519, p-value = 0.1002
## alternative hypothesis: highest value 59 is an outlier
## 
## 
## $Hampel_Limits
## [1] -55.2692  69.2692
## 
## $Outlier_Count
## [1] 0
## 
## 
## Nov :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.9708, U = 0.8683, p-value = 0.0752
## alternative hypothesis: highest value 64 is an outlier
## 
## 
## $Hampel_Limits
## [1] -44.478  44.478
## 
## $Outlier_Count
## [1] 3
## 
## 
## Dec :
## $Grubbs
## 
##  Grubbs test for one outlier
## 
## data:  x
## G = 2.9127, U = 0.8734, p-value = 0.09302
## alternative hypothesis: highest value 69 is an outlier
## 
## 
## $Hampel_Limits
## [1] -55.2692  69.2692
## 
## $Outlier_Count
## [1] 0

6. Imputación de Valores Faltantes

Este paso se hace para completar los datos ausentes para poder hacer un mejor analisis:

  • Comprobación de si existen NA
  • Aplicación de tres métodos de imputación (pmm, norm.predict, mean)con mice.
  • Finalizar con una visualización de la distribución de las temperaturas imputadas en cada método.
if (sum(is.na(temp_data)) > 0) {
  methods <- c("pmm", "norm.predict", "mean")
  
  imputed_data <- map(methods, ~{
    imp <- mice(temp_data, method = .x, m = 1, maxit = 5, seed = 123)
    complete(imp)
  })
  
  map2(imputed_data, methods, ~{
    .x %>%
      pivot_longer(-Year, names_to = "Month", values_to = "Temp") %>%
      ggplot(aes(x = Temp)) +
      geom_histogram(bins = 30, fill = "steelblue") +
      facet_wrap(~Month, scales = "free") +
      ggtitle(paste("Distribución después de imputación -", .y)) +
      theme_minimal()
  })
}
## 
##  iter imp variable
##   1   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   2   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   3   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   4   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   5   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
## 
##  iter imp variable
##   1   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   2   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   3   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   4   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   5   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
## 
##  iter imp variable
##   1   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   2   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   3   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   4   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
##   5   1  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
## [[1]]

## 
## [[2]]

## 
## [[3]]

7. Analisis de tendencias

Analizar la evoución de la T media anual a lo largo del tiempo, con:

  • Cálculo de promedio anual (rowMeans)
  • Gráfico de serie de tiempo (geom_line) con ajuste de suavizado LOESS (geom_smooth)
annual_data <- temp_data %>%
  mutate(Annual = rowMeans(select(., -Year), na.rm = TRUE))

ggplot(annual_data, aes(x = Year, y = Annual)) +
  geom_line(color = "red3", size = 1) +
  geom_smooth(method = "loess", color = "blue", se = FALSE) +
  labs(title = "Evolución de la Temperatura Global Anual",
       y = "Anomalía de Temperatura (°C)",
       caption = "Datos: NASA GISTEMP") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'

8. Matriz de correlación

Esta matriz se realizó con la finalidad de analizar la relación entre las anomalias de temperatura de diferentes meses:

  • Cálculo de matriz de correlaciones (cor) ignorando valores faltantes.

  • Visualización con corrplot mostrando coeficientes y color.

cor_matrix <- cor(temp_data[, -1], use = "complete.obs")
corrplot(cor_matrix, method = "color", type = "upper",
         tl.col = "black", addCoef.col = "black",
         number.cex = 0.7, title = "Correlación entre Meses")

9. Exportación de resultados

Los resultados seran exportados en un archivo “nasa_global_temps_clean.csv” de manera que puedan ser utilizados en futuros analisis.

write_csv(temp_data, "nasa_global_temps_clean.csv")
write_csv(desc_stats %>% t() %>% as.data.frame(), "nasa_temp_stats.csv")
cat("ANÁLISIS COMPLETADO")
## ANÁLISIS COMPLETADO