Overview

Row

Registered deaths, 2024

206,415

Sex ratio at death

126

Registration completeness

44.8%

Row

The undercount, in one bar

National completeness trend, 2020-2024

Age & Sex

Row

Where the gap between men and women widens: sex ratio at death by age

Row

Same causes, different weight: leading causes of death by sex, 2024

Causes by Age

Row

What kills you depends on how old you are: leading causes by age band, 2024

Geography

Row

Where registration is most and least complete

Row

Full county table

---
title: "What Kills Kenya — Mortality by Sex, Age, and County"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: fill
    theme: flatly
    source_code: embed
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)

library(flexdashboard)
library(dplyr)
library(tidyr)
library(ggplot2)
library(plotly)
library(leaflet)
library(sf)
library(scales)
library(DT)

# raster/terra (pulled in by sf/leaflet) are known to silently mask
# common dplyr verbs -- pin these explicitly so the right version
# always wins, regardless of package load order.
filter <- dplyr::filter
select <- dplyr::select
recode <- dplyr::recode
rename <- dplyr::rename

source("data_prep.R")   # loads and tidies all data frames used below

# House style for the static ggplot layer before it's handed to ggplotly()
theme_story <- function() {
  theme_minimal(base_size = 12) +
    theme(
      panel.grid.minor = element_blank(),
      plot.title = element_text(face = "bold", size = 13),
      plot.subtitle = element_text(color = "grey40", size = 10)
    )
}

pal_sex <- c("Male" = "#2b6cb0", "Female" = "#c05621")
```

Overview
=====================================

Row {data-height=120}
-------------------------------------

### Registered deaths, 2024

```{r}
valueBox(comma(kenya_national_completeness$reg_total),
         caption = "Registered deaths (KNBS, 2024)",
         icon = "fa-file-text", color = "#2b6cb0")
```

### Sex ratio at death

```{r}
sex_ratio_national <- round(sum(age_sex_deaths$male) / sum(age_sex_deaths$female) * 100)

valueBox(sex_ratio_national,
         caption = "Male deaths per 100 female deaths",
         icon = "fa-venus-mars", color = "#c05621")
```

### Registration completeness

```{r}
valueBox(paste0(kenya_national_completeness$comp_total, "%"),
         caption = "Share of expected deaths actually registered",
         icon = "fa-exclamation-triangle", color = "#e53e3e")
```

Row {data-height=440}
-------------------------------------

### The undercount, in one bar

```{r}
gap_df <- kenya_national_completeness |>
  transmute(
    Registered = reg_total,
    Unregistered = exp_total - reg_total
  ) |>
  pivot_longer(everything(), names_to = "status", values_to = "n") |>
  mutate(status = factor(status, levels = c("Registered", "Unregistered")))

p <- ggplot(gap_df, aes(x = "2024", y = n, fill = status,
                         text = paste0(status, ": ", comma(n)))) +
  geom_col(width = 0.5) +
  scale_fill_manual(values = c("Registered" = "#2b6cb0", "Unregistered" = "#cbd5e0")) +
  scale_y_continuous(labels = comma) +
  coord_flip() +
  labs(title = "Fewer than half of Kenya's expected deaths get registered",
       subtitle = "460,585 deaths were expected in 2024; 206,417 were registered",
       x = NULL, y = "Deaths") +
  theme_story() +
  theme(legend.title = element_blank())

ggplotly(p, tooltip = "text") |> layout(hovermode = "closest")
```

### National completeness trend, 2020-2024

```{r}
trend_national <- county_completeness_trend |> filter(county == "Kenya")

p <- ggplot(trend_national, aes(x = year, y = completeness_pct,
                                 text = paste0(year, ": ", completeness_pct, "%"))) +
  geom_line(color = "#2b6cb0", linewidth = 1) +
  geom_point(color = "#2b6cb0", size = 2) +
  scale_y_continuous(limits = c(0, 60), labels = function(x) paste0(x, "%")) +
  labs(title = "Completeness is improving, unevenly",
       subtitle = "Death registration completeness, Kenya, 2020-2024",
       x = NULL, y = "Completeness (%)") +
  theme_story()

ggplotly(p, tooltip = "text")
```

Age & Sex
=====================================

Row {data-height=500}
-------------------------------------

### Where the gap between men and women widens: sex ratio at death by age

```{r}
p <- ggplot(age_sex_deaths, aes(x = age_group, y = sex_ratio, group = 1,
                                 text = paste0(age_group, ": ", sex_ratio,
                                               " male deaths per 100 female deaths"))) +
  geom_hline(yintercept = 100, linetype = "dashed", color = "grey60") +
  geom_line(color = "#c05621", linewidth = 1) +
  geom_point(color = "#c05621", size = 2) +
  labs(title = "More men die at almost every age",
       subtitle = "Sex ratio at death by age group, Kenya, 2024 (100 = parity)",
       x = "Age group", y = "Male deaths per 100 female deaths") +
  theme_story() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(p, tooltip = "text")
```

Row {data-height=500}
-------------------------------------

### Same causes, different weight: leading causes of death by sex, 2024

```{r fig.height=6}
dumbbell_df <- causes_dumbbell |>
  filter(!is.na(Male) | !is.na(Female)) |>
  mutate(cause = reorder(cause, gap))

