Policy‑Relevant Inflation (PCE)

Equal‑weight composite of headline and core PCE inflation to mirror the Fed’s policy focus. Regimes: Hot, Neutral, Cool.
v1.1 adds: revision/benchmark notes, optional confidence band, level vs momentum decomposition, and target‑aware thresholds.

Abstract

This signal combines the year‑over‑year change in the PCE price index and its ex‑food‑and‑energy counterpart (Core PCE). Each series is standardised via a rolling robust z‑score and averaged 50/50. Thresholds map the composite to policy‑relevant regimes. v1.1 separates the signal into level (distance from target) and momentum (direction and speed of change), and recommends publishing a confidence band that reflects recent volatility and revision risk.

1. Data

1.1 Primary series (required)

Operational notes
  • Release & vintage behavior: PCE is released in BEA’s Personal Income and Outlays report and may be revised with new source data and methodological changes, including annual updates/benchmark revisions.
  • Frequency & lag: monthly, typically released with a lag versus the reference month (varies by calendar).
  • Unit consistency: both are chain‑type indices; do not mix with “percent change” series without checking units.
  • Calendar alignment: use month‑end timestamps and compute transforms on a strict monthly grid (no forward‑fill).

1.2 Optional supplementary series (not used in the core composite)

If you incorporate trimmed‑mean PCE, document that its construction (including component weights) is updated by the publisher and can change historically.

2. Transformations

2.1 Inflation rates

  1. YoY inflation: \Delta\%_{12m}(X)_t = X_t/X_{t-12} − 1
  2. Momentum (recommended): compute a shorter‑horizon annualized rate (e.g., 3‑month annualized) to represent direction/speed: \Delta\%_{3m,ann}(X)_t = (X_t/X_{t-3})^{4} − 1. Publish as a separate field, not a replacement for YoY.

2.2 Robust standardisation (for the composite)

Convert each YoY series to a rolling robust z‑score (median/MAD) to reduce sensitivity to outliers and level shifts.

z_{rob}(X)_t = \dfrac{X_t − \mathrm{median}(X)_{t,w}}{1.4826·\mathrm{MAD}(X)_{t,w}}
Use w = 60 months (min 24). Replace MAD=0 with NaN.

2.3 Target‑anchored level component (for interpretation)

In parallel to z‑scores, compute an interpretable distance‑to‑target in percentage points:

Level_t = 0.5·(PCE_{YoY,t} − \pi^{*}) + 0.5·(CorePCE_{YoY,t} − \pi^{*})
where \pi^{*} is the policy inflation objective (default 2% unless updated).

3. Composite & Regimes

The signal score remains the equal‑weight composite of robust z‑scores on YoY inflation.

C_t = 0.5·z(PCE_{YoY,t}) + 0.5·z(CorePCE_{YoY,t})

3.1 Regime thresholds (target‑aware)

Base thresholds are expressed in z‑score units. If the policy objective \pi^{*} changes, keep the z‑score thresholds fixed (comparability), but update interpretation text and the target‑anchored level component accordingly.

If\ C_t > h \rightarrow \textit{Hot};\quad |C_t| \le h \rightarrow \textit{Neutral};\quad C_t < -h \rightarrow \textit{Cool}
Default: h = 0.75. Calibrate only with an explicit change log.

4. Confidence band (recommended)

Publish an uncertainty band around C_t that reflects recent volatility and helps users treat “near‑threshold” readings as low‑confidence. This is not a formal statistical standard error; it is an operational confidence indicator.

\sigma_{rob,t} = 1.4826·\mathrm{MAD}(C)_{t,w}
Band_t = [C_t − k·\sigma_{rob,t},\; C_t + k·\sigma_{rob,t}]
Use w = 60, k = 1 by default. Flag LOW_CONFIDENCE if the band intersects a regime boundary (±h).
Revision sensitivity flag (recommended)
  • Store the latest observed value and compare against a prior vintage (e.g., ALFRED) when available; flag if the last 3 observations were revised beyond a tolerance.
  • At minimum: publish the release date and a REVISION_RISK boolean during annual updates/benchmark windows.

5. Interpretation

5.1 How to read the output

5.2 Policy relevance (evidence base)

PCE inflation is widely treated as the Federal Reserve’s preferred inflation gauge for its longer‑run objective and communication, and “core” measures are commonly used to filter transitory volatility. Central‑tendency measures such as trimmed‑mean PCE are supported in the literature as improving signal quality and forecastability relative to headline measures in some settings.

Suggested citations to anchor interpretation (add to your report)
  • Fed longer‑run goals / 2% objective (policy anchor).
  • Dallas Fed research comparing trimmed‑mean PCE vs ex‑food‑and‑energy measures (predictability / slack linkage).
  • BEA documentation on PCE price index production and revisions (benchmark/annual update context).

6. Implementation (Python)

import pandas as pd
import numpy as np

def robust_z(s, win=60, min_win=24):
    x = pd.to_numeric(s, errors="coerce").astype(float)
    w = max(min_win, min(win, x.dropna().size))
    med = x.rolling(w, min_periods=min_win).median()
    mad = (x - med).abs().rolling(w, min_periods=min_win).median()
    return (x - med) / (1.4826 * mad.replace(0, np.nan))

def ann_change(x, m):
    # annualized m-month change for an index level series
    return (x / x.shift(m)) ** (12.0 / m) - 1

p = df_pce.copy()

# Required inputs: monthly index levels
p["PCE_YoY"]     = p["PCEPI"].pct_change(12)
p["CorePCE_YoY"] = p["PCEPILFE"].pct_change(12)

# Momentum (optional, interpretation)
p["PCE_3m_ann"]     = ann_change(p["PCEPI"], 3)
p["CorePCE_3m_ann"] = ann_change(p["PCEPILFE"], 3)

# Robust z-scores (for the regime score)
p["Z_PCE"]  = robust_z(p["PCE_YoY"])
p["Z_cPCE"] = robust_z(p["CorePCE_YoY"])

p["Score_C"] = 0.5*p["Z_PCE"] + 0.5*p["Z_cPCE"]

# Target-anchored level (percentage points vs target)
pi_star = 0.02  # update if the policy objective changes
p["Level_pp"] = 0.5*((p["PCE_YoY"] - pi_star) * 100.0) + 0.5*((p["CorePCE_YoY"] - pi_star) * 100.0)

# Confidence band on score (operational)
win = 60
k = 1.0
medC = p["Score_C"].rolling(win, min_periods=24).median()
madC = (p["Score_C"] - medC).abs().rolling(win, min_periods=24).median()
sigC = 1.4826 * madC.replace(0, np.nan)

p["C_lo"] = p["Score_C"] - k*sigC
p["C_hi"] = p["Score_C"] + k*sigC

h = 0.75
p["LOW_CONFIDENCE"] = ((p["C_lo"] <= h) & (p["C_hi"] >= h)) | ((p["C_lo"] <= -h) & (p["C_hi"] >= -h))

def regime(c):
    if pd.isna(c): return np.nan
    return "Hot" if c > h else ("Cool" if c < -h else "Neutral")

p["Regime"] = p["Score_C"].apply(regime)

df_sig_pce = p

7. Limitations

Change log

v1.1 — Adds: (i) explicit revision/benchmark guidance, (ii) optional confidence band and low‑confidence flag, (iii) level vs momentum decomposition, (iv) target‑aware interpretation.
v1.0 — Initial release: YoY robust z‑scores, equal‑weight composite, static thresholds.