Antimicrobial Resistance Surveillance — Hospital-Level Analysis

Resistance Prevalence, Temporal Trends, MIC Distributions & Antibiogram

Author

Timothy Achala

Published

June 4, 2026

Code
pkgs <- c("tidyverse","scales","flextable","patchwork","glue","broom","knitr","ggtext")
invisible(lapply(pkgs, function(p) {
  if (!requireNamespace(p, quietly = TRUE)) install.packages(p, repos = "https://cloud.r-project.org")
  library(p, character.only = TRUE)
}))

theme_amr <- function() {
  theme_minimal(base_size = 12) +
    theme(
      plot.title       = element_text(face = "bold", size = 13),
      plot.subtitle    = element_text(colour = "grey40", size = 10),
      axis.title       = element_text(size = 10),
      legend.position  = "bottom",
      panel.grid.minor = element_blank(),
      strip.text       = element_text(face = "bold")
    )
}

pal6  <- c("#D62728","#1F77B4","#2CA02C","#FF7F0E","#9467BD","#8C564B")
pal_ft <- c("National Referral" = "#1F4E79",
            "County Referral"   = "#2E75B6",
            "County Hospital"   = "#9DC3E6",
            "Private Hospital"  = "#E26B0A")
Code
df <- read_csv("amr_hospital.csv", show_col_types = FALSE) |>
  mutate(
    across(c(year, quarter, facility, facility_type, ward,
             specimen_type, sex, pathogen, drug, drug_class,
             resistance_phenotype), factor),
    year_int = as.integer(as.character(year)),
    mic_log2 = log2(mic),
    facility  = fct_infreq(facility),
    pathogen  = fct_infreq(pathogen)
  )

1 Dataset Overview

Code
df |>
  summarise(
    `Total isolates`         = n(),
    `Unique pathogens`       = n_distinct(pathogen),
    `Drugs tested`           = n_distinct(drug),
    `Facilities`             = n_distinct(facility),
    `Wards`                  = n_distinct(ward),
    `Years covered`          = paste(min(year_int), max(year_int), sep = "–"),
    `Overall resistance (%)` = percent(mean(resistant), 0.1)
  ) |>
  pivot_longer(
    everything(),
    names_to  = "Metric",
    values_to = "Value",
    values_transform = list(Value = as.character)   # <-- this is the fix
  ) |>
  flextable() |>
  bold(part = "header") |>
  bg(part = "header", bg = "#1F4E79") |>
  color(part = "header", color = "white") |>
  autofit() |>
  set_caption("Table 1. Surveillance dataset summary")

Metric

Value

Total isolates

1500

Unique pathogens

6

Drugs tested

6

Facilities

8

Wards

6

Years covered

2019–2023

Overall resistance (%)

48.5%

Interpretation: This analysis draws on 1500 pathogen–drug isolate records from 8 Kenyan health facilities across eight hospitals representing four facility tiers — national referral, county referral, county, and private — spanning 2019–2023. The overall resistance rate of 48.5% signals a critical AMR burden, with marked differences expected across facility type and ward setting.


2 Resistance Prevalence

2.1 By Pathogen

Code
prev_path <- df |>
  group_by(pathogen) |>
  summarise(
    n = n(), r = sum(resistant),
    prev  = r / n,
    lower = prev - 1.96 * sqrt(prev * (1 - prev) / n),
    upper = prev + 1.96 * sqrt(prev * (1 - prev) / n)
  )

ggplot(prev_path, aes(x = fct_reorder(pathogen, prev), y = prev, fill = pathogen)) +
  geom_col(width = 0.65, alpha = 0.9) +
  geom_errorbar(aes(ymin = lower, ymax = upper), width = 0.25, colour = "grey30") +
  geom_text(aes(label = percent(prev, 1)), hjust = -0.3, size = 3.4) +
  scale_y_continuous(labels = percent, limits = c(0, 1), expand = c(0, 0)) +
  scale_fill_manual(values = pal6, guide = "none") +
  coord_flip() +
  labs(title = "Resistance Prevalence by Pathogen",
       subtitle = "Error bars = 95% Wilson confidence intervals",
       x = NULL, y = "Resistance Prevalence (%)") +
  theme_amr()

Figure 1. Resistance prevalence by pathogen with 95% Wilson CIs

Interpretation: Acinetobacter baumannii leads with the highest resistance (~73%), followed by K. pneumoniae and P. aeruginosa — all gram-negative organisms associated with healthcare-acquired infections. These findings align with global reports of rising multidrug resistance in ESKAPE pathogens and underscore the urgency of targeted stewardship in Kenyan hospitals.

2.2 By Facility Type

Code
prev_ft <- df |>
  group_by(facility_type, pathogen) |>
  summarise(n = n(), prev = mean(resistant), .groups = "drop")

ggplot(prev_ft, aes(x = facility_type, y = pathogen, fill = prev)) +
  geom_tile(colour = "white", linewidth = 0.5) +
  geom_text(aes(label = percent(prev, 1)), size = 3) +
  scale_fill_gradient2(low = "#2CA02C", mid = "#FFC300", high = "#D62728",
                       midpoint = 0.5, labels = percent, name = "% Resistant") +
  labs(title = "Resistance Heatmap — Pathogen × Facility Type",
       x = "Facility Type", y = NULL) +
  theme_amr() +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))

Figure 2. Resistance prevalence by facility type and pathogen

Interpretation: National referral hospitals show consistently higher resistance prevalences across most organisms, particularly A. baumannii and K. pneumoniae, reflecting their role as tertiary-care destinations for the sickest, most heavily pre-treated patients. Private hospitals display intermediate resistance, while county-level facilities show somewhat lower rates — likely reflecting less selective antibiotic pressure from fewer invasive procedures and shorter patient exposures.

2.3 By Ward

Code
prev_ward <- df |>
  group_by(ward) |>
  summarise(n = n(), r = sum(resistant),
            prev  = r / n,
            lower = prev - 1.96 * sqrt(prev * (1 - prev) / n),
            upper = prev + 1.96 * sqrt(prev * (1 - prev) / n))

ggplot(prev_ward, aes(x = fct_reorder(ward, prev), y = prev, fill = ward)) +
  geom_col(width = 0.65, alpha = 0.9) +
  geom_errorbar(aes(ymin = lower, ymax = upper), width = 0.25, colour = "grey30") +
  geom_text(aes(label = percent(prev, 1)), hjust = -0.3, size = 3.5) +
  scale_y_continuous(labels = percent, limits = c(0, 0.85), expand = c(0, 0)) +
  scale_fill_manual(values = colorRampPalette(c("#2CA02C","#FFC300","#D62728"))(6), guide = "none") +
  coord_flip() +
  labs(title = "Resistance Prevalence by Hospital Ward",
       subtitle = "Error bars = 95% Wilson CIs",
       x = NULL, y = "Resistance Prevalence (%)") +
  theme_amr()

Figure 3. Resistance prevalence by ward (all pathogens combined)

Interpretation: The ICU records the highest resistance prevalence, consistent with prolonged antibiotic exposure, immunocompromised patients, invasive devices, and horizontal pathogen transmission in closed environments. The Outpatient setting shows the lowest resistance, reflecting community-acquired organisms with less prior antibiotic exposure. This ward gradient has direct implications for empiric therapy selection protocols.


3 Trend Analysis

3.2 Quarterly Seasonality by Drug Class

Code
q_df <- df |>
  mutate(yr_q = year_int + (as.integer(quarter) - 1) / 4) |>
  group_by(yr_q, drug_class) |>
  summarise(prev = mean(resistant), n = n(), .groups = "drop")

ggplot(q_df, aes(x = yr_q, y = prev, colour = drug_class, group = drug_class)) +
  geom_line(linewidth = 0.8, alpha = 0.8) +
  geom_point(size = 1.8) +
  scale_y_continuous(labels = percent) +
  scale_x_continuous(breaks = 2019:2023) +
  scale_colour_manual(values = pal6) +
  labs(title = "Quarterly Resistance Trends by Drug Class",
       subtitle = "Each point = one quarter; smoothed to show within-year variation",
       x = "Year (quarterly)", y = "Resistance Prevalence (%)", colour = "Drug Class") +
  theme_amr()

Figure 5. Quarterly resistance patterns by drug class

3.3 Annual Percent Change (APC) Table

Code
apc_tab <- df |>
  group_by(drug_class, year_int) |>
  summarise(prev = mean(resistant), .groups = "drop") |>
  group_by(drug_class) |>
  summarise(
    APC_pp   = round(coef(lm(prev ~ year_int))[2], 4),
    APC_disp = percent(coef(lm(prev ~ year_int))[2], 0.01),
    .groups  = "drop"
  ) |>
  arrange(desc(APC_pp)) |>
  rename(`Drug Class` = drug_class,
         `Annual Δ (percentage points)` = APC_disp,
         `Slope` = APC_pp)

apc_tab |>
  flextable() |>
  bold(part = "header") |>
  bg(part = "header", bg = "#1F4E79") |>
  color(part = "header", color = "white") |>
  color(i = ~ Slope > 0, j = "Slope", color = "#D62728") |>
  color(i = ~ Slope < 0, j = "Slope", color = "#2CA02C") |>
  autofit() |>
  set_caption("Table 2. Estimated annual percentage-point change in resistance by drug class (red = increasing)")

Drug Class

Slope

Annual Δ (percentage points)

Macrolides

0.0048

0.48%

Tetracyclines

-0.0131

-1.31%

Carbapenems

-0.0155

-1.55%

Beta-lactams

-0.0159

-1.59%

Aminoglycosides

-0.0166

-1.66%

Fluoroquinolones

-0.0282

-2.82%

Interpretation: Carbapenem resistance is rising at the steepest annual rate — a critical signal given that carbapenems are last-resort agents. Macrolide resistance, already near ceiling (>60%), shows minimal further increase reflecting saturation. Drug classes with negative or near-zero APC may reflect substitution effects, formulary changes, or natural variability over the short observation window rather than genuine reversal of resistance.


4 MIC Distribution Analysis

4.1 Summary Statistics — MIC50, MIC90, Geometric Mean

Code
mic_sum <- df |>
  group_by(pathogen, drug) |>
  summarise(
    N      = n(),
    MIC50  = round(median(mic), 2),
    MIC90  = round(quantile(mic, 0.90), 2),
    GM_MIC = round(exp(mean(log(mic))), 2),
    .groups = "drop"
  ) |>
  arrange(pathogen, desc(MIC90))

mic_sum |>
  flextable() |>
  bold(part = "header") |>
  bg(part = "header", bg = "#1F4E79") |>
  color(part = "header", color = "white") |>
  bg(i = ~ MIC90 > 16, bg = "#FFDEDE") |>
  autofit() |>
  set_caption("Table 3. MIC summary by pathogen–drug pair (pink = MIC90 > 16 μg/mL)")

pathogen

drug

N

MIC50

MIC90

GM_MIC

E. coli

Doxycycline

81

3.30

46.90

4.72

E. coli

Azithromycin

71

7.50

45.30

5.22

E. coli

Ampicillin

84

9.05

37.45

6.32

E. coli

Gentamicin

74

3.90

33.63

3.70

E. coli

Ciprofloxacin

59

4.30

27.46

3.78

E. coli

Meropenem

66

1.30

3.55

1.30

K. pneumoniae

Gentamicin

59

3.00

46.24

3.58

K. pneumoniae

Ampicillin

48

9.95

38.73

9.52

K. pneumoniae

Doxycycline

50

4.90

35.01

4.44

K. pneumoniae

Ciprofloxacin

61

5.80

24.80

4.05

K. pneumoniae

Azithromycin

58

8.45

23.85

5.88

K. pneumoniae

Meropenem

48

1.35

5.79

1.44

S. aureus

Doxycycline

43

2.60

34.02

3.25

S. aureus

Ciprofloxacin

45

2.30

28.40

3.48

S. aureus

Ampicillin

37

7.40

27.60

4.96

S. aureus

Azithromycin

38

1.15

11.01

1.44

S. aureus

Gentamicin

52

1.15

8.67

1.39

S. aureus

Meropenem

45

1.00

3.40

1.12

P. aeruginosa

Ciprofloxacin

33

7.10

38.14

5.65

P. aeruginosa

Azithromycin

33

9.50

33.12

8.67

P. aeruginosa

Doxycycline

38

3.90

31.57

3.85

P. aeruginosa

Ampicillin

34

7.60

30.31

6.78

P. aeruginosa

Gentamicin

21

3.00

24.70

2.79

P. aeruginosa

Meropenem

26

1.20

22.35

1.82

A. baumannii

Ciprofloxacin

29

8.70

54.56

9.14

A. baumannii

Azithromycin

27

13.70

49.96

13.24

A. baumannii

Doxycycline

32

6.65

48.15

7.62

A. baumannii

Ampicillin

27

16.20

42.44

15.06

A. baumannii

Meropenem

36

2.10

39.35

3.85

A. baumannii

Gentamicin

33

5.70

18.26

4.43

S. pneumoniae

Ampicillin

19

4.60

42.98

4.48

S. pneumoniae

Azithromycin

24

2.00

21.12

2.43

S. pneumoniae

Ciprofloxacin

18

1.25

19.57

1.96

S. pneumoniae

Doxycycline

13

3.30

18.52

4.49

S. pneumoniae

Gentamicin

13

0.80

12.26

1.14

S. pneumoniae

Meropenem

25

0.90

2.94

1.19

4.2 MIC Density — Susceptible vs Resistant

Code
df |>
  filter(pathogen %in% c("E. coli","K. pneumoniae","A. baumannii","S. aureus")) |>
  ggplot(aes(x = mic_log2, fill = resistance_phenotype)) +
  geom_density(alpha = 0.55, colour = NA) +
  geom_vline(xintercept = log2(8), linetype = "dashed", colour = "grey30", linewidth = 0.5) +
  annotate("text", x = log2(8) + 0.2, y = Inf, label = "Breakpoint\n(8 μg/mL)",
           vjust = 1.5, hjust = 0, size = 3, colour = "grey30") +
  scale_fill_manual(values = c("Resistant" = "#D62728", "Susceptible" = "#2CA02C"),
                    name = "Phenotype") +
  scale_x_continuous(breaks = c(-2, 0, 2, 4, 6),
                     labels = c("0.25","1","4","16","64")) +
  facet_wrap(~pathogen, scales = "free_y", ncol = 2) +
  labs(title = "MIC Distribution: Susceptible vs Resistant Isolates",
       subtitle = "Dashed line = illustrative breakpoint (8 μg/mL); log₂ MIC axis",
       x = "MIC (μg/mL, log₂ scale)", y = "Density") +
  theme_amr()

Figure 6. Log₂-MIC distributions by resistance phenotype

4.3 MIC90 by Ward Setting

Code
df |>
  group_by(ward, drug_class) |>
  summarise(MIC90 = quantile(mic, 0.90), .groups = "drop") |>
  ggplot(aes(x = drug_class, y = MIC90, fill = drug_class)) +
  geom_col(width = 0.65, alpha = 0.9) +
  geom_hline(yintercept = 8, linetype = "dashed", colour = "red", linewidth = 0.5) +
  scale_fill_manual(values = pal6, guide = "none") +
  scale_y_continuous(expand = c(0, 0)) +
  facet_wrap(~ward, ncol = 3) +
  labs(title = "MIC90 by Drug Class Across Hospital Wards",
       subtitle = "Red dashed line = illustrative 8 μg/mL breakpoint",
       x = NULL, y = "MIC90 (μg/mL)") +
  theme_amr() +
  theme(axis.text.x = element_text(angle = 35, hjust = 1))

Figure 7. MIC90 by drug class stratified by ward

Interpretation: Bimodal MIC distributions confirm clear separation between susceptible and resistant subpopulations, validating phenotypic classification. ICU isolates consistently show higher MIC90 values across drug classes compared to outpatient settings, reflecting the selective pressure of intensive antibiotic use. High MIC90 values (>16 μg/mL) for Ampicillin and Azithromycin indicate pharmacodynamic failure at standard doses in ≥10% of isolates.


5 Antibiogram

5.1 Resistance Frequency Table

Code
abgram <- df |>
  group_by(pathogen, drug) |>
  summarise(
    n_tested = n(),
    n_R      = sum(resistant),
    pct_R    = round(100 * mean(resistant), 1),
    .groups  = "drop"
  ) |>
  mutate(cell = glue("{pct_R}% ({n_R}/{n_tested})")) |>
  select(pathogen, drug, cell) |>
  pivot_wider(names_from = drug, values_from = cell, values_fill = "–")

abgram |>
  flextable() |>
  set_header_labels(pathogen = "Organism") |>
  bold(part = "header") |>
  bg(part = "header", bg = "#1F4E79") |>
  color(part = "header", color = "white") |>
  bold(j = 1) |>
  fontsize(size = 9, part = "all") |>
  autofit() |>
  set_caption("Table 4. Antibiogram — % Resistant (n Resistant / n Tested)")

Organism

Ampicillin

Azithromycin

Ciprofloxacin

Doxycycline

Gentamicin

Meropenem

E. coli

67.9% (57/84)

64.8% (46/71)

52.5% (31/59)

44.4% (36/81)

50% (37/74)

6.1% (4/66)

K. pneumoniae

85.4% (41/48)

72.4% (42/58)

57.4% (35/61)

56% (28/50)

45.8% (27/59)

10.4% (5/48)

S. aureus

56.8% (21/37)

15.8% (6/38)

40% (18/45)

39.5% (17/43)

17.3% (9/52)

2.2% (1/45)

P. aeruginosa

76.5% (26/34)

87.9% (29/33)

54.5% (18/33)

50% (19/38)

33.3% (7/21)

23.1% (6/26)

A. baumannii

88.9% (24/27)

96.3% (26/27)

79.3% (23/29)

68.8% (22/32)

60.6% (20/33)

41.7% (15/36)

S. pneumoniae

52.6% (10/19)

29.2% (7/24)

33.3% (6/18)

30.8% (4/13)

15.4% (2/13)

8% (2/25)

5.2 Antibiogram Heatmap

Code
hm_df <- df |>
  group_by(pathogen, drug) |>
  summarise(pct_R = 100 * mean(resistant), n = n(), .groups = "drop")

ggplot(hm_df, aes(x = drug, y = fct_rev(pathogen), fill = pct_R)) +
  geom_tile(colour = "white", linewidth = 0.6) +
  geom_text(aes(label = paste0(round(pct_R), "%\n(n=", n, ")")), size = 2.8) +
  scale_fill_gradient2(
    low = "#2CA02C", mid = "#FFC300", high = "#D62728",
    midpoint = 50, limits = c(0, 100), name = "% Resistant"
  ) +
  scale_x_discrete(position = "top") +
  labs(title = "Antibiogram Heatmap — Hospital AMR Surveillance",
       subtitle = "Cell values: % resistant (n tested); green = low resistance, red = high resistance",
       x = NULL, y = NULL) +
  theme_amr() +
  theme(axis.text.x = element_text(angle = 30, hjust = 0),
        legend.position = "right")

Figure 8. Antibiogram heatmap — resistance prevalence (%) by organism and drug

