Normalization
Every gap-filling strategy, fallback chains, per-field overrides, point-in-time safety, and templates.
Raw signals rarely land on a perfect grid. When a segment row has no
value within tolerance for some field, that cell is a gap — and the
normalization strategy decides what fills it. Choosing well matters: fill
a status flag by interpolation and you invent states that never happened;
carry a noisy sensor forward and you freeze noise into fact.
The strategy object
A strategy is a config object with a primary, an ordered fallback chain, and its own tolerance:
{
"primary_strategy": "linear_interpolation",
"fallback_strategies": ["nearest_value", "previous_value", "mean"],
"tolerance": 60
}The primary is tried first; each fallback runs only for cells the
previous strategies couldn't fill. A chain ending in zero or none
always resolves — chains that end in a value-dependent strategy can still
leave empty cells at the edges of your data.
A segment sets one global strategy and may override it per field:
{
"normalization_strategy": {
"primary_strategy": "nearest_value",
"fallback_strategies": ["previous_value", "zero"],
"tolerance": 43200
},
"segment_strategies": {
"promo_active": {
"primary_strategy": "previous_value",
"fallback_strategies": ["zero"],
"tolerance": 86400
}
}
}Per-field overrides win for their field; everything else uses the global strategy.
The strategy catalog
| Strategy | Fills a gap with | Best for |
|---|---|---|
previous_value | The most recent earlier value | States, counters, sparse series — anything that "stays" until it changes |
forward_value | The next later value | Backfilling historical tables where future context is acceptable |
nearest_value | The closest value, earlier or later | General-purpose numeric data |
linear_interpolation | A straight line between neighbors | Smooth continuous quantities (temperature, price) |
mean | The field's segment-wide mean | Noisy, stationary series |
median | The field's segment-wide median | Like mean, robust to outliers |
zero | 0 | Counts and event-like fields where absence means zero |
none | Nothing (left empty) | When the model should see missingness |
custom:<value> | A constant you choose (custom:42, custom:false) | Domain-specific sentinel values |
db_previous / db_nearest | Same as previous_value / nearest_value | Accepted for backward compatibility |
Anything outside this catalog is rejected with 422 at create/edit time
— a segment that can't be served never gets saved.
Choosing per data shape
| Your field looks like | Use |
|---|---|
| Continuous sensor reading (temperature, load) | linear_interpolation → nearest_value → previous_value |
State or category (promo_active, mode) | previous_value → nearest_value — never interpolate states |
| Counter / accumulating total | previous_value → nearest_value → zero |
| Sparse, slow-moving (monthly indicator) | previous_value with a generous tolerance — and consider event features |
| Highly variable / noisy | mean or median in the chain to smooth |
| Event counts | zero — no event means zero |
Point-in-time safety
Several strategies look forward in time: forward_value,
nearest_value (its forward half), and linear_interpolation (which
needs the next point). That's fine for training tables built from settled
history — but if you're simulating "what did we know at time T?"
(backtests, leakage-sensitive evaluation), forward-looking fills leak the
future.
Set pit_safe: true on the segment and every strategy — global and
per-field — is substituted with its causal equivalent automatically:
| Configured | Executed under pit_safe |
|---|---|
forward_value, nearest_value, linear_interpolation | previous_value |
mean, median | zero |
previous_value, zero, none, custom:<value> | unchanged |
Your stored configuration is untouched — the substitution happens at query time, so one segment can serve both a standard training table and a point-in-time-safe one.
Templates and suggestions
Rather than picking blind, fetch pre-built templates plus suggestions derived from your workspace's actual data patterns — cadence, variability, counter-like behavior, categorical fields:
curl "$API_BASE/v1/segment/normalization-templates" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.segments.get_segment_normalization_templates()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.segments.getSegmentNormalizationTemplates();
{
"data": {
"templates": [
{
"name": "Time Series Sensor Data",
"description": "Ideal for continuous numeric data like temperature, pressure, etc.",
"global_strategy": {
"primary_strategy": "linear_interpolation",
"fallback_strategies": ["nearest_value", "previous_value", "mean"],
"tolerance": 60
},
"field_examples": ["temperature", "pressure", "humidity", "voltage"]
},
{ "name": "Categorical or State Data", … },
{ "name": "Counter or Accumulating Data", … },
{ "name": "Sparse Data", … },
{ "name": "Highly Variable Data", … }
],
"suggested": [
{
"name": "Workspace Counter Data",
"description": "Optimized for your fields that show accumulating/counting behavior",
"global_strategy": {
"primary_strategy": "previous_value",
"fallback_strategies": ["nearest_value", "zero"],
"tolerance": 120
},
"field_examples": ["page_views", "order_count"],
"is_suggested": true,
"confidence": "high",
"reason": "Based on the accumulating/counting pattern detected in these fields"
},
…
],
"analysis": {
"fields_analyzed": 12,
"numeric_fields": 10,
"string_fields": 2,
"patterns_detected": { "accumulating": 2, "highly_variable": 3, "categorical": 2 }
}
}
}templates are the five standard starting points; suggested entries
are generated from your data (each with a confidence and a reason)
and name the actual fields they were derived from in field_examples.
Copy a global_strategy straight into your create call — the shapes
match.
Workspaces with fewer than two signals get the standard templates and a default suggestion only — the pattern analysis needs data to work with.

