# 1 panel 1 covers BOTH cells of column 1 -- matrix() fills down the
# first column, so c(1, 2, 1, 3) with nrow = 2 is column 1 = (1, 2)
# and column 2 = (1, 3). Panel 1 is repeated, so it spans the rows.
layout(matrix(c(1, 2, 1, 3), nrow = 2)); layout.show(3); layout(1)
# 2
op <- par(mar = c(3, 4, 2, 1))
layout(matrix(c(1, 2, 1, 3), nrow = 2))
plot(comp_y$date, comp_y$pm25, type = "l", xlab = "", ylab = "PM2.5",
main = "Compton 2025")
hist(comp_y$pm25, breaks = seq(-5, 60, 5), main = "Compton", xlab = "")
hist(d$pm25[d$site == "Lancaster - Fairgrounds"], breaks = seq(-5, 60, 5),
main = "Lancaster", xlab = "")
layout(1); par(op)
# 3 heights =, and they are RATIOS, not inches
layout(matrix(c(1, 1, 2, 3), nrow = 2, byrow = TRUE), heights = c(3, 1))
layout.show(3); layout(1)
# 4 draw the rectangle FIRST, or use a translucent fill, or the shading
# covers the line. Here: line, then translucent rect, then line again.
plot(comp$date, comp$pm25, type = "l", lwd = 2, xlab = "", ylab = "PM2.5")
rect(as.Date("2025-01-01"), -5, as.Date("2025-01-21"), 80,
col = adjustcolor("#b31b1b", alpha.f = 0.15), border = NA)
lines(comp$date, comp$pm25, lwd = 2)
# 5 five months: 01, 02, 05, 11, 12. rect() is vectorised, so one call
# with vectors of edges draws all five.
cm <- tapply(comp_y$pm25, format(comp_y$date, "%m"), mean)
hot <- names(cm)[cm > 12]
left <- as.Date(paste0("2025-", hot, "-01"))
right <- left + 31
plot(comp_y$date, comp_y$pm25, type = "l", xlab = "", ylab = "PM2.5")
rect(left, -5, right, 80, col = adjustcolor("#b31b1b", 0.12), border = NA)
lines(comp_y$date, comp_y$pm25)
length(hot)
# 6
br <- seq(-5, 60, 2.5)
hist(comp_y$pm25, breaks = br, col = adjustcolor("#b31b1b", 0.5),
border = NA, main = "", xlab = "PM2.5 (ug/m3)")
hist(d$pm25[d$site == "Lancaster - Fairgrounds"], breaks = br,
col = adjustcolor("#1f6fb4", 0.5), border = NA, add = TRUE)
legend("topright", c("Compton", "Lancaster"), bty = "n",
fill = adjustcolor(c("#b31b1b", "#1f6fb4"), 0.5))
# 7 a U: high in January, a trough through spring and summer, rising
# again into December. The band is narrow in the middle where the data
# is dense and flares at both ends. R-squared is only 0.21 -- the
# seasonal shape is real but it explains a fifth of the variation.
day <- as.numeric(comp_y$date - as.Date("2025-01-01"))
fit <- lm(comp_y$pm25 ~ day + I(day^2))
X <- cbind(1, day, day^2)
se <- sqrt(diag(X %*% vcov(fit) %*% t(X)))
i <- order(day)
plot(comp_y$date, comp_y$pm25, pch = 16, cex = 0.5, col = "grey60",
xlab = "", ylab = "PM2.5 (ug/m3)")
polygon(c(comp_y$date[i], rev(comp_y$date[i])),
c((fitted(fit) + 1.96 * se)[i], rev((fitted(fit) - 1.96 * se)[i])),
col = adjustcolor("#b31b1b", 0.25), border = NA)
lines(comp_y$date[i], fitted(fit)[i], col = "#b31b1b", lwd = 2)
summary(fit)$r.squared
# 8
library(RColorBrewer)
mon <- as.numeric(format(d$date, "%m"))
cols <- colorRampPalette(brewer.pal(11, "Spectral"))(12)
plot(d$pm25, d$Daily.AQI.Value, pch = 16, cex = 0.6, col = cols[mon],
xlab = "Daily mean PM2.5 (ug/m3)", ylab = "Daily AQI")
legend("bottomright", month.abb, col = cols, pch = 16, ncol = 2,
bty = "n", cex = 0.7)
# 9 Lancaster 4.48, Pasadena 11.67, Long Beach 12.21, Compton 13.38 --
# Lancaster lands in its own bucket and the other three share one
am <- sort(tapply(d$pm25, d$site, mean))
brk <- seq(0, 15, length.out = 6)
pal <- colorRampPalette(c("white", "#b31b1b"))(5)
op <- par(mar = c(4, 15, 1, 1))
barplot(am, horiz = TRUE, las = 1, col = pal[findInterval(am, brk)],
xlab = "Mean PM2.5, 2025 (ug/m3)")
par(op)
# 10
op <- par(mar = c(3, 5, 1, 1))
plot(comp_y$date, comp_y$pm25, type = "l", col = "grey30",
axes = FALSE, xlab = "", ylab = "")
firsts <- as.Date(paste0("2025-", sprintf("%02d", 1:12), "-01"))
axis(1, at = firsts, labels = month.abb)
axis(1, at = firsts + 15, tck = -0.01, lwd = 0, lwd.tick = 1, labels = FALSE)
axis(2, las = 2)
box()
mtext("Daily PM2.5 (ug/m3)", side = 2, line = 3)
par(op)
# 11 BESIDE answers it. A stacked bar shows the month's total across all
# four stations, so a station reporting nothing is invisible inside
# someone else's bar; side by side, a missing station is a missing bar.
m <- table(format(d$date, "%m"), d$site)
op <- par(mfrow = c(2, 1), mar = c(3, 4, 2, 1))
barplot(t(m), col = grey.colors(4), main = "stacked")
barplot(t(m), col = grey.colors(4), beside = TRUE, main = "beside = TRUE")
par(op)
# 12 Lancaster (4.20) is clearly different from all three others. Compton
# (10.70) and Long Beach (10.65) are not different at all -- their
# notches overlap almost completely. Pasadena (9.30) is borderline
# against both, and it has the fewest days, which is why its notch is
# the widest.
boxplot(pm25 ~ site, data = d, notch = TRUE, names = c("Com", "Lan", "LB", "Pas"),
xlab = "", ylab = "PM2.5 (ug/m3)")
# 13 Lancaster is the outlier row, pale all year. December and January
# are the worst columns -- and December is worse than January at
# three of the four sites.
mm <- tapply(d$pm25, list(d$site, format(d$date, "%m")), mean)
mm <- mm[order(rowMeans(mm)), ]
op <- par(mar = c(3, 12, 2, 1))
image(t(mm), col = colorRampPalette(c("white", "#b31b1b"))(20), axes = FALSE)
axis(1, at = seq(0, 1, length.out = 12), labels = month.abb, tick = FALSE)
mtext(rownames(mm), side = 2, at = seq(0, 1, length.out = 4), las = 2,
cex = 0.7, line = 0.5)
box()
par(op)
# 14 Lancaster sits north of the mountains; the other three cluster in
# the basin, which is the whole reason its readings differ
library(maps)
co <- unique(d[, c("site", "Site.Latitude", "Site.Longitude")])
co$mean <- tapply(d$pm25, d$site, mean)[co$site]
map("state", region = "california")
points(co$Site.Longitude, co$Site.Latitude, pch = 21, bg = "#b31b1b",
cex = co$mean / 4)
text(co$Site.Longitude, co$Site.Latitude, round(co$mean, 1), pos = 4, cex = 0.7)