This vignette demonstrates basic usage of surveil for public health research. The package is designed for routine time trend analysis, namely for time trends in disease incidence rates or mortality rates. Models were built using the Stan modeling language for Bayesian inference with Markov chain Monte Carlo (MCMC), but users only need to be familiar with the R language.
The package also contains special methods for age-standardization,
printing and plotting model results, and for measuring and visualizing
health inequalities. For age-standardization see
vignette("age-standardization"). For discussion and
demonstration analysis see Donegan et al. (2022).
To use the models provided by surveil, the surveillance data minimally must contain case counts, population at risk estimates, and a discrete time period variable. The data may also include one or more grouping variables, such as race-ethnicity. Time periods should consist of equally spaced intervals.
This vignette analyzes colorectal cancer incidence data by race-ethnicity, year, and Texas MSA for ages 50-79 (data obtained from CDC Wonder). The race-ethnicity grouping includes (non-Hispanic) black, (non-Hispanic) white, and Hispanic, and the MSAs include those centered on the cities of Austin, Dallas, Houston, and San Antonio.
head(msa) |>
kable(booktabs = TRUE,
caption = "Glimpse of colorectal cancer incidence data (CDC Wonder)") | Year | Race | MSA | Count | Population |
|---|---|---|---|---|
| 1999 | Black or African American | Austin-Round Rock, TX | 28 | 14421 |
| 2000 | Black or African American | Austin-Round Rock, TX | 16 | 15215 |
| 2001 | Black or African American | Austin-Round Rock, TX | 22 | 16000 |
| 2002 | Black or African American | Austin-Round Rock, TX | 24 | 16694 |
| 2003 | Black or African American | Austin-Round Rock, TX | 34 | 17513 |
| 2004 | Black or African American | Austin-Round Rock, TX | 26 | 18429 |
The primary function in surveil is stan_rw,
which fits random walk models to surveillance data. The function is
expects the user to provide a data.frame with specific
column names. There must be one column named Count
containing case counts, and another column named
Population, containing the sizes of the populations at
risk. The user must provide the name of the column containing the time
period (the default is time = Year, to match CDC Wonder
data). Optionally, one can provide a grouping factor. For the MSA data
printed above, the grouping column is Race and the time column is
Year.
We will demonstrate using aggregated CRC cases across Texas’s top
four MSAs. The msa data from CDC Wonder already has the
necessary format (column names and contents), but these data are
dis-aggregated by MSA. So for this analysis, we first group the data by
year and race, and then combine cases across MSAs.
The following code chunk aggregates the data by year and race-ethnicity:
The following code provides a glimpse of the aggregated data:
head(msa2) |>
kable(booktabs = TRUE,
caption = "Glimpse of aggregated Texas metropolitan CRC cases, by race and year")| Year | Race | Count | Population |
|---|---|---|---|
| 1999 | Black or African American | 471 | 270430 |
| 2000 | Black or African American | 455 | 283280 |
| 2001 | Black or African American | 505 | 298287 |
| 2002 | Black or African American | 539 | 313133 |
| 2003 | Black or African American | 546 | 329481 |
| 2004 | Black or African American | 602 | 346886 |
The base surveil model is specified as follows. The Poisson model is used as the likelihood: the probability of observing a given number of cases, \(y_t\), conditional on a given level of risk, \(e^{\phi_t}\), and known population at risk, \(p_t\), is: \[y_t \sim \text{Pois}(p_t \cdot e^{\phi_t})\] where \(t\) indexes the time period.
Next, we need a model for the log-rates, \({\phi_t}\). The first-difference prior states that our expectation for the log-rate at any time is its previous value, and we assign a normal probability distribution to deviations from the previous value (Clayton 1996). This is also known as the random-walk prior: \[\phi_t \sim \text{Gau}(\phi_{t-1}, \tau^2)\] This places higher probability on a smooth trend through time, specifically implying that underlying disease risk tends to have less variation than crude incidence.
The log-risk for time \(t=1\) has no previous value to anchor its expectation; thus, we assign a prior probability distribution directly to \(\phi_1\). For this prior, surveil uses a normal distribution. The scale parameter, \(\tau\), also requires a prior distribution, and again surveil uses a normal model which is diffuse relative to the log incidence rates.
In addition to the Poisson model, the binomial model is also available: \[y_t \sim \text{Binom}(p_t \cdot g^{-1}(\phi_t))\] where \(g\) is the logit function and \(g^{-1}(x) = \frac{exp(x)}{1 + exp(x)}\) (the inverse-logit function). If the binomial model is used the rest of the model remains the same as stated above. The Poisson model is typically preferred for rare events (such as rates below .01), otherwise the binomial model is usually more appropriate. The remainder of this vignette will proceed using the Poisson model only.
The time series model is fit by passing surveillance data to the
stan_rw function. Here, Year and
Race indicate the appropriate time and grouping columns in
the msa2 data frame.
fit <- stan_rw(msa2, time = Year, group = Race, iter = 1e3)
#> Distribution: normal
#> Distribution: normal
#> [1] "Setting normal prior(s) for eta_1: "
#> location scale
#> -6 5
#> [1] "\nSetting half-normal prior for sigma: "
#> location scale
#> 0 1
#>
#> SAMPLING FOR MODEL 'RW' NOW (CHAIN 1).
#> Chain 1:
#> Chain 1: Gradient evaluation took 1.7e-05 seconds
#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.17 seconds.
#> Chain 1: Adjust your expectations accordingly!
#> Chain 1:
#> Chain 1:
#> Chain 1: Iteration: 1 / 1000 [ 0%] (Warmup)
#> Chain 1: Iteration: 501 / 1000 [ 50%] (Sampling)
#> Chain 1: Iteration: 1000 / 1000 [100%] (Sampling)
#> Chain 1:
#> Chain 1: Elapsed Time: 0.225 seconds (Warm-up)
#> Chain 1: 0.152 seconds (Sampling)
#> Chain 1: 0.377 seconds (Total)
#> Chain 1:
#>
#> SAMPLING FOR MODEL 'RW' NOW (CHAIN 2).
#> Chain 2:
#> Chain 2: Gradient evaluation took 1.2e-05 seconds
#> Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.12 seconds.
#> Chain 2: Adjust your expectations accordingly!
#> Chain 2:
#> Chain 2:
#> Chain 2: Iteration: 1 / 1000 [ 0%] (Warmup)
#> Chain 2: Iteration: 501 / 1000 [ 50%] (Sampling)
#> Chain 2: Iteration: 1000 / 1000 [100%] (Sampling)
#> Chain 2:
#> Chain 2: Elapsed Time: 0.262 seconds (Warm-up)
#> Chain 2: 0.154 seconds (Sampling)
#> Chain 2: 0.416 seconds (Total)
#> Chain 2:
#>
#> SAMPLING FOR MODEL 'RW' NOW (CHAIN 3).
#> Chain 3:
#> Chain 3: Gradient evaluation took 1.2e-05 seconds
#> Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.12 seconds.
#> Chain 3: Adjust your expectations accordingly!
#> Chain 3:
#> Chain 3:
#> Chain 3: Iteration: 1 / 1000 [ 0%] (Warmup)
#> Chain 3: Iteration: 501 / 1000 [ 50%] (Sampling)
#> Chain 3: Iteration: 1000 / 1000 [100%] (Sampling)
#> Chain 3:
#> Chain 3: Elapsed Time: 0.893 seconds (Warm-up)
#> Chain 3: 0.285 seconds (Sampling)
#> Chain 3: 1.178 seconds (Total)
#> Chain 3:
#>
#> SAMPLING FOR MODEL 'RW' NOW (CHAIN 4).
#> Chain 4:
#> Chain 4: Gradient evaluation took 1.2e-05 seconds
#> Chain 4: 1000 transitions using 10 leapfrog steps per transition would take 0.12 seconds.
#> Chain 4: Adjust your expectations accordingly!
#> Chain 4:
#> Chain 4:
#> Chain 4: Iteration: 1 / 1000 [ 0%] (Warmup)
#> Chain 4: Iteration: 501 / 1000 [ 50%] (Sampling)
#> Chain 4: Iteration: 1000 / 1000 [100%] (Sampling)
#> Chain 4:
#> Chain 4: Elapsed Time: 0.25 seconds (Warm-up)
#> Chain 4: 0.155 seconds (Sampling)
#> Chain 4: 0.405 seconds (Total)
#> Chain 4:The iter = 1e3 line controls how long the MCMC sampling
continues for (in this case, 1,000 samples: 500 warmup, then 500 more
for inference). The default is 3,000, which is more than sufficient for
this example model. By default, there are four independent MCMC chains
each with 500 post-warmup samples (for a total of 2,000 MCMC samples
used for the estimates).
To speed things up, we could take advantage of parallel processing
using the cores argument (e.g., add cores = 4)
to run on 4 cores simultaneously. You can suppress the messages seen
above by adding refresh = 0.
The print method will print the estimates with 95%
credible intervals to the console; adding scale = 100e3
will display rates per 100,000:
print(fit, scale = 100e3)
#> Summary of surveil model results
#> Time periods: 19
#> Grouping variable: Race
#> Correlation matrix: FALSE
#> time Race mean lwr_2.5 upr_97.5
#> 1 1999 Black or African American 170.17620 158.56603 182.84421
#> 2 2000 Black or African American 166.65960 157.06565 176.49169
#> 3 2001 Black or African American 168.57204 159.08615 178.73883
#> 4 2002 Black or African American 169.27150 159.80362 179.16973
#> 5 2003 Black or African American 167.01645 157.85997 176.22353
#> 6 2004 Black or African American 166.81808 158.00431 176.27193
#> 7 2005 Black or African American 158.96709 150.16450 168.00437
#> 8 2006 Black or African American 154.68172 146.75835 162.77986
#> 9 2007 Black or African American 152.61540 144.85032 161.19238
#> 10 2008 Black or African American 149.56281 141.75279 158.32158
#> 11 2009 Black or African American 143.35815 135.84849 151.43697
#> 12 2010 Black or African American 138.76102 131.68338 146.37552
#> 13 2011 Black or African American 130.86321 123.69239 137.94591
#> 14 2012 Black or African American 125.27442 118.20779 131.92147
#> 15 2013 Black or African American 124.75531 118.28214 131.42784
#> 16 2014 Black or African American 126.14475 119.68626 132.70496
#> 17 2015 Black or African American 122.43709 116.47278 128.84942
#> 18 2016 Black or African American 122.01009 116.03626 128.24360
#> 19 2017 Black or African American 122.55407 116.02847 129.60759
#> 20 1999 Hispanic 101.60744 94.97870 108.12345
#> 21 2000 Hispanic 104.19405 98.42559 110.97374
#> 22 2001 Hispanic 102.51346 97.29833 108.09451
#> 23 2002 Hispanic 101.57005 96.12340 107.23750
#> 24 2003 Hispanic 100.61554 95.42831 105.87804
#> 25 2004 Hispanic 100.94366 96.01808 106.65581
#> 26 2005 Hispanic 98.62167 93.83938 103.70796
#> 27 2006 Hispanic 96.87079 92.20459 101.85314
#> 28 2007 Hispanic 94.21150 89.57709 99.09583
#> 29 2008 Hispanic 90.52740 85.86766 95.05179
#> 30 2009 Hispanic 88.83456 84.35083 93.33417
#> 31 2010 Hispanic 87.99281 83.76223 91.85287
#> 32 2011 Hispanic 87.46709 83.37560 91.29235
#> 33 2012 Hispanic 87.80676 83.92391 91.71173
#> 34 2013 Hispanic 87.22368 83.29873 91.16412
#> 35 2014 Hispanic 85.79275 81.86547 89.60209
#> 36 2015 Hispanic 85.87133 82.13569 89.85844
#> 37 2016 Hispanic 84.83554 81.09612 88.44778
#> 38 2017 Hispanic 85.91234 81.78675 90.26341
#> 39 1999 White 135.21738 130.43345 140.42452
#> 40 2000 White 136.57860 131.95270 141.38682
#> 41 2001 White 134.09319 129.38081 138.74150
#> 42 2002 White 130.15841 125.95231 134.50807
#> 43 2003 White 127.49018 123.42115 131.90979
#> 44 2004 White 120.01871 116.13311 124.24389
#> 45 2005 White 115.07711 111.48627 118.85946
#> 46 2006 White 109.74862 105.98128 113.61629
#> 47 2007 White 108.18774 104.63897 111.93827
#> 48 2008 White 103.43684 99.75436 107.15861
#> 49 2009 White 98.89327 95.52748 102.41391
#> 50 2010 White 96.26625 92.97231 99.50212
#> 51 2011 White 93.67948 90.50950 96.92205
#> 52 2012 White 91.12084 88.07536 94.22922
#> 53 2013 White 90.12701 86.86186 93.25926
#> 54 2014 White 91.47527 88.51840 94.55892
#> 55 2015 White 93.07195 89.84169 96.36630
#> 56 2016 White 89.05848 85.97388 92.12206
#> 57 2017 White 92.31277 89.24290 95.62076This information is also stored in a data frame,
fit$summary:
head(fit$summary)
#> time mean lwr_2.5 upr_97.5 Race Year Count
#> 1 1999 0.001701762 0.001585660 0.001828442 Black or African American 1999 471
#> 2 2000 0.001666596 0.001570656 0.001764917 Black or African American 2000 455
#> 3 2001 0.001685720 0.001590862 0.001787388 Black or African American 2001 505
#> 4 2002 0.001692715 0.001598036 0.001791697 Black or African American 2002 539
#> 5 2003 0.001670164 0.001578600 0.001762235 Black or African American 2003 546
#> 6 2004 0.001668181 0.001580043 0.001762719 Black or African American 2004 602
#> Population Crude
#> 1 270430 0.001741671
#> 2 283280 0.001606185
#> 3 298287 0.001693000
#> 4 313133 0.001721313
#> 5 329481 0.001657152
#> 6 346886 0.001735440The fit$summary object can be used to create custom
plots and tables.
If we call plot on a fitted surveil model, we
get a ggplot object depicting risk estimates with 95%
credible intervals:
The crude incidence rates (observed values) are also plotted here as
points.
The plot method has a number of optional arguments that
control its appearance. For example, the base_size argument
controls the size of labels. The size of the points for the crude rates
can be adjusted using size, and size = 0
removes them altogether. We can also use ggplot to add
custom modifications:
fig <- plot(fit, scale = 100e3, base_size = 11, size = 0)
#> Plotted rates are per 100,000
fig +
theme(legend.position = "right") +
labs(title = "CRC incidence per 100,000",
subtitle = "Texas MSAs, 50-79 y.o.")The plot method has a style argument that controls how
uncertainty is represented. The default, style = "mean_qi",
shows the mean of the posterior distribution as the estimate and adds
shading to depict the 95% credible interval (as above). The alternative,
style = "lines", plots MCMC samples from the joint
probability distribution for the estimates:
By default, M = 250 samples are plotted. The
style option is available for all of the surveil
plot methods. This style is sometimes helpful for visualizing multiple
groups when their credible intervals overlap.
The apc method calculates percent change by period and
cumulatively over time:
The object returned by apc contains two data frames. The
first contains estimates of percent change from the previous period:
head(fit_pc$apc)
#> time group apc lwr upr
#> 1 1999 Black or African American 0.0000000 0.000000 0.000000
#> 2 2000 Black or African American -1.9853879 -8.677548 4.738472
#> 3 2001 Black or African American 1.1999017 -4.845906 7.991774
#> 4 2002 Black or African American 0.4686197 -5.942371 7.102559
#> 5 2003 Black or African American -1.2797341 -7.355442 5.029313
#> 6 2004 Black or African American -0.0619090 -6.399530 6.868433Those estimates typically have high uncertainty.
The second data frame contains estimates of cumulative percent change (since the first observed period):
head(fit_pc$cpc)
#> time group cpc lwr upr
#> 1 1999 Black or African American 0.0000000 0.000000 0.000000
#> 2 2000 Black or African American -1.9853879 -8.677548 4.738472
#> 3 2001 Black or African American -0.8342763 -8.824925 7.437139
#> 4 2002 Black or African American -0.4131436 -8.688848 8.464513
#> 5 2003 Black or African American -1.7328798 -10.317148 7.149338
#> 6 2004 Black or African American -1.8538927 -10.229462 7.239751Each value in the cpc column is an estimate of the
difference in incidence rates between that year and the first year (in
this case, 1999) expressed as a percent of the first year’s rate. The
lwr and upr columns are the lower and upper
bounds of the 95% credible intervals for the estimates.
This information can also be plotted:
If desired, the average annual percent change from the first period can be calculated by dividing the cumulative percent change (CPC) by the appropriate number of periods. For example, the CPC from 1999 to 2017 for whites is about -31 for an average annual percent change of about \(-31/18 = -1.72\). The credible intervals for the average annual percent change can also be obtained from the CPC table using the same method. For this example, the correct denominator is \(2017-1999=18\) (generally: the last year minus the first year).
If you do not see any warnings printed to the R console at the end of
the model fitting process then you can simply move forward with the
analysis. If there is a warning, it may say that the effective sample
size is low or that the R-hat values are large. For a crash course on
MCMC analysis with surveil, including MCMC diagnostics, see the
vignette on the topic vignette("surveil-mcmc").
A quick and dirty summary is that you want to watch two key
diagnostics, which are effective MCMC sample size (ESS) and R-hat. For
all your parameters of interest, ESS should be at least 400 or so. If
you want to increase the ESS, use the iter argument to draw
more samples. The R-hat values should all be pretty near to one, such as
within the range \(1 \pm .02\). For the
simple models we are using here, large R-hat values can often be fixed
just by drawing more samples.
You can find these diagnostics by printing results
print(fit$samples) and seeing the columns
n_eff (for ESS) and Rhat.
The MCMC sampling is generally fast and without trouble when the
numbers are not too small (as in this example model). When the incidence
rates are based on small numbers (like counts less than 20), the
sampling often proceeds more slowly and a higher iter value
may be needed.
In most applications, the base model specification described above will be entirely sufficient. However, surveil provides an option for users to add a correlation structure to the model when multiple groups are modeled together. For correlated trends, this can increase the precision of estimates.
The log-rates for \(k\) populations, \(\boldsymbol \phi_t\), are assigned a multivariate normal distribution (Brandt and Williams 2007): \[\boldsymbol \phi_t \sim \text{Gau}(\boldsymbol \phi_{t-1}, \boldsymbol \Sigma),\] where \(\boldsymbol \Sigma\) is a \(k \times k\) covariance matrix.
The covariance matrix can be decomposed into a diagonal matrix containing scale parameters for each variable, \(\boldsymbol \Delta = diag(\tau_1,\dots \tau_k)\), and a symmetric correlation matrix, \(\boldsymbol \Omega\) (Stan Development Team 2021): \[\boldsymbol \Sigma = \boldsymbol \Delta \boldsymbol \Omega \boldsymbol \Delta\] When the correlation structure is added to the model, then a prior distribution is also required for the correlation matrix. surveil uses the LKJ model, which has a single shape parameter, \(\eta\) (Stan Development Team 2021). If \(\eta=1\), the LKJ model will place uniform prior probability on any \(k \times k\) correlation matrix; as \(\eta\) increases from one, it expresses ever greater skepticism towards large correlations. When \(\eta <1\), the LKJ model becomes ‘concave’—expressing skepticism towards correlations of zero.
If we wanted to add a correlation structure to the model, we would
add cor = TRUE to stan_rw, as in: