Get Weather Data
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=Truesurfaces the per-field QC flag - Present weather:
include_weather_types=Truereports the day's phenomena (fog, thunder, hail, freezing rain, ...) from GHCNWT**codes and GSOD'sFRSHTTindicator - Explainable gaps:
explain=Truetells you why a value is missing (stations_considered+ a per-fieldmissingreason) — 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_errornote 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
snowcomes 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(orweather.setup(force=True)) after upgrading: v4 fixes a nearest-station ranking bug, so indexes built by v3 contain wrong distances. WeatherResultgainedtobs,latitude,longitude, andunits; 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
574ff946b39b943743fc64a7f48a8f8d7db5d35b67b16342b84948735e4c236b
|
|
| MD5 |
0d1d56f52b81c96fe3a83a3586d468e6
|
|
| BLAKE2b-256 |
cbd35c4db4826b24cd0cbc7b8c3a7b17cbf03aed823236f17e36b6caebfdf1b0
|
Provenance
The following attestation bundles were made for get_weather_data-6.1.0.tar.gz:
Publisher:
release.yml on gojiplus/get-weather-data
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
get_weather_data-6.1.0.tar.gz -
Subject digest:
574ff946b39b943743fc64a7f48a8f8d7db5d35b67b16342b84948735e4c236b - Sigstore transparency entry: 2255387603
- Sigstore integration time:
-
Permalink:
gojiplus/get-weather-data@b6c000da65266a9e8c16c8f1bf8f91388192a348 -
Branch / Tag:
refs/tags/v6.1.0 - Owner: https://github.com/gojiplus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b6c000da65266a9e8c16c8f1bf8f91388192a348 -
Trigger Event:
push
-
Statement type:
File details
Details for the file get_weather_data-6.1.0-py3-none-any.whl.
File metadata
- Download URL: get_weather_data-6.1.0-py3-none-any.whl
- Upload date:
- Size: 75.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2da03b77bcb56a4d8ab4719f79a609d67bd74f8caf554f62c4c197521702e671
|
|
| MD5 |
4eb242b82f73612201435cfd58a527e1
|
|
| BLAKE2b-256 |
7753e371453f84f806ff6b7c7df674d5caeb376d9c0429907d5e8f5b99dfd495
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
get_weather_data-6.1.0-py3-none-any.whl -
Subject digest:
2da03b77bcb56a4d8ab4719f79a609d67bd74f8caf554f62c4c197521702e671 - Sigstore transparency entry: 2255387618
- Sigstore integration time:
-
Permalink:
gojiplus/get-weather-data@b6c000da65266a9e8c16c8f1bf8f91388192a348 -
Branch / Tag:
refs/tags/v6.1.0 - Owner: https://github.com/gojiplus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b6c000da65266a9e8c16c8f1bf8f91388192a348 -
Trigger Event:
push
-
Statement type: