Lattice Grid Buy a licence

demo D234

This month against last

Two grids compared column by column, ranked by effect size rather than a p-value

grid.statistics.datasetVsDataset(otherGrid)

Building…
Loading a live grid…

The configuration

import * as ng from '@angular/core';
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';

const { LatticeGridComponent } = createLatticeGrid({ ng, createGrid });

// The same columns for both books, so datasetVsDataset can line them up.
const columns = [
  { field: 'account', title: 'Account', layout: { pin: 'start', width: 150 } },
  { field: 'region', title: 'Region', filter: { type: 'set' }, layout: { width: 110 } },
  { field: 'plan', title: 'Plan', filter: { type: 'set' }, layout: { width: 130 } },
  { field: 'mrr', title: 'MRR', type: 'number', format: { style: 'currency', currency: 'USD', decimals: 0 }, total: 'sum', layout: { width: 130 } },
  { field: 'seats', title: 'Seats', type: 'number', total: 'sum', layout: { width: 100 } },
  { field: 'supportTickets', title: 'Tickets', type: 'number', total: 'sum', layout: { width: 110 } },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [LatticeGridComponent],
  template:
    '<div style="display:flex;gap:14px">' +
    '  <lattice-grid #a [config]="left" (filter-changed)="report()" style="flex:1;height:460px"></lattice-grid>' +
    '  <lattice-grid #b [config]="right" (filter-changed)="report()" style="flex:1;height:460px"></lattice-grid>' +
    '</div>' +
    '<pre style="margin-top:12px">{{ out }}</pre>',
})
export class AppComponent implements AfterViewInit {
  @ViewChild('a') aRef!: LatticeGridComponent;
  @ViewChild('b') bRef!: LatticeGridComponent;
  out = '';

  left = { rowKey: 'id', columns, rows: [/* last month */] };
  right = { rowKey: 'id', columns, rows: [/* the same accounts, a month later */] };

  // datasetVsDataset ranks the columns two independent grids share by what moved
  // most between them, on one bounded 0 to 1 scale, so a numeric shift and a
  // category mix shift compare honestly. It follows the filters on both grids.
  report() {
    const a = this.aRef && this.aRef.grid;
    const b = this.bRef && this.bRef.grid;
    if (a && b) this.out = JSON.stringify(a.statistics.datasetVsDataset(b), null, 2);
  }

  ngAfterViewInit() { this.report(); }
}

bootstrapApplication(AppComponent);

Comparing this month against last, column by column

subsetVsPopulation answers “what makes this filtered slice different from the rest of the same dataset.” grid.statistics.datasetVsDataset(otherGrid) answers the sibling question: what changed between two independent datasets, such as this month’s book and last month’s, two regions’ rosters, or a test group against a control. Call it on one grid, pass the other, and it returns the same shape of ranked result: every shared column scored on a single bounded scale and ordered by how much it moved, with columns that exist in only one dataset named separately as unmatched rather than silently skipped. The numeric measure is Cohen’s d against a pooled deviation rather than the asymmetric measure a subset-vs-population comparison uses, because with two independent datasets neither one is the baseline the other is judged against; comparing A to B and B to A gives the same ranking, just with the sign flipped, which is the property that makes the comparison trustworthy in either direction. Each grid keeps its own filter, so narrowing dataset A to a segment before comparing answers a narrower question without touching dataset B at all.

How do I compare two datasets and find what changed between them?

Build two grids, one over each dataset, then call gridA.statistics.datasetVsDataset(gridB). The result’s ranked array orders every column both grids share by effect size, nA and nB report how many rows each side compared, and unmatched.onlyA / unmatched.onlyB name any columns that exist in one dataset but not the other. Filtering either grid before calling the method compares whatever rows are currently in scope, not the full underlying set.

Why not just diff a summary table between the two periods?

A summary table shows the numbers moved; it does not say which of thirty columns moved the most, and a manual scan treats a categorical shift and a numeric one as impossible to compare on the same axis. datasetVsDataset puts a number’s shift and a category’s shift on one scale and sorts by it, so the column actually worth investigating is first rather than found by scrolling.