library(tidyverse)
library(here)
library(ggthemes)
library(gt)
library(dplyr)
library(ggplot2)
library(patchwork)
library(rnaturalearth)
library(rnaturalearthdata)
library(sf)
library(dplyr)National WASH transitions in low and lower-middle income countries: Nepal’s coverage-quality paradox (2014-2024)
A comparative analysis of drinking water and sanitation coverage and quality across 77 countries with focus on Nepal
Introduction
Availability of and access to safe water, sanitation, and hygiene (WASH) services is fundamental to public health, human dignity, and sustainable development. The Sustainable Development Goal (SDG) 6 targets not only universal access to basic WASH services but emphasises quality service through “safely managed” standards. These standards require water to be accessible on premises, available when needed, and free from contamination, and sanitation systems to safely dispose of or treat human waste (WHO/UNICEF Joint Monitoring Programme (JMP), 2025). Between 2014 and 2024, many low and lower-middle income countries expanded basic WASH coverage. A critical gap, however, persists between basic service provision and safely managed (SM) services, which is termed the “coverage-quality gap” in this report. This gap represents access to improved infrastructure but with relatively low quality and safety mechanisms.
Nepal presents a particularly interesting case of divergent WASH trajectories during a period of remarkable disruption and transformation. Between 2014 and 2024, the country experienced multiple major upheavals that shaped development priorities and institutional capacity. In April 2015, a devastating 7.8 magnitude earthquake killed approximately 9,000 people and destroyed critical infrastructure, including water and sanitation (Uprety et al., 2017). The constitutional restructuring of 2015 soon followed, which created seven new provinces that fundamentally re-organised governance and service delivery responsibilities. The 2019-2021 COVID-19 pandemic further disrupted priorities, diverting resources and attention toward emergency health response.
Despite these challenges, however, Nepal achieved notable WASH coverage expansion (Shrestha et al., 2023). Understanding the patterns and trends of WASH coverage expansion and gaps in quality can offer insights into how policy prioritisation, institutional reforms, and sector-specific approaches affect quality outcomes even during adverse and challenging times.
As such, this analysis examines national WASH transitions across low and lower-middle income countries to compare Nepal’s status against its peers. In addition, Nepal’s trajectory as well as sub-national patterns are further investigated to understand how coverage expansion and quality improvement can diverge across different services, economic backgrounds, and geographic contexts.
Methods
Data sources
This analysis uses data from the WHO/UNICEF Joint Monitoring Programme (JMP) for WASH, which provides internationally comparable estimates of WASH coverage based on nationally representative household surveys (WHO/UNICEF Joint Monitoring Programme (JMP), 2025). The dataset was used to analyse national-level coverage estimates for 77 low and lower-middle income countries Figure 1 for the period 2014-2024, encompassing “basic” and “safely managed” service levels for drinking water and sanitation.
# Read raw data
gdp_raw_data <- read_csv(here::here("data/raw/global_latest_gdp_per_cap.csv"),
col_names = TRUE, skip = 4)
gdp_metadata <- read_csv(here::here("data/raw/global_latest_gdp_per_cap_metadata.csv"))
l_lm_washdata <- read_csv(here::here("data/raw/washdata-low-lowermiddle.csv"))
nepal_raw_data <- read_csv(here::here("data/raw/washdata-nepal.csv"))
# Process GDP data
gdp_l_lm <- gdp_raw_data |>
left_join(gdp_metadata, by = 'Country Code') |>
filter(IncomeGroup %in% c("Low income", "Lower middle income"))
gdp_latest <- gdp_l_lm |>
pivot_longer(
cols = `1960`:`2024`,
names_to = "year",
values_to = "gdp_per_capita"
) |>
filter(!is.na(gdp_per_capita)) |>
group_by(`Country Code`, `Country Name`, IncomeGroup) |>
slice_max(order_by = as.numeric(year), n = 1) |>
ungroup() |>
select(`Country Code`, `Country Name`, IncomeGroup, latest_year = year, gdp_per_capita) |>
rename(
country_code = `Country Code`,
country_name = `Country Name`,
income_group = IncomeGroup
)
# Create wide WASH table from long for selected columns (years 2014 and 2024)
l_lm_wash_binary <- l_lm_washdata |>
mutate(
service_level_binary = case_when(
`Service level` == "Safely managed service" ~ "Safely managed",
TRUE ~ "Basic or below"
)
) |>
rename(
iso3 = ISO3,
country = Country,
residence_type = `Residence Type`,
service_type = `Service Type`,
year = Year,
coverage = Coverage,
population = Population,
service_level = `Service level`
)
# Create national-level aggregated data
national_wash_aggregated <- l_lm_wash_binary |>
filter(residence_type == "total") |>
filter(year %in% c(2014, 2024)) |>
group_by(country, iso3, service_type, year, service_level, population) |>
summarise(coverage = sum(coverage, na.rm = TRUE), .groups = 'drop')
# Create clean wide format
national_clean <- national_wash_aggregated |>
filter(service_type %in% c("Drinking water", "Sanitation")) |>
select(country, iso3, service_type, year, service_level, coverage) |>
pivot_wider(
names_from = service_level,
values_from = coverage,
values_fill = 0
) |>
mutate(
at_least_basic = `Basic service` + `Safely managed service`,
safety_gap = `Basic service`
) |>
rename(
basic_service = `Basic service`,
safely_managed_service = `Safely managed service`,
limited_service = `Limited service`,
unimproved = Unimproved,
surface_water = `Surface water`,
open_defecation = `Open defecation`
)
# Calculate deltas
national_with_deltas <- national_clean |>
group_by(country, iso3, service_type) |>
arrange(year) |>
mutate(
delta_basic = basic_service - first(basic_service),
delta_sm = safely_managed_service - first(safely_managed_service),
delta_gap = safety_gap - first(safety_gap)
) |>
ungroup()
# Get 2024 data with GDP
national_2024_final <- national_with_deltas |>
filter(year == 2024) |>
left_join(
gdp_latest |> select(country_code, income_group, gdp_per_capita),
by = c("iso3" = "country_code")
)
# Process Nepal data
nepal_wide <- nepal_raw_data |>
pivot_wider(
id_cols = c(Country, ISO3, `Residence Type`, `Service Type`, Year),
names_from = `Service level`,
values_from = Coverage,
values_fill = 0
) |>
rename(
country = Country,
iso3 = ISO3,
residence_type = `Residence Type`,
service_type = `Service Type`,
year = Year,
basic_service = `Basic service`,
safely_managed_service = `Safely managed service`,
limited_service = `Limited service`,
unimproved = Unimproved,
surface_water = `Surface water`,
open_defecation = `Open defecation`,
)
# Create directory
dir.create(here::here("data/processed"), showWarnings = FALSE, recursive = TRUE)
# Save all processed datasets
write_csv(
gdp_latest,
here::here("data/processed/country_context.csv")
)
write_csv(
national_with_deltas,
here::here("data/processed/national_wash_2014_2024.csv")
)
write_csv(
national_2024_final,
here::here("data/processed/national_wash_2024.csv")
)
write_csv(
nepal_wide,
here::here("data/processed/nepal_wash_detailed.csv")
)# Get world map
world <- ne_countries(scale = "medium", returnclass = "sf")
# Get income groups from data
country_income <- gdp_latest |>
distinct(country_code, income_group)
# Join
world_map <- world |>
left_join(country_income, by = c("iso_a3" = "country_code")) |>
mutate(
income_category = case_when(
iso_a3 == "NPL" ~ "Nepal",
!is.na(income_group) & income_group == "Low income" ~ "Low income",
!is.na(income_group) & income_group == "Lower middle income" ~ "Lower middle income",
TRUE ~ "Not in study"
),
income_category = factor(income_category,
levels = c("Nepal", "Low income", "Lower middle income", "Not in study"))
)
# Map
ggplot(world_map) +
geom_sf(aes(fill = income_category), color = "white", size = 0.1) +
scale_fill_manual(
values = c(
"Nepal" = "#e34a33",
"Low income" = "#fdcc8a",
"Lower middle income" = "#2b8cbe",
"Not considered in this study" = "#ecf0f1"
),
name = ""
) +
theme_minimal() +
theme(
legend.name = "Income category",
legend.position = "right",
panel.grid = element_blank(),
axis.text = element_blank(),
axis.title = element_blank()
)
For Nepal, additional data was obtained by disaggregating data by residence type (national, urban, and rural) to examine sub-national variation in WASH transitions.
Economic data, in particularly, Gross Domestic Product (GDP) per capita (current US$) and World Bank income classification, were obtained from the World Bank World Development Indicators database (World Bank, 2024) and merged with WASH coverage data using ISO3 country codes.
Key Metrics
Table 1 provides the complete JMP definitions for “basic” and “safely managed” service levels (WHO/UNICEF Joint Monitoring Programme (JMP), 2025). The key distinction is that basic service represents improved infrastructure meeting minimum access standards, while safely managed service requires additional quality assurance: on-premises access and contamination-free water for drinking water services, and safe excreta disposal or treatment for sanitation.
# Create definitions table
jmp_definitions <- tibble::tribble(
~Service, ~`Basic service`, ~`Safely managed service`,
"Drinking water",
"Drinking water from an improved source, provided collection time is not more than 30 minutes for a round trip, including queuing",
"Drinking water from an improved water source that is located on premises, available when needed and free from faecal and priority chemical contamination",
"Sanitation",
"Use of improved facilities that are not shared with other households",
"Use of improved facilities that are not shared with other households and where excreta are safely disposed of in situ or transported and treated offsite"
)
jmp_definitions |>
gt() |>
cols_label(
Service = "Service Type",
`Basic service` = "Basic Service",
`Safely managed service` = "Safely Managed Service"
) |>
tab_style(
style = cell_text(size = px(11)),
locations = cells_body()
) |>
cols_width(
Service ~ px(120),
`Basic service` ~ px(250),
`Safely managed service` ~ px(250)
) |>
tab_options(
table.font.size = px(11),
data_row.padding = px(8)
) |> tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels(everything())
)| Service Type | Basic Service | Safely Managed Service |
|---|---|---|
| Drinking water | Drinking water from an improved source, provided collection time is not more than 30 minutes for a round trip, including queuing | Drinking water from an improved water source that is located on premises, available when needed and free from faecal and priority chemical contamination |
| Sanitation | Use of improved facilities that are not shared with other households | Use of improved facilities that are not shared with other households and where excreta are safely disposed of in situ or transported and treated offsite |
Changes in coverage between 2014 and 2024 were computed to assess trajectories over the 10-year period. Only the drinking water and sanitation services were considered, as hygiene lacks a “safely managed” tier in JMP methodology.
Results
Economic development and service quality
Figure 2 shows the relationship between GDP per capita (latest available value) and safely managed WASH coverage at the national level in 2024 for the 77 countries. Both drinking water and sanitation demonstrate positive associations with economic development, with regression lines indicating that wealthier countries achieve substantially higher safely managed coverage. However, the 95% confidence intervals reveal considerable variation around these trends, with some countries achieving high coverage despite lower GDP while others under-perform relative to their economic level.
Nepal (indicated by orange triangles) falls noticeably below the trendline for drinking water, with safely managed coverage approximately 20 percentage points (pp) lower than predicted by its GDP level. For sanitation, Nepal’s position aligns more closely with or slightly above the regression line.
# create wide wash table from long for selected columns (for e.g. years 2014 and 2024)
national_wash_data_wide <- l_lm_wash_binary |>
filter(residence_type == "total") |>
filter(year %in% c(2014, 2024)) |>
pivot_wider(
names_from = service_level,
values_from = coverage
) |>
rename(
basic_service = `Basic service`,
limited_service = `Limited service`,
no_handwashing_facility = `No handwashing facility`,
open_defecation = `Open defecation`,
surface_water = `Surface water`,
safely_managed_service = `Safely managed service`,
unimproved = Unimproved,
at_least_basic = `At least basic`
)# Plot gdp vs safely managed service, separated by WA, S and H.
plot_data <- national_wash_data_wide |>
filter(year == 2024) |> # Use most recent year
left_join(gdp_latest, by = c("iso3" = "country_code")) |>
# Get safely managed percentages for each service
filter(service_type %in% c("Drinking water", "Sanitation")) |>
select(country, service_type, safely_managed_service, gdp_per_capita, income_group, population)
# Prepare data for plotting
# Separate layers for other countries and Nepal
ggplot(plot_data, aes(x = gdp_per_capita, y = safely_managed_service)) +
# Other countries with income group colors
geom_point(data = filter(plot_data, country != "Nepal"),
aes(color = income_group), size = 3, alpha = 0.7, shape = 16) +
# Nepal as orange triangle
geom_point(data = filter(plot_data, country == "Nepal"),
color = "#e34a33", size = 3, shape = 17) +
geom_smooth(method = "lm", se = TRUE, color = "darkblue",
linetype = "dashed", formula = y ~ x) +
facet_wrap(~ service_type, nrow = 1) +
scale_x_log10(labels = scales::label_comma()) +
scale_y_continuous(
limits = c(0, 100)
) +
scale_color_manual(
values = c("Low income" = "#fdcc8a", "Lower middle income" = "#31a354"),
name = "Income group",
na.translate = FALSE
) +
labs(
x = "GDP per capita [US$] (log scale)",
y = "Safely managed coverage \n [percentage points (pp)]",
) +
theme_minimal() +
theme(
# Axis lines
axis.line = element_line(color = "black", linewidth = 0.5),
# Font sizes
axis.title = element_text(size = 14), # Axis titles
axis.text = element_text(size = 12), # Axis tick labels
legend.title = element_text(size = 13), # Legend title
legend.text = element_text(size = 12), # Legend labels
strip.text = element_text(face = "bold", size = 13), # Facet labels
# Other
legend.position = "right",
panel.grid.minor = element_blank()
)
Coverage changes across countries (2014-2024)
Figure 3 illustrates the distribution of changes in national drinking water coverage across the 77 countries between 2014 and 2024. For basic service, most countries achieved modest improvements with a median change of approximately 2 pp, though variability was substantial (interquartile range approximately -2 to +6 pp). Nepal’s increase of approximately 14 pp in basic coverage positions it well above the median, indicating strong infrastructure expansion.
The pattern for safely managed water services, however, reveals a critical paradox: while most countries achieved small improvements (median approximately 4-5 pp), Nepal experienced a decline of nearly 10 pp, making it a clear outlier among the 77 countries analysed. The simultaneous expansion of basic infrastructure and contraction of quality-assured service indicates a systemic challenge in maintaining water quality standards during rapid coverage expansion.
national_dw_metrics <- national_wash_aggregated |>
filter(service_type == "Drinking water",
service_level %in% c("Basic service", "Safely managed service"))
# Get Basic service for 2014 and 2024
dw_basic_data <- national_dw_metrics |>
filter(service_level == "Basic service") |>
select(country, iso3, year, coverage) |>
pivot_wider(
names_from = year,
values_from = coverage,
names_prefix = "basic_"
) |>
mutate(is_nepal = ifelse(country == "Nepal", "Nepal", "Other"))
# Get Safely managed for 2014 and 2024
dw_sm_data <- national_dw_metrics |>
filter(service_level == "Safely managed service") |>
select(country, iso3, year, coverage) |>
pivot_wider(
names_from = year,
values_from = coverage,
names_prefix = "sm_"
) |>
mutate(is_nepal = ifelse(country == "Nepal", "Nepal", "Other"))# Calculate changes
dw_change_data <- bind_rows(
dw_basic_data |>
mutate(
service = "Basic Service",
change = basic_2024 - basic_2014
) |>
select(country, iso3, service, change, is_nepal),
dw_sm_data |>
mutate(
service = "Safely Managed Service",
change = sm_2024 - sm_2014
) |>
select(country, iso3, service, change, is_nepal)
)
ggplot(dw_change_data, aes(x = service, y = change)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey") +
geom_boxplot(outlier.shape = NA, alpha = 0.3, fill = "#3498db") +
geom_jitter(aes(color = is_nepal, shape = is_nepal),
width = 0.2, size = 3, alpha = 0.7) +
scale_color_manual(
values = c("Nepal" = "#e34a33", "Other" = "#3498db"),
name = ""
) +
scale_shape_manual(
values = c("Nepal" = 17, "Other" = 16),
name = ""
) +
labs(
x = "",
y = "Change in coverage between 2014 and 2024 \n [percentage points (pp)]"
) +
theme_minimal() +
theme(
# Axis lines
axis.line = element_line(color = "black", linewidth = 0.5),
# Font sizes
axis.title = element_text(size = 14), # Axis titles
axis.text = element_text(size = 12), # Axis tick labels
legend.title = element_text(size = 13), # Legend title
legend.text = element_text(size = 12), # Legend labels
strip.text = element_text(face = "bold", size = 13), # Facet labels
# Other
legend.position = "right",
panel.grid.minor = element_blank()
)
On the other hand, sanitation coverage changes (Figure 4) contrast sharply with water patterns. Nepal demonstrated strong performance across both metrics: basic sanitation increased by nearly 13 pp and safely managed coverage by approximately 20 pp, placing Nepal as one of the top performers among its peers.
# Get Basic sanitation for 2014 and 2024
national_san_metrics <- national_wash_aggregated |>
filter(service_type == "Sanitation",
service_level %in% c("Basic service", "Safely managed service"))
# Get Basic service for 2014 and 2024
san_basic_data <- national_san_metrics |>
filter(service_level == "Basic service") |>
select(country, iso3, year, coverage) |>
pivot_wider(
names_from = year,
values_from = coverage,
names_prefix = "basic_"
) |>
mutate(is_nepal = ifelse(country == "Nepal", "Nepal", "Other"))
# Get Safely managed for 2014 and 2024
san_sm_data <- national_san_metrics |>
filter(service_level == "Safely managed service") |>
select(country, iso3, year, coverage) |>
pivot_wider(
names_from = year,
values_from = coverage,
names_prefix = "sm_"
) |>
mutate(is_nepal = ifelse(country == "Nepal", "Nepal", "Other"))# Calculate changes
san_change_data <- bind_rows(
san_basic_data |>
mutate(
service = "Basic Service",
change = basic_2024 - basic_2014
) |>
select(country, iso3, service, change, is_nepal),
san_sm_data |>
mutate(
service = "Safely Managed Service",
change = sm_2024 - sm_2014
) |>
select(country, iso3, service, change, is_nepal)
)
ggplot(san_change_data, aes(x = service, y = change)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey50") +
geom_boxplot(outlier.shape = NA, alpha = 0.3, fill = "#bcbddc") +
geom_jitter(aes(color = is_nepal, shape = is_nepal),
width = 0.2, size = 3, alpha = 0.7) +
scale_color_manual(
values = c("Nepal" = "#e34a33", "Other" = "#bcbddc"),
name = ""
) +
scale_shape_manual(
values = c("Nepal" = 17, "Other" = 16),
name = ""
) +
labs(
x = "",
y = "Change in coverage between 2014 and 2024 \n [percentage points (pp)]"
) +
theme_minimal() +
theme(
# Axis lines
axis.line = element_line(color = "black", linewidth = 0.5),
# Font sizes
axis.title = element_text(size = 14), # Axis titles
axis.text = element_text(size = 12), # Axis tick labels
legend.title = element_text(size = 13), # Legend title
legend.text = element_text(size = 12), # Legend labels
strip.text = element_text(face = "bold", size = 13), # Facet labels
# Other
legend.position = "right",
panel.grid.minor = element_blank()
)
Table 2 identifies the countries with the largest safety gaps, revealing the extent of the coverage-quality paradox across low and lower-middle income countries. For drinking water, Nepal ranks first with a safety gap of 77.2 pp, meaning that 77.2% of the national population has access to improved water infrastructure but lacks safely managed service.
The top 10 countries with largest water safety gaps share common patterns: most are lower-middle income countries (Nepal, Senegal, Lao PDR, Cambodia) that achieved substantial infrastructure expansion but lack the treatment systems, monitoring systems, or institutional capacity to ensure water quality. Island nations (Vanuatu, Kiribati) face particular challenges, possibly due to limited freshwater resources and saltwater intrusion, while countries emerging from conflict or instability (Sierra Leone, Benin) may be recovering from infrastructure damage or institutional weakness affecting quality assurance.
For sanitation, the pattern differs substantially. Morocco leads with a 79.4 pp gap, followed by Lebanon (73.5 pp) and Samoa (53.0 pp). Nepal ranks 7th for sanitation safety gaps at 32.6 pp, which is markedly lower than its water gap in absolute terms.
The gap percentages reveal an additional insight: in most countries, the majority of people with basic infrastructure lack safely managed service. In other words, expanding access without quality assurance is the dominant pattern globally, not unique to Nepal. However, Nepal’s combination of highest absolute gap (77.2 pp) positions it as the most extreme case of the coverage-quality paradox among the 77 countries.
# Calculate deltas
national_with_deltas <- national_clean |>
group_by(country, iso3, service_type) |>
arrange(year) |>
mutate(
delta_basic = basic_service - first(basic_service),
delta_sm = safely_managed_service - first(safely_managed_service),
delta_gap = safety_gap - first(safety_gap)
) |>
ungroup()
# Get 2024 only
national_2024 <- national_with_deltas |>
filter(year == 2024)
# Join with GDP and income
national_2024_final <- national_2024 |>
left_join(
gdp_latest |> select(country_code, income_group, gdp_per_capita),
by = c("iso3" = "country_code")
)gap_data <- national_2024_final |>
mutate(
gap_percent = (safety_gap / at_least_basic) * 100
)
# Get drinking water top 10
water_top <- gap_data |>
filter(service_type == "Drinking water") |>
slice_max(order_by = safety_gap, n = 10) |>
mutate(rank = row_number()) |>
select(rank, country, safety_gap) |>
rename(
water_country = country,
water_gap = safety_gap
)
# Get sanitation top 10
san_top <- gap_data |>
filter(service_type == "Sanitation") |>
slice_max(order_by = safety_gap, n = 10) |>
mutate(rank = row_number()) |>
select(rank, country, safety_gap) |>
rename(
san_country = country,
san_gap = safety_gap
)
# Combine side by side
full_join(water_top, san_top, by = "rank") |>
gt() |>
tab_spanner(
label = "Drinking Water",
columns = c(water_country, water_gap)
) |>
tab_spanner(
label = "Sanitation",
columns = c(san_country, san_gap)
) |>
fmt_number(columns = c(water_gap, san_gap), decimals = 1) |>
cols_label(
rank = "Rank",
water_country = "Country",
water_gap = "Gap [pp]",
san_country = "Country",
san_gap = "Gap [pp]"
) |>
tab_style(
style = cell_fill(color = "#fff3cd"),
locations = cells_body(rows = water_country == "Nepal")
) |>
tab_style(
style = cell_fill(color = "#fff3cd"),
locations = cells_body(rows = san_country == "Nepal")
) |> tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels(everything())
) |> tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_spanners(everything())
)| Rank |
Drinking Water
|
Sanitation
|
||
|---|---|---|---|---|
| Country | Gap [pp] | Country | Gap [pp] | |
| 1 | Nepal | 77.2 | Morocco | 79.4 |
| 2 | Vanuatu | 67.9 | Lebanon | 73.5 |
| 3 | Kiribati | 64.0 | Samoa | 53.0 |
| 4 | Senegal | 60.9 | Viet Nam | 50.1 |
| 5 | Lao People's Democratic Republic | 60.7 | Egypt | 39.8 |
| 6 | Sierra Leone | 57.2 | Bhutan | 37.4 |
| 7 | Malawi | 54.9 | Nepal | 32.6 |
| 8 | Cambodia | 52.9 | Senegal | 32.4 |
| 9 | Nigeria | 52.3 | Cambodia | 31.7 |
| 10 | Benin | 51.9 | Bangladesh | 30.3 |
Nepal’s sub-national WASH patterns
Figure 5 reveals detailed temporal and geographic patterns in Nepal’s WASH transitions between 2014 and 2024. For drinking water, all three residence types (national, urban, and rural) showed declining safely managed coverage. Urban areas experienced the steepest decline in safely managed water (from 34.9% to 23.6%; a loss of 11.3 pp), while rural areas declined from 26.2% to 15.8% (10.4 pp) and the national average from 27.0% to 16.5% (10.5 pp).
On the other hand, basic water coverage increased across all residence types, rising from 62.1% to 77.2% nationally (15.1 pp). This created a widening safety gap between populations with improved infrastructure and those with quality-assured services. By 2024, urban areas had 68.7% basic water coverage but only 23.6% safely managed, representing a 45 pp gap, meaning two-thirds of those with improved water infrastructure lack safely managed service.
Sanitation demonstrates the opposite pattern across all residence types. Safely managed coverage improved steadily, with particularly strong gains in rural areas (from 32.3% in 2014 to 53.4% in 2024, a 21.1 pp increase) and urban areas (from 30.0% to 46.0%, 16.0 pp). National sanitation coverage rose from 31.7% to 52.7% (21.0 pp). Basic sanitation coverage increased modestly while safely managed coverage grew faster, resulting in a narrow safety gap. By 2024, safely managed sanitation coverage exceeded basic-only coverage across all residence types.
# Filter for Nepal across all residence types
# Create a helper function
get_nepal_data <- function(service, residence) {
nepal_wide |>
filter(service_type == service, residence_type == residence) |>
select(year, basic_service, safely_managed_service)
}
# Use the function to extract relevant data:
nepal_dw_national <- get_nepal_data("Drinking water", "total")
nepal_dw_urban <- get_nepal_data("Drinking water", "urban")
nepal_dw_rural <- get_nepal_data("Drinking water", "rural")
nepal_san_national <- get_nepal_data("Sanitation", "total")
nepal_san_urban <- get_nepal_data("Sanitation", "urban")
nepal_san_rural <- get_nepal_data("Sanitation", "rural")nepal_timeseries <- nepal_wide |>
filter(service_type %in% c("Drinking water", "Sanitation")) |>
select(residence_type, service_type, year, basic_service, safely_managed_service) |>
pivot_longer(
cols = c(basic_service, safely_managed_service),
names_to = "service_level",
values_to = "coverage"
) |>
mutate(
residence_type = factor(residence_type,
levels = c("rural", "urban", "total"),
labels = c("Rural", "Urban", "National")),
service_level = factor(service_level,
levels = c("basic_service", "safely_managed_service"),
labels = c("Basic", "Safely Managed"))
)
ggplot(nepal_timeseries, aes(x = year, y = coverage,
color = residence_type,
group = residence_type)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2.5) +
facet_grid(service_level ~ service_type) +
scale_color_manual(
values = c("Rural" = "#27ae60", "National" = "#2c3e50", "Urban" = "#3498db"),
name = ""
) +
scale_x_continuous(breaks = seq(2014, 2024, 2)) +
scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20)) +
labs(
x = "Year",
y = "Coverage [%]"
) +
theme_minimal() +
theme(
# Add borders around each panel
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
# Axis lines (these only show on outer edges with facets)
axis.line = element_line(color = "black", linewidth = 0.5),
# Font sizes
axis.title = element_text(size = 14),
axis.text = element_text(size = 11),
legend.title = element_text(size = 13),
legend.text = element_text(size = 12),
strip.text = element_text(size = 12),
# Other
legend.position = "right",
panel.grid.minor = element_blank(),
panel.spacing = unit(1, "lines")
)
Discussion
Across countries, safely managed coverage for both drinking water and sanitation increases with GDP per capita, but the income–service relationship is steeper and more consistent for sanitation than for drinking water.
In the sanitation panel, for instance, low- and lower-middle-income countries clustered tightly around the regression line, which indicates that modest economic growth translates efficiently into sanitation gains, driven largely by the improvements in household-scale technologies (latrines, septic tanks). For Nepal, the sanitation trajectory aligns with this pattern, lying close to the expected income-based trend. It also benefitted from strong institutional and policy-level interventions, for example, to dissuade open defecation (OD). Behavioural interventions, household-level ownership, and lower requirements for ongoing service reliability may have further supported sustained improvements.
In contrast, the drinking water exhibits substantially greater dispersion around the income gradient, particularly among lower-middle-income countries. At comparable GDP levels, countries display widely varying safely managed water coverage, reflecting differences in service management models, system reliability, and water quality control, rather than income alone. In Nepal, it is likely that safely managed drinking water declined primarily because JMP definitions increasingly emphasised service continuity, on-premises access, and verified water quality (particularly microbiological safety and arsenic compliance). While physical access to improved sources expanded, many rural gravity-fed schemes and peri-urban supply systems may have suffered from inadequate operation and maintenance, seasonal water scarcity, post-earthquake infrastructure degradation, and limited source protection.
Conclusions
Considering that availability of and access to safe WASH services is essential to foster human well-being and sustainable development, our analysis sought to assess progress in the drinking water and sanitation services in low and lower middle income countries between 2014 and 2024, with emphasis in Nepal. The analysis led to the following conclusions:
Income–service coupling differs by sector: Safely managed sanitation shows a stronger and more consistent positive relationship with GDP per capita than drinking water, indicating that economic growth translates more predictably into sanitation gains, whereas drinking water outcomes are more weakly and variably linked to income.
Sanitation gains are driven by scalable, household-level interventions: Low- and lower-middle-income countries, including Nepal, cluster closely around the income-based trend for sanitation, which likely reflects the effectiveness of household-scale technologies, behavioural change programmes, and policy-driven initiatives such as those to dissuade open-defecation.
Drinking water performance depends on system durability and governance: The wide dispersion in safely managed drinking water at comparable income levels highlights the dominant role of service management, operation and maintenance, and water quality control. In Nepal, stricter JMP criteria combined with ageing, decentralised supply systems and environmental stresses (for example, earthquake and COVID-19) likely contributed to declining safely managed coverage despite expansion in basic services.