knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
set.seed(1)
library(flexsynth)

dp <- dp_control(epsilon = 1, delta = 1e-6, mechanism = "gaussian")
dp

n <- 1500
real <- data.frame(
  id     = seq_len(n),
  age    = round(rnorm(n, 62, 11)),
  sex    = factor(sample(c("F", "M"), n, TRUE, prob = c(0.45, 0.55))),
  smoker = sample(c(FALSE, TRUE), n, TRUE, prob = c(0.7, 0.3))
)
real$sbp <- round(0.6 * real$age + ifelse(real$smoker, 8, 0) + rnorm(n, 90, 10))
head(real)

df_txt <- data.frame(id = seq_len(400),
                     age = round(rnorm(400, 60, 8)),
                     site = sample(c("north", "south", "east", "west"), 400, TRUE),
                     stringsAsFactors = FALSE)          # site is bare character
dp_txt <- dp_control(epsilon = 6, delta = 1e-6, mechanism = "gaussian",
                     bounds = list(age = c(18, 100)))
synth(df_txt, ~ id, privacy = dp_txt, seed = 1)$privacy

dp <- dp_control(
  epsilon    = 2,
  delta      = 1e-6,
  mechanism  = "gaussian",
  bounds     = list(age = c(18, 100), sbp = c(60, 240))
)
res <- synth(real, structure = ~ id, privacy = dp, seed = 1)
res

syn <- as.data.frame(res)
head(syn)

res$privacy

dp_auto <- dp_control(epsilon = 2, delta = 1e-6, mechanism = "gaussian")
res_auto <- synth(real, structure = ~ id, privacy = dp_auto, seed = 1)
res_auto$privacy

b <- list(age = c(18, 100), sbp = c(60, 240))
dp_tree  <- dp_control(epsilon = 6, mechanism = "laplace", dependence = "tree",
                       bins = 8, bounds = b)
dp_indep <- dp_control(epsilon = 6, mechanism = "laplace", dependence = "independent",
                       bins = 8, bounds = b)
s_tree  <- as.data.frame(synth(real, ~ id, privacy = dp_tree,  seed = 1))
s_indep <- as.data.frame(synth(real, ~ id, privacy = dp_indep, seed = 1))
c(real  = cor(real$age, real$sbp),
  tree  = cor(s_tree$age,  s_tree$sbp),
  indep = cor(s_indep$age, s_indep$sbp))

dp_eff <- dp_control(epsilon = 6, mechanism = "laplace", dependence = "tree",
                     bins = 8, bounds = b, structure_frac = 0.25)
synth(real, ~ id, privacy = dp_eff, seed = 1)$privacy

dp_aim <- dp_control(epsilon = 6, delta = 1e-6, mechanism = "gaussian",
                     select = "adaptive", treewidth = 2,
                     bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_aim, seed = 1)$privacy

dp_anneal <- dp_control(epsilon = 6, delta = 1e-6, mechanism = "gaussian",
                        select = "adaptive", treewidth = 2, anneal = TRUE,
                        bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_anneal, seed = 1)$privacy

dp_bayes <- dp_control(epsilon = 6, delta = 1e-6, mechanism = "gaussian",
                       dependence = "tree", degree = 2,
                       bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_bayes, seed = 1)$privacy

dp_pgm <- dp_control(epsilon = 6, delta = 1e-6, mechanism = "gaussian",
                     dependence = "tree", estimator = "pgm",
                     bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_pgm, seed = 1)$privacy

dp_aim <- dp_control(epsilon = 8, delta = 1e-6, mechanism = "gaussian",
                     select = "aim", treewidth = 2,
                     bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_aim, seed = 1)$privacy

dp_aim_anneal <- dp_control(epsilon = 8, delta = 1e-6, mechanism = "gaussian",
                            select = "aim", treewidth = 2, anneal = TRUE,
                            bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_aim_anneal, seed = 1)$privacy

# The default (scoring = "model") is shown above; this opts back to the cheaper
# one-way-product reference.
dp_aim_indep <- dp_control(epsilon = 8, delta = 1e-6, mechanism = "gaussian",
                           select = "aim", treewidth = 2, scoring = "independence",
                           bounds = list(age = c(18, 100), sbp = c(60, 240)))
synth(real, ~ id, privacy = dp_aim_indep, seed = 1)$privacy

for (e in c(0.5, 2, 8)) {
  d <- dp_control(epsilon = e, mechanism = "laplace",
                  dependence = "independent",
                  bounds = list(age = c(18, 100), sbp = c(60, 240)))
  s <- as.data.frame(synth(real, ~ id, privacy = d, seed = 1))
  cat(sprintf("epsilon = %-3g  mean(sbp): real %.1f  syn %.1f\n",
              e, mean(real$sbp), mean(s$sbp)))
}

long <- do.call(rbind, lapply(1:400, function(i) {
  nv <- sample(2:4, 1); s <- numeric(nv); s[1] <- rnorm(1, 130, 12)
  for (t in seq_len(nv)[-1]) s[t] <- 0.85 * s[t - 1] + 0.15 * 130 + rnorm(1, 0, 5)
  data.frame(id = i, visit = seq_len(nv), sbp = round(s),
             sex = sample(c("F", "M"), 1))
}))
long$sex <- factor(long$sex)

dp_long <- dp_control(epsilon = 8, mechanism = "laplace",
                      max_rows_per_person = 4, bounds = list(sbp = c(60, 240)))
res_long <- synth(long, structure = ~ id / visit, privacy = dp_long, seed = 1)
res_long$privacy

