R source for the quadrant plots in the comparative review of the Medical Snapshot system. Data is embedded in the script. Save as snapshot-comparison.R and run using Rscript snapshot-comparison.R.
# Medical Snapshot System: Quadrant Analysis # # Two scatter plots positioning 25 health data systems, plus the Snapshot itself, on # research mechanics vs. Snapshot-feature axes. # # Supporting exhaustive review at https://shearer.org/research/medical-observer-systems-review/ and # the overview of the Snapshot proposal at https://shearer.org/research/medical-snapshot/ # # and various works by Dan Shearer 2007-2026 # # Output: combined snapshot_quadrant.pdf and snapshot_quadrant.png # also snapshot_quadrant_p1.png and _p2.png separated out # # Dependencies: tidyverse, ggrepel, patchwork # install.packages(c("tidyverse", "ggrepel", "patchwork")) options(repos = c(CRAN = "https://cran.r-project.org")) if (!requireNamespace("ggrepel", quietly = TRUE)) install.packages("ggrepel") if (!requireNamespace("patchwork", quietly = TRUE)) install.packages("patchwork") library(tidyverse) library(ggrepel) library(patchwork) # -- 1. Data ------------------------------------------------------------------ # mech : research mechanics score A1-B7 (max 7) # core : Snapshot participant-benefit features D13,F16,C8-C10,D11 (max 6) # voluntary consent + public data release + individual clinical benefit # full : Snapshot governance features C8-F18 (max 11) # adds fiduciary duty, state exclusion, multi-jurisdictional, benefit-sharing # category : display group # label : display name (NA = unlabelled in plot) systems <- tribble( ~id, ~label, ~mech, ~core, ~full, ~category, # -- isolated / labelled points --------------------------------------------- # Evidence tiers: A = Chinese govt source, B = Citizen Lab/HRW primary doc, # C = HRW interpretive inference only. ASPI excluded entirely. # "Physicals for All": mechanics (A1-B7) A/B-confirmed; D12+F17 inversions # A-confirmed via Article 16, 2019 Human Genetic Resources Regulations; # D13 inversion C-only (single HRW interview, culturally contested). # MPS database: A2+D13 inversions B-confirmed via Citizen Lab. # Composite scores (core, full) are unchanged from pre-exclusion values # because all inversions count zero regardless of confidence tier. "snapshot", "Medical Snapshot", 7.0, 6.0, 11.0, "Target", "physicals", "Physicals for All", 7.0, 0.0, 0.0, "Coercive", "dodsr", "DoD Serum Repository", 6.0, 2.0, 2.0, "Military", "tuskegee", "Tuskegee", 2.0, 0.0, 0.0, "Coercive", "nz", "NZ Experiment", 1.0, 0.0, 0.0, "Coercive", "ndnad", "NDNAD / CODIS", 3.0, 0.0, 0.0, "Coercive", "decode", "deCODE", 4.0, 0.0, 1.0, "Coercive", "guthrie", "Guthrie Card", 3.5, 2.0, 2.0, "Other", # -- research biobank cluster (exact scores; jitter applied in geom) -------- "epic", NA, 5.5, 2.0, 2.0, "Research", "ckb", NA, 5.0, 2.0, 2.0, "Research", "ukbb", NA, 5.0, 2.0, 2.5, "Research", "taizhou", NA, 5.0, 2.0, 2.0, "Research", "cnhbm", NA, 5.0, 1.0, 1.0, "Research", "framingham", NA, 4.0, 2.0, 2.0, "Research", "whitehall", NA, 4.0, 2.0, 2.0, "Research", "nurses", NA, 4.0, 2.0, 2.0, "Research", "alspac", NA, 4.0, 2.0, 2.5, "Research", "bbjapan", NA, 4.0, 2.0, 2.0, "Research", "allofus", NA, 4.0, 2.0, 3.0, "Research", "genscot", NA, 3.5, 2.0, 2.0, "Research", "estonian", NA, 3.0, 2.0, 2.0, "Research", "finngen", NA, 3.0, 1.0, 2.0, "Research", "ofh", NA, 3.0, 1.0, 1.0, "Research", "majengo", NA, 2.5, 0.5, 0.5, "Other", # -- surveillance ----------------------------------------------------------- "cngb", NA, 4.0, 0.0, 0.0, "Surveillance", "mps", NA, 3.0, 0.0, 0.0, "Surveillance" ) %>% mutate( category = factor(category, levels = c("Target","Research","Coercive", "Military","Surveillance","Other")), labelled = !is.na(label) ) # -- 2. Aesthetics ------------------------------------------------------------ pal <- c( "Target" = "#16a34a", "Research" = "#2563eb", "Coercive" = "#dc2626", "Military" = "#7c3aed", "Surveillance" = "#d97706", "Other" = "#6b7280" ) shp <- c( "Target" = 23, # diamond (filled) "Research" = 21, # circle (filled) "Coercive" = 24, # triangle up (filled) "Military" = 22, # square (filled) "Surveillance" = 25, # triangle down (filled) "Other" = 21 # circle (filled) ) base_theme <- theme_minimal(base_size = 13) + theme( legend.position = "bottom", legend.title = element_blank(), legend.key.size = unit(0.45, "cm"), legend.text = element_text(size = 9), panel.grid.major = element_line(colour = "grey92", linewidth = 0.35), panel.grid.minor = element_blank(), axis.title = element_text(size = 10, colour = "grey40"), axis.text = element_text(size = 9, colour = "grey50"), plot.title = element_text(size = 11, face = "plain", margin = margin(b = 2)), plot.subtitle = element_text(size = 9, colour = "grey40", lineheight = 1.3, margin = margin(b = 6)), plot.margin = margin(6, 12, 6, 6) ) # -- 3. Plot function --------------------------------------------------------- make_quad <- function(dat, y_col, y_max, q_y, y_label, title, subtitle, x_max = 7.8, gap_bracket = FALSE) { y_step <- if (y_max == 6) 1 else 2 p <- ggplot(dat, aes(x = mech, y = .data[[y_col]], colour = category, fill = category, shape = category)) + # Quadrant shading --------------------------------------------------------- annotate("rect", xmin = 3.5, xmax = Inf, ymin = -Inf, ymax = q_y, fill = "#fef2f2", alpha = 0.6) + annotate("rect", xmin = 3.5, xmax = Inf, ymin = q_y, ymax = Inf, fill = "#eff6ff", alpha = 0.28) + # Research-biobank cluster box -------------------------------------------- annotate("rect", xmin = 3.25, xmax = 5.75, ymin = 1.15, ymax = 3.10, fill = "#dbeafe", colour = "#93c5fd", alpha = 0.4, linewidth = 0.45) + annotate("text", x = 4.5, y = 3.35, label = "research biobanks", size = 3, colour = "#1d4ed8", hjust = 0.5) + # Quadrant dividers ------------------------------------------------------- geom_vline(xintercept = 3.5, linetype = "dashed", colour = "grey55", linewidth = 0.45) + geom_hline(yintercept = q_y, linetype = "dashed", colour = "grey55", linewidth = 0.45) + # Points: jitter unlabelled to reveal cluster density -------------------- geom_jitter(data = filter(dat, !labelled), position = position_jitter(width = 0.13, height = 0.08, seed = 42), size = 2.2, alpha = 0.8, stroke = 0.4) + # Points: exact positions for labelled ------------------------------------ geom_point(data = filter(dat, labelled), size = 3.5, stroke = 0.65) + # Labels (ggrepel handles overlap) ---------------------------------------- geom_label_repel( data = filter(dat, labelled), aes(label = label), colour = "grey15", fill = "white", size = 3.5, label.size = 0.3, label.padding = unit(0.20, "lines"), box.padding = unit(0.6, "lines"), point.padding = unit(0.4, "lines"), min.segment.length = 0.2, show.legend = FALSE ) + # Scales ------------------------------------------------------------------ scale_colour_manual(values = pal, drop = FALSE) + scale_fill_manual(values = pal, drop = FALSE) + scale_shape_manual(values = shp, drop = FALSE) + scale_x_continuous(limits = c(-0.2, x_max), breaks = 0:7, expand = expansion(0)) + scale_y_continuous(limits = c(-0.6, y_max + 1.6), breaks = seq(0, y_max, y_step), expand = expansion(0)) + labs(x = "Research mechanics (0 - 7)", y = y_label, title = title, subtitle = subtitle, colour = NULL, fill = NULL, shape = NULL) + base_theme # Bracket showing unclaimed gap (plot 2 only) ------------------------------ if (gap_bracket) { p <- p + annotate("segment", x = 7.3, xend = 7.3, y = 3.05, yend = 10.9, colour = "grey50", linewidth = 0.55, arrow = arrow(ends = "both", type = "open", length = unit(0.12, "cm"))) + annotate("text", x = 7.42, y = 7.0, label = "8 unachieved\nfeatures", size = 3.2, colour = "grey40", hjust = 0, lineheight = 1.2) } p } # -- 4. Build plots ----------------------------------------------------------- p1 <- make_quad( dat = systems, y_col = "core", y_max = 6, q_y = 1, y_label = "Snapshot participant-benefit features (0 - 6)", title = "Plot 1 -- Snapshot participant-benefit features", subtitle = paste0( "Voluntary consent + public data release + individual clinical benefit.\n", "Research biobanks and DoD Serum Repository reach score 2 by different routes." ) ) p2 <- make_quad( dat = systems, y_col = "full", y_max = 11, q_y = 1, y_label = "Snapshot governance features (0 - 11)", title = "Plot 2 -- Snapshot governance features", subtitle = paste0( "Adds fiduciary duty, state-access exclusion, multi-jurisdictional\n", "architecture, and benefit-sharing. Research biobanks barely move." ), x_max = 8.9, gap_bracket = TRUE ) # -- 5. Combine and save ------------------------------------------------------ combined <- (p1 | p2) + plot_layout(guides = "collect") & theme(legend.position = "bottom") ggsave("snapshot_quadrant.pdf", combined, width = 18, height = 8, device = cairo_pdf) ggsave("snapshot_quadrant.png", combined, width = 18, height = 8, dpi = 300, bg = "white") ggsave("snapshot_quadrant_p1.pdf", p1, width = 9, height = 8, device = cairo_pdf) ggsave("snapshot_quadrant_p1.png", p1, width = 9, height = 8, dpi = 300, bg = "white") ggsave("snapshot_quadrant_p2.pdf", p2, width = 9, height = 8, device = cairo_pdf) ggsave("snapshot_quadrant_p2.png", p2, width = 9, height = 8, dpi = 300, bg = "white") message("Saved: combined, p1, and p2 as PDF and PNG")