import pandas as pd
import numpy as np
import xarray as xrParte II — Análisis multidimensional con xarray
En esta parte aplicaremos xarray a un problema real combinando:
- datos de movilidad
- datos epidemiológicos
El objetivo es calcular un indicador de riesgo asociado a la movilidad:
risk(t, i, j)
donde: - t → tiempo - i → región de origen - j → región de destino
Motivación
Hasta ahora hemos trabajado con dataframes.
Sin embargo, este tipo de problema tiene una estructura naturalmente multidimensional:
- movilidad → (t, origen, destino)
- casos → (t, región)
Con xarray podemos representar esta estructura directamente.
covid_df = pd.read_parquet("data/processed/cnig_provincias_covid_cases.parquet")
trips_df = pd.read_parquet("data/processed/cnig_provincias_daily_mobility.parquet")
pop_df = pd.read_parquet("data/processed/cnig_provincias_zone_movements.parquet")
fechas = pop_df.index.get_level_values('date').unique()
covid_df = covid_df.loc[fechas]Ejercicio 1 — Exploración
- ¿Qué columnas tiene cada dataset?
- ¿Qué representa cada variable?
- ¿Qué dimensiones implícitas puedes identificar?
- ¿Qué rango de fechas tiene cada dataset?
Ejercicio 2 — Conversión a xarray (*)
Convierte los tres dataframes a estructuras xarray adecuadas.
Primero vamos a intentar hacerlo de forma manual, es decir: 1. creando las coordinadas 2. estructurando los datos
De esta forma vamos a entender mejor el funcionamiento. Para la segunda parte del ejercicio, vamos a volver a crear los xarray pero empleando funciones específicas Pistas: - usa .set_index(...) - luego .to_xarray()
# crear los xarray a partir de los dataframes de forma manual
# covid_df = covid_df[['date','id','new_cases']]
fechas = covid_df['date'].unique()
provincias = covid_df['id'].unique()
m = fechas.shape[0]
n = provincias.shape[0]
tot = covid_df['new_cases'].values.shape[0]
print(m,n,tot)
a = covid_df['new_cases'].values
data = a.reshape(m,n)
np.abs(data[:,0] - covid_df.loc[covid_df['id']=='01','new_cases'].values).max()
coords = {'fecha': fechas, 'provincia': provincias}
xr.DataArray(data=data, coords=coords)pop_ds = pop_df.set_index(['date', 'id']).to_xarray()
pop_ds['total_population'] - pop_ds['moving_population']
pop_xa = pop_ds['total_population']
# crear los xarray a partir de los dataframes de empleando las funciones especializadas de pandas
covid_ds = covid_df.to_xarray()
covid_ds['total_population'] = pop_df['total_population'].to_xarray()
covid_ds["IA10"] = covid_ds['new_cases'].rolling(date=10, min_periods=1).sum()
covid_ds["prob_inf"] = covid_ds["IA10"] / covid_ds['total_population']Ejercicio 3 — Dimensiones y coordenadas
Para cada dataset:
- identifica dimensiones
- identifica coordenadas
- comprueba que están alineadas
Ejercicio 4 — Incidencia acumulada (*)
Calcula una incidencia acumulada a 10 días usando rolling sum.
Pregunta:
- ¿por qué usamos esto como proxy?
Ejercicio 5 — Normalización (*)
Calcula la densidad de infectados:
density(t, i) = casos / población
Pregunta:
- ¿qué dimensiones tiene el resultado?
Ejercicio 6 — Cálculo del riesgo (*)
Queremos calcular:
risk(t, i, j)
a partir de:
- trips(t, i, j)
- density(t, i)
👉 intenta hacerlo directamente
trips_xa = trips_df['trips'].to_xarray()
trips_xa * covid_ds['prob_inf'].rename({'id': 'source'})Ejercicio 7 — Análisis
Calcula:
- riesgo total saliente por región
- riesgo total entrante
Pregunta:
- ¿qué regiones destacan?
Ejercicio 8 — Riesgo y propagación de la epidemia
En este ejercicio analizaremos si el riesgo asociado a la movilidad entre regiones puede estar relacionado con la aparición de nuevos casos en la región de destino.
La idea es explorar si:
- un aumento en el riesgo desde i → j
- precede a un aumento en la incidencia en j
Esto podría indicar que los viajes desde i han contribuido a la propagación hacia j.
Selecciona dos provincias (i, j) y analiza:
- el riesgo asociado a los viajes desde i → j
- la incidencia en la región j
Ejemplo
Madrid → Asturias
Paso 1
Selecciona una pareja de regiones (origen, destino).
origin = "28" # Madrid
target = "33" # AsturiasPaso 2
Extrae las siguientes series temporales:
- riesgo desde i → j
- incidencia en j
Paso 3
Representa ambas series en una misma figura.
Pregunta:
- ¿Cómo evolucionan en el tiempo?
- ¿Hay relación entre ellas?
- ¿observas patrones similares?
- ¿hay desfase temporal entre ambas?
Paso 4 — Interpretación
Reflexiona:
- ¿el aumento del riesgo ocurre antes que el aumento de casos en j?
- ¿podría existir una relación causal?
Parte III — Operaciones avanzadas con xarray
Ejercicio 1 — Construcción de un Dataset
Crea un objeto xarray.Dataset que contenga:
- incidencia: casos y densidad
- población
- movilidad (trips)
- riesgo
Asegúrate de que:
- comparten coordenadas
- las dimensiones están alineadas
Pregunta:
- ¿qué ventajas tiene agrupar variables en un Dataset?
Ejercicio 2 — Validación
Comprueba:
- que las coordenadas de tiempo coinciden
- que las regiones están alineadas
Pregunta:
- ¿qué problemas podrían surgir si no lo estuvieran?
Ejercicio 3 — Guardado (*)
Guarda el dataset en formato NetCDF.
Luego:
- vuelve a cargarlo
- verifica que es igual al original
Pregunta:
- ¿qué ventajas tiene NetCDF frente a CSV o Parquet?
Ejercicio 4 — Reducción
Calcula:
- riesgo total saliente por región
- riesgo total entrante por región
- riesgo medio en el tiempo
Pregunta:
- ¿cómo interpretas cada uno?
Ejercicio 5 — Subconjuntos
Selecciona:
- un subconjunto de fechas
- un subconjunto de regiones
Pregunta:
- ¿cómo cambia la estructura del dataset?
Ejercicio 6 — Agregación temporal
Agrupa los datos por:
- semana o
- mes
y calcula el riesgo medio
Pregunta:
- ¿qué patrones emergen?
Ejercicio — Matriz de riesgo neto
Para una fecha concreta:
- representa la matriz risk(i, j)
Pregunta:
- ¿qué estructura observas?
Ejercicio avanzado — Correlación temporal
Queremos estudiar la relación entre:
- riesgo desde i → j
- incidencia en j
a lo largo del tiempo.
Objetivo
Calcular la correlación en ventanas deslizantes.
Paso 1: Selecciona una pareja (i, j)
Paso 2: Calcula la correlación usando ventanas móviles (rolling window)
Hint
xr.corr(risk_ij.rolling(date=7), incidence_j.rolling(date=7))
De matrices a redes
La matriz de riesgo risk(i, j) puede interpretarse como una red dirigida:
- nodos → regiones
- aristas → flujo de riesgo entre regiones
Riesgo neto
Definimos el riesgo neto como (usar transpose):
net(i, j) = risk(i, j) - risk(j, i)
Interpretación:
- net(i, j) > 0 → flujo neto de riesgo de i hacia j
- net(i, j) < 0 → flujo neto de riesgo de j hacia i
Para construir una red dirigida, nos quedamos con:
net(i, j) > 0
Ejercicio Avanzado — Red de riesgo dirigida (*)
A partir de la matriz risk(i, j), construye una red dirigida basada en el riesgo neto.
Paso 1 — Riesgo neto
Selecciona una fecha concreta.
Calcula:
net(i, j) = risk(i, j) - risk(j, i)
y conserva solo los valores positivos.
date = "2020-03-15"
risk_day = risk.sel(date=date)
net = risk_day - risk_day.transpose("target", "source")
net_pos = net.where(net > 0)Paso 2 — Construcción de la red
Convierte la matriz en un grafo dirigido.
import networkx as nx
edges = (
net_pos.to_dataframe(name="weight")
.dropna()
.reset_index()
)
G = nx.DiGraph()
for _, row in edges.iterrows():
G.add_edge(row["source"], row["target"], weight=row["weight"])Paso 3 — Posición de nodos
Usa los centroides de las regiones como posiciones.
coord_dict = {
idx: (geom.centroid.x, geom.centroid.y)
for idx, geom in gdf["geometry"].items()
}Paso 4 — plot con flechas
- Leemos las geometrías de las provincias
- calculamos los centroides para poder dibujar las fechas
import geopandas as gpd
gdf = gpd.read_parquet("data/covid19/provincias.parquet")import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10,8))
# mapa base
gdf.plot(ax=ax, color="lightgrey", edgecolor="black")
# nodos
nx.draw_networkx_nodes(
G,
pos=coord_dict,
node_size=10,
node_color="black",
ax=ax
)
# edges con flechas
nx.draw_networkx_edges(
G,
pos=coord_dict,
arrowstyle='-|>',
arrowsize=8,
connectionstyle="arc3,rad=0.2",
width=0.5,
alpha=0.7,
ax=ax
)
ax.set_title(f"Directed risk network — {date}")
ax.set_axis_off()
plt.show()Paso 5 — Filtrado
Filtra las conexiones más relevantes.
threshold = net_pos.quantile(0.95)
edges = edges[edges["weight"] > threshold]Interpretación
- ¿Qué regiones son emisores netos?
- ¿Qué regiones reciben más riesgo?
- ¿Hay estructura geográfica?