lag1 <- function(d) {
  d <- d[order(d$id, d$visit), ]
  prev <- ave(d$sbp, d$id, FUN = function(x) c(NA, head(x, -1)))
  ok <- !is.na(prev); cor(prev[ok], d$sbp[ok])
}
syn_long <- as.data.frame(res_long)
c(real = lag1(long), synthetic = lag1(syn_long))

res_base <- synth(long, structure = ~ id / visit,
                  privacy = dp_control(epsilon = 8, mechanism = "laplace",
                                       max_rows_per_person = 4,
                                       bounds = list(sbp = c(60, 240)),
                                       baseline = "sex"),
                  seed = 1)

# every synthetic person now has a single sex across their visits
syn_base <- as.data.frame(res_base)
max(tapply(syn_base$sex, syn_base$id, function(s) length(unique(s))))
res_base$privacy   # one fewer transition histogram than the release above

res_ho <- synth(long, structure = ~ id / visit,
                privacy = dp_control(epsilon = 8, mechanism = "laplace",
                                     dependence = "tree", max_rows_per_person = 4,
                                     bounds = list(sbp = c(60, 240)),
                                     baseline = "sex",
                                     transition_order = 2, transition_cross = 1),
                seed = 1)
res_ho$privacy   # note: transitions order 2 + 1 cross-parent, same (eps, delta)

set.seed(1)
patients <- data.frame(
  id  = 1:300,
  age = round(rnorm(300, 60, 11)),
  sex = factor(sample(c("F", "M"), 300, TRUE)))
adm <- do.call(rbind, lapply(patients$id, function(pid) {
  n <- rpois(1, 1.4); if (n == 0) return(NULL)
  data.frame(id = pid, admission_id = seq_len(n), los = 1L + rpois(n, 4))
}))

dp_link <- dp_control(
  epsilon = 4, mechanism = "laplace",
  max_rows_per_person = list(admissions = 6),        # <= 6 admissions per patient
  domain = "public",
  bounds = list(age = c(18, 100), los = c(0, 60)))

res_link <- synth_linked(
  tables     = list(patients = patients, admissions = adm),
  structures = list(patients = ~ id, admissions = ~ id / admission_id),
  keys       = list(patients = "id", admissions = c("id", "admission_id")),
  privacy    = dp_link, seed = 1)
res_link$privacy

check_linkage(res_link)

dp_cross <- dp_control(
  epsilon = 4, mechanism = "laplace", cross_table = TRUE,
  max_rows_per_person = list(admissions = 6),
  domain = "public",
  bounds = list(age = c(18, 100), los = c(0, 60)))

res_cross <- synth_linked(
  tables     = list(patients = patients, admissions = adm),
  structures = list(patients = ~ id, admissions = ~ id / admission_id),
  keys       = list(patients = "id", admissions = c("id", "admission_id")),
  privacy    = dp_cross, seed = 1)
res_cross$privacy

set.seed(1)
visits <- do.call(rbind, lapply(patients$id, function(pid) {
  k  <- 2L + rpois(1, 1.2)
  st <- character(k); st[1] <- sample(c("stable", "worse"), 1)
  for (i in 2:k) st[i] <- if (runif(1) < 0.85) st[i - 1]
                          else setdiff(c("stable", "worse"), st[i - 1])
  data.frame(id = pid, visit_num = seq_len(k),
             status = factor(st, levels = c("stable", "worse")))
}))

dp_longi <- dp_control(
  epsilon = 6, delta = 1e-6, mechanism = "gaussian",
  max_rows_per_person = c(visits = 6), longitudinal = "visits",
  domain = "public", bounds = list(age = c(18, 100)))

res_longi <- synth_linked(
  tables     = list(patients = patients, visits = visits),
  structures = list(patients = ~ id, visits = ~ id / visit_num),
  keys       = list(patients = "id", visits = c("id", "visit_num")),
  privacy    = dp_longi, seed = 1)
res_longi$privacy

dp_both <- dp_control(
  epsilon = 6, delta = 1e-6, mechanism = "gaussian",
  max_rows_per_person = c(visits = 6), longitudinal = "visits",
  cross_table = TRUE, domain = "public", bounds = list(age = c(18, 100)))

res_both <- synth_linked(
  tables     = list(patients = patients, visits = visits),
  structures = list(patients = ~ id, visits = ~ id / visit_num),
  keys       = list(patients = "id", visits = c("id", "visit_num")),
  privacy    = dp_both, seed = 1)
res_both$privacy

dp_order2 <- dp_control(
  epsilon = 6, delta = 1e-6, mechanism = "gaussian",
  max_rows_per_person = c(visits = 6), longitudinal = "visits",
  transition_order = 2, domain = "public", bounds = list(age = c(18, 100)))

res_order2 <- synth_linked(
  tables     = list(patients = patients, visits = visits),
  structures = list(patients = ~ id, visits = ~ id / visit_num),
  keys       = list(patients = "id", visits = c("id", "visit_num")),
  privacy    = dp_order2, seed = 1)
res_order2$privacy

dp_tp <- dp_control(
  epsilon = 6, delta = 1e-6, mechanism = "gaussian",
  max_rows_per_person = c(visits = 6), longitudinal = "visits",
  cross_table = TRUE, transition_parent = 1,
  domain = "public", bounds = list(age = c(18, 100)))

res_tp <- synth_linked(
  tables     = list(patients = patients, visits = visits),
  structures = list(patients = ~ id, visits = ~ id / visit_num),
  keys       = list(patients = "id", visits = c("id", "visit_num")),
  privacy    = dp_tp, seed = 1)
res_tp$privacy