p <- ggplot(dumbbell_df) +
  geom_segment(aes(x = cause, xend = cause, y = Female, yend = Male),
               color = "grey70", linewidth = 1) +
  geom_point(aes(x = cause, y = Male, text = paste0(cause, " (male): ", comma(Male))),
             color = pal_sex["Male"], size = 3) +
  geom_point(aes(x = cause, y = Female, text = paste0(cause, " (female): ", comma(Female))),
             color = pal_sex["Female"], size = 3) +
  coord_flip() +
  scale_y_continuous(labels = comma) +
  labs(title = "Leading causes of registered health-facility deaths, by sex",
       subtitle = "Blue = male deaths, orange = female deaths. Sorted by size of the gap.",
       x = NULL, y = "Registered deaths, 2024") +
  theme_story()

ggplotly(p, tooltip = "text")
```

Causes by Age
=====================================

Row {data-height=650}
-------------------------------------

### What kills you depends on how old you are: leading causes by age band, 2024

```{r fig.height=8}
top5_by_age <- causes_by_age_band |>
  group_by(age_band) |>
  slice_min(rank, n = 5) |>
  ungroup() |>
  mutate(cause = reorder(cause, deaths))

p <- ggplot(top5_by_age, aes(x = cause, y = deaths, fill = age_band,
                              text = paste0(cause, ": ", comma(deaths)))) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  facet_wrap(~ age_band, scales = "free", ncol = 4) +
  scale_y_continuous(labels = comma) +
  labs(title = NULL, x = NULL, y = "Registered deaths, 2024") +
  theme_story() +
  theme(strip.text = element_text(face = "bold", size = 9),
        axis.text.y = element_text(size = 8))

ggplotly(p, tooltip = "text", height = 750) |>
  layout(margin = list(l = 40))
```

Geography
=====================================

Row {data-height=600}
-------------------------------------

### Where registration is most and least complete

```{r}
# --------------------------------------------------------------
# This map needs a Kenya county boundary file, which is NOT part
# of the KNBS release and must be fetched separately (one-time).
# Two easy options:
#
# 1) geoBoundaries (simplest, no auth):
#    download.file(
#      "https://www.geoboundaries.org/api/current/gbOpen/KEN/ADM1/",
#      destfile = "data/ken_adm1.json"
#    )
#    # then follow the returned "simplifiedGeometryGeoJSON" URL to
#    # get the actual GeoJSON, and save it as data/kenya_counties.geojson
#
# 2) rKenyaCensus package (bundles county shapefiles directly):
#    install.packages("rKenyaCensus")
#    counties_sf <- rKenyaCensus::KenyaCounties_SHP |> sf::st_as_sf()
#
# Once you have counties_sf with a county-name column, run the
# join below. County-name spelling will very likely need manual
# reconciliation -- see the notes at the bottom of data_prep.R.
# --------------------------------------------------------------

geojson_path <- "data/kenya_counties.geojson"

if (file.exists(geojson_path)) {

  counties_sf <- st_read(geojson_path, quiet = TRUE)

  # Adjust "shapeName" to whatever the name column is actually called
  # in your shapefile (inspect with names(counties_sf)).
  counties_sf <- counties_sf |>
    mutate(county_join = shapeName |>
             stringr::str_replace("Murang'a", "Muranga") |>
             stringr::str_replace("Nairobi City", "Nairobi") |>
             stringr::str_replace("Taita/Taveta", "Taita Taveta") |>
             stringr::str_replace("Elgeyo/Marakwet", "Elgeyo Marakwet"))

  map_data <- county_completeness |>
    mutate(county_join = county_clean |>
             stringr::str_replace("Murang'a", "Muranga") |>
             stringr::str_replace("Nairobi City", "Nairobi") |>
             stringr::str_replace("Taita/Taveta", "Taita Taveta") |>
             stringr::str_replace("Elgeyo/Marakwet", "Elgeyo Marakwet"))

  counties_sf <- counties_sf |> left_join(map_data, by = "county_join")

  pal <- colorNumeric("YlOrRd", domain = counties_sf$comp_total, reverse = TRUE)

  leaflet(counties_sf) |>
    addProviderTiles(providers$CartoDB.Positron) |>
    addPolygons(
      fillColor = ~pal(comp_total),
      fillOpacity = 0.8,
      color = "white",
      weight = 1,
      label = ~paste0(county_clean, ": ", comp_total, "% completeness"),
      highlightOptions = highlightOptions(weight = 2, color = "#333", bringToFront = TRUE)
    ) |>
    addLegend(pal = pal, values = ~comp_total, title = "Completeness (%)",
              position = "bottomright")

} else {

  # Fallback so the dashboard still renders before you've added the
  # shapefile: a sorted bar chart of the same data.
  bar_df <- county_completeness |>
    arrange(comp_total) |>
    mutate(county_clean = factor(county_clean, levels = county_clean))

  p <- ggplot(bar_df, aes(x = county_clean, y = comp_total,
                           text = paste0(county_clean, ": ", comp_total, "%"))) +
    geom_col(fill = "#c05621") +
    coord_flip() +
    labs(title = "Death registration completeness by county, 2024",
         subtitle = "Map view needs data/kenya_counties.geojson -- see code comments",
         x = NULL, y = "Completeness (%)") +
    theme_story() +
    theme(axis.text.y = element_text(size = 7))

  ggplotly(p, tooltip = "text", height = 900)
}
```

Row {data-height=400}
-------------------------------------

### Full county table

```{r}
county_completeness |>
  select(County = county_clean,
         `Registered` = reg_total,
         `Expected` = exp_total,
         `Completeness (%)` = comp_total) |>
  arrange(`Completeness (%)`) |>
  datatable(options = list(pageLength = 10, order = list(list(3, "asc"))),
            rownames = FALSE)
```