fit, transform, predict¶
transforms, estimators and simulators share one lifecycle. you build the object with its options, fit it to samples, and then ask it for results. the same calls work for a normal-score transform, for kriging and for simulation.
three states¶
fit stores what the object needs from the samples. the estimator then predicts at targets; the transform maps values to scores and, with inverse_transform, scores back to values. a simulator follows the estimator's lane.- configured. the constructor takes options only: a variogram, a search, a number of classes. it computes nothing yet.
- fitted.
fittakes the samples and returns the same object, now holding what it learned. since it returns the object, you can chain:bt.OrdinaryKriging(variogram, search).fit(samples, "V"). - results. a transform answers
transformandinverse_transform; an estimator answerspredict; a simulator answerssimulate. you can ask as often as you like, at as many targets as you like, without fitting again.
transforms¶
a transform learns a mapping from the data and applies it to any values. NormalScore learns the table that turns the grades into scores with a standard normal distribution:
import boitata as bt
import numpy as np
samples = bt.datasets.walker_lake()
normal_score = bt.NormalScore().fit(samples["V"])
scores = normal_score.transform(samples["V"])
back = normal_score.inverse_transform(scores)
print(f"std {scores.std():.2f}, back to V: {np.allclose(back, samples['V'])}")
std 1.00, back to V: True
fit_transform does both steps at once, with one difference: it gives tied values, such as walker lake's 22 zeros, distinct scores by breaking ties at random from seed, while transform sends equal values to one score.
Capping, BoxCox, HermiteAnamorphosis, PPMT, PCA, MAF and the other transforms follow the same pattern; the transforms API lists them.
estimators¶
an estimator takes a variogram and a search, fits to samples and predicts at targets. the targets can be an (n, 3) array, a PointSet or a BlockModel:
variogram = bt.experimental_variogram(samples, "V", 10.0, 120.0).fit("spherical")
search = bt.Search(radius=60, max_samples=24, min_samples=4)
grid = bt.BlockModel(origin=(2.5, 2.5), size=(5, 5), count=(52, 60))
kriging = bt.OrdinaryKriging(variogram, search).fit(samples, "V")
estimate, variance = kriging.predict(grid, return_variance=True)
grid = grid.with_column("V", estimate).with_column("V_variance", variance)
print(grid)
BlockModel(regular, 3120 of 3120 cells, count [52, 60, 1], size [5.0, 5.0, 1.0], rotation [0.0, 0.0, 0.0])
V: Float64
V_variance: Float64
where results go¶
predict returns plain numpy arrays, one value per target in the order of the targets. for a block model that order is the row order, so you can put the array on the model with with_column. a target the search could not fill comes back as NaN, and the container stores it as null.
ask for diagnostics=True and predict returns a Table with one row per target instead:
report = kriging.predict(grid, diagnostics=True)
print(report.column_names)
grid = grid.with_columns({"slope": report["slope"], "n_samples": report["n_samples"]})
['value', 'variance', 'efficiency', 'slope', 'n_samples', 'pass', 'n_holes', 'n_other_domain', 'mean_distance', 'negative_weight_sum', 'lagrange', 'support_variance', 'estimate_variance', 'max_samples_reached', 'target_met']
the slope of regression and the kriging efficiency tell you how far to trust each estimate; kriging diagnostics reads them.
a fitted estimator also checks itself. cross_validate re-estimates every sample from the others with the same variogram and search:
check = kriging.cross_validate()
print(f"mean error {check.mean_error:.1f}, RMSE {check.rmse:.1f}, slope {check.slope:.2f}")
mean error 6.8, RMSE 182.8, slope 1.11
OrdinaryKriging, SimpleKriging, UniversalKriging, BlockKriging, IndicatorKriging, InverseDistance and the rest share fit, predict, cross_validate and with_search; the estimation API lists them.
simulators and seeds¶
a simulator draws many equally likely maps, called realizations, in place of one smooth estimate. SGS fits like an estimator, on normal scores, and simulate returns a SimulationSummary: the mean, variance and probabilities over the realizations, plus the realizations themselves when you pass keep=True.
sgs = bt.SGS(bt.Variogram([("spherical", 1.0, 40.0)]), search).fit(samples, scores)
first = sgs.simulate(grid, n=10, seed=42, keep=True)
again = sgs.simulate(grid, n=10, seed=42, keep=True)
print(first.realizations.shape, np.array_equal(first.realizations, again.realizations))
(10, 3120) True
realization k draws from a seed built from your seed and k, and boitatá splits the work across threads so the thread count never changes the numbers. the same seed gives the same realizations with one thread or many; change the seed to get a new set.
key idea
options go to the constructor, data to fit, targets to predict, transform or simulate. results come back as arrays in target order, ready for with_column.
pitfall
the method names match scikit-learn's, yet these objects take one column of values plus coordinates in place of a 2D feature matrix, and they have no get_params. they do not drop into a scikit-learn Pipeline unchanged, so chain them by hand, as above.
the examples apply this lifecycle in the normal-score transform, ordinary kriging, cross-validation and sequential gaussian simulation. saving models shows how a fitted transform saves as JSON and a fitted estimator as parquet.