Peak Fitting of FTIR Spectra

Philip Bulsink

Identifying Functional Groups in Biodiesel Spectra

A common task in FTIR spectroscopy is identifying and quantifying functional groups within a complex mixture. Biodiesel-diesel blends provide an excellent example: the presence and concentration of biodiesel introduces characteristic carbonyl (C=O), ester (C-O-C), and alkene (=C-H) absorption bands that are absent in pure diesel.

This vignette walks through a complete peak fitting workflow — from detecting overlapping peaks to evaluating fit quality — using the built-in biodiesel dataset. The goal is not to catalogue individual functions, but to demonstrate a practical analysis pipeline that you can adapt to your own samples.

The Biodiesel Dataset

The biodiesel dataset contains ATR-FTIR spectra of diesel samples with increasing biodiesel content (0% to 10% plus commercial blends). Each spectrum was collected across the standard mid-IR range, and the fingerprint region (approximately 1800–1000 cm⁻¹) contains the most diagnostic features for distinguishing biodiesel from conventional diesel.

data(biodiesel)
head(biodiesel)
#> PlotFTIR data:
#>   Spectral range: 700.7395 - 710.0579 cm⁻¹
#>   Resolution: variable
#>   Intensity type: absorbance 
#>   Number of samples: 1 
#>   Sample IDs: biodiesel_0

The data is in the long-format structure: wavenumber, absorbance, and sample_id columns with 11 distinct samples. The biodiesel_0 sample (pure diesel) serves as our baseline for comparison.

Visualizing the Raw Spectrum

Before attempting peak fitting, it is important to understand the raw spectral landscape. The fingerprint region of the pure diesel sample already shows several discernible features:

sample_spectrum <- biodiesel[biodiesel$sample_id == "biodiesel_0", ]

plot_ftir(
  sample_spectrum,
  plot_title = c("Pure Diesel Spectrum", "Fingerprint region (1800–1000 cm⁻¹)"),
  legend_title = "Sample"
) |>
  zoom_in_on_range(c(1800, 1000))
FTIR Spectra of pure diesel, from 1800-1000 cm-1.

FTIR Spectra of pure diesel, from 1800-1000 cm-1.

Even this pure diesel sample reveals multiple overlapping bands. As biodiesel content increases, additional peaks emerge — particularly a carbonyl stretch near 1740 cm⁻¹ that is absent in pure diesel. This is the feature we will isolate and quantify.

Detecting Peaks Automatically

The first step in any peak fitting workflow is identifying candidate peak locations. Rather than manually inspecting the spectrum, find_ftir_peaks() combines several mathematical techniques to detect peaks automatically.

How Peak Detection Works

The algorithm applies Savitzky-Golay smoothing to reduce noise, then identifies peaks through three complementary approaches:

  1. Second derivative minima — the primary method for locating sharp peaks
  2. First derivative zero-crossings — captures broad, asymmetric peaks that the second derivative may miss
  3. Signal maxima re-scanning — catches any peaks the derivative methods overlooked

Peaks detected by different methods within a configurable window are merged into single representative locations.

subset_spectrum <- sample_spectrum[
  sample_spectrum$wavenumber < 1800 & sample_spectrum$wavenumber > 1000,
]

detected_peaks <- find_ftir_peaks(subset_spectrum)
detected_peaks
#>  [1] 1034.802 1064.155 1153.611 1159.202 1162.929 1214.180 1304.568 1339.978
#>  [9] 1358.615 1376.785 1408.934 1459.253 1503.981 1606.949 1697.802 1741.599

The algorithm detected 16 peaks across the fingerprint region. These locations will serve as initial estimates for the fitting procedure. Note that the peak detection is performed on smoothed data, so the exact wavenumbers may differ slightly from visual inspection of the raw spectrum — this is expected and desirable, as it reduces the influence of noise on peak positioning.

Fitting Peaks: Choosing a Model

With peak locations identified, the next step is fitting each peak with a mathematical function. The fit_peaks() function supports four peak shapes, each appropriate for different spectral characteristics. The choice of model affects both the accuracy of peak parameters and the interpretability of the results.

Gaussian Profile

Gaussian functions are appropriate when instrumental broadening dominates the line shape, or when computational speed is a priority. They are simpler than Voigt profiles and work well for narrow, symmetric peaks with minimal overlap.

fitted_gauss <- fit_peaks(subset_spectrum, method = "gauss")
fit_peak_df(fitted_gauss)
#>      sample_id peak wavenumber     sigma   mix_ratio peak_shape
#> 1  biodiesel_0    1   1025.633 15.695189 0.039982655      gauss
#> 2  biodiesel_0    2   1076.450 20.872454 0.042003518      gauss
#> 3  biodiesel_0    3   1133.032 20.141444 0.025185099      gauss
#> 4  biodiesel_0    4   1159.059 20.217322 0.014117550      gauss
#> 5  biodiesel_0    5   1170.351 15.914914 0.016049505      gauss
#> 6  biodiesel_0    6   1229.561 25.563969 0.042091636      gauss
#> 7  biodiesel_0    7   1294.075 22.117027 0.064185307      gauss
#> 8  biodiesel_0    8   1337.630 12.032651 0.037822852      gauss
#> 9  biodiesel_0    9   1363.732 10.980559 0.051104570      gauss
#> 10 biodiesel_0   10   1377.579  7.179903 0.106445051      gauss
#> 11 biodiesel_0   11   1419.442 16.878896 0.058655133      gauss
#> 12 biodiesel_0   12   1456.609 12.058274 0.412416771      gauss
#> 13 biodiesel_0   13   1498.467 20.551715 0.057451542      gauss
#> 14 biodiesel_0   14   1594.650 24.549848 0.027559921      gauss
#> 15 biodiesel_0   15   1682.487 23.003104 0.002251302      gauss
#> 16 biodiesel_0   16   1744.946 20.202827 0.002677590      gauss

The Gaussian fit produced similar peak locations to the Voigt model but with narrower widths. This is expected: without the Lorentzian long-tail component, Gaussian peaks are more confined and may underestimate the extent of peak shoulders.

Lorentzian Profile

Lorentzian functions describe natural line width effects and are appropriate when the broadening is dominated by the natural lifetime of molecular transitions. They produce the characteristic long tails that make them useful for analyzing heavily overlapped peaks.

fitted_lorentz <- fit_peaks(subset_spectrum, method = "lorentz")
fit_peak_df(fitted_lorentz)
#>      sample_id peak wavenumber        gam   mix_ratio peak_shape
#> 1  biodiesel_0    1   1019.822  27.343565 0.044647245    lorentz
#> 2  biodiesel_0    2   1072.032  25.804662 0.036392725    lorentz
#> 3  biodiesel_0    3   1143.794  33.237798 0.022335263    lorentz
#> 4  biodiesel_0    4   1156.531  33.163230 0.021228812    lorentz
#> 5  biodiesel_0    5   1163.658  30.375450 0.021163227    lorentz
#> 6  biodiesel_0    6   1243.210  28.808297 0.033323541    lorentz
#> 7  biodiesel_0    7   1295.446  22.999894 0.054921068    lorentz
#> 8  biodiesel_0    8   1339.375  17.375919 0.045839838    lorentz
#> 9  biodiesel_0    9   1367.361   9.430853 0.059338098    lorentz
#> 10 biodiesel_0   10   1377.695   4.834317 0.102798290    lorentz
#> 11 biodiesel_0   11   1443.236   7.788562 0.089910434    lorentz
#> 12 biodiesel_0   12   1458.512   9.172563 0.391955449    lorentz
#> 15 biodiesel_0   15   1461.928 147.054710 0.001823384    lorentz
#> 16 biodiesel_0   16   1465.919 184.126613 0.001313200    lorentz
#> 13 biodiesel_0   13   1469.822  16.202340 0.057854108    lorentz
#> 14 biodiesel_0   14   1604.872   8.916487 0.015155318    lorentz

The Lorentzian fit yielded broader sigma values than the Gaussian model, reflecting the longer tails. For FTIR analysis, pure Lorentzian profiles are less commonly used than Voigt because real spectra rarely exhibit purely Lorentzian line shapes — they typically show a mix of both broadening mechanisms.

Doniach-Sunjić-Gauss Profile

The Doniach-Sunjić (DSG) function extends the Voigt profile with an additional asymmetry parameter (alpha), making it particularly useful for asymmetric peaks that arise from overlapping transitions or instrumental effects. This is especially relevant for FTIR spectra where bands often exhibit shoulders or asymmetry due to Fermi resonance or overlapping functional groups.

fitted_dsg <- fit_peaks(subset_spectrum, method = "dsg")
fit_peak_df(fitted_dsg)
#>      sample_id peak wavenumber      sigma        eta        alpha   mix_ratio
#> 1  biodiesel_0    1   1021.776  21.478736 0.23276904 1.049155e-01 0.040221847
#> 2  biodiesel_0    2   1073.490  20.899919 0.26723203 4.754677e-02 0.035230315
#> 3  biodiesel_0    3   1143.371  27.091393 0.47418449 1.536343e-06 0.023874732
#> 4  biodiesel_0    4   1155.345  28.303541 0.76345199 1.561619e-02 0.022389494
#> 5  biodiesel_0    5   1162.507  27.641576 0.99999837 2.912457e-02 0.022282430
#> 6  biodiesel_0    6   1236.036  26.701457 0.22506264 4.777725e-03 0.032110568
#> 7  biodiesel_0    7   1295.193  22.040561 0.25121851 1.536343e-06 0.052925636
#> 8  biodiesel_0    8   1340.234  15.928599 0.92390882 7.108259e-03 0.048768640
#> 9  biodiesel_0    9   1367.382  10.734311 0.59165574 7.265695e-03 0.064193746
#> 10 biodiesel_0   10   1377.587   5.024684 0.56711766 3.413371e-03 0.108458343
#> 11 biodiesel_0   11   1436.471  13.177921 0.32451088 2.142091e-02 0.088006613
#> 12 biodiesel_0   12   1457.333  10.205644 0.07415162 1.536343e-06 0.362734553
#> 13 biodiesel_0   13   1485.438  20.359853 0.18880913 1.536343e-06 0.071237677
#> 15 biodiesel_0   15   1576.212 137.830608 0.27728205 1.885750e-02 0.003109714
#> 16 biodiesel_0   16   1598.191 170.800139 0.70802262 2.881136e-03 0.002568777
#> 14 biodiesel_0   14   1602.555  12.716794 0.24007689 7.799314e-02 0.021886915
#>              peak_shape
#> 1  doniach-sunjic-gauss
#> 2  doniach-sunjic-gauss
#> 3  doniach-sunjic-gauss
#> 4  doniach-sunjic-gauss
#> 5  doniach-sunjic-gauss
#> 6  doniach-sunjic-gauss
#> 7  doniach-sunjic-gauss
#> 8  doniach-sunjic-gauss
#> 9  doniach-sunjic-gauss
#> 10 doniach-sunjic-gauss
#> 11 doniach-sunjic-gauss
#> 12 doniach-sunjic-gauss
#> 13 doniach-sunjic-gauss
#> 15 doniach-sunjic-gauss
#> 16 doniach-sunjic-gauss
#> 14 doniach-sunjic-gauss

The DSG fit introduces the alpha parameter, which controls the degree of asymmetry. When alpha is near zero, the DSG profile reduces to a symmetric Voigt. Non-zero values indicate asymmetric broadening — a common feature in complex mixtures like diesel-biodiesel blends.

Comparing Fit Models

To understand how each model represents the data, we can overlay the fitted spectra on the original:

# Generate fitted spectra for each model
voigt_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_voigt)
gauss_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_gauss)
lorentz_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_lorentz)

# Build comparison data frame
comparison_data <- data.frame(
  wavenumber = rep(subset_spectrum$wavenumber, 4),
  absorbance = c(subset_spectrum$absorbance, voigt_fit, gauss_fit, lorentz_fit),
  type = rep(c("Original", "Voigt", "Gaussian", "Lorentz"),
             each = nrow(subset_spectrum))
)

# Plot comparison
ggplot(comparison_data, aes(x = wavenumber, y = absorbance, color = type)) +
  geom_line(linewidth = 0.8) +
  facet_wrap(~type, ncol = 1, scales = "free_y") +
  labs(
    title = "Comparison of Peak Fitting Models",
    x = "Wavenumber (cm⁻¹)",
    y = "Absorbance"
  ) +
  theme_minimal()
Plots of each type of peak fit, comparing the results against the original spectrum.

Plots of each type of peak fit, comparing the results against the original spectrum.

The Voigt and Lorentzian fits produce the closest representations of the original spectrum, with the Voigt model offering the best balance of accuracy and computational efficiency. The Gaussian fit, while faster, tends to overestimate peak heights and underestimate widths — a consequence of its symmetric, rapidly-decaying shape. For most FTIR applications, the Voigt profile is the recommended default.

Visualizing Fit Components

Once a fit is complete, it is useful to examine the individual peak components that comprise the overall model. The plot_components() function decomposes the fitted spectrum into its constituent peaks, revealing which functional groups contribute to each region of the spectrum.

plot_components(
  subset_spectrum,
  fitted_voigt,
  plot_fit = TRUE,
  plot_title = c("Voigt Peak Components", "Decomposition of the fingerprint region")
)
Plotted components of a pseudo-voigt peak-shape fitted diesel.

Plotted components of a pseudo-voigt peak-shape fitted diesel.

Each colored line represents a single fitted peak component. The thick black line shows the sum of all components overlaid on the original spectrum. Peaks that are nearly identical in color to the original spectrum indicate a good fit. Gaps between the component sum and the original spectrum highlight regions where the current peak model is insufficient — these may indicate additional peaks that were not detected, or the need for a different peak shape.

Evaluating Fit Quality with Residuals

A good peak fit should leave residuals (the difference between observed and fitted values) that resemble random noise. Systematic patterns in the residuals indicate that the model is missing important features.

plot_fit_residuals(
  subset_spectrum,
  fitted_voigt,
  plot_title = c("Residuals of Voigt Fit", "Differences between original and fitted spectrum")
)
Residuals of pseudo-voigt peak fitting

Residuals of pseudo-voigt peak fitting

The residual plot shows the magnitude and pattern of the remaining error. Ideally, residuals should be centered around zero with no systematic structure. Large residuals at specific wavenumbers suggest either undetected peaks or a mismatch between the chosen peak shape and the actual spectral feature. In this case, the residuals are relatively small across most of the spectrum, confirming that the Voigt model provides a reasonable representation of the data.

Applying Peak Fitting Across Multiple Samples

Peak fitting is not limited to a single spectrum. When analyzing a series of samples — such as the biodiesel calibration standards — fitting each sample independently allows you to track how peak parameters change with concentration.

sample_ids <- unique(biodiesel$sample_id)
peak_table <- data.frame()

for (sid in sample_ids) {
  sample_data <- biodiesel[biodiesel$sample_id == sid, ]
  subset_data <- sample_data[
    sample_data$wavenumber < 1800 & sample_data$wavenumber > 1000,
  ]
  fitted <- fit_peaks(subset_data, method = "voigt")
  peak_df <- fit_peak_df(fitted)
  peak_df$sample_id <- sid
  peak_table <- rbind(peak_table, peak_df)
}

head(peak_table)
#>     sample_id peak wavenumber    sigma       eta  mix_ratio peak_shape
#> 1 biodiesel_0    1   1020.413 22.71464 0.4197296 0.04140067      voigt
#> 2 biodiesel_0    2   1073.977 21.17409 0.4834221 0.03595312      voigt
#> 3 biodiesel_0    3   1142.979 26.59045 0.5870270 0.02436659      voigt
#> 4 biodiesel_0    4   1155.392 28.73669 0.8131919 0.02225917      voigt
#> 5 biodiesel_0    5   1162.685 30.66516 0.8898188 0.02209477      voigt
#> 6 biodiesel_0    6   1235.001 26.95515 0.3463904 0.03395994      voigt

The resulting table contains fitted peak parameters for every sample. By filtering for specific peaks (e.g., the carbonyl peak near 1740 cm⁻¹), you can quantify how peak intensity, width, and position correlate with biodiesel concentration. This is the foundation of quantitative FTIR analysis: peak fitting converts qualitative spectral features into measurable, comparable numerical data.

Practical Considerations

Choosing a Peak Shape

Peak Shape Best For Trade-offs
Voigt (default) General FTIR spectra; combines instrumental and natural broadening Slightly slower than Gaussian; recommended starting point
Gaussian Narrow, symmetric peaks; computational efficiency May underestimate peak shoulders and asymmetric features
Lorentzian Peaks dominated by natural line width effects Less common in FTIR; produces long tails that may overfit
Doniach-Sunjić-Gauss Asymmetric peaks with shoulders; overlapping transitions More parameters to tune; may overfit simple spectra

Tips for Reliable Fitting

References

The peak fitting methods in PlotFTIR are implemented via the EMpeaksR package, which uses spectrum-adapted expectation-maximization algorithms:


Identification des Groupes Fonctionnels dans les Spectres de Biodiesel

Une tâche courante en spectroscopie IRTF est l’identification et la quantification des groupes fonctionnels au sein d’un mélange complexe. Les mélanges diesel-biodiesel fournissent un excellent exemple : la présence et la concentration de biodiesel introduisent des bandes d’absorption caractéristiques du carbonyle (C=O), de l’ester (C-O-C) et de l’alcène (=C-H) qui sont absentes dans le diesel pur.

Ce didacticiel présente un flux de travail complet d’ajustement de pics — de la détection des pics chevauchés à l’évaluation de la qualité de l’ajustement — en utilisant l’ensemble de données biodiesel intégré. L’objectif n’est pas de cataloguer les fonctions individuelles, mais de démontrer un pipeline d’analyse pratique que vous pouvez adapter à vos propres échantillons.

L’ensemble de données Biodiesel

L’ensemble de données biodiesel contient des spectres IRTF-ATR de mélanges diesel avec du biodiesel croissant (0 % à 10 % plus des mélanges commerciaux). Chaque spectre a été collecté sur la gamme moyenne-IR standard, et la région de l’empreinte digitale (environ 1800–1000 cm⁻¹) contient les caractéristiques les plus diagnostiques pour distinguer le biodiesel du diesel conventionnel.

data(biodiesel)
head(biodiesel)
#> PlotFTIR data:
#>   Spectral range: 700.7395 - 710.0579 cm⁻¹
#>   Resolution: variable
#>   Intensity type: absorbance 
#>   Number of samples: 1 
#>   Sample IDs: biodiesel_0

La sortie montre la structure au format long : les colonnes wavenumber, absorbance et sample_id avec 11 échantillons distincts. L’échantillon biodiesel_0 (diesel pur) sert de référence pour la comparaison.

Visualisation du Spectre Brut

Avant de tenter un ajustement de pics, il est important de comprendre le paysage spectral brut. La région de l’empreinte digitale de l’échantillon de diesel pur révèle déjà plusieurs caractéristiques discernables :

sample_spectrum <- biodiesel[biodiesel$sample_id == "biodiesel_0", ]

plot_ftir(
  sample_spectrum,
  plot_title = c("Spectre de Diesel Pur", "Région de l'empreinte digitale (1800–1000 cm⁻¹)"),
  legend_title = "Échantillon",
  lang = "fr"
) |>
  zoom_in_on_range(c(1800, 1000))
Spectres FTIR du diesel pur, de 1800 à 1000 cm-1.

Spectres FTIR du diesel pur, de 1800 à 1000 cm-1.

Même cet échantillon de diesel pur révèle plusieurs bandes superposées. À mesure que la teneur en biodiesel augmente, des pics supplémentaires apparaissent — en particulier une bande de carbonyle vers 1740 cm⁻¹ qui est absente dans le diesel pur. C’est la caractéristique que nous allons isoler et quantifier.

Détection Automatique des Pics

La première étape de tout flux de travail d’ajustement de pics est l’identification des emplacements candidats des pics. Plutôt que d’inspecter manuellement le spectre, find_ftir_peaks() combine plusieurs techniques mathématiques pour détecter les pics automatiquement.

Comment Fonctionne la Détection des Pics

L’algorithme applique un lissage de Savitzky-Golay pour réduire le bruit, puis identifie les pics par trois approches complémentaires :

  1. Minima de la dérivée seconde — la méthode principale pour localiser les pics nets
  2. Zéro-crossings de la dérivée première — capture les pics larges et asymétriques que la dérivée seconde peut manquer
  3. Rebalayage des maxima du signal — rattrape les pics que les méthodes de dérivée ont négligés

Les pics détectés par différentes méthodes dans une fenêtre configurable sont fusionnés en emplacements représentatifs uniques.

subset_spectrum <- sample_spectrum[
  sample_spectrum$wavenumber < 1800 & sample_spectrum$wavenumber > 1000,
]

detected_peaks <- find_ftir_peaks(subset_spectrum)
detected_peaks
#>  [1] 1034.802 1064.155 1153.611 1159.202 1162.929 1214.180 1304.568 1339.978
#>  [9] 1358.615 1376.785 1408.934 1459.253 1503.981 1606.949 1697.802 1741.599

L’algorithme a détecté 16 pics sur la région de l’empreinte digitale. Ces emplacements serviront d’estimations initiales pour la procédure d’ajustement. Notez que la détection des pics est effectuée sur des données lissées, donc les nombres d’ondes exacts peuvent différer légèrement de l’inspection visuelle du spectre brut — ce qui est attendu et souhaitable, car cela réduit l’influence du bruit sur le positionnement des pics.

Ajustement des Pics : Choix d’un Modèle

Une fois les emplacements des pics identifiés, l’étape suivante consiste à ajuster chaque pic avec une fonction mathématique. La fonction fit_peaks() prend en charge quatre formes de pics, chacune appropriée pour différentes caractéristiques spectrales. Le choix du modèle affecte à la fois la précision des paramètres des pics et l’interprétabilité des résultats.

Profil de Voigt (Recommandé comme Point de Départ)

Le profil de Voigt est une convolution des fonctions de Gauss et de Lorentz. Il est généralement le modèle le plus approprié pour les spectres IRTF car il tient compte à la fois de l’élargissement instrumental (composante Gaussienne) et des effets de largeur de raie naturelle (composante de Lorentz). L’approximation pseudo-Voigt utilisée ici est efficace sur le plan computationnel tout en maintenant la précision.

fitted_voigt <- fit_peaks(subset_spectrum, method = "voigt")
fit_peak_df(fitted_voigt)
#>      sample_id peak wavenumber      sigma        eta   mix_ratio peak_shape
#> 1  biodiesel_0    1   1020.413  22.714636 0.41972956 0.041400669      voigt
#> 2  biodiesel_0    2   1073.977  21.174087 0.48342208 0.035953116      voigt
#> 3  biodiesel_0    3   1142.979  26.590449 0.58702704 0.024366592      voigt
#> 4  biodiesel_0    4   1155.392  28.736687 0.81319187 0.022259174      voigt
#> 5  biodiesel_0    5   1162.685  30.665160 0.88981877 0.022094765      voigt
#> 6  biodiesel_0    6   1235.001  26.955147 0.34639035 0.033959941      voigt
#> 7  biodiesel_0    7   1294.880  22.562646 0.32615082 0.054933212      voigt
#> 8  biodiesel_0    8   1339.374  14.482396 0.87823501 0.046511657      voigt
#> 9  biodiesel_0    9   1366.233  11.221113 0.67046643 0.061342282      voigt
#> 10 biodiesel_0   10   1377.533   5.595815 0.63297368 0.109267084      voigt
#> 11 biodiesel_0   11   1431.896  17.154142 0.40251546 0.076010705      voigt
#> 12 biodiesel_0   12   1456.959  10.943677 0.08425625 0.375290320      voigt
#> 13 biodiesel_0   13   1490.297  20.265143 0.40700345 0.068834924      voigt
#> 15 biodiesel_0   15   1600.329 140.204354 0.53035808 0.003082964      voigt
#> 14 biodiesel_0   14   1600.354  15.126079 0.64840440 0.022102834      voigt
#> 16 biodiesel_0   16   1643.817 162.995308 0.70987334 0.002589761      voigt

La sortie montre les paramètres ajustés pour chaque pic : l’emplacement du nombre d’ondes (wavenumber), la largeur (sigma), le paramètre de forme (eta) et la contribution relative (mix_ratio). L’ajustement de Voigt a trouvé des pics aux alentours de 1457, 1378, and 1366 cm⁻¹ — ceux-ci correspondent aux vibrations connues de flexion C-H, et d’étirement C-C dans les chaînes hydrocarbonées.

Profil Gaussien

Les fonctions gaussiennes sont appropriées lorsque l’élargissement instrumental domine la forme de raie, ou lorsque la vitesse computationnelle est une priorité. Elles sont plus simples que les profils de Voigt et fonctionnent bien pour les pics étroits et symétriques avec un chevauchement minimal.

fitted_gauss <- fit_peaks(subset_spectrum, method = "gauss")
fit_peak_df(fitted_gauss)
#>      sample_id peak wavenumber     sigma   mix_ratio peak_shape
#> 1  biodiesel_0    1   1025.633 15.695189 0.039982655      gauss
#> 2  biodiesel_0    2   1076.450 20.872454 0.042003518      gauss
#> 3  biodiesel_0    3   1133.032 20.141444 0.025185099      gauss
#> 4  biodiesel_0    4   1159.059 20.217322 0.014117550      gauss
#> 5  biodiesel_0    5   1170.351 15.914914 0.016049505      gauss
#> 6  biodiesel_0    6   1229.561 25.563969 0.042091636      gauss
#> 7  biodiesel_0    7   1294.075 22.117027 0.064185307      gauss
#> 8  biodiesel_0    8   1337.630 12.032651 0.037822852      gauss
#> 9  biodiesel_0    9   1363.732 10.980559 0.051104570      gauss
#> 10 biodiesel_0   10   1377.579  7.179903 0.106445051      gauss
#> 11 biodiesel_0   11   1419.442 16.878896 0.058655133      gauss
#> 12 biodiesel_0   12   1456.609 12.058274 0.412416771      gauss
#> 13 biodiesel_0   13   1498.467 20.551715 0.057451542      gauss
#> 14 biodiesel_0   14   1594.650 24.549848 0.027559921      gauss
#> 15 biodiesel_0   15   1682.487 23.003104 0.002251302      gauss
#> 16 biodiesel_0   16   1744.946 20.202827 0.002677590      gauss

L’ajustement Gaussien a produit des emplacements de pics similaires au modèle de Voigt mais avec des largeurs plus étroites. Cela est attendu : sans la composante de queue longue de Lorentz, les pics gaussiens sont plus confinés et peuvent sous-estimer l’étendue des épaules de pics.

Profil de Lorentz

Les fonctions de Lorentz décrivent les effets de largeur de raie naturelle et sont appropriées lorsque l’élargissement est dominé par la durée de vie naturelle des transitions moléculaires. Elles produisent les queues longues caractéristiques qui les rendent utiles pour l’analyse des pics fortement chevauchés.

fitted_lorentz <- fit_peaks(subset_spectrum, method = "lorentz")
fit_peak_df(fitted_lorentz)
#>      sample_id peak wavenumber        gam   mix_ratio peak_shape
#> 1  biodiesel_0    1   1019.822  27.343565 0.044647245    lorentz
#> 2  biodiesel_0    2   1072.032  25.804662 0.036392725    lorentz
#> 3  biodiesel_0    3   1143.794  33.237798 0.022335263    lorentz
#> 4  biodiesel_0    4   1156.531  33.163230 0.021228812    lorentz
#> 5  biodiesel_0    5   1163.658  30.375450 0.021163227    lorentz
#> 6  biodiesel_0    6   1243.210  28.808297 0.033323541    lorentz
#> 7  biodiesel_0    7   1295.446  22.999894 0.054921068    lorentz
#> 8  biodiesel_0    8   1339.375  17.375919 0.045839838    lorentz
#> 9  biodiesel_0    9   1367.361   9.430853 0.059338098    lorentz
#> 10 biodiesel_0   10   1377.695   4.834317 0.102798290    lorentz
#> 11 biodiesel_0   11   1443.236   7.788562 0.089910434    lorentz
#> 12 biodiesel_0   12   1458.512   9.172563 0.391955449    lorentz
#> 15 biodiesel_0   15   1461.928 147.054710 0.001823384    lorentz
#> 16 biodiesel_0   16   1465.919 184.126613 0.001313200    lorentz
#> 13 biodiesel_0   13   1469.822  16.202340 0.057854108    lorentz
#> 14 biodiesel_0   14   1604.872   8.916487 0.015155318    lorentz

L’ajustement de Lorentz a produit des valeurs sigma plus larges que le modèle gaussien, reflétant les queues plus longues. Pour l’analyse IRTF, les profils de Lorentz purs sont moins couramment utilisés que Voigt car les spectres réels présentent rarement des formes de raie purement lorentziennes — elles montrent généralement un mélange des deux mécanismes d’élargissement.

Profil Doniach-Sunjić-Gauss

La fonction Doniach-Sunjić (DSG) étend le profil de Voigt avec un paramètre d’asymétrie supplémentaire (alpha), ce qui la rend particulièrement utile pour les pics asymétriques qui résultent de transitions chevauchées ou d’effets instrumentaux. Ceci est particulièrement pertinent pour les spectres IRTF où les bandes présentent souvent des épaules ou une asymétrie dues à la résonance de Fermi ou à des groupes fonctionnels chevauchés.

fitted_dsg <- fit_peaks(subset_spectrum, method = "dsg")
fit_peak_df(fitted_dsg)
#>      sample_id peak wavenumber      sigma        eta        alpha   mix_ratio
#> 1  biodiesel_0    1   1021.776  21.478736 0.23276904 1.049155e-01 0.040221847
#> 2  biodiesel_0    2   1073.490  20.899919 0.26723203 4.754677e-02 0.035230315
#> 3  biodiesel_0    3   1143.371  27.091393 0.47418449 1.536343e-06 0.023874732
#> 4  biodiesel_0    4   1155.345  28.303541 0.76345199 1.561619e-02 0.022389494
#> 5  biodiesel_0    5   1162.507  27.641576 0.99999837 2.912457e-02 0.022282430
#> 6  biodiesel_0    6   1236.036  26.701457 0.22506264 4.777725e-03 0.032110568
#> 7  biodiesel_0    7   1295.193  22.040561 0.25121851 1.536343e-06 0.052925636
#> 8  biodiesel_0    8   1340.234  15.928599 0.92390882 7.108259e-03 0.048768640
#> 9  biodiesel_0    9   1367.382  10.734311 0.59165574 7.265695e-03 0.064193746
#> 10 biodiesel_0   10   1377.587   5.024684 0.56711766 3.413371e-03 0.108458343
#> 11 biodiesel_0   11   1436.471  13.177921 0.32451088 2.142091e-02 0.088006613
#> 12 biodiesel_0   12   1457.333  10.205644 0.07415162 1.536343e-06 0.362734553
#> 13 biodiesel_0   13   1485.438  20.359853 0.18880913 1.536343e-06 0.071237677
#> 15 biodiesel_0   15   1576.212 137.830608 0.27728205 1.885750e-02 0.003109714
#> 16 biodiesel_0   16   1598.191 170.800139 0.70802262 2.881136e-03 0.002568777
#> 14 biodiesel_0   14   1602.555  12.716794 0.24007689 7.799314e-02 0.021886915
#>              peak_shape
#> 1  doniach-sunjic-gauss
#> 2  doniach-sunjic-gauss
#> 3  doniach-sunjic-gauss
#> 4  doniach-sunjic-gauss
#> 5  doniach-sunjic-gauss
#> 6  doniach-sunjic-gauss
#> 7  doniach-sunjic-gauss
#> 8  doniach-sunjic-gauss
#> 9  doniach-sunjic-gauss
#> 10 doniach-sunjic-gauss
#> 11 doniach-sunjic-gauss
#> 12 doniach-sunjic-gauss
#> 13 doniach-sunjic-gauss
#> 15 doniach-sunjic-gauss
#> 16 doniach-sunjic-gauss
#> 14 doniach-sunjic-gauss

L’ajustement DSG introduit le paramètre alpha, qui contrôle le degré d’asymétrie. Lorsque alpha est proche de zéro, le profil DSG se réduit à un Voigt symétrique. Des valeurs non nulles indiquent un élargissement asymétrique — une caractéristique commune dans les mélanges complexes comme les mélanges diesel-biodiesel.

Comparaison des Modèles d’Ajustement

Pour comprendre comment chaque modèle représente les données, nous pouvons superposer les spectres ajustés sur l’original :

# Générer les spectres ajustés pour chaque modèle
voigt_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_voigt)
gauss_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_gauss)
lorentz_fit <- PlotFTIR:::.get_fit_spectra(subset_spectrum, fitted_lorentz)

# Construire le tableau de comparaison
comparison_data <- data.frame(
  wavenumber = rep(subset_spectrum$wavenumber, 4),
  absorbance = c(subset_spectrum$absorbance, voigt_fit, gauss_fit, lorentz_fit),
  type = rep(c("Original", "Voigt", "Gaussien", "Lorentz"),
             each = nrow(subset_spectrum))
)

# Tracer la comparaison
ggplot(comparison_data, aes(x = wavenumber, y = absorbance, color = type)) +
  geom_line(linewidth = 0.8) +
  facet_wrap(~type, ncol = 1, scales = "free_y") +
  labs(
    title = "Comparaison des Modèles d'Ajustement de Pics",
    x = "Nombre d'ondes (cm⁻¹)",
    y = "Absorbance"
  ) +
  theme_minimal()
Graphiques de chaque type d'ajustement de pic, comparant les résultats au spectre original.

Graphiques de chaque type d’ajustement de pic, comparant les résultats au spectre original.

Les ajustements de Voigt et de Lorentz produisent les représentations les plus proches du spectre original, le modèle de Voigt offrant le meilleur équilibre entre précision et efficacité computationnelle. L’ajustement gaussien, bien que plus rapide, a tendance à surestimer les hauteurs de pics et à sous-estimer les largeurs — une conséquence de sa forme symétrique à décroissance rapide. Pour la plupart des applications IRTF, le profil de Voigt est le défaut recommandé.

Visualisation des Composants de l’Ajustement

Une fois un ajustement terminé, il est utile d’examiner les composants individuels des pics qui composent le modèle global. La fonction plot_components() décompose le spectre ajusté en ses pics constitutifs, révélant quels groupes fonctionnels contribuent à chaque région du spectre.

