| Title: | Chi-Plots for Assessing Bivariate and Multivariate Dependence |
|---|---|
| Description: | Graphical methods for assessing dependence. The chi-plot of Fisher and Switzer (1985) <doi:10.1093/biomet/72.2.253> and (2001) <doi:10.1198/000313001317098248> supplements a scatterplot of two variables, producing characteristic patterns for different forms of association. Its multivariate extension (Fisher and Lee, 2026) uses generalized orthants and data depth to explore whether a multivariate distribution factorizes into independent blocks of variables, with permutation tests and guidelines. Functions are provided for single chi-plots, a scatterplot matrix with chi-plots above the diagonal, multivariate chi-plots for a given partition of the variables, and a search over all partitions. Computation-intensive parts are written in C. |
| Authors: | Nicholas I. Fisher [aut, cre, cph], Alan J. Lee [aut], ValueMetrics Australia [cph] (copyright holder of the geochem data) |
| Maintainer: | Nicholas I. Fisher <[email protected]> |
| License: | GPL (>= 2) |
| Version: | 0.1.0 |
| Built: | 2026-11-07 01:06:37 UTC |
| Source: | https://github.com/cran/chiplot |
The chi-plot (Fisher and Switzer, 1985, 2001) is a graphical aid to interpreting a scatterplot of two variables: under independence the points form a horizontal band, and different forms of association produce characteristically different patterns. Fisher and Lee (2026) extend the idea to multivariate data, to explore whether a multivariate distribution factorizes into independent blocks of variables.
The main functions are:
chiplota chi-plot for two variables;
chipairsa scatterplot matrix with the corresponding chi-plots above the diagonal;
chimultchi- and lambda-values, permutation tests and the multivariate chi-plot for a given partition of the variables into blocks;
chiparta search over all partitions of the variables, keeping those that show a significant departure from independence;
chidepththe data depth functions used for the lambda-values of multivariate chi-plots.
The calculations of chi-values are carried out in compiled C code. All other code uses base R only.
Nicholas I. Fisher and Alan J. Lee. Maintainer: Nicholas I. Fisher <[email protected]>
Fisher, N. I. and Switzer, P. (1985). Chi-plots for assessing dependence. Biometrika, 72, 253–265. doi:10.1093/biomet/72.2.253
Fisher, N. I. and Switzer, P. (2001). Graphical assessment of dependence: is a picture worth a hundred tests? The American Statistician, 55, 233–239. doi:10.1198/000313001317098248
Fisher, N. I. and Lee, A. J. (2026). Chi-plots for assessing multivariate dependence. Submitted to Journal of Computational and Graphical Statistics.
Calculates the data depth of each row of a data matrix with respect to the
whole sample. These depths are the basis of the lambda-values in
chimult.
chidepth(X, depth = c("RP", "TD", "PD", "MhD"), Nsim = 100)chidepth(X, depth = c("RP", "TD", "PD", "MhD"), Nsim = 100)
X |
a numeric matrix without missing values. |
depth |
the depth function: one of the names below, or a function. |
Nsim |
the number of random directions for |
All the depth functions are implemented in base R:
"RP"random projection depth: the average, over Nsim
random directions, of the univariate halfspace depth of the projected
data. The directions are generated as in fda.usc::mdepth.RP, so the
results are identical for the same random number seed. This is the
default.
"TD"the average of the univariate halfspace (Tukey) depths of
the coordinates, as fda.usc::mdepth.TD. Note that this is not the
exact multivariate Tukey depth.
"PD"projection depth (Zuo and Serfling, 2000),
with outlyingness
,
the maximum being taken over Nsim random directions.
"MhD"Mahalanobis depth, with the
Mahalanobis distance from the mean, as fda.usc::mdepth.MhD. This is
very fast but parametric.
The names "mdepth.RP", "mdepth.TD", "mdepth.MhD" and
"Projection" are also accepted.
A user-specified depth must be a function of the data matrix that
returns one depth per row, either as a numeric vector or as a list with
component dep. For example, function(X) fda.usc::mdepth.RP(X,
proj = 500)$dep, or a function calling depth() in package
DepthProc.
A numeric vector of depths, one for each row of X.
Zuo, Y. and Serfling, R. (2000). General notions of statistical depth function. The Annals of Statistics, 28, 461–482.
set.seed(1) X <- matrix(rnorm(200), 100, 2) d <- cbind(RP = chidepth(X, "RP"), TD = chidepth(X, "TD"), PD = chidepth(X, "PD"), MhD = chidepth(X, "MhD")) round(cor(d, method = "spearman"), 2) ## a user-supplied depth: inverse distance from the coordinatewise median chidepth(X[1:5, ], function(X) 1 / (1 + sqrt(rowSums(t(t(X) - apply(X, 2, median))^2))))set.seed(1) X <- matrix(rnorm(200), 100, 2) d <- cbind(RP = chidepth(X, "RP"), TD = chidepth(X, "TD"), PD = chidepth(X, "PD"), MhD = chidepth(X, "MhD")) round(cor(d, method = "spearman"), 2) ## a user-supplied depth: inverse distance from the coordinatewise median chidepth(X[1:5, ], function(X) 1 / (1 + sqrt(rowSums(t(t(X) - apply(X, 2, median))^2))))
For a multivariate sample and a partition of the variables into blocks,
chimult calculates the chi- and lambda-values of Fisher and Lee (2026),
permutation tests of independence of the blocks, and the material for the
multivariate chi-plot, which is drawn by plot.
chimult(Data, D, nperm = 100, Nsim = 100, depth = "RP", jitter = NULL, jitter.amount = 0.25, plot = FALSE, verbose = TRUE, ...) ## S3 method for class 'chimult' plot(x, pc.level = 0.95, main = "", col1 = "red", col2 = "blue", pt = 12, box = TRUE, cex = NULL, mar = c(4, 4, 2, 2), guide.option = 1, del = 0.2, ...) ## S3 method for class 'chimult' print(x, ...)chimult(Data, D, nperm = 100, Nsim = 100, depth = "RP", jitter = NULL, jitter.amount = 0.25, plot = FALSE, verbose = TRUE, ...) ## S3 method for class 'chimult' plot(x, pc.level = 0.95, main = "", col1 = "red", col2 = "blue", pt = 12, box = TRUE, cex = NULL, mar = c(4, 4, 2, 2), guide.option = 1, del = 0.2, ...) ## S3 method for class 'chimult' print(x, ...)
Data |
a numeric matrix or data frame ( |
D |
the block sizes, in column order: for example, if |
nperm |
the number of random permutations used for the p-values and null distributions. |
Nsim |
the number of random directions for the |
depth |
the data depth used for the lambda-values: |
jitter, jitter.amount
|
jittering to break ties, for example for ordered
categorical variables: |
plot |
logical: draw the chi-plot? |
verbose |
logical: report removed observations and jittered variables? |
... |
for |
x |
an object of class |
pc.level |
probability level for the guidelines. |
main |
title of the chi-plot. |
col1, col2
|
colours of the horizontal guidelines and of the vertical reference lines. |
pt |
point size of the text listing the groups. |
box |
logical: draw a box around the list of groups? |
cex |
size of the points; by default chosen from the sample size. |
mar |
plot margins, as in |
guide.option |
the horizontal guidelines: |
del |
tolerance used by |
Observations with missing values are removed. Each column is centred at its median and divided by its interquartile range.
For each observation , the generalized orthant is the set of
with if and
otherwise. With the proportion of the sample in this orthant, and
the proportions in the corresponding orthants for
the variables of each block, the chi-value is
where and
. Under independence of the blocks
the chi-values are approximately standard normal. The lambda-value of each
observation is its data depth, transformed by the empirical distribution
function of the depths.
The null distributions are estimated by permuting the rows of blocks
, keeping the rows within each block together.
sigP.chi is the proportion of permutations whose mean squared
chi-value is at least that of the data; sigP.lam is the p-value of a
two-sample Kolmogorov–Smirnov test comparing the data depths with those of
the permuted data.
The plot shows the variables in each group, the chi-plot, and, if
sigP.lam <= 1 - pc.level, a Q-Q plot of the data depths against their
permutation distribution.
With the same random number seed, chimult gives the same results as
the authors' original chimult.lam function with
fda.usc::mdepth.RP.
An object of class "chimult", a list with components
chi |
the chi-values of the data. |
chisq.actual |
the mean of the squared chi-values. |
chi.values.perm |
the chi-values of the permuted data sets. |
chisq.values.perm |
the mean squared chi-values of the permuted data sets. |
sigP.chi |
the permutation p-value of the mean squared chi-value. |
lambda.actual |
the data depths of the observations. |
lambda |
the lambda-values: the data depths transformed by their empirical distribution function. |
lambda.values.perm |
the data depths of the permuted data sets. |
sigP.lam |
the p-value of the Kolmogorov–Smirnov test for the depths. |
X |
the centred and scaled data. |
D, k
|
the block sizes and the number of blocks. |
depth, nperm, Nsim
|
the settings used. |
n.removed, rows
|
the number of observations removed because of missing
values, and the rows of |
jittered |
the names of jittered variables. |
call |
the call. |
Fisher, N. I. and Lee, A. J. (2026). Chi-plots for assessing multivariate dependence. Submitted to Journal of Computational and Graphical Statistics.
chipart to search over partitions;
chidepth; chiplot.
## Two independent blocks of two correlated normal variables set.seed(161225) n <- 200 z1 <- matrix(rnorm(2 * n), n) %*% chol(matrix(c(1, 0.5, 0.5, 1), 2)) z2 <- matrix(rnorm(2 * n), n) %*% chol(matrix(c(1, 0.7, 0.7, 1), 2)) Data <- data.frame(z1, z2) colnames(Data) <- paste0("Var.", 1:4) res <- chimult(Data, D = c(2, 2), nperm = 100) res plot(res) ## Dependence between the blocks Data$Var.3 <- Data$Var.3 + 0.8 * Data$Var.1 res2 <- chimult(Data, D = c(2, 2), nperm = 100) plot(res2, guide.option = 3) ## Five variables, mutually uncorrelated but with a hole in the data set.seed(280926) z <- matrix(rnorm(20000 * 5), ncol = 5) z <- z[sqrt(rowSums((z - 1)^2)) >= 2.5, ][1:200, ] res3 <- chimult(z, D = rep(1, 5), nperm = 500) plot(res3, guide.option = 4, pt = 10)## Two independent blocks of two correlated normal variables set.seed(161225) n <- 200 z1 <- matrix(rnorm(2 * n), n) %*% chol(matrix(c(1, 0.5, 0.5, 1), 2)) z2 <- matrix(rnorm(2 * n), n) %*% chol(matrix(c(1, 0.7, 0.7, 1), 2)) Data <- data.frame(z1, z2) colnames(Data) <- paste0("Var.", 1:4) res <- chimult(Data, D = c(2, 2), nperm = 100) res plot(res) ## Dependence between the blocks Data$Var.3 <- Data$Var.3 + 0.8 * Data$Var.1 res2 <- chimult(Data, D = c(2, 2), nperm = 100) plot(res2, guide.option = 3) ## Five variables, mutually uncorrelated but with a hole in the data set.seed(280926) z <- matrix(rnorm(20000 * 5), ncol = 5) z <- z[sqrt(rowSums((z - 1)^2)) >= 2.5, ][1:200, ] res3 <- chimult(z, D = rep(1, 5), nperm = 500) plot(res3, guide.option = 4, pt = 10)
A scatterplot matrix, like pairs, with the scatterplots below
the diagonal and the corresponding chi-plots above the diagonal.
chipairs(Data, labels = NULL, ctrl.lines = 2, jitter = NULL, jitter.amount = 0.25, cex.points = NULL, pch.type = 19, dot.col = "black", cex.label = NULL, cex.axis = 0.7, plgap = 0.2, axes = TRUE, main = NULL, ...)chipairs(Data, labels = NULL, ctrl.lines = 2, jitter = NULL, jitter.amount = 0.25, cex.points = NULL, pch.type = 19, dot.col = "black", cex.label = NULL, cex.axis = 0.7, plgap = 0.2, axes = TRUE, main = NULL, ...)
Data |
a numeric matrix or data frame ( |
labels |
variable names for the diagonal; by default the column names. |
ctrl.lines |
control lines on the chi-plots, as in |
jitter, jitter.amount
|
jittering of variables to break ties, as in
|
cex.points |
size of the points. By default it is chosen from the number of points and the number of panels. |
pch.type, dot.col
|
plotting symbol and colour of the points. |
cex.label |
size of the variable names on the diagonal. |
cex.axis |
size of the axis annotation. |
plgap |
space between panels, in lines of text. |
axes |
logical: draw axes on the outer panels? The scatterplots are annotated on the bottom and left, the chi-plots (on the scale -1 to 1) on the top and right. |
main |
an overall title. |
... |
further graphical parameters passed to |
As in pairs, the panel in row and column has
variable on the horizontal axis and variable on the vertical
axis. Each pair of variables uses all the observations for which both are
present. The chi-plot is symmetric in the two variables.
NULL, invisibly.
chipairs(geochem[, 1:5], main = "geochem") ## a smaller sample, with the points emphasised chipairs(geochem[1:300, c("Cr2O3", "MgO", "CaO", "Ni")], cex.points = 0.5)chipairs(geochem[, 1:5], main = "geochem") ## a smaller sample, with the points emphasised chipairs(geochem[1:300, c("Cr2O3", "MgO", "CaO", "Ni")], cex.points = 0.5)
chipart enumerates the partitions of the variables into two or
more blocks, runs chimult for each, and keeps the results for
partitions where either test statistic has p-value less than SigProb.
chi.partitions lists the partitions examined.
chipart(Data, kfix = 0, SigProb = 0.05, nperm = 100, Nsim = 100, depth = "RP", nblocks = NULL, nperm.screen = NULL, screen.level = 0.2, jitter = NULL, jitter.amount = 0.25, verbose = TRUE) chi.partitions(p, kfix = 0, nblocks = NULL) ## S3 method for class 'chipart' plot(x, which = NULL, ask = NULL, ...) ## S3 method for class 'chipart' print(x, ...)chipart(Data, kfix = 0, SigProb = 0.05, nperm = 100, Nsim = 100, depth = "RP", nblocks = NULL, nperm.screen = NULL, screen.level = 0.2, jitter = NULL, jitter.amount = 0.25, verbose = TRUE) chi.partitions(p, kfix = 0, nblocks = NULL) ## S3 method for class 'chipart' plot(x, which = NULL, ask = NULL, ...) ## S3 method for class 'chipart' print(x, ...)
Data |
a numeric matrix or data frame ( |
kfix |
if positive, the first |
SigProb |
the significance level used to select interesting partitions. |
nperm, Nsim, depth
|
as in |
nblocks |
if given, only partitions with these numbers of blocks are
examined; for example, |
nperm.screen |
if given, a smaller number of permutations used to screen
all partitions; partitions with either p-value at most |
screen.level |
the screening threshold used with |
jitter, jitter.amount
|
jittering to break ties, applied once to the
data before the search, as in |
verbose |
logical: report progress? |
p |
the number of variables. |
x |
an object of class |
which |
partition numbers to plot; by default all interesting partitions. |
ask |
logical: pause before each plot? By default, on interactive devices when there is more than one plot. |
... |
arguments passed to |
The number of partitions grows quickly with : the Bell numbers are 1,
2, 5, 15, 52, 203, 877, 4140, 21147, 115975, .... The options
kfix, nblocks, nperm.screen and a fast depth such as
depth = "MhD" reduce the computing time.
For each partition the variables are reordered so that the blocks are
contiguous, keeping the original order within each block, and
chimult is called. A partition is “interesting” if
sigP.chi or sigP.lam is less than SigProb. No allowance
is made for multiple testing.
chipart returns an object of class "chipart", a list with
components
summary |
a data frame with one row per partition: the partition number,
the groups, the number of blocks, |
results |
a list of |
partitions |
the partitions: a matrix with one column per partition giving the block number of each variable. |
variables, SigProb, kfix, nperm, nperm.screen, Nsim, depth, n, n.removed, call
|
details of the search. |
chi.partitions returns the matrix of partitions.
Fisher, N. I. and Lee, A. J. (2026). Chi-plots for assessing multivariate dependence. Submitted to Journal of Computational and Graphical Statistics.
## the partitions examined for 4 variables, and with the first kept fixed chi.partitions(4) chi.partitions(4, kfix = 1) ## four variables, of which the second and fourth are dependent set.seed(5) W <- matrix(rnorm(100 * 4), 100, 4) W[, 4] <- W[, 4] + W[, 2] colnames(W) <- paste0("V", 1:4) res <- chipart(W, nperm = 20, verbose = FALSE) res plot(res, which = 3) ## screening with fewer permutations res2 <- chipart(W, nperm = 200, nperm.screen = 20) res2$summary## the partitions examined for 4 variables, and with the first kept fixed chi.partitions(4) chi.partitions(4, kfix = 1) ## four variables, of which the second and fourth are dependent set.seed(5) W <- matrix(rnorm(100 * 4), 100, 4) W[, 4] <- W[, 4] + W[, 2] colnames(W) <- paste0("V", 1:4) res <- chipart(W, nperm = 20, verbose = FALSE) res plot(res, which = 3) ## screening with fewer permutations res2 <- chipart(W, nperm = 200, nperm.screen = 20) res2$summary
chivals calculates the chi- and lambda-values of Fisher and Switzer
(1985) for a bivariate sample; chiplot calculates them and draws the
chi-plot.
chiplot(x, y = NULL, ctrl.lines = 2, jitter = NULL, jitter.amount = 0.25, cex.points = NULL, pch.type = 19, dot.col = "black", main = "", cex.axis = 1, cex.lam = 1.2, plot = TRUE, ...) chivals(x, y = NULL, jitter = NULL, jitter.amount = 0.25) ## S3 method for class 'chiplot' plot(x, ctrl.lines = 2, cex.points = NULL, pch.type = 19, dot.col = "black", main = "", cex.axis = 1, cex.lam = 1.2, xlab = expression(lambda), ylab = expression(chi), col.ctrl = "red", lty.ctrl = 1, ...) ## S3 method for class 'chiplot' print(x, ...)chiplot(x, y = NULL, ctrl.lines = 2, jitter = NULL, jitter.amount = 0.25, cex.points = NULL, pch.type = 19, dot.col = "black", main = "", cex.axis = 1, cex.lam = 1.2, plot = TRUE, ...) chivals(x, y = NULL, jitter = NULL, jitter.amount = 0.25) ## S3 method for class 'chiplot' plot(x, ctrl.lines = 2, cex.points = NULL, pch.type = 19, dot.col = "black", main = "", cex.axis = 1, cex.lam = 1.2, xlab = expression(lambda), ylab = expression(chi), col.ctrl = "red", lty.ctrl = 1, ...) ## S3 method for class 'chiplot' print(x, ...)
x |
a numeric vector or ordered factor; or, if |
y |
a numeric vector or ordered factor of the same length as |
ctrl.lines |
control lines enclosing, on average, the given percentage of
points under independence: |
jitter |
which variables to jitter, to break ties (for example, for
ordered categorical variables): |
jitter.amount |
size of the jitter, as a fraction (less than 0.5) of the smallest gap between distinct values, so that the order of distinct values is preserved. |
cex.points |
size of the plotted points. The default |
pch.type, dot.col
|
plotting symbol and colour of the points. |
main |
title of the plot. |
cex.axis |
size of the axis annotation. |
cex.lam |
size of the axis labels |
plot |
logical: if |
xlab, ylab
|
axis labels. |
col.ctrl, lty.ctrl
|
colour and line type of the control lines. |
... |
further graphical parameters passed to |
Observations with a missing value in either variable are removed. For each
point , let , and be the
proportions of the other points with ,
, and both, respectively. Then
where and
. Points with
, at the edges of the data, are not
plotted. Under independence the points lie in a horizontal band about
, approximately the stated percentage of them between the
control lines at , with
= 1.54, 1.78 and 2.18 for 90%, 95% and 99%.
As in the original program, the sine transformation of the chi-values mentioned by Fisher and Switzer (1985) is not used. The chi- and lambda-values depend only on the ranks of the data.
An object of class "chiplot" (returned invisibly by chiplot): a
list with components
lambda, chi
|
the lambda- and chi-values of the plotted points; |
index |
the row numbers, in the original data, of the plotted points; |
n |
the number of complete pairs; |
npts |
the number of points plotted; |
n.missing |
the number of pairs removed because of missing values; |
names |
the names of the two variables. |
Fisher, N. I. and Switzer, P. (1985). Chi-plots for assessing dependence. Biometrika, 72, 253–265. doi:10.1093/biomet/72.2.253
Fisher, N. I. and Switzer, P. (2001). Graphical assessment of dependence: is a picture worth a hundred tests? The American Statistician, 55, 233–239. doi:10.1198/000313001317098248
chipairs for all pairs of variables;
chimult for blocks of variables.
set.seed(1) op <- par(mfrow = c(2, 2)) ## Independent normal variables x <- rnorm(100); y <- rnorm(100) chiplot(x, y, main = "Independence") ## Bivariate normal, correlation 0.7 y <- 0.7 * x + sqrt(1 - 0.7^2) * rnorm(100) chiplot(x, y, main = "Correlation 0.7") ## Non-monotone association: points near a half circle x <- rnorm(2000); y <- rnorm(2000) keep <- y >= 0 & abs(x^2 + y^2 - 1) <= 0.5 chiplot(x[keep][1:100], y[keep][1:100], main = "Half ring") ## Ordered categorical variable: jittered automatically u <- rnorm(100) grade <- cut(u + rnorm(100), c(-Inf, -1, 0, 1, Inf), ordered_result = TRUE) res <- chiplot(u, grade, main = "Ordered categories") par(op) resset.seed(1) op <- par(mfrow = c(2, 2)) ## Independent normal variables x <- rnorm(100); y <- rnorm(100) chiplot(x, y, main = "Independence") ## Bivariate normal, correlation 0.7 y <- 0.7 * x + sqrt(1 - 0.7^2) * rnorm(100) chiplot(x, y, main = "Correlation 0.7") ## Non-monotone association: points near a half circle x <- rnorm(2000); y <- rnorm(2000) keep <- y >= 0 & abs(x^2 + y^2 - 1) <= 0.5 chiplot(x[keep][1:100], y[keep][1:100], main = "Half ring") ## Ordered categorical variable: jittered automatically u <- rnorm(100) grade <- cut(u + rnorm(100), c(-Inf, -1, 0, 1, Inf), ordered_result = TRUE) res <- chiplot(u, grade, main = "Ordered categories") par(op) res
Measurements of 11 geochemical constituents on 1000 samples, used to
illustrate chipairs. The values are the ranks of the
measurements within each variable, with tied values given their average rank.
geochemgeochem
A data frame with 1000 rows and 11 numeric columns: TiO2,
Cr2O3, FeO, MnO, MgO, CaO, Ni,
Zn, Ga, Y and Zr.
ValueMetrics Australia. Included with the permission of the copyright holder.
summary(geochem[, 1:4]) chipairs(geochem[, c("Cr2O3", "MgO", "Ni")])summary(geochem[, 1:4]) chipairs(geochem[, c("Cr2O3", "MgO", "Ni")])