Introducción

Este trabajo analiza la temperatura en los 32 estados de México entre 1985 y 2025, utilizando datos de temperatura mínima, máxima y media.

Mediante técnicas de clustering, los estados se agrupan en categorías climáticas (fríos, templados, cálidos) para identificar patrones regionales y comparar su diversidad térmica.

Instalación de Paqueterias y Llamado de Librerias

library(cluster)
library(ggplot2)
library(data.table)
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:data.table':
## 
##     between, first, last
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Llamado de base de datos

path <- "/Users/brisnaordaz/Downloads/data (1).csv" 
clima <- read.csv(path, stringsAsFactors = FALSE)

Datos entendidos

summary(clima)
##     PERIOD             STATE_ID     STATE               MIN_C      
##  Length:16071       Min.   : 0   Length:16071       Min.   :-2.20  
##  Class :character   1st Qu.: 8   Class :character   1st Qu.: 9.50  
##  Mode  :character   Median :16   Mode  :character   Median :14.10  
##                     Mean   :16                      Mean   :13.96  
##                     3rd Qu.:24                      3rd Qu.:18.60  
##                     Max.   :32                      Max.   :26.50  
##      MEAN_C          MAX_C       PRECIPITATION_MM     MIN_F      
##  Min.   : 7.50   Min.   :15.30   Min.   :  0.00   Min.   :28.04  
##  1st Qu.:17.60   1st Qu.:25.60   1st Qu.:  7.70   1st Qu.:49.10  
##  Median :21.70   Median :29.10   Median : 35.70   Median :57.38  
##  Mean   :21.39   Mean   :28.85   Mean   : 73.86   Mean   :57.12  
##  3rd Qu.:25.20   3rd Qu.:32.20   3rd Qu.:111.75   3rd Qu.:65.48  
##  Max.   :33.40   Max.   :40.40   Max.   :851.00   Max.   :79.70  
##      MEAN_F          MAX_F        PRECIPITATION_IN 
##  Min.   :45.50   Min.   : 59.54   Min.   : 0.0000  
##  1st Qu.:63.68   1st Qu.: 78.08   1st Qu.: 0.3031  
##  Median :71.06   Median : 84.38   Median : 1.4055  
##  Mean   :70.51   Mean   : 83.93   Mean   : 2.9080  
##  3rd Qu.:77.36   3rd Qu.: 89.96   3rd Qu.: 4.3996  
##  Max.   :92.12   Max.   :104.72   Max.   :33.5039
str(clima)
## 'data.frame':    16071 obs. of  11 variables:
##  $ PERIOD          : chr  "1985-01-01" "1985-01-01" "1985-01-01" "1985-01-01" ...
##  $ STATE_ID        : int  0 1 2 3 4 5 6 7 8 9 ...
##  $ STATE           : chr  "Mexico" "Aguascalientes" "Baja California" "Baja California Sur" ...
##  $ MIN_C           : num  7.8 3.1 5.6 9.2 15.7 2.9 14.4 14.1 -1.4 4.1 ...
##  $ MEAN_C          : num  15.9 12.2 12.9 17.1 22.7 10.3 22.7 20.9 7.5 12.5 ...
##  $ MAX_C           : num  23.9 21.3 20.2 25 29.7 17.7 30.9 27.8 16.5 20.9 ...
##  $ PRECIPITATION_MM: num  36 4.9 12.2 30.3 20.9 ...
##  $ MIN_F           : num  46 37.6 42.1 48.6 60.3 ...
##  $ MEAN_F          : num  60.6 54 55.2 62.8 72.9 ...
##  $ MAX_F           : num  75 70.3 68.4 77 85.5 ...
##  $ PRECIPITATION_IN: num  1.417 0.193 0.48 1.193 0.823 ...

Parseo de fecha y limpieza mínima

clima$PERIOD <- as.character(clima$PERIOD)

x <- as.Date(clima$PERIOD)                  # funciona si viene AAAA-MM-DD
bad <- is.na(x)
if (any(bad)) {
  x[bad] <- as.Date(paste0(clima$PERIOD[bad], "-01"))  # para AAAA-MM
}
clima$PERIOD <- x

clima <- subset(clima, STATE_ID != 0)
clima <- subset(clima, PERIOD >= as.Date("1985-01-01") & PERIOD <= as.Date("2025-07-01"))

Features por estado (temperatura)

features <- clima %>%
  group_by(STATE) %>%
  summarise(
    min_c_monthly_mean  = mean(MIN_C,  na.rm = TRUE),
    max_c_monthly_mean  = mean(MAX_C,  na.rm = TRUE),
    mean_c_monthly_mean = mean(MEAN_C, na.rm = TRUE)
  ) %>%
  mutate(thermal_amplitude = max_c_monthly_mean - min_c_monthly_mean) %>%
  ungroup()

head(features)
## # A tibble: 6 × 5
##   STATE               min_c_monthly_mean max_c_monthly_mean mean_c_monthly_mean
##   <chr>                            <dbl>              <dbl>               <dbl>
## 1 Aguascalientes                    8.91               26.4                17.6
## 2 Baja California                  12.0                26.8                19.4
## 3 Baja California Sur              15.5                30.3                22.9
## 4 Campeche                         20.3                33.1                26.7
## 5 Chiapas                          18.2                30.7                24.5
## 6 Chihuahua                         9.21               26.8                18.0
## # ℹ 1 more variable: thermal_amplitude <dbl>

Escalar los datos

X <- features %>% select(min_c_monthly_mean,
                         max_c_monthly_mean,
                         mean_c_monthly_mean,
                         thermal_amplitude)
X_scaled <- scale(X)

# Codo
fviz_nbclust(as.data.frame(X_scaled), kmeans, method = "wss") +
  ggtitle("Elección de K - Codo")

# Silueta
fviz_nbclust(as.data.frame(X_scaled), kmeans, method = "silhouette") +
  ggtitle("Elección de K - Silueta")

K-means

set.seed(42)
km4 <- kmeans(X_scaled, centers = 4, nstart = 50)
features$cluster_k4 <- factor(km4$cluster)

# Vista rápida del cluster plot
fviz_cluster(km4, data = as.data.frame(X_scaled), geom = "point",
             ellipse.type = "norm",
             main = "Clusters de temperatura por estado (K = 4)")

ord <- features %>%
  group_by(cluster_k4) %>%
  summarise(mean_temp = mean(mean_c_monthly_mean), .groups = "drop") %>%
  arrange(mean_temp) %>%
  mutate(label = c("Frío","Templado","Cálido","Muy cálido"))  # si cambias K, ajusta

features <- features %>%
  left_join(ord %>% select(cluster_k4, label), by = "cluster_k4")

Etiquetas interpretables

tabla_final <- features %>%
dplyr::arrange(mean_c_monthly_mean) %>%
dplyr::select(STATE,
min_c_monthly_mean,
max_c_monthly_mean,
mean_c_monthly_mean,
thermal_amplitude,
cluster_k4,
categoria = label)


tabla_final
## # A tibble: 32 × 7
##    STATE            min_c_monthly_mean max_c_monthly_mean mean_c_monthly_mean
##    <chr>                         <dbl>              <dbl>               <dbl>
##  1 Tlaxcala                       6.18               23.2                14.7
##  2 Estado de México               6.99               22.7                14.8
##  3 Ciudad de México               9.65               24.0                16.8
##  4 Zacatecas                      8.63               26.2                17.4
##  5 Hidalgo                        9.81               25.2                17.5
##  6 Aguascalientes                 8.91               26.4                17.6
##  7 Durango                        8.89               26.8                17.8
##  8 Puebla                        10.5                25.4                17.9
##  9 Chihuahua                      9.21               26.8                18.0
## 10 Guanajuato                    10.5                27.1                18.8
## # ℹ 22 more rows
## # ℹ 3 more variables: thermal_amplitude <dbl>, cluster_k4 <fct>,
## #   categoria <chr>
out_dir <- dirname(path)
write.csv(tabla_final, file.path(out_dir, "temperature_clusters_k4_1985_2025.csv"), row.names = FALSE)