plot_components(
  subset_spectrum,
  fitted_voigt,
  plot_fit = TRUE,
  plot_title = c("Composants des Pics de Voigt", "Décomposition de la région de l'empreinte digitale"),
  lang = "fr"
)
Composantes tracées d'un moteur diesel équipé d'un profil de pic pseudo-Voigt.

Composantes tracées d’un moteur diesel équipé d’un profil de pic pseudo-Voigt.

Chaque ligne colorée représente un pic ajusté individuel. La ligne épaisse noire montre la somme de tous les composants superposée sur le spectre original. Les pics qui sont presque identiques en couleur au spectre original indiquent un bon ajustement. Les écarts entre la somme des composants et le spectre original mettent en évidence les régions où le modèle de pic actuel est insuffisant — ceux-ci peuvent indiquer des pics supplémentaires qui n’ont pas été détectés, ou le besoin d’une forme de pic différente.

Évaluation de la Qualité de l’Ajustement par les Résidus

Un bon ajustement de pics devrait laisser des résidus (la différence entre les valeurs observées et ajustées) qui ressemblent à du bruit aléatoire. Des motifs systématiques dans les résidus indiquent que le modèle manque de caractéristiques importantes.

plot_fit_residuals(
  subset_spectrum,
  fitted_voigt,
  plot_title = c("Résidus de l'Ajustement de Voigt", "Différences entre le spectre original et le spectre ajusté"),
  lang = "fr"
)
Résidus de l'ajustement de pic pseudo-Voigt

Résidus de l’ajustement de pic pseudo-Voigt

Le graphique de résidus montre l’amplitude et le motif de l’erreur restante. Idéalement, les résidus devraient être centrés autour de zéro sans structure systématique. De grands résidus à des nombres d’ondes spécifiques indiquent soit des pics non détectés, soit un manque de correspondance entre la forme de pic choisie et la caractéristique spectrale réelle. Dans ce cas, les résidus sont relativement petits sur la majeure partie du spectre, confirmant que le modèle de Voigt fournit une représentation raisonnable des données.

Application de l’Ajustement de Pics à Plusieurs Échantillons

L’ajustement de pics n’est pas limité à un seul spectre. Lorsqu’on analyse une série d’échantillons — comme les étalons de calibration biodiesel — l’ajustement de chaque échantillon indépendamment permet de suivre comment les paramètres des pics changent avec la concentration.

sample_ids <- unique(biodiesel$sample_id)
peak_table <- data.frame()

for (sid in sample_ids) {
  sample_data <- biodiesel[biodiesel$sample_id == sid, ]
  subset_data <- sample_data[
    sample_data$wavenumber < 1800 & sample_data$wavenumber > 1000,
  ]
  fitted <- fit_peaks(subset_data, method = "voigt")
  peak_df <- fit_peak_df(fitted)
  peak_df$sample_id <- sid
  peak_table <- rbind(peak_table, peak_df)
}

head(peak_table)
#>     sample_id peak wavenumber    sigma       eta  mix_ratio peak_shape
#> 1 biodiesel_0    1   1020.413 22.71464 0.4197296 0.04140067      voigt
#> 2 biodiesel_0    2   1073.977 21.17409 0.4834221 0.03595312      voigt
#> 3 biodiesel_0    3   1142.979 26.59045 0.5870270 0.02436659      voigt
#> 4 biodiesel_0    4   1155.392 28.73669 0.8131919 0.02225917      voigt
#> 5 biodiesel_0    5   1162.685 30.66516 0.8898188 0.02209477      voigt
#> 6 biodiesel_0    6   1235.001 26.95515 0.3463904 0.03395994      voigt

Le tableau résultant contient les paramètres de pics ajustés pour chaque échantillon. En filtrant pour des pics spécifiques (par exemple, le pic de carbonyle vers 1740 cm⁻¹), vous pouvez quantifier comment l’intensité, la largeur et la position du pic corrélatent avec la concentration en biodiesel. C’est le fondement de l’analyse quantitative IRTF : l’ajustement de pics convertit les caractéristiques spectrales qualitatives en données numériques mesurables et comparables.

Considérations Pratiques

Choisir une Forme de Pic

Forme de Pic Idéale Pour Compromis
Voigt (défaut) Spectres IRTF généraux ; combine l’élargissement instrumental et naturel Légèrement plus lent que Gaussien ; point de départ recommandé
Gaussien Pics étroits et symétriques ; efficacité computationnelle Peut sous-estimer les épaules de pics et les caractéristiques asymétriques
Lorentzien Pics dominés par les effets de largeur de raie naturelle Moins courant en IRTF ; produit des queues longues qui peuvent surajuster
Doniach-Sunjić-Gauss Pics asymétriques avec épaules ; transitions chevauchées Plus de paramètres à régler ; peut surajuster les spectres simples

Conseils pour un Ajustement Fiable

Références

Les méthodes d’ajustement de pics dans PlotFTIR sont implémentées via le package EMpeaksR, qui utilise des algorithmes d’espérance-maximisation adaptés aux spectres :