PurpleAir¶
PurpleAir operates a global network of 30,000+ low-cost air quality sensors, popular with researchers and citizen scientists.
Overview¶
- Coverage: Global (30,000+ sensors)
- Sensors: Dual-channel laser particle counters
- Data quality: QA/QC via channel agreement
- API key: Required (free tier available)
- Operator: PurpleAir Inc.
Getting an API Key¶
- Visit PurpleAir Developer Portal
- Create an account and register your application
- You'll receive 1,000,000 API points for free
- Set the environment variable:
Available Measurements¶
- PM1.0, PM2.5, PM10 (particulate matter)
- Temperature
- Humidity
- Pressure
Finding Sensors¶
import aeolus
# Get outdoor sensors in a bounding box (London example)
# bbox format: (min_lon, min_lat, max_lon, max_lat) - same as GeoJSON/shapely
sites = aeolus.portals.find_sites(
"PURPLEAIR",
bbox=(-0.5, 51.3, 0.3, 51.7),
location_type=0 # 0 = outdoor, 1 = indoor
)
# View available sensors
print(sites[['site_code', 'site_name', 'latitude', 'longitude']])
Downloading Data¶
import aeolus
from datetime import datetime
# Download from specific sensors (use sensor indices from map.purpleair.com)
data = aeolus.download(
sources="PURPLEAIR",
sites=["131075", "131076"],
start_date=datetime(2024, 1, 1),
end_date=datetime(2024, 1, 31)
)
QA/QC Methodology¶
PurpleAir sensors have two laser particle counters (Channel A and Channel B) for redundancy. Aeolus applies literature-based QA/QC thresholds:
Concentration-Dependent Thresholds¶
| Concentration | Threshold Type | Agreement Required |
|---|---|---|
| < 0.3 µg/m³ | Detection limit | Flagged as noise |
| 0.3-100 µg/m³ | Absolute | ±10 µg/m³ |
| 100-1000 µg/m³ | Relative | ±10% |
| > 1000 µg/m³ | Saturation | Flagged as saturated |
Ratification Flags¶
Validated: Both channels agree within thresholdsChannel Disagreement: Both valid but disagree beyond thresholdsSingle Channel (A)/Single Channel (B): Only one channel validBelow Detection Limit: Value below sensor noise floorSensor Saturation: Value above sensor range
Raw Data Access¶
For custom QA/QC or analysis, you can access raw channel data:
from aeolus.sources.purpleair import fetch_purpleair_data
# Get raw wide-format data with both channels
raw_data = fetch_purpleair_data(
sites=["131075"],
start_date=datetime(2024, 1, 1),
end_date=datetime(2024, 1, 7),
raw=True # Returns pm2.5_atm_a, pm2.5_atm_b, etc.
)
# Get only validated data (exclude flagged measurements)
clean_data = fetch_purpleair_data(
sites=["131075"],
start_date=datetime(2024, 1, 1),
end_date=datetime(2024, 1, 7),
include_flagged=False
)
Example: London Air Quality¶
import aeolus
from datetime import datetime
# Find sensors in London
sites = aeolus.portals.find_sites(
"PURPLEAIR",
bbox=(-0.5, 51.3, 0.3, 51.7),
location_type=0
)
# Download a week of data
sensor_ids = sites['site_code'].head(5).tolist()
data = aeolus.download(
sources="PURPLEAIR",
sites=sensor_ids,
start_date=datetime(2024, 1, 1),
end_date=datetime(2024, 1, 7)
)
# Check QA/QC flag distribution
print(data['ratification'].value_counts())
Resources¶
- PurpleAir Map - Find sensor indices
- PurpleAir API Documentation
- Developer Portal
- Community Forum - QA/QC methodology discussions