home about blog

Serrated Cascade

HIV  R  graphics 

The idea that populations of people living with HIV ‘cascade’ over the steps towards controlling the virus with medication – testing, initiating treatment, suppressing the virus by adhering to the correct treatment – has been around for a long time.

Increasingly interventions are being tested that aim to reduce the number of people with infectious HIV in a population. HIV prevalence has been added so that the cascade describes everyone in the population (whether living with HIV or not). The ‘outcome’ of evaluations of these interventions is how the cascade has changed overall, and so we used a very simple ‘serrated cascade’ to show this in a recent paper looking at the effect of an intervention for female sex workers in Zimbabwe. The graph looked like this:

Serrated cascade from Cowen *et al.* 2018 Lancet HIV

In this case, the HIV prevalence did not change much over time, so the UNAIDS’ ‘90:90:90’ targets where pretty much the same from the start to the end of the trial.

In another study looking before-after the introduction of services for sex workers in Zimbabwe, the HIV prevalence did change quite a bit in some places we we needed to adapt the picture. We added indicators for the 90:90:90 targets at the start and the end of the period, but we were never sure how well this worked (we also didn’t have viral load data this time, but we did show the 95% confidence intervals in grey):

Serrated cascade using data from Ndori‐Mharadze *et al.* 2018 JAIS

Is this a good way of looking at the (HIV-related) health of a population? It captures a lot, but focusing on the treatment cascade ignores the degree of susceptibility of HIV-negative people in the population (e.g. the use of PrEP). Changing susceptibility will eventually be reflected in the HIV prevalence, but slowly and mixed-in with the changes in levels of treatment. Perhaps it would be possible to show the ‘prevention cascade’ data on the same graph?

Below is the data and the R code used to make the second of these graphs. Feel free to use the code (but please reference Cowen et al 2018, thanks!)

Data for graph:

level year percentage value pcLCI LCI pcUCI UCI
HIV prev 2011 50.6 0.51 41.7 0.42 59.0 0.59
HIV prev 2015 41.3 0.41 34.6 0.35 48.1 0.48
HIV positive Know Status 2011 50.8 0.26 36.2 0.15 65.4 0.39
HIV positive Know Status 2015 59.3 0.24 48.5 0.17 70.0 0.34
On ART, of those knowing status 2011 56.0 0.14 37.2 0.06 74.7 0.29
On ART, of those knowing status 2015 72.8 0.18 52.7 0.09 93.0 0.31


# Read the data (requires package openxlsx)
d <- read.xlsx('data.xlsx', sheet = i)
c <- unique(d$level)
y <- d$value
x <- c(0.01,.1, .11,.2, .21,.3,.31,.4,.31,.4)
xy <- cbind(x[1:length(y)],y,d$LCI,d$UCI)
pt <- c(21,19,21,19,21,19,21,19,21,19)

# Save PNG
png(filename = paste0('Cascade',sites[i],'.png'), width = 6, height = 8, units = 'in', res = 300)

# Create the plot and the points 
plot(xy, ylim=c(-.1,1), xlim=c(0.01,(max(xy[,1])-0.006)), yaxt="n", xaxt="n", type='n',
     pch=pt, bty="n", xlab="", ylab="Proportions in all women", cex.lab=.6, main=sites[i])
    # Add the confidence intervals
    for (r in 1:length(y)){
    rect(xleft=xy[r,1]-.0015, xright=xy[r,1]+.0015, ybottom=xy[r,3], ytop=xy[r,4], border=NA, col='gray80')

    # Add the sloping lines 
    for (r in seq(1, by=2, length.out = length(y)/2)){
      lines(xy[r:(r+1),1], xy[r:(r+1),2], type="l", lwd=1.7)
    #  Add the vertical lines
    for (r in c(1:length(y))){
      lines(c(xy[r,1],xy[r,1]),c(0,xy[r,2]), lwd=.9)
    # Add the points
    points(xy, pch=pt, bg='white')
    # Add horizontal line at bottom
    lines(c(0.01,.71), c(0,0))
    # Add the 90:90:90 markers
    for (i in 1:2){
      nnn <- c(y[i]*.9,          # First 90 for diagnosis 
               y[i]*.9*.9,       # Second 90 for treatment  
               y[i]*.9*.9*.9)    # Third 90 for suppression 
      points(.1-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[1], cex=2)
      points(.2-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[2], cex=2)
      points(.3-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[3], cex=2)
      text(.1-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[1],labels=paste0("90"), cex=.6)
      text(.2-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[2],labels=paste0("81"), cex=.6)
      text(.3-(1-i)*.1+(2-i)*.013+(1-i)*.003,nnn[3],labels=paste0("73"), cex=.6)
    # Add the dates 
    lines(c(xy[1,1],xy[1,1]),c(xy[1,4],xy[1,4]+.06), lty=3, lwd=.7)
    lines(c(xy[2,1],xy[2,1]),c(xy[2,4],xy[2,4]+.06), lty=3, lwd=.7)
    text(xy[1,1]+0.011,xy[1,4]+.07,labels=paste0(min(as.numeric(d$year))), cex=1)
    text(xy[2,1]+0.011,xy[2,4]+.07,labels=paste0(max(as.numeric(d$year))), cex=1)

    # Add axes     
    ap <- c(0,.2,.4,.6,.8,1)
    axis(2, at=ap, lab=paste0(ap * 100, "%"), las=TRUE, cex.axis=0.6)

    xp <- c(.05, .15, .25, .35, .55,.65)    
         labels=str_wrap(unique(d$level),25), cex=.8)
    legend(0,.97,c("Before intervention", "After intervention"),pch=c(1,19), bty="n", cex=1)
Written on October 4, 2018