Skip to contents

The score for anonymised data that publishes *regions* instead of values: "30代" rather than 37, "東京都" rather than 千代田区, "135****" rather than a full postcode. No distance can be computed on such a column – and [score_num()] on it either errors or coerces to NA and reports that nobody was reidentified, which reads as "safe". The question containment asks instead is which RAW records *could* have produced this ANON record.

Usage

score_containment(
  dat_raw_anon,
  targets,
  row_number = "ROW_NUMBER",
  hierarchy = NULL,
  rules = NULL,
  units = generalization_units(),
  .fn_name = "score_containment"
)

Arguments

dat_raw_anon

dataframe of raw_anon form

targets

character vector of column names (before RAW_/ANON_ prefixing) to intersect

row_number

name of the row-number column *before* the RAW_/ANON_ prefixing done by [join_raw_anon_data()] (default: "ROW_NUMBER")

hierarchy

a "reid_hierarchy" object from [read_generalization_hierarchy()] or [generalization_hierarchy()], or NULL (the default) when every generalisation is an interval that speaks for itself

rules

optional named character vector forcing a rule per column, one of `"auto"` (default: interval if the value parses as one, else exact string equality, in both cases widened by the hierarchy), `"interval"`, `"exact"` or `"prefix"` (for masked codes such as `"135****"`)

units

unit strings that may follow a number, see [generalization_units()]

Warns when any ANON record ends up with an **empty** candidate set. That is the failure Issue #101 is about: nothing is contained, every candidate ties at 1, and the record contributes the random baseline to the reported rate – while [reid_evaluate()]'s `blocked` / `n_true_missing` / `truth_coverage` all still report a healthy join, because the candidate table keeps its full shape. The warning names the published values that excluded everybody.

.fn_name

name used in error messages; a function that wraps this one passes its own name so the message points at the function the user actually called

Value

a "reid_scores" table (a distance in \[0, 1\]: smaller is a better match), carrying a `candidate_count` attribute – the `k` per ANON record. [reid_evaluate()] reads it and prints how many records have `k == 0`.

Details

Several `targets` are intersected, not added: a record must fall inside the published region of **every** attribute to stay a candidate. That is what makes weak attackers measurable – each attribute the attacker holds cuts the candidate set again, and the cuts multiply.

The score is `1 - 1/k` for a contained candidate, where `k` is the number of RAW records that survive for that ANON record, and `1` for an excluded one. All survivors therefore tie, [match_greedy()] draws among them uniformly and reports `CONFIDENCE = 1/k`, and `1/k` is exactly the information the release gave away. Use [containment_counts()] to see the `k` values directly.

A value of `"*"`, `"**"`, `""` or `NA` on the ANON side means "suppressed" and matches every RAW value, so a fully suppressed column contributes nothing rather than excluding everybody.

See also

[containment_counts()] for the per-record narrowing, and [generalize_value()] for building generalised columns.

Examples

raw <- data.frame(ROW_NUMBER = 1:4, AGE = c(31, 37, 46, 52),
                  SEX = c("M", "F", "F", "M"), stringsAsFactors = FALSE)
anon <- data.frame(ROW_NUMBER = 1:4, AGE = c("30s", "30s", "40s", "50s"),
                   SEX = c("M", "F", "F", "M"), stringsAsFactors = FALSE)
d <- join_raw_anon_data(raw, anon)
match_greedy(score_containment(d, c("AGE", "SEX")))
#>   ANON_ROW_NUMBER RAW_ROW_NUMBER CONFIDENCE RESULT
#> 1               1              1          2   TRUE
#> 2               2              2          2   TRUE
#> 3               3              3          2   TRUE
#> 4               4              4          2   TRUE