-
Notifications
You must be signed in to change notification settings - Fork 3
Update scrub typhus vignette with published citation and corrected analysis #499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f385618
04b674b
4f757d3
bc2e53b
6a1452b
92f5cce
7fd7df5
a6b5872
9e72d86
dc6e4a0
7bb08e2
29e5443
58fc566
6db90bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| --- | ||||||
| title: "Scrub Typhus Seroincidence Vignette" | ||||||
| author: "UC Davis Seroepidemiology Research Group (SERG)" | ||||||
| date: "First Published: 2024-AUG-01 | Updated: 2026-JAN-28" | ||||||
| output: | ||||||
| bookdown::html_document2: | ||||||
| base_format: rmarkdown::html_vignette | ||||||
|
|
@@ -14,7 +15,9 @@ bibliography: ../references.bib | |||||
| --- | ||||||
| ## Introduction | ||||||
|
|
||||||
| This vignette reproduces the analysis for: [**Estimating the seroincidence of scrub typhus using antibody dynamics following infection**](https://www.medrxiv.org/content/10.1101/2022.11.07.22282017v2) (@Aiemjoy_2024_scrub). | ||||||
| This vignette reproduces the analysis for: [**Estimating the Seroincidence of Scrub Typhus using Antibody Dynamics after Infection**](https://www.ajtmh.org/view/journals/tpmd/111/2/article-p267.xml) (@Aiemjoy_2024_scrub). | ||||||
|
|
||||||
| **Citation:** Aiemjoy, Kristen, Nishan Katuwal, Krista Vaidya, Sony Shrestha, Melina Thapa, Peter Teunis, Isaac I. Bogoch et al. "Estimating the Seroincidence of Scrub Typhus using Antibody Dynamics after Infection." The American Journal of Tropical Medicine and Hygiene 111, no. 2 (2024): 267. https://doi.org/10.4269/ajtmh.23-0475 | ||||||
|
|
||||||
| ## Methods | ||||||
|
|
||||||
|
|
@@ -38,7 +41,7 @@ Given the seroresponse, this marginal distribution of antibody concentrations ca | |||||
|
|
||||||
| ## Scrub Typhus Seroincidence | ||||||
|
|
||||||
| Scrub typhus, a vector-borne bacterial infection, is an important but neglected disease globally. Accurately characterizing burden is challenging due to non-specific symptoms and limited diagnostics. Prior seroepidemiology studies have struggled to find consensus cutoffs that permit comparing estimates across contexts and time. In this study, we present a novel approach that does not require a cutoff and instead uses information about antibody kinetics after infection to estimate seroincidence. We use data from three cohorts of scrub typhus patients in Chiang Rai, Thailand, and Vellore, India to characterize antibody kinetics after infection and two population serosurveys in the Kathmandu valley, Nepal, and Tamil Nadu, India to estimate seroincidence. The samples were tested for IgM and IgG responses to Orientia tsutsugamushi-derived recombinant 56-kDa antigen using commercial ELISA kits. We used Bayesian hierarchical models to characterize antibody responses after scrub typhus infection and used the joint distributions of the peak antibody titers and decay rates to estimate population-level incidence rates in the cross-sectional serosurveys. | ||||||
| Scrub typhus, a vector-borne bacterial infection, is an important but neglected disease globally. Accurately characterizing burden is challenging due to non-specific symptoms and limited diagnostics. Prior seroepidemiology studies have struggled to find consensus cutoffs that permit comparing estimates across contexts and time. In this study, we present a novel approach that does not require a cutoff and instead uses information about antibody kinetics after infection to estimate seroincidence. We use data from three cohorts of scrub typhus patients in Chiang Rai, Thailand, and Vellore, India to characterize antibody kinetics after infection and two population serosurveys in the Kathmandu valley, Nepal, and Tamil Nadu, India to estimate seroincidence. The samples were tested for IgM and IgG responses to Orientia tsutsugamushi-derived recombinant 56-kDa antigen using commercial ELISA kits. These antigens (OT56kdaIgG and OT56kdaIgM) represent IgG and IgM responses to a 56 kilodalton antigen on the membrane of *Orientia tsutsugamushi* (OT) that have been found to be specific to this organism and are used in diagnosis. We used with-host Bayesian hierarchical models to characterize antibody responses after scrub typhus infection and used the joint distributions of the peak antibody titers and decay rates to estimate population-level incidence rates in the cross-sectional serosurveys. | ||||||
|
||||||
|
|
||||||
|
|
||||||
| ```{r, include = FALSE} | ||||||
|
|
@@ -94,7 +97,8 @@ curves <- | |||||
| We can graph the decay curves with an `autoplot()` method: | ||||||
|
|
||||||
| ```{r} | ||||||
| curves |> autoplot() | ||||||
| # Visualize curve parameters with custom colors | ||||||
| curves |> autoplot() | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -135,10 +139,13 @@ xs_data |> summary(strata = "country") | |||||
| Let's also take a look at how antibody responses change by age. | ||||||
|
|
||||||
| ```{r plot-age} | ||||||
| # Plot antibody responses by age | ||||||
| autoplot(object = xs_data, type = "age-scatter", strata = "country") | ||||||
| # Plot antibody responses by age with custom colors | ||||||
| autoplot(object = xs_data, type = "age-scatter", strata = "country") + | ||||||
| scale_color_manual(values = c("India" = "orange2", "Nepal" = "#39558CFF")) | ||||||
| ``` | ||||||
|
|
||||||
| As is clear from these data, the study in Nepal was performed in children, while the study in India was performed in adults, yet we can see that the values were higher on average and with less variation in Nepal. | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| ### Compile noise parameters | ||||||
|
|
@@ -160,45 +167,73 @@ Column Name | Description | |||||
| *Note that variable names are case-sensitive.* | ||||||
|
|
||||||
| ```{r message=FALSE, warning=FALSE} | ||||||
| # biologic noise | ||||||
| # Biologic noise calculation | ||||||
| # Note: There was an error in the SD calculation in the original paper. | ||||||
| # The correct approach is to use the sigma value directly from the mixture model, | ||||||
| # not sqrt(sigma). This vignette uses the corrected calculation. | ||||||
|
|
||||||
| set.seed(1234) | ||||||
|
|
||||||
|
|
||||||
| b_noise <- xs_data |> | ||||||
| group_by(antigen_iso) |> | ||||||
| filter(!is.na(value)) |> | ||||||
| filter(age < 40) |> # restrict to young ages to capture recent exposures | ||||||
| do({ | ||||||
| set.seed(54321) | ||||||
| # Fit the mixture model | ||||||
| mixmod <- normalmixEM(.$value, k = 2, maxit = 1000) | ||||||
| # k is the number of components, adjust as necessary | ||||||
| # Assuming the first component is the lower distribution: | ||||||
| lower_mu <- mixmod$mu[1] | ||||||
| lower_sigma <- sqrt(mixmod$sigma[1]) | ||||||
| # Calculate the 90th percentile of the lower distribution | ||||||
| percentile75 <- qnorm( | ||||||
| 0.75, | ||||||
| lower_mu, | ||||||
| lower_sigma | ||||||
| group_by(antigen_iso) |> | ||||||
| group_modify(~{ | ||||||
| mixmod <- mixtools::normalmixEM(.x$value, k = 2, maxit = 1000) | ||||||
|
|
||||||
| lower_k <- which.min(mixmod$mu) # pick the lower-mean component | ||||||
| lower_mu <- mixmod$mu[lower_k] | ||||||
| lower_sd <- mixmod$sigma[lower_k] | ||||||
|
|
||||||
| tibble::tibble( | ||||||
| percentile95 = qnorm(0.95, mean = lower_mu, sd = lower_sd) | ||||||
| ) | ||||||
|
Comment on lines
+188
to
190
|
||||||
| # Return the results | ||||||
| data.frame(antigen_iso = .$antigen_iso[1], | ||||||
| percentile75 = percentile75) | ||||||
| }) | ||||||
| }) |> | ||||||
| ungroup() | ||||||
|
|
||||||
| # Biologic noise calculation (using children age <2 with lower liklihood of prior exposure) | ||||||
|
||||||
| # Biologic noise calculation (using children age <2 with lower liklihood of prior exposure) | |
| # Biologic noise calculation (using children age <5 with lower likelihood of prior exposure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct?
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The noise table assigns nu/nu_u5 by positional indexing (e.g., b_noise[2,2]), which is brittle and can silently mis-map values if group order changes. Instead, map/join by antigen_iso (or pivot wider) and then build noise from those keyed values. Also note nu_u5 is not a recognized column in noise_param_names and will be dropped during estimation, so it currently only affects the printed table (not the model inputs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might be where the NA comes from?
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in narrative: “IgG responsed” should be “IgG responses” (or “IgG response”). Fix the text and then remove “responsed” from inst/WORDLIST rather than whitelisting the misspelling.
| Now we are ready to begin estimating seroincidence using IgG responsed to 56kda. We will use `est.incidence.by` to calculate stratified seroincidence rates. | |
| Now we are ready to begin estimating seroincidence using IgG responses to 56kda. We will use `est.incidence.by` to calculate stratified seroincidence rates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Copilot is correct on this one?
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This chunk switches from the project’s standard native pipe (|>) to %>%, which will be flagged by the repo’s pipe consistency linter (see .lintr.R pipe_consistency_linter(pipe = "|>") at ~line 63). Please convert these %>% steps to |> for consistency and to avoid CI lint failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see https://r4ds.hadley.nz/data-transform.html#sec-the-pipe:~:text=So%20why%20do%20we%20recommend%20the%20base%20pipe%3F for the reasoning for |> vs %>%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
responsedis a misspelling (should be “responses”). It looks like this was added to bypass spell checking, but it’s better to fix the typo in the vignette and remove this entry from WORDLIST so genuine typos aren’t whitelisted.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(see previous comment)