Runs a principal component analysis (PCA) on the numeric columns of
data and returns loadings, scores, and the share of variance each
retained component explains. PCA finds the directions of greatest spread
in the data; use it to see how much of that spread a few components carry
and which columns drive them.
Usage
fs_pca(
data,
num_pc = NULL,
scale_data = TRUE,
center_data = TRUE,
label_col = NULL,
plot = FALSE,
verbose = FALSE
)Arguments
- data
A data.frame or data.table with at least two rows and at least one numeric column. Column names must be unique and non-empty. A non-numeric (label) column named like a retained score column (
PC1,PC2, ...) would be hidden by the scores inpca_df, so that is an error. Columns in extreme units (beyond about1e154or below1e-154) are kept, because the zero-variance check is relative to each column's magnitude; withscale_data = FALSEtheir variance cannot be represented and they are an error.- num_pc
Number of principal components to retain: a whole number
>= 1, orNULL(the default) formin(2, max_possible).max_possibleismin(nrow - 1, ncol)of the usable numeric data, after incomplete rows and zero-variance columns are dropped. A larger explicit value is an error.- scale_data
Logical; scale numeric columns to unit variance. Default
TRUE.scale_data = TRUEwithcenter_data = FALSEis an error, becausestats::prcomp()andbigstatsr::big_scale()handle uncentered scaling differently (the latter ignores it), and the result would depend on which engine the data size selected.- center_data
Logical; center numeric columns. Default
TRUE.- label_col
Optional single string naming a column of
dataused to label and color points. It is left out of the PCA features and carried intopca_df. DefaultNULL: no labels, and therefore no plot.- plot
Logical; if
TRUE, print the PC1 vs PC2 scatterplot. DefaultFALSE, whether or notlabel_colis supplied. The plot object is returned in$ploteither way whenever one can be built (see Details).- verbose
Logical; print progress messages naming the non-numeric columns held back as labels, the engine chosen, and any fallback from 'bigstatsr'. Default
FALSE.
Value
A plain list, not an fs_result: fs_pca() reduces dimensions
rather than selecting features, so it has no selected set to report.
The components are:
pc_loadings: numeric matrix of variable loadings, one row per retained numeric feature (named) and one column per retained component (PC1,PC2, ...).pc_scores: numeric matrix of observation scores, one row per kept observation and one column per retained component.var_explained: numeric vector of lengthnum_pc, the proportion of total variance carried by each retained component (see Details).pca_df: data.table of the scores with the label columns (every non-numeric column, pluslabel_col) bound alongside, restricted to the kept rows.meta: list withnumeric_cols(the features actually decomposed),rows_kept(logical vector over the rows ofdata),n_rows_used, andn_cols_used.plot: the ggplot object, present only when a plot could be built (alabel_colwas supplied, at least two components were retained, and 'ggplot2' is installed). Test for it with"plot" %in% names(res).
Details
PCA is unsupervised dimensionality reduction, not feature selection. Every component is a linear combination of all the retained numeric columns, chosen without reference to any outcome. A PCA therefore does not shorten the list of variables you have to measure, and the leading components are the highest-variance directions, which need not be the ones related to a response.
Before the decomposition:
Non-numeric columns (character, factor, logical, Date, ...) are left out of the features and carried along as labels. An explicit
label_col, numeric or not, is also left out of the features.Rows with a missing value in any numeric feature column are dropped. An infinite value in a feature column is an error.
Zero-variance columns are dropped with a warning. A column counts as zero-variance when its standard deviation is at most
1000 * .Machine$double.eps(about 2e-13) times its largest absolute value. A column that is constant up to floating-point round-off is therefore dropped, while small-unit or large-offset columns (timestamps, say) are kept.
Data with fewer than 1e7 cells is decomposed with stats::prcomp(). From
1e7 cells up, if the suggested package 'bigstatsr' is installed,
bigstatsr::big_SVD() runs on a temporary file-backed matrix (the backing
file is deleted when the call returns) and can compute at most one fewer
component than there are usable columns. Without 'bigstatsr' the call falls
back to prcomp(), and says so only when verbose = TRUE.
var_explained is the proportion of total variance explained by each
retained component under both engines. It sums to 1 only when every
available component is retained; with the default two components it sums
to the fraction those two capture. prcomp() computes every component, so
the total is the sum of all eigenvalues. big_SVD() computes only the top
num_pc singular values, so the total is recovered separately from the
column statistics of the (implicitly centered and scaled) matrix. Under
either engine, read the entries as shares of the whole, never as shares of
the components that happened to be computed.
plot controls printing only, never construction. Whenever a plot can be
built (a label_col was supplied, at least two components were retained,
and the suggested package 'ggplot2' is installed), the ggplot object is
returned in $plot, whatever the value of plot. plot = TRUE also
prints it and makes 'ggplot2' required: its absence is then an error rather
than a missing $plot. plot = TRUE without a label_col, or with fewer
than two retained components, warns and skips the plot.
See also
The Dimensionality reduction guide, which runs this function on a worked example.
Examples
res <- fs_pca(mtcars, num_pc = 2, label_col = "cyl")
res$var_explained
#> [1] 0.5697822 0.2644102
head(res$pca_df)
#> PC1 PC2 cyl
#> <num> <num> <num>
#> 1: -0.68400200 -1.6918186 6
#> 2: -0.64964186 -1.5108421 6
#> 3: -2.45446503 0.1707165 4
#> 4: -0.25539550 2.3333568 6
#> 5: 1.68827810 0.7361403 8
#> 6: 0.02536833 2.7401361 6
if (requireNamespace("ggplot2", quietly = TRUE)) {
# plot = FALSE (the default) draws nothing, but $plot is built anyway
res <- fs_pca(iris, label_col = "Species", verbose = TRUE)
print(inherits(res$plot, "ggplot"))
# plot = TRUE draws it as well
res <- fs_pca(iris, label_col = "Species", plot = TRUE)
}
#> Excluding 1 non-numeric column(s) from the PCA features (kept as label candidates): Species
#> Using prcomp for PCA computation.
#> [1] TRUE