Precipitation and Drought Records Collection

10-Year Historical Records for South Dakota and Oglala Lakota County

Author

Antigravity Coding Assistant

Published

July 19, 2026

Introduction

This document contains Python code to programmatically retrieve, process, and store 10 years of historical data for: 1. Weekly Drought Monitor records for Oglala Lakota County (FIPS 46102) and Todd County (FIPS 46121), South Dakota. 2. Daily Precipitation records for the State of South Dakota (state-wide mean).

The collected datasets are saved to the data/ directory as: - data/oglala_lakota_drought_weekly.csv - data/todd_drought_weekly.csv - data/south_dakota_precipitation_daily.csv


Data Sources & Citations

  1. U.S. Drought Monitor (USDM)
    • Source: National Drought Mitigation Center (NDMC), University of Nebraska-Lincoln, USDA, and NOAA.
    • Description: Weekly reports of drought severity classifications (None, D0-D4) indicating the percentage of area affected.
    • Data Access: Retrieved via the USDM REST Data Services API: https://usdmdataservices.unl.edu/api/.
    • Citation: > U.S. Drought Monitor. (2026). U.S. Drought Monitor data archives. National Drought Mitigation Center. Retrieved from https://droughtmonitor.unl.edu/
  2. Applied Climate Information System (ACIS)
    • Source: NOAA Regional Climate Centers (RCCs).
    • Description: Daily precipitation observations aggregated and interpolated across the state of South Dakota, computed as a statewide mean.
    • Data Access: Retrieved via the ACIS GridData API: https://data.rcc-acis.org/GridData.
    • Citation: > Applied Climate Information System (ACIS). (2026). Gridded Daily Precipitation (NRCC Interpolated Grid). NOAA Regional Climate Centers. Retrieved from https://www.rcc-acis.org/

Data Collection Pipeline

The following Python block dynamically computes the date range for the past 10 years and calls the respective APIs to fetch and store the records.

Code
import os
import io
import datetime
import requests
import pandas as pd

# Create target data directory
os.makedirs("data", exist_ok=True)

# Calculate dynamic dates for the past 10 years
today = datetime.date.today()
try:
    start_date = today.replace(year=today.year - 10)
except ValueError:
    # Handle leap year edge case (Feb 29)
    start_date = today.replace(year=today.year - 10, day=28)

start_date_str = start_date.strftime("%Y-%m-%d")
end_date_str = today.strftime("%Y-%m-%d")

print(f"Collection timeframe: {start_date_str} to {end_date_str}")

# --- Task 1: Fetch U.S. Drought Monitor Data for Counties ---
counties = {
    "46102": ("Oglala Lakota County", "oglala_lakota_drought_weekly.csv"),
    "46121": ("Todd County", "todd_drought_weekly.csv")
}

for fips_code, (county_name, filename) in counties.items():
    usdm_url = (
        f"https://usdmdataservices.unl.edu/api/CountyStatistics/GetDroughtSeverityStatisticsByAreaPercent"
        f"?aoi={fips_code}&startdate={start_date_str}&enddate={end_date_str}&statisticsType=1"
    )
    print(f"Querying USDM API for {county_name}...")
    usdm_response = requests.get(usdm_url)
    if usdm_response.status_code == 200:
        usdm_csv = usdm_response.text
        usdm_path = os.path.join("data", filename)
        with open(usdm_path, "w", encoding="utf-8") as f:
            f.write(usdm_csv)
        print(f"-> Successfully saved weekly drought data to {usdm_path}")
    else:
        print(f"-> Error fetching USDM data for {county_name}: {usdm_response.status_code}")
        print(usdm_response.text[:500])

# --- Task 2: Fetch Daily Precipitation Records for South Dakota (State Mean) ---
acis_url = "https://data.rcc-acis.org/GridData"
acis_payload = {
    "state": "sd",
    "sdate": start_date_str,
    "edate": end_date_str,
    "grid": "1",
    "elems": [{"name": "pcpn", "area_reduce": "state_mean"}]
}

print(f"Querying ACIS GridData API...")
acis_response = requests.post(acis_url, json=acis_payload)
if acis_response.status_code == 200:
    acis_json = acis_response.json()
    raw_records = acis_json.get("data", [])
    
    # Process the nested list format into a standard DataFrame
    processed = []
    for date_val, val_dict in raw_records:
        val = val_dict.get("SD") if isinstance(val_dict, dict) else None
        # Handle potential trace ('T') or missing ('M') string codes
        if val is None or val in ["M", "M ", " M", ""]:
            val_float = None
        elif val == "T" or val == "T ":
            val_float = 0.0001
        else:
            try:
                val_float = float(val)
            except ValueError:
                val_float = None
        processed.append({"date": date_val, "precipitation_inches": val_float})
        
    df_precip = pd.DataFrame(processed)
    precip_path = os.path.join("data", "south_dakota_precipitation_daily.csv")
    df_precip.to_csv(precip_path, index=False)
    print(f"-> Successfully saved daily precipitation data to {precip_path}")
