Changes in version 0.8.3 (2026-10-15) New features - cellpca(): robust PCA by casewise and cellwise weighting (cellPCA, Centofanti, Hubert and Rousseeuw). It handles outlying observations, outlying cells and missing values by minimizing a single objective, with weights between 0 and 1 for every cell and observation. The iteratively reweighted least squares and the predictions of new data are computed in C++; the results agree with the reference code of the authors to numerical precision. predict(), plot_outlier_map() (enhanced outlier map), plot_cell_map() and flagged_regions() accept its fits, and step_cellpca() is its recipe step. Breaking changes - The detection of deviating cells (DDC) behind macropca() and cellpca() now follows the DDC algorithm of the cellWise package: 1-step M estimators for the standardization and the residual scales, the robust Gnanadesikan-Kettenring correlations for the neighbors (wrapped correlations, found exactly, for more than 750 variables), robust slopes refined by least squares, each cell included in its own prediction, and the standalone variables. It flags the same cells as cellWise, and more of the outlying cells with far fewer false flags than before. - macropca() now follows the MacroPCA algorithm of Hubert, Rousseeuw and Van den Bossche (2019), built on ROBPCA, as implemented in cellWise, whose results it reproduces (with scale = FALSE): projection pursuit on the data where only the observations with the fewest flagged cells are cell-imputed, iterative subspace estimation on a fixed set of observations, one reweighting step on the orthogonal distances, concentration steps and the deterministic MCD within the subspace, and distances from the data with only the missing cells imputed. New data are analyzed as by MacroPCApredict (with the DDC model of the fit). Its results change. tol is now the largest angle between successive subspaces as a fraction of a right angle (default 0.005, as in the paper), the cut-offs of the score and orthogonal distances are at the 99% level as in the paper, the residual scales are 1-step M scales, and the fit returns the observations set aside by DDC (flagged_rows). Bug fixes - baseline_lsp() uses the improved modified polynomial fit (IModPoly) of Zhao et al. (2007) by default (method = "imodpoly"). The modified polynomial fit of Lieber and Mahadevan-Jansen (2003), still available with method = "modpoly", is pulled up by strong lines, and its baseline could then fall far below the background at the ends of the spectrum (for example above 290 nm in the 240-300 nm window of the forageLIBS spectra). The default max.iter is now 100. Dependencies - ggsci, prospectr and XICOR move from Imports to Suggests. generalized_boxplot() uses the default ggplot2 palette when ggsci is not installed, and correlation(method = "chatterjee") asks for XICOR. y_gradient_glsw() computes its Savitzky-Golay derivatives with the package's own filter (same results). - mt is no longer suggested. - Every step_*() function checks that recipes is installed. Documentation - Examples for the help pages that had none (the step_*() functions, predict() methods, reject_shots(), pds(), robust_bcyj(), umad(), the wavelength calibration helpers and the tuning parameters). - The rospca() example runs on a spectral window, and the plot_loadings() example that queries the NIST database is run with \donttest and try(). - The package description covers the whole workflow, with references. - Non-ASCII mathematical symbols removed from the help pages. Changes in version 0.8.1 Bug fixes - The Jackson-Mudholkar limit of the Q residuals (q_residuals(), and the Q limits of plot_influence()) was wrong when its exponent h0 is close to zero or negative, which happens for spectra (a few large eigenvalues of the components left out, then a long tail of small ones): it fell below the mean of Q, so that nearly every sample was flagged. For example, with 3 components on the forageLIBS spectra, the 99% limit was 1.8e8 instead of 2.0e9, and 355 of the 368 spectra were flagged. The sign of h0 is now kept, as its derivation requires, and the limit as h0 tends to zero is used near zero. Changes in version 0.8.0 Breaking changes - opls() has the arguments of the other orthogonalization methods: opls(x, y, ncomp = NULL, center = TRUE, scale = FALSE, crossval = 7, permutation = 0). ncomp (the number of orthogonal components, NULL for the automatic choice) replaces ncomp.ortho, and the logical center and scale replace the character scale; for Pareto scaling, apply pareto_scale() to x first (or step_pareto_scale() before step_opls()), which gives the same filter. The former arguments still work, with a deprecation warning. The permutation test is now skipped by default (permutation = 0), as it refits the model as many times. - projected_osc()'s ncomp is now the number of orthogonal components removed, as in opls(), osc() and direct_osc(), instead of the number of PLS components (one more): replace ncomp = k + 1 with ncomp = k. The default is 4 (formerly 5, the same filter). step_projected_osc() is unchanged. - macropca() and robust_bcyj() (and step_macropca(), step_robust_bcyj()) are implemented natively, after the published algorithms, and no longer depend on cellWise, which moves to Suggests. - macropca() detects the deviating cells with DDC (Rousseeuw and Van den Bossche, 2018), whose neighbor search and cell predictions are computed in C++ by blocks of variables: on the 368 x 7152 forageLIBS spectra, DDC takes about 5 s instead of 13 s, and macropca() about 12 s instead of 16 s. The results are close to, but not the same as, those of cellWise::MacroPCA() (orthogonal distances correlated at 0.99 on forageLIBS, nearly the same subspace); the score and orthogonal distance cut-offs are those of robpca(). In simulations with known rowwise and cellwise outliers and missing values, it estimates the PCA subspace more accurately than cellWise::MacroPCA(), imputes the missing values as accurately, and flags far fewer regular observations as outliers (cellWise compares the distances computed with the outlying cells to a cut-off estimated with them imputed). Its arguments scale, ndir, maxiter and tol replace the ... passed to cellWise::MacroPCA(), the result has no fit element any more, and imputed holds the data with the missing values imputed. - robust_bcyj() fits the transformations of Raymaekers and Rousseeuw (2021), with nearly the same parameters as cellWise::transfo(), and leaves the variables that cannot be transformed unchanged (method "none"). - boltzmann_plot() and saha_boltzmann_plot() are renamed boltzmann() and saha_boltzmann(), so that they are not confused with plot_boltzmann(), which draws their result. The old names still work, with a deprecation warning. - In dmodx(), the nominal degrees of freedom (K - k) are now selected with df = "nominal". - The soilLIBS data set is removed, to keep the package data under the 5 MB CRAN guideline: data(soilLIBS) no longer works. The examples use forageLIBS instead; the example of reject_shots(), which relied on the soil replicates, is removed. The raw data and the script that built it remain in data-raw/ of the source repository. New features - som() fits a self-organizing map of spectra (batch SOM, in C++), which maps similar spectra onto the same or neighboring units of a hexagonal or rectangular grid and follows nonlinear relations (matrix effects, self-absorption, plasma changes) that PCA can miss. It starts from the plane of the first two principal components (deterministic training), chooses the grid from the number of spectra, and reports the quantization and topographic errors. robust = TRUE down-weights outlying spectra by Huber weights of their quantization errors. predict() places new spectra on their best-matching unit and flags as novel those beyond a robust cut-off of the training quantization errors. plot_som() draws the counts, the U-matrix, the quantization errors, the component planes (one map per wavelength or line), the spectra on the map and the prototype spectra, and som_stability() measures how consistently the spectra keep their neighbors over resampled fits. On the 368 x 7152 forageLIBS spectra, a map takes about 2 s. Plots - plot_spectra() has an offset argument, to shift successive spectra vertically and horizontally (offset = c(horizontal, vertical), or a single number for a vertical offset), for example to stack them. - plot_embedding() recognizes UMAP maps (axes named UMAP1, UMAP2, ..., as by embed::step_umap()): it leaves out the lines through the origin, whose position means nothing on such a map, and ignores hotelling ("all" or "group"), with a warning, since its T-squared limits assume linear scores. On other maps, the lines through the origin are light grey instead of black. - correlation() takes several responses (var = c(K, Ca), or dplyr::all_of(minerals)), each with its own observations, so that a response with many missing values does not reduce the data of the others. The result gains an outcome column, and plot = TRUE draws a heatmap of the responses against the wavelengths (or the other variables), on a fixed scale from -1 to 1. Pearson and Spearman correlations of spectra are computed in one step: the 12 minerals of forageLIBS against its 7152 channels take under a second. With cluster = TRUE, the rows are ordered by a hierarchical clustering of the responses on their correlation profiles (1 - r, average linkage), so that responses whose correlations rise and fall at the same wavelengths are adjacent, with the dendrogram on the left. - Plot titles are bold, and a title longer than 45 characters is split at a natural break (": ", " (", " - " or ", ") into a shorter title and a subtitle, for example plot_calibration()'s "Linear calibration" above its R², LOD and LOQ. This applies to every plot with a title, including the interactive (plotly) ones and plot_cell_map(). plot_fit() now shows its title as a title rather than a subtitle. Documentation - The examples use the forageLIBS spectra and mineral contents instead of simulated data, wherever the function applies to them (some taken from the vignettes). Examples of pure helper functions (line profiles, plasma criteria, tuning parameters) keep simple inputs, and the examples of pds() and step_reject_shots(), for which forageLIBS has no suitable data, are removed. plot_influence() gains an example. - Every example calls dplyr functions with dplyr::, so that it runs without dplyr attached. - The vignettes are reorganized into the four stages of a LIBS analysis, all on the forageLIBS spectra: fitting emission lines (vignette("peak-fitting"), which replaces "line-fitting"), preprocessing with recipe steps (vignette("preprocessing")), calibration curves and figures of merit (vignette("calibration")) and plasma diagnostics (vignette("plasma-diagnostics")). The orthogonalization vignette is folded into the preprocessing one. - The README is shorter, with an overview of the functions by task. Changes in version 0.7.0 Performance - Column medians are computed in C++ (reject_shots(), msc() and emsc() with robust = TRUE, center(method = "median"), robpca()): reject_shots() is about 20 times faster on 400 spectra of 7152 channels. - q_residuals() and dmodx() compute the residuals of the calibration samples from the scores of the components left out, without reconstructing the data (about 100 times faster), and line_intensities() measures all the spectra of a line at once (about 3 times faster). The results are unchanged. Breaking changes - opls() is implemented natively and no longer needs the Bioconductor package ropls. It reproduces ropls::opls() (scores, loadings, R2X, R2Y, Q2, RMSEE, VIP and permutation p-values), but returns an object of class specproc_opls instead of a list holding the ropls model: the model element is removed, and the model has new elements correction (the OPLS-filtered data), fitted, coefficients, vip, ortho_vip, components (R2X, R2Y and Q2 of each component) and permutation. x and y can be matrices or vectors as well as data frames. When ncomp.ortho = NA and only the predictive component is significant, the model has no orthogonal component (with a warning) instead of failing. predict() filters new data, or predicts the response or the scores (type), and crossval = 0 skips the cross-validation. - The data sets are renamed: specLIBS is now soilLIBS, and fourrage is now forageLIBS. Replace data(specLIBS) with data(soilLIBS) and data(fourrage) with data(forageLIBS). - macropca() (and step_macropca()) now only centers the variables by default, instead of also scaling them as cellWise::MacroPCA() does: scaling spectra gives noise and continuum channels as much weight as emission lines. Use macropca(x, scale = TRUE) for the previous behavior. - plot_cell_map() is redrawn with ggplot2 and patchwork instead of cellWise::cellMap(), for spectra: the map fills the plot, with a wavelength axis, cells combined into at most resolution blocks (within detector segments), flagged cells colored by the sign and size of their residual, a strip of the outlier type of each observation, rows optionally sorted by orthogonal distance (order = "od"), and a top panel of the share of flagged cells at each wavelength (profile). The arguments nrowsinblock, ncolumnsinblock and ... are replaced by resolution. The profile shows the mean spectrum in grey (of the imputed data, or of spectra) and labels the most flagged regions, with the emission lines they match (lines), and order = "cluster" groups the observations with similar flagged cells. - flagged_regions() lists the wavelength regions where many observations have cells flagged by macropca() (at least threshold of them), with their extent, share, direction and matching emission lines. New features - plot_influence() is drawn like plot_outlier_map(): points outlined in black and filled by type (in the colors of the outlier map), dashed limits at the highest confidence level (dotted, ... at the others, with a legend only for several levels), a compact legend at the bottom, and labels on the labels (default 3) most outlying samples only, instead of all the flagged ones. It gains the options of plot_outlier_map(): relative (distances divided by their limits), shade (the outlying regions), log, colour_by = "distance" and point styles in ..., including a size per sample. The arguments after label changed order: name log and title. In both plots, labels near the right edge are placed on the left of their point, so that they are not cut off. - step_opls() removes the orthogonal components of an opls() model in a recipe, with num_comp tunable. It gives the same filtered data as step_projected_osc(), and also offers Pareto scaling. - step_o2pls() removes the outcome-orthogonal components of an o2pls() model in a recipe, estimated against one or several outcomes (recipe(K + Ca ~ ., ...)), with num_comp and joint_comp tunable for a single outcome. Only the predictors are filtered. Its help page shows a workflow predicting several outcomes and, since tune_grid() does not support several outcomes, how to tune one workflow per outcome with a workflow set (workflow_map("tune_grid", ...)), each filter still estimated against all the outcomes. - plot_embedding() is drawn with a grey panel outside the outermost ellipse (of T-squared, or of each group) and white inside, with no grid, a fixed aspect_ratio (0.7) and lines through the origin. The axis titles give the explained variance of the components of PCA fits, and the T-squared ellipses of the groups (hotelling = "group") are filled with their color. size can be a variable (such as a concentration) that sets the size of the points, and biplot = TRUE draws the loadings of a PCA over the scores, with the biplot_top most important variables (emission lines, for spectra) as labeled arrows. With hotelling = "all", the T-squared of a PCA model is that of the model (its eigenvalues, centered at 0): for a robust fit, the ellipses are no longer inflated or tilted by the outlying samples, and the flagged samples are the leverage points of plot_outlier_map(). Without groups, ellipse = TRUE with hotelling = "all" draws only the T-squared ellipse, with a warning, instead of two ellipses of all the samples. The samples beyond the T-squared limit are counted in the subtitle, circled in red only with flag = TRUE, and labeled only with label (TRUE, or a column or vector of labels), independently of each other. - plot_outlier_map() takes a size per sample (size), and colour_by = "distance" colors its points by their reduced distance from the origin (the larger of SD and OD over their cut-offs), on a rainbow from dark red to blue; with relative = TRUE, where both cut-offs are at 1, yellow marks the cut-offs, between the regular observations (dark red to orange) and the outlying ones (green to blue). - The legends of plot_embedding() and plot_outlier_map() are more compact: smaller text and keys, three sizes, the T-squared levels written on their ellipses instead of in a legend, and no key for the kind of samples without new samples. - contributions() computes the contributions of each variable to the Q residual or to Hotelling's T-squared of samples (whose squares add up to Q and T-squared), for a prcomp() fit or a robust fit (where they decompose the orthogonal and score distances), and relative contributions against reference samples (reference, or "regular" for all regular samples). plot_contributions() draws them against wavelength with the largest labeled and matched to emission lines. - plot_loadings() plots the loadings of a PCA (prcomp(), robpca(), rospca() or macropca()) against wavelength, one panel per component, and labels the wavelengths that contribute most to each component, with the emission lines they match in a line list from libs_lines(). The mean spectrum can be drawn behind the loadings, type = "contribution" plots the variance of each wavelength explained by the components, and interactive = TRUE makes a plotly figure. loading_peaks() returns the labeled peaks as a table, with their candidate lines and a flag for derivative shapes (line shifts). - plot_outlier_map() gains relative, to plot the reduced distances (divided by their cut-offs) so that maps of different models share one scale, shade, to shade the good leverage, orthogonal outlier and bad leverage regions in greys of increasing darkness and name them, and log, for logarithmic axes. The points are outlined in black, and ... passes styling such as alpha or size to geom_point(). - dmodx() computes the distance to the model in the space of the variables (DModX) of a PCA model, normalized or absolute, for the calibration samples or new ones. Its limits use the effective number of residual dimensions by default (df = "effective"), because the nominal degrees of freedom (df = "nominal") flag a large share of ordinary samples when spectra have far more channels than samples. plot_influence() draws DModX or Q against T-squared. - hotelling_t2(), q_residuals(), dmodx(), plot_influence() and the T-squared ellipses of plot_embedding() take any confidence level(s) (conf_level, 0.975 by default; in plot_embedding(), one conf_level sets both its confidence and T-squared ellipses), and the Beta distribution of T-squared for the samples of the model (method = "beta"). hotelling_t2() and the T-squared ellipses of plot_embedding() now use the HotellingEllipse package (1.3.0 or later). - wavelength_calibration() fits a correction of the wavelength axis from reference lines (for example, from the NIST database), located to a fraction of a channel by Gaussian interpolation, with one correction per detector segment (constant, linear or quadratic) and automatic rejection of edge, weak, saturated and outlying lines. apply_calibration() relabels the wavelengths of spectra without changing their intensities, predict() corrects any wavelength, and plot_wavelength_calibration() draws the offsets and the fit. - savitzky_golay() also splits the axis where overlapping detectors make the wavelengths step back. - The periodic table of line_finder() can be hidden (button Hide table, or show_table = FALSE at start) to give the spectrum the whole height of the window; the selected elements stay listed in the panel header. - savitzky_golay() and step_savgol(): Savitzky-Golay smoothing and first or second derivatives, keeping every channel (polynomial fits at the edges) and filtering the segments between detector gaps separately. The window, polynomial order and derivative are tunable (new dials parameter savgol_derivative()). - normalize() gains the L1 ("l1"), L2 or vector ("l2") and maximum ("max") norms, and step_spectral_norm() normalizes each spectrum in a recipe by its L1 norm, total area, L2 norm or maximum, with the method tunable (new dials parameter spectral_norm_method()). - wavelet_features() and step_wavelet(): discrete wavelet transform of spectra (Haar, Daubechies d4/d6/d8 and least asymmetric la8 wavelets) as compressed features: the approximation at a level or all coefficients, optionally the coefficients of largest variance in the training data. The level and number of coefficients are tunable (new dials parameter wavelet_level()). - q_residuals() computes Hotelling's T-squared and the Q residual (SPE) of each sample for a PCA model, for the calibration samples or new ones, with their 95% and 99% limits (Jackson-Mudholkar or Box for Q), and classifies the samples as regular, extreme, residual or both. plot_influence() draws Q against T-squared with the limits. - calibration_curve() gains prediction intervals: plot_calibration() draws the confidence band, the prediction band or both (interval), and shows new samples (newdata) at their predicted concentrations with their intervals; predict(type = "signal") gives the expected signal at given concentrations with a confidence or prediction interval; the coefficients have confidence intervals. - The plots of correlation() are redesigned. For spectra (variables named by wavelength), the plot is a correlation spectrum with the 5% significance thresholds; otherwise, a sorted chart colored by sign and labeled with the values, with top to show only the strongest correlations. The interactive versions are built directly with plotly (WebGL for spectra) instead of converted from ggplot2. color now takes one color or two (positive and negative correlations). - plot_embedding() draws two-dimensional embeddings (UMAP from embed::step_umap(), principal components from recipes::step_pca(), prcomp() or robpca()), colored by a variable, with optional confidence ellipses per group (classical or robust, normal or Hotelling quantiles) from the ConfidenceEllipse package, and Hotelling's T-squared 95% and 99% ellipses with the outlying samples labeled (hotelling, for all samples or within groups, on k components). The preprocessing vignette uses it to compare PCA and UMAP maps of the samples. - hotelling_t2() computes Hotelling's T-squared of each sample on k components of an embedding, with its 95% and 99% limits, for all samples or within groups. Bug fixes - macropca() failed when k was not given: cellWise::MacroPCA() with k = 0 only reports the explained variance. k is now chosen from the new arguments var_explained (0.8) and kmax (10), as in robpca(), and MacroPCA's message and scree plot are no longer shown. Changes in version 0.6.0 New features - cf_libs(): calibration-free LIBS quantification (Ciucci et al., 1999). The temperature is estimated from the common slope of parallel Boltzmann plots (one per species) or, when the electron density is known, Saha-Boltzmann plots (one per element, much more precise). The densities follow from the intercepts and the partition functions, and the ionization stages not observed from the Saha equation. The composition (atomic and mass fractions) is normalized by closure or by the known concentration of one element. plot_boltzmann() draws the plots of the fit. The plasma diagnostics vignette applies it to the forage samples and compares it with their laboratory values. - nist_levels() retrieves energy levels from the NIST Atomic Spectra Database, and partition_function() computes partition functions from them, optionally truncated at a maximum energy (such as the lowered ionization energy). - reject_shots() flags the outlying laser shots of each sample (total intensity, correlation with or distance to the median spectrum, by robust z-scores), before they are averaged with average(). - line_intensities() measures the area (or height, or Voigt-fitted area) of emission lines in spectra, searching each peak near its tabulated wavelength, with a signal-to-noise ratio and saturation check. It keeps the columns of a table of lines, so its result feeds boltzmann_plot(), cf_libs() and calibration_curve(). step_line_intensities() does the same in a recipe. - correct_self_absorption() corrects line intensities for self-absorption by the internal reference method of Sun and Yu (2009), with the temperature given or estimated from a Saha-Boltzmann plot. - calibration_curve() fits univariate (linear or quadratic, optionally weighted) calibration curves, with the sensitivity, LOD and LOQ, Mandel's and lack-of-fit tests of linearity, inverse prediction of concentrations with confidence intervals (predict()) and plot_calibration(). - New recipe steps: step_reject_shots() removes the outlying shots from the training data (skipped on new data), and step_line_ratio() normalizes each spectrum to the area or height of a reference line (internal standard). - line_finder(): a Shiny app to identify the emission lines of LIBS spectra. Elements are selected on a periodic table, and the strongest lines of their ionization stages, from the NIST Atomic Spectra Database, are overlaid on a spectrum in an interactive plotly graph (one color per stage), with controls for the temperature, wavelength range, number of lines, wavelength shift and spectrum (mean, group mean or single spectrum). Fetched lines can be saved and reloaded for offline use. - libs_lines() lists the lines of selected species expected to be the strongest in a plasma at a given temperature (relative intensities in LTE), and plot_lines() overlays them on a spectrum (plotly or ggplot2). Changes in version 0.5.0 This release adds plasma diagnostics for LIBS: electron density from Stark broadening, excitation temperature from Boltzmann and Saha-Boltzmann plots, and checks of LTE, self-absorption and detector saturation, with atomic data from the NIST Atomic Spectra Database and Stark parameters from STARK-B. New features - Plasma diagnostics for LIBS: - starkb_lines() retrieves Stark widths and shifts of the lines of an atom or ion from the STARK-B database (Sahal-Bréchot, Dimitrijević and Moreau) through its VAMDC service, on demand; read_starkb() reads STARK-B data saved as XSAMS files, for offline and reproducible work; stark_table() builds the same table from user-supplied widths or from fitted temperature laws. stark_width() interpolates the width at a given temperature and electron density, and scales multiplet data to a line of the multiplet (lambda-squared rule). - electron_density() estimates the electron density from the Stark (Lorentzian) width of a line, with STARK-B data or a reference width, or from the H-alpha line (Gigosos et al., 2003). - boltzmann_plot() and saha_boltzmann_plot() estimate the excitation temperature from line intensities and atomic data, and plot_boltzmann() draws the plots. - mcwhirter_criterion() checks the McWhirter criterion for LTE, and self_absorption() computes self-absorption coefficients from line widths (El Sherbini et al., 2005). - saturation_summary() finds channels at the saturation limit of the detector. - nist_lines() and nist_ionization_energy() retrieve transition probabilities, level energies, statistical weights and ionization energies from the NIST Atomic Spectra Database, on demand. - voigt_fwhm() computes the full width at half maximum of a Voigt profile (Olivero and Longbothum, 1977). - New vignette "Plasma diagnostics: electron density, temperature and self-absorption", on specLIBS and fourrage spectra. Changes in version 0.4.0 This release adds robust PCA, a proper net analyte signal with figures of merit, and rewrites the documentation around tidymodels. Breaking changes - nas() now computes the net analyte signal (Lorber, Faber and Kowalski, 1997; Faber, 1998) of an inverse PLS or PCR calibration model, with its figures of merit: sensitivity, selectivity and, given the noise level, analytical sensitivity, limits of detection and quantification, and signal-to-noise ratios. A predict() method gives the NAS, selectivity and predicted concentration of new samples. Previously, nas() returned spectra filtered by direct orthogonalization, identical to direct_orthogonal(), which remains available for that purpose. New features - Robust PCA: robpca() (ROBPCA, Hubert, Rousseeuw and Vanden Branden, 2005) and rospca() (robust sparse PCA, Hubert, Reynkens, Schmitt and Verdonck, 2016), implemented from the published algorithms with C++ kernels for the Stahel-Donoho outlyingness, FAST-MCD and the grid-search sparse PCA; and macropca(), a wrapper around cellWise::MacroPCA() for cellwise outliers and missing values. All three return score and orthogonal distances with cut-offs, an outlier classification, and a predict() method for new observations. - plot_outlier_map() draws the outlier map (score distance against orthogonal distance) of any of the three, optionally with new observations; plot_cell_map() draws the cell map of a MacroPCA fit. - New recipe steps step_robpca(), step_rospca() and step_macropca(), robust counterparts of recipes::step_pca() that can also add the score and orthogonal distances as columns, and step_robust_bcyj(), a robust counterpart of recipes::step_BoxCox() and recipes::step_YeoJohnson(). Documentation - The four vignettes and the README use recipe steps, tidymodels (workflows, tune, rsample) and the tidyverse. Preprocessing pipelines are written as recipes; the calibration vignette runs its repeated nested cross-validation with rsample::nested_cv() and tune::tune_grid(), and the orthogonalization vignette tunes every filter together with the PLS model. Vignettes that need suggested packages stop with a message when those are not installed. Changes in version 0.3.0 This release connects specProc to the tidymodels framework. Preprocessing and orthogonalization can now be estimated on calibration data and applied to new spectra, either directly with predict() or as recipe steps that are re-estimated on every resample and tuned together with the model. New features - The orthogonalization filters can now be applied to new spectra: epo(), osc(), direct_orthogonal(), direct_osc(), projected_osc() and o2pls() return classed objects with a predict() method, which corrects new data with the centers, scales and components estimated on the calibration data. Existing fields are unchanged. - New recipe steps for tidymodels: step_epo(), step_glsw(), step_osc(), step_direct_orthogonal(), step_direct_osc(), step_projected_osc() and step_y_gradient_glsw(), with tidy() and tunable() methods, and the dials parameter glsw_alpha(). In a workflow, the filters are re-estimated on every resample and can be tuned with the model. The GLSW steps store the filter in factored form instead of a p x p matrix. recipes and dials are suggested, not required. - New emsc(): extended multiplicative signal correction (Martens and Stark, 1991), with a polynomial baseline and optional interferent spectra, and a predict() method for new spectra. With degree = 0 it equals msc(). - New recipe steps for spectral preprocessing: step_baseline() (arPLS, ALS or LSP), step_snv(), step_msc(), step_emsc(), step_pareto_scale() and step_poisson_scale(). MSC and EMSC estimate the reference spectrum, and the scaling steps the column scales, on the training data only. The baseline lambda or degree and the EMSC degree are tunable; the new dials parameter baseline_lambda() covers lambda. - New data set fourrage: LIBS spectra of 365 forage samples with reference contents of 12 elements, and a vignette based on it, "Removing unwanted variation: a comparison of orthogonalization methods" (what EPO, GLSW, the OSC family, DO/NAS, DOSC, POSC/OPLS and y-gradient GLSW remove, whether it improves potassium predictions, and which methods are equivalent). Changes in version 0.2.0 This release fixes a large number of bugs so that every exported function now runs and returns numerically correct results. It adds a test suite (400+ expectations), and several results are checked against independent reference implementations. Breaking changes - Functions were renamed to a consistent snake_case scheme. The old names still work but give a deprecation warning (see ?"specProc-deprecated"): whittaker() -> baseline_als(), lorentzian() -> lorentzian_profile(), pseudo_voigt() -> pseudo_voigt_profile(), peakfit() -> peak_fit(), multipeakfit() -> multipeak_fit(), plotfit() -> plot_fit(), plotSpec() -> plot_spectra(), outlierplot() -> plot_outliers(), directOutlyingness() -> directional_outlyingness(), iqrMethod() -> iqr_outliers(), robustBCYJ() -> robust_bcyj(), rousseeuwCroux() -> rousseeuw_croux(), summaryStats() -> summary_stats(), tukeyGH() -> tukey_gh(), yGradientglsw() -> y_gradient_glsw(), pareto() -> pareto_scale(). - gaussian() is renamed to gaussian_profile() without an alias, because exporting gaussian() masked stats::gaussian(), which breaks glm(family = gaussian) when specProc is loaded. - baseline_arpls(), baseline_lsp() and whittaker() return tibbles (they previously returned plain lists of columns). They no longer replace negative corrected values with 1. - average() with .group_by now returns the group labels as the first column. - biweight_location() and biweight_scale() use the unscaled MAD, following the standard definition (Beers et al., 1990), so that biweight_scale()^2 equals biweight_midvariance(). biweight_scale() loses its unused tol and max_iter arguments. loc = NULL now means "median of x". - direct_orthogonal() loses its unused max_iter and tol arguments, and direct_osc() loses max_iter. tol now sets the pseudo-inverse tolerance. - epo() gains a clutter argument (the matrix describing the external variation). Its default ncomp is now 2, as documented. It now removes the dominant singular directions; before, it removed the smallest ones. - o2pls() has a new interface: o2pls(x, y, ncomp, nx, ny, center, scale). - pds() returns transfer_matrix (was transfert_matrix), alpha now blends the local and global models, and progress is no longer printed. - projected_osc() always returns the corrected training data. New data are preprocessed with the training centers and scales. - quantile_weight() uses whole-distribution quantiles, as in Brys et al. (2006), and defaults to p = 0.125, q = 0.875. - summaryStats(robust = TRUE) drops the rsd column. It applied the 1.4826 consistency factor twice, and mad already holds the correct value. - opls() now requires the Bioconductor package ropls to be installed (it moved to Suggests). It no longer prints output or draws plots. - R >= 4.1.0 is required. Bug fixes - Fixed crashes in baseline_arpls(), whittaker(), baseline_lsp() (data frames), correlation(method = "bicor"), directOutlyingness(maxRatio = ), direct_osc(), msc(), multipeakfit(), nas(), o2pls(), osc() ("wold", "fearn"), pds(), peakfit(), projected_osc() and yGradientglsw(). - glsw() and yGradientglsw() squared the eigenvalues of the covariance matrix when building the filter. - msc() regressed each wavelength instead of each spectrum on the reference. - generalized_boxplot() transformed the ranks instead of the data. This made the g-and-h estimates meaningless. It also failed when the two tails had different numbers of outliers. - pseudo_voigt() counted the area and the offset twice. - biweight_scale() used a wrong exponent in the denominator. It also ignored reduced = TRUE. - biweight_location() did not iterate. - average() did not ignore missing values. It also read out of bounds for factors with unused levels or missing group values. - normalize(method = "background") rejected data frames for bkg. - plotSpec() connected all spectra into a single line when id was not given. - outlierplot() no longer changes the user's random number generator state. - iqrMethod() no longer changes global options. Missing values no longer cause errors. New features - Three vignettes based on specLIBS: "Preprocessing LIBS spectra" (baseline, normalization judged by replicate RSD and intraclass correlation, robust screening of shots), "Fitting emission lines" (profile choice, sources of uncertainty, identifiability) and "Predicting soil clay content from LIBS spectra" (a compositional log-ratio PLS model with nested, repeated cross-validation by sample, and the optimism of common shortcuts). - plot_fit() draws the fitted profiles on a fine wavelength grid. - Functions taking a response now accept 1-d arrays, such as the output of tapply(). - New voigt_profile(): the exact Voigt profile, computed in C++ from the Faddeeva function with Weideman's (1994) rational approximation (relative error below 1e-10). peak_fit() and multipeak_fit() now use it for profile = "voigt"; the pseudo-Voigt approximation is available as profile = "pseudo_voigt". - New data set specLIBS: 400 LIBS spectra (50 soil samples x 8 locations, 7152 channels, 199-822 nm) with the clay, sand and silt content of each sample. - generalized_boxplot() whiskers now end at the most extreme observations within the fences, as in standard boxplots. The fences are returned as lower_fence and upper_fence. - The README has been rewritten around a complete worked example on specLIBS. Improvements - Asymmetric least squares and arPLS baselines are solved in C++ with a banded Cholesky factorization. The cost is O(n) per spectrum, so they scale to spectra with tens of thousands of channels. - EPO and GLSW use Eigen's divide-and-conquer SVD and never form the p x p covariance or projection matrices. - peakfit() and multipeakfit() estimate starting values from the data when wL, wG or A are not given. multipeakfit() fits all lines simultaneously and returns each line's contribution. A spectrum that fails to fit produces a warning instead of stopping the whole batch. - The orthogonalization functions return the centers and scales, so the same preprocessing can be applied to new data. - Fewer dependencies: corrr, cowplot, forcats, mt, broom and ropls are no longer imported. Changes in version 0.1.0 - Added a NEWS.md file to track changes to the package.