---
title: "Getting started with flexsynth"
output:
  litedown::html_format:
    meta:
      css: ["@default"]
vignette: >
  %\VignetteEngine{litedown::vignette}
  %\VignetteIndexEntry{Getting started with flexsynth}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
set.seed(1)
library(flexsynth)
```

## What flexsynth does

`flexsynth` generates new records intended to reproduce selected statistical
structure from a real dataset. The default **Track A** engine is a
utility-oriented sequential synthesiser. It may reproduce source values or even
records, carries **no** formal privacy guarantee, and must not be treated as
anonymisation. The package's empirical utility and disclosure-risk diagnostics
inform a release review; they do not prove that a release is safe. The opt-in
differentially private **Track B** and the conditions required for its formal
guarantee are described in `vignette("differential-privacy")`.

## A first synthesis

We start with a plain single-table dataset — one row per patient.

```{r data}
n <- 300
real <- data.frame(
  id     = seq_len(n),
  age    = round(rnorm(n, 62, 11)),
  sex    = sample(c("F", "M"), n, replace = TRUE, prob = c(0.45, 0.55)),
  smoker = sample(c(FALSE, TRUE), n, replace = 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)
```

`synth()` takes the data and a `structure` formula naming the unit identifier.
Here every row is its own unit, so the structure is just `~ id`.

```{r synth}
res <- synth(real, structure = ~ id, seed = 1)
res
syn <- as.data.frame(res)
head(syn)
```

The unit identifier is regenerated, and every other column is synthesised in
turn from a model fitted on the real data (the default `method = "cart"` draws
from the matching leaf of a regression / classification tree).

## Is the synthetic data any good? — `diagnose()`

`diagnose()` compares real and synthetic data on four descriptive views:
per-variable marginals (a Kolmogorov-Smirnov statistic for numeric variables and
total-variation distance for categorical ones), numeric correlations,
categorical associations (Cramer's V), and a **propensity (pMSE)** score. For the
default main-effects logistic model, a `ratio` near 1 is its null benchmark;
larger values indicate greater row-level distinguishability. The score is fitted
and evaluated in-sample, and repeated rows in longitudinal data are not
independent evidence, so do not interpret the ratio as a hypothesis test.

```{r diagnose}
analysis_vars <- c("age", "sex", "smoker", "sbp")
d <- diagnose(real, res, vars = analysis_vars)
d
```

The `plot()` method overlays each marginal.

```{r diag-plot, fig.width = 7, fig.height = 5}
plot(d)
```

## How risky is it? — `disclosure_risk()`

Synthetic data is **not** anonymisation. `disclosure_risk()` provides four
empirical checks: replicated uniques; distance to the closest real record (DCR);
membership inference when a genuine, synthesis-excluded holdout is supplied;
and Target Correct Attribution Probability (TCAP) when a categorical sensitive
`target` is named. Pass genuinely identifying columns as `quasi`, excluding
surrogate keys such as the regenerated `id`. These diagnostics flag potential
identity, membership, and attribute-disclosure problems; no threshold certifies
a release as safe.

```{r risk}
disclosure_risk(real, res, quasi = c("age", "sex", "smoker", "sbp"), seed = 1)
```

## Choosing a method

Methods live in an extensible registry. Built-ins include `sample`, `cart`,
`forest` (a bagged CART ensemble), `ctree` (via `partykit`), and the parametric
numeric methods `norm` and `normrank`.

```{r methods}
list_methods()
```

Set one method for all variables, or per variable via `synth_control()`:

```{r per-var}
ctrl <- synth_control(method = c(sbp = "norm", age = "cart"))
res2 <- synth(real, ~ id, tuning = ctrl, seed = 1)
```

You can register your own with `register_method()` — supply a `fit()` and a
`draw()`.

## Constraints

`rule()` declares a logical constraint the synthetic data must satisfy;
`synth()` enforces it by rejection sampling at the unit grain.

```{r rules}
res3 <- synth(real, ~ id,
              constraints = rule(sbp >= 80 & sbp <= 220),
              seed = 1)
range(as.data.frame(res3)$sbp)
```

## Tuning

`synth_control()` also exposes `smoothing` (kernel-smooth numeric draws) and
`predictor_matrix` (restrict which variables may predict each target). See
`?synth_control`.

Track A output must never be described as differentially private. Use the
diagnostics above to decide whether the utility / risk balance is acceptable for
your release.
