Lattice Grid Buy a licence

developer guide

Statistics and shadow columns

What the grid knows about its own numbers, and how they have moved since the page loaded.

Reductions in the totals row

Any numeric column can carry a statistical reduction in its total: total: 'median', 'p95', 'stddev', 'iqr' and more than twenty others. Every figure is computed over the filtered rows, and quantiles use the R type 7 definition a spreadsheet's PERCENTILE agrees with.

grid.statistics

The same figures, and the ones a column at a time cannot answer, are on grid.statistics: a full column profile, any single reduction, two-column figures like correlation and regression, and whole-series measures such as volatility and drawdown over a date-ordered column.

grid.statistics.profile('margin');
// { rows, present, missing, distinct, min, max, mean, median,
//   q1, q3, iqr, stddev, outliers, histogram }

grid.statistics.reduce('margin', 'p95');       // any registered kernel
grid.statistics.correlation('spend', 'revenue'); // Pearson's r
grid.statistics.regression('spend', 'revenue');  // { slope, intercept, r2, n }
grid.statistics.series('price', { by: 'date' }); // volatility, drawdown, ...

The statistics panel

The end-user half of profile() is a docked tool panel: a column picker, the twelve one-pass figures and a histogram of the column's shape, all following the filters. It also shows Shape (skewness, kurtosis, Jarque-Bera), Robust (trimmed and winsorized means, MAD), Concentration (Gini, HHI, entropy) and Capability where a column declares a spec. A section whose reductions all return null is left out rather than shown as a column of dashes.

toolPanel: { side: 'left', panels: ['columns', 'statistics'] }

Shadow columns

A shadow column is declared against another column and maintained by the grid. It has no field in the data, and it is not a computed column either, because its value depends on what happened before. It is a real column throughout: sortable, filterable, groupable, exportable, saveable into a view, which is what makes "every row repriced more than twice, most-changed first" one gesture.

columns: [
  { field: 'price', type: 'number' },
  { id: 'moved',  title: 'Change',  shadow: { of: 'price', kind: 'delta' } },
  { id: 'rank',   title: 'Rank',    shadow: { of: 'price', kind: 'rank' } },
  { id: 'movers', title: 'Places',  shadow: { of: 'price', kind: 'rankChange' } },
  { id: 'run',    title: 'Streak',  shadow: 'streak' }, // shorthand: the column beside it
]

The sixteen kinds

KindValue
updatesHow many times the value has changed. Arrival is not a change.
updatedAtWhen it last changed, as a Date.
sinceUpdateMilliseconds since it last changed.
deltaCurrent value minus the baseline.
deltaPercentThe same as a percentage; a change from nothing reads null.
rateChange per second, from the last two readings.
historyThe recent readings, oldest first; depth sets how many.
firstValueThe baseline itself.
streakConsecutive moves in one direction, signed; it resets on a turn.
rankCompetition rank, largest first; ties share the better rank.
rankAscThe same ranking from the other end.
rankChangePlaces climbed since the baseline; the "top movers" column.
percentileThe share of rows at or below this one, 0 to 100.
quartile1 to 4, agreeing with percentile.
zScoreDeviations from the mean; no spread reads null.
shareOfTotalThe value over the column's total, as a percentage.

Shadow columns follow the data live. As values arrive or change - a streaming price, an edit, a new row - the grid keeps every shadow current, so the change count climbs, the delta moves and the rank re-sorts in front of the user without a redraw of your own. A live trading book shows the whole set moving at once.

Positional kinds rank over every tracked row, not the filtered set, so "the top ten movers" does not depend on what happens to be on screen; pass scope: 'filtered' to rank within the filters instead. grid.statistics.rebase(col) is "mark all": it moves every baseline to now, so the deltas and the rank change start again from the current values.

Process capability

Declare a tolerance on a column and the capability figures, a control chart and any out-of-tolerance rule all read the same limits. Cp and Cpk use short-term variation; Pp and Ppk use the overall deviation, and the gap between them is the point: Cpk well above Ppk means the process drifted.

columns: [{ field: 'mm', type: 'number',
  spec: { lower: 9.5, upper: 10.8, target: 10 } }]

grid.statistics.capability('mm');
// { cp, cpk, pp, ppk, sigmaWithin, sigmaOverall, outOfSpec, defectRate, violations }

Confidence intervals

Every figure the grid reports is an estimate from the rows in front of it. interval() says how tightly that estimate is pinned down: the range a mean is likely to sit in, with its margin of error, rather than a single number read to more decimal places than the data earns. Ask for a proportion instead with kind: 'proportion', and process capability can report an interval on Cpk too, which is what turns "the process is capable" into "capable, on this many parts".

grid.statistics.interval('margin');
// { mean, lower, upper, margin, n, confidence }  a 95% interval by default

grid.statistics.interval('passed', { kind: 'proportion' });
// { proportion, lower, upper, n, confidence }

grid.statistics.interval('margin', { confidence: 0.99 }); // a wider, surer interval

It reads the rows the filters leave, so the interval narrows as the grid narrows: it describes the population on screen, not the whole table. It is a range, not a verdict; the grid states how well the figure is known and leaves the judgement to you.

Distribution-driven formatting

The same knowledge drives conditional formatting that describes the data rather than a threshold you typed: an { op: 'outlier' } rule flags the statistical extremes, and a colour scale with from: 'quantile' spans a percentile range so a single large value does not flatten it. See the data-driven rules and quantile scales demos.

See the statistics overview and its live demos.