Skip to main content

Get Weather Data

PyPI Version CI Downloads License Python 3.11+ Docs

Historical daily weather for any US ZIP code or latitude/longitude. NOAA station data (GHCN Daily and GSOD), automatic nearest-station selection, consistent metric or imperial output.

No-code app

Prefer clicking to coding? A Streamlit app gives the same data through a web UI — enter a ZIP or lat/lon, pick dates, get a table, chart, and CSV download. Run it with streamlit run app/streamlit_app.py or deploy it free on Streamlit Community Cloud.

Installation

pip install get-weather-data

Or with uv:

uv pip install get-weather-data

Quick Start

Python API

from get_weather_data import Weather

# Initialize and set up database (downloads ~60MB first time)
weather = Weather()
weather.setup()

# By ZIP code...
result = weather.get("10001", "2024-01-15")
print(f"Max temp: {result.tmax} °C")  # e.g. -1.6
print(f"Precip:  {result.prcp} mm")  # 0.0 means zero, None means no data
print(f"Station: {result.station_name} ({result.station_distance_meters} m away)")

# ...or by coordinates
result = weather.get((40.7484, -73.9967), "2024-01-15")

# Imperial units if you want them
weather_f = Weather(units="imperial")
result = weather_f.get("10001", "2024-01-15")
print(f"Max temp: {result.tmax} °F")

Command Line

# Set up database (first time only)
get-weather setup

# By ZIP or by coordinates; metric by default
get-weather get 10001 2024-01-15
get-weather get "40.75,-73.99" 2024-01-15 --units imperial

# Process a CSV file
get-weather process input.csv output.csv

Online Mode (no setup)

Skip the station-database build by querying NOAA's Climate Data Online API directly (only a small ZIP-coordinates file, a few MB, is cached on first use). Get a free token at https://www.ncdc.noaa.gov/cdo-web/token and set NCDC_TOKEN:

from get_weather_data import Weather

weather = Weather(online=True)  # requires NCDC_TOKEN
result = weather.get("10001", "2024-01-15")
NCDC_TOKEN=your-token get-weather get 10001 2024-01-15 --online

Notes on online mode:

  • Tokens are limited to 5 requests/second and 10,000 requests/day, so process_csv (batch jobs) requires the local database.
  • Same result contract as the local path: nearest reporting station first, same units, real station distances. Online covers GHCN stations only (no GSOD fallback).

Features

  • ZIP or lat/lon everywhere: get(), get_range(), CSV batch, and the CLI all take either a ZIP code or coordinates
  • Consistent units: real metric values (°C, mm, m/s) across the API, CLI, and CSV output — or units="imperial" (°F, in, mph)
  • pandas-ready: Weather.get_frame(...) returns a tidy DataFrame (pip install get-weather-data[pandas])
  • 13 variables: temperatures, precipitation, snow, wind + gust, dew point, pressures, and visibility
  • Quality-controlled: values that failed NOAA's QC are dropped by default; include_flags=True surfaces the per-field QC flag
  • Present weather: include_weather_types=True reports the day's phenomena (fog, thunder, hail, freezing rain, ...) from GHCN WT** codes and GSOD's FRSHTT indicator
  • Explainable gaps: explain=True tells you why a value is missing (stations_considered + a per-field missing reason) — a blank cell becomes diagnosable, not silent
  • Automatic station selection: nearest station first, farther stations fill in missing variables; coverage(...) reports how well a point is served
  • Interpolation: Weather(interpolate=True) inverse-distance-weights the nearest stations instead of taking the single closest one
  • Universal CONUS coverage: source="grid" (or "auto") pulls NOAA's authoritative 5-km nClimGrid daily grid, so any Lower-48 lat/long returns real temperature + precipitation — no station gaps (pip install get-weather-data[grid])
  • Three data sources: GHCN Daily (~93K US/CA/MX stations), GSOD (~9K airport stations), and the nClimGrid gridded product
  • Robust batch processing: streams CSVs in chunks, one bad row gets a weather_error note instead of killing the job
  • Parquet + SQL: stream batch output to columnar Parquet and query it (or a glob of many files) with query_weather(...) via DuckDB (pip install get-weather-data[parquet])
  • Cache management: TTL-based refresh of station lists, get-weather cache info / cache clear

Usage Examples

Get Weather for a Date Range

from datetime import date
from get_weather_data import Weather

weather = Weather()

results = weather.get_range(
    "90210",
    start_date=date(2024, 7, 1),
    end_date=date(2024, 7, 7),
)

for r in results:
    if r.tmax is not None:
        print(f"{r.date}: High {r.tmax:.0f}°C")

Get a pandas DataFrame

from get_weather_data import Weather

weather = Weather()  # needs: pip install get-weather-data[pandas]

df = weather.get_frame("90210", "2024-07-01", "2024-07-07")
# one row per day; columns = metadata + tmax/tmin/prcp/... in your units
df[["date", "tmax", "prcp"]].head()

Process a CSV File

from get_weather_data import Weather

weather = Weather()

# ZIP-based input (zip, year, month, day columns)
weather.process_csv("locations.csv", "with_weather.csv")

# Coordinate-based input
weather.process_csv(
    "points.csv",
    "with_weather.csv",
    lat_column="lat",
    lon_column="lon",
    date_column="date",
)

Output rows carry the weather columns below (already in your chosen units), plus weather_error explaining any row that could not be resolved. More examples in the examples/ directory.

Parquet output and SQL queries

For the analytical "millions of rows" workflow, write results as Parquet (typed, compressed columns — far smaller than CSV and directly queryable) and run SQL over them with DuckDB. Needs the parquet extra (pip install get-weather-data[parquet]):

from get_weather_data import Weather, query_weather

# Streaming Parquet output (inferred from the .parquet suffix; no pandas
# needed, memory stays bounded over millions of rows):
Weather().process_csv("locations.csv", "weather.parquet", date_column="date")

# Query one file or a glob of many with SQL:
rows = query_weather(
    "SELECT station_id, avg(tmax) AS mean_high "
    "FROM t WHERE prcp > 0 GROUP BY station_id ORDER BY mean_high DESC",
    tables={"t": "weather.parquet"},  # or "runs/*.parquet"
)

get-weather process in.csv out.parquet (or --format parquet) does the same on the CLI. The daily and hourly DataFrames also write Parquet directly: get_frame(...).to_parquet(path) / get_hourly_frame(...).to_parquet(path).

Interpolate between stations

For a point between stations, interpolate=True blends the nearest stations by inverse-distance weighting rather than returning the single closest station's raw value:

weather = Weather(interpolate=True)
result = weather.get((40.75, -73.99), "2024-01-15")
# result.station_type == "interpolated"

Check coverage before trusting the numbers

cov = weather.coverage("59718", "2024-01-01", "2024-12-31")
print(cov.station_name, cov.station_distance_meters, "m away")
print(f"tmax present on {cov.fraction('tmax'):.0%} of days")

Hourly data

For sub-daily detail, get_hourly(...) returns one HourlyResult per hour from NOAA's ISD-Lite (nearest airport/USAF-WBAN station). Timestamps are UTC and the local station database is required (setup()); there is no online equivalent.

weather = Weather(units="imperial")

hours = weather.get_hourly("11371", "2023-07-16")  # LaGuardia, one UTC day
for h in hours[:3]:
    print(h.observed_at, h.temp, "F", h.wind_speed, "mph", h.wind_direction, "deg")

# pandas: one row per hour (needs the [pandas] extra)
df = weather.get_hourly_frame("11371", "2023-07-16", "2023-07-17")

CLI: get-weather hourly 11371 2023-07-16 [--end 2023-07-17] [--units imperial]. Fields: temp, dewpoint, sea_level_pressure, wind_direction (degrees), wind_speed, sky_condition, precip_1h, precip_6h.

Weather Variables

All values are floats in the units below (or their imperial equivalents); None/empty means the station network had no reading — a genuine zero is reported as 0.0.

Variable Description Metric Imperial
tmax Maximum temperature °C °F
tmin Minimum temperature °C °F
tavg Average temperature °C °F
tobs Temperature at observation time °C °F
prcp Precipitation mm in
snow Snowfall (GHCN stations only) mm in
snwd Snow depth mm in
awnd Average wind speed m/s mph
wind_gust Peak wind gust m/s mph
dewpoint Average dew point °C °F
sea_level_pressure Sea-level pressure hPa inHg
station_pressure Station pressure hPa inHg
visibility Visibility (GSOD stations only) km mi

With include_flags=True, result.flags maps each field to its GHCN quality-control flag (blank = passed all checks; GHCN stations only).

Present-weather types

The numeric variables above say how much rain fell but not whether it was thunder, freezing rain, or fog. With include_weather_types=True, result.weather_types is a set of the day's phenomena, drawn from GHCN WT** occurrence codes (fog, thunder, hail, glaze, blowing snow, ...) and, for GSOD airport stations, the FRSHTT indicator (fog / rain / snow / hail / thunder / tornado):

w = Weather(online=True, include_weather_types=True)
r = w.get("11430", "2023-07-16")  # JFK on a stormy afternoon
print(sorted(r.weather_types))  # ['fog', 'heavy_fog', 'thunder']

get-weather get <loc> <date> --weather-types shows them on the CLI, and process --weather-types adds a comma-joined weather_types column to the batch CSV.

Why is a value missing?

A blank tmax could mean many things: no station nearby, a station that measures only precipitation, or a gap on that date. With explain=True, the result carries stations_considered and a missing map giving a reason for each requested field that came back empty:

w = Weather(online=True, explain=True)
r = w.get("59221", "2023-01-15")  # remote NE Montana
print(r.stations_considered)  # e.g. 20
print(r.missing.get("tmax"))
# 'none of the 20 CDO stations near this point reported tmax on 2023-01-15'

get --explain prints the reasons on the CLI, process --explain adds stations_considered and missing columns to the batch CSV, and get_frame(...) includes them when present. (For a data consumer, this turns a silent NaN into a diagnosable gap.)

Which backend?

source= Data Coverage Needs Notes
"station" (default) Raw GHCN/GSOD station observations Wherever a station is near setup() (local DB) All 13 variables
"grid" nClimGrid 5-km daily grid Any point in the Lower 48 [grid] extra Temp + precip only; CONUS only
"auto" Station where available, grid to fill gaps Best of both in CONUS both Prefers real observations
from get_weather_data import Weather  # pip install get-weather-data[grid]

# Any Lower-48 coordinate, guaranteed — no local database needed
weather = Weather(source="grid")
result = weather.get((44.06, -121.31), "2024-01-15")  # remote Oregon
print(result.tmax, result.station_type)  # -> value, "gridded"

nClimGrid caveats: contiguous US only (no Alaska/Hawaii/PR), maximum, minimum, and average temperature plus precipitation only, and a ~2-3 day latency for the most recent days. Its daily values follow NOAA's "24-hour period ending in the early morning of the specified day" convention, which can differ slightly from a single station's calendar-day value.

Data Sources

This package uses data from NOAA's National Centers for Environmental Information:

  • GHCN Daily: Global Historical Climatology Network daily summaries (~93K stations across the US, Canada, and Mexico — border ZIPs get the truly nearest station)
  • GSOD: Global Summary of the Day from USAF/WBAN airport stations (~9K); GSOD reports no snowfall, so snow comes from GHCN stations
  • nClimGrid-Daily: NOAA's authoritative 5-km gridded daily temperature and precipitation for the contiguous US, 1951-present, interpolated from GHCN-Daily (used by source="grid"/"auto")
  • GeoNames: ZIP code to coordinates mapping

Database Setup and Disk Use

setup() downloads the station lists and ZIP coordinates (~60MB) and builds a nearest-stations index; it takes a few minutes, once. Station lists refresh automatically when older than 30 days.

Weather data itself is fetched lazily per year: each GHCN year you touch builds a local SQLite file (roughly 1–3 GB for recent years); GSOD adds one small CSV per station-year. Historical years never re-download; the current and previous year refresh monthly. Inspect or reclaim space anytime:

get-weather cache info
get-weather cache clear --ghcn        # or --gsod / --stations / --all
weather = Weather()
weather.setup()

info = weather.info()
print(f"GHCN stations: {info['ghcn_stations']:,}")
print(f"USAF stations: {info['usaf_stations']:,}")
print(f"ZIP codes: {info['zipcodes']:,}")

Configuration

Custom database location:

weather = Weather(database_path="/path/to/my.db")

Or via CLI:

get-weather --database /path/to/my.db setup

Upgrading from v3

v4 is a breaking release:

  • Values are now real metric floats (°C, mm, m/s) everywhere — previously the Python API returned raw GHCN tenths. Divide-by-10 code should be removed.
  • Run get-weather setup --force (or weather.setup(force=True)) after upgrading: v4 fixes a nearest-station ranking bug, so indexes built by v3 contain wrong distances.
  • WeatherResult gained tobs, latitude, longitude, and units; it is now importable from the package root.

License

MIT License. See LICENSE for details.

Authors

  • Suriyan Laohaprapanon
  • Gaurav Sood

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

get_weather_data-6.1.0.tar.gz (86.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

get_weather_data-6.1.0-py3-none-any.whl (75.6 kB view details)

Uploaded Python 3

File details

Details for the file get_weather_data-6.1.0.tar.gz.

File metadata

  • Download URL: get_weather_data-6.1.0.tar.gz
  • Upload date:
  • Size: 86.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for get_weather_data-6.1.0.tar.gz
Algorithm Hash digest
SHA256 574ff946b39b943743fc64a7f48a8f8d7db5d35b67b16342b84948735e4c236b
MD5 0d1d56f52b81c96fe3a83a3586d468e6
BLAKE2b-256 cbd35c4db4826b24cd0cbc7b8c3a7b17cbf03aed823236f17e36b6caebfdf1b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for get_weather_data-6.1.0.tar.gz:

Publisher: release.yml on gojiplus/get-weather-data

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file get_weather_data-6.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for get_weather_data-6.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2da03b77bcb56a4d8ab4719f79a609d67bd74f8caf554f62c4c197521702e671
MD5 4eb242b82f73612201435cfd58a527e1
BLAKE2b-256 7753e371453f84f806ff6b7c7df674d5caeb376d9c0429907d5e8f5b99dfd495

See more details on using hashes here.

Provenance

The following attestation bundles were made for get_weather_data-6.1.0-py3-none-any.whl:

Publisher: release.yml on gojiplus/get-weather-data

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page