**Generalised columns are refused.** Edit distance between a raw value and a published *region* – `adist("37", "[30,40)")` is 6 – measures the length of the bracket string and nothing else, but it is a plausible-looking number and no error is raised. On `docs/investigation/generalization-benchmark.R` that misuse reports a success rate of 0.1017 where [score_containment()] reports 0.4450, so the release looks about four times safer than it is (Issue #40, and `docs/lessons-learned.md` section 2). This stops instead; see [is_generalized_value()] for exactly what is detected, and note that a *categorical* generalisation (千代田区 published as 東京都) cannot be detected structurally at all.
Usage
score_char(
dat_raw_anon,
target,
row_number = "ROW_NUMBER",
generalized = c("stop", "warn", "ignore"),
.fn_name = "score_char"
)Arguments
- dat_raw_anon
dataframe of raw_anon form
- target
target column
- row_number
name of the row-number column *before* the RAW_/ANON_ prefixing done by [join_raw_anon_data()] (default: "ROW_NUMBER")
- generalized
what to do when `target` turns out to hold generalised values on the ANON side: `"stop"` (default), `"warn"` (compute the edit distances anyway, having said so) or `"ignore"` (skip the check). Use `"ignore"` only when the column is meant to be compared literally, e.g. when RAW and ANON carry the *same* already-binned values.
- .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 whose SCORE is the edit distance between the RAW and ANON values of `target` (a distance: smaller is a better match).
Examples
raw <- data.frame(ROW_NUMBER = 1:3, NAME = c("aaa", "bbb", "ccc"))
anon <- data.frame(ROW_NUMBER = 1:3, NAME = c("aax", "bbb", "ccd"))
d <- join_raw_anon_data(raw, anon)
score_char(d, "NAME")
#> reid scores (distance): 9 candidate pair(s), 3 ANON x 3 RAW record(s)
#> RAW_ROW_NUMBER ANON_ROW_NUMBER SCORE
#> 1 1 1 1
#> 2 2 1 3
#> 3 3 1 3
#> 4 1 2 3
#> 5 2 2 0
#> 6 3 2 3
#> # ... 3 more pair(s)
# a generalised ANON column is refused rather than scored (Issue #40)
g_raw <- data.frame(ROW_NUMBER = 1:3, AGE = c("31", "37", "46"))
g_anon <- data.frame(ROW_NUMBER = 1:3, AGE = c("30s", "30s", "40s"))
try(score_char(join_raw_anon_data(g_raw, g_anon), "AGE"))
#> Error : score_char(): column "AGE" is generalised on the ANON side (100% of its published values are regions) -- the published value is a region containing the raw one, not a value to compare with it (RAW "31" falls inside ANON "30s"; RAW "46" falls inside ANON "40s"). Comparing them directly measures the printed shape of the region, not the risk: on generalised data it reports a success rate several times lower than the real one and raises no error, so the release looks safer than it is (docs/lessons-learned.md section 2). Use score_containment(dat, "AGE"), which asks which RAW records could have produced each published region. If this column really is meant to be compared literally, pass generalized = "warn" or generalized = "ignore". In a column specification -- attacker_knowledge() or score_multi() -- declare it as "containment" instead: c(AGE = "containment").