geoml.plots
The figures, in two backends that draw the same set under the same names:
Explorer in matplotlib, to print, and Interactive in plotly, to look at.
Both are built from a container plus a choice of variable, and several
plotly figures can be linked on one page with Dashboard.
Three of them are only honest on data the model has not seen — accuracy,
spread_check and variogram — since at a training location a model
interpolates its own measurement.
Matplotlib
Looking at a data set before modelling it.
An Explorer is a choice of data and variables – one continuous (or vector) and one categorical – held on to, so that the figures can be asked for one after another without repeating it. The categorical variable is what splits and colours every other figure, which is the question worth asking of most geoscientific data: does this population behave as one, or as several?
eda = geoml.plots.Explorer(point, continuous="Elements",
categorical="Rock")
eda.histogram()
eda.pairs()
eda.pca(explained=0.9)
eda.scene()
These are matplotlib figures, meant to be saved and printed. Interactive is the same set of figures in plotly, for looking at on a screen.
- class geoml.plots.explorer.Explorer(data, continuous=None, categorical=None, model=None, palette=None, cmap=None, uncertainty=None)[source]
Bases:
SelectionExploratory figures for one data set and a choice of variables.
Takes its arguments from base.Selection: the container, a continuous and a categorical variable, a model for the figures that need one, and the colours to draw them in.
- histogram(bins=25, statistics=True, figsize=None)[source]
The distribution of the continuous variable, one panel per component.
Split by category when there is one: the populations are drawn over each other with a common set of bins, so their spreads can be compared rather than only their shapes.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are.
statistics (bool) – Sum each panel up in a box: the count, the mean, the standard deviation, the coefficient of variation, the skewness and the kurtosis, beside the minimum, the quartiles, the median and the maximum. They are of every measured value, the categories pooled, and the kurtosis is the excess over a normal’s. The box sits over the half of the bins with the lower bars, and the axis is raised so that none of those runs under it.
- Return type:
Figure
- pairs(kind='scatter', alpha=0.7, bins=60, log_counts=False, log=False, principal_components=0, upper=None, figsize=None, size=6)[source]
Every component against every other, with its distribution down the diagonal.
- Parameters:
kind (str) – “scatter”, or “hist2d” to count the points into cells instead, for when there are too many to draw one by one. Counts make a single surface, so the categories are pooled.
alpha (float) – How transparent each point is, for kind=”scatter”.
bins (int) – Cells along each axis, for kind=”hist2d”.
log_counts (bool) – Colour the cells by the logarithm of the count. Worth turning on whenever a few cells hold most of the data, which is most of the time with a skewed variable: on a linear scale they take the whole colour range and everything else reads as empty.
log (bool) – Draw the data on a log scale: the centred log-ratio for a composition, whose parts carry a constant sum, and an ordinary logarithm otherwise. Geochemical data is usually closer to symmetric this way, and it is what makes the principal components of a composition drawable – they are directions in the log-ratios, so this is the space they belong to.
principal_components (int) – Draw this many principal components over the data, in the data’s own axes – the reverse of pca, which puts the data on the components’ axes. Each is a line through the mean, one standard deviation of that component long either way, so its length says how much of the spread it accounts for and its slant says which measurements move together. The sign of a component means nothing, hence a line rather than an arrow.
upper (str) – What to put in the upper triangle, which is otherwise left empty: “hist2d” or “density” for where the mass is with the categories pooled, or “correlation” for the coefficient alone. The lower triangle says who each point is; the upper says where the data as a whole sits.
- Return type:
Figure
- pca(explained=0.9, log=False, kind='scatter', alpha=0.7, bins=60, log_counts=False, figsize=None, size=6)[source]
The same pairs plot, on principal components instead of measurements.
Only the components carrying explained of the variance are drawn. Each panel also holds the loadings: an arrow per original column, showing what it contributes to the two components on the axes. A composition is opened up with the centred log-ratio first – see prepare.centred_log_ratio for why.
- Parameters:
explained (float) – Share of the total variance to reach, between 0 and 1.
log (bool) – Take the components of the logarithms rather than of the measurements. A composition is opened up either way – there is no useful PCA of proportions, whose covariance the constant sum makes singular – so this decides the matter only for everything else.
kind – As in pairs.
alpha – As in pairs.
bins – As in pairs.
log_counts – As in pairs.
- Return type:
Figure
- scene(color=None, clip=None, figsize=None, size=14)[source]
Where the data is, coloured by a variable.
The coordinates decide the drawing: a value against position in 1D, a map in 2D, a scatter in 3D. For anything more than a look at a 3D data set, as_pyvista() on the container is the better road.
- Parameters:
color (str) – The variable to colour by. Defaults to the continuous variable the Explorer holds, or the categorical one if that is all there is.
clip (pair of floats) – Where to end the colour scale, as quantiles: [0, 0.99] for a variable with a long right tail, which is most assays. Without it one value far from the rest takes the whole scale and leaves everything else in a single shade. Nothing is dropped – the points beyond the ends take the end colour. Has no effect in 1D, where the value is an axis rather than a colour.
- Return type:
Figure
- training_curve(window=None, figsize=None)[source]
The ELBO against the iteration it was measured at.
Every value is an estimate, from a sample of the latent variables and, under train_svi, a sample of the data too, so the curve is noisy whether or not training has settled. The running mean over it is the line to read: flat means finished, still climbing means it is not.
- Parameters:
window (int) – Points to average over. Defaults to a fiftieth of the log.
- Return type:
Figure
- transformed_pairs(kind='scatter', alpha=0.7, bins=60, log_counts=False, upper=None, figsize=None, size=6)[source]
The measurements as the model sees them, after its warping.
Two things worth checking before trusting a fitted model, and both are easier to see than to test. Down the diagonal, whether the warping made each variable Gaussian: a normal of the column’s own mean and spread is drawn over each histogram, so the shape is judged and not the scale. Off the diagonal, whether what is left is independent: a round cloud with a correlation near zero is what the model assumes, and a tilted or curved one is structure it will not capture.
The columns are numbered rather than named: a warping may rotate the data or bend it, so a column is generally a mixture of what was measured rather than any one of it.
- Parameters:
kind – As in pairs.
alpha – As in pairs.
bins – As in pairs.
log_counts – As in pairs.
upper (str) – What to put in the upper triangle, otherwise left empty: “hist2d”, “density” or “correlation”, as in pairs. “density” earns its place here: the contours of a pair the warping has done its work on are round and centred, and any lean or corner in them is the dependence the model is about to assume away.
- Return type:
Figure
- simulation_pairs(kind='hist2d', bins=60, log_counts=False, most=100000, margin=0.1, alpha=0.6, figsize=None, size=6)[source]
What was measured against what was simulated.
The measurements fill the lower triangle and the simulations the upper, in the same form and between the same limits, so the two halves of the matrix can be read against each other: a simulation that reproduces the data has an upper half that mirrors the lower one. Down the diagonal the measured histogram carries the simulated density over it, which is the same comparison one variable at a time.
Both halves are counted into cells by default. Simulations come in location-by-realization blocks that run to millions of values, and a scatter of that many points is a filled rectangle whatever its transparency.
- Parameters:
kind (str) – “hist2d”, the default here, or “scatter”.
bins – As in pairs.
log_counts – As in pairs.
alpha – As in pairs.
size – As in pairs.
most (int) – About how many simulated values to draw per component. They are taken by striding through the block, so the sample spans locations and realizations alike; the same stride is used for every component, since it is the pairs that are being looked at.
margin (float) – How far past the measured range to look, as a share of it. Simulated values outside that window are left out: a few realizations reaching far beyond anything measured would otherwise set the scale for every panel and squeeze the comparison into a corner of it. They are dropped rather than pinned to the edge, which would pile the whole tail into the last cell and read as a mode that is not there.
- Return type:
Figure
- prediction_scatter(component=None, kind='scatter', alpha=0.6, bins=60, log_counts=False, trim=None, figsize=None, size=10)[source]
What was predicted against what was measured.
A single variable is drawn with its two distributions along the sides, which is where a bias shows that the scatter alone hides: the same cloud can sit on the 1:1 line while the predicted values are packed into a narrower range than the real ones. Several components are drawn as a panel each; name one with component to get the margins for it.
- Parameters:
component (str) – One component of a vector variable, drawn on its own.
kind (str) – “scatter”, or “hist2d” for a block model, where the points are past counting and a scatter is a solid mass whatever its transparency.
alpha (float) – How transparent each point is. Worth lowering as the points pile up, until there are enough to want kind=”hist2d” instead.
bins (int) – Bins along each axis, for kind=”hist2d”.
log_counts (bool) – Colour the cells by the logarithm of the count.
trim (pair of floats) – Leave the outliers out, as quantiles: [0, 0.99] for a variable with a long right tail, which is most assays. Without it a few values far from the rest set the limits and squeeze everything else into a corner. The window runs from the lower quantile of the measured or the predicted values, whichever is lower, to the upper quantile of whichever is higher, and a location outside it on either axis is left out of the panel and its margins. Each panel is trimmed on its own, and counts in a corner how many it left out.
- Return type:
Figure
- accuracy(probabilities=None, figsize=None)[source]
Whether the simulated spread is the spread the errors actually have.
For each probability, the share of true values that fall inside the interval holding that share of the simulations. On the 1:1 line the model knows what it does not know; below it the intervals are too narrow for the errors they have to cover, and above it the model is hedging. Deutsch’s goodness statistic sums that up in one number.
The intervals come from the model rather than from the container: what is stored there is the ground, the likelihood noise having been integrated out, and an assay is a measurement of the ground rather than the ground itself. Scoring the stored simulations against measured values would ask the model a question it never answered, and it would fail – so this figure needs the model the selection was built with.
- Return type:
Figure
- reliability(bins=10, figsize=None)[source]
Whether a claimed probability is the frequency it claims.
One curve per category: the locations binned by the probability the model assigned to it, each bin’s mean claim against the share of its locations actually measured as that category. On the diagonal a 70% claim is that category 70% of the time; below it the model is overconfident, above it hedging. The legend carries each curve’s expected calibration error, the count-weighted mean distance from the diagonal.
The same locations count as in confusion_matrix: a contact has two measurements and no one truth, and locations missing either the measurement or the prediction are left out.
Only honest on data the model has not seen: at a training location the claim was fitted to its own outcome. The out-of-fold container models.cross_validate fills is the honest input.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are. A count gives equal-count bins over the claimed probabilities; pass explicit edges for equal width.
- Return type:
Figure
- confusion_matrix(figsize=None)[source]
What was measured against what the model called there, counted.
Rows are the measured categories, columns the predicted ones, so the diagonal is agreement and each row reads as one category’s fate. The shading is each cell’s share of its measured row – categories are as unbalanced as rock types usually are, and raw counts would light the dominant row and hide what happens to a rare one – and the counts are written in the cells.
Contacts do not count: a rock type variable carries two measurements there, and neither alone is the truth the prediction is measured against. Locations missing either the measurement or the prediction are left out likewise.
Only honest on data the model has not seen: at a training location the prediction interpolates its own measurement, and the diagonal congratulates the model on remembering it. The out-of-fold container models.cross_validate fills is the honest input.
- Return type:
Figure
- swath(predicted, axis=0, bins=12, where=None, weights=None, quantiles=(0.05, 0.95), figsize=None)[source]
The data’s mean against the model’s, slab by slab along one axis.
The check that localizes conditional bias instead of aggregating it away: a model unbiased overall can run high in one part of the deposit and low in another, and only a mean per slab shows where. Two corrections make the comparison fair. The data’s means are declustered – the stored “declustering” column, else weights computed here – so a crowded patch of holes speaks once; and the model’s means run only over the ground the data informs, which where names. Where the model carries simulations the band between two quantiles of the realizations’ slab means is drawn, which a kriging swath cannot.
Draws the continuous variable when one was given, else the categorical one as stacked shares: the data’s declustered share of each category against the model’s mean predicted probability.
- Parameters:
predicted – The grid or block model carrying the model’s prediction.
axis (int or str) – Which coordinate the slabs cut across, by index or by label.
bins (int or sequence) – How many slabs, of equal width, or where their edges are.
where – Which locations of predicted take part: a boolean mask or the name of a boolean metadata column, as assign_from_data writes. Everything, by default.
weights – One declustering weight per sample, overriding the stored column.
quantiles – The two quantiles of the realizations’ slab means drawn as a band.
- Return type:
Figure
- proportions(predicted, where=None, weights=None, figsize=None)[source]
The data’s category shares against the model’s, one bar pair each.
The whole-model reading of the categorical swath: the declustered share of each category among the samples beside the model’s mean predicted probability of it – its expected share – over the ground where names, each block at its own volume. The confusion matrix is row-normalized and so cannot see a model that calls the dominant rock over ground the data never reached; this figure can.
- Parameters:
predicted – The grid or block model carrying the model’s prediction.
where – Which locations of predicted take part: a boolean mask or the name of a boolean metadata column, as assign_from_data writes. Everything, by default.
weights – One declustering weight per sample, overriding the stored column.
- Return type:
Figure
- contact(contacts, pair, domain=None, bins=6, max_distance=None, quantiles=(0.25, 0.75), figsize=None)[source]
The grade against its distance down the hole to a domain contact.
Contact analysis on the data alone, the figure the hard-or-soft boundary decision is read from: every sample placed by its signed distance down the hole to the nearest contact between the two domains of pair, the samples themselves faint behind the binned length-weighted mean, a band between two sample quantiles, and the counts below. A step at zero with flat profiles either side is a hard boundary; a ramp is a soft one, its width how far one domain’s estimate may borrow from the other.
- Parameters:
contacts – The contact points from DrillholeData.get_contacts.
pair – The two domain labels, in the order the axis runs: the first left of the contact, the second right of it.
domain – A categorical variable on the data naming each sample’s own domain, when the samples carry it, so a third domain beyond the far one stays off the profile.
bins – Bins of equal width on each side of the contact, or the edges.
max_distance – How far from the contact the profile reaches.
quantiles – The two sample quantiles drawn as a band.
- Return type:
Figure
- spread_check(bins=8, figsize=None)[source]
Whether the noise the model fitted is the noise the data has.
A residual holds two things at once – how wrong the model was about the ground, and how far the assay fell from the ground – so it is read against the two together. The band is the noise, the line the whole claim, the points what the errors actually did. On the line is calibrated, below it is hedging, above it is over-confident.
The level axis is what says which term is at fault. A warping bends, so the noise grows with the value while the model’s own uncertainty does not: a shortfall widening with the grade is the noise, a flat one is the posterior. Points inside the band alone are the plainest case – the fitted noise over-explains the errors by itself.
Only honest on data the model has not seen: at a training location it interpolates its own measurement, and the residual is not an error.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are. A count gives equal-count bins, since a predicted grade is skewed and equal width would leave the top bins with a sample each; pass np.linspace(…) to ask for equal width instead.
- Return type:
Figure
- variogram(n_lags=15, max_lag=None, direction=None, tolerance=45.0, residuals=False, decluster=True, figsize=None)[source]
The data’s spatial structure, against the fan the simulations make.
The experimental semivariogram of the measurements, with one thin curve per realization on the same pairs. A model that learned the spatial structure scatters its fan around the data’s curve; a kernel too smooth sags below it at short lags, and a nugget fitted into the range lifts it there. Neither shows in accuracy or spread_check, which judge one location at a time.
The measurements carry the likelihood noise and the realizations do not, so the fan is raised by what an independent error at each location adds to a semivariogram, taken from noise_variance. Without that the two curves are not the same quantity and every model looks over-smooth by a nugget.
With residuals=True it is the variogram of measured - predicted and the fan is dropped: structure left in the residuals is structure the model missed – honest on cross-validated predictions (models.cross_validate).
Each panel’s title carries VS, the variogram score of
geoml.metrics.variogram_score()over the same locations and weights: the eye’s verdict on the fan as one number, for comparing two models without squinting. Lower is better, but only against another model on the same data – the score keeps a bias the curves are corrected for, and never reaches zero.- Parameters:
n_lags (int) – Number of equal-width lag bins.
max_lag (float, optional) – Longest separation considered; half the bounding-box diagonal by default.
direction (array-like, optional) – Direction vector for a directional variogram; omnidirectional when absent. The anisotropy ellipsoid’s principal axes are the directions worth asking about.
tolerance (float) – Angular tolerance around direction, in degrees.
residuals (bool) – Variogram of the residuals instead, without the fan.
decluster (bool or float) – Weight pairs by cell-declustering weights, so that the curve estimates the field’s variogram rather than the sampling’s. True chooses the cell size, a number fixes it, False leaves the pairs raw.
- Return type:
Figure
- grade_tonnage(component=None, density=None, cutoffs=30, max_uncertainty=None, log_mass=False, figsize=None)[source]
How much material clears each cut-off, and how good it is.
Tonnage falls and grade rises as the cut-off climbs, and where the two cross is the question the curve is drawn to answer. Simulations are carried through one by one and drawn as a family, with the median over them picked out: the spread between the thin lines is what the model does not know about the answer.
- Parameters:
component (str) – Which grade, when the variable is a vector one. A cut-off applies to a single number, so there is nothing to guess here.
density (float or str) – A number, a metadata column, or a ContinuousVariable – and in that last case its simulations are matched with the grade’s, one to one. Without a density the curve is in volume.
cutoffs (int or array-like) – The grades to cut at, or how many of them to spread evenly across the range of the data.
max_uncertainty (float) – Leave out the blocks the model doubts more than this, reading the column named when the Explorer was built. A block the model cannot speak for is not tonnage, and counting it flatters the answer at exactly the cut-offs where there is least data to go on.
log_mass (bool) – Put the tonnage on a logarithmic scale. Most of a deposit clears the low cut-offs, so on a linear axis the high ones are a flat line along the bottom and the spread between the realizations there – which is where the decision usually is – cannot be seen at all. A cut-off that nothing clears has no logarithm and drops out of the curve rather than being drawn at the axis floor.
- Return type:
Figure
- dispersion_by_support(component=None, kind='box', alpha=0.2, most=5000, figsize=None, size=6)[source]
How much the ground varies inside a block, against the block’s size.
Only a BlockSet3D has blocks of several sizes. Every block is merged into its parent, level by level up to the coarsest, so each size the lattice has holds a distribution: the within-block standard deviation of every block of that size, the finest on the left. The line joins each size’s root mean square, the dispersion of the ground within blocks of that size. A parent is put together from the blocks inside it, realization by realization, and never predicted.
A block the refinement left whole reads its dispersion off its own sub-blocks, one position per child, and one put together from its descendants off all of theirs. Fewer positions see less of the ground, so at one size a block left whole reads lower than a split block over the same ground; kind=”jitter” colours every block by how many times the refinement split it, which is where that shows. Each size’s label gives how many blocks it holds and the share of the volume they cover: the fine sizes exist only where the refinement went, so the distributions are of different ground.
- Parameters:
component (str) – One component of a vector variable, drawn on its own.
kind (str) – “box”, “violin” or “jitter”, the last one point per block, coloured by how many times the refinement split it.
alpha (float) – How opaque each point is, for kind=”jitter”. Low by default: a size can hold thousands of blocks, and where they pile up is what there is to see.
most (int) – About how many blocks of each size kind=”jitter” draws, taken by striding through them; the box and the violin use them all.
size (float) – Point size, for kind=”jitter”.
- Return type:
Figure
See also
geoml.plots.prepare.dispersion_by_supportthe numbers drawn here.
- volume_dispersion(shells, kind='box', relative=False, alpha=0.2, size=18, figsize=None)[source]
How much the realizations’ meshes vary in volume, against the prediction’s.
For every cut-off or category of a MeshSet built with its realizations, the distribution of the realizations’ mesh volumes, with the prediction’s marked. The prediction is smoother than any realization, so its mesh tends to hold less volume at a high cut-off and more at a low one; how far it sits from the middle of the distribution is how far one mesh misreports the volume, and the spread is what no single mesh can show. The figure reads what the set measured as it was made, and loads no mesh.
- Parameters:
shells (MeshSet) – A MeshSet built with simulations=True.
kind (str) – “box”, “violin” or “jitter”, the last one point per realization.
relative (bool) – Whether to divide every volume by the prediction’s.
alpha (float) – How opaque each point is, for kind=”jitter”.
size (float) – Point size, for kind=”jitter”.
- Return type:
Figure
See also
geoml.plots.prepare.volume_dispersionthe numbers drawn here.
geoml.data.MeshSet.volume_dispersionthe same, as a table.
- connectivity(shells, figsize=None)[source]
Whether the ground above each cut-off holds together.
Two panels for a MeshSet: the share of each mesh’s volume in its largest piece, and how many pieces it is in. Read along the cut-offs the first is a connectivity curve – where it drops, the ground breaks into pods – and where the set holds realizations their P10 to P90 is drawn as a band, their median dashed.
- Parameters:
shells (MeshSet) – The set.
- Return type:
Figure
See also
geoml.plots.prepare.connectivitythe numbers drawn here.
- section(shells, axis, value, component=None, resolution=None, figsize=None)[source]
Every mesh of a set where it crosses a plane, over the model.
The lines each mesh draws on a plane across one axis, a colour per cut-off or category, over the prediction of the continuous variable this selection names – a grade under its own shells, or under a rock model’s contacts. Without a continuous variable, the lines alone.
- Parameters:
shells (MeshSet) – The set.
axis – The coordinate held fixed, by index or by label.
value (float) – Where along it the plane sits.
component (str) – For a vector variable, the component to draw beneath.
resolution (float) – The spacing the prediction is sampled at on the plane.
- Return type:
Figure
See also
geoml.plots.prepare.mesh_sectionthe numbers drawn here.
geoml.data.MeshSet.sectionthe lines, as arrays.
Plotly
The same figures, for looking at rather than for printing.
Interactive answers every question Explorer does, with the same method names and the same arguments, and hands back a plotly figure instead of a matplotlib one:
eda = geoml.plots.Interactive(point, continuous="Elements",
categorical="Rock")
eda.pairs().show()
eda.scene()
What that buys is the things a printed figure cannot do: zooming a panel of a scatter matrix takes its whole row and column with it, so a cluster can be followed across every pair at once; clicking a category in the legend takes it out of every panel; and a three-dimensional scene can be turned around.
Every trace drawn from locations carries, in customdata, the row of the container each of its points came from. That is what makes a Dashboard able to link one figure’s selection to another’s – see dashboard. Figures whose points are not locations, such as the training curve, carry none, and a simulated value is a realization rather than a place, so the simulated half of simulation_pairs carries none either.
The numbers all come from prepare, the same functions Explorer reads, so the two backends cannot drift into showing different things.
- class geoml.plots.interactive.Interactive(data, continuous=None, categorical=None, model=None, palette=None, cmap=None, uncertainty=None)[source]
Bases:
SelectionExploratory figures for one data set, as plotly figures.
Takes its arguments from base.Selection: the container, a continuous and a categorical variable, a model for the figures that need one, and the colours to draw them in. Every method mirrors the one of the same name on Explorer, down to the arguments, except that a figure is sized in pixels (height, width) rather than in inches.
- dashboard(figures=('scene', 'histogram', 'pairs'), **options)[source]
The named figures on one page, sharing a selection.
Each name is a method of this class, called with its own defaults. That is the short way; for figures drawn with arguments of your own, or captioned, or from more than one data set, build a Dashboard directly – it takes figures rather than names:
geoml.plots.Dashboard( [("Where the cadmium is", eda.scene(color="Cd")), eda.pairs(log=True, upper="density")], title="Jura", columns=2)
- Parameters:
figures (sequence of str) – Which figures, in the order they are to be laid out. Asking for one this Interactive cannot draw raises whatever that figure would have raised on its own – there is no guessing here about what a data set can support.
options – Passed to Dashboard: title, columns, plotlyjs, hint.
- histogram(bins=25, statistics=True, height=None, width=None)[source]
The distribution of the continuous variable, one panel per component.
Split by category when there is one: the populations are drawn over each other with a common set of bins, so their spreads can be compared rather than only their shapes.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are.
statistics (bool) – Sum each panel up in a box: the count, the mean, the standard deviation, the coefficient of variation, the skewness and the kurtosis, beside the minimum, the quartiles, the median and the maximum. They are of every measured value, the categories pooled, and the kurtosis is the excess over a normal’s. The box sits over the half of the bins with the lower bars, and the axis is raised so that none of those runs under it.
- Return type:
Figure
- pairs(kind='scatter', alpha=0.7, bins=60, log_counts=False, log=False, principal_components=0, upper=None, size=5, height=None, width=None)[source]
Every component against every other, with its distribution down the diagonal.
- Parameters:
kind (str) – “scatter”, or “hist2d” to count the points into cells instead, for when there are too many to draw one by one. Counts make a single surface, so the categories are pooled – and a counted cell is not a location, so a matrix drawn this way takes no part in a dashboard’s linked selection.
alpha (float) – How opaque each point is, for kind=”scatter”.
bins (int) – Cells along each axis, for kind=”hist2d”.
log_counts (bool) – Colour the cells by the logarithm of the count. Worth turning on whenever a few cells hold most of the data, which is most of the time with a skewed variable.
log (bool) – Draw the data on a log scale: the centred log-ratio for a composition, whose parts carry a constant sum, and an ordinary logarithm otherwise.
principal_components (int) – Draw this many principal components over the data, in the data’s own axes – the reverse of pca. Each is a line through the mean, one standard deviation of that component long either way.
upper (str) – What to put in the upper triangle, which is otherwise left empty: “hist2d” or “density” for where the mass is with the categories pooled, or “correlation” for the coefficient alone.
- Return type:
Figure
- pca(explained=0.9, log=False, kind='scatter', alpha=0.7, bins=60, log_counts=False, size=5, height=None, width=None)[source]
The same pairs plot, on principal components instead of measurements.
Only the components carrying explained of the variance are drawn. Each panel also holds the loadings: an arrow per original column, showing what it contributes to the two components on the axes. A composition is opened up with the centred log-ratio first.
- Parameters:
explained (float) – Share of the total variance to reach, between 0 and 1.
log (bool) – Take the components of the logarithms rather than of the measurements. A composition is opened up either way.
kind – As in pairs.
alpha – As in pairs.
bins – As in pairs.
log_counts – As in pairs.
- Return type:
Figure
- scene(color=None, clip=None, size=4, height=None, width=None)[source]
Where the data is, coloured by a variable.
The coordinates decide the drawing: a value against position in 1D, a map in 2D, a scatter that can be turned around in 3D – which is the one plotly is worth reaching for on its own account.
A three-dimensional scene takes no part in a dashboard’s linked selection. Plotly has no box or lasso over a 3D scatter to select with, and no per-point selection state on one to show a selection made elsewhere. Several of them on one page do turn together, though.
- Parameters:
color (str or list) –
The variable to colour by. Defaults to the continuous variable held, or the categorical one if that is all there is.
A list of them puts a menu on the figure instead, one entry per choice, and the ground is drawn once with the values swapped over it. That is the answer to several variables over the same body of rock: five grades become one scene with five entries on a menu rather than five scenes, which is lighter, and it holds the point cloud still while the variable changes – the comparison one is actually making. A name in the list may be a variable, one component of a vector variable, or a vector variable standing for all of its components, so color=[“Elements”] names every grade in it.
Only the locations measured in all of them are drawn; see prepare.color_choices.
clip (pair of floats) – Where to end the colour scale, as quantiles: [0, 0.99] for a variable with a long right tail, which is most assays, and [0.01, 0.99] to take both ends. Without it one value far from the rest takes the whole scale and leaves everything else in a single shade. Nothing is dropped – the points beyond the ends take the end colour, and the hover still reports what was measured. A menu clips each of its choices by its own quantiles, since they are different variables and share nothing but the ground. Has no effect in 1D, where the value is an axis rather than a colour.
- Return type:
Figure
- training_curve(window=None, height=None, width=None)[source]
The ELBO against the iteration it was measured at.
Every value is an estimate, from a sample of the latent variables and, under train_svi, a sample of the data too, so the curve is noisy whether or not training has settled. The running mean over it is the line to read: flat means finished, still climbing means it is not.
- Parameters:
window (int) – Points to average over. Defaults to a fiftieth of the log.
- Return type:
Figure
- transformed_pairs(kind='scatter', alpha=0.7, bins=60, log_counts=False, upper=None, size=5, height=None, width=None)[source]
The measurements as the model sees them, after its warping.
Two things worth checking before trusting a fitted model, and both are easier to see than to test. Down the diagonal, whether the warping made each variable Gaussian: the normal it is aiming at is drawn over each histogram. Off the diagonal, whether what is left is independent: a round cloud with a correlation near zero is what the model assumes, and a tilted or curved one is structure it will not capture.
The columns are numbered rather than named: a warping may rotate the data or bend it, so a column is generally a mixture of what was measured rather than any one of it.
- Parameters:
kind – As in pairs.
alpha – As in pairs.
bins – As in pairs.
log_counts – As in pairs.
upper – As in pairs.
- Return type:
Figure
- simulation_pairs(kind='hist2d', bins=60, log_counts=False, most=100000, margin=0.1, alpha=0.6, size=5, height=None, width=None)[source]
What was measured against what was simulated.
The measurements fill the lower triangle and the simulations the upper, in the same form and between the same limits, so the two halves of the matrix can be read against each other: a simulation that reproduces the data has an upper half that mirrors the lower one. Down the diagonal the measured histogram carries the simulated density over it, which is the same comparison one variable at a time.
Both halves are counted into cells by default. Simulations come in location-by-realization blocks that run to millions of values, and a scatter of that many points is a filled rectangle whatever its transparency.
- Parameters:
kind (str) – “hist2d”, the default here, or “scatter”.
bins – As in pairs.
log_counts – As in pairs.
alpha – As in pairs.
size – As in pairs.
most (int) – About how many simulated values to draw per component.
margin (float) – How far past the measured range to look, as a share of it. Simulated values outside that window are left out.
- Return type:
Figure
- prediction_scatter(component=None, kind='scatter', alpha=0.6, bins=60, log_counts=False, trim=None, size=6, height=None, width=None)[source]
What was predicted against what was measured.
A single variable is drawn with its two distributions along the sides, which is where a bias shows that the scatter alone hides: the same cloud can sit on the 1:1 line while the predicted values are packed into a narrower range than the real ones. Several components are drawn as a panel each; name one with component to get the margins for it.
- Parameters:
component (str) – One component of a vector variable, drawn on its own.
kind (str) – “scatter”, or “hist2d” for a block model, where the points are past counting.
alpha (float) – How opaque each point is.
bins (int) – Bins along each axis, for kind=”hist2d”.
log_counts (bool) – Colour the cells by the logarithm of the count.
trim (pair of floats) – Leave the outliers out, as quantiles: [0, 0.99] for a variable with a long right tail, which is most assays. Without it a few values far from the rest set the limits and squeeze everything else into a corner. The window runs from the lower quantile of the measured or the predicted values, whichever is lower, to the upper quantile of whichever is higher, and a location outside it on either axis is left out of the panel and its margins. Each panel is trimmed on its own, and counts in a corner how many it left out.
- Return type:
Figure
- accuracy(probabilities=None, height=None, width=None)[source]
Whether the simulated spread is the spread the errors actually have.
For each probability, the share of true values that fall inside the interval holding that share of the simulations. On the 1:1 line the model knows what it does not know; below it the intervals are too narrow for the errors they have to cover, and above it the model is hedging. Deutsch’s goodness statistic sums that up in one number.
The intervals come from the model rather than from the container: what is stored there is the ground, the likelihood noise having been integrated out, and an assay is a measurement of the ground rather than the ground itself. So this figure needs the model the selection was built with.
- Return type:
Figure
- reliability(bins=10, height=None, width=None)[source]
Whether a claimed probability is the frequency it claims.
One curve per category: the locations binned by the probability the model assigned to it, each bin’s mean claim against the share of its locations actually measured as that category. On the diagonal a 70% claim is that category 70% of the time; below it the model is overconfident, above it hedging. The legend carries each curve’s expected calibration error, the count-weighted mean distance from the diagonal.
The same locations count as in confusion_matrix: a contact has two measurements and no one truth, and locations missing either the measurement or the prediction are left out.
Only honest on data the model has not seen: at a training location the claim was fitted to its own outcome. The out-of-fold container models.cross_validate fills is the honest input.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are. A count gives equal-count bins over the claimed probabilities; pass explicit edges for equal width.
- Return type:
Figure
- confusion_matrix(height=None, width=None)[source]
What was measured against what the model called there, counted.
Rows are the measured categories, columns the predicted ones, so the diagonal is agreement and each row reads as one category’s fate. The shading is each cell’s share of its measured row – categories are as unbalanced as rock types usually are, and raw counts would light the dominant row and hide what happens to a rare one – and the counts are written in the cells.
Contacts do not count: a rock type variable carries two measurements there, and neither alone is the truth the prediction is measured against. Locations missing either the measurement or the prediction are left out likewise.
Only honest on data the model has not seen: at a training location the prediction interpolates its own measurement, and the diagonal congratulates the model on remembering it. The out-of-fold container models.cross_validate fills is the honest input.
- Return type:
Figure
- swath(predicted, axis=0, bins=12, where=None, weights=None, quantiles=(0.05, 0.95), height=None, width=None)[source]
The data’s mean against the model’s, slab by slab along one axis.
The check that localizes conditional bias instead of aggregating it away. The data’s means are declustered and the model’s run only over the ground where names – the reach assign_from_data writes – so the comparison describes the deposit rather than the drilling; with simulations, the band between two quantiles of the realizations’ slab means is drawn. Draws the continuous variable when one was given, else the categorical one as stacked shares.
A swath draws slab aggregates, not locations, so like variogram it carries no row index for the dashboard to link on.
- Parameters:
predicted – As in Explorer.swath.
axis – As in Explorer.swath.
bins – As in Explorer.swath.
where – As in Explorer.swath.
weights – As in Explorer.swath.
quantiles – As in Explorer.swath.
- Return type:
Figure
- proportions(predicted, where=None, weights=None, height=None, width=None)[source]
The data’s category shares against the model’s, one bar pair each.
The whole-model reading of the categorical swath: the data’s declustered share of each category beside the model’s expected share over the ground where names, each block at its own volume. Like swath it draws aggregates, not locations, so it carries no row index for the dashboard to link on.
- Parameters:
predicted – As in Explorer.proportions.
where – As in Explorer.proportions.
weights – As in Explorer.proportions.
- Return type:
Figure
- contact(contacts, pair, domain=None, bins=6, max_distance=None, quantiles=(0.25, 0.75), height=None, width=None)[source]
The grade against its distance down the hole to a domain contact.
Contact analysis on the data alone: every sample placed by its signed distance down the hole to the nearest contact between the two domains of pair, faint behind the binned length-weighted mean, a band between two sample quantiles, and the counts below. A step at zero is a hard boundary, a ramp a soft one. The samples are locations, so they carry the row index the dashboard links on.
- Parameters:
contacts – As in Explorer.contact.
pair – As in Explorer.contact.
domain – As in Explorer.contact.
bins – As in Explorer.contact.
max_distance – As in Explorer.contact.
quantiles – As in Explorer.contact.
- Return type:
Figure
- spread_check(bins=8, height=None, width=None)[source]
Whether the noise the model fitted is the noise the data has.
A residual holds two things at once – how wrong the model was about the ground, and how far the assay fell from the ground – so it is read against the two together. The band is the noise, the line the whole claim, the points what the errors actually did. On the line is calibrated, below it is hedging, above it is over-confident.
The level axis is what says which term is at fault. A warping bends, so the noise grows with the value while the model’s own uncertainty does not: a shortfall widening with the grade is the noise, a flat one is the posterior.
Only honest on data the model has not seen.
- Parameters:
bins (int or sequence) – How many bins, or where their edges are. A count gives equal-count bins; pass np.linspace(…) for equal width.
- Return type:
Figure
- variogram(n_lags=15, max_lag=None, direction=None, tolerance=45.0, residuals=False, decluster=True, height=None, width=None)[source]
The data’s spatial structure, against the fan the simulations make.
The experimental semivariogram of the measurements, with one thin curve per realization on the same pairs. A model that learned the spatial structure scatters its fan around the data’s curve; a kernel too smooth sags below it at short lags, and a nugget fitted into the range lifts it there. Neither shows in accuracy or spread_check, which judge one location at a time.
The measurements carry the likelihood noise and the realizations do not, so the fan is raised by what an independent error at each location adds to a semivariogram, taken from noise_variance. Without that the two curves are not the same quantity and every model looks over-smooth by a nugget.
With residuals=True it is the variogram of measured - predicted and the fan is dropped: structure left in the residuals is structure the model missed – honest on cross-validated predictions (models.cross_validate).
Each panel’s title carries VS, the variogram score of
geoml.metrics.variogram_score()over the same locations and weights: the eye’s verdict on the fan as one number, for comparing two models without squinting. Lower is better, but only against another model on the same data – the score keeps a bias the curves are corrected for, and never reaches zero.- Parameters:
n_lags (int) – Number of equal-width lag bins.
max_lag (float, optional) – Longest separation considered; half the bounding-box diagonal by default.
direction (array-like, optional) – Direction vector for a directional variogram; omnidirectional when absent.
tolerance (float) – Angular tolerance around direction, in degrees.
residuals (bool) – Variogram of the residuals instead, without the fan.
decluster (bool or float) – Weight pairs by cell-declustering weights, so that the curve estimates the field’s variogram rather than the sampling’s. True chooses the cell size, a number fixes it, False leaves the pairs raw.
- Return type:
Figure
- grade_tonnage(component=None, density=None, cutoffs=30, max_uncertainty=None, log_mass=False, height=None, width=None)[source]
How much material clears each cut-off, and how good it is.
Tonnage falls and grade rises as the cut-off climbs, and where the two cross is the question the curve is drawn to answer. Simulations are carried through one by one and drawn as a family, with the median over them picked out: the spread between the thin lines is what the model does not know about the answer.
Two scales share one frame, so each gets gridlines in the colour of its own curve – a reader following the grade curve to a grey line would otherwise read a tonnage off it.
- Parameters:
component (str) – Which grade, when the variable is a vector one.
density (float or str) – A number, a metadata column, or a ContinuousVariable – and in that last case its simulations are matched with the grade’s, one to one. Without a density the curve is in volume.
cutoffs (int or array-like) – The grades to cut at, or how many of them to spread evenly across the range of the data.
max_uncertainty (float) – Leave out the blocks the model doubts more than this, reading the column named when the Interactive was built.
log_mass (bool) – Put the tonnage on a logarithmic scale. Most of a deposit clears the low cut-offs, so on a linear axis the high ones are a flat line along the bottom and the spread between the realizations there – which is where the decision usually is – cannot be seen. A cut-off that nothing clears has no logarithm and drops out.
- Return type:
Figure
- dispersion_by_support(component=None, kind='box', alpha=0.2, most=5000, size=5, height=None, width=None)[source]
How much the ground varies inside a block, against the block’s size.
Only a BlockSet3D has blocks of several sizes. Every block is merged into its parent, level by level up to the coarsest, so each size the lattice has holds a distribution: the within-block standard deviation of every block of that size, the finest on the left. The line joins each size’s root mean square, the dispersion of the ground within blocks of that size. A parent is put together from the blocks inside it, realization by realization, and never predicted.
A block the refinement left whole reads its dispersion off its own sub-blocks, one position per child, and one put together from its descendants off all of theirs. Fewer positions see less of the ground, so at one size a block left whole reads lower than a split block over the same ground; kind=”jitter” colours every block by how many times the refinement split it, which is where that shows. Each size’s label gives how many blocks it holds and the share of the volume they cover: the fine sizes exist only where the refinement went, so the distributions are of different ground.
A block put together from others is a row of no container, so the points carry no row for a dashboard to link on.
- Parameters:
component (str) – One component of a vector variable, drawn on its own.
kind (str) – “box”, “violin” or “jitter”, the last one point per block, coloured by how many times the refinement split it.
alpha (float) – How opaque each point is, for kind=”jitter”. Low by default: a size can hold thousands of blocks, and where they pile up is what there is to see.
most (int) – About how many blocks of each size kind=”jitter” draws, taken by striding through them; the box and the violin use them all.
size (float) – Point size, for kind=”jitter”.
- Return type:
Figure
See also
geoml.plots.prepare.dispersion_by_supportthe numbers drawn here.
- volume_dispersion(shells, kind='box', relative=False, alpha=0.2, size=6, height=None, width=None)[source]
How much the realizations’ meshes vary in volume, against the prediction’s.
For every cut-off or category of a MeshSet built with its realizations, the distribution of the realizations’ mesh volumes, with the prediction’s marked. The prediction is smoother than any realization, so its mesh tends to hold less volume at a high cut-off and more at a low one; how far it sits from the middle of the distribution is how far one mesh misreports the volume, and the spread is what no single mesh can show. The figure reads what the set measured as it was made, and loads no mesh.
A mesh volume is a number about a whole realization, a row of no container, so the points carry no row for a dashboard to link on.
- Parameters:
shells (MeshSet) – A MeshSet built with simulations=True.
kind (str) – “box”, “violin” or “jitter”, the last one point per realization.
relative (bool) – Whether to divide every volume by the prediction’s.
alpha (float) – How opaque each point is, for kind=”jitter”.
size (float) – Point size, for kind=”jitter”.
- Return type:
Figure
See also
geoml.plots.prepare.volume_dispersionthe numbers drawn here.
geoml.data.MeshSet.volume_dispersionthe same, as a table.
- connectivity(shells, height=None, width=None)[source]
Whether the ground above each cut-off holds together.
Two panels for a MeshSet: the share of each mesh’s volume in its largest piece, and how many pieces it is in. Read along the cut-offs the first is a connectivity curve – where it drops, the ground breaks into pods – and where the set holds realizations their P10 to P90 is drawn as a band, their median dashed.
- Parameters:
shells (MeshSet) – The set.
- Return type:
Figure
See also
geoml.plots.prepare.connectivitythe numbers drawn here.
- section(shells, axis, value, component=None, resolution=None, height=None, width=None)[source]
Every mesh of a set where it crosses a plane, over the model.
The lines each mesh draws on a plane across one axis, a colour per cut-off or category, over the prediction of the continuous variable this selection names – a grade under its own shells, or under a rock model’s contacts. Without a continuous variable, the lines alone.
- Parameters:
shells (MeshSet) – The set.
axis – The coordinate held fixed, by index or by label.
value (float) – Where along it the plane sits.
component (str) – For a vector variable, the component to draw beneath.
resolution (float) – The spacing the prediction is sampled at on the plane.
- Return type:
Figure
See also
geoml.plots.prepare.mesh_sectionthe numbers drawn here.
geoml.data.MeshSet.sectionthe lines, as arrays.
Several figures on one page, sharing a selection.
A figure answers one question. Most questions worth asking of spatial data are about two things at once – those samples, the ones out on the tail of the histogram, where are they on the map? – and no single figure answers that. A dashboard does: drag a box over any panel and the same locations light up in every other one.
eda = geoml.plots.Interactive(point, continuous="Elements",
categorical="Rock")
board = eda.dashboard()
board.write_html("jura.html")
board renders itself in a notebook, and write_html writes a page that carries plotly with it, so it can be sent to someone who has neither geoML nor a network.
Take the long way for control over what goes on it, and over how it is laid out – a nested entry is a row of its own, so the rows need not be equal:
geoml.plots.Dashboard(
[("Where the samples are", eda.scene(color="Cd")),
[eda.histogram(), eda.training_curve()],
eda.pairs(log=True, upper="density")],
title="Jura, before modelling")
What links the panels is customdata: every trace Interactive draws from locations carries the row of the container each of its points came from, and the script this module writes matches selections by those rows. Anything without them – a counted cell, a simulated value, a training curve, a 3D scene – is simply left alone when a selection is made, which is the honest answer for a panel whose points are not places.
3D scenes are linked the other way. There is no brushing them, but there is turning them, and a page of several answers a question one of them cannot: the same body of ground under two variables, seen from the same angle. Turn one and the rest follow.
- class geoml.plots.dashboard.Dashboard(figures, title=None, columns=2, plotlyjs='embed', hint=True)[source]
Bases:
objectA page of figures whose selections are shared.
- Parameters:
figures (list) –
The figures, in the order they are to be laid out. An entry may be:
a figure;
a `(caption, figure)` pair – the caption replaces whatever title the figure came with, which is how the same figure serves in one place under its own name and in another under the question it is there to answer;
a list or tuple of either, which is a row of its own, however many panels are in it.
Nest one entry and the whole list is read as rows, a bare figure counting as a row of one; nest none and the figures are dealt out columns to a row. Rows are what a page usually wants: a map beside a histogram, and the scatter matrix that goes with them across the full width underneath.
- Dashboard([eda.scene(),
[eda.histogram(), eda.training_curve()], eda.pairs()])
title (str) – The heading over the page.
columns (int) – How many panels to a row, when the rows were not given outright. A narrow window falls back to one, whatever this says.
plotlyjs (str) – “embed” writes plotly into the page, some four megabytes of it, and the result opens anywhere with no network at all – which is what makes it a report rather than a link. “cdn” fetches the library instead, leaving a file small enough to keep in a repository or a notebook, and needing a connection to open.
hint (str or bool) – The line under the heading saying what can be done with the page. False leaves it out.
- height(page=1000)[source]
How tall the page comes out, on one page pixels wide.
Only an estimate, and only wanted for the height to give the frame a notebook shows this in: the panels are laid out by the browser, and what they end up as depends on the width they are given. A row is as tall as the tallest panel in it, and a panel is as tall as its own proportions make it once it has been given its share of the width.
The arithmetic behind them
prepare holds what both backends read and imports no plotting library, so
the numbers behind a figure can be had without drawing it — which is also
how they are tested.
The numbers behind the figures.
Nothing here draws anything, and nothing here imports matplotlib. A figure is one way of looking at these arrays; a dashboard is another, and it reads the same functions rather than working the values out a second time. It also means the arithmetic – which components a fraction of variance asks for, what a composition looks like once it is opened up – can be tested against numbers instead of against pictures.
- geoml.plots.prepare.variable(container, name)[source]
The variable called name, or a message saying what there is.
- geoml.plots.prepare.variable_or_component(container, name)[source]
The variable called name, or the component of a vector variable.
A composition is held as one variable, so its parts are not in container.variables and asking for “Zn” would otherwise mean reaching into Elements by hand. The search is the container’s own – this module kept a near-copy of it for years, which is the duplication the path work was started to remove.
- geoml.plots.prepare.numeric_values(var)[source]
A continuous or vector variable as a matrix, and the rows that hold one.
- Returns:
values (array) – (n_data, n_columns), whatever the variable’s length.
measured (array) – Boolean, (n_data,). A vector variable is measured at a location only where every component is.
labels (list) – A name per column.
- geoml.plots.prepare.axis_label(part)[source]
A continuous variable’s name, with its unit where it declares one.
- geoml.plots.prepare.category_values(var)[source]
A categorical variable as one label per location.
- Returns:
values (array) – The label at each location; the empty string where nothing was measured, which is how a missing code reads.
measured (array) – Boolean, (n_data,).
labels (list) – The categories actually present, in the variable’s own order.
- geoml.plots.prepare.groups(values, measured, labels)[source]
A boolean mask per category, in the order the labels come in.
- geoml.plots.prepare.centred_log_ratio(values)[source]
A composition opened up into ordinary numbers.
Proportions carry a constant sum, so their covariance is negative by construction and a PCA of them describes the constraint as much as the data. The log-ratios have no such constraint. This is warping’s CenteredLogRatio in NumPy, for exploring rather than for modelling.
- Return type:
NDArray[float64]
- geoml.plots.prepare.logarithm(values, compositional=False)[source]
The data on a log scale, by the road that suits it.
A composition takes the centred log-ratio: its parts carry a constant sum, and logging them one at a time leaves that constraint in place, whereas dividing each by the row’s geometric mean first removes it. Anything else takes an ordinary logarithm.
Non-positive values stop this rather than quietly becoming infinities: which columns they are in is worth knowing, since a zero in an assay usually means below detection rather than absent, and what to put in its place is a decision about the data, not about the figure.
- Return type:
NDArray[float64]
- geoml.plots.prepare.principal_components(values, explained=0.9)[source]
The principal components of values, down to a share of the variance.
Follows warping.PCA: an eigendecomposition of the covariance, taken largest first. The scores are the plain projection, which is what a biplot puts on its axes; the loadings are the eigenvectors, for the arrows.
- Parameters:
values (array) – (n_data, n_columns).
explained (float) – The share of the total variance to reach. The number of components is the fewest that reach it – never fewer than two, so that there is a plot to draw, and never more than there are columns.
- Returns:
dict – scores (n_data, n_components), loadings (n_columns, n_components), ratio (the share each component carries, all of them), n_components, and – for drawing the components back onto the data they came from – mean, the centre they turn about, and eigenvalues, the variance along each, whose square root is a length in the data’s own units.
- geoml.plots.prepare.component_analysis(var, explained=0.9, log=False)[source]
principal_components for a variable, on a log scale if asked.
A composition is opened up whether or not log is set, and that is not an oversight: its parts carry a constant sum, so their covariance is singular and the first component of the raw proportions would spend itself describing the closure rather than the data. There is no useful PCA of a composition to refuse it. Everything else is left as measured unless log says otherwise.
- geoml.plots.prepare.warped_values(model, name)[source]
The measurements as the model sees them, after its warping.
A model does not work on the data as measured: the likelihood’s warping takes it somewhere it can be treated as Gaussian and, for several variables at once, uncorrelated. Whether it succeeded is a thing to look at rather than assume, and looking at it means passing the data through the same warping the model trained with – already fitted, since VGPNetwork initializes each likelihood from the measurements.
Only measured rows go through. get_measurements fills the rest with 1.0 to keep the array rectangular, and a warping has no reason to be kind to a value that was never there.
- Returns:
values (array) – (n_measured, warping.size_out).
measured (array) – Boolean, (n_data,), saying which rows those are.
labels (list) – A name per column.
- geoml.plots.prepare.simulation_sample(var, most=100000)[source]
The simulated values, thinned to a number that can be drawn.
A simulated variable holds n_data x n_sim values per component, which on a block model runs to hundreds of millions – more than a figure can show and, spread across every pair of a matrix, more than memory should be asked to hold. What a distribution looks like is settled by far fewer, so a stride is taken through the block – and taken while reading, a band of locations at a time, so the values that are thrown away are never all in memory together either.
The stride is the same for every component, and the mask that drops non-finite values is applied to whole rows. Thinning the components separately would pair the value simulated at one location with the value simulated at another, which is not a pair the model ever produced – the joint shape, which is the thing being looked at, would be an artefact.
- Returns:
values (array) – (n_sample, n_columns), columns in the variable’s own order.
- Return type:
NDArray[float64]
- geoml.plots.prepare.padded_range(values, margin=0.1)[source]
The span of each column, opened up by margin at both ends.
Returns a (low, high) pair per column. A column of one repeated value has no span to open up, so it is given a unit of room rather than a window of zero width.
- geoml.plots.prepare.color_limits(values, clip=None)[source]
The ends of a colour scale, set by where the data mostly is.
One value far from the rest takes the whole of a colour scale with it and leaves everything else in a single shade – the usual fate of a geochemical assay, whose tail is long and whose interest is not in it. Naming a pair of quantiles ends the scale where the data mostly ends instead: [0, 0.99] for a variable skewed to the right, [0.01, 0.99] to take both tails.
Nothing is dropped and nothing is altered. The values keep their own numbers and a hover still reports what was measured; what is bounded is the scale, so the few beyond it take the end colour rather than setting it.
Returns (low, high), or None when clip is None – and None again when the two ends come out equal, since a scale of zero width is no scale.
- geoml.plots.prepare.color_choices(container, names)[source]
Several variables’ values over the locations that carry all of them.
For a figure that keeps one set of points and swaps the values over them. The locations are those measured in every one of the variables named, which is the point: a cloud that gained and lost points as the choice changed would be two things changing at once, and the comparison – this variable against that one, here – is exactly what would be lost.
A name may be a variable, one component of a vector variable, or a vector variable itself, which stands for all of its components in order. So [“Elements”] names seven grades and [“Cd”, “Zn”] names two.
- Returns:
values (array) – (n_kept, n_columns), a column per choice in the order asked for.
rows (array) – Which locations those are, as indices into the container.
labels (list) – A name per column.
- Return type:
tuple[NDArray[float64], NDArray[integer], list]
- geoml.plots.prepare.cells(n_points, most)[source]
How many cells to count n_points into, at most most of them.
The two halves of a comparison rarely hold comparable numbers – a few hundred measurements against a hundred thousand simulated values – and binning both the same way leaves the sparse half as scattered single counts that read as noise. Roughly the square root of the count keeps several points in a typical cell either way.
- geoml.plots.prepare.counts_2d(x, y, bins=60, log=False)[source]
Points counted into cells, with the empty ones left empty.
A cell holding nothing comes back as NaN rather than as zero, so that whatever draws it can leave it unpainted: painted, it takes the bottom of the colour scale and fills the panel with a background that looks like data.
- Returns:
dict – x and y, the cell centres; z, what the colour is to be taken from, which is the count or its base-ten logarithm; and count, the count itself, so that a hover can say how many points a cell holds whichever of the two is being coloured. z and count are shaped (n_y, n_x), the way an image is indexed.
- geoml.plots.prepare.density_grid(x, y, grid=60, most=4000)[source]
Smoothed density over a pair of columns, on a regular mesh.
The estimate costs one pass over the data for every mesh cell, so a long column is thinned first: a density is a shape, and a few thousand points settle it as well as a few million do.
Returns (x_axis, y_axis, density) with density shaped (grid, grid) – rows along y_axis, as an image is – or None when there is nothing to smooth: fewer than three points, or a column that never varies, which has no width for a kernel to sit in.
- geoml.plots.prepare.density_curve(values, limits, points=200, most=5000)[source]
A smoothed distribution as a line, across limits.
Thinned as density_grid is, and for the same reason. Returns (grid, density), or None when there is nothing to smooth.
- geoml.plots.prepare.normal_curve(values, low, high, points=200)[source]
The normal of the same mean and spread as values, across a window.
Fitted rather than standard, so that the curve asks about the shape alone. A warping’s parameters are trained along with everything else and need not leave the data at unit variance – the GP’s amplitude absorbs a scale factor – so a standard normal would call a perfectly symmetric result skewed. Returns (x, density), or None for a column that never varies.
- geoml.plots.prepare.summary_statistics(values)[source]
The numbers a distribution is summed up by.
The moments are those of the values themselves, in their population form, and the kurtosis is the excess over a normal’s, so a normal reads 0 on the skewness and the kurtosis alike. The coefficient of variation is the standard deviation over the mean, and missing where the mean is not positive, which leaves it meaning nothing. Values that are not finite are left out.
- Returns:
dict – n, mean, std, cv, skewness, kurtosis, min, q1, median, q3 and max.
- Return type:
dict[str, float]
- geoml.plots.prepare.statistics_lines(statistics)[source]
Summary statistics as lines of text for a box in a monospace font.
Two columns, aligned: the count and the moments on the left, the order statistics on the right. A value in the variable’s units keeps four significant figures and a ratio – the coefficient of variation, the skewness, the kurtosis – two decimals. A number that is missing reads as a dash.
- Return type:
list[str]
- geoml.plots.prepare.statistics_side(counts)[source]
The upper corner a box of statistics takes over a histogram.
The one over whichever half of the bins has the lower bars: the right, for the long tail of an assay.
- Parameters:
counts (ArrayLike) – The height of the tallest bar in each bin, left to right.
- Returns:
str – “left” or “right”.
- Return type:
str
- geoml.plots.prepare.statistics_top(counts, edges, low, high, height)[source]
How tall a histogram’s axis has to be for its bars to clear a box.
Every bar whose bin reaches under the box has to end below it, with a little room to spare; the bars beside it may run as high as they like.
- Parameters:
counts (ArrayLike) – The height of the tallest bar in each bin, left to right.
edges (ArrayLike) – The bins’ edges.
low (float) – Where the box starts and ends along the axis, in the axis’s units.
high (float) – Where the box starts and ends along the axis, in the axis’s units.
height (float) – How much of the axis’s height the box covers, from the top.
- Returns:
float – The top the axis needs.
- Return type:
float
- geoml.plots.prepare.continuous_parts(var)[source]
The continuous columns a figure draws, one per component.
A scalar variable is its own single part; a vector or compositional one comes back as its components, in label order. Everything that reads measurements, prediction, noise_variance or the simulations asks through this, so a figure handed a categorical variable is refused by name rather than failing later at whichever column it reached for first.
- Parameters:
var – A variable, as
variable()returns it.- Returns:
list of geoml.data.ContinuousVariable – One part for a scalar variable, one per component otherwise.
- Raises:
ValueError – If the variable carries no continuous columns of its own.
- Return type:
list[ContinuousVariable]
- geoml.plots.prepare.component_names(var)[source]
The components a caller can name, ready to drop into a message.
Every figure that needs one grade out of several says so the same way, and each of them used to reach for labels – an attribute only some variables have. Asking through continuous_parts keeps that reach in one place.
- Return type:
str
- geoml.plots.prepare.prediction_values(container, name)[source]
What was measured against what was predicted, component by component.
- Returns:
measured_values, predicted_values (array) – (n_compared, n_columns), holding only the locations that carry both.
labels (list) – A name per column.
rows (array) – Which locations those are, as indices into the container. A figure that can be brushed needs to say which row each of its points came from, and only the rows carrying both a measurement and a prediction are drawn.
- geoml.plots.prepare.inside_trim(measured, predicted, trim=None)[source]
Which locations a predicted-against-measured panel keeps under a trim.
A few values far from the rest set the limits of such a panel and squeeze everything else into a corner of it. Naming a pair of quantiles sets a window instead, running from the lower quantile of the measured or the predicted values, whichever is lower, to the upper quantile of whichever is higher; a location outside it on either axis is left out. [0, 0.99] takes off the long right tail of an assay, [0.01, 0.99] both ends.
Returns a boolean mask over the locations, every one kept when trim is None.
- geoml.plots.prepare.step_path(lo, hi, values)[source]
A per-bin value as a polyline that steps at the edges.
One number per bin is not a curve, and drawing it through the bin centres says it is. Stepping at the edges shows where the bins are without a second thing on the figure to say so. A gap between bins comes back as a break rather than a line across it.
- geoml.plots.prepare.reliability(container, name, bins=10)[source]
Whether a claimed probability is the frequency it claims.
The categorical half of what the accuracy figure asks of a continuous variable: for each category, the locations are binned by the probability the model assigned to it, and each bin’s mean claim is set against the share of its locations actually measured as that category. On the diagonal a 70% claim is that category 70% of the time; below it the model is overconfident, above it hedging. The expected calibration error summarizes a curve as the count-weighted mean distance from the diagonal.
The same locations count as in confusion_matrix: a contact carries two measurements and no one truth, and locations missing either the measurement or the prediction are left out.
Only honest on data the model has not seen: at a training location the claim was fitted to its own outcome. The out-of-fold container models.cross_validate fills is the honest input.
- Parameters:
container (_SpatialData) – Any container from the data module.
name (str) – The name of a categorical variable holding measurements and predicted probabilities.
bins (int | Sequence[float]) – How many bins, or where their edges are. A count gives equal-count bins over the claimed probabilities — for a rare category the claims pile up near zero and for a dominant one near one, and equal width would leave most bins holding nothing — so pass explicit edges to ask for equal width instead.
- Returns:
panels (list of dict) – One per category, in the variable’s own order: label; claimed and observed, one pair per non-empty bin; count, the locations in each; ece, the count-weighted mean |observed - claimed|.
- Raises:
TypeError – If the variable holds no measured categories or no components.
ValueError – If no location carries both a measurement and a probability.
- Return type:
list[dict]
- geoml.plots.prepare.confusion_matrix(container, name)[source]
Measured categories against predicted ones, counted.
Rows are what was measured, columns what the model called there. Only the unambiguous locations count: a rock type variable carries two measurements per location, and where they disagree the point lies on a contact and there is no one truth for the prediction to be right about; locations missing either the measurement or the prediction are left out likewise.
Only honest on data the model has not seen: at a training location the prediction interpolates its own measurement. The out-of-fold container models.cross_validate fills is the honest input.
- Parameters:
container (_SpatialData) – Any container from the data module.
name (str) – The name of a categorical variable holding measurements and predictions.
- Returns:
table (dict) – counts – an integer matrix, measured by predicted; share – each cell as a share of its measured row, zero where a row holds nothing; labels – one name per row and column, the categories present in either role in the variable’s own order, with any measured category the variable does not model appended after; agreement – the diagonal’s share of the total.
- Raises:
TypeError – If the variable holds no measured and predicted categories.
ValueError – If no location carries both.
- Return type:
dict
- geoml.plots.prepare.spread_check(container, name, bins=8)[source]
What a model claims a value’s spread is, against what it turned out to be.
A residual holds two things at once – how wrong the model was about the ground, and how far the assay fell from the ground – so it can only be read against the two together. This lays all three out along the predicted value: the noise the model fitted, the whole spread it claims, and the spread the errors actually had.
Reading it: the observed points on the claimed line means calibrated, below it means hedging, above it means over-confident. The level axis is what says which term is at fault. A warping bends, so the noise grows with the value while the model’s own uncertainty does not, and a shortfall that widens with the grade is the noise where a flat one is the posterior. Observed points sitting inside the noise band alone are the plainest case of all: the fitted noise over-explains the errors by itself.
Only honest on data the model has not seen. At a training location the model interpolates its own measurement and the residual is not an error.
- Parameters:
container (_SpatialData) – Point data carrying measurements and a prediction.
name (str) – The variable.
bins (int or sequence) – How many bins, or where their edges are. A count gives equal-count bins; positions are taken as they come, so np.linspace(…) is how to ask for equal width.
- Returns:
list of dict – One per component, with label, the bin bounds lo/hi, the mean predicted value in each centre, the count, the observed root mean square residual and its observed_error, and the claimed noise and total spreads.
- Return type:
list[dict]
- geoml.plots.prepare.swath(data, predicted, name, axis=0, bins=12, weights=None, where=None, quantiles=(0.05, 0.95))[source]
The data’s mean against the model’s, slab by slab along one axis.
The check that localizes conditional bias instead of aggregating it away: a model that runs high in the north and low in the south can score unbiased overall, and only a mean per slab shows where it drifts. Two corrections make the comparison fair, and without them the figure describes the drilling rather than the deposit. The data’s means are declustered – explicit weights, else the “declustering” column container.decluster() keeps, else cell weights computed here – so a crowded patch of holes speaks once. The model’s means run only over the ground its data informs, which where names: the boolean column assign_from_data writes, or any mask. Over a block model each block counts at its own volume.
Where the model carries simulations, the slab mean of every realization is taken as well and the band between two of their quantiles reported, which a kriging swath cannot draw.
- Parameters:
data (_SpatialData) – The samples, carrying measurements of name.
predicted (_SpatialData) – The grid or block model carrying the model’s prediction of it.
name (str) – The variable.
axis (int | str) – Which coordinate the slabs cut across, by index or by label.
bins (int | Sequence[float]) – How many slabs, or where their edges are. A count gives slabs of equal width over the range both containers span.
weights (ArrayLike | None) – One declustering weight per sample, overriding the stored column.
where (ndarray | Sequence[int] | str | None) – Which locations of predicted take part: a boolean mask, or the name of a boolean metadata column. Everything, by default.
quantiles – The two quantiles of the realizations’ slab means drawn as a band.
- Returns:
list of dict – One per component, with label, the slab bounds lo/hi and centre, axis, then data_mean, data_weight (the summed weights, an effective count) and data_count, model_mean and model_count, band_lo/band_hi (None without simulations), and cell, the declustering cell when the weights were computed here.
- Return type:
list[dict]
See also
categorical_swaththe same figure for a categorical variable.
geoml.data.PointData.declusterthe stored weights this reads.
geoml.data.PointData.assign_from_datathe stored reach where names.
- geoml.plots.prepare.categorical_swath(data, predicted, name, axis=0, bins=12, weights=None, where=None)[source]
The data’s category shares against the model’s, slab by slab.
The categorical swath: per slab, the declustered share of each category among the samples against the model’s mean predicted probability of it – its expected share – over the locations where names, each block at its own volume. The shares of a slab sum to one on both sides, which is what lets them stack.
The declustering weights are explicit weights or the stored “declustering” column; a categorical variable offers no values to compute a cell from, so without either the shares are raw, which is said in the result.
- Parameters:
data (_SpatialData) – As in swath.
predicted (_SpatialData) – As in swath.
name (str) – As in swath.
axis (int | str) – As in swath.
bins (int | Sequence[float]) – As in swath.
weights (ArrayLike | None) – As in swath.
where (ndarray | Sequence[int] | str | None) – As in swath.
- Returns:
dict – labels, axis, the slab bounds lo/hi and centre, data_share and model_share ((n_slabs, n_labels)), data_weight, data_count, model_count, and declustered.
- Return type:
dict
- geoml.plots.prepare.proportions(data, predicted, name, weights=None, where=None)[source]
The data’s category shares against the model’s, over the whole model.
categorical_swath without the slabs: the declustered share of each category among the samples against the model’s mean predicted probability of it – its expected share – over the locations where names, each block at its own volume. Both sides sum to one. It is the global check the row-normalized confusion matrix cannot make: a model can place every measured sample in its right category and still call the dominant rock over ground the data never reached.
- Parameters:
data (_SpatialData) – As in swath.
predicted (_SpatialData) – As in swath.
name (str) – As in swath.
weights (ArrayLike | None) – As in swath.
where (ndarray | Sequence[int] | str | None) – As in swath.
- Returns:
dict – labels, data_share and model_share (one per label), data_count, model_count, and declustered.
- Return type:
dict
- geoml.plots.prepare.contact(data, name, contacts, pair, domain=None, bins=6, max_distance=None, quantiles=(0.25, 0.75))[source]
A grade against its distance down the hole to a domain contact.
Contact analysis on the data alone: every sample is placed by its signed distance down the hole to the nearest contact between the two domains of pair in the same hole – negative on the first domain’s side, positive on the second’s – and the samples are binned by that distance, each bin reporting its length-weighted mean, its count and two sample quantiles. A hard boundary reads as a step at zero with flat profiles either side; a soft one as a ramp, whose width is how far one domain’s estimate may borrow from the other.
The distance is measured down the hole, not through the rock: exact where a hole crosses the contact squarely and stretched where it runs oblique to it, as every downhole contact analysis is.
- Parameters:
data (_SpatialData) – Samples from DrillholeData.as_point_data, carrying the HOLEID and DEPTH metadata that conversion writes, and LENGTH for the weighting.
name (str) – The continuous variable to profile.
contacts (_SpatialData) – The contact points from DrillholeData.get_contacts, carrying the domain above and below each.
pair (tuple[str, str]) – The two domain labels, in the order the axis runs: negative distances on the first’s side, positive on the second’s.
domain (str | None) – A categorical variable on data naming each sample’s own domain, when the samples carry it: samples logged as neither of the pair are then left out, which keeps a third domain beyond the far one off the profile.
bins (int | Sequence[float]) – How many bins of equal width on each side of the contact, or where their edges are.
max_distance (float | None) – How far from the contact the profile reaches; the furthest placed sample by default.
quantiles (tuple[float, float]) – The two sample quantiles reported per bin.
- Returns:
dict – pair, label, the bin bounds lo/hi and centre, mean, count, band_lo/band_hi and quantiles; the placed samples’ distance, value and rows; n_unplaced (samples in holes without a contact of the pair), n_outside (samples domain excluded) and weighted (whether by LENGTH).
- Return type:
dict
- geoml.plots.prepare.variogram(container, name, n_lags=15, max_lag=None, direction=None, tolerance=45.0, max_pairs=2000000, residuals=False, decluster=True)[source]
The data’s spatial structure, against the fan the simulations reproduce.
The experimental semivariogram of the measured values, and one curve per realization computed on the same pairs: a model that learned the spatial structure scatters its realizations around the data’s curve, a kernel too smooth sags below it at short lags, and a nugget fitted into the range lifts it there. Neither shows in the marginal checks (accuracy, spread_check), which is what this figure exists for.
The two curves are put on the same footing before being compared. The measurements carry the likelihood noise and the stored realizations do not (predict integrates it out), so the fan is raised by what that noise adds to a semivariogram. For noise independent between locations that is exactly the pair-averaged (var_i + var_j) / 2, taken from the noise_variance column and added bin by bin – no draw, no seed, and exact in expectation rather than approximated. Without the correction the fan sits a nugget below the data at every lag and every model looks over-smooth. A container predicted without include_noise carries no such column and its fan is left where it is.
Pairs are declustered by default, which is the other half of making the comparison fair and matters more than it sounds. Samples follow the ore, so an experimental variogram computed on raw pairs describes the sampling as much as the field: on the bundled Walker Lake set, whose exhaustive truth is known, the raw sample curve runs 1.4 times the true variogram at the sill and 2.3 times at the shortest lag, and a model matching the field perfectly would look far too smooth against it. Each pair is therefore weighted by w_i * w_j from
geoml.math.geometry.declustering_weights(), and the sill likewise, with the same weights used for the fan so that both sides estimate the same thing. Pass decluster=False for the raw curve, or a number to fix the cell size rather than let it be chosen.With residuals=True the variogram is of measured - predicted instead and the fan is omitted: structure left in the residuals is structure the model missed, and on cross-validated predictions (models.cross_validate) it is honest. Whatever the flag, at a training location a model interpolates its own measurement, so the fan is only worth reading against data the model has not seen or with the data curve as the anchor.
- Parameters:
container (_SpatialData) – Point data carrying measurements (and simulations, for the fan).
name (str) – The variable.
n_lags (int) – Number of equal-width lag bins between zero and max_lag.
max_lag (float, optional) – The longest separation considered. Half the bounding-box diagonal by default.
direction (array-like, optional) – A direction vector for a directional variogram. Omnidirectional when absent. Call once per direction to compare them – the anisotropy ellipsoid’s principal axes are the ones worth asking about.
tolerance (float) – Angular tolerance around direction, in degrees.
max_pairs (int) – The pair budget. Past it the locations are strided down – deterministic, so two calls agree.
residuals (bool) – Variogram of measured - predicted rather than of the measurements, with no fan.
decluster (bool or float) – Weight pairs by cell-declustering weights, so that the curve estimates the field’s variogram rather than the sampling’s. True chooses the cell size, a number fixes it, False leaves the pairs raw.
- Returns:
list of dict – One per component, with label, the bin centres lag, the pair count per bin, the data curve data, the sample variance sill, and realizations – an (n_realizations, n_lags) array, or None when there is nothing simulated or residuals was asked. noise is what was added to the fan per bin to put it on the measurements’ footing, or None when the container could not say. cell is the declustering cell used, or None when the pairs were left raw. score is
geoml.metrics.variogram_score()over the same locations and weights, or None when there was no fan to score.- Return type:
list[dict]
Notes
The score is the figure’s verdict as one number, and it is a ranking rather than a measurement: unlike the curves it cannot be put on the measurements’ footing, since |difference| ** p is not a second moment and has no constant to add. It never reaches zero, and only comparisons between models on the same data mean anything. It is also taken over every pair of the locations kept, not only the binned ones, so it does not answer to max_lag or move when direction does.
- geoml.plots.prepare.moving_average(values, window)[source]
The running mean of values, and where each point belongs.
Returns the positions as well: a mean over window points only exists once there are that many, so the curve starts later than the one it smooths and has to be drawn against its own x.
- Return type:
tuple[NDArray[integer], NDArray[float64]]
- geoml.plots.prepare.training_curve(model, window=None)[source]
The training log, with a running mean over it.
Each entry is one optimizer step – and under train_svi one batch, so the curve is noisy by construction: every value is the ELBO estimated from a sample of the data and a sample of the latent variables. The mean is what says whether it is still climbing.
- Parameters:
window (int) – Points to average over. Defaults to a fiftieth of the log, which keeps the smoothing proportionate to however long training ran, but never fewer than five: a mean of two or three smooths nothing, and draws a second line on top of the first saying the same thing. A window longer than the log leaves it alone, and then there is no second line at all.
- geoml.plots.prepare.realizations(var)[source]
A variable’s simulations, or its prediction as the only one there is.
Returns (n_data, n_realizations) either way, so that whatever reads it does not have to care which it got.
- geoml.plots.prepare.realization_store(var)[source]
A variable’s realizations as a store to read in bands, not as an array.
The same (n_data, n_realizations) that realizations hands back, without asking memory for all of it at once. A block model’s simulations are the one thing a container holds that will not fit – hundreds of gigabytes is an ordinary size for them – and every use of them here is a reduction over locations, which never needs more than a band of rows at a time.
A variable with no simulations falls back on its prediction, which is a single column and already in RAM, and comes back as a one-band store so that the caller has only the one path to write.
- geoml.plots.prepare.block_density(container, density, n_data, n_realizations)[source]
A density per block, to turn a volume into a tonnage.
density may be a number, the name of a metadata column, or the name of a ContinuousVariable. Only the last of these can be uncertain, and when it is, its realizations are matched one to one with the grade’s: simulation i of the density belongs with simulation i of the grade, and pairing them any other way would invent a correlation that was never modelled.
Comes back as a single number where the density is one, and otherwise as a store to read in bands alongside the grade. A simulated density is exactly as big as the grade, so materializing it would give back everything not materializing the grade saves.
- geoml.plots.prepare.containing_variable(container, var)[source]
The variable var is a component of, if it is a component of one.
- geoml.plots.prepare.uncertainty_values(container, name, variables=())[source]
A number per location saying how much the model doubts itself there.
Which number that is depends on what was modelled, so it is named rather than guessed at. name may be:
an array, one value per location. Whatever the number is and wherever it came from, it can always be handed over directly, which is the way out of every case the names below do not reach.
a path, naming exactly where to read it – “Elements/uncertainty” while the grade is one of its components, or a column belonging to some other variable entirely. (The old dotted “Variable.column” is refused with the replacement spelled out: a . inside a label would make a wrong guess look like a working one.)
a bare name, looked for on each of variables in turn and then among the metadata columns.
variables is what a bare name is tried against: the grade, and then the variable containing it. That second one is not a nicety. A component of a vector variable has latent_variance set to None and no uncertainty of its own – the column that exists belongs to the parent, so grading Zn and asking for “uncertainty” has to reach Elements to find anything.
- geoml.plots.prepare.block_volume(container)[source]
What one block is worth, as a number or as one value per block.
A regular grid has a single spacing and so a single volume; a BlockSet3D carries a size per block and answers with a column. Kept apart from the density because only one of them is a property of the container.
- geoml.plots.prepare.grade_tonnage(container, name, density=None, cutoffs=30, uncertainty=None, max_uncertainty=None)[source]
How much material sits above a cut-off, and how good it is.
Each block contributes its volume, or its mass where a density is given, to every cut-off its grade clears. Simulations are carried through separately rather than averaged first: the curve of the mean model is not the mean of the curves, since a cut-off is a threshold and averaging either side of it gives different answers.
The simulations are read a band of blocks at a time and never held whole: a block model runs to hundreds of gigabytes of them, and what comes out is one small number per cut-off per realization. Each block is placed at the highest cut-off it clears and the curve is the running total from the top down, so the cost is one pass over the grade rather than one per cut-off. Giving cutoffs as values rather than as a count saves the pass that would otherwise be needed to find their range.
- Parameters:
container – A gridded container – the volume of a block comes from its spacing.
name (str) – The variable to take as the grade.
density (float or str) – A number, a metadata column, or a ContinuousVariable. Without one the curve is in volume.
cutoffs (int or array-like) – The grades to cut at, or how many of them to spread evenly across the range of the data.
uncertainty (str or array) – Where to read how sure the model is at each block: a column name, a a path (“Variable/column”) naming which variable it belongs to, or the values themselves. See uncertainty_values. A bare name is looked for on the grade, then on the variable containing it, then in the metadata.
max_uncertainty (float) – Blocks doubted more than this are left out altogether – not counted at any cut-off, and not counted towards the grade above one. A block the model cannot speak for is not tonnage.
- Returns:
dict – cutoff (n_cutoffs,); tonnage, grade and metal (n_cutoffs, n_realizations); extent, what is accumulated above the cut-off – a block’s own extent, or “mass” where a density was given; unit, what the grade is measured in where it says so; and kept and total, the blocks that survived the uncertainty filter and the blocks there were.
- geoml.plots.prepare.dispersion_by_support(container, name, component=None)[source]
The within-block standard deviation at every block size a block set has.
Each block’s dispersion says how much the ground varies inside it, on the block’s own support. Merging the blocks into their parents, level by level up to the coarsest, gives the same number at every size the lattice has, so it can be read against the block size.
A parent is never predicted; it is put together from the blocks inside it. Its dispersion is theirs, volume-weighted, plus how much their block values differ among themselves – taken realization by realization, a realization being the one thing that comes across a regrouping exactly, and averaged over the realizations as a block’s own dispersion is. Every realization the variable holds is used, read a band of blocks at a time.
A parent missing any of its ground – a block never predicted, such as one a where= filter left out – is left out at that size and at every size above it, as BlockSet3D.group refuses a partial family.
- Parameters:
container (BlockSet3D) – The block set.
name (str) – The continuous variable.
component (str | None) – One component of a vector or compositional variable; every one by default.
- Returns:
list of dict – One per component, in label order, with name, label and sizes: one dict per level, the finest first, holding level, size (the block size along each axis), deviation (the within-block standard deviation of every block of that size), depth (how many times the refinement split inside each: 0 for a block it left whole), rms (the root mean square of deviation, the dispersion of the ground within blocks of that size), count, share (of the set’s volume the blocks cover) and left_out (blocks of that size missing some of their ground).
- Raises:
ValueError – If the container is not a BlockSet3D, or the variable carries no dispersion or no simulations.
KeyError – If component names no component of the variable.
- Return type:
list[dict]
- geoml.plots.prepare.split_label(depth)[source]
How many times the refinement split inside a block, in words.
- Return type:
str
- geoml.plots.prepare.support_tick(entry)[source]
The lines naming one block size on the axis of dispersion_by_support: the size, then how much of the set it is.
- Return type:
list[str]
- geoml.plots.prepare.jitter(n, width=0.5)[source]
Offsets spreading n points across a strip width wide.
Taken from the golden-ratio sequence rather than at random, so a figure comes out the same every time and the points spread evenly whatever their number.
- Return type:
NDArray[float64]
- geoml.plots.prepare.support_strip(sizes, most=5000)[source]
The points of dispersion_by_support drawn as a strip.
One (depth, x, y) per depth of splitting, x being the position of the block’s size plus a spread across the strip. At most about most blocks of each size are kept, taken by striding through them.
- Return type:
list[tuple]
- geoml.plots.prepare.grid_shape(n_panels)[source]
Rows and columns for n_panels, as square as they go.
- geoml.plots.prepare.key_label(shells, key)[source]
A mesh set’s key as a figure writes it: a category’s name, or the cut-off as a number.
- Return type:
str
- geoml.plots.prepare.key_axis(shells)[source]
What the keys of a mesh set are, for the axis they run along.
- Return type:
str
- geoml.plots.prepare.volume_dispersion(shells, relative=False)[source]
What volume_dispersion draws: every realization’s mesh volume at each cut-off or category, beside the prediction’s.
Read off what the set measured as each realization was made, so no mesh is loaded.
- Parameters:
shells (MeshSet) – A set built with its realizations.
relative (bool) – Whether to divide every volume by the prediction’s, so that the prediction reads one.
- Returns:
dict – labels, one per key; values, the realizations’ volumes at each key; prediction, the prediction’s; title; axis, what the volumes are; keys, what the keys are.
- Return type:
dict
- geoml.plots.prepare.connectivity(shells)[source]
What connectivity draws: how many pieces each mesh is in, and the largest one’s share of its volume.
- Returns:
dict – labels; x, the cut-offs or the categories’ positions; numeric, whether x is a scale; largest and pieces, the prediction’s; band, the realizations’ P10 and P90 of the largest share, median their P50 and pieces_median the pieces’ P50 – None without realizations; title; keys.
- Return type:
dict
- geoml.plots.prepare.mesh_section(shells, axis, value, variable=None, resolution=None)[source]
What section draws: where each mesh of a set crosses a plane, and what the model holds on it.
- Parameters:
shells (MeshSet) – The set.
axis – The coordinate held fixed, by index or by label.
value (float) – Where along it the plane sits.
variable – A continuous variable or component of the set’s container, drawn beneath the lines; nothing is drawn beneath without one.
resolution (float | None) – The spacing to sample the variable at on the plane. The finest block by default, or a grid’s own spacing.
- Returns:
dict – lines, for every key’s label a list of (n, 2) polylines in the plane; axes, the labels of the two in-plane coordinates; image, the sampled variable or None; title; keys.
- Return type:
dict
- geoml.plots.prepare.section_image(container, var, index, value, resolution=None)[source]
A variable’s prediction sampled on a plane across one axis.
A block model answers exactly which block holds each sample; any other container its nearest location, left empty past half a cell’s diagonal. At most 600 samples a side.
- Returns:
dict or None – values (n_v, n_u), extent (u_min, u_max, v_min, v_max) and label; None when the variable holds no prediction.
- Return type:
dict | None
Style
One place defining what a geoML figure looks like.
Every function in this sub-package draws inside context(), a matplotlib.rc_context, so the settings below apply to the figure being drawn and to nothing else: importing geoML does not change how anyone else’s plots look. Call use() to take them globally, if that is what you want.
PALETTE is what tells one category from another, and it is the same list wherever a category is drawn, so a rock type keeps its colour from a histogram to a map to the dashboard. Change it here and every figure follows.