Scores every declared column, puts the results on a common scale and adds them up, so that a candidate is judged on everything the attacker holds rather than on one attribute at a time.
Usage
score_multi(
dat_raw_anon,
targets,
row_number = "ROW_NUMBER",
weights = NULL,
normalize = c("range", "zscore", "rank", "none"),
method = c("weighted", "mahalanobis"),
split = ":",
cov_from = c("raw", "anon", "pooled"),
ridge = 1e-06,
source = c("anon", "raw", "pooled"),
weight = c("idf", "inv_log", "inv", "none"),
hierarchy = NULL,
rules = NULL,
screen = c("warn", "drop", "none"),
alpha = 0.05,
.fn_name = "score_multi"
)Arguments
- dat_raw_anon
dataframe of raw_anon form
- targets
either a named character vector mapping column name to score type – `c(AGE = "num", ZIP = "char")`, the same form [attacker_knowledge()] uses – or a plain character vector of column names, which is read as all-numeric.
- row_number
name of the row-number column *before* the RAW_/ANON_ prefixing done by [join_raw_anon_data()] (default: "ROW_NUMBER")
- weights
numeric vector of per-column weights, one per entry of `targets`, in the same order (default: all 1)
- normalize
normalisation applied to each component before summing; see [normalize_scores()]. The default `"range"` is bounded and matches the stopgap that Issue #13 needed.
- method
`"weighted"` (default) or `"mahalanobis"`; see above
- split
separator passed to [score_dist()] for `"dist"` columns
- cov_from, ridge
passed to [score_mahalanobis()] when `method = "mahalanobis"`
- source, weight
passed to [score_idf_match()] for `"idf"` columns
- hierarchy, rules
passed to [score_containment()] for `"containment"` columns; a hierarchy is what makes a *categorical* generalisation (千代田区 published as 東京都) scorable at all
- screen
what to do about a column that, measured on its own, does not rank the true record better than chance (see [axis_informativeness()]):
- `"warn"`
(default) report it and change nothing. The returned score is identical to `screen = "none"`.
- `"drop"`
leave it out of the combination – equivalent to giving it weight 0, except that under `method = "mahalanobis"` it also leaves the covariance block. If *every* column fails, all of them are kept and the warning says so: dropping them all would leave no attack.
- `"none"`
skip the check.
Screening reads the RAW/ANON row-number correspondence, i.e. the ground truth, so it models an attacker who has been told which of their attributes are worth using.
- alpha
significance level for the screen, passed to [axis_informativeness()]; ignored when `screen = "none"`
- .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 over the candidate pairs of `dat_raw_anon`. Unless `screen = "none"` it carries the per-axis report in its `"axes"` attribute, readable with [axis_report()].
Choosing `method`
`"weighted"` scores each column separately, normalises, and takes the weighted sum. Every column is treated as independent evidence.
`"mahalanobis"` scores all `"num"` columns *jointly* with [score_mahalanobis()], using the covariance of the reference population, and adds any remaining columns as separate normalised terms. The Mahalanobis block is given the combined weight of the numeric columns it absorbed, so the two methods spend the same total weight on the same columns and their success rates can be compared directly. Use it when some of the numeric columns are correlated – see [score_mahalanobis()] for why a plain sum double-counts them.
Columns scored as a block
Three kinds of column are *not* scored one at a time. Columns declared `"idf"` are handed together to [score_idf_match()], because the relative size of the rarity weights across columns is the method itself and normalising each column separately would discard it. Columns declared `"containment"` go together to [score_containment()], because the published regions are **intersected** – each attribute the attacker holds cuts the candidate set again, and the cuts multiply rather than add. Under `method = "mahalanobis"` the `"num"` columns are likewise handled together. In every case the block receives the summed weight of the columns it absorbed, and is normalised as a single component against the rest.
Generalised columns
Declare a column the release publishes as regions (`"[30,40)"`, `"135****"`, 東京都) as `"containment"`. Every other type compares a raw value against a printed region and so measures the region's shape, not the risk; those types refuse such a column rather than returning a number (Issue #100). See [is_generalized_value()] for what is detected, and pass a `hierarchy` for categorical generalisations, which no structural test can recognise.
Examples
d <- create_dummy_qi_data(people = 20, seed = 1)
j <- join_raw_anon_data(d, d)
s <- score_multi(j, c(AGE = "num", ZIP = "char", VISIT_COUNT = "num"))
match_greedy(s)
#> ANON_ROW_NUMBER RAW_ROW_NUMBER CONFIDENCE RESULT
#> 1 1 1 0.54906762 TRUE
#> 2 2 2 0.95446301 TRUE
#> 3 3 3 0.08638766 TRUE
#> 4 4 4 0.08837780 TRUE
#> 5 5 5 0.89963860 TRUE
#> 6 6 6 1.14478421 TRUE
#> 7 7 7 0.82049721 TRUE
#> 8 8 8 0.41944263 TRUE
#> 9 9 9 1.40308470 TRUE
#> 10 10 10 0.31464513 TRUE
#> 11 11 11 0.45327105 TRUE
#> 12 12 12 0.29745824 TRUE
#> 13 13 13 0.47973161 TRUE
#> 14 14 14 0.59944272 TRUE
#> 15 15 15 0.60523156 TRUE
#> 16 16 16 0.86203753 TRUE
#> 17 17 17 0.58762644 TRUE
#> 18 18 18 0.63558750 TRUE
#> 19 19 19 0.45345759 TRUE
#> 20 20 20 0.83834705 TRUE
axis_report(s)
#> axis informativeness (3 axis/axes, alpha = 0.05)
#> AGE success 0.9000 baseline 0.0500 lift 18.00x rank 0.055 z 7.30 p = 0.0000 informative
#> ZIP success 0.2000 baseline 0.0500 lift 4.00x rank 0.158 z 7.53 p = 0.0000 informative
#> VISIT_COUNT success 0.6000 baseline 0.0500 lift 12.00x rank 0.075 z 7.03 p = 0.0000 informative