Cuando el permafrost se derrite y los arroyos se vuelven ácido sulfúrico#
pH 2.7. Tan ácido como el vinagre. Corriendo libre por arroyos del Yukon ártico que nadie había tocado.
En la última década, decenas de surgencias ácidas brotaron en cabeceras de ríos del norte de Canadá. Donde antes había musgo y arbustos, ahora hay tierra muerta y agua color óxido. Y no es contaminación de minas — son los propios sulfuros del subsuelo, expuestos por un permafrost que se derrite, oxidándose al aire por primera vez en milenios.
Paper: Skierszkan et al., Science (2026) Datos abiertos: Preprint EarthArXiv (Open Access) + Table S1 + Figura S8F
Qué pasó#
El equipo monitoreó 156 muestras de tres arroyos (KM99, KM71, KM175) entre 2018 y 2025 en el área de Tombstone-Wernecke-Ogilvie (Yukon, Canadá). Identificaron 130 arroyos con signos claros de oxidación de sulfuros minerales (SMO, sulfide-mineral oxidation) en ~59.500 km² de la cuenca del río Peel. Los datos del paper documentan tres niveles del fenómeno:
Las surgencias (10 puntos muestreados): el origen químico. pH entre 2.7 y 6.1, metales disueltos a concentraciones letales para vida acuática.
Los arroyos receptores (KM99, KM71, KM175): el efecto inmediato. Concentraciones de cadmio, zinc y níquel que superan los umbrales canadienses de toxicidad (CCME) en hasta el 100% de las muestras.
Los ríos grandes (Peel, Ogilvie, Klondike): la huella corriente abajo. pH estable (≈8), pero sulfato subiendo de forma sostenida durante décadas.
El motor: Dawson City, la estación meteorológica más cercana, sube +0,04 °C por año desde 1961 (p<0,001). Cuando el permafrost retrocede, los sulfuros de la roca madre (bedrock) entran en contacto con agua y oxígeno, y la reacción genera ácido sulfúrico que disuelve metales pesados.
# ══════════════════════════════════════════════════════════════
# Configuración — modifica estos valores para explorar
# ══════════════════════════════════════════════════════════════
PH_NEUTRO = 7.0 # pH de agua pura
PH_ACIDO_BIOTA = 5.0 # Umbral de daño a vida acuática (paper: 19% de streams históricos)
PH_MUY_ACIDO = 3.0 # Rango típico de drenaje ácido de mina
COLOR_DATOS = "#2563EB" # Azul CaM (datos principales)
COLOR_ALERTA = "#DC2626" # Rojo (umbral o valor crítico)
COLOR_REFERENCIA = "#D97706" # Ámbar (referencia/baseline)
COLOR_SECUNDARIO = "#059669" # Emerald
COLOR_CONTEXTO = "#7C3AED" # Violeta
FUENTE = "Fuente: Skierszkan et al. (2026), Science | Datos: preprint EarthArXiv (Open Access)"
# ── Imports + estilo CaM ─────────────────────────────────────
import os, urllib.request
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
style_file = "../../cam.mplstyle"
if not os.path.exists(style_file):
style_file = "/tmp/cam.mplstyle"
if not os.path.exists(style_file):
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/Ciencia-a-Mordiscos/lab/main/cam.mplstyle",
style_file,
)
plt.style.use(style_file)
os.makedirs("figuras", exist_ok=True)
# ── Cargar los 6 CSVs del paper ──────────────────────────────
DATOS = "datos"
streams = pd.read_csv(f"{DATOS}/streams_concentraciones.csv")
seepages = pd.read_csv(f"{DATOS}/seepages_quimica.csv")
rios = pd.read_csv(f"{DATOS}/rios_grandes.csv")
distrib = pd.read_csv(f"{DATOS}/streams_impactados_distribucion.csv")
dawson = pd.read_csv(f"{DATOS}/dawson_temperatura_resumen.csv")
km99 = pd.read_csv(f"{DATOS}/km99_perfil_espacial.csv")
print(f"Streams (Table S1): {len(streams)} filas — {streams['stream'].nunique()} arroyos × {streams['elemento'].nunique()} elementos")
print(f"Surgencias activas (Fig S8F): {len(seepages)} puntos en {seepages['sitio'].nunique()} sitios")
print(f"Ríos grandes: {len(rios)} ríos con tasas de sulfato")
print(f"Distribución de SMO-impactados: {distrib['n_streams'].sum()} arroyos sumados ({distrib['n_streams'].sum()} ≠ 130 del texto; ver tabla de verificación)")
print(f"Calentamiento Dawson: +{dawson['tasa_C_per_yr'].iloc[0]} °C/año ({dawson['periodo'].iloc[0]}, {dawson['p_value'].iloc[0]})")
print(f"Perfil KM99: {len(km99)} puntos en gradiente upstream → downstream")
Streams (Table S1): 30 filas — 3 arroyos × 10 elementos
Surgencias activas (Fig S8F): 10 puntos en 4 sitios
Ríos grandes: 3 ríos con tasas de sulfato
Distribución de SMO-impactados: 136 arroyos sumados (136 ≠ 130 del texto; ver tabla de verificación)
Calentamiento Dawson: +0.04 °C/año (1961-2024, p_lt_0.001)
Perfil KM99: 4 puntos en gradiente upstream → downstream
El primer dato#
Aquí está.
# % de muestras que superan el umbral de toxicidad canadiense (CCME)
# para cada metal en los 3 arroyos receptores.
# Datos: Table S1 del preprint (página 24).
# Solo metales (sulfato no tiene guideline directo en este dataset)
metales_ord = ["Al", "Co", "Cu", "Ni", "Fe", "Cd", "Se", "Zn", "Mn"]
streams_ord = ["KM99", "KM71", "KM175"]
colors_stream = {"KM99": COLOR_DATOS, "KM71": COLOR_SECUNDARIO, "KM175": COLOR_CONTEXTO}
# Pivot: filas=metal, cols=stream
pivot = streams[streams["elemento"].isin(metales_ord)].pivot_table(
index="elemento", columns="stream", values="pct_exceed_guideline", aggfunc="first"
).reindex(metales_ord).reindex(columns=streams_ord)
fig, ax = plt.subplots(figsize=(13, 5.5))
x = np.arange(len(metales_ord))
width = 0.27
for i, stream in enumerate(streams_ord):
vals = pivot[stream].values
bars = ax.bar(
x + (i - 1) * width, vals, width,
color=colors_stream[stream], alpha=0.85,
edgecolor="white", linewidth=0.8,
label=stream,
)
# Anotar valores ≥90 sobre la barra
for xi, v in zip(x + (i - 1) * width, vals):
if not np.isnan(v) and v >= 90:
ax.text(xi, v + 1.5, f"{int(v)}", ha="center", fontsize=8,
color=colors_stream[stream], fontweight="bold")
# Línea de referencia: 50% (mitad de las muestras tóxicas)
ax.axhline(y=50, color=COLOR_ALERTA, linewidth=1.2, linestyle="--", alpha=0.55)
ax.text(8.6, 51.5, "50% — la mitad de muestras supera el umbral",
fontsize=8.5, color=COLOR_ALERTA, ha="right", fontweight="bold")
ax.set_xticks(x)
ax.set_xticklabels(metales_ord, fontsize=10)
ax.set_ylabel("% de muestras sobre el umbral CCME", fontsize=11)
ax.set_ylim(0, 110)
ax.set_title("¿Cuántas muestras superan el umbral de toxicidad?",
fontsize=14, fontweight="bold", pad=28)
ax.text(0.5, 1.03, "Tres arroyos receptores · 9 metales · guidelines canadienses CCME",
transform=ax.transAxes, fontsize=10, color="#666666", ha="center")
ax.legend(fontsize=10, loc="upper left", framealpha=0.9, ncol=3)
fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color="#999999", style="italic")
plt.savefig("figuras/exceedance_metales.png", dpi=200, bbox_inches="tight")
plt.show()
Lo que muestran estos datos: cadmio está sobre el umbral canadiense en prácticamente todas las muestras de los tres arroyos (92–100%). El zinc lo está en KM99 (98%) y KM175 (100%), pero solo en 57% de las muestras de KM71. El níquel también es severo en KM99 (90%) y KM175 (62%), pero ausente en KM71 (0%). No es un metal puntual: son varios metales tóxicos simultáneos, los nueve principales reportados en la Table S1.
El cadmio merece subrayado. Es un disruptor endocrino y se bioacumula en peces. Cuando 98% de las muestras de un arroyo lo tienen por encima del umbral durante años, la vida acuática local no tiene escapatoria.
KM71 muestra un patrón distinto: bajo en aluminio y cobre, alto en cadmio y selenio. La firma química depende de qué minerales sulfurosos específicos contiene el bedrock que se está oxidando — el problema no es uniforme.
¿De dónde sale toda esa toxicidad?#
De las surgencias — manantiales que brotan directo del permafrost en deshielo, justo donde el bedrock sulfurado queda expuesto. El equipo muestreó 10 de estas surgencias en 4 sitios distintos. Una sola surgencia puede tener concentraciones 100 a 1.000 veces más altas que el arroyo que la recibe.
# Las 10 surgencias activas: pH vs sulfato.
# Cada punto es una surgencia individual; color = sitio geográfico.
color_map_sitio = {
"KM99": COLOR_DATOS,
"KM175": COLOR_CONTEXTO,
"KM175.5": COLOR_SECUNDARIO,
"KM182": COLOR_REFERENCIA,
}
fig, ax = plt.subplots(figsize=(13, 5.5))
for sitio, sub in seepages.groupby("sitio"):
ax.scatter(
sub["pH"], sub["SO4_mg_L"],
color=color_map_sitio[sitio], s=100, alpha=0.85,
edgecolors="white", linewidths=0.8, zorder=5,
label=sitio,
)
# Línea de referencia: pH de la lluvia ácida (4.5) y pH neutro (7)
ax.axvline(x=PH_NEUTRO, color="#999999", linewidth=1.0, linestyle=":", alpha=0.6)
ax.text(PH_NEUTRO + 0.05, 1000, "pH neutro", fontsize=8.5, color="#666666", rotation=90, va="bottom")
# Anotar la más extrema (KM99 location 8a — el dieback)
extrema = seepages[(seepages["location"].astype(str) == "8a")]
if not extrema.empty:
e = extrema.iloc[0]
ax.annotate(
f"Surgencia KM99 dieback\npH {e['pH']}, SO₄ {int(e['SO4_mg_L'])} mg/L",
xy=(e["pH"], e["SO4_mg_L"]),
xytext=(4.5, 12000),
fontsize=9, color=COLOR_ALERTA, fontweight="bold",
arrowprops=dict(arrowstyle="->", color=COLOR_ALERTA, lw=1.3),
)
ax.set_xlabel("pH", fontsize=11)
ax.set_ylabel("Sulfato (mg/L)", fontsize=11)
ax.set_xlim(2.5, 7)
ax.set_ylim(0, 20000)
ax.set_title("¿Qué tan ácidas y cargadas de sulfato son las surgencias?",
fontsize=14, fontweight="bold", pad=28)
ax.text(0.5, 1.03, "10 surgencias activas en 4 sitios del área Tombstone-Wernecke-Ogilvie",
transform=ax.transAxes, fontsize=10, color="#666666", ha="center")
ax.legend(fontsize=9.5, loc="upper right", framealpha=0.9, title="Sitio")
fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color="#999999", style="italic")
plt.savefig("figuras/seepages_ph_sulfato.png", dpi=200, bbox_inches="tight")
plt.show()
print(f"\npH mediano de las 10 surgencias: {seepages['pH'].median():.1f}")
print(f"Sulfato mediano: {seepages['SO4_mg_L'].median():.0f} mg/L")
print(f"Surgencia más ácida: pH {seepages['pH'].min():.1f} (KM99 zona de dieback)")
pH mediano de las 10 surgencias: 3.1
Sulfato mediano: 4773 mg/L
Surgencia más ácida: pH 2.7 (KM99 zona de dieback)
Y aguas abajo, ¿qué pasa?#
Los arroyos pequeños son la zona caliente. Pero el área de estudio drena hacia ríos mucho más grandes — Peel (74.000 km²), Ogilvie (4.500 km²), Klondike (7.800 km²) — que alimentan al Mackenzie, una de las dos cuencas (sub)árticas más grandes de Norteamérica.
Los ríos grandes mantienen su pH casi neutro (≈8) porque los carbonatos del suelo amortiguan el ácido. Pero el sulfato es conservativo: no se precipita, no se descompone. Es la huella química del fenómeno, y está subiendo de forma sostenida en los tres ríos durante décadas.
# Tasas de aumento de sulfato en los 3 ríos tributarios principales.
# Datos: ECCC National Long-term Water Quality Monitoring,
# transcritos del texto del paper (p.10).
rios_ord = rios.sort_values("SO4_rate_actual_mg_L_yr", ascending=False).reset_index(drop=True)
colors_rio = [COLOR_ALERTA, COLOR_DATOS, COLOR_SECUNDARIO]
fig, ax = plt.subplots(figsize=(13, 5.5))
bars = ax.barh(
rios_ord["rio"], rios_ord["SO4_rate_actual_mg_L_yr"],
color=colors_rio, alpha=0.85, edgecolor="white", linewidth=0.8,
)
# Anotar tasa + período + significancia a la derecha de cada barra
for i, row in rios_ord.iterrows():
rate = row["SO4_rate_actual_mg_L_yr"]
periodo = row["SO4_rate_actual_periodo"]
pval = row["SO4_rate_previo_p_value"]
pval_clean = pval.replace("p_lt_", "p<").replace("p=", "p=")
cuenca = int(row["cuenca_km2"])
ax.text(
rate + 0.3, i,
f"+{rate} mg/L/año · {periodo} · {pval_clean} · cuenca {cuenca:,} km²".replace(",", "."),
fontsize=10, va="center", color="#333333", fontweight="normal",
)
ax.set_xlabel("Tasa de aumento de sulfato (mg/L por año)", fontsize=11)
ax.set_xlim(0, 17)
ax.set_title("¿Qué tan rápido sube el sulfato en los ríos grandes?",
fontsize=14, fontweight="bold", pad=28)
ax.text(0.5, 1.03, "Tres ríos tributarios del Mackenzie · monitoreo ECCC desde 1979",
transform=ax.transAxes, fontsize=10, color="#666666", ha="center")
ax.invert_yaxis()
# Nota sobre interpretación
ax.text(0.98, 0.02,
"El Ogilvie tiene la cuenca más pequeña y la tasa más alta —\nrefleja proximidad geográfica a las zonas con SMO activa",
transform=ax.transAxes, fontsize=8.5, color="#999999",
ha="right", va="bottom", style="italic")
fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color="#999999", style="italic")
plt.savefig("figuras/rios_grandes_tasas.png", dpi=200, bbox_inches="tight")
plt.show()
print("\nResumen pH (estables, no acidificados):")
for _, r in rios.iterrows():
print(f" {r['rio']:10s}: pH {r['pH_promedio']} ± {r['pH_sd']}")
Resumen pH (estables, no acidificados):
Peel : pH 7.9 ± 0.4
Ogilvie : pH 8.1 ± 0.2
Klondike : pH 7.9 ± 0.2
¿Qué tan ácidas son estas surgencias en perspectiva?#
El pH es logarítmico: cada unidad por debajo de 7 multiplica por 10 la concentración de H⁺. Veamos dónde caen las 10 surgencias en la escala completa.
# Histograma de pH de las 10 surgencias con líneas de referencia.
# Referencias:
# pH 7 = neutro (agua pura)
# pH 5 = umbral de daño a vida acuática (19% de streams históricos del Yukon estaban bajo este nivel)
# pH 3 = rango típico de drenaje ácido de mina
fig, ax = plt.subplots(figsize=(13, 5.5))
n, bins, patches = ax.hist(
seepages["pH"], bins=np.arange(2.5, 7, 0.3),
color=COLOR_DATOS, alpha=0.55, edgecolor=COLOR_DATOS, linewidth=0.8,
)
y_max = max(n) * 1.4
ax.set_ylim(0, y_max)
# Línea: pH neutro
ax.axvline(x=PH_NEUTRO, color="#666666", linewidth=1.2, linestyle="--", alpha=0.7)
ax.text(PH_NEUTRO + 0.05, y_max * 0.92, "pH 7 (neutro)",
fontsize=9.5, color="#666666", fontweight="bold")
# Línea: pH 5 — umbral de daño biológico
ax.axvline(x=PH_ACIDO_BIOTA, color=COLOR_REFERENCIA, linewidth=1.5, linestyle="--", alpha=0.8)
ax.text(PH_ACIDO_BIOTA + 0.05, y_max * 0.82, "pH 5 — daño a vida acuática",
fontsize=9.5, color=COLOR_REFERENCIA, fontweight="bold")
# Línea: pH 3 — drenaje ácido de mina
ax.axvline(x=PH_MUY_ACIDO, color=COLOR_ALERTA, linewidth=1.5, linestyle="--", alpha=0.8)
ax.text(PH_MUY_ACIDO + 0.05, y_max * 0.72, "pH 3 — drenaje ácido de mina",
fontsize=9.5, color=COLOR_ALERTA, fontweight="bold")
# Anotar la mediana
mediana_ph = seepages["pH"].median()
ax.axvline(x=mediana_ph, color=COLOR_DATOS, linewidth=2.5)
ax.annotate(
f"Mediana de las\n10 surgencias: pH {mediana_ph:.1f}",
xy=(mediana_ph, y_max * 0.55),
xytext=(4.3, y_max * 0.55),
fontsize=10, color=COLOR_DATOS, fontweight="bold",
arrowprops=dict(arrowstyle="->", color=COLOR_DATOS, lw=1.5),
)
# Flecha bidireccional: distancia mediana ↔ neutro
ax.annotate(
"", xy=(PH_NEUTRO, y_max * 0.35), xytext=(mediana_ph, y_max * 0.35),
arrowprops=dict(arrowstyle="<->", color="#666666", lw=1.5),
)
diff_ph = PH_NEUTRO - mediana_ph
ax.text(
(PH_NEUTRO + mediana_ph) / 2, y_max * 0.38,
f"Δ pH = {diff_ph:.1f} → ácido {10**diff_ph:,.0f}× más concentrado".replace(",", "."),
fontsize=10, color="#666666", ha="center", fontweight="bold",
)
ax.set_xlabel("pH", fontsize=11)
ax.set_ylabel("Número de surgencias", fontsize=11)
ax.set_xlim(2.2, 7.5)
ax.set_title("¿Qué tan ácidas son las 10 surgencias frente a la escala normal?",
fontsize=14, fontweight="bold", pad=28)
ax.text(0.5, 1.03,
"Distribución de pH de las surgencias activas vs. referencias biológicas",
transform=ax.transAxes, fontsize=10, color="#666666", ha="center")
fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color="#999999", style="italic")
plt.savefig("figuras/histograma_ph_referencias.png", dpi=200, bbox_inches="tight")
plt.show()
print(f"\nDe las 10 surgencias:")
print(f" · pH < 3.5 (drenaje ácido de mina): {(seepages['pH'] < 3.5).sum()} de 10")
print(f" · pH < 5 (tóxico para vida acuática): {(seepages['pH'] < 5).sum()} de 10")
print(f" · pH < 7 (ácidas vs agua pura): {(seepages['pH'] < 7).sum()} de 10")
De las 10 surgencias:
· pH < 3.5 (drenaje ácido de mina): 7 de 10
· pH < 5 (tóxico para vida acuática): 8 de 10
· pH < 7 (ácidas vs agua pura): 10 de 10
Lo que los datos del paper soportan#
Afirmación |
¿Soportada? |
Detalle |
|---|---|---|
Hay 130 arroyos con signos de oxidación de sulfuros en ~59.500 km² del Peel |
✅ |
Texto literal del paper (p.3). La distribución por tamaño suma 136, no 130 — el paper explica que hay solapamiento; usamos el número canónico. |
Las surgencias activas tienen pH 2.7–6.1 |
✅ |
Figura S8F del preprint, 10 puntos muestreados. Mediana del dataset: pH 3.1. |
Cadmio y zinc superan el umbral CCME en ~90–100% de las muestras de los 3 arroyos |
✅ |
Table S1 directa. Cd: 98% (KM99), 92% (KM71), 100% (KM175). |
El Ogilvie sube +12 mg/L de sulfato por año |
⚠️ |
Tasa correcta para el período 2016–2025 (p=0,04). El paper aclara que es significativa pero marginal; en períodos previos no lo era. |
El calentamiento es el motor del fenómeno |
⚠️ |
Relación causal inferida, consistente con la física conocida (oxidación expuesta por deshielo) — el estudio es observacional, no demuestra causalidad. El paper usa lenguaje atenuado: «signals a major perturbation», «an emergent climate-geosphere feedback». |
El fenómeno es regional, no solo local del TWO |
⚠️ |
Proyección basada en la distribución de litologías ricas en sulfuros + el dato histórico de 19% de 10.578 arroyos del este de Yukon con pH<5 (1976–2001). El paper enmarca como «sugiere», no como medición directa. |
Limitaciones que el paper reconoce:
Diseño observacional (2018–2025 + series ECCC 1979–2025): no hay control experimental.
El número de surgencias muestreadas (n=10) es pequeño frente a la escala del fenómeno.
Los datos originales completos viven en el repositorio Dryad oficial; nosotros usamos tablas resumen del preprint EarthArXiv (el contenido es idéntico al paper publicado).
La causalidad entre calentamiento y acidificación es químicamente sólida pero no aislada de otros factores locales (erosión, hidrología).
Ahora tú#
Tres preguntas para explorar con los datos cargados:
¿Qué metal tiene la mayor variabilidad entre arroyos? Compara
streams.groupby('elemento')['pct_exceed_guideline'].std()y mira cuál depende más del sitio que del fenómeno.¿Las surgencias más ácidas son también las más concentradas en zinc? Calcula la correlación entre
seepages['pH']yseepages['Zn_mg_L']— ¿es monótona?El Ogilvie sube sulfato 6 veces más rápido que el Peel. Si la tasa actual continúa lineal, ¿en cuántos años se igualan en concentración absoluta? (necesitas inventar una concentración baseline 2016 — el paper no la da explícita; usa la cifra que prefieras y revisa la sensibilidad).
# --- EXPERIMENTA AQUÍ ---
# Pregunta 2 resuelta: ¿pH vs zinc en las surgencias?
from scipy import stats
# Spearman (no asumimos normalidad — n=10 muy pequeño)
rho, p = stats.spearmanr(seepages["pH"], seepages["Zn_mg_L"])
print(f"Correlación Spearman pH vs Zinc (n={len(seepages)}): ρ = {rho:.2f}, p = {p:.3f}")
print(f" → {'monótona negativa significativa' if rho < -0.5 and p < 0.05 else 'no estadísticamente concluyente con n=10'}")
print(f" Más ácida = más zinc disuelto (química esperada del drenaje ácido)")
# Ahora cambia "Zn_mg_L" por "Al_mg_L", "Ni_mg_L", "Cd_mg_L" y compara
Correlación Spearman pH vs Zinc (n=10): ρ = -0.49, p = 0.150
→ no estadísticamente concluyente con n=10
Más ácida = más zinc disuelto (química esperada del drenaje ácido)
Fuentes#
Paper: Abrupt stream acidification and metal mobilization from permafrost degradation
Science, 2026-05-21 · paywall — acceso vía preprint Open Access
Preprint Open Access: Stream acidification and metal mobilization linked to permafrost degradation
EarthArXiv, 2025-09-13 — fuente real de extracción de tablas y figuras
Datos canónicos y de referencia:
Dryad — Data from: Abrupt stream acidification and metal mobilization from permafrost degradation
Government of Canada (ECCC) — National Long-term Water Quality Monitoring Data
38 afirmaciones del notebook verificadas contra estas fuentes
Repo: github.com/Ciencia-a-Mordiscos/lab — notebook reproducible bajo licencia MIT. Video Ciencia a Mordiscos: [Pendiente]