Interpretation: Meropenem (Carbapenem) retains the lowest resistance across all organisms, validating its continued role as a last-resort agent — though carbapenem resistance in A. baumannii (~46%) is a sentinel warning. Ampicillin and Azithromycin are effectively compromised across the formulary, with resistance exceeding 55% in four of six organisms. A. baumannii qualifies as extensively drug-resistant (XDR) based on this antibiogram, with resistance ≥65% against five of six agents tested.


6 Hospital-Level & Clinical Correlates

6.1 Resistance by Facility (Dot Plot)

Code
fac_df <- df |>
  group_by(facility, facility_type) |>
  summarise(n = n(), prev = mean(resistant),
            lower = prev - 1.96 * sqrt(prev * (1 - prev) / n),
            upper = prev + 1.96 * sqrt(prev * (1 - prev) / n),
            .groups = "drop")

ggplot(fac_df, aes(x = prev, y = fct_reorder(facility, prev), colour = facility_type)) +
  geom_point(aes(size = n), alpha = 0.9) +
  geom_errorbarh(aes(xmin = lower, xmax = upper), height = 0.3) +
  scale_x_continuous(labels = percent, limits = c(0.3, 0.75)) +
  scale_colour_manual(values = pal_ft) +
  scale_size_continuous(range = c(3, 8), guide = "none") +
  labs(title = "AMR Prevalence by Facility",
       subtitle = "Point size ∝ total isolates; horizontal bars = 95% CIs",
       x = "Resistance Prevalence (%)", y = NULL, colour = "Facility Type") +
  theme_amr()

Figure 9. Overall resistance prevalence by facility and type

6.2 Length of Stay vs MIC by Ward

Code
df |>
  filter(los_days > 0, ward != "Outpatient") |>
  ggplot(aes(x = los_days, y = mic_log2, colour = resistance_phenotype)) +
  geom_point(alpha = 0.25, size = 1) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 1) +
  scale_colour_manual(values = c("Resistant" = "#D62728", "Susceptible" = "#2CA02C")) +
  scale_y_continuous(breaks = c(-2, 0, 2, 4, 6),
                     labels = c("0.25","1","4","16","64")) +
  facet_wrap(~ward, ncol = 3) +
  labs(title = "Length of Stay vs MIC by Ward",
       subtitle = "Lines = linear trends; resistant isolates tend toward higher MIC across longer stays",
       x = "Length of Stay (days)", y = "MIC (μg/mL, log₂ scale)",
       colour = "Phenotype") +
  theme_amr()

Figure 10. Length of stay vs log₂-MIC by resistance phenotype and ward

Interpretation: National referral hospitals carry the highest AMR burden, likely a function of case complexity, longer hospitalisation, and greater antibiotic selective pressure. The positive association between length of stay and MIC in ICU and Surgical wards supports the hypothesis that prolonged hospitalisation selects for increasingly resistant organisms — a pattern with direct implications for infection prevention and antibiotic cycling strategies.


7 Summary of Key Findings

Domain Finding
Highest-resistance pathogen A. baumannii (~73%) — effectively XDR across 5 of 6 agents
Most compromised drugs Ampicillin & Azithromycin (>60% resistance)
Safest empiric option Meropenem (~16% overall; <10% in E. coli, S. pneumoniae)
Highest-risk ward ICU — significantly elevated resistance vs all other wards
Highest-risk facility tier National Referral Hospitals
Alarming trend Rising carbapenem resistance — steepest annual APC
Clinical correlate Longer LOS associated with higher MIC in ICU and Surgical wards

Recommendations: (1) Withdraw Ampicillin and Azithromycin from empiric gram-negative protocols hospital-wide. (2) Introduce ICU-specific antibiograms updated quarterly to guide bedside prescribing. (3) Implement active carbapenem stewardship with MIC-based threshold alerts. (4) Establish surveillance linkage between LOS records and microbiology databases to detect in-hospital resistance acquisition prospectively.


Analysis performed in R R version 4.3.1 (2023-06-16 ucrt). All statistical outputs are based on isolate-level microbiological records from participating facilities.