Abstract

This project explores air-quality trends in Wake County, North Carolina, from 2017 to 2025. Using data on PM2.5, PM10, and ozone collected at Millbrook School and Triple Oak, I analyzed changes in pollutant levels over time and compared the two monitoring sites. The data were cleaned ,filtered and visualized in R using descriptive statistics, trend plots, boxplots, and geographic maps.

The results show that PM2.5 and PM10 fluctuated throughout the study period, while ozone remained relatively stable. In addition, observation completness percentages were generally high, suggesting that the dataset is reliable for long-term analysis. Overall, this project demonstrates how data cleaning, visualization, and statistical analysis can be used to better understand environmental conditions.

#Introduction Air quality is closely related to public health and environmental conditions. This project focuses on three major pollutants: PM2.5, PM10, and ozone. The goal is to understand how air quality changed in Wake County between 2017 and 2025 and whether there are differences between the two monitoring sites. The main research questions are:

  1. How did pollutant levels change over time?
  2. Are there differences between Millbrook School and Triple Oak for the pollutant levels?
  3. Which pollutant showed the greatest variation?
  4. How complete are the observations?
library(tidyverse)
## Warning: package 'dplyr' was built under R version 4.2.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   4.0.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

#Data The data come from the Air Quality Annual Summary dataset. The dataset contains annual measurements collected in Wake County, North Carolina. The analysis uses data from 2017 to 2025 and focuses on PM2.5, PM10, and ozone and uses other varialbles in the dataset.

Methods

The data were cleaned by filtering the years and pollutants relevant to the project. Descriptive statistics were used to summarize the data, while line charts, boxplots, and geographic visualizations were used to examine trends and distributions.

library(tidyverse)

air_data <- read_delim(
  "annual_37_183_0021_2017.csv",
  delim = ";"
)
## Rows: 1880 Columns: 52
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr  (16): site_number, datum, parameter_name, duration_description, polluta...
## dbl  (31): state_code, county_code, parameter_code, poc, latitude, longitude...
## lgl   (1): tribe_name
## dttm  (4): first_maximum_datetime, second_maximum_datetime, third_maximum_da...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
air_selected <- air_data |>
  filter(
    parameter_name %in% c(
      "PM2.5 - Local Conditions",
      "Ozone",
      "PM10 Total 0-10um STP"
    ),
    year >= 2017,
    year <= 2025
  ) |>
  select(
    site_number,
    local_site_name,
    latitude,
    longitude,
    parameter_name,
    year,
    units_of_measure,
    observation_count,
    observation_percent,
    arithmetic_mean,
    minimum_value,
    first_maximum_value,
    address
  )

#Result

#Table 1 Summary

table1 <- air_selected |>
  group_by(
    parameter_name
  ) |>
  summarise(

    observations = n(),

    mean_value = mean(
      arithmetic_mean,
      na.rm = TRUE
    ),

    median_value = median(
      arithmetic_mean,
      na.rm = TRUE
    ),

    minimum = min(
      arithmetic_mean,
      na.rm = TRUE
    ),

    maximum = max(
      arithmetic_mean,
      na.rm = TRUE
    ),

    average_observation_percent = mean(
      observation_percent,
      na.rm = TRUE
    )

  )

table1
## # A tibble: 3 × 7
##   parameter_name           observations mean_value median_value minimum maximum
##   <chr>                           <int>      <dbl>        <dbl>   <dbl>   <dbl>
## 1 Ozone                              16     0.0418       0.0418  0.0359   0.047
## 2 PM10 Total 0-10um STP              24    14.6         14.7    11.4     17.2  
## 3 PM2.5 - Local Conditions           82     8.14         7.85    5.85    10.2  
## # ℹ 1 more variable: average_observation_percent <dbl>

Table 1 summarizes the distribution of the three pollutants. PM10 has the highest average value, while ozone has the highest observation completeness.

#Table 2 Sites

table2 <- air_selected |>
  select(

    site_number,local_site_name,latitude,longitude,address) |>
  distinct() |>
  arrange(
    site_number
  )

table2
## # A tibble: 2 × 5
##   site_number local_site_name  latitude longitude address               
##   <chr>       <chr>               <dbl>     <dbl> <chr>                 
## 1 0014        Millbrook School     35.9     -78.6 3801 SPRING FOREST RD.
## 2 0021        Triple Oak           35.9     -78.8 2826 TRIPLE OAK DRIVE

#Figure 1 Geographic

site_locations <- air_selected |>
  select(
    site_number,
    local_site_name,
    longitude,
    latitude
  ) |>
  distinct()

site_locations
## # A tibble: 2 × 4
##   site_number local_site_name  longitude latitude
##   <chr>       <chr>                <dbl>    <dbl>
## 1 0014        Millbrook School     -78.6     35.9
## 2 0021        Triple Oak           -78.8     35.9
figure1 <- site_locations |>
  ggplot(
    aes(
      x = longitude,
      y = latitude
    )
  ) +
  geom_point(
    size = 4
  ) +
  geom_text(
    aes(
      label = local_site_name
    ),
    nudge_y = 0.0015
  ) +
  xlim(
    -78.82,
    -78.57
  ) +
  ylim(
    35.856,
    35.870
  ) +
  labs(
    title = "Air-Quality Monitoring Sites in Wake County",
    subtitle = "Millbrook School and Triple Oak monitoring sites",
    x = "Longitude",
    y = "Latitude"
  ) +
  theme_minimal()

figure1

Figure 1 shows the locations of Millbrook School and Triple Oak in Wake County.

#Figure 2 Annual Trend

annual_summary <- air_selected |>
  group_by(
    year,
    site_number,
    local_site_name,
    parameter_name,
    units_of_measure
  ) |>
  summarise(
    annual_average = mean(
      arithmetic_mean,
      na.rm = TRUE
    ),
    .groups = "drop"
  )
figure2 <- annual_summary |>
  ggplot(
    aes(
      x = year,
      y = annual_average,
      group = local_site_name,
      linetype = local_site_name
    )
  ) +
  geom_line(
    linewidth = 0.8
  ) +
  geom_point(
    size = 2
  ) +
  facet_wrap(
    ~ parameter_name,
    scales = "free_y"
  ) +
  labs(
    title = "Annual Air-Quality Trends by Monitoring Site",
    subtitle = "Annual average measurements from 2017 to 2025",
    x = "Year",
    y = "Annual Average",
    linetype = "Monitoring Site"
  ) +
  theme_minimal()

figure2

PM2.5 and PM10 fluctuated over time and reached relatively high levels around 2023, while ozone remained relatively stable. In addition, the two monitoring sites showed different patterns for some pollutants.

#Figure 3 Distribution Analysis

figure3 <- air_selected |>
  filter(
    !is.na(arithmetic_mean)
  ) |>
  ggplot(
    aes(
      x = local_site_name,
      y = arithmetic_mean
    )
  ) +
  geom_boxplot() +
  facet_wrap(
    ~ parameter_name,
    scales="free_y"
  ) +
  labs(
    title = "Distribution of Air-Quality Measurements by Monitoring Site",
    subtitle = "Comparison of arithmetic mean measurements from 2017 to 2025",
    x = "Monitoring Site",
    y = "Arithmetic Mean"
  ) +
  theme_minimal()

figure3

#Figure 4

observation_summary <- air_selected |>
  group_by(
    year,
    parameter_name
  ) |>
  summarise(
    average_observation_percent = mean(
      observation_percent,
      na.rm = TRUE
    ),
    .groups = "drop"
  )

The boxplots show that PM10 and PM2.5 have greater variability than ozone. Millbrook School generally recorded higher PM2.5 values than Triple Oak.

Figure 4 Observation Completeness

figure4 <- observation_summary |>
  ggplot(
    aes(
      x = year,
      y = average_observation_percent,
      group = parameter_name,
      linetype = parameter_name
    )
  ) +
  geom_line(
    linewidth = 0.8
  ) +
  geom_point(
    size = 2
  ) +
  labs(
    title = "Observation Completeness by Year",
    subtitle = "Average percentage of expected observations",
    x = "Year",
    y = "Average Observation Percentage",
    linetype = "Air-Quality Parameter"
  ) +
  theme_minimal()

figure4

Observation percentages remained above 90% for most years, indicating good data quality. Ozone had the most complete observations, while PM2.5 and PM10 showed lower percentages around 2022.

Discussion

The results show that PM2.5 and PM10 fluctuated between 2017 and 2025, while ozone remained relatively stable. Both PM2.5 and PM10 reached higher levels around 2023. In addition, Millbrook School generally recorded higher PM2.5 concentrations than Triple Oak, suggesting that local environmental conditions may influence air quality.

Observation completeness remained above 90% for most years, indicating that the dataset is reliable for long-term analysis. However, this study only includes two monitoring sites and focuses on annual averages. Future research could examine seasonal changes and explore the effects of weather, traffic, and population growth on air quality.

#Conclusion This project analyzed air-quality trends in Wake County from 2017 to 2025. PM2.5 and PM10 has noticeable variation over time, while ozone remained relatively stable. The analysis also found differences between Millbrook School and Triple Oak.

Overall, the results demonstrate how data visualization and statistical analysis in R can be used to better understand environmental conditions and long-term trends. There were still many thngs to improve.

Reference

United States Environmental Protection Agency. Air Quality Annual Summary Dataset. https://catalog.data.gov/dataset/air-quality-annual-summary