Skip to main content

PartitionedTimeSeries

classpond-tssource

View over a TimeSeries that scopes stateful transforms to within each partition. Created by TimeSeries.partitionBy(by).

Most pond-ts stateful operators read from neighboring events when computing each output. On a multi-entity series (events for many hosts interleaved by time), those neighbors silently cross entity boundaries: a fill('linear') for host-A would interpolate using host-B's value as a "neighbor"; a rolling('5m', { cpu: 'avg' }) would average across all hosts in the window.

partitionBy runs the transform independently on each partition's events. The view is persistent across chains — each sugar method returns another PartitionedTimeSeries carrying the same partition columns, so multi-step per-partition workflows compose cleanly:

const cleaned = ts
  .partitionBy('host')
  .dedupe({ keep: 'last' })   // per-host
  .fill({ cpu: 'linear' })    // per-host
  .rolling('5m', { cpu: 'avg' })  // per-host
  .collect();                 // back to TimeSeries<S>

Call .collect() (or .apply(fn) for arbitrary transforms) to materialize back to a regular TimeSeries. Without .collect(), the chain stays in partition view.

Constructor

new PartitionedTimeSeries(source: TimeSeries<S>, by: keyof EventDataForSchema<S> & string | readonly (keyof EventDataForSchema<S> & string)[], options?: { groups?: readonly K[] })

Properties

byreadonly (keyof EventDataForSchemaEventDataForSchematypepond-ts{ [C in DataColumnsForSchema<S>[number] as C['name']]: DataValueForColumn<C> }<S> & string)[]readonly
groupsreadonly K[]readonly

Declared partition values when partitionBy(col, { groups }) was used. When set, toMap iterates in declared order (not insertion order), empty declared groups still appear as empty TimeSeries entries, and unknown partition values throw at construction time.

sourceTimeSeriesTimeSeriesclasspond-ts

An immutable, schema-typed, ordered collection of events — the batch layer's core primitive. A series is constructed whole from complete data and never mutated: every transform (filter, align, rollup, …) returns a new TimeSeries, so the full analytical surface can sort, scan, or index freely. Example: new TimeSeries({ name, schema, rows }).

<S>
readonly

Methods

aggregate

aggregate(sequence: SequenceLike, mapping: Mapping, options?: { range?: TemporalLike }): PartitionedTimeSeries<readonly [ColumnDef<'interval', 'interval'>, AggregateColumns<ValueColumnsForSchema<S>, Mapping>], K>

Per-partition aggregate. See TimeSeries.aggregate.

align

align(sequence: SequenceLike, options?: { method?: AlignMethod; range?: TemporalLike; sample?: AlignSample }): PartitionedTimeSeries<readonly [ColumnDef<'interval', 'interval'>, OptionalizeColumns<ValueColumnsForSchema<S>>], K>

Per-partition align. See TimeSeries.align.

apply

apply(fn: (group: TimeSeries<S>) => TimeSeries<R>): TimeSeries<R>

Run a transform fn independently on each partition and return a TimeSeries<R> directly (terminal — does not stay in the partitioned view). The escape hatch for compositions or operators not exposed as sugar.

To keep the partition after a custom transform, use the sugar methods (which preserve partition state) or call .partitionBy(...) again on the result.

```ts
// chain two stateful ops within each partition (one shot)
const out = series.partitionBy('host').apply(g =>
g.fill({ cpu: 'linear' }).rolling('5m', { cpu: 'avg' }),
);
```

baseline

baseline(col: Col, options: { alignment?: RollingAlignment; minSamples?: number; names?: { avg?: AvgName; lower?: LowerName; sd?: SdName; upper?: UpperName }; sigma: number; window: DurationInput }): PartitionedTimeSeries<readonly [S[0], ValueColumnsForSchema<S>, OptionalNumberColumn<AvgName>, OptionalNumberColumn<SdName>, OptionalNumberColumn<UpperName>, OptionalNumberColumn<LowerName>]>

