--- title: "When XAI methods disagree" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{When XAI methods disagree} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` `judge_clusters()` asks one question: does this panel of judges fall into groups, or is it one population? This vignette puts it on a dataset built to divide the panel and gets the answer *no*. That is the right answer, and the reason it is the right answer is the most useful thing the function has to teach: the two methods here disagree enormously, and they disagree about something a ranking cannot express. ## A dataset built to cause it `x1` and `x2` are two noisy readings of the same underlying quantity, and only that quantity drives the response. Either one alone predicts; neither is needed once you have the other. ```{r setup} library(rankimp) set.seed(1) n <- 200 z <- rnorm(n) df <- data.frame( x1 = z + rnorm(n, sd = 1 / 3), # correlation of about 0.9 with x2 x2 = z + rnorm(n, sd = 1 / 3), x3 = rnorm(n), x4 = rnorm(n), x5 = rnorm(n) ) df$y <- 1.5 * z + 0.8 * df$x3 + rnorm(n) round(cor(df$x1, df$x2), 2) ``` `x3` is the only predictor that is both real and irreplaceable. `x1` and `x2` each carry more signal than `x3` does, but each can stand in for the other. ## Building the panel Two methods, three seeds: six judges. ```{r panel} library(randomForest) set.seed(2) fit <- randomForest(y ~ ., data = df, ntree = 200) J <- importance_judges( fit, methods = c("permutation", "loco"), data = df, target = "y", seeds = 1:3, n_perm = 5 ) J ``` ## The two methods do disagree The design worked. Look at the scores behind those ranks, which the panel keeps: ```{r scores} round(attr(J, "scores"), 3) ``` Permutation puts `x3` and `x1` within about 15% of each other. LOCO puts `x3` roughly six times above `x1`. That is the difference the correlation was built to produce, and it is large. It is also exactly what the two methods are for. Permuting `x1` in a fitted forest barely hurts, because the forest still has `x2` and reroutes through it, so a *marginal* measure applied to a *fitted* model already discounts a replaceable predictor. Dropping `x1` and refitting discounts it much harder, because now the comparison is between two models rather than two inputs to one. Both demote the correlated pair; they disagree about how far. ## But the rankings agree ```{r consensus} cr <- consensus_rank(J) cr$tau ``` Five of the six judges produce an identical ordering, and the sixth differs by one adjacent swap. A factor of six in the scores became nothing in the ranks: both methods put `x3` first, then the correlated pair, then the two noise variables. Ranking is a coarse instrument, and here it has thrown away the whole disagreement. ## Is there one panel, or two? ```{r clusters} het <- judge_clusters(J) het ``` `k = 1`. The panel's sharpest division in two scores a silhouette of 0.833, which looks decisive until you ask what a *single* population of six judges scores: on the 199 reference panels, 0.579 on average, and 13 of them reach 0.833 or better. Six judges always fall into some grouping, and the reference is what tells a real seam from an arbitrary one. ```{r test} het$test[c("statistic", "p_value", "null_mean")] ``` Read `p = 0.07` for what it is. It does not say the panel is one population. It says a single population produces a seam this sharp about one time in fourteen, which is not enough to report two. ```{r plot, fig.width = 6, fig.height = 4} autoplot(het) ``` ## How often does it divide? `inst/simulations/cluster-recovery.R` runs this design over many datasets, so the rate is measured rather than asserted. Panels of permutation and LOCO judges on correlated predictors, 100 datasets per cell: | correlation | judges | divided in two | division was exactly the method families | |---|---|---|---| | 0.5 | 6 | 4 / 100 | 4 / 4 | | 0.9 | 6 | 23 / 100 | 23 / 23 | | 0.9 | 16 | 82 / 100 | 69 / 82 | **Panel size is what buys the finding.** At a correlation of 0.9, two methods by three seeds divides one time in four; two methods by eight seeds divides four times in five. Six judges is very little to establish a grouping from: six points in a discrete space fall into *some* arrangement, and the reference panels are strict about it for good reason. If the question matters, add seeds. Those rates are over fresh datasets, though, not over more judges on one dataset, and the two are not the same thing. The dataset above is a stubborn one: run it with eight seeds instead of three and the sixteen-judge panel still comes back undivided, because on this draw the two methods really do produce the same ordering. More judges buy power against a seam that is there. They cannot find one that is not. The division it does find is trustworthy. At six judges all 27 divisions across both correlations fell exactly on the method families, with no arbitrary seams. At sixteen, 69 of 82 did, and a homogeneous panel of sixteen is divided anyway 8% of the time, which is roughly the shortfall. The mean `tau` over those same runs is 0.74 at a correlation of 0.9. The panel mostly agrees about the ordering however many judges you give it; what more judges buy is the power to see the seam that is there. ## What a rank-based diagnostic can and cannot see `judge_clusters()` works on rankings, because rankings are what make different importance measures comparable at all. Scores from permutation and scores from LOCO are not on one scale, and nothing can put them there. The price is fixed and worth stating plainly: **a disagreement that lives entirely in the magnitudes is invisible to it.** Here that was a factor of six. So use it for what it answers. If the panel divides, `het$centres` holds each group's consensus, and the honest report is two rankings with an explanation rather than one ranking with a caveat. If it does not divide, that is a real finding about the ordering, and the scores, kept on the panel as `attr(J, "scores")`, are where a disagreement about magnitude will still be sitting.