PartitionStability

class PartitionStability(val baselineNumComponents: Int, val numObservations: Int, val displacements: List<Double>, val hellingerFromBaseline: List<Double?>, val numObservationsReassigned: List<Int?>, val gapShare: Double = defaultGapShare, val gapMovement: Double = defaultGapMovement, val followsShare: Double = defaultFollowsShare)(source)

How far the fitted density moves, and how much of the sample changes hands, when the cuts between the components are nudged.

The method partitions the sorted sample and estimates each component from its own group, so every reported component depends on where two cuts fell. That dependence is invisible in the result: a mixture assembled from arbitrary cuts looks exactly like one assembled from meaningful cuts. This measures it, by moving each cut a little way along the axis the data lives on and refitting the components from the groups that result.

The cut moves in data units, not in observations. That is the whole point, and the reason the obvious alternative does not work. Displacing a cut by a fixed number of observations, or widening each component's estimation window by a fraction of its group, always moves data across the boundary — so a genuinely well-separated fit, whose components sit far apart, is disturbed the most, because the observations that cross are the most alien ones available. That inverts the reading. Moving the cut a fixed distance along the axis instead reassigns nothing when the cut sits in an empty gap, which is exactly when the partition is not doing arbitrary work, and reassigns a great deal when the sample is one blob.

The count of reassigned observations turned out to matter more than the distance. Calibrated against the designed experiment, the share of the sample that changes group separates answerable fits from unanswerable ones better than the density movement does — a Youden's J of 0.33 against 0.23 — and it is free, being a property of the displaced partition rather than of anything fitted to it. So the reading below is governed mainly by the share, with the movement as a guard against the case where a handful of reassigned observations happen to move the density a long way.

The alternative fits are not here, deliberately. They are computed, measured, and dropped. Returning them would offer the analyst a menu of densities differing by a tuning constant they have no basis to choose among, and the question this answers is not "which fit" but "does the fit depend on the cuts".

Parameters

baselineNumComponents

the number of components in the reported fit

numObservations

the size of the sample, so that a count of reassigned observations can be read as a share

displacements

the signed cut displacements that were tried, each as a fraction of the span of the two groups the cut separates

hellingerFromBaseline

how far the refitted density sat from the reported one at each displacement. Null where the refit could not be measured — the displaced partition was inadmissible, no mixture could be assembled from it, or the quadrature could not represent both densities — because an unmeasured displacement is not a movement of zero.

numObservationsReassigned

how many observations changed groups at each displacement, or null where the displacement could not be applied at all

gapShare

the share of the sample that may change group and still count as nothing

gapMovement

the movement permitted alongside that share before the gap reading is refused

followsShare

the share above which the density is taken to follow the cuts

Constructors

Link copied to clipboard
constructor(baselineNumComponents: Int, numObservations: Int, displacements: List<Double>, hellingerFromBaseline: List<Double?>, numObservationsReassigned: List<Int?>, gapShare: Double = defaultGapShare, gapMovement: Double = defaultGapMovement, followsShare: Double = defaultFollowsShare)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
Link copied to clipboard

What the displacements revealed.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

The largest share of the sample that changed groups under any displacement.

Link copied to clipboard

The largest movement over the displacements that could be measured, or null when none could.

Link copied to clipboard
Link copied to clipboard

How many displacements produced no usable measurement.

Functions

Link copied to clipboard
fun caveat(): String

What a reader must be told alongside the reading.

Link copied to clipboard
open override fun toString(): String