spine_measure¶
Turn your data into a nonnegative measure.
The measure is a scientific input, not a setting. SPINE does not know what your data means; choosing the measure is the modelling step. There is no default and there never should be.
You rarely call this directly: spine calls it for you. It is here so you
can see what a measure produces, and build one yourself.
Syntax and the ten measures¶
from entropy_spine import spine_measure
help(spine_measure)
Help on function spine_measure in module entropy_spine.interface.measures:
spine_measure(X, name=None, *, k: 'int' = 5)
spine_measure: turn data into a nonnegative measure.
The measure is a scientific input, not a setting. SPINE does not know what
your data means; choosing the measure is the modelling step. There is no
default and there never should be.
You rarely call this directly: ``spine`` calls it for you. It is here so you
can see what a measure produces, and build one yourself.
The ten measures, and the usual orientation of each:
asis your own precomputed measure yours
knn distance to the K-th nearest neighbour upper
frequency count divided by the total, counts given lower
statefrequency tally the distinct rows, frequency per state lower
degree row sum of an adjacency matrix upper
singular the singular values of your matrix upper
dist2mean distance from the mean upper
dist2median distance from the median upper
zrownorm distance from the mean, in std-deviation units upper
robustrownorm distance from the median, in MAD units upper
'singular' and 'statefrequency' change what an object IS: for 'singular' the
objects are the singular values, for 'statefrequency' the distinct states.
``meta["objectsChanged"]`` records both. The conventional orientation is
recorded in meta, never enforced: you choose the orientation.
Returns ``(mu, meta)``.
Seeing a measure on its own¶
Objects are rows. Every measure returns one nonnegative value per object.
import numpy as np
rng = np.random.default_rng(5)
X = np.vstack([rng.standard_normal((150, 3)),
rng.standard_normal((12, 3)) + 8])
mu, meta = spine_measure(X, "robustrownorm")
print(f"{mu.size} objects, measure from {mu.min():.3g} to {mu.max():.3g}")
162 objects, measure from 0.0579 to 14
What the measure records about itself¶
The second output says what was done: the name, any parameters, columns dropped for having no spread, and whether the measure redefined what an object is.
Xc = np.column_stack([X, np.ones(X.shape[0])]) # a constant column
_, meta = spine_measure(Xc, "zrownorm")
{k: v for k, v in meta.items() if k != "states"}
{'name': 'zrownorm',
'droppedColumns': [4],
'parameters': {},
'objectsChanged': False,
'conventionalOrientation': 'upper',
'nObjects': 162}
Measures that change what an object is¶
singular makes the objects the singular values, so the count is
min(X.shape) rather than the number of rows. statefrequency does the same
in its own way: the objects become the distinct states, however many rows
produced them, and meta["objectsChanged"] records both. frequency and
degree keep one object per row and only reinterpret it, as a category or a
node.
M = rng.standard_normal((60, 15)) @ np.diag([20, 12, 7] + [0.3] * 12)
sv, meta_sv = spine_measure(M, "singular")
print(f"{M.shape[0]} rows in, {sv.size} objects out, "
f"objectsChanged = {meta_sv['objectsChanged']}")
60 rows in, 15 objects out, objectsChanged = True
The one measure that accepts text¶
statefrequency compares rows literally rather than computing with them, so it takes strings, sequences of rows, and DataFrames mixing text and numbers.
states = ["ok", "ok", "warn", "ok", "fault", "warn", "ok", "fault"]
freq, meta_state = spine_measure(np.asarray(states), "statefrequency")
list(zip(meta_state["objectIds"], meta_state["counts"].tolist(),
freq.round(3).tolist()))
[(np.str_('fault'), 2, 0.25),
(np.str_('ok'), 4, 0.5),
(np.str_('warn'), 2, 0.25)]