Are downpours getting more intense, even if the yearly total isn't changing?
Draw a rectangle, then check which collections cover it. Most NASA data is global, so a big box matches a lot.
72.6, 18 → 73.6, 19.6 (Mumbai & the Konkan coast)Rainfall intensity is about *how* the rain arrives, not just how much. A place can get the same number of millimetres in a year but have it dumped in fewer, more violent bursts instead of spread over many gentle days. This page measures the biggest single-day and multi-day downpours each year and asks whether they're climbing, even when the yearly total stays flat.
Are downpours getting more intense, even if the yearly total isn’t changing?
Rainfall intensity is about how the rain arrives, not just how much. A place can get the same number of millimetres in a year but have it dumped in fewer, more violent bursts instead of spread over many gentle days. This page measures the biggest single-day and multi-day downpours each year and asks whether they’re climbing, even when the yearly total stays flat.
Which data to use, and how
Use this dataset: GPM IMERG daily (short name GPM_3IMERGDF),
a satellite rainfall map that gives you one rainfall value per day for every ~10 km square,
from 2000 to today. It’s the easiest to work with because it’s already daily, which is the
grain you need for “biggest day of the year.” (The half-hourly version,
GPM IMERG half-hourly GPM_3IMERGHH, is finer but heavier. Skip it
until you specifically need sub-daily bursts.)
Then do four steps (the runnable code further down does all of this; this is just the idea):
- Pick your area and years. A small latitude/longitude box, plus the span of years you care about. Average the daily rainfall inside the box so you get one number per day.
- Squeeze each year into a few “intensity” numbers. The biggest 1-day total (Rx1day), the biggest 5-day total (Rx5day), the average rain on rainy days (SDII, simple daily intensity), and how much rain fell on the most extreme days (R95p / R99p, the totals from days above the 95th and 99th percentile of wet days).
- Stack the years up. You now have a small table: one row per year, one column per intensity number.
- Fit a trend and test it. For each intensity number, draw the long-term line (a Theil–Sen slope, which ignores freak outliers) and check it’s real and not noise (a Mann–Kendall test, which just asks “is this mostly going up or mostly going down?”). Rising intensity numbers while the yearly total stays flat means downpours are getting fiercer.
That’s the whole method. Below, “How a scientist answers this” names the exact tools, and the Run it cell does it live on synthetic data so you can see each step work.
What you can find out
- The biggest 1-day and 5-day rainfall each year (Rx1day, Rx5day).
- How much of the year’s rain comes from a few extreme days (R95p / R99p totals and their share of the annual rain).
- The average intensity of a rainy day (SDII: mean rain per wet day).
- Whether any of these trends are real. The Theil–Sen slope says how fast, the Mann–Kendall test says whether it’s bigger than the noise.
- The key decoupling: whether the extremes are rising even though the yearly sum is holding steady. That gap is the whole point of the question.
What it can’t tell you
- The true size of the worst storms. The satellite underestimates the heaviest rain rates, so the absolute numbers run low. The trend (is it going up?) is usable. The absolute design-value rainfall an engineer needs requires ground gauges or radar.
- Cloudbursts smaller than a pixel. Each value covers a ~10 km square, so a violent, very local cloudburst gets smeared out into the wider average. The half-hourly product and ground gauges see more of it.
- Why it’s changing. A rising trend doesn’t say whether it’s human-caused warming or just natural year-to-year swings. Separating those needs a different toolkit (detection-attribution methods).
- Weak trends with confidence. IMERG is only ~25 years old. That’s a short record, so faint trends are hard to prove. Treat borderline (marginal) Mann–Kendall results with caution.
Gotchas to watch for
- The satellite lowballs the heaviest rain. Compared to ground gauges, IMERG under-measures the most intense downpours, worst during heavy monsoon storms (convective rain), so your absolute extremes will come out too low. The safe move is to report change over time rather than absolute thresholds, and to cross-check against CHIRPS (a gauge-blended rainfall product) or local India Meteorological Department (IMD) gauges.
- An index means nothing without its definition. “Rx1day” or “R95p” only make sense if you state exactly how they’re computed and which years set the percentile baseline; leave that out and two people can pull different answers from the same data. Pin the baseline period (the years used to define “extreme”) and never let it drift year to year.
- Mountains fool the satellite. Along the Western Ghats / Konkan coast, rain forced up by terrain (orographic rain) is systematically under-sensed from space, so coastal and hill totals read low. Cross-check those with gauges and treat absolute coastal numbers skeptically.
- The record is short. ~25 years limits how confidently you can spot weak trends. You can reach back to 1998 with the older TRMM 3B42 product, but only carefully: stitching two different instruments can create fake jumps (inter-product inhomogeneity).
- Use the current version (V07). V06 is deprecated, so reprocess any cached pre-2023 data. Keep in mind too that the polished “Final” run lags real time by ~3.5 months, while the faster Late/Early runs trade accuracy for speed.
The real-data code
The Run it cell above runs this method on synthetic data with no login. Below is the whole recipe against the real archive: search, open each year’s IMERG granules, average to one number per day, squeeze each year into its extreme indices, fit Theil–Sen + Mann–Kendall, and plot it. It needs a free Earthdata Login.
import earthaccess
import xarray as xr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# pip install pymannkendall (Theil–Sen slope + Mann–Kendall test)
import pymannkendall as mk
earthaccess.login(strategy="netrc")
# AOI: Mumbai & the Konkan coast, as (W, S, E, N)
aoi = (72.6, 18.0, 73.6, 19.6)
years = range(2001, 2026)
wet_day = 1.0 # mm/day threshold defining a "wet day"
def annual_indices(daily, p95, p99):
"""daily: 1-D pandas Series of AOI-mean mm/day for one calendar year."""
rx1 = daily.max() # Rx1day: biggest single day
rx5 = daily.rolling(5).sum().max() # Rx5day: biggest 5-day run
wet = daily[daily >= wet_day]
sdii = wet.mean() # mean rain per wet day
r95p = wet[wet > p95].sum() # rain on the wettest ~5% of days
r99p = wet[wet > p99].sum() # rain on the wettest ~1% of days
annual = daily.sum() # total rain (the "is it flat?" control)
return dict(Rx1day=rx1, Rx5day=rx5, SDII=sdii, R95p=r95p, R99p=r99p, Annual=annual)
# 1. One number per day, per year: open each year's IMERG granules, clip to the box,
# and average the daily precip field over the AOI. (IMERG dims: lon, lat, time.)
def daily_series(yr):
grans = earthaccess.search_data(
short_name="GPM_3IMERGDF", bounding_box=aoi,
temporal=(f"{yr}-01-01", f"{yr}-12-31"))
ds = xr.open_mfdataset(earthaccess.open(grans), combine="by_coords")
box = ds["precipitation"].sel( # V07 units are mm/day
lon=slice(aoi[0], aoi[2]), lat=slice(aoi[1], aoi[3]))
return box.mean(dim=["lon", "lat"]).to_series().clip(lower=0)
# 2. Fix the wet-day percentile baseline ONCE (first 10 yrs), so later years compare.
baseline = pd.concat([daily_series(y) for y in list(years)[:10]])
bwet = baseline[baseline >= wet_day]
p95, p99 = bwet.quantile(0.95), bwet.quantile(0.99)
# 3. Squeeze every year into its extreme indices -> one row per year.
records = {yr: annual_indices(daily_series(yr), p95, p99) for yr in years}
df = pd.DataFrame(records).T
# 4. Robust trend + significance per index (both shrug off freak outliers).
yr_arr = np.array(list(years))
for col in df.columns:
res = mk.original_test(df[col].values)
tag = "(significant)" if res.h else "(not distinguishable from noise)"
print(f"{col:7s} slope/yr: {res.slope:+.3f} p={res.p:.3f} -> {res.trend} {tag}")
# Verdict: are downpours intensifying while the yearly total stays flat?
rx1, ann = mk.original_test(df.Rx1day.values), mk.original_test(df.Annual.values)
print(f"VERDICT: Rx1day {rx1.trend} ({rx1.slope:+.2f} mm/yr), "
f"annual total {ann.trend} -> "
f"{'intensifying bursts' if rx1.h and not ann.h else 'no clean decoupling'}")
# 5. Plot Rx1day with its Theil–Sen line, annual total alongside for contrast.
fig, ax = plt.subplots()
ax.plot(yr_arr, df.Rx1day, "o-", label="Rx1day (mm)")
ax.plot(yr_arr, rx1.intercept + rx1.slope * (yr_arr - yr_arr[0]), "k--",
label=f"Theil–Sen {rx1.slope:+.2f} mm/yr")
ax2 = ax.twinx(); ax2.plot(yr_arr, df.Annual, "C1.", alpha=.5, label="annual total")
ax.set_xlabel("year"); ax.set_ylabel("Rx1day (mm)"); ax2.set_ylabel("annual (mm)")
ax.legend(loc="upper left"); plt.tight_layout(); plt.show()
# Before you trust it: IMERG lowballs the heaviest rates, so cross-check the trend
# against CHIRPS or an IMD gauge and report relative change, not absolute thresholds.
Where the data comes from
Everything you need comes from NASA through one free login (Earthdata Login). GPM IMERG, both daily and half-hourly, lives in a single archive (GES DISC). The optional cross-checks, CHIRPS (from UCSB/CHC) and any IMD gauge data, come from outside NASA and are separate steps. So the core analysis is a single dataset and a single sign-in.
Sources
- GPM IMERG: https://gpm.nasa.gov/data/imerg
- IMERG V07 release notes: https://gpm.nasa.gov/sites/default/files/2023-07/IMERG_V07_ReleaseNotes_230713-signed.pdf
- ETCCDI extreme climate indices: https://etccdi.pacificclimate.org/list_27_indices.shtml
- CHIRPS: https://www.chc.ucsb.edu/data/chirps
- Mann–Kendall / Theil–Sen (
pymannkendall): https://pypi.org/project/pymannkendall/
Make it yours → Set your AOI and the years; the notebook computes the standard extreme indices (Rx1day, Rx5day, R95p, R99p, SDII) and runs Theil–Sen + Mann–Kendall on each. Choose the percentile baseline period and swap IMERG for CHIRPS to check whether the trend is robust across products.
A safe place to practise the method on GPM_3IMERGDF. The code is the real earthaccess workflow, but we feed it stand-in data shaped like the real product — so you can run it, change the numbers, and break it without an account. When you want the real data, use the recipe under “The real-data code” above, or download the notebook below.
Datasets used
Computed · real data Worked examples, computed end-to-end on real satellite data — not yet scientist-verified · expand ▾
We ran the analysis on real data for Indian regions and report exactly what came back — including the honest nulls and refusals. Each card shows the slope, significance, and (for rainfall) a gauge cross-check.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
The data doesn't support a rainfall change here — an earned null, not a missing answer.
…and what it refuses to answer
trend span < 10y — the validator refuses ill-posed questions rather than guessing. That discipline is the point.