Weather Data Dashboard: Pulling, Storing & Visualizing Live API Data
Data Science & ML Projects (Beginner)
Chapter 3 · Weather Data Dashboard: Pulling, Storing & Visualizing Live API Data
ds1-3's own JSON-handling skills applied to a live response instead of a file on disk, then charted with ds1-7's and ds1-8's own tools.
What We're Building
A small tool that fetches an hourly weather forecast from Open-Meteo — a genuinely free, public weather API that needs no signup or API key, which is exactly why it's used here — turns the response into a DataFrame, appends it to a growing local dataset, and builds two small charts from it: a temperature-over-time line chart and a precipitation-by-hour bar chart.
Step 1: Making the API Request
import requests params = { "latitude": 51.51, # London, as an example — swap for any location "longitude": -0.13, "hourly": "temperature_2m,precipitation", "forecast_days": 2, } response = requests.get("https://api.open-meteo.com/v1/forecast", params=params) print(response.status_code) # 200 — success
Passing a params dict to requests.get() builds the full query-string URL automatically — no manual string concatenation needed. No API key here, unlike most weather services, which is exactly why this API was chosen for a beginner project: nothing to configure, sign up for, or accidentally commit to a public repository.
Step 2: Parsing JSON From a Live Response
data = response.json() print(list(data["hourly"].keys())) # ['time', 'temperature_2m', 'precipitation']
ds1-3 read JSON with json.load(open(path)) — a file that already existed on disk. Here, response.json() does the identical parsing job, but on a response body that only came into existence the moment this script made the request. The structure once parsed is identical (nested dicts and lists); only its source differs.
Step 3: A Genuinely Different JSON Shape
import pandas as pd hourly = data["hourly"] df = pd.DataFrame({ "time": pd.to_datetime(hourly["time"]), "temperature": hourly["temperature_2m"], "precipitation": hourly["precipitation"], }) print(df.head())
pd.DataFrame(), not looping over a list of row dicts. Real APIs don't all agree on one shape; reading the actual response before assuming its structure is the real skill.
Step 4: Storing Results Locally Over Time
from pathlib import Path def append_to_log(df, path="weather_log.csv"): if Path(path).exists(): df.to_csv(path, mode="a", header=False, index=False) else: df.to_csv(path, index=False) print(f"Appended {len(df)} rows to {path}.")
Running this script once a day builds a genuinely growing local weather history out of nothing but repeated forecast pulls — mode="a" appends rather than overwriting, and skipping the header on every call after the first keeps the CSV valid.
Step 5: Building the Dashboard
import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6)) ax1.plot(df["time"], df["temperature"], color="#15803D") ax1.set_title("Temperature Forecast") ax1.set_ylabel("°C") ax2.bar(df["time"], df["precipitation"], color="#EAB308") ax2.set_title("Precipitation Forecast") ax2.set_ylabel("mm") plt.tight_layout() plt.savefig("weather_dashboard.png")
ds1-7's own line-chart-for-a-trend and bar-chart-for-discrete-values choices, applied directly — temperature is a continuous value changing over time (a line), precipitation at each hour is a discrete quantity (a bar). Two subplots stacked in one figure make a small, genuine dashboard rather than two disconnected charts.
The Complete Script
import requests, pandas as pd, matplotlib.pyplot as plt from pathlib import Path params = {"latitude": 51.51, "longitude": -0.13, "hourly": "temperature_2m,precipitation", "forecast_days": 2} data = requests.get("https://api.open-meteo.com/v1/forecast", params=params).json() hourly = data["hourly"] df = pd.DataFrame({ "time": pd.to_datetime(hourly["time"]), "temperature": hourly["temperature_2m"], "precipitation": hourly["precipitation"], }) log_path = "weather_log.csv" df.to_csv(log_path, mode="a" if Path(log_path).exists() else "w", header=not Path(log_path).exists(), index=False) fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6)) ax1.plot(df["time"], df["temperature"], color="#15803D") ax1.set_title("Temperature Forecast") ax2.bar(df["time"], df["precipitation"], color="#EAB308") ax2.set_title("Precipitation Forecast") plt.tight_layout() plt.savefig("weather_dashboard.png") print("Dashboard updated.")
Query parameters as a dict
requests.get(url, params={...}) builds the URL automatically.
response.json()
Parses a live response body the same way ds1-3 parsed a saved file.
Parallel-list JSON
Some APIs return matching lists instead of row-like records — read before assuming.
Appending, not overwriting
mode="a" builds a real local history out of repeated small pulls.
Try these on your own:
- Add a second location and plot both on the same temperature chart, with a legend, to compare two cities at once.
- Wrap the request in
try/exceptso a network failure logs a message instead of crashing the whole script. - Once
weather_log.csvhas several days of real history, load it back in and plot a week-long trend instead of just the latest forecast. - Use a Seaborn heatmap (
ds1-8) to show temperature by hour-of-day across several logged days at once.
What's Next
Chapter 4: Personal Finance Analyzer, Revisited With pandas — taking py4-8's own plain-Python expense tracker and rebuilding its analysis with the pandas/numpy toolkit this course has been using all along.
Chapter 3 Quick Reference
- The first project pulling from a live API rather than a static file — data that doesn't exist until requested
- requests.get(url, params={...}) builds a query-string URL automatically; .json() parses the live response, same job as ds1-3's own json.load()
- Real APIs don't share one JSON shape — Open-Meteo's own parallel-list structure required a genuinely different DataFrame-building approach
- Appending (
mode="a") to a CSV over repeated runs builds a real local dataset out of small live pulls - ds1-7's line-vs-bar chart choice applied directly: continuous trend (temperature) as a line, discrete quantity (precipitation) as a bar
- Free APIs still have limits — schedule pulls reasonably, don't poll in a tight loop