Basic transformations

Let’s show some SpectroChemPy features on a group of IR spectra

[1]:
import spectrochempy as scp
from spectrochempy import MASKED
from spectrochempy import DimensionalityError
from spectrochempy import error_
[2]:
dataset = scp.read_omnic("irdata/nh4y-activation.spg")
dataset.y -= dataset.y[0]
dataset.y.title = "time"
dataset
[2]:
NDDataset [nh4y-activation] — float64, shape: (y:55, x:5549), a.u.
name
:
nh4y-activation
author
:
runner@runnervm76f27
created
:
2026-08-21 13:20:11+00:00
description
:
Omnic title: NH4Y-activation.SPG
Omnic filename: /home/runner/.spectrochempy/testdata/irdata/nh4y-activation.spg
history
:
2026-08-21 13:20:11+00:00> Imported from spg file /home/runner/.spectrochempy/testdata/irdata/nh4y-activation.spg.
2026-08-21 13:20:11+00:00> Sorted by date
Data
title
:
absorbance
values
:
[[ 2.057 2.061 ... 2.013 2.012]
[ 2.033 2.037 ... 1.913 1.911]
...
[ 1.794 1.791 ... 1.198 1.198]
[ 1.816 1.815 ... 1.24 1.238]] a.u.
shape
:
(y:55, x:5549)
Dimension `x`
size
:
5549
title
:
wavenumbers
coordinates
:
[ 6000 5999 ... 650.9 649.9] cm⁻¹
Dimension `y`
size
:
55
title
:
time
coordinates
:
[ 0 600 ... 3.24e+04 3.3e+04] s
labels
:
[[ 2016-07-06 19:03:14+00:00 2016-07-06 19:13:14+00:00 ... 2016-07-07 04:03:17+00:00 2016-07-07 04:13:17+00:00]
[ vz0466.spa, Wed Jul 06 21:00:38 2016 (GMT+02:00) vz0467.spa, Wed Jul 06 21:10:38 2016 (GMT+02:00) ...
vz0520.spa, Thu Jul 07 06:00:41 2016 (GMT+02:00) vz0521.spa, Thu Jul 07 06:10:41 2016 (GMT+02:00)]]
[3]:
prefs = scp.preferences
prefs.figure.figsize = (6, 3)
prefs.colormap = "Dark2"
prefs.colorbar = True
ax = dataset.plot()
../../_images/userguide_processing_transformations_3_0.png

Masking data

if we try to get for example the maximum of this dataset, we face a problem due to the saturation around 1100 cm\(^{-1}\).

[4]:
dataset.max()
[4]:
6.0 a.u.

One way is to apply the max function to only a part of the spectrum (using slicing). Another way is to mask the undesired data.

Masking values in this case is straightforward. Just set a value masked or True for those data you want to mask.

[5]:
dataset[:, 1290.0:890.0] = MASKED
# note that we specify floating values in order to sect wavenumbers, not index.

Here is a display the figure with the new mask

[6]:
_ = dataset.plot_stack()
../../_images/userguide_processing_transformations_10_0.png

Now the max function return the maximum in the unmasked region, which is exactly what we wanted.

[7]:
dataset.max()
[7]:
3.8080601692199707 a.u.

To clear this mask, we can simply do:

[8]:
dataset.remove_masks()
_ = dataset.plot()
../../_images/userguide_processing_transformations_14_0.png

Transposition

Dataset can be transposed

[9]:
dataset[:, 1290.0:890.0] = MASKED  # we mask the unwanted columns
t_dataset = dataset.T
t_dataset
[9]:
NDDataset [nh4y-activation] — float64, shape: (x:5549, y:55), a.u.
name
:
nh4y-activation
author
:
runner@runnervm76f27
created
:
2026-08-21 13:20:11+00:00
description
:
Omnic title: NH4Y-activation.SPG
Omnic filename: /home/runner/.spectrochempy/testdata/irdata/nh4y-activation.spg
history
:
2026-08-21 13:20:11+00:00> Imported from spg file /home/runner/.spectrochempy/testdata/irdata/nh4y-activation.spg.
2026-08-21 13:20:11+00:00> Sorted by date
2026-08-21 13:20:12+00:00> Data transposed
Data
title
:
absorbance
values
:
[[ 2.057 2.033 ... 1.794 1.816]
[ 2.061 2.037 ... 1.791 1.815]
...
[ 2.013 1.913 ... 1.198 1.24]
[ 2.012 1.911 ... 1.198 1.238]] a.u.
shape
:
(x:5549, y:55)
Dimension `x`
size
:
5549
title
:
wavenumbers
coordinates
:
[ 6000 5999 ... 650.9 649.9] cm⁻¹
Dimension `y`
size
:
55
title
:
time
coordinates
:
[ 0 600 ... 3.24e+04 3.3e+04] s
labels
:
[[ 2016-07-06 19:03:14+00:00 2016-07-06 19:13:14+00:00 ... 2016-07-07 04:03:17+00:00 2016-07-07 04:13:17+00:00]
[ vz0466.spa, Wed Jul 06 21:00:38 2016 (GMT+02:00) vz0467.spa, Wed Jul 06 21:10:38 2016 (GMT+02:00) ...
vz0520.spa, Thu Jul 07 06:00:41 2016 (GMT+02:00) vz0521.spa, Thu Jul 07 06:10:41 2016 (GMT+02:00)]]

As it can be observed the dimension xand yhave been exchanged, e.g. the original shape was (x: 5549, y: 55), and after transposition it is (y:55, x:5549). (the dimension names stay the same, but the index of the corresponding axis are exchanged).

Let’s visualize the result:

[10]:
_ = t_dataset.plot()
../../_images/userguide_processing_transformations_20_0.png

Changing units

Units of the data and coordinates can be changed, but only towards compatible units. For instance, data are in absorbance units, which are dimensionless (a.u). So a dimensionless units such as radian is allowed, even if in this case it makes very little sense.

[11]:
dataset.units = "radian"
[12]:
_ = dataset.plot()
../../_images/userguide_processing_transformations_24_0.png

Trying to change it in ‘meter’ for instance, will generate an error!

[13]:
try:
    dataset.to("meter")
except DimensionalityError as e:
    error_(DimensionalityError, e)
 ERROR | DimensionalityError: Cannot convert from 'radian' (dimensionless) to 'meter' ([length])

If this is for some reasons something you want to do, you must for the change:

[14]:
d = dataset.to("meter", force=True)
d.units
INFO | units forced to change
[14]:
m

When units are compatible there is no problem to modify it. For instance, we can change the y dimension units ( Time) to hours. Her we use the inplace transformation ito .

[15]:
dataset.y.ito("hours")
_ = dataset.plot()
../../_images/userguide_processing_transformations_30_0.png

See Units for more details on these units operations

Chemometric preprocessing

SpectroChemPy provides standard preprocessing operations commonly used in chemometrics and spectroscopic data analysis. They operate along a chosen dimension and respect masks, units, coordinates, and metadata.

[16]:
# Load a dataset and focus on a small region for clarity
ds = scp.read_omnic("irdata/nh4y-activation.spg")
ds = ds[:, 4000.0:2000.0]

Normalization

normalize scales data along a dimension. The default method is 'max'.

[17]:
nd = ds.normalize(method="max", dim="x")
_ = nd.plot(title="Max-normalized spectra")
../../_images/userguide_processing_transformations_35_0.png

Other methods include 'sum', 'vector' (L2 norm), and 'minmax'.

[18]:
nd = ds.normalize(method="minmax", dim="x")
_ = nd.plot(title="Min-max scaled to [0, 1]")
../../_images/userguide_processing_transformations_37_0.png

Mean-centering and autoscaling

center subtracts the mean. autoscale mean-centres and divides by the standard deviation (z-score). By default these operate per variable (dim='y'), which is the convention before PCA or PLS.

[19]:
nd = ds.center(dim="y")
_ = nd.plot(title="Mean-centered (per wavenumber)")
../../_images/userguide_processing_transformations_39_0.png
[20]:
nd = ds.autoscale(dim="y")
_ = nd.plot(title="Autoscaled (z-score per wavenumber)")
../../_images/userguide_processing_transformations_40_0.png

Standard Normal Variate (SNV)

SNV is equivalent to autoscaling each spectrum individually (dim='x'). It is a classic NIR preprocessing step.

[21]:
nd = ds.snv()
_ = nd.plot(title="SNV corrected")
../../_images/userguide_processing_transformations_42_0.png

Multiplicative Scatter Correction (MSC)

MSC corrects for multiplicative and additive scattering effects by linearly regressing each spectrum against a reference (the mean spectrum by default). In transformer workflows, MSCTransformer.fit() learns only the reference spectrum. The intercept and slope remain local to each transformed spectrum, so inverse_transform() is intentionally unsupported.

[22]:
nd = ds.msc()
_ = nd.plot(title="MSC corrected")
../../_images/userguide_processing_transformations_44_0.png

Using transformers for machine-learning workflows

For train/test splits or cross-validation, transformer classes make preprocessing state explicit. Feature-wise scalers reuse statistics learned from a training set:

scaler = scp.AutoscaleTransformer(dim="y")
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled  = scaler.transform(X_test)   # uses train mean/std

Sample-local operations such as SNVTransformer, NormalizeTransformer(dim="x"), and the per-spectrum regression step in MSCTransformer still compute each observation’s statistics during transform(), because those statistics belong to the spectrum being transformed rather than to the training set. Their inverse transform is not supported unless a future API carries the required per-observation state on the transformed result.

All nine operations have a matching transformer (e.g. CenterTransformer, NormalizeTransformer, MSCTransformer, …). They implement the familiar fit() / transform() / fit_transform() / inverse_transform() lifecycle where meaningful and expose get_params() / set_params() for scikit-learn-compatible cloning.

All operations support inplace=True and can be called as either top-level functions (scp.normalize(...)) or dataset methods (dataset.normalize(...)).