Skip to contents

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