"""
The Iso module is the class for performing calculation using plant wax stable
isotope data imported as a 2D array-like object
"""
# import pandas as pd
import numpy as np
import scipy.stats
# from ..utils import validate_data
[docs]
class Isotope:
"""
Represents leaf wax compound-specific stable isotope data imported as a
2-D, array-like object (i.e., list, array) with rows representing unique
samples and columns representing unique data types (carbon chain-length
number).
Parameters
----------
input_data : 2-D array-like
User leaf wax isotope data.
Attributes
----------
input_data : 2-D array-like
User leaf wax isotope data.
Examples
--------
.. jupyter-execute::
from leafwaxtools import Isotope
"""
def __init__(self, input_data):
self.data = input_data
if self.data.ndim != 2:
raise TypeError("'input_data' must be 2-dimensional")
[docs]
def value_range(self):
"""
Calculates the maximum isotope value range (max - min) between all
chain-lengths (columns) of each sample (rows).
Returns
-------
value_range : numpy.ndarray
1-D Numpy array of maximum isotope value ranges for each sample
(row).
"""
value_range = np.zeros(len(self.data[:,0]))
for row in range(0, len(self.data[:,0])):
value_range[row] = np.max(self.data[row,:]) - np.min(self.data[row,:])
return value_range
[docs]
def conc_avg(self, chain_data):
"""
Calculates the chain-length concentration-weighted average isotope
value of each sample (rows) using .
Parameters
----------
chain_data : 2-D array-like
User leaf wax chain-length concentration/abundance data. Must be
the same shape (same number of rows and columns) as the user
isotope input data. Chain-length data with a value of NaN is
treated as having a concentration/abundance of 0.
Raises
------
ValueError
Raises an error when Isotope.data and 'chain_data' are not the
same shape (same number of rows and columns).
Returns
-------
conc_avg : numpy.ndarray
1-D Numpy array of chain-length concentration-weighted average
isotope values for each sample (row).
"""
if np.shape(self.data) != np.shape(chain_data):
raise ValueError("Input isotope and chain-length distribution data must have the same number of rows and columns")
conc_avg = np.zeros(len(self.data[:,0]))
for row in range(0, len(self.data[:,0])):
for col in range(0, len(self.data[0,:])):
if self.data[row,col] == np.nan:
chain_data[row,col] = 0
for row in range(0, len(self.data[:,0])):
for col in range(0, len(self.data[0,:])):
conc_avg[row] += self.data[row,col] * chain_data[row,col]
conc_avg[row] = conc_avg[row]/np.sum(chain_data[row,:])
return conc_avg
[docs]
def epsilon(self, epsilon_numerator=None, epsilon_denominator=None):
"""
Calculates the isotopic fractionation factor (epsilon) between stable
isotope values/arrays (permil units) in the numerator and denominator
of the following equation (e.g., Diefendorf & Freimuth, 2017):
epsilon = (((1000 + numerator) / (1000 + denominator)) - 1) * 1000
This equation is based on epsilon calculations for leaf wax stable
hydrogen and carbon isotope vlaues.
References:
Diefendorf, A. F., & Freimuth, E. J. (2017). Extracting the most from
terrestrial plant-derived n-alkyl lipids and their carbon isotopes
from the sedimentary record: A review. Organic Geochemistry, 103,
1-21. https://doi.org/10.1016/j.orggeochem.2016.10.016
Parameters
----------
epsilon_numerator : 1-D or 2-D array-like, optional
Numerator stable isotope value/array. Uses Isotope.data by default
if no argument is passed. The default is None.
epsilon_denominator : 1-D or 2-D array-like, optional
Denominator stable isotope value/array. Uses Isotope.data by
default if no argument is passed. The default is None.
Returns
-------
epsilon : 1-D or 2-D array-like
1-D or 2-D array-like of epsilon values for each sample (row) and
chain-length (column; if applicable).
"""
if epsilon_numerator is None:
epsilon_numerator = self.data
if epsilon_denominator is None:
epsilon_denominator = self.data
epsilon = (((1000+epsilon_numerator)/(1000+epsilon_denominator))-1)*1000
return epsilon
[docs]
def wax_to_source(self, epsilon, epsilon_numerator=None):
"""
Calculates the isotopic value of a source material to a leaf wax using
an isotopic fractionation factor (epsilon). A common application is the
calculation of source water (e.g., precipitation, lake water) stable
hydrogen isotope (d2H) values using leaf wax d2H values and an
associated epsilon value between the source water and leaf wax (e.g.,
Feakins, 2013; Holtzman et al., 2025).
References:
Feakins, S. J. (2013). Pollen-corrected leaf wax D/H reconstructions of
northeast African hydrological changes during the late Miocene.
Palaeogeography, Palaeoclimatology, Palaeoecology, 374, 62-71.
https://doi.org/10.1016/j.palaeo.2013.01.004
Holtzman, H., Thomas, E. K., Erb, M., Marshall, L., CastaƱeda, I. S.,
Kaufman, D., ... & Melles, M. (2025). Early Holocene atmospheric
circulation changes over northern Europe based on isotopic and
biomarker evidence from Kola Peninsula. Paleoceanography and
Paleoclimatology, 40(3), e2024PA005076.
https://doi.org/10.1029/2024PA005076
Parameters
----------
epsilon : 1-D or 2-D array-like
Isotopic fractionation factor (epsilon) value/array.
epsilon_numerator : 1-D or 2-D array-like, optional
Numerator stable isotope value/array. Uses Isotope.data by default
if no argument is passed. The default is None.
Returns
-------
source_isotope : 1-D or 2-D array-like
1-D or 2-D array-like of source isotope values for each sample
(row) and chain-length (column; if applicable).
"""
if epsilon_numerator is None:
epsilon_numerator = self.data
source_isotope = ((1000+epsilon_numerator)/((epsilon/1000)+1))-1000
return source_isotope
[docs]
def corr_rvals(self, minimum_obs=2):
"""
Calculates the Pearson correlation r-values between each leaf wax
chain-length (columns). To be extended with other correlation methods
(Spearman, Kendall Tau) in a future version.
Parameters
----------
minimum_obs : int, optional
Minimum number of observations (samples/rows) required to return a
Pearson r-value. The default is 2.
Returns
-------
r_vals : numpy.ndarray
2-D Numpy array of Pearson correlation r-values between each leaf
wax chain-length (column) with all values in the major diagonal
equal to 1.
"""
r_vals = np.zeros((len(self.data[0,:]), len(self.data[0,:])))
for row in range(0, len(r_vals[:,0])):
for col in range(0, len(r_vals[0,:])):
x_corr = np.array(self.data[:,row])
y_corr = np.array(self.data[:,col])
if (len(x_corr) >= minimum_obs) and (len(y_corr) >= minimum_obs):
r_vals[row,col] = scipy.stats.pearsonr(x_corr, y_corr)[0]
else:
r_vals[row,col] = np.nan
return r_vals
[docs]
def corr_pvals(self, minimum_obs=2):
"""
Calculates the Pearson correlation p-values between each leaf wax
chain-length (columns). To be extended with other correlation methods
(Spearman, Kendall Tau) in a future version.
Parameters
----------
minimum_obs : int, optional
Minimum number of observations (samples/rows) required to return a
Pearson r-value. The default is 2.
Returns
-------
p_vals : numpy.ndarray
2-D Numpy array of Pearson correlation p-values between each leaf
wax chain-length (column).
"""
p_vals = np.zeros((len(self.data[0,:]), len(self.data[0,:])))
for row in range(0, len(p_vals[:,0])):
for col in range(0, len(p_vals[0,:])):
x_corr = np.array(self.data[:,row])
y_corr = np.array(self.data[:,col])
if (len(x_corr) >= minimum_obs) and (len(y_corr) >= minimum_obs):
p_vals[row,col] = scipy.stats.pearsonr(x_corr, y_corr)[1]
else:
p_vals[row,col] = np.nan
return p_vals