Attributable fraction using a latent class model

  collapse = TRUE,
  comment = "#>"

By John Aponte and Orvalho Augusto.


In malaria endemic areas, asymptomatic carriage of malaria parasites occurs frequently and the detection of malaria parasites in blood films from a febrile individual does not necessarily indicate clinical malaria.

A case definition for symptomatic malaria that is used widely in endemic areas requires the presence of fever or history of fever together with a parasite density above a specific cutoff. If the parasite density is equal or higher than the cutoff point, the fever is considered due to malaria.

How to estimate what is the sensitivity and specificity of the cutoff point in the classification of the fever, without knowing what is the true value of the fevers due to malaria?

Using the attributable fraction, one can estimate the expected number of true cases due to malaria and with the positive predictive value associated with a given cutoff point, we can estimate the expected number of true cases among the fever cases that have a parasite density higher or equal than the selected cutoff point.

In order to estimate the attributable fraction and the positive predictive values, we follow the method proposed by Vounatsou et al (1) fitting a bayesian latent class model.

The latent class model have several advantages over the logistic exponential model: do not make any assumption on the shape of the risk of the fever, only that at higher categories, higher the risk of fever so there is little risk of bias, and provide direct estimation of the attributable fraction and the other quantities like specificity and sensitivity, allowing to estimate directly confidence intervals, however the attributable fraction can be underestimated if the number of categories is too high.

Here it is presented how to do it with the afdx package for the R-software.

Example using synthetic data

The data used in this example (malaria_df1) is a simulated data set as seen frequently in malaria cross-sectionals where two main outcomes are measured, the presence of fever or history of fever (fever column) and the measured parasite density in parasites per $\mu l$ (density column).


  head(malaria_df1, n = 6),
       caption = "head(malaria_df1) first 6 observations", 
       format = "html") %>%
kable_styling( position = "left")

In this simulation, there are 2000 observations, from which r sum(malaria_df1$fever) have fever or history of fever and r sum(malaria_df1$density > 0) have a density of malaria greater than 0. A total of r sum(malaria_df1$fever * (malaria_df1$density > 0)) have both fever and a malaria density higher than 0.

cutoffs <- c(0,1,100,200,400,800,1600,3200,6400,12800,25600,51200, 102400, 204800)
data <- 
  malaria_df1 %>%
  mutate(k = cut(density,c(cutoffs,Inf), include.lowest =T, labels = cutoffs)) %>%
  group_by(k,fever) %>%
  tally() %>%
  mutate(category = ifelse(fever ==1,"n (fever)","m (no fever)")) %>%
  select(-fever) %>%
  pivot_wider(names_from = "category", values_from = "n", values_fill = list(n = 0)) %>%
  rename(`k (category lower limit)` = k)

kable(data, "html", caption="Distribution of fevers by density categories") %>%
kable_styling(position = "left")  

The latent class model

* TODO: Include more Details on the model

* TODO: Include Detail on the calculation of sensitivity, specificity and predictive values

Estimating the bayesian latent class model

The adfx provide functions that facilitate the fitting of the bayesian latent class model using the rjags package, but the user is responsible to setup the appropriate bayesian workflow and confirm the convergence of the model. Here we present one way to do it but there are many other possibilities. We use a burn-in of 1000 iterations and monitor samples from 4 chains 10000 iterations.

The function get_latent_model() provides a string with a model that can be use by rjags,

model <- get_latent_model()

Data in the model must be provided as a list with two vectors:

The model calculate as data the number of categories (K) and the total number of subjects in each group (Sn, Sm)


# compile the model
af_latent <-
    data = list(n = data$`n (fever)`,
                m = data$`m (no fever)`),
    n.chains = 4,
    n.adapt = 1000,
    inits = list(
      list( = "base::Wichmann-Hill", .RNG.seed = 1111),
      list( = "base::Wichmann-Hill", .RNG.seed = 2222),
      list( = "base::Wichmann-Hill", .RNG.seed = 3333),
      list( = "base::Wichmann-Hill", .RNG.seed = 4444)

# Simulate the posterior
latent_sim <-
    model = af_latent,
    variable.names = c('lambda','sens','spec','ppv','npv'),
    n.thinning = 5,
    n.iter =  10000 )

# Extract and Analyze the posterior
latent_sum <-  summary(latent_sim)
latent_eff <-  effectiveSize(latent_sim)
# Load results from the model
latent_sum <- readRDS(system.file("vignette_data/latent_sum.RDS", package = "afdx"))
latent_eff <- readRDS(system.file("vignette_data/latent_eff.RDS", package = "afdx"))
# reformat to present the results
summary_table <-
  data.frame(latent_sum[[1]]) %>%
  bind_cols(data.frame(latent_sum[[2]])) %>%
  mutate(varname = row.names(latent_sum[[1]])) %>%
  mutate(cutoff  = c(NA, rep(cutoffs,4))) %>%
  select(varname, cutoff,Mean, X2.5., X50., X97.5.,Naive.SE ) %>%
  mutate(eff_size = floor(latent_eff)) %>%
  filter( | cutoff != 0) 

mean_table <- summary_table %>%
  rename(point = Mean) %>%
  rename(lci = `X2.5.`) %>%
  rename(uci = `X97.5.`) %>%
  mutate(varname = gsub("\\[.*\\]","",varname)) %>%
  filter(varname != "lambda") %>%
  select(cutoff,varname, lci, uci,point) %>%
  pivot_longer(-c("cutoff","varname"),names_to = "xxv", values_to = "value") %>%
  unite("varx",varname,xxv ) %>%
  pivot_wider(names_from = "varx", values_from = "value") %>%
         npv_uci) %>%
  rename(sensitivity = sens_point) %>%
  rename(specificity = spec_point) %>%
  rename(ppv = ppv_point) %>%
  rename(npv = npv_point) %>%
  mutate_if(is.numeric, round,3)

# Lambda corresponds to the attributable fraction
afrow <- 
  summary_table %>%
  filter(varname == "lambda") %>% 
  mutate_if(is.numeric, round,3)
kable(mean_table, caption = "Summary of diagnostic characteristics at selected cutoff points") %>% kable_styling()

Attributable fraction: r afrow$Mean 95%CI(r afrow$X2.5., r afrow$X97.5. )


  1. Vounatsou P, Smith T, Smith AFM. Bayesian analysis of two-component mixture distributions applied to estimating malaria attributable fractions. Journal of the Royal Statistical Society: Series C (Applied Statistics). 1998;47(4):575–87.

  2. Müller I, Genton B, Rare L, Kiniboro B, Kastens W, Zimmerman P, et al. Three different Plasmodium species show similar patterns of clinical tolerance of malaria infection. Malar J. 2009;8(1):158.

  3. Plucinski MM, Rogier E, Dimbu PR, Fortes F, Halsey ES, Aidoo M, et al. Performance of Antigen Concentration Thresholds for Attributing Fever to Malaria among Outpatients in Angola. J Clin Microbiol [Internet]. 2019 Feb 27 [cited 2020 May 4];57(3). Available from:

Try the afdx package in your browser

Any scripts or data that you put into this service are public.

afdx documentation built on May 25, 2021, 5:09 p.m.