Missing Values & Clamped Data (Sentinels & Censoring)#
This recipe explains how to handle user-defined missing values (sentinels, refusal codes, error intervals) and clamped data (top-coded and bottom-coded privacy thresholds) in FAIR Data JSON Schema.
See the companion schema file: ../../../examples/missing-and-clamped-values.json
How-to: Implement Missing & Clamped Values
Use
oneOf: Partition the property domain into distinct subschemas representing valid values, boundary brackets, and missing codes.Flag Missing Codes with
fair:sentinel: true: Apply to non-substantive codes (const: 99,const: -1) or error intervals (minimum: -99, maximum: -90) to instruct parsers to mask them asNaN.Flag Boundary Brackets with
fair:clamped: true: Apply to substantive measurements that have been capped for confidentiality (e.g.Age 99+orHours <= 5).Rely on Standard Range Keywords: Use
minimumfor top-coding (upper clamp) andmaximumfor bottom-coding (lower clamp)—no custom condition strings needed.Prevent Overlaps: Use negative numbers (e.g.
-1) for sentinels to allow open-ended top-coding without mutual exclusivity collisions.
1. The Core Dilemma: Sentinels vs. Clamped Observations#
In microdata, censuses, administrative registers, and health databases, variables frequently contain special boundary numbers. In standard JSON Schema, declaring "type": "integer" allows any integer through validation, but downstream software and AI agents must know how to treat these numbers:
Dimension |
|
|
|---|---|---|
Data Nature |
Non-substantive: Refusal, don’t know, processing error, system null |
Substantive observation: Real measurement bracketed for confidentiality or outlier control |
Statistical Action |
Mask as |
Retain in sample; model as censored observations (e.g. Tobit regression, survival models) |
Typical Origins |
Survey refusal codes ( |
Census anonymization ( |
AI / Agent Behavior |
LLM recognizes non-response without calculating averages over codes like |
LLM knows observations at boundary represent bracketed populations ($\ge 99$ or $\le 5$) |
2. Terminology Equivalence Across Domains, Sectors, and Audiences#
Different disciplines, sectors, and software ecosystems use completely different vocabularies to describe the exact same underlying mathematical and data phenomena. FAIR Data JSON Schema bridges these worlds with a unified, cross-domain syntax:
Audience / Sector |
Missing & Non-Response |
Top-Coded / Upper Boundary |
Bottom-Coded / Lower Boundary |
Bounded Interval |
|---|---|---|---|---|
Official Statistics & Census |
User-missing codes, Refusal / Non-response, Suppressed codes |
Top-coding (Upper-coding, disclosure avoidance) |
Bottom-coding (Lower-coding, disclosure avoidance) |
Classification bracket, uncompressed range |
Econometrics & Biostatistics |
Non-response (MCAR, MAR, MNAR) |
Right-censoring (Tobit regression, survival analysis) |
Left-censoring (Limit of Detection / LoD in assays) |
Interval-censoring |
Computer Science & Software |
Sentinel values, in-band error signaling ( |
Upper clamp, ceiling cap |
Lower clamp, floor cap |
Clamping ( |
Data Engineering & ML |
Null masking ( |
Outlier Winsorization, upper feature clipping ( |
Lower Winsorization, lower feature clipping ( |
Feature bounds, min-max scaling box |
AI Agents & LLMs (MCP) |
Masked non-evaluable sentinels (excluded from summaries) |
Bounded parameter threshold (upper) |
Bounded parameter threshold (lower) |
Valid continuous tool argument domain |
Metadata Standards (DDI-CDI) |
|
|
|
Bounded continuous / interval domain |
FAIR Data JSON Schema |
|
|
|
|
Understanding the Disciplinary Perspectives#
Statistical Disclosure Control (SDC) & Census Stewardship:
Statistical agencies (e.g. US Census Bureau, Eurostat, Statistics Canada, IPUMS) routinely top-code and bottom-code microdata to protect respondent confidentiality.
For example, high incomes are top-coded at $$250,000+$ and older ages at $99+$ to prevent outlier identification.
Knowing an observation is top-coded is vital: running ordinary least squares (OLS) regression on top-coded income yields biased estimates unless modeled appropriately.
Econometrics, Biostatistics, & Clinical Trials:
Econometricians and clinical researchers refer to top-coding as right-censoring and bottom-coding as left-censoring (e.g. assay Limit of Detection, where viral load $< 20$ copies/mL).
In statistical modeling, censored observations are modeled using Tobit regression, Kaplan-Meier estimators, or Cox proportional hazards models rather than being treated as literal point measurements.
Computer Science & Data Engineering:
Software engineers use the universal term clamping (
std::clampin C++,clamp()in CSS and Rust) to restrict an expression between upper and lower thresholds while preserving substantive validity.Machine learning pipelines perform Winsorization or feature clipping (
torch.clamp(),numpy.clip()) to prevent extreme values from distorting model weights.
Why
fair:clampedWas Selected:It is universally recognized across software engineering and numerical computing.
It avoids calling continuous physical measurements or ages a “code” (which falsely implies categorical classification).
It leaves the direction to be inferred naturally from the schema keyword (
minimumfor upper clamp,maximumfor lower clamp).
3. Standard JSON Schema Validation Mechanics#
Instead of inventing custom condition strings (such as ">=" or "<=") that break standard JSON Schema validators, FAIR Data JSON Schema relies strictly on native Draft 2020-12 range keywords:
Discrete code:
"const": 99or"const": -1Closed interval $[6, 98]$:
"minimum": 6, "maximum": 98Upper boundary $[99, \infty)$:
"minimum": 99Lower boundary $(-\infty, 5]$:
"maximum": 5
Direction is 100% Inferred#
When paired with "fair:clamped": true:
"minimum": 99is inherently an upper clamp (top-coded / right-censored)."maximum": 5is inherently a lower clamp (bottom-coded / left-censored).
No redundant directional flags (floor/ceiling or top/bottom) are required. Data types declared on the parent property (e.g. type: "integer") are inherited by oneOf branches automatically.
4. Universal Cross-Ecosystem Mapping#
The distinction between missing sentinels and censored boundaries is a universal challenge across statistical software, programming ecosystems, metadata standards, and AI systems:
Environment |
Sentinel / Missing Value Mechanism |
Clamped / Censored Mechanism |
Equivalent in FAIR Data JSON Schema |
|---|---|---|---|
SPSS ( |
|
Handled via recoding or value labels (e.g. |
|
Stata |
Extended missing values ( |
Censored regression models ( |
|
SAS |
Special missing values ( |
Model statements in |
|
Python ( |
|
Censored modeling in |
Inspect schema: mask |
R ( |
|
Censored regression in |
Inspect schema: map |
DDI-CDI / DDI |
|
|
Direct 1:1 conceptual mapping |
AI Agents (MCP / OpenAPI) |
Agent parses |
Agent recognizes capped distributions for tool queries |
Machine-actionable metadata directly in OpenAPI / JSON Schema |
5. Practical Implementation Patterns#
Pattern A: Categorical Non-Response (fair:sentinel with const)#
Standard survey questions often contain discrete substantive responses alongside refusal or “don’t know” codes:
{
"type": "integer",
"title": "Employment Status",
"oneOf": [
{ "const": 1, "title": "Employed full-time" },
{ "const": 2, "title": "Employed part-time" },
{ "const": 3, "title": "Unemployed seeking work" },
{ "const": 4, "title": "Retired" },
{ "const": 98, "title": "Don't know", "fair:sentinel": true },
{ "const": 99, "title": "Refused / No answer", "fair:sentinel": true }
]
}
Pattern B: The Demographic Age Bracket (5–99)#
In census and survey microdata, age is commonly bottom-coded for early childhood and top-coded for older adults to prevent respondent identification:
{
"type": "integer",
"title": "Age of Respondent",
"oneOf": [
{ "minimum": 0, "maximum": 5, "title": "5 years and under (bottom-coded for confidentiality)", "fair:clamped": true },
{ "minimum": 6, "maximum": 98, "title": "Recorded age in single years (6 to 98)" },
{ "minimum": 99, "title": "99 years and older (top-coded for confidentiality)", "fair:clamped": true },
{ "const": -1, "title": "Refused / Not reported", "fair:sentinel": true }
]
}
Why Negative Sentinels & Bounded Zero Floors Are Recommended
Bounding the bottom-coded bracket with minimum: 0 ensures valid toddler ages (0 to 5) are captured while preventing negative sentinel codes like -1 from satisfying maximum: 5. Because all valid substantive ages are $\ge 0$, the top-coded threshold minimum: 99 can remain completely open-ended without matching missing codes or needing an artificial upper ceiling.
Pattern C: Top-Coded Earnings & Negative Processing Range#
Capturing continuous monetary amounts with a high-earner privacy cap ($250,000+) alongside closed machine error codes:
{
"type": "number",
"title": "Annual Gross Income (Thousands USD)",
"oneOf": [
{ "minimum": 0, "maximum": 249, "title": "Recorded earnings under $250k" },
{ "minimum": 250, "title": "$250,000 and above (top-coded for privacy)", "fair:clamped": true },
{ "minimum": -99, "maximum": -90, "title": "Negative processing error codes (-99 to -90)", "fair:sentinel": true },
{ "const": -1, "title": "Refused / Not reported", "fair:sentinel": true }
]
}
Pattern D: Closed Suppressed Non-Response Range#
When surveys flag suppressed or invalid observations with a designated positive range (common in legacy SPSS files with n_missing = -2):
{
"type": "integer",
"title": "Weekly Hours Worked",
"oneOf": [
{ "minimum": 0, "maximum": 5, "title": "5 hours or fewer (bottom-coded)", "fair:clamped": true },
{ "minimum": 6, "maximum": 89, "title": "Recorded hours worked (6 to 89)" },
{ "minimum": 90, "maximum": 99, "title": "Suppressed non-response range (90 to 99)", "fair:sentinel": true }
]
}
6. Technical Validation & Mutual Exclusivity in oneOf#
Standard JSON Schema validators (ajv, jsonschema) require that each valid instance matches exactly one branch in a oneOf array:
Value: 92 --> Matches: [6, 98] --> VALID (1 match)
Value: 99 --> Matches: [99, inf) (clamped) --> VALID (1 match)
Value: -1 --> Matches: const: -1 (sentinel) --> VALID (1 match)
Value: 4 --> Matches: [0, 5] (clamped) --> VALID (1 match)
Rule for Positive Sentinel Codes
If your dataset uses positive sentinel codes higher than a top-code threshold (e.g. 999: Refused alongside an Age 99+ top-code), you must bound the top-code branch (e.g. minimum: 99, maximum: 120). Otherwise, 999 would satisfy both minimum: 99 and const: 999, triggering a oneOf multi-match validation error.
7. Automated Pipeline Ingest Example#
Here is a complete Python snippet demonstrating how an automated ETL script or AI agent uses the schema annotations to prepare tabular data:
import numpy as np
import pandas as pd
def classify_value(val: float, branches: list[dict]) -> tuple[float, str]:
"""Inspects schema oneOf branches to mask sentinels and preserve clamped data."""
for branch in branches:
matches = True
if "const" in branch and val != branch["const"]:
matches = False
if "minimum" in branch and val < branch["minimum"]:
matches = False
if "maximum" in branch and val > branch["maximum"]:
matches = False
if matches:
if branch.get("fair:sentinel"):
# Mask non-substantive codes as NaN
return np.nan, "missing"
elif branch.get("fair:clamped"):
# Retain substantive measurement, tag censoring direction
direction = "top" if "minimum" in branch else "bottom"
return val, f"clamped_{direction}"
else:
return val, "substantive"
return val, "unmatched"
# Example: Respondent ages evaluated against Pattern B
raw_ages = [4, 25, 72, 99, 104, -1]
age_branches = [
{"minimum": 0, "maximum": 5, "fair:clamped": True},
{"minimum": 6, "maximum": 98},
{"minimum": 99, "fair:clamped": True},
{"const": -1, "fair:sentinel": True},
]
results = [classify_value(age, age_branches) for age in raw_ages]
df = pd.DataFrame(results, columns=["clean_age", "status"])
print(df)
# clean_age status
# 0 4.0 clamped_bottom
# 1 25.0 substantive
# 2 72.0 substantive
# 3 99.0 clamped_top
# 4 104.0 clamped_top
# 5 NaN missing
Notice how -1 is cleanly converted to NaN, preventing calculation distortions, while 4 and 99 are preserved with their clamping indicators ready for survival analysis or Tobit regression.