From 7d566c4a2216aa9de5d188597e532fff27fff6a1 Mon Sep 17 00:00:00 2001 From: nfrerebeau Date: Mon, 11 Mar 2024 18:16:08 +0100 Subject: [PATCH] Allow to highlight supplementary qualitative variables --- NEWS.md | 3 + R/AllGenerics.R | 15 +- R/dimensio-internal.R | 19 +- R/mutators.R | 6 +- R/viz_ellipse.R | 8 +- R/viz_hull.R | 8 +- R/wrap_ellipses.R | 7 +- R/wrap_hull.R | 3 +- inst/examples/ex-envelopes.R | 15 ++ inst/examples/ex-wrap.R | 16 +- .../_tinysnapshot/PCA_ind_highlight_quali.svg | 228 ++++++++++++++++++ inst/tinytest/test_plot_pca.R | 7 +- man/prepare.Rd | 6 +- man/viz_individuals.Rd | 6 +- man/viz_points.Rd | 6 +- man/viz_variables.Rd | 6 +- man/viz_wrap.Rd | 24 +- man/wrap.Rd | 19 +- 18 files changed, 321 insertions(+), 81 deletions(-) create mode 100644 inst/examples/ex-envelopes.R create mode 100644 inst/tinytest/_tinysnapshot/PCA_ind_highlight_quali.svg diff --git a/NEWS.md b/NEWS.md index 9907b4f..f230170 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,9 @@ ## Bugfixes & changes * The default number of labeled points can now be changed in `viz_individuals()`, `viz_row()`, `viz_variables()` and `viz_columns()`. +## Enhancements +* Allow to highlight supplementary qualitative variables in `viz_individuals()`, `viz_row()`, `viz_variables()` and `viz_columns()`. + # dimensio 0.6.0 ## New classes and methods * Add `predict()` method for MCA. diff --git a/R/AllGenerics.R b/R/AllGenerics.R index 322a712..475fca9 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -699,7 +699,8 @@ setGeneric( #' indicates variables/columns. #' @param axes A length-two [`numeric`] vector giving the dimensions #' for which to compute results. -#' @param group A vector specifying the group an observation belongs to. +#' @param group A vector specifying the group an observation belongs to, or a +#' single `character` string giving the name of a categorical variable. #' @param level A [`numeric`] vector specifying the confidence/tolerance level. #' @param ... Currently not used. #' @return @@ -738,21 +739,13 @@ setGeneric( #' Plot Envelopes #' -#' @param x An object from which to wrap observations (a [`CA-class`], -#' [`MCA-class`] or [`PCA-class`] object). -#' @param margin A length-one [`numeric`] vector giving the subscript which the -#' data will be returned: `1` indicates individuals/rows (the default), `2` -#' indicates variables/columns. -#' @param axes A length-two [`numeric`] vector giving the dimensions -#' for which to compute results. -#' @param group A vector specifying the group an observation belongs to. -#' @param level A [`numeric`] vector specifying the confidence/tolerance level. +#' @inheritParams wrap #' @param ... Further [graphical parameters][graphics::par] to be passed to #' [graphics::polygon()]. #' @return #' `viz_*()`is called for its side-effects: it results in a graphic being #' displayed. Invisibly returns `x`. -#' @example inst/examples/ex-wrap.R +#' @example inst/examples/ex-envelopes.R #' @author N. Frerebeau #' @docType methods #' @family plot methods diff --git a/R/dimensio-internal.R b/R/dimensio-internal.R index f88898a..7f2e356 100644 --- a/R/dimensio-internal.R +++ b/R/dimensio-internal.R @@ -119,9 +119,9 @@ drop_variable <- function(x, f, negate = FALSE, sup = NULL, extra = NULL, #' plotted? #' @param highlight A vector specifying the information to be highlighted. #' If `NULL` (the default), no highlighting is applied. If a single `character` -#' string is passed, it must be one of "`observation`", "`mass`", "`sum`", -#' "`contribution`" or "`cos2`" (see [`augment()`]). Any unambiguous substring -#' can be given. +#' string is passed, it must be the name of a categorical variable, or one of +#' "`observation`", "`mass`", "`sum`", "`contribution`" or "`cos2`" +#' (see [`augment()`]). # It will only be mapped if at least one [graphical parameters][graphics::par] # is explicitly specified (see examples). #' @param col The colors for lines and points. @@ -164,9 +164,16 @@ prepare <- function(x, margin, ..., axes = c(1, 2), active = TRUE, data$observation <- "active" data$observation[data$supplementary] <- "suppl." if (length(highlight) == 1) { - choices <- c("mass", "sum", "contribution", "cos2", "observation") - highlight <- match.arg(highlight, choices = choices, several.ok = FALSE) - highlight <- data[[highlight]] + high <- NULL + if (has_extra(x)) { + high <- get_extra(x)[[highlight]] %||% data[[highlight]] + } + if (is.null(high)) { + choices <- c("mass", "sum", "contribution", "cos2", "observation") + highlight <- match.arg(highlight, choices = choices, several.ok = FALSE) + high <- data[[highlight]] + } + highlight <- high } if (length(highlight) > 1) arkhe::assert_length(highlight, n) diff --git a/R/mutators.R b/R/mutators.R index 2909dfd..dfb5b3f 100644 --- a/R/mutators.R +++ b/R/mutators.R @@ -22,7 +22,11 @@ get_order <- function(x, margin = 1) { ord } get_extra <- function(x) { - x@extra + as.data.frame(x@extra) +} +has_extra <- function(x) { + extra <- get_extra(x) + NROW(extra) > 0 && NCOL(extra) > 0 } `set_extra<-` <- function(x, value) { x@extra <- value diff --git a/R/viz_ellipse.R b/R/viz_ellipse.R index 478cf18..e9dddcb 100644 --- a/R/viz_ellipse.R +++ b/R/viz_ellipse.R @@ -68,10 +68,10 @@ setMethod( n <- length(ell) ## Graphical parameters - if (length(border) != n) border <- rep(border, length.out = n) - if (length(col) != n) col <- rep(col, length.out = n) - if (length(lty) != n) lty <- rep(lty, length.out = n) - if (length(lwd) != n) lwd <- rep(lwd, length.out = n) + if (length(border) == 1) border <- rep(border, length.out = n) + if (length(col) == 1) col <- rep(col, length.out = n) + if (length(lty) == 1) lty <- rep(lty, length.out = n) + if (length(lwd) == 1) lwd <- rep(lwd, length.out = n) for (i in seq_along(ell)) { lvl <- ell[[i]] diff --git a/R/viz_hull.R b/R/viz_hull.R index a7652d5..a299717 100644 --- a/R/viz_hull.R +++ b/R/viz_hull.R @@ -17,10 +17,10 @@ setMethod( col <- list(...)$col %||% NA lty <- list(...)$lty %||% graphics::par("lty") lwd <- list(...)$lwd %||% graphics::par("lwd") - if (length(border) != n) border <- rep(border, length.out = n) - if (length(col) != n) col <- rep(col, length.out = n) - if (length(lty) != n) lty <- rep(lty, length.out = n) - if (length(lwd) != n) lwd <- rep(lwd, length.out = n) + if (length(border) == 1) border <- rep(border, length.out = n) + if (length(col) == 1) col <- rep(col, length.out = n) + if (length(lty) == 1) lty <- rep(lty, length.out = n) + if (length(lwd) == 1) lwd <- rep(lwd, length.out = n) for (i in seq_along(hull)) { graphics::polygon(x = hull[[i]], border = border[i], diff --git a/R/wrap_ellipses.R b/R/wrap_ellipses.R index 89af5a3..adf1e6e 100644 --- a/R/wrap_ellipses.R +++ b/R/wrap_ellipses.R @@ -17,7 +17,8 @@ setMethod( ## Add groups, if any k <- get_order(x, margin = margin) - if (!is.null(group)) { + if (length(group) == 1) group <- get_extra(x)[[group]] + if (length(group) > 0) { arkhe::assert_length(group, nrow(data)) group <- group[k] } else if (has_groups(x, margin = margin)) { @@ -25,7 +26,6 @@ setMethod( } else { group <- rep("", length(k)) } - group <- as.character(group) ## Compute ellipse data <- split(data, f = group) @@ -57,7 +57,8 @@ setMethod( ## Add groups, if any k <- get_order(x, margin = margin) - if (!is.null(group)) { + if (length(group) == 1) group <- get_extra(x)[[group]] + if (length(group) > 0) { arkhe::assert_length(group, nrow(data)) group <- group[k] } else if (has_groups(x, margin = margin)) { diff --git a/R/wrap_hull.R b/R/wrap_hull.R index b14d789..6a8d98e 100644 --- a/R/wrap_hull.R +++ b/R/wrap_hull.R @@ -15,7 +15,8 @@ setMethod( ## Add groups, if any k <- get_order(x, margin = margin) - if (!is.null(group)) { + if (length(group) == 1) group <- get_extra(x)[[group]] + if (length(group) > 0) { arkhe::assert_length(group, nrow(data)) group <- group[k] } else if (has_groups(x, margin = margin)) { diff --git a/inst/examples/ex-envelopes.R b/inst/examples/ex-envelopes.R new file mode 100644 index 0000000..d8a6b8d --- /dev/null +++ b/inst/examples/ex-envelopes.R @@ -0,0 +1,15 @@ +## Load data +data("iris") + +## Compute principal components analysis +X <- pca(iris, scale = TRUE, sup_quali = "Species") + +## Plot with convex hulls +col <- c("#004488", "#DDAA33", "#BB5566") +viz_rows(X, highlight = "Species", col = col) +viz_hull(X, group = "Species", border = col) + +## Plot with tolerance ellipses +col <- c("#004488", "#DDAA33", "#BB5566") +viz_rows(X, highlight = "Species", col = col) +viz_tolerance(X, group = "Species", border = col) diff --git a/inst/examples/ex-wrap.R b/inst/examples/ex-wrap.R index ddb280e..98edd64 100644 --- a/inst/examples/ex-wrap.R +++ b/inst/examples/ex-wrap.R @@ -2,19 +2,13 @@ data("iris") ## Compute principal components analysis -X <- pca(iris, scale = TRUE) - -## Convex hull coordinates -hulls <- wrap_hull(X, margin = 1, group = iris$Species) +X <- pca(iris, scale = TRUE, sup_quali = "Species") ## Confidence ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, - level = c(0.68, 0.95)) +conf <- wrap_confidence(X, margin = 1, group = "Species", level = c(0.68, 0.95)) ## Tolerance ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, level = 0.95) +conf <- wrap_confidence(X, margin = 1, group = "Species", level = 0.95) -## Plot with convex hulls -col <- c("#004488", "#DDAA33", "#BB5566") -viz_rows(X, highlight = iris$Species, col = col) -viz_hull(X, group = iris$Species, border = col) +## Convex hull coordinates +hulls <- wrap_hull(X, margin = 1, group = "Species") diff --git a/inst/tinytest/_tinysnapshot/PCA_ind_highlight_quali.svg b/inst/tinytest/_tinysnapshot/PCA_ind_highlight_quali.svg new file mode 100644 index 0000000..d2ab7ce --- /dev/null +++ b/inst/tinytest/_tinysnapshot/PCA_ind_highlight_quali.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-3 +-2 +-1 +0 +1 +2 +3 + + + + + + + + +-3 +-2 +-1 +0 +1 +2 +3 + +F1 (73.3%) +F2 (23%) + + + + + + +setosa +versicolor +virginica + + diff --git a/inst/tinytest/test_plot_pca.R b/inst/tinytest/test_plot_pca.R index eecfb96..7490d39 100644 --- a/inst/tinytest/test_plot_pca.R +++ b/inst/tinytest/test_plot_pca.R @@ -10,7 +10,7 @@ if (at_home()) { # PCA - Plot coordinates ===================================================== sup_ind <- seq(from = 1, to = 150, by = 5) - res <- pca(iris, sup_row = sup_ind, sup_col = 4) + res <- pca(iris, sup_row = sup_ind, sup_col = 4, sup_quali = 5) for (i in c(TRUE, FALSE)) { for (j in c(TRUE, FALSE)) { @@ -32,9 +32,12 @@ if (at_home()) { } # PCA - Plot aesthetics ====================================================== - res <- pca(iris) + res <- pca(iris, sup_quali = 5) # Individuals + plot_ind_quali <- function() viz_individuals(res, highlight = "Species") + expect_snapshot_plot(plot_ind_quali, "PCA_ind_highlight_quali") + plot_ind_cos2 <- function() viz_individuals(res, highlight = "cos2") expect_snapshot_plot(plot_ind_cos2, "PCA_ind_highlight_cos2") diff --git a/man/prepare.Rd b/man/prepare.Rd index d36d57c..34eff0f 100644 --- a/man/prepare.Rd +++ b/man/prepare.Rd @@ -45,9 +45,9 @@ returned? If \code{FALSE}, standard coordinates are returned.} \item{highlight}{A vector specifying the information to be highlighted. If \code{NULL} (the default), no highlighting is applied. If a single \code{character} -string is passed, it must be one of "\code{observation}", "\code{mass}", "\code{sum}", -"\code{contribution}" or "\code{cos2}" (see \code{\link[=augment]{augment()}}). Any unambiguous substring -can be given.} +string is passed, it must be the name of a categorical variable, or one of +"\code{observation}", "\code{mass}", "\code{sum}", "\code{contribution}" or "\code{cos2}" +(see \code{\link[=augment]{augment()}}).} \item{col}{The colors for lines and points.} diff --git a/man/viz_individuals.Rd b/man/viz_individuals.Rd index 7a5ee2b..2a993c3 100644 --- a/man/viz_individuals.Rd +++ b/man/viz_individuals.Rd @@ -76,9 +76,9 @@ Only used if \code{labels} is \code{TRUE}.} \item{highlight}{A vector specifying the information to be highlighted. If \code{NULL} (the default), no highlighting is applied. If a single \code{character} -string is passed, it must be one of "\code{observation}", "\code{mass}", "\code{sum}", -"\code{contribution}" or "\code{cos2}" (see \code{\link[=augment]{augment()}}). Any unambiguous substring -can be given.} +string is passed, it must be the name of a categorical variable, or one of +"\code{observation}", "\code{mass}", "\code{sum}", "\code{contribution}" or "\code{cos2}" +(see \code{\link[=augment]{augment()}}).} \item{xlim}{A length-two \code{\link{numeric}} vector giving the x limits of the plot. The default value, \code{NULL}, indicates that the range of the diff --git a/man/viz_points.Rd b/man/viz_points.Rd index 3174e4d..4986695 100644 --- a/man/viz_points.Rd +++ b/man/viz_points.Rd @@ -54,9 +54,9 @@ Only used if \code{labels} is \code{TRUE}.} \item{highlight}{A vector specifying the information to be highlighted. If \code{NULL} (the default), no highlighting is applied. If a single \code{character} -string is passed, it must be one of "\code{observation}", "\code{mass}", "\code{sum}", -"\code{contribution}" or "\code{cos2}" (see \code{\link[=augment]{augment()}}). Any unambiguous substring -can be given.} +string is passed, it must be the name of a categorical variable, or one of +"\code{observation}", "\code{mass}", "\code{sum}", "\code{contribution}" or "\code{cos2}" +(see \code{\link[=augment]{augment()}}).} \item{xlim}{A length-two \code{\link{numeric}} vector giving the x limits of the plot. The default value, \code{NULL}, indicates that the range of the diff --git a/man/viz_variables.Rd b/man/viz_variables.Rd index 46efbaf..8a886c0 100644 --- a/man/viz_variables.Rd +++ b/man/viz_variables.Rd @@ -98,9 +98,9 @@ Only used if \code{labels} is \code{TRUE}.} \item{highlight}{A vector specifying the information to be highlighted. If \code{NULL} (the default), no highlighting is applied. If a single \code{character} -string is passed, it must be one of "\code{observation}", "\code{mass}", "\code{sum}", -"\code{contribution}" or "\code{cos2}" (see \code{\link[=augment]{augment()}}). Any unambiguous substring -can be given.} +string is passed, it must be the name of a categorical variable, or one of +"\code{observation}", "\code{mass}", "\code{sum}", "\code{contribution}" or "\code{cos2}" +(see \code{\link[=augment]{augment()}}).} \item{xlim}{A length-two \code{\link{numeric}} vector giving the x limits of the plot. The default value, \code{NULL}, indicates that the range of the diff --git a/man/viz_wrap.Rd b/man/viz_wrap.Rd index d6ee692..49afa8a 100644 --- a/man/viz_wrap.Rd +++ b/man/viz_wrap.Rd @@ -49,7 +49,8 @@ indicates variables/columns.} \item{axes}{A length-two \code{\link{numeric}} vector giving the dimensions for which to compute results.} -\item{group}{A vector specifying the group an observation belongs to.} +\item{group}{A vector specifying the group an observation belongs to, or a +single \code{character} string giving the name of a categorical variable.} \item{level}{A \code{\link{numeric}} vector specifying the confidence/tolerance level.} } @@ -65,22 +66,17 @@ Plot Envelopes data("iris") ## Compute principal components analysis -X <- pca(iris, scale = TRUE) - -## Convex hull coordinates -hulls <- wrap_hull(X, margin = 1, group = iris$Species) - -## Confidence ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, - level = c(0.68, 0.95)) - -## Tolerance ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, level = 0.95) +X <- pca(iris, scale = TRUE, sup_quali = "Species") ## Plot with convex hulls col <- c("#004488", "#DDAA33", "#BB5566") -viz_rows(X, highlight = iris$Species, col = col) -viz_hull(X, group = iris$Species, border = col) +viz_rows(X, highlight = "Species", col = col) +viz_hull(X, group = "Species", border = col) + +## Plot with tolerance ellipses +col <- c("#004488", "#DDAA33", "#BB5566") +viz_rows(X, highlight = "Species", col = col) +viz_tolerance(X, group = "Species", border = col) } \seealso{ Other plot methods: diff --git a/man/wrap.Rd b/man/wrap.Rd index 6ca789e..41a3194 100644 --- a/man/wrap.Rd +++ b/man/wrap.Rd @@ -39,7 +39,8 @@ indicates variables/columns.} \item{axes}{A length-two \code{\link{numeric}} vector giving the dimensions for which to compute results.} -\item{group}{A vector specifying the group an observation belongs to.} +\item{group}{A vector specifying the group an observation belongs to, or a +single \code{character} string giving the name of a categorical variable.} \item{level}{A \code{\link{numeric}} vector specifying the confidence/tolerance level.} } @@ -61,22 +62,16 @@ belongs to. data("iris") ## Compute principal components analysis -X <- pca(iris, scale = TRUE) - -## Convex hull coordinates -hulls <- wrap_hull(X, margin = 1, group = iris$Species) +X <- pca(iris, scale = TRUE, sup_quali = "Species") ## Confidence ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, - level = c(0.68, 0.95)) +conf <- wrap_confidence(X, margin = 1, group = "Species", level = c(0.68, 0.95)) ## Tolerance ellipse coordinates -conf <- wrap_confidence(X, margin = 1, group = iris$Species, level = 0.95) +conf <- wrap_confidence(X, margin = 1, group = "Species", level = 0.95) -## Plot with convex hulls -col <- c("#004488", "#DDAA33", "#BB5566") -viz_rows(X, highlight = iris$Species, col = col) -viz_hull(X, group = iris$Species, border = col) +## Convex hull coordinates +hulls <- wrap_hull(X, margin = 1, group = "Species") } \seealso{ Other plot methods: