---
title: "Getting started with dapper: randomized response"
output:
  rmarkdown::html_vignette:
    toc: true
bibliography: references.bib
vignette: >
  %\VignetteIndexEntry{Getting started with dapper: randomized response}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE,
  fig.width = 6,
  fig.height = 3.5,
  fig.align = "center"
)
```

This vignette adapts Example 1 of *dapper: Data Augmentation for Private
Posterior Estimation in R*, in which `dapper` is used to analyze a privatized
record-level release produced by randomized response. The example uses a subset
of the UC Berkeley admissions data aggregated across departments [@Bickel1975]
to demonstrate privacy-aware inference for the odds ratio.

## Prepare the data and privacy mechanism

We start with the four confidential cell counts in `datasets::UCBAdmissions`,
summed over departments. For computational convenience, we take a subsample of
400 applicants.
The first column encodes sex (male = 1, female = 0), and the second encodes
admission status (admitted = 1, rejected = 0).

```{r confidential-data}
library(dapper)
library(ggplot2)

cells <- data.frame(
  sex = c(1, 1, 0, 0),
  status = c(1, 0, 1, 0)
)
counts <- c(1198, 1493, 557, 1278)
cnf_df <- cells[rep(seq_len(nrow(cells)), times = counts), ]

set.seed(1)
n <- 400
ix <- sample(seq_len(nrow(cnf_df)), n, replace = FALSE)
cnf_df <- cnf_df[ix, ]
```

Randomized response [@Warner1965] is applied independently to both binary
attributes. Each released value matches the
confidential value with probability 3/4 and is flipped with probability 1/4. This
can be done using, for example, two flips of a fair coin. Applying
this mechanism to both attributes gives a privacy budget of at most
$\epsilon = 2\log(3)$ for replacing one individual's pair of attributes.

We store the release in `sdp`, a length-800 vector formed by stacking the sex
column above the admission-status column.

```{r randomized-release}
ri <- as.logical(rbinom(2 * n, 1, 1/2))
ra <- rbinom(sum(ri), 1, 1/2)

sdp <- c(as.matrix(cnf_df))
sdp[ri] <- ra
prv_df <- data.frame(sex = sdp[seq_len(n)], status = sdp[n + seq_len(n)])
```

The following tables show the confidential and privatized counts. In an actual
private-data analysis, the analyst would have access only to the privatized
release and the known privacy mechanism.

```{r admission-tables}
admission_table <- function(x) {
  table(
    Sex = factor(x$sex, levels = c(0, 1), labels = c("Female", "Male")),
    Status = factor(x$status, levels = c(1, 0),
                    labels = c("Admitted", "Rejected"))
  )
}

