toolsrtm.inversion
Trait-inversion tools: CARS-PLS and VIF predictor selection, LUT nearest-
neighbour (“merit function”) matching, and a 12-algorithm ML dispatcher
(PLSR/SVM/RF/GB/NN/Bayesian/AdaBag/BRNN/xGB/RVM/qLASSO/Ensemble) built on
scikit-learn/xgboost. Direct port of ToolsRTM::carspls/get.cars.pls,
getVIF, get.inversionOpt, get.inversion, hybrid_inversion/
hybrid_inversionE.
Note
Needs the optional ml extra: pip install "toolsrtm[ml]". Nothing
in this module is imported by toolsrtm/__init__.py’s own import chain
– a plain import toolsrtm never requires scikit-learn/xgboost.
Note
R’s get.inversion/hybrid_inversion dispatch to specific caret
methods (bartMachine, rqlasso, rvmLinear, AdaBag, brnn,
…). See ALGORITHMS for exactly which
scikit-learn/xgboost estimator each algorithm name maps to, and, where
there’s no direct equivalent, what was substituted and why.
Trait-inversion tools: CARS-PLS feature selection, VIF-based collinearity pruning, LUT nearest-neighbour (“merit function”) inversion, and a multi-algorithm ML dispatcher.
Python port of ToolsRTM/R/carspls.R / get.cars.pls.R, getVIF.R,
get.inversion.R, hybrid_inversion.R / hybrid_inversionE.R, and
get.inversionOpt.R. Needs the optional ml extra
(pip install toolsrtm[ml]: scikit-learn, xgboost) – none of these
functions are imported by toolsrtm/__init__.py at import time, and each
imports its own ML dependencies lazily so a plain import toolsrtm never
requires scikit-learn/xgboost to be installed.
R’s get.inversion/hybrid_inversion dispatch by name to specific
caret methods (bartMachine, rqlasso, rvmLinear, AdaBag, brnn, …). Several
of those have no direct scikit-learn/xgboost equivalent; where that’s the
case the docstring of the relevant function says exactly which estimator was
substituted and why. Unlike the pure radiative-transfer math ported
elsewhere in this package, none of this module is verified to floating-point
precision against R – caret’s own cross-validated tuning is stochastic,
so a Python port using different (but comparable) estimators and search
grids will not reproduce R’s numbers bit-for-bit even in principle. What’s
verified instead: each algorithm runs end-to-end on held-out data and
produces sane, comparable-magnitude accuracy metrics (see
tests/test_inversion.py).
- class toolsrtm.inversion.CarsPlsResult(coef, n_var, rmsecv, num_lv, optimal_iteration, min_error, selected_variables)[source]
Bases:
objectResult of
carspls(). Mirrors the RCARSlist 1:1 exceptselected_variablesis 0-indexed (Python) instead of 1-indexed (R).- Parameters:
- optimal_iteration: int
1-indexed iteration with the lowest RMSECV (matches R)
- min_error: float
- toolsrtm.inversion.carspls(X, y, n_lv=2, fold=10, scale_pretreat=True, iteration=50, partition_type='interleaved', verbose=False)[source]
Competitive Adaptive Reweighted Sampling for PLS variable selection.
Python port of
carspls/get.cars.pls(R, original algorithm by Yizeng Liang & Hongdong Li, MATLAB->R port by Hongdong Li 2009). At each ofiterationrounds: fits a PLS model on the currently-retained variables, cross-validates it to get an RMSECV curve over 1..n_lv components, records the coefficient-magnitude-ranked variable importance, and forcibly eliminates the lowest-ranked variables via an exponentially decreasing retention schedule (Monte-Carlo/EDF sampling). The iteration with the lowest RMSECV gives the final selected variable set.- Parameters:
X (ndarray) – (n_samples, n_vars) predictor matrix.
y (ndarray) – (n_samples,) response vector.
n_lv (int) – number of PLS latent variables (components) to fit/tune over.
fold (int) – number of cross-validation segments.
scale_pretreat (bool) – if True, scale (not just center) each predictor.
iteration (int) – number of CARS-PLS elimination rounds.
partition_type (Literal['interleaved', 'consecutive', 'random']) – cross-validation fold assignment:
"interleaved"(round-robin),"consecutive"(contiguous blocks), or"random".verbose (bool) – print progress per iteration (matches R’s own screen output).
- Returns:
- Return type:
- toolsrtm.inversion.get_vif(frame, columns=None, thresh=10.0, trace=True)[source]
Backward-elimination variable selection by Variance Inflation Factor.
Python port of
getVIF(R, VIF function originally from https://beckmw.wordpress.com/2013/02/05/collinearity-and-stepwise-vif-selection/). Iteratively regresses each remaining variable on all others; drops the variable with the highest VIF (1 / (1 - R^2)) as long as any VIF exceedsthresh.- Parameters:
frame (ndarray) – (n_samples, n_vars) array, or a pandas.DataFrame.
columns (Sequence[str] | None) – variable names, required if
frameis a bare array; ignored (and taken fromframe.columns) ifframeis a DataFrame.thresh (float) – VIF threshold above which a variable is flagged as collinear.
trace (bool) – print each elimination step (matches R’s own console output).
- Returns:
names (or 0-indexed positions, if
columnsis None andframeis a bare array) of the retained variables.- Return type:
list[str] | list[int]
- class toolsrtm.inversion.InversionOptResult(rfl_best: 'np.ndarray', lut_best: "'pd.DataFrame'")[source]
Bases:
object- Parameters:
rfl_best (np.ndarray)
lut_best (pd.DataFrame)
- rfl_best: np.ndarray
(n_obs, n_wave) best-matching (n_opt-averaged) simulated spectra
- lut_best: pd.DataFrame
(n_obs, n_lut_columns) n_opt-averaged LUT parameters per observation
- toolsrtm.inversion.get_inversion_opt(rfl_sensor, rfl_rtm, lut, wave=None, method='merit-RMSE', n_opt=1, custom_stat=None)[source]
LUT (look-up table) inversion by nearest-neighbour spectral matching.
Python port of
get.inversionOpt(R). For each observed spectrum inrfl_sensor, ranks every simulated spectrum inrfl_rtmby a merit (error) function and averages then_optbest matches’ LUT parameters and reflectance. Fully vectorized (broadcasts each observation against the whole LUT at once) rather than R’s nested per-row loop – same algorithm, no numerical differences expected for the built-in merit functions (verified against a hand-computed reference below).- Parameters:
rfl_sensor (ndarray) – (n_obs, n_wave) observed/sensor reflectance.
rfl_rtm (ndarray) – (n_lut, n_wave) simulated reflectance from the LUT.
lut – (n_lut, n_params) pandas.DataFrame of the LUT’s input parameters.
wave (Sequence[float] | None) – wavelengths corresponding to columns of
rfl_sensor/rfl_rtm(only used to name the returned reflectance columns; optional).method (str) – one of
"merit-RMSE","merit-NRMSE","merit-MAE","merit-NMB","merit-FGE", or"merit-custom.metric"(requirescustom_stat).n_opt (int) – number of best-matching LUT rows to average per observation.
custom_stat (Callable[[ndarray, ndarray], ndarray] | None) – optional
f(sim, obs) -> errormerit function, broadcast over the last axis exactly like the built-in ones; overridesmethod.
- Returns:
- Return type:
- toolsrtm.inversion.ALGORITHMS = {'AdaBag': "sklearn AdaBoostRegressor over shallow DecisionTreeRegressor stumps (matches caret method 'AdaBag')", 'BRNN': "sklearn MLPRegressor with strong L2 (alpha) regularization -- substitute: approximates 'Bayesian regularization' via explicit weight decay, not R's brnn Gauss-Newton/Levenberg-Marquardt fit", 'Bayesian': "sklearn BayesianRidge -- substitute: no BART implementation in sklearn/xgboost; BayesianRidge is a Bayesian *linear* model, not R's bartMachine (Bayesian additive trees)", 'Ensemble': "sklearn StackingRegressor(GB + SVR + MLP, final_estimator=LinearRegression) (matches caretEnsemble::caretStack(..., method='glm'))", 'GB': "sklearn GradientBoostingRegressor (matches caret method 'gbm')", 'NN': "sklearn MLPRegressor, single hidden layer (matches caret method 'nnet')", 'PLSR': "sklearn PLSRegression, n_components tuned by 5-fold CV (matches caret method 'pls')", 'RF': "sklearn RandomForestRegressor, max_features tuned (matches caret method 'rf')", 'RVM': "sklearn BayesianRidge -- substitute: no Relevance Vector Machine in sklearn/xgboost; BayesianRidge shares RVM's sparsity-favouring linear-Bayesian character", 'SVM': "sklearn SVR(kernel='rbf'), gamma/C tuned by grid search (matches caret method 'svmRadial' via e1071::tune.svm)", 'qLASSO': "sklearn QuantileRegressor(quantile=0.5, solver='highs'), L1-penalized (matches caret method 'rqlasso')", 'xGB': "xgboost XGBRegressor(booster='gblinear') (matches caret method 'xgbLinear')"}
R’s caret-method name each algorithm dispatches to, and, where scikit-learn/ xgboost has no direct equivalent, the substitution actually used here.
- class toolsrtm.inversion.InversionResult(model_label: 'str', model: 'object', predictions: 'dict', statistics: 'dict', importance: 'dict | None')[source]
Bases:
object- Parameters:
model_label (str)
model (object)
predictions (dict)
statistics (dict)
importance (dict | None)
- model_label: str
- model: object
- predictions: dict
np.ndarray, “test”: np.ndarray}
- Type:
{“train”
- statistics: dict
{“r2”:.., “rmse”:.., “mae”:..}, “test”: {…}}
- Type:
{“train”
- importance: dict | None
importance}, or None if not available for this algorithm
- Type:
{input_name
- toolsrtm.inversion.get_inversion(data, dep_var, inputs, algorithm='PLSR', seed=123, n_samples=500, test_size=0.3)[source]
Fit and evaluate a plant-trait inversion model with one of 12 algorithms, on a held-out train/test split.
Python port of
get.inversion(R). SeeALGORITHMSfor exactly which scikit-learn/xgboost estimator eachalgorithmname dispatches to, and, for the 4 algorithms with no direct equivalent (Bayesian,BRNN,RVM– and noteAdaBag/xGB/qLASSOdo have close matches), what was substituted and why. Unlike R’s version, tuning here is a single small grid search per algorithm (not caret’s full repeated-CV search), to keep this runnable as a demo rather than a multi-hour job – the same design choice already used byScripts/Python/*/2_inversion_ml.py.- Parameters:
data – pandas.DataFrame containing
dep_varand allinputscolumns.dep_var (str) – name of the response column to predict.
inputs (Sequence[str]) – names of the predictor columns.
algorithm (str) – one of the keys of
ALGORITHMS.seed (int) – random seed for the train/test split and any stochastic estimator.
n_samples (int | None) – if given and less than
len(data), randomly subsample this many rows before splitting (matches R’s own tuning-sample-size argument).test_size (float) – fraction of (sub-sampled) data held out for testing.
- Returns:
- Return type:
- class toolsrtm.inversion.HybridInversionResult(model: 'object', keep_variables: 'list[str]', statistics: "'pd.DataFrame'", predictions: 'dict')[source]
Bases:
object- Parameters:
model (object)
keep_variables (list[str])
statistics (pd.DataFrame)
predictions (dict)
- model: object
- keep_variables: list[str]
predictor columns actually used, after pattern/collinearity selection
- statistics: pd.DataFrame
rows “train”/”test” (+ “field” if field_data given), columns r2/rmse/mae
- predictions: dict
np.ndarray, “test”: np.ndarray[, “field”: np.ndarray]}, original (untransformed) scale
- Type:
{“train”
- toolsrtm.inversion.hybrid_inversion(lut, input, split=0.8, seed=None, method=None, collinearity=None, pattern=None, trans=True, field_data=None, acron=None)[source]
Fit a single-algorithm trait-inversion model with optional predictor selection (by name pattern, then optionally VIF or CARS-PLS pruning) and an optional log-transform of the response.
Python port of
hybrid_inversion(R).methoddispatches to the same 5 estimators asget_inversion()’sSVM/RF/GB/NN/Ensemble("nnet"here maps to"NN"there, matching R’s own caret method name) – seeALGORITHMSfor what each one is. Note: R’s train/test split usescaret::createDataPartition(percentile- stratified on the response); this port uses a plain random split via scikit-learn, which is not percentile-stratified – a documented approximation, not expected to change results materially for the LUT-sized (typically hundreds of rows) datasets this is meant for.- Parameters:
lut – pandas.DataFrame with the response column
inputand candidate predictor columns.input (str) – name of the response column to predict.
split (float) – train-fraction of the train/test split (R’s own convention; note this is the train fraction, unlike
get_inversion()’stest_size).seed (int | None) – random seed.
method (str | None) – one of
"SVM","RF","GB","nnet","Ensemble". Defaults to"SVM"(matches R’s own default).collinearity (Literal['VIF', 'CARS'] | None) –
None(use everypattern-matched column),"VIF"(prune viaget_vif()), or"CARS"(select viacarspls()).pattern (str | None) – substring that predictor column names must contain (e.g.
"B"for reflectance-band columns namedB1,B2, …); ifNone, every column exceptinputis a candidate.trans (bool) – log-transform
inputbefore fitting (matches R’s own default); predictions/statistics are reported back on the original scale.field_data – optional pandas.DataFrame of field observations to validate against, in addition to the LUT’s own test split.
acron (str | None) – suffix appended to
inputto find the observed column infield_data(e.g.acron="_obsv"looks forf"{input}_obsv"). Required iffield_datais given.
- Returns:
- Return type:
- toolsrtm.inversion.hybrid_inversion_ensemble(lut, input, split=0.8, seed=None, collinearity=None, pattern=None, field_data=None, acron=None)[source]
hybrid_inversion()withmethod="Ensemble"fixed.Python port of
hybrid_inversionE(R) – the R function ishybrid_inversionwith the model choice hardcoded to the 3-model (GB + SVM + neural net) stacking ensemble and notrans/log-transform option, which this wrapper matches (trans=False).- Parameters:
input (str)
split (float)
seed (int | None)
collinearity (Literal['VIF', 'CARS'] | None)
pattern (str | None)
acron (str | None)
- Return type: