Nothing
## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
## ----setup--------------------------------------------------------------------
library(vismeteor)
## ----echo=TRUE----------------------------------------------------------------
data(PER_2015_rates)
obs <- PER_2015_rates$observations |> subset(
rad_alt > 15 & moon_alt < 0 & sun_alt < -5 &
sl_start >= 139.2 & sl_end <= 140.6
)
deg2rad <- \(x) x * pi / 180
obs$sl <- (obs$sl_start + obs$sl_end) / 2
obs$t <- with(obs, t_eff * sin(deg2rad(rad_alt)) / f)
rates <- data.frame(
sl = obs$sl,
t = obs$t,
limmag = obs$lim_magn,
freq = obs$freq
)
rates <- rates[order(rates$sl), ]
nrow(rates)
## ----echo=TRUE----------------------------------------------------------------
fit_glm <- \(rates, knots) {
f <- if (length(knots) == 0L) {
freq ~ splines::ns(sl) + limmag + offset(log(t))
} else {
freq ~ splines::ns(sl, knots = knots) +
limmag + offset(log(t))
}
stats::glm(f, data = rates, family = stats::poisson())
}
score_bic <- \(rates, knots) stats::BIC(fit_glm(rates, knots))
## ----echo=TRUE----------------------------------------------------------------
rates$q <- vismeteor::freq_quantile(rates$freq, 12) # at least 12 meteors per bin
rates$qsl <- with(
rates,
ave(freq * sl, q, FUN = sum) / ave(freq, q, FUN = sum)
)
cand <- sort(unique(round(rates$qsl, 2)))
length(cand)
## ----echo=TRUE----------------------------------------------------------------
res_fwd <- vismeteor::select_knots(rates, cand, score_bic)
sort(c(res_fwd$knots, res_fwd$fixed_knots))
res_fwd$score
## ----echo=TRUE, fig.alt="BIC trajectory of the forward search over the number of interior knots, with the score optimum at the endpoint"----
plot(res_fwd$history$n_knots, res_fwd$history$score,
type = "b", pch = 19, col = "blue",
xlab = "number of interior knots", ylab = "BIC",
main = "Forward selection: BIC trajectory"
)
points(tail(res_fwd$history$n_knots, 1L), res_fwd$score,
pch = 19, col = "red", cex = 1.5
)
abline(h = res_fwd$score, lty = 2, col = "grey50")
## ----echo=TRUE----------------------------------------------------------------
res_fwd_plus1 <- vismeteor::select_knots(rates, cand, score_bic,
fixed_knots = c(
res_fwd$knots,
res_fwd$fixed_knots
),
n_steps = 1L
)
res_fwd_plus1$score - res_fwd$score # always >= 0: how much worse one step makes it
## ----echo=TRUE----------------------------------------------------------------
res_bwd <- vismeteor::select_knots(rates, cand, score_bic, backward = TRUE)
sort(c(res_bwd$knots, res_bwd$fixed_knots))
res_bwd$score
## ----echo=TRUE, fig.alt="BIC trajectory of the backward search over the number of interior knots, with the score optimum at the endpoint"----
plot(res_bwd$history$n_knots, res_bwd$history$score,
type = "b", pch = 19, col = "blue",
xlab = "number of interior knots", ylab = "BIC",
main = "Backward elimination: BIC trajectory"
)
points(tail(res_bwd$history$n_knots, 1L), res_bwd$score,
pch = 19, col = "red", cex = 1.5
)
abline(h = res_bwd$score, lty = 2, col = "grey50")
## ----echo=TRUE----------------------------------------------------------------
best <- if (res_fwd$score <= res_bwd$score) {
c(res_fwd$knots, res_fwd$fixed_knots)
} else {
c(res_bwd$knots, res_bwd$fixed_knots)
}
fit <- fit_glm(rates, sort(best))
# Data-driven population index of the shower, with 95% Wald CI
# (log-scale CI on the limmag coefficient, exponentiated).
limmag_row <- summary(fit)$coefficients["limmag", ]
r_hat <- exp(limmag_row["Estimate"])
r_ci <- exp(limmag_row["Estimate"] +
c(-1, 1) * 1.96 * limmag_row["Std. Error"])
c(
estimate = unname(r_hat),
lower = unname(r_ci[1]),
upper = unname(r_ci[2])
)
## ----echo=TRUE, fig.alt="Fitted ZHR curve over solar longitude with a 95% confidence band and selected knots marked on the axis"----
sl_grid <- seq(139.2, 140.6, length.out = 400)
pred_df <- data.frame(sl = sl_grid, limmag = 6.5, t = 1)
link <- predict(fit, newdata = pred_df, type = "link", se.fit = TRUE)
zhr <- exp(link$fit)
zhr_lo <- exp(link$fit - 1.96 * link$se.fit)
zhr_hi <- exp(link$fit + 1.96 * link$se.fit)
plot(sl_grid, zhr,
type = "n",
ylim = range(c(zhr_lo, zhr_hi)),
xlab = "solar longitude (deg)", ylab = "ZHR",
main = "PER 2015 - fitted activity profile (95% CI)"
)
polygon(c(sl_grid, rev(sl_grid)),
c(zhr_lo, rev(zhr_hi)),
col = adjustcolor("blue", alpha.f = 0.2), border = NA
)
lines(sl_grid, zhr, col = "blue", lwd = 2)
rug(best, col = "red")
## ----echo=TRUE, fig.alt="Binned observed-over-expected ratios with one-sigma error bars over solar longitude"----
# Use a coarser binning here than for the candidate grid above
# (>= 500 meteors per bin) so that bin uncertainties are small enough
# to read systematic deviations from the diagnostic plot.
rates$qres <- vismeteor::freq_quantile(rates$freq, 500)
rates$pred <- predict(fit, type = "response")
bins <- with(rates, {
obs_n <- tapply(freq, qres, sum)
pred_n <- tapply(pred, qres, sum)
data.frame(
sl = tapply(sl * freq, qres, sum) / obs_n,
ratio = obs_n / pred_n,
se = sqrt(obs_n) / pred_n
)
})
nrow(bins)
plot(bins$sl, bins$ratio,
pch = 19, col = "blue",
ylim = range(c(
bins$ratio - 2 * bins$se,
bins$ratio + 2 * bins$se
)),
xlab = "solar longitude (deg)",
ylab = "observed / expected",
main = "PER 2015 - binned residual ratios (+/- 1 sigma)"
)
arrows(bins$sl, bins$ratio - bins$se,
bins$sl, bins$ratio + bins$se,
angle = 90, code = 3, length = 0.03, col = "blue"
)
abline(h = 1, lty = 2, col = "grey50")
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.