knitr::kable(admission_table(cnf_df), caption = "Confidential counts")
knitr::kable(admission_table(prv_df), caption = "Privatized counts")
```

## Specify the four model components

Let $\theta = (\pi_{11}, \pi_{10}, \pi_{01}, \pi_{00})$ contain the joint
probabilities of the four sex/status combinations, in the order used by
`cells`. We place a Dirichlet(1, 1, 1, 1) prior on these probabilities.

`new_privacy()` requires four functions, with their argument names and order
as shown below.

### Generate latent data: `latent_f(theta)`

Given the current probabilities, generate a proposed confidential dataset.
Sampling from the four possible rows gives an $n \times 2$ matrix.

```{r latent-component}
latent_f <- function(theta) {
  tl <- list(c(1, 1), c(1, 0), c(0, 1), c(0, 0))
  rs <- sample(tl, n, replace = TRUE, prob = theta)
  do.call(rbind, rs)
}
```

### Update the parameters: `posterior_f(dmat, theta)`

Conditional on the confidential data, the posterior is Dirichlet with
parameters equal to the four cell counts plus one. Normalized gamma draws
produce a draw from this posterior. The `theta` argument is required by
the interface but is unused for this conjugate update.

```{r posterior-component}
posterior_f <- function(dmat, theta) {
  sex <- dmat[, 1]
  status <- dmat[, 2]
  x <- c(
    sum(sex & status),
    sum(sex & !status),
    sum(!sex & status),
    sum(!sex & !status)
  )
  t1 <- rgamma(4, shape = x + 1, rate = 1)
  t1 / sum(t1)
}
```

### Summarize one record: `statistic_f(xi, sdp, i)`

For row $i$, count the attributes that match their released values:

$$
t_i(x_i, s_{dp}) =
\mathbb{1}(x_{i1} = s_{dp,i}) +
\mathbb{1}(x_{i2} = s_{dp,n+i}).
$$

The sampler sums these contributions across rows. This allows it to update
the total efficiently when proposing a change to one latent record.

```{r statistic-component}
statistic_f <- function(xi, sdp, i) {
  n <- length(sdp) %/% 2
  sum(xi == sdp[c(i, n + i)])
}
```

### Evaluate the privacy likelihood: `mechanism_f(sdp, sx)`

If `sx` is the total number of matching attributes, then `length(sdp) - sx`
is the number that differ. The log likelihood of the randomized-response
release is therefore

$$
\texttt{sx}\log(3/4) + (2n - \texttt{sx})\log(1/4).
$$

```{r mechanism-component}
mechanism_f <- function(sdp, sx) {
  sx * log(3/4) + (length(sdp) - sx) * log(1/4)
}
```

## Run the sampler and inspect the output

Combine the components with `new_privacy()`, then pass the model and release
to `dapper_sample()`. As in the paper, we run four chains with 6,000 iterations
each and discard the first 1,000 iterations per chain as warmup. This leaves
20,000 draws in total. By default, the chains run
sequentially.

```{r sample-posterior}
dmod <- new_privacy(
  posterior_f = posterior_f,
  latent_f = latent_f,
  mechanism_f = mechanism_f,
  statistic_f = statistic_f,
  npar = 4,
  varnames = c("pi_11", "pi_10", "pi_01", "pi_00")
)

dp_out <- dapper_sample(
  dmod,
  sdp = sdp,
  seed = 123,
  niter = 6000,
  warmup = 1000,
  chains = 4,
  init_par = rep(0.25, 4)
)
```

For parallel execution, see the example in `?dapper_sample`. Progress can be
monitored by wrapping the sampling call in `progressr::with_progress()`.

`summary()` reports posterior summaries and diagnostics using the
**posterior** package. Inspect the R-hat values, effective sample sizes, and
trace plots together before drawing conclusions. The iteration count here
is chosen for a short demonstration. More iterations may be needed for precise
estimates of tail probabilities or quantiles.

```{r parameter-summary}
summary(dp_out)
```

`plot()` produces trace plots through `bayesplot::mcmc_trace()`. Privacy noise
can make the chains mix slowly, so checking these plots is particularly
useful.

```{r trace-plot, fig.height=4, fig.cap="Trace plots for the four cell probabilities. Colors distinguish the four chains.", fig.alt="Four panels show the sampled cell probabilities over iterations, with a different color for each chain."}
plot(dp_out) +
  scale_color_manual(values = c("#00468B", "#ED0000", "#42B540", "#925E9F")) +
  theme_bw(base_size = 11)
```

## Estimate the odds ratio

The odds ratio compares the odds of admission for males with those for
females:

$$
\mathrm{OR} = \frac{\pi_{11}/\pi_{10}}{\pi_{01}/\pi_{00}}.
$$

Values above one indicate higher aggregate odds of admission for males.
Because `dp_out$chain` is a **posterior** `draws_matrix`, we can create this
derived quantity and summarize it directly.

```{r odds-ratio}
odds_ratio_draws <- posterior::mutate_variables(
  dp_out$chain,
  odds_ratio = (pi_11 * pi_00) / (pi_10 * pi_01)
)
odds_ratio_draws <- posterior::subset_draws(
  odds_ratio_draws, variable = "odds_ratio"
)
posterior::summarise_draws(odds_ratio_draws)
```

The shaded region below marks the central 80% posterior interval, while the outer
curve covers the central 95%. The dashed line marks an odds ratio of one.

```{r odds-ratio-density, fig.height=2.8, fig.cap="Privacy-aware posterior distribution of the odds ratio, displayed from 0 to 10.", fig.alt="Posterior density of the odds ratio, with a shaded central 80 percent interval and a dashed vertical reference line at one."}
bayesplot::mcmc_areas(
  odds_ratio_draws,
  prob = 0.8,
  prob_outer = 0.95,
  point_est = "none"
) +
  geom_vline(xintercept = 1, linetype = "dashed", linewidth = 0.5) +
  coord_cartesian(xlim = c(0, 10)) +
  labs(x = "Odds ratio", y = NULL) +
  theme_bw(base_size = 11) +
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank())
```

## Compare with analyses that omit the privacy mechanism

To examine the impact of the privacy mechanism, we can
compare the posterior draws from dapper to an equivalent Bayesian
analysis on the confidential data. Moreover, we can see the consequence
of ignoring the privacy mechanism by repeating the analysis on the privatized records, 
but treating them as if they were the confidential data.

```{r comparison-draws}
set.seed(1)
confidential_data <- as.matrix(cnf_df)
cps <- t(replicate(20000, posterior_f(confidential_data, NULL)))
odds_ratio_conf <- (cps[, 1] * cps[, 4]) / (cps[, 2] * cps[, 3])

set.seed(1)
noisy_data <- matrix(sdp, ncol = 2)
cps <- t(replicate(20000, posterior_f(noisy_data, NULL)))
odds_ratio_noisy <- (cps[, 1] * cps[, 4]) / (cps[, 2] * cps[, 3])

odds_ratio_private <- posterior::extract_variable(
  odds_ratio_draws, "odds_ratio"
)
```

The privacy-aware odds-ratio posterior has a long right tail, so we evaluate
each density on a common plotting grid from 0 to 8, using all draws to
estimate the density. 

```{r comparison-densities}
posterior_density <- function(draws, data, analysis) {
  estimate <- density(draws, from = 0, to = 8)
  data.frame(odds_ratio = estimate$x, density = estimate$y,
             data = data, analysis = analysis)
}

comparison <- rbind(
  posterior_density(odds_ratio_conf, "Confidential", "Privacy-aware"),
  posterior_density(odds_ratio_private, "Privatized", "Privacy-aware"),
  posterior_density(odds_ratio_conf, "Confidential", "Naïve"),
  posterior_density(odds_ratio_noisy, "Privatized", "Naïve")
)
comparison$analysis <- factor(comparison$analysis,
                              levels = c("Privacy-aware", "Naïve"))
```

Each panel uses the confidential-data posterior as a reference. The left
compares it with the privacy-aware posterior from `dapper`; the right
compares it with the naïve posterior that results from treating the privatized
data as if they were confidential.

```{r posterior-comparison, fig.cap="Odds-ratio posteriors for confidential data (blue) and privatized data (red). The left panel accounts for randomized response; the right panel ignores it.", fig.alt="Two density panels compare confidential and privatized analyses. The privacy-aware posterior is wider, while the naïve posterior is concentrated closer to one."}
data_colors <- c(Confidential = "#00468B", Privatized = "#ED0000")

ggplot(comparison, aes(x = odds_ratio, y = density, fill = data, color = data)) +
  geom_area(position = "identity", alpha = 0.2, color = NA) +
  geom_line(linewidth = 0.6) +
  facet_wrap(~ analysis) +
  coord_cartesian(xlim = c(0, 8)) +
  scale_fill_manual(name = "Data", values = data_colors) +
  scale_color_manual(name = "Data", values = data_colors) +
  labs(x = "Odds ratio", y = "Density") +
  theme_bw(base_size = 11) +
  theme(legend.position = "bottom")
```

The plots demonstrate that the naïve posterior exhibits some bias towards one and
is overconfident. 
On the other hand, the privacy-aware analysis accounts for the
additional uncertainty introduced by randomized response and produces a
wider posterior. This comparison illustrates why the privacy mechanism
should be accounted for in a statistical analysis.

## References
