ART Coverage Performance visualization, Reimagined
Borrowing from psychometrics to show who’s achieving, and how much it matters
Author
GMan
Published
March 11, 2026
The Global Fund tracks performance across hundreds of grants worldwide. This note pulls live data from the API and borrows a trick from psychometrics — the Wright Map — to show not just distribution of performance, but also how much countries contribute to the global target.
We pull two endpoints: Grants (to link grants to geographies) and AllProgrammaticIndicators (filtered to ART coverage, national-level, most recent reporting period ending in 2023).
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library( ggplot2 )library( patchwork )df_grants <-request("https://fetch.theglobalfund.org/v4.1/odata/Grants") %>%req_url_query() %>%req_perform() %>%resp_body_string() %>%fromJSON(flatten =TRUE) %>% .[["value"]] %>%select( grantId = id , geographyId , differentiationCategory ) %>%distinct( )df_geo <-request("https://fetch.theglobalfund.org/v4.1/odata/Geographies") %>%req_url_query() %>%req_perform() %>%resp_body_string() %>%fromJSON(flatten =TRUE) %>% .[["value"]] %>%select( geographyId = id , code , name )df_geo_grant <- df_grants %>%left_join( df_geo , by =c( "geographyId" ))httr_RnT <-request("https://fetch.theglobalfund.org/v4.2/odata/AllProgrammaticIndicators") %>%req_url_query(`$filter`="programmaticDataSet eq 'IMPLEMENTATION_PERIOD_TARGETS_RESULTS' AND valueType eq 'Coverage / Output indicator' AND performance ne null AND endDate gt 2019-12-31T00:00:00Z" ,`$expand`="implementationPeriod,activityArea" ) %>%req_perform()df_RnT <- httr_RnT %>%resp_body_string() %>%fromJSON(flatten =TRUE) %>% .[["value"]]df_RnT_ <- df_RnT %>%left_join( df_geo_grant , by =c("implementationPeriod.grantId"="grantId" ) ) %>%filter( !is.na( targetValueNumerator ) ) %>%mutate( endDate = lubridate::ymd_hms( endDate ) ,startDate = lubridate::ymd_hms( startDate ) ) %>%group_by( code ) %>%arrange( startDate ) %>%mutate( is_last_rp = startDate ==max( startDate) ) %>%ungroup() %>%filter( is_last_rp & endDate >= lubridate::ymd( "2023-07-01") & endDate <= lubridate::ymd( "2023-12-31") & geographicCoverage =="National, 100% of national program target"& indicatorName =="Percentage of people on ART among all people living with HIV at the end of the reporting period" ) %>%select( country = name , iso3 = code , endDate , targetValueNumerator , indicatorName , performance ) %>%distinct()
Preprocess for plots
The Wright Map logic: countries are binned by performance, then stacked within each bin. Label size scales with targetValueNumerator — so a large label means a large national target. Bigger text = more people.
The histogram shows the distribution of performance across countries. The boxplot below anchors the spread. Together they answer three questions at once: Where do countries cluster? Who are the outliers? And how much does each country’s result actually weigh?