Introduction to vayuayan

Introduction

vayuayan (वायुअयन) is an R package that downloads and analyzes air quality data. The data comes from WUSTL ACAG satellite PM2.5 (global) and the CPCB and OAQ monitoring networks (India). The name contains two Sanskrit words:

  • Vayu (वायु) means wind or air.
  • Ayan (अयन) means path, journey or movement.

Together, “Vayuayan” means “the path of wind.”

Installation

devtools::install_github("saketlab/vayuayan", subdir = "R")

Data sources

The package gives access to three data sources:

  1. The WUSTL ACAG satellite PM2.5 data from Washington University
    • Global coverage at 0.01 degree (approximately 1 km) resolution
    • Monthly and annual data (1998 to 2024, product V6.GL.03)
  2. The CPCB (Central Pollution Control Board) monitoring network in India
    • Historical and real-time AQI data
    • Station-level and city-level data
  3. The OAQ (Open Air Quality) low-cost sensor networks in India
    • Many providers, for example Airnet
    • Daily, monthly, and annual data

Quick start

library(vayuayan)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

vayuayan_welcome()
#> 
#> ── Vayuayan - Air quality data analysis ────────────────────────────────────────
#> Version 0.3.0
#> 
#> ── Available functions: ──
#> 
#> • cpcb_get_state_list(), cpcb_get_city_list() - Historical AQI data from CPCB
#> • cpcb_get_live_aqi_data(), cpcb_get_nearest_station() - Real-time AQI
#> monitoring
#> • pm25_get_stats(), pm25_download_netcdf() - Satellite PM2.5 data processing
#> • oaq_session(), oaq_get_stations_latest() - OAQ open air quality platform
#> 
#> ── Quick start: ──
#> 
#> states <- cpcb_get_state_list()
#> cities <- cpcb_get_city_list('Maharashtra')
#>  
#> location <- cpcb_get_system_location()
#> aqi <- cpcb_get_live_aqi_data()
#>  
#> stats <- pm25_get_stats('region.geojson', 2023, 11, region = 'AS')

Historical AQI data

The cpcb_ functions give access to historical air quality data from the Central Pollution Control Board.

Available data

The lists of states, cities and stations come from a CPCB station directory in the package. Thus, you can use these calls offline.

states <- cpcb_get_state_list()
head(states)
#> [1] "Andaman And Nicobar" "Andhra Pradesh"      "Arunachal Pradesh"  
#> [4] "Assam"               "Bihar"               "Chandigarh"

cities <- cpcb_get_city_list("Maharashtra")
head(cities)
#> [1] "Ahmednagar" "Akola"      "Ambernath"  "Amravati"   "Aurangabad"
#> [6] "Badlapur"

stations <- cpcb_get_station_list("Mumbai")
head(stations)
#>                                 label     value
#> 1 Bandra Kurla Complex, Mumbai - IITM site_5392
#> 2 Bandra Kurla Complex, Mumbai - MPCB site_5810
#> 3               Bandra, Mumbai - MPCB  site_168
#> 4        Borivali East, Mumbai - IITM site_5400
#> 5        Borivali East, Mumbai - MPCB site_5113
#> 6               Byculla, Mumbai - BMC site_5960

Station coordinates

delhi_stations <- cpcb_add_coords(cpcb_get_station_list("Delhi"))
#> Done: 59/59 stations have coordinates
head(delhi_stations)
#>                       label       value      lat      lon
#> 1      Alipur, Delhi - DPCC   site_5024 28.81533 77.15301
#> 2 Anand Vihar, Delhi - DPCC    site_301 28.64762 77.31581
#> 3 Ashok Vihar, Delhi - DPCC   site_1420 28.69538 77.18166
#> 4   Aya Nagar, Delhi - IITM    site_108 28.47069 77.10994
#> 5       Badarpur,Delhi-DPCC site_100409 28.51330 77.32634
#> 6      Bawana, Delhi - DPCC   site_1560 28.77620 77.05107

Daily data in long format

cpcb_city_daily() downloads the CPCB daily file for each city. The function returns one row for each day. The package caches the files. Thus, subsequent calls read the files from disk.

daily <- cpcb_city_daily(c("Delhi", "Mumbai"), 2024, cache_dir = tempdir())

daily %>%
  group_by(city, month) %>%
  summarise(pm25 = mean(value), .groups = "drop")

cpcb_station_daily() does the same for stations:

station_daily <- cpcb_station_daily(
  head(delhi_stations$value, 3), 2025,
  cache_dir = tempdir()
)
count(station_daily, station_id)

cpcb_download_city_data() and cpcb_download_station_data() save the raw CPCB sheet. Use these functions to get the file.

Live AQI data

The live readings come from the public station feed. The feed contains only the data of the last hour.

nearest <- cpcb_get_nearest_station(coords = c(19.0760, 72.8777))
nearest

aqi_data <- cpcb_get_live_aqi_data(station_id = nearest$station_id)
aqi_data$metrics

Without arguments, cpcb_get_live_aqi_data() finds the station nearest to your IP location.

Station summaries

stations_to_dataframe() changes the live feed into one row for each station. Each row also has an AQI category.

live <- stations_to_dataframe(cpcb_get_all_india())

live %>%
  group_by(state_id) %>%
  summarise(
    stations = n(),
    live = sum(live),
    avg_aqi = mean(avg_aqi, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(stations)) %>%
  head(10)

count(live, aqi_category, sort = TRUE)
library(ggplot2)
library(sf)

aqi_colors <- c(
  "Good"         = "green",
  "Satisfactory" = "lightgreen",
  "Moderate"     = "yellow",
  "Poor"         = "orange",
  "Very Poor"    = "red",
  "Severe"       = "darkred",
  "No Data"      = "grey70"
)

live %>%
  filter(live) %>%
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326) %>%
  ggplot() +
  geom_sf(aes(color = aqi_category), size = 2, alpha = 0.6) +
  scale_color_manual(values = aqi_colors) +
  labs(title = "Live CPCB monitoring stations", color = "AQI category") +
  theme_minimal()

PM2.5 satellite data

The pm25_ functions process satellite PM2.5 data from WUSTL ACAG (product V6.GL.03 on the AWS Open Data registry). The Asia subset (region = "AS") includes India. The file of this subset is smaller than the global file. The package caches the files in tools::R_user_dir("vayuayan", "cache"). The vignette build does not run these chunks.

pm25_nc <- pm25_download_netcdf(2023, 11, region = "AS")
delhi <- india_boundary(state = "Delhi")

Regional statistics

pm25_get_stats(
  geojson_file = "https://geo.bharatviz.org/geojsons/cities/delhi.geojson",
  nc_file      = pm25_nc
)

pm25_get_stats(
  geojson_file = "https://geo.bharatviz.org/geojsons/districts/India_LGD_districts.geojson",
  nc_file      = pm25_nc,
  group_by     = "state_name"
)

Maps

r <- pm25_load_netcdf_bbox(pm25_nc, delhi, var = "PM25", mask = TRUE)
pm25_map(r, delhi, title = "Delhi, November 2023")

Cache management

pm25_list_cached_files()
pm25_clear_cache()

Utility functions

clean_station_name("Dr. Karni Singh Shooting Range, Delhi - DPCC")
#> [1] "Dr_Karni_Singh_Shooting_Range_Delhi_DPCC"

get_aqi_categories(c(42, 180, 320))
#> [1] "Good"      "Moderate"  "Very Poor"

# Mumbai to Delhi, in km
haversine_distance(19.0760, 72.8777, 28.7041, 77.1025)
#> [1] 1153.241