Tabla final por estado

ggplot(tabla_final,
aes(x = reorder(STATE, mean_c_monthly_mean),
y = mean_c_monthly_mean, fill = categoria)) +
geom_col() +
coord_flip() +
labs(x = "Estado", y = "Temp. media (°C, 1985–2025)",
title = "Temperatura media por estado y categoría (K=4)") +
theme_minimal()

LS0tCnRpdGxlOiAiQ2F0ZWdvcml6YWNpw7NuIGRlIFRlbXBlcmF0dXJhIHBvciBFc3RhZG8gZW4gTcOpeGljbyIKYXV0aG9yOiAiQTAxMjc2MDQ1IC0gQlJJU05BIEYuIE9SREFaIE1PTlJPWSIKZGF0ZTogIjIwMjUtMDgtMTkiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBmbGF0bHkKICAgIGhpZ2hsaWdodDogdGFuZ28KbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQo8Y2VudGVyPgohW10oaHR0cHM6Ly93d3cuZ2VvZ3JhZmlhaW5maW5pdGEuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzA3L0NsaW1hcy1kZS1tZXhpY28uanBnKQoKIyA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Ij4gSW50cm9kdWNjacOzbiA8L3NwYW4+CkVzdGUgdHJhYmFqbyBhbmFsaXphIGxhIHRlbXBlcmF0dXJhIGVuIGxvcyAzMiBlc3RhZG9zIGRlIE3DqXhpY28gZW50cmUgMTk4NSB5IDIwMjUsIHV0aWxpemFuZG8gZGF0b3MgZGUgdGVtcGVyYXR1cmEgbcOtbmltYSwgbcOheGltYSB5IG1lZGlhLiAKCk1lZGlhbnRlIHTDqWNuaWNhcyBkZSBjbHVzdGVyaW5nLCBsb3MgZXN0YWRvcyBzZSBhZ3J1cGFuIGVuIGNhdGVnb3LDrWFzIGNsaW3DoXRpY2FzIChmcsOtb3MsIHRlbXBsYWRvcywgY8OhbGlkb3MpIHBhcmEgaWRlbnRpZmljYXIgcGF0cm9uZXMgcmVnaW9uYWxlcyB5IGNvbXBhcmFyIHN1IGRpdmVyc2lkYWQgdMOpcm1pY2EuCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Ij4gSW5zdGFsYWNpw7NuIGRlIFBhcXVldGVyaWFzIHkgTGxhbWFkbyBkZSBMaWJyZXJpYXMgIDwvc3Bhbj4KCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkoZHBseXIpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrOyI+IExsYW1hZG8gZGUgYmFzZSBkZSBkYXRvcyAgPC9zcGFuPgpgYGB7cn0KcGF0aCA8LSAiL1VzZXJzL2JyaXNuYW9yZGF6L0Rvd25sb2Fkcy9kYXRhICgxKS5jc3YiIApjbGltYSA8LSByZWFkLmNzdihwYXRoLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrOyI+IERhdG9zIGVudGVuZGlkb3MgPC9zcGFuPgpgYGB7cn0Kc3VtbWFyeShjbGltYSkKc3RyKGNsaW1hKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrOyI+IFBhcnNlbyBkZSBmZWNoYSB5IGxpbXBpZXphIG3DrW5pbWEgPC9zcGFuPgpgYGB7cn0KY2xpbWEkUEVSSU9EIDwtIGFzLmNoYXJhY3RlcihjbGltYSRQRVJJT0QpCgp4IDwtIGFzLkRhdGUoY2xpbWEkUEVSSU9EKSAgICAgICAgICAgICAgICAgICMgZnVuY2lvbmEgc2kgdmllbmUgQUFBQS1NTS1ERApiYWQgPC0gaXMubmEoeCkKaWYgKGFueShiYWQpKSB7CiAgeFtiYWRdIDwtIGFzLkRhdGUocGFzdGUwKGNsaW1hJFBFUklPRFtiYWRdLCAiLTAxIikpICAjIHBhcmEgQUFBQS1NTQp9CmNsaW1hJFBFUklPRCA8LSB4CgpjbGltYSA8LSBzdWJzZXQoY2xpbWEsIFNUQVRFX0lEICE9IDApCmNsaW1hIDwtIHN1YnNldChjbGltYSwgUEVSSU9EID49IGFzLkRhdGUoIjE5ODUtMDEtMDEiKSAmIFBFUklPRCA8PSBhcy5EYXRlKCIyMDI1LTA3LTAxIikpCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Ij4gRmVhdHVyZXMgcG9yIGVzdGFkbyAodGVtcGVyYXR1cmEpIDwvc3Bhbj4KCmBgYHtyfQoKZmVhdHVyZXMgPC0gY2xpbWEgJT4lCiAgZ3JvdXBfYnkoU1RBVEUpICU+JQogIHN1bW1hcmlzZSgKICAgIG1pbl9jX21vbnRobHlfbWVhbiAgPSBtZWFuKE1JTl9DLCAgbmEucm0gPSBUUlVFKSwKICAgIG1heF9jX21vbnRobHlfbWVhbiAgPSBtZWFuKE1BWF9DLCAgbmEucm0gPSBUUlVFKSwKICAgIG1lYW5fY19tb250aGx5X21lYW4gPSBtZWFuKE1FQU5fQywgbmEucm0gPSBUUlVFKQogICkgJT4lCiAgbXV0YXRlKHRoZXJtYWxfYW1wbGl0dWRlID0gbWF4X2NfbW9udGhseV9tZWFuIC0gbWluX2NfbW9udGhseV9tZWFuKSAlPiUKICB1bmdyb3VwKCkKCmhlYWQoZmVhdHVyZXMpCgoKYGBgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Ij4gRXNjYWxhciBsb3MgZGF0b3MgPC9zcGFuPgoKYGBge3J9ClggPC0gZmVhdHVyZXMgJT4lIHNlbGVjdChtaW5fY19tb250aGx5X21lYW4sCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfY19tb250aGx5X21lYW4sCiAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuX2NfbW9udGhseV9tZWFuLAogICAgICAgICAgICAgICAgICAgICAgICAgdGhlcm1hbF9hbXBsaXR1ZGUpClhfc2NhbGVkIDwtIHNjYWxlKFgpCgojIENvZG8KZnZpel9uYmNsdXN0KGFzLmRhdGEuZnJhbWUoWF9zY2FsZWQpLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArCiAgZ2d0aXRsZSgiRWxlY2Npw7NuIGRlIEsgLSBDb2RvIikKCiMgU2lsdWV0YQpmdml6X25iY2x1c3QoYXMuZGF0YS5mcmFtZShYX3NjYWxlZCksIGttZWFucywgbWV0aG9kID0gInNpbGhvdWV0dGUiKSArCiAgZ2d0aXRsZSgiRWxlY2Npw7NuIGRlIEsgLSBTaWx1ZXRhIikKYGBgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Ij4gSy1tZWFucyA8L3NwYW4+CgoKYGBge3J9CnNldC5zZWVkKDQyKQprbTQgPC0ga21lYW5zKFhfc2NhbGVkLCBjZW50ZXJzID0gNCwgbnN0YXJ0ID0gNTApCmZlYXR1cmVzJGNsdXN0ZXJfazQgPC0gZmFjdG9yKGttNCRjbHVzdGVyKQoKIyBWaXN0YSByw6FwaWRhIGRlbCBjbHVzdGVyIHBsb3QKZnZpel9jbHVzdGVyKGttNCwgZGF0YSA9IGFzLmRhdGEuZnJhbWUoWF9zY2FsZWQpLCBnZW9tID0gInBvaW50IiwKICAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJub3JtIiwKICAgICAgICAgICAgIG1haW4gPSAiQ2x1c3RlcnMgZGUgdGVtcGVyYXR1cmEgcG9yIGVzdGFkbyAoSyA9IDQpIikKYGBgCgpgYGB7cn0Kb3JkIDwtIGZlYXR1cmVzICU+JQogIGdyb3VwX2J5KGNsdXN0ZXJfazQpICU+JQogIHN1bW1hcmlzZShtZWFuX3RlbXAgPSBtZWFuKG1lYW5fY19tb250aGx5X21lYW4pLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBhcnJhbmdlKG1lYW5fdGVtcCkgJT4lCiAgbXV0YXRlKGxhYmVsID0gYygiRnLDrW8iLCJUZW1wbGFkbyIsIkPDoWxpZG8iLCJNdXkgY8OhbGlkbyIpKSAgIyBzaSBjYW1iaWFzIEssIGFqdXN0YQoKZmVhdHVyZXMgPC0gZmVhdHVyZXMgJT4lCiAgbGVmdF9qb2luKG9yZCAlPiUgc2VsZWN0KGNsdXN0ZXJfazQsIGxhYmVsKSwgYnkgPSAiY2x1c3Rlcl9rNCIpCgpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrOyI+IEV0aXF1ZXRhcyBpbnRlcnByZXRhYmxlcyA8L3NwYW4+CgpgYGB7cn0KdGFibGFfZmluYWwgPC0gZmVhdHVyZXMgJT4lCmRwbHlyOjphcnJhbmdlKG1lYW5fY19tb250aGx5X21lYW4pICU+JQpkcGx5cjo6c2VsZWN0KFNUQVRFLAptaW5fY19tb250aGx5X21lYW4sCm1heF9jX21vbnRobHlfbWVhbiwKbWVhbl9jX21vbnRobHlfbWVhbiwKdGhlcm1hbF9hbXBsaXR1ZGUsCmNsdXN0ZXJfazQsCmNhdGVnb3JpYSA9IGxhYmVsKQoKCnRhYmxhX2ZpbmFsCgoKb3V0X2RpciA8LSBkaXJuYW1lKHBhdGgpCndyaXRlLmNzdih0YWJsYV9maW5hbCwgZmlsZS5wYXRoKG91dF9kaXIsICJ0ZW1wZXJhdHVyZV9jbHVzdGVyc19rNF8xOTg1XzIwMjUuY3N2IiksIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpibGFjazsiPiBUYWJsYSBmaW5hbCBwb3IgZXN0YWRvIDwvc3Bhbj4KYGBge3J9CmdncGxvdCh0YWJsYV9maW5hbCwKYWVzKHggPSByZW9yZGVyKFNUQVRFLCBtZWFuX2NfbW9udGhseV9tZWFuKSwKeSA9IG1lYW5fY19tb250aGx5X21lYW4sIGZpbGwgPSBjYXRlZ29yaWEpKSArCmdlb21fY29sKCkgKwpjb29yZF9mbGlwKCkgKwpsYWJzKHggPSAiRXN0YWRvIiwgeSA9ICJUZW1wLiBtZWRpYSAowrBDLCAxOTg14oCTMjAyNSkiLAp0aXRsZSA9ICJUZW1wZXJhdHVyYSBtZWRpYSBwb3IgZXN0YWRvIHkgY2F0ZWdvcsOtYSAoSz00KSIpICsKdGhlbWVfbWluaW1hbCgpCmBgYAoK