else:
    print(f"-> Error fetching ACIS data: {acis_response.status_code}")
    print(acis_response.text[:500])
Collection timeframe: 2016-07-22 to 2026-07-22
Querying USDM API for Oglala Lakota County...
-> Successfully saved weekly drought data to data/oglala_lakota_drought_weekly.csv
Querying USDM API for Todd County...
-> Successfully saved weekly drought data to data/todd_drought_weekly.csv
Querying ACIS GridData API...
-> Successfully saved daily precipitation data to data/south_dakota_precipitation_daily.csv

Data Previews & Summaries

Let’s load the saved datasets to verify their content and print summary statistics.

U.S. Drought Monitor Data Summary (Oglala Lakota County)

Code
df_usdm = pd.read_csv("data/oglala_lakota_drought_weekly.csv")

# Clean dates and sort chronologically
df_usdm["ValidStart"] = pd.to_datetime(df_usdm["ValidStart"])
df_usdm["ValidEnd"] = pd.to_datetime(df_usdm["ValidEnd"])
df_usdm = df_usdm.sort_values("ValidStart").reset_index(drop=True)

print(f"Oglala Lakota County weekly USDM records: {len(df_usdm)}")
df_usdm[["ValidStart", "County", "None", "D0", "D1", "D2", "D3", "D4"]].tail()
Oglala Lakota County weekly USDM records: 522
ValidStart County None D0 D1 D2 D3 D4
517 2026-06-16 Oglala Lakota County 0.0 100.0 100.0 97.83 34.58 0.0
518 2026-06-23 Oglala Lakota County 0.0 100.0 100.0 97.83 34.58 0.0
519 2026-06-30 Oglala Lakota County 0.0 100.0 100.0 97.83 18.48 0.0
520 2026-07-07 Oglala Lakota County 0.0 100.0 100.0 97.83 18.48 0.0
521 2026-07-14 Oglala Lakota County 0.0 100.0 100.0 97.83 18.48 0.0

Oglala Lakota Drought Summary Stats

Code
df_usdm[["None", "D0", "D1", "D2", "D3", "D4"]].describe()
None D0 D1 D2 D3 D4
count 522.000000 522.000000 522.000000 522.000000 522.000000 522.0
mean 34.515670 65.484330 47.302778 17.747835 4.817050 0.0
std 46.187019 46.187019 46.171407 32.746903 16.674187 0.0
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.0
25% 0.000000 0.000000 0.000000 0.000000 0.000000 0.0
50% 0.000000 100.000000 42.270000 0.000000 0.000000 0.0
75% 100.000000 100.000000 100.000000 13.580000 0.000000 0.0
max 100.000000 100.000000 100.000000 100.000000 97.570000 0.0

U.S. Drought Monitor Data Summary (Todd County)

Code
df_todd = pd.read_csv("data/todd_drought_weekly.csv")

# Clean dates and sort chronologically
df_todd["ValidStart"] = pd.to_datetime(df_todd["ValidStart"])
df_todd["ValidEnd"] = pd.to_datetime(df_todd["ValidEnd"])
df_todd = df_todd.sort_values("ValidStart").reset_index(drop=True)

print(f"Todd County weekly USDM records: {len(df_todd)}")
df_todd[["ValidStart", "County", "None", "D0", "D1", "D2", "D3", "D4"]].tail()
Todd County weekly USDM records: 522
ValidStart County None D0 D1 D2 D3 D4
517 2026-06-16 Todd County 0.0 100.0 100.0 41.99 0.0 0.0
518 2026-06-23 Todd County 0.0 100.0 100.0 41.99 0.0 0.0
519 2026-06-30 Todd County 0.0 100.0 73.2 0.00 0.0 0.0
520 2026-07-07 Todd County 0.0 100.0 73.2 0.00 0.0 0.0
521 2026-07-14 Todd County 0.0 100.0 73.2 0.00 0.0 0.0

Todd County Drought Summary Stats

Code
df_todd[["None", "D0", "D1", "D2", "D3", "D4"]].describe()
None D0 D1 D2 D3 D4
count 522.000000 522.000000 522.000000 522.000000 522.000000 522.0
mean 43.378467 56.621533 28.607739 11.567433 2.533314 0.0
std 46.856031 46.856031 41.678066 29.347983 12.100079 0.0
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.0
25% 0.000000 0.000000 0.000000 0.000000 0.000000 0.0
50% 1.850000 98.150000 0.000000 0.000000 0.000000 0.0
75% 100.000000 100.000000 73.015000 0.000000 0.000000 0.0
max 100.000000 100.000000 100.000000 100.000000 79.050000 0.0

Daily Precipitation Data Summary

Code
df_precip = pd.read_csv("data/south_dakota_precipitation_daily.csv")
df_precip["date"] = pd.to_datetime(df_precip["date"])
df_precip = df_precip.sort_values("date").reset_index(drop=True)

print(f"Number of daily precipitation records: {len(df_precip)}")
df_precip.tail()
Number of daily precipitation records: 3653
date precipitation_inches
3648 2026-07-18 0.007313
3649 2026-07-19 0.024344
3650 2026-07-20 0.045114
3651 2026-07-21 0.000978
3652 2026-07-22 NaN

Precipitation Summary Stats

Code
df_precip["precipitation_inches"].describe()
count    3652.000000
mean        0.059845
std         0.123770
min         0.000000
25%         0.000670
50%         0.008960
75%         0.056663
max         1.374540
Name: precipitation_inches, dtype: float64

Visualizations

Let’s visualize the trends over the 10-year period.

Code
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid")
fig, axes = plt.subplots(3, 1, figsize=(7.5, 9.5), sharex=False)

# 1. Plot Weekly Drought Monitor Trends (Oglala Lakota County)
axes[0].fill_between(df_usdm["ValidStart"], df_usdm["D0"], color="#ffff00", alpha=0.5, label="D0 (Abnormally Dry)")
axes[0].fill_between(df_usdm["ValidStart"], df_usdm["D1"], color="#fcd37f", alpha=0.6, label="D1 (Moderate Drought)")
axes[0].fill_between(df_usdm["ValidStart"], df_usdm["D2"], color="#ffaa00", alpha=0.7, label="D2 (Severe Drought)")
axes[0].fill_between(df_usdm["ValidStart"], df_usdm["D3"], color="#e60000", alpha=0.8, label="D3 (Extreme Drought)")
axes[0].fill_between(df_usdm["ValidStart"], df_usdm["D4"], color="#730000", alpha=0.9, label="D4 (Exceptional Drought)")
axes[0].set_title("Weekly Drought Monitor Area Severity (%) - Oglala Lakota County", fontsize=12, fontweight="bold")
axes[0].set_ylabel("Cumulative % Area affected", fontsize=10)
axes[0].set_ylim(0, 100)
axes[0].legend(loc="upper right", frameon=True, fontsize=8)

# 2. Plot Weekly Drought Monitor Trends (Todd County)
axes[1].fill_between(df_todd["ValidStart"], df_todd["D0"], color="#ffff00", alpha=0.5, label="D0 (Abnormally Dry)")
axes[1].fill_between(df_todd["ValidStart"], df_todd["D1"], color="#fcd37f", alpha=0.6, label="D1 (Moderate Drought)")
axes[1].fill_between(df_todd["ValidStart"], df_todd["D2"], color="#ffaa00", alpha=0.7, label="D2 (Severe Drought)")
axes[1].fill_between(df_todd["ValidStart"], df_todd["D3"], color="#e60000", alpha=0.8, label="D3 (Extreme Drought)")
axes[1].fill_between(df_todd["ValidStart"], df_todd["D4"], color="#730000", alpha=0.9, label="D4 (Exceptional Drought)")
axes[1].set_title("Weekly Drought Monitor Area Severity (%) - Todd County", fontsize=12, fontweight="bold")
axes[1].set_ylabel("Cumulative % Area affected", fontsize=10)
axes[1].set_ylim(0, 100)
axes[1].legend(loc="upper right", frameon=True, fontsize=8)

# 3. Plot Monthly Aggregated Precipitation (South Dakota Mean)
df_precip["year_month"] = df_precip["date"].dt.to_period("M")
df_monthly = df_precip.groupby("year_month")["precipitation_inches"].sum().reset_index()
df_monthly["date_index"] = df_monthly["year_month"].dt.to_timestamp()

axes[2].bar(df_monthly["date_index"], df_monthly["precipitation_inches"], width=25, color="#1f77b4", alpha=0.8)
df_monthly["rolling_mean"] = df_monthly["precipitation_inches"].rolling(12, center=True).mean()
axes[2].plot(df_monthly["date_index"], df_monthly["rolling_mean"], color="#d62728", linewidth=2.5, label="12-Month Rolling Mean")
axes[2].set_title("South Dakota Monthly Total Precipitation (Statewide Average)", fontsize=12, fontweight="bold")
axes[2].set_ylabel("Precipitation (inches)", fontsize=10)
axes[2].legend(loc="upper right", frameon=True, fontsize=8)

plt.tight_layout()
plt.show()