How to Analyse US Drought Monitor County Statistics in Python

On this page

Problem statement

The US Drought Monitor publishes a map every week, and behind the map a table: for each of the 3,109 counties of the lower 48 states and DC, the share of its area in each drought category, from D0 (abnormally dry) to D4 (exceptional drought). Pulled for January 2000 to December 2025 that is 4,218,913 county-weeks, and the questions people bring to it are simple β€” which counties are most often in drought, which year was worst, how much of the country was dry in a given week. They go wrong on the first step, because the service's default columns are cumulative: D1 means "D1 or worse", not "exactly D1". This guide reads the table, turns each county-week into one severity number, and answers those questions for 2000–2025.

Quick answer

Download each state's county statistics from the Drought Monitor web service, concatenate them, and work on the D1 column directly: in the cumulative form it already is the share of the county in moderate drought or worse. Counting the weeks with at least half the county in D1+ ranks the counties.

usdm["year"] = usdm.MapDate.dt.year
usdm["in_d1"] = usdm.D1 >= 50                       # half the county or more in moderate drought or worse
share = usdm.groupby(["FIPS", "County", "State"]).in_d1.mean().sort_values()
print(f"median county: {share.median():.1%} of weeks with half its area in D1 or worse, 2000-2025")
print(share.tail(5).round(3).to_string())
print(share.head(3).round(3).to_string())
median county: 19.6% of weeks with half its area in D1 or worse, 2000-2025
FIPS   County           State
04021  Pinal County     AZ       0.628
04003  Cochise County   AZ       0.630
35023  Hidalgo County   NM       0.637
04001  Apache County    AZ       0.648
35031  McKinley County  NM       0.648
FIPS   County           State
36007  Broome County    NY       0.011
36077  Otsego County    NY       0.011
36017  Chenango County  NY       0.012

The median county spent 19.6% of the 26 years with at least half its area in D1 or worse. The driest counties are all in Arizona and New Mexico, near two thirds of all weeks (Apache and McKinley, 0.648); the wettest are in upstate New York, where Broome and Otsego counties crossed that line in about one week in a hundred.

Map of the lower 48 states' counties shaded by mean weekly drought severity index from 2000 to 2025, darkest across Arizona, New Mexico and Nevada and palest in the North-East Map of the lower 48 states' counties shaded by mean weekly drought severity index from 2000 to 2025, darkest across Arizona, New Mexico and Nevada and palest in the North-East
Mean Drought Severity and Coverage Index per county, 2000–2025: the South-West carries the most drought, upstate New York and Ohio the least.

From the weekly table to a county map

1. One row per county per week

print(usdm.columns.tolist())
print(usdm.MapDate.dt.day_name().value_counts().to_dict())
print(f"duplicates: {usdm.duplicated(['FIPS', 'MapDate']).sum()}; weeks per county: median {usdm.groupby('FIPS').size().median():.0f}")
['MapDate', 'FIPS', 'County', 'State', 'None', 'D0', 'D1', 'D2', 'D3', 'D4', 'ValidStart', 'ValidEnd', 'StatisticFormatID', 'year', 'in_d1']
{'Tuesday': 4218913}
duplicates: 0; weeks per county: median 1357

Every map is dated on a Tuesday, there are no duplicate county-weeks, and the median county has all 1,357 weekly maps. ValidStart and ValidEnd give the week each map stands for; Drought Monitor dates are a week off explains how to join them to daily data.

2. Cumulative columns, read correctly

row = usdm[(usdm.FIPS == "31109") & (usdm.MapDate == "2012-09-25")]
print(row[["County", "State", "None", "D0", "D1", "D2", "D3", "D4"]].to_string(index=False))
cat = usdm[["D0", "D1", "D2", "D3", "D4"]].sub(usdm[["D1", "D2", "D3", "D4"]].assign(D5=0).values)
print("same week as categories:", cat.loc[row.index].round(2).to_dict("records"))
          County State  None    D0    D1    D2    D3  D4
Lancaster County    NE   0.0 100.0 100.0 100.0 100.0 0.0
same week as categories: [{'D0': 0.0, 'D1': 0.0, 'D2': 0.0, 'D3': 100.0, 'D4': 0.0}]

On 25 September 2012 Lancaster County, Nebraska, shows 100 in D0, D1, D2 and D3: not four times the county, but the whole county in D3. Subtracting each column from the one before turns the cumulative form back into categories, and the same week becomes 100% in exactly D3. Summing the cumulative columns gives figures up to 500%; that mistake has its own page.

3. A single number per county-week

w = np.array([0, 1, 2, 3, 4, 5])
levels = pd.concat([usdm["None"], cat], axis=1).values / 100
usdm["dsci"] = 100 * (levels[:, 1:] * w[1:]).sum(axis=1)            # Drought Severity and Coverage Index, 0-500
print(usdm.dsci.describe(percentiles=[0.5, 0.9, 0.99]).round(0).to_string())
lan = usdm[usdm.FIPS == "31109"].set_index("MapDate").dsci
print("Lancaster County, Nebraska, yearly maximum:", lan.groupby(lan.index.year).max().loc[[2002, 2006, 2012, 2018, 2022, 2023]].round(0).to_dict())
count    4218913.0
mean          80.0
std          118.0
min            0.0
50%            0.0
90%          281.0
99%          473.0
max          500.0
Lancaster County, Nebraska, yearly maximum: {2002: 321.0, 2006: 300.0, 2012: 400.0, 2018: 101.0, 2022: 300.0, 2023: 488.0}

The Drought Severity and Coverage Index (DSCI) weights each category's share by 1 for D0 up to 5 for D4, so a county wholly in D4 scores 500 and one with no drought scores 0. The median county-week scores 0 β€” most weeks, most counties are not dry β€” while the top tenth of weeks score 281 or more. Lancaster County's worst years show how the index separates them: 2012 peaked at 400 (all D3), 2023 at 488, almost the whole county in D4.

Area chart of Lancaster County, Nebraska's weekly drought index from 2000 to 2025 with peaks labelled for 2002, 2006, 2012, 2022 and 2023 Area chart of Lancaster County, Nebraska's weekly drought index from 2000 to 2025 with peaks labelled for 2002, 2006, 2012, 2022 and 2023
Lancaster County's weekly DSCI: 2012 peaked at 400 with the whole county in D3, 2023 at 488.

4. Map it

import geopandas as gpd

counties = gpd.read_file("data/cb_2023_us_county_20m.zip").to_crs(5070)
counties = counties[~counties.STATEFP.isin(["02", "15", "72"])]
dsci = usdm.groupby("FIPS").dsci.mean().rename("mean_dsci")
m = counties.merge(dsci, left_on="GEOID", right_index=True, how="left")
print(f"counties on the map: {len(m):,}; without Drought Monitor statistics: {m.mean_dsci.isna().sum()}")
print(m.groupby("STUSPS").mean_dsci.mean().sort_values().round(0).iloc[[0, 1, -2, -1]].to_string())
counties on the map: 3,109; without Drought Monitor statistics: 0
STUSPS
OH     27.0
NY     28.0
NV    183.0
AZ    187.0

All 3,109 counties in the Census boundary file have Drought Monitor statistics, because both now use Connecticut's planning regions. Averaged over 26 years, Ohio and New York counties score 27 and 28; Nevada and Arizona 183 and 187.

Worst years and the weekly share of counties

The data used on this page

The US Drought Monitor's county statistics web service, weekly from January 2000 to December 2025, in its cumulative form (statistics type 1), produced by the National Drought Mitigation Center at the University of Nebraska–Lincoln with USDA and NOAA; and the Census Bureau's 2023 cartographic boundary counties.

from pathlib import Path

import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
API = "https://usdmdataservices.unl.edu/api"
STATES = ["AL", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL", "GA", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD",
          "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI",
          "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY"]
for st in STATES:
    f = Path(f"data/usdm-county-{st}.csv")
    if not f.exists():
        # statisticsType 1 = cumulative percent of each county's area in each category or worse
        r = requests.get(f"{API}/CountyStatistics/GetDroughtSeverityStatisticsByAreaPercent",
                         params={"aoi": st, "startdate": "1/1/2000", "enddate": "12/31/2025", "statisticsType": 1}, timeout=600)
        f.write_text(r.text)
usdm = pd.concat([pd.read_csv(f"data/usdm-county-{st}.csv", dtype={"FIPS": str}) for st in STATES], ignore_index=True)
usdm["MapDate"] = pd.to_datetime(usdm.MapDate.astype(str), format="%Y%m%d")
for name, url in {"cb_2023_us_county_20m.zip": "https://www2.census.gov/geo/tiger/GENZ2023/shp/cb_2023_us_county_20m.zip"}.items():
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(url, timeout=300).content)
print(f"{len(usdm):,} county-weeks, {usdm.FIPS.nunique():,} counties, {usdm.MapDate.nunique():,} weekly maps, "
      f"{usdm.MapDate.min():%Y-%m-%d} to {usdm.MapDate.max():%Y-%m-%d}")
4,218,913 county-weeks, 3,109 counties, 1,357 weekly maps, 2000-01-04 to 2025-12-30

Example 1: each state's worst year

st = usdm.groupby(["State", "year"]).dsci.mean().unstack()
worst = st.idxmax(axis=1)
print(worst.value_counts().head(6).to_string())
2002    13
2012     7
2021     6
2007     3
2000     3
2003     3

For 13 of the 49 areas (48 states and DC), 2002 was the worst year by mean county DSCI; 2012 was the worst for 7 and 2021 for 6. The 2012 drought was the most widespread single event, but 2002 was the worse year for more states because it hit the East as well as the Plains.

Area chart of the weekly share of US counties at least half in moderate drought or worse, peaking in July 2012 and lowest in April 2019 Area chart of the weekly share of US counties at least half in moderate drought or worse, peaking in July 2012 and lowest in April 2019
The share of counties at least half in D1 or worse each week: 60.8% on 17 July 2012, 1.3% on 23 April 2019.

Example 2: the share of counties in drought each week

weekly = usdm.groupby("MapDate").in_d1.mean()
print(f"most counties in D1+: {weekly.max():.1%} on {weekly.idxmax():%d %B %Y}; fewest: {weekly.min():.1%} on {weekly.idxmin():%d %B %Y}")
print(weekly.groupby(weekly.index.year).mean().loc[[2002, 2006, 2011, 2012, 2019, 2022, 2025]].round(3).to_dict())
most counties in D1+: 60.8% on 17 July 2012; fewest: 1.3% on 23 April 2019
{2002: 0.329, 2006: 0.28, 2011: 0.292, 2012: 0.415, 2019: 0.072, 2022: 0.362, 2025: 0.274}

The peak came on 17 July 2012, with 60.8% of counties at least half in D1+; the low on 23 April 2019, at 1.3%. As yearly averages, 2012 (0.415) stands out, with 2022 next (0.362).

Why counties and why cumulative

The Drought Monitor is a weekly expert judgement drawn as polygons on a map, combining precipitation indices, soil moisture, streamflow, vegetation and reports from the ground. The county table is that map intersected with county boundaries, so each row says how much of the county's area the authors placed in each category. Counties are useful because they join to everything else β€” population, crop insurance claims, disaster declarations β€” and they have stayed the same over the whole record except for Connecticut.

The cumulative form exists because "D1 or worse" is what most users ask for: the share of a county in moderate drought or worse is a single column. It is also why naive row sums exceed 100. The DSCI collapses the six shares into one number that rises with both how much of the county is dry and how badly, which is what makes counties and years comparable.

Things that trip people up

  • The service caps each request. Asking for every county for 26 years at once times out; one state at a time works, and saving each response means a re-run reads files instead of the API.
  • FIPS codes are strings. Read them with dtype={"FIPS": str} or Alabama's 01001 becomes 1001 and stops matching the boundary file.
  • A map is a judgement, not a measurement. Category edges are drawn by hand from several indicators, so a change of one category in one week can reflect a new author's reading as much as new rain. Look at runs of weeks, not single ones.
  • Alaska, Hawaii and Puerto Rico are in the service but not in this page's state list; add them to STATES if needed, and use a different projection for the map.

FAQ

Is the D1 column the share in moderate drought only?

Not in the default (cumulative) statistics: it is the share in D1, D2, D3 or D4. Request statisticsType=2 for exact categories, or subtract the next column, as in step 2.

Which counties are most often in drought?

Over 2000–2025, by weeks with half the county or more in D1+, McKinley County, New Mexico, and Apache County, Arizona, each at 0.648 of all weeks.