Reducing malaria deaths is a primary concern for NMPs, hence, identifying areas with high malaria mortality is essential in SNT to prioritize interventions. Ideally, there would be subnational data on malaria mortality from routine surveillance of sufficient quality to inform decision-making. However, despite notable improvements over the past years, vital registration systems remain insufficient to provide the required level or data quality, and many malaria deaths occur outside of the health sector. SNT teams need to explore alternate sources of mortality estimates.
Because it is difficult to get high-quality data or estimates on malaria-attributable deaths, the SNT team may consider all-cause under-5 mortality rate (U5MR) as a proxy for malaria mortality. In areas of high transmission, malaria is a primary cause of death among children under 5 years of age. This page focuses on how to extract U5MR estimates from national household survey data.
Objectives
Access household survey data on U5MR at the adm1 level
Inspect, visualize, and validate U5MR estimates
Compile and save clean, subnational summaries of U5MR estimates for integration into SNT workflows
Data Needs
Estimating Mortality Rates from Household Surveys
The Demographic and Health Survey (DHS) and Malaria Indicator Survey (MIS), while similarly structured, serve slightly different purposes. Because DHS has a primary objective of measuring demographic indicators, it is possible to use DHS data to generate a direct estimate of U5MR. While MIS does not contain the same level of detail in its demographic questionnaire, it is still possible to generate an indirect estimate of U5MR using MIS results. If the country’s most recent survey was an MIS and post-dates the most recent DHS by several years, the SNT team may be interested in considering the more recent indirect U5MR inferred from MIS in addition to a less recent direct U5MR from DHS.
Estimating U5MR from household surveys uses data from the women’s questionnaire. In the direct method, the mortality rate is calculated from the list of children birthed by each woman and, for each child, their date of birth and status (living or deceased) at the time of the survey. The status at the time of survey is used to infer the age at death and ultimately the U5MR.
In the indirect (Brass) method, the number of children birthed by each woman and the number of children surviving at the time of the survey are used. The women are then stratified by age to infer the U5MR by year.
Limitations to Keep in Mind When Interpreting U5MR Survey Data
When using U5MR from DHS, it is important to understand what these estimates represent, and just as importantly, what they don’t. Although U5MR figures are often used to infer recent patterns in child survival, they’re inherently retrospective. The 5-year mortality estimates demonstrated on this page are based on births and deaths that occurred in the years leading up to the survey, not during the survey year itself. As a result, any major programmatic shifts, shocks, or emergencies that occurred shortly before or after the survey won’t be reflected in the data.
The quality of these estimates also depends on how accurately women report the birth histories of their children. Underreporting or misremembering dates (especially around children who died very young) is not uncommon and can subtly bias the rates downward. In some areas, cultural factors or the sensitive nature of these questions may influence whether a woman feels comfortable sharing the full birth history, especially for deceased children.
District-level estimates can also be imprecise due to small sample sizes. In most countries, household surveys are not powered to produce mortality estimates at adm-2. Confidence intervals can be wide, and results in less-populated or harder-to-reach districts should be viewed as indicative, not definitive. Even when statistical techniques are applied, they can only partially account for the underlying uncertainty.
It’s also worth remembering that U5MR reflects deaths from all causes. While malaria is often a leading contributor in high-burden areas, U5MR does not distinguish between malaria-related deaths and those due to malnutrition, pneumonia, birth complications, or other causes. For this reason, U5MR is most useful when interpreted in combination with additional contextual indicators: malaria prevalence, incidence, seasonality, and access to health services.
These limitations should be seen as signals to proceed with care. U5MR is a useful metric in SNT, especially when direct cause-of-death data is limited, but it needs to be triangulated and interpreted in context.
Step-by-Step
In this section, we apply Option 1 (pull standard indicators directly from DHS) and Option 2 (build from raw data via API), as discussed and presented in the DHS Data Overview and Preparation page. While Option 1 is the quickest approach, it has limitations. Only predefined indicators are available, with no control over aggregation or customization. To address this, we also offer Option 2, which provides full flexibility by using the rdhs package to access microdata and construct indicators directly.
To skip the step-by-step explanation, jump to the full code at the end of this page.
Option 1 (DHS Indicators Download)
This approach uses pre-calculated U5MR estimates from the DHS API at subnational levels. It allows for quick summaries and broad comparisons across regions but offers limited flexibility. Users cannot modify the disaggregation variables or adjust the underlying mortality calculation logic.
Step 1.1: Install or load required packages
In this step, we download subnational indicator data using the specified DHS Indicator IDs. We then join these indicators with our shapefile of interest (either admin-1 or admin-2), which determines the geographic level of analysis. This merged dataset forms the basis for plotting results and conducting subnational analysis.
We start by loading the required packages and setting up for the full section. For Option 1, we use two custom functions: check_dhs_indicators() and download_dhs_indicators().
# install or load required packagespacman::p_load( here, # for handling relative file paths haven, # for reading DHS .dta files and labelled data dplyr, # for data wrangling stringr, # for filtering U5MR rows using regex tibble, # for rownames_to_column ggplot2, # for visualizing U5MR maps sf, # for working with shapefiles rio, # for saving outputs in .csv and .rds formats DHS.rates # for calculating under-five mortality rates)#' Check DHS Indicator List from API#'#' @param countryIds DHS country code(s), e.g., "EG"#' @param indicatorIds Specific indicator ID(s)#' @param surveyIds Survey ID(s)#' @param surveyYear Exact year#' @param surveyYearStart Start of year range#' @param surveyYearEnd End of year range#' @param surveyType DHS survey type (e.g., "DHS", "MIS")#' @param surveyCharacteristicIds Filter by survey characteristic ID#' @param tagIds Filter by tag ID#' @param returnFields Fields to return#' (default: IndicatorId, Label, Definition)#' @param perPage Max results per page (default = 500)#' @param page Specific page to return (default = 1)#' @param f Format (default = "json")#'#' @return A data.frame of indicators#' @exportcheck_dhs_indicators <-function(countryIds =NULL,indicatorIds =NULL,surveyIds =NULL,surveyYear =NULL,surveyYearStart =NULL,surveyYearEnd =NULL,surveyType =NULL,surveyCharacteristicIds =NULL,tagIds =NULL,returnFields =c("IndicatorId", "Label", "Definition", "MeasurementType" ),perPage =NULL,page =NULL,f ="json") {# base URL base_url <-"https://api.dhsprogram.com/rest/dhs/indicators?"# build query string params <-list(countryIds = countryIds,indicatorIds = indicatorIds,surveyIds = surveyIds,surveyYear = surveyYear,surveyYearStart = surveyYearStart,surveyYearEnd = surveyYearEnd,surveyType = surveyType,surveyCharacteristicIds = surveyCharacteristicIds,tagIds = tagIds,returnFields =paste(returnFields, collapse =","),perPage = perPage,page = page,f = f )# drop NULLs and encode query <-paste( purrr::compact(params) |> purrr::imap_chr(~paste0( .y, "=", URLencode(as.character(.x), reserved =TRUE) ) ),collapse ="&" )# full URL full_url <-paste0(base_url, query)# fetch with progress bar response <- httr::GET(full_url, httr::progress()) jsonlite::fromJSON(httr::content( response,as ="text",encoding ="UTF-8" ))$Data}#' Query DHS API Directly via URL Parameters#'#' Builds and queries DHS API for indicator data using URL-based access#' instead of rdhs package.#'#' @param countryIds Comma-separated DHS country code(s), e.g., "SL"#' @param indicatorIds Comma-separated DHS indicator ID(s),#' e.g., "CM_ECMR_C_U5M"#' @param surveyIds Optional comma-separated survey ID(s), e.g., "SL2016DHS"#' @param surveyYear Optional exact survey year, e.g., "2016"#' @param surveyYearStart Optional survey year range start#' @param surveyYearEnd Optional survey year range end#' @param breakdown One of: "national", "subnational", "background", "all"#' @param f Format to return (default is "json")#'#' @return A data.frame containing the `Data` portion of the API response.#' @exportdownload_dhs_indicators <-function( countryIds, indicatorIds,surveyIds =NULL,surveyYear =NULL,surveyYearStart =NULL,surveyYearEnd =NULL,breakdown ="subnational",f ="json") {# base URL base_url <-"https://api.dhsprogram.com/rest/dhs/data?"# assemble query string query <-paste0("breakdown=", breakdown,"&indicatorIds=", indicatorIds,"&countryIds=", countryIds,if (!is.null(surveyIds)) paste0("&surveyIds=", surveyIds),if (!is.null(surveyYear)) paste0("&surveyYear=", surveyYear),if (!is.null(surveyYearStart)) {paste0("&surveyYearStart=", surveyYearStart) },if (!is.null(surveyYearEnd)) {paste0("&surveyYearEnd=", surveyYearEnd) },"&lang=en&f=", f ) full_url <-paste0(base_url, query) cli::cli_alert_info("Downloading DHS data...") response <- httr::GET(full_url, httr::progress())if (httr::http_error(response)) {stop("API request failed: ", httr::status_code(response)) } content_raw <- httr::content(response, as ="text", encoding ="UTF-8") data <- jsonlite::fromJSON(content_raw)$Data cli::cli_alert_success("Download complete: {nrow(data)} records retrieved." )return(data)}
Step 1.2: Download the relevant indicators
As shown in Step 1.2 of Option 1 in the DHS Data Overview and Preparation page, we use the check_dhs_indicators() function to explore available indicators and ensure that we are pulling the correct DHS indicator IDs for the 2019 Sierra Leone DHS. In this section, we focus on the U5MR and its associated confidence intervals.
The relevant indicator IDs are:
CM_ECMR_C_U5M: Under-five mortality rate
CM_ECMR_C_U5L: Under-five mortality rate - CI lower bound (-2SE)
CM_ECMR_C_U5U: Under-five mortality rate - CI upper bound (+2SE)
# get mean under-five mortality rate at subnational levelu5mr_mean_dhs_ind <-download_dhs_indicators(countryIds ="SL",surveyYear =2019,indicatorIds ="CM_ECMR_C_U5M",breakdown ="subnational") |> dplyr::mutate(dhs_indicator_id ="CM_ECMR_C_U5M",indicator ="under-five mortality rate" ) |> dplyr::select( dhs_indicator_id, indicator,adm2_code = RegionId,mean_u5mr = Value )# get lower interval under-five mortality rate at subnational levelu5mr_low_dhs_ind <-download_dhs_indicators(countryIds ="SL",surveyYear =2019,indicatorIds ="CM_ECMR_C_U5L",breakdown ="subnational") |> dplyr::mutate(dhs_indicator_id ="CM_ECMR_C_U5L",indicator ="under-five mortality rate" ) |> dplyr::select(adm2_code = RegionId,low_u5mr = Value )# get upper interval under-five mortality rate at subnational levelu5mr_upp_dhs_ind <-download_dhs_indicators(countryIds ="SL",surveyYear =2019,indicatorIds ="CM_ECMR_C_U5U",breakdown ="subnational") |> dplyr::mutate(dhs_indicator_id ="CM_ECMR_C_U5U",indicator ="under-five mortality rate" ) |> dplyr::select(adm2_code = RegionId,upp_u5mr = Value )
To adapt the code:
Lines 2, 20, and 36: Replace SL with your country’s DHS code (e.g., NG for Nigeria, BF for Burkina Faso).
Lines 3, 21, 37: Optional. Set to a specific year if you want to restrict to one round, e.g., 2019.
Lines 4, 22, 38: Use the specific IndicatorId relevant to your analysis, e.g., CM_ECMR_C_U5M for U5MR mean, CM_ECMR_C_U5L for lower CI, CM_ECMR_C_U5U for upper CI.
Lines 5, 23, 39: Keep subnational to get region-level data; use national for country-wide aggregates.
Step 1.3: Join indicators with shapefile
After downloading the U5MR, we need to ensure they are correctly matched to the appropriate administrative units. To do this, we also download the country-specific shapefile for the 2019 Sierra Leone DHS directly from the DHS Spatial Data Repository. This shapefile corresponds to the survey year and administrative level of the extracted indicators and is required for data cleaning and visualization.
The indicators include results for both admin-1 and admin-2 levels. In this example, we focus on admin-2 and use the corresponding shapefile. After downloading both indicators, we combine them into a single dataset and join them to the shapefile using the region codes. This prepares a clean, subnational dataset for mapping and analysis.
If working at the admin-1 level, the same process applies. Simply download the admin-1 shapefile from the DHS Spatial Data Repository and join it using the appropriate region code via dplyr::inner_join(). This approach allows flexible use of either administrative level, depending on your analysis needs.
Lines 2–6: If working at the admin-1 level, load the corresponding adm1 shapefile instead, e.g., 1ai_adm1. In that case, do not rename or select adm2 columns. Keep only adm1 and the appropriate join code.
Lines 9–12: When using admin-1, you can skip any code referencing adm2 variables, e.g., adm2 = DHSREGEN or mutate(adm2 = ...). Focus on cleaning and renaming just the admin-1 fields.
Lines 20–22: All three U5MR estimates (mean and confidence intervals) contain results at both admin-1 and admin-2 levels. You do not need to filter them beforehand. Instead, your choice of shapefile and join key, e.g., adm2_code, will implicitly filter the indicator data to match only the relevant administrative level.
Line 25: Use inner_join() to retain only rows that match your shapefile’s geographic level. For admin-1, this would be by = "adm1_code"; for admin-2, use by = "adm2_code" as shown.
Step 1.4: Visualize subnational indicator data
Once the indicator data has been downloaded and joined with a shapefile, we can create a map to explore geographic variation in both indicators. In the example below, we categorize the indicator into 10% bins and visualize it using ggplot2.
After downloading and formatting the U5MR indicator data at the subnational level, we save it for use later on.
# define save directorysave_path <- here::here("1.6_health_systems/1.6a_dhs")# save final joined U5MR datario::export( sle_dhs_shp2 |> sf::st_drop_geometry(), here::here( save_path, "processed", "final_u5mr_sle_adm2.csv" ))# save final joined U5MR data with spatial datario::export( sle_dhs_shp2, here::here( save_path, "processed", "final_u5mr_sle_adm2.rds" )
Instead of calculating child mortality from scratch, the R package DHS.rates provides the function chmort for this purpose. The chmort function follows DHS conventions for childhood mortality, including age cutoffs, censoring rules, and time periods. It automatically incorporates survey weights, stratification, and clustering to produce representative estimates. The function also handles left- and right-censoring as well as multiple births—complexities that are difficult to implement otherwise.
# calculate five-year under-five mortality ratesmort_sl <- sle_dhs_kr |> DHS.rates::chmort(JK ="Yes", # For getting confidence intervalsStrata ="v022", # Strata variable for complex survey designCluster ="v021", # Cluster (PSU) identifierWeight ="v005", # Sampling weightDate_of_interview ="v008", # Date of interview (CMC format)Date_of_birth ="b3", # Child’s date of birth (CMC format)Class ="location"# Grouping variable (e.g., region, district) ) mort_sl
Output
The current function calculated Childhood Mortality Rates based on a reference period of 60 months
The reference period ended at the time of the interview, in 2019.58 OR May - Sep 2019
The average reference period is 2017.08
Line 2: Replace sle_dhs_kr with the relevant KR dataset for your country (e.g., the dataset loaded in Step 2.1).
Line 10: To disaggregate by a different variable (e.g., urban or b4 for sex), modify only the Class argument. Keep all other arguments unchanged.
If using standard DHS naming, no other changes should be needed.
Step 2.3: Prepare subnational U5MR for plotting
In this step, we extract the U5MR estimates produced in Step 2 and link them to their corresponding geographic units (admin-1 and admin-2). This prepares the data for mapping, summary statistics, or subnational comparisons. We focus only on the U5MR rows and discard other mortality metrics (e.g., neonatal or infant mortality).
# extract U5MR and join with location-level metadatamort_sl2 <- mort_sl |> tibble::rownames_to_column(var ="type") |># convert rownames to a column dplyr::filter(stringr::str_detect(type, "U5MR")) |># keep only U5MR rows dplyr::left_join(# attach admin names from original dataset dplyr::distinct(sle_dhs_kr, location, adm1, adm2),by =c("Class"="location") ) |># keep only the relevant columns dplyr::select( adm1, adm2,mean_u5mr = R,low_u5mr = LCI,upp_u5mr = UCI )mort_sl2
Output
adm1 adm2 mean_u5mr low_u5mr upp_u5mr
1 SOUTHERN PUJEHUN 91.46 88.43 94.48
2 NORTHERN TONKOLILI 105.06 70.71 139.40
3 NORTHERN FALABA 94.33 87.28 101.38
4 WESTERN WESTERN AREA URBAN 87.28 81.18 93.37
5 WESTERN WESTERN AREA RURAL 132.39 124.00 140.79
6 SOUTHERN MOYAMBA 105.79 103.35 108.23
7 EASTERN KONO 103.80 98.03 109.56
8 NORTH WESTERN KAMBIA 115.55 107.81 123.28
9 SOUTHERN BONTHE 82.48 76.42 88.54
10 EASTERN KAILAHUN 81.59 77.63 85.56
11 NORTHERN BOMBALI 103.89 96.33 111.46
12 EASTERN KENEMA 129.75 121.23 138.27
13 NORTH WESTERN KARENE 112.16 110.25 114.06
14 NORTHERN KOINADUGU 117.78 105.25 130.31
15 SOUTHERN BO 146.00 142.69 149.31
16 NORTH WESTERN PORT LOKO 149.00 149.00 149.00
To adapt the code:
Line 8: Replace sle_dhs_kr with the relevant KR dataset for your country (e.g., NGKR7AFL.rds for Nigeria).
Line 9: To disaggregate by a different variable (e.g., urban or b4 for sex), modify the Class argument in the left_join() statement. Keep all other arguments unchanged.
If using standard DHS naming, no other changes should be needed.
Example Interpretation: In Kailahun district in the Eastern region, the under-five mortality rate is estimated at 81.6 deaths per 1,000 live births. This estimate is statistically robust, with a 95% confidence interval ranging from 77.6 to 85.6 deaths, suggesting relatively lower childhood mortality in the district compared to many others in Sierra Leone. Note: DHS sampling is designed to be representative at the admin-1 level, hence admin-2 estimates carry more uncertainty than admin-1 estimates.
Modify only if relevant parameters were not created exactly as in earlier steps.
Validate with SNT Team
Once you’ve generated and saved the subnational U5MR estimates, review the results in consultation with the SNT team.
Do these mortality patterns align with known high-burden districts?
Are there areas where high U5MR suggests poor access to effective treatment or delays in seeking care?
Are there any recent events (insecurity, disasters, epidemics, etc.) to be aware of whose impact on mortality are not captured in these historical data?
This step is essential to ground-truth the analysis and contextualize the results.
After calculating and formatting the U5MR data at the subnational level, we save the cleaned dataset for use in later steps such as visualization, tabulation, or integration with other indicators.
# define save directorysave_path <- here::here("1.6_health_systems/1.6a_dhs")# save final joined U5MR datario::export( sle_dhs_shp2 |> sf::st_drop_geometry(), here::here( save_path, "processed", "final_u5mr_sle_adm2.csv" ))# save final joined U5MR data with spatial datario::export( sle_dhs_shp2, here::here( save_path, "processed", "final_u5mr_sle_adm2.rds" ))
Summary
In this section, we demonstrate how to extract subnational U5MR using both Option 1 and Option 2. Option 1 uses the DHS API to pull pre-calculated U5MR values and associated confidence intervals directly; this is fast and convenient for quick summaries or comparisons across districts. However, it is limited in flexibility: you cannot redefine the calculation logic or disaggregate by custom variables. Option 2 uses the raw DHS KR dataset and the DHS.rates::chmort() function to calculate U5MR. This allows for flexible choices of aggregation and stratification such as by sex or urbanicity and indicator assumptions. By combining both approaches, we ensure accessibility, reproducibility, and analytical depth.
Full code
Find the full code script for extracting under-five mortality rates from DHS data below.