## Column

### Quick API snapshot

```js
import Statistics ,{ Table, Column } from 'als-statistics';

// Column
// static
Column.key(name, ...parts)
// properties/getters
col.name
col.labels?         // optional labels aligned with values
col.invalid         // indices of invalid inputs
col.values          // get/set (validated)
col.n               // length
// cache/events
col.$(key, compute) // memoize custom computations
col.onchange(fn)    // subscribe to structural changes
// mutation helpers (invalidate caches automatically)
col.addValue(value, index?)
col.deleteValue(index)
col.clone(name?)
col.insertAt(index, ...items)
col.setAt(index, item)
col.removeAt(index, deleteCount=1)
col.splice(start, deleteCount, ...items)
col.push(...items)
// descriptive on Column (same names as Stats one-liners)
col.sum, col.mean, col.median, col.mode
col.variance, col.varianceSample, col.stdDev, col.stdDevSample, col.cv, col.range, col.iqr, col.mad
col.percentile(p), col.q1, col.q3, col.p10, col.p90
col.zScore(v), col.zScores(), col.zScoresSorted(), col.outliersZScore(z=3), col.outliersIQR()
col.weightedMean(weights), col.confidenceInterval, col.slope, col.regressionSlope(customX)
col.spectralPowerDensityArray, col.spectralPowerDensityMetric
```

---

### How it works (principles)

- **Validation-first.** Columns accept **only finite numbers**. Any non-finite input (`NaN`, `±Infinity`, non-number) is rejected or tracked via `col.invalid`, and excluded from descriptive metrics.
- **Cached results.** Many results are cached (e.g., `col.mean`, `col.stdDev`). To keep caches correct, you must **not** mutate the underlying array directly.  
  Instead, either:
  - assign a **new array** via the validated setter: `col.values = [...newNumbers]`, **or**
  - use the **provided mutators** (`setAt`, `splice`, `push`, …).  
  These paths automatically **invalidate** caches and fire `onchange` events.
- **Alignment in tables.** By default, a `Table` aligns columns to a common length (truncates to the **shortest** column). You can change this behavior with constructor options (e.g., `alignColumns: false`, `minK`) or call `t.alignColumns()` explicitly.
- **In-place transforms.** Most `Table` methods mutate. Chain them freely, or use `clone()` to keep the original around.


### Creating and validating

```js
import { Column } from 'als-statistics';

const scores = new Column([10, 12, 13, 9, 14], 'Score');

// set a new validated series (replaces data, clears caches)
scores.values = [11, 11, 10, 12, 15];

// invalid values are tracked and excluded from stats
scores.values = [11, 12, NaN, 10, 9, Infinity];
console.log(scores.invalid); // [2, 5]
console.log(scores.mean);    // mean over valid entries only
```

> Do **not** mutate `scores.values` in place (e.g., `scores.values[0] = 999`), as caches won’t know about it. Use `setAt(...)` instead.

### Safe mutations (cache-aware)

```js
// append values
scores.push(10, 11);

// insert at position
scores.insertAt(1, 99);

// replace a single value
scores.setAt(0, 12);

// delete & splice
scores.deleteValue(2);
scores.splice(3, 1, 50, 51);
```

All of these **invalidate caches** and emit `onchange`:

```js
scores.onchange((col, prev, meta) => {
  console.log('column changed:', meta.type)
});
```

### Caching your own computations

```js
// memoize expensive custom metric
const kurt = scores.$('kurtosis', () => {
  // compute once, then served from cache until data changes
  return scores.kurtosis; // or any custom formula
});
```

### Descriptives on Column

Every descriptive method available in `Stats` exists on `Column` too and always respects validation/caching:

```js
console.log({
  mean: scores.mean,
  sd  : scores.stdDevSample,
  q1  : scores.q1,
  p90 : scores.p90,
  outliersZ: scores.outliersZScore(3)
});
```

---