Per-partition baseline. See TimeSeries.baseline.

collect

collect(): TimeSeries<S>

Materialize the partitioned view back into a regular TimeSeries. Terminal operation — call this at the end of a chain to "collect" the per-partition results. Equivalent to .apply(g => g) but cheaper (no fn dispatch, just returns the source as-is).

```ts
const cleaned = ts
.partitionBy('host')
.fill({ cpu: 'linear' })
.rolling('5m', { cpu: 'avg' })
.collect(); // <- TimeSeries<S>
```

cumulative

cumulative(spec: { [K in string]: 'sum' | 'min' | 'max' | 'count' | (acc: number, value: number) => number }): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Targets>], K>

Per-partition cumulative. See TimeSeries.cumulative.

dedupe

dedupe(options?: { keep?: DedupeKeep<S> }): PartitionedTimeSeries<S, K>

Per-partition dedupe. The duplicate key becomes "same partition columns AND same timestamp" — partitionBy provides the partition segregation, dedupe handles the within-partition timestamp collapse. The most common dedupe shape for multi-entity ingest.

See TimeSeries.dedupe.

diff

diff(columns: Target | readonly Target[], options?: { drop?: boolean }): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Target>], K>

Per-partition diff. See TimeSeries.diff.

fill

fill(strategy: FillStrategy | FillMapping<S>, options?: { limit?: number; maxGap?: DurationInput }): PartitionedTimeSeries<S, K>

Per-partition fill. See TimeSeries.fill.

materialize

materialize(sequence: SequenceLike, options?: { range?: TemporalLike; sample?: AlignSample; select?: 'first' | 'last' | 'nearest' }): PartitionedTimeSeries<readonly [ColumnDef<'time', 'time'>, OptionalizeColumns<ValueColumnsForSchema<S>>], K>

Per-partition materialize. See TimeSeries.materialize.

Bonus over the bare TimeSeries.materialize call: every output row, including empty-bucket rows, gets the partition columns auto-populated from the partition's known key values. Without this, empty buckets would emit rows with undefined partition columns — forcing a follow-up .fill({ host: 'hold' }) step that fails for partitions where every event sits in a long-outage gap.

outliers

outliers(col: Col, options: { alignment?: RollingAlignment; minSamples?: number; sigma: number; window: DurationInput }): PartitionedTimeSeries<S, K>

Per-partition outliers. See TimeSeries.outliers.

pctChange

pctChange(columns: Target | readonly Target[], options?: { drop?: boolean }): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Target>], K>

Per-partition pctChange. See TimeSeries.pctChange.

rate

rate(columns: Target | readonly Target[], options?: { drop?: boolean }): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Target>], K>

Per-partition rate. See TimeSeries.rate.

rolling

rolling(window: DurationInput, mapping: Mapping, options?: { alignment?: RollingAlignment; minSamples?: number }): PartitionedTimeSeries<readonly [S[0], AggregateColumns<ValueColumnsForSchema<S>, Mapping>], K>

Per-partition rolling. See TimeSeries.rolling.

rolling(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: { alignment?: RollingAlignment; minSamples?: number; range?: TemporalLike; sample?: AlignSample }): PartitionedTimeSeries<readonly [ColumnDef<'interval', 'interval'>, AggregateColumns<ValueColumnsForSchema<S>, Mapping>], K>

Per-partition rolling. See TimeSeries.rolling.

sample

sample(strategy: BatchSampleStrategy): PartitionedTimeSeries<S, K>

Per-partition sample. Each partition gets its own independent sample state — separate stride counter or its own K-event reservoir. Safe by construction; no unsafeGlobal: true token. See TimeSeries.sample.

scan

scan(source: Source, step: ScanStep<A>, init: A): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Source>], K>

Per-partition scan. See TimeSeries.scan.

scan(source: Source, step: ScanStep<A>, init: A, options: { output: Name }): PartitionedTimeSeries<readonly [S[0], ValueColumnsForSchema<S>, ColumnDef<Name, 'number'>], K>

Per-partition scan. See TimeSeries.scan.

shift

shift(columns: Target | readonly Target[], n: number): PartitionedTimeSeries<readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Target>], K>

Per-partition shift. See TimeSeries.shift.

smooth

smooth(column: Target, method: SmoothMethod, options: { alpha: number; output?: Output; warmup?: number } | { alignment?: RollingAlignment; output?: Output; window: DurationInput } | { output?: Output; span: number }): PartitionedTimeSeries<Output extends string ? readonly [S[0], ValueColumnsForSchema<S>, OptionalNumberColumn<Output>] : readonly [S[0], ReplaceSmoothedColumn<ValueColumnsForSchema<S>, Target>]>

Per-partition smooth. See TimeSeries.smooth.

toMap

toMap(): Map<K, TimeSeries<S>>

Materialize the partitioned view as a Map<key, TimeSeries<S>>, one entry per partition. Terminal — exits the partition view.

Use this when downstream code needs to iterate or look up per partition (typical in dashboards: one chart line per host, one tooltip per region). Without this, the equivalent dance was .collect().groupBy(col, fn) — two operators where one would do.

The map key is the stringified partition value for single-column partitions, or a JSON.stringify'd array of values for composite partitions. The single-column form preserves the value's natural string representation (a host column with values 'api-1' yields keys 'api-1'); composite keys produce JSON like '["api-1","eu"]'. Map iteration order matches the order each partition was first encountered in the source events.

undefined partition values become the literal ' undefined' with a leading space — this avoids colliding with a string column whose value happens to be the literal text 'undefined'. The two are distinct buckets:

series // events with host=undefined and host='undefined'
  .partitionBy('host')
  .toMap();
// → 2 entries: ' undefined' (missing) vs 'undefined' (string literal)

Divergence from series.groupBy(col): groupBy uses bare 'undefined' (no leading space) for missing values, so it collapses these two cases. toMap's leading-space sentinel is an intentional improvement — the older groupBy shape silently loses the distinction between "missing" and "the string 'undefined'". Migrating from groupBy to toMap will produce different keys for partitions with undefined values; lookup code that previously did .get('undefined') should change to .get(' undefined') (note the leading space) to find the missing-value bucket.

Composite encoder. For composite partitions, JSON.stringify with a ?? null fallback emits both null and undefined as JSON null. In practice this only matters if event data contains explicit null values, which the standard validation/ingest paths convert to undefined upfront — so the single-column-vs-composite asymmetry is unreachable through the normal API.

```ts
// Per-host event lookup
const byHost = events.partitionBy('host').toMap();
const apiEvents = byHost.get('api-1');

// With a transform — one-shot per-partition shape change
const points = events.partitionBy('host').toMap((g) => g.toPoints());
for (const [host, rows] of points) {
chart.addSeries(host, rows);
}

// Composite partition
const byHostRegion = events
.partitionBy(['host', 'region'])
.toMap();
const apiEu = byHostRegion.get('["api-1","eu"]');
```
toMap(transform: (group: TimeSeries<S>) => TimeSeries<R>): Map<K, TimeSeries<R>>

Materialize the partitioned view as a Map<key, TimeSeries<S>>, one entry per partition. Terminal — exits the partition view.

Use this when downstream code needs to iterate or look up per partition (typical in dashboards: one chart line per host, one tooltip per region). Without this, the equivalent dance was .collect().groupBy(col, fn) — two operators where one would do.

The map key is the stringified partition value for single-column partitions, or a JSON.stringify'd array of values for composite partitions. The single-column form preserves the value's natural string representation (a host column with values 'api-1' yields keys 'api-1'); composite keys produce JSON like '["api-1","eu"]'. Map iteration order matches the order each partition was first encountered in the source events.

undefined partition values become the literal ' undefined' with a leading space — this avoids colliding with a string column whose value happens to be the literal text 'undefined'. The two are distinct buckets:

series // events with host=undefined and host='undefined'
  .partitionBy('host')
  .toMap();
// → 2 entries: ' undefined' (missing) vs 'undefined' (string literal)

Divergence from series.groupBy(col): groupBy uses bare 'undefined' (no leading space) for missing values, so it collapses these two cases. toMap's leading-space sentinel is an intentional improvement — the older groupBy shape silently loses the distinction between "missing" and "the string 'undefined'". Migrating from groupBy to toMap will produce different keys for partitions with undefined values; lookup code that previously did .get('undefined') should change to .get(' undefined') (note the leading space) to find the missing-value bucket.

Composite encoder. For composite partitions, JSON.stringify with a ?? null fallback emits both null and undefined as JSON null. In practice this only matters if event data contains explicit null values, which the standard validation/ingest paths convert to undefined upfront — so the single-column-vs-composite asymmetry is unreachable through the normal API.

```ts
// Per-host event lookup
const byHost = events.partitionBy('host').toMap();
const apiEvents = byHost.get('api-1');

// With a transform — one-shot per-partition shape change
const points = events.partitionBy('host').toMap((g) => g.toPoints());
for (const [host, rows] of points) {
chart.addSeries(host, rows);
}

// Composite partition
const byHostRegion = events
.partitionBy(['host', 'region'])
.toMap();
const apiEu = byHostRegion.get('["api-1","eu"]');
```
toMap(transform: (group: TimeSeries<S>) => R): Map<K, R>

Materialize the partitioned view as a Map<key, TimeSeries<S>>, one entry per partition. Terminal — exits the partition view.

Use this when downstream code needs to iterate or look up per partition (typical in dashboards: one chart line per host, one tooltip per region). Without this, the equivalent dance was .collect().groupBy(col, fn) — two operators where one would do.

The map key is the stringified partition value for single-column partitions, or a JSON.stringify'd array of values for composite partitions. The single-column form preserves the value's natural string representation (a host column with values 'api-1' yields keys 'api-1'); composite keys produce JSON like '["api-1","eu"]'. Map iteration order matches the order each partition was first encountered in the source events.

undefined partition values become the literal ' undefined' with a leading space — this avoids colliding with a string column whose value happens to be the literal text 'undefined'. The two are distinct buckets:

series // events with host=undefined and host='undefined'
  .partitionBy('host')
  .toMap();
// → 2 entries: ' undefined' (missing) vs 'undefined' (string literal)

Divergence from series.groupBy(col): groupBy uses bare 'undefined' (no leading space) for missing values, so it collapses these two cases. toMap's leading-space sentinel is an intentional improvement — the older groupBy shape silently loses the distinction between "missing" and "the string 'undefined'". Migrating from groupBy to toMap will produce different keys for partitions with undefined values; lookup code that previously did .get('undefined') should change to .get(' undefined') (note the leading space) to find the missing-value bucket.

Composite encoder. For composite partitions, JSON.stringify with a ?? null fallback emits both null and undefined as JSON null. In practice this only matters if event data contains explicit null values, which the standard validation/ingest paths convert to undefined upfront — so the single-column-vs-composite asymmetry is unreachable through the normal API.

```ts
// Per-host event lookup
const byHost = events.partitionBy('host').toMap();
const apiEvents = byHost.get('api-1');

// With a transform — one-shot per-partition shape change
const points = events.partitionBy('host').toMap((g) => g.toPoints());
for (const [host, rows] of points) {
chart.addSeries(host, rows);
}

// Composite partition
const byHostRegion = events
.partitionBy(['host', 'region'])
.toMap();
const apiEu = byHostRegion.get('["api-1","eu"]');
```