Forecast Error Variance Decomposition¶
Quick Reference
Class: panelbox.var.fevd.FEVDResult
Method: PanelVARResult.fevd()
Import: from panelbox.var import PanelVAR (FEVD accessed via results)
Stata equivalent: irf table fevd
R equivalent: vars::fevd()
Overview¶
Forecast Error Variance Decomposition (FEVD) measures the proportion of the forecast error variance of each variable that is attributable to shocks from each variable in the system. While IRFs trace the effect of a single shock over time, FEVD answers: What fraction of the uncertainty in forecasting variable \(i\) at horizon \(h\) is due to shocks in variable \(j\)?
At each horizon \(h\) and for each variable \(i\), the FEVD sums to 100%:
where \(\omega_{ij}(h)\) is the share of forecast error variance of variable \(i\) at horizon \(h\) explained by shocks to variable \(j\).
For Cholesky-identified FEVD:
FEVD provides a complementary perspective to IRFs: IRFs show the direction and magnitude of effects, while FEVD shows their relative importance.
Quick Example¶
from panelbox.var import PanelVARData, PanelVAR
# Estimate model
var_data = PanelVARData(df, endog_vars=["gdp", "inflation", "rate"],
entity_col="country", time_col="year", lags=2)
model = PanelVAR(data=var_data)
results = model.fit(cov_type="clustered")
# Compute FEVD
fevd = results.fevd(periods=20, method="cholesky")
# View decomposition for GDP
print(fevd["gdp"]) # Returns (periods+1, K) array
# Summary at selected horizons
print(fevd.summary(horizons=[1, 5, 10, 20]))
# Plot
fevd.plot()
When to Use¶
- Relative importance: Which variables drive the most uncertainty in your variable of interest?
- Exogeneity assessment: If a variable's forecast error is dominated by own shocks at all horizons, it behaves as (nearly) exogenous
- Shock dominance: At what horizon do external shocks start to dominate own shocks?
- Model validation: FEVD patterns should be consistent with economic theory and IRF findings
Key Assumptions
- Same identification assumptions as IRFs (Cholesky ordering or Generalized)
- VAR must be stable for well-defined FEVD
- Cholesky FEVD depends on variable ordering (same as Cholesky IRFs)
- Generalized FEVD is order-invariant but requires normalization (rows may not sum to 1 before normalization)
Detailed Guide¶
Computing FEVD¶
fevd = results.fevd(
periods=20, # Number of horizons
method="cholesky", # "cholesky" or "generalized"
order=None, # Custom ordering (Cholesky only)
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
periods |
int |
20 |
Number of horizons to compute |
method |
str |
"cholesky" |
"cholesky" or "generalized" |
order |
list[str] |
None |
Variable ordering for Cholesky |
Identification Methods¶
Based on orthogonalized IRFs from Cholesky decomposition. Shares sum exactly to 100% at every horizon.
Accessing Results¶
# Access FEVD for a specific variable
gdp_fevd = fevd["gdp"] # np.ndarray (periods+1, K)
# gdp_fevd[h, j] = share of GDP variance at horizon h from shock j
# Convert to DataFrame
df_gdp = fevd.to_dataframe(variable="gdp")
df_gdp_horizons = fevd.to_dataframe(variable="gdp", horizons=[1, 5, 10, 20])
df_all = fevd.to_dataframe(horizons=[1, 5, 10, 20])
# Summary table
print(fevd.summary(horizons=[1, 5, 10, 20]))
FEVDResult Attributes:
| Attribute | Type | Description |
|---|---|---|
decomposition |
np.ndarray |
Shape (periods+1, K, K). decomposition[h, i, j] = share of var \(i\) variance at \(h\) from shock \(j\) |
var_names |
list[str] |
Variable names |
periods |
int |
Number of horizons |
method |
str |
Identification method |
ordering |
list[str] or None |
Variable ordering used |
Visualization¶
# Stacked area chart (default) -- shows evolution of shares over horizons
fevd.plot(kind="area", backend="matplotlib")
# Stacked bar chart at selected horizons
fevd.plot(kind="bar", horizons=[1, 5, 10, 20])
# Plot specific variables only
fevd.plot(variables=["gdp", "inflation"])
# Interactive Plotly chart
fevd.plot(backend="plotly", theme="professional")
Interpreting FEVD¶
Typical patterns:
| Horizon | Pattern | Interpretation |
|---|---|---|
| \(h = 1\) | Own shock dominates (~80-100%) | At short horizons, variables are mostly driven by their own shocks |
| \(h = 5\) | Cross-variable shares increase | External shocks start accumulating effect |
| \(h = 20\) | Shares stabilize | Long-run decomposition reveals structural importance |
Reading the decomposition:
Variable: gdp
Horizon Shock gdp Shock inflation Shock rate
h=1 95.2% 3.1% 1.7%
h=5 72.4% 18.3% 9.3%
h=10 58.1% 25.6% 16.3%
h=20 52.3% 28.4% 19.3%
This tells us: - At \(h=1\), 95% of GDP forecast uncertainty comes from GDP's own shocks - By \(h=20\), inflation shocks explain 28% and interest rate shocks explain 19% of GDP forecast uncertainty - Inflation is a more important driver of GDP uncertainty than the interest rate
Consistency Check
FEVD results should be consistent with IRFs. If the IRF shows that inflation has a large effect on GDP, then inflation shocks should explain a meaningful share of GDP's FEVD. If they don't match, investigate the identification assumptions.
Comparing with IRFs¶
# Compute both IRF and FEVD with same identification
irf = results.irf(periods=20, method="cholesky", order=["rate", "inflation", "gdp"])
fevd = results.fevd(periods=20, method="cholesky", order=["rate", "inflation", "gdp"])
# IRF shows direction and magnitude
print("IRF: GDP response to inflation shock")
print(irf["gdp", "inflation"])
# FEVD shows relative importance
print("\nFEVD: GDP variance decomposition")
print(fevd.to_dataframe(variable="gdp", horizons=[1, 5, 10, 20]))
Configuration Options¶
Method Comparison¶
| Feature | Cholesky | Generalized |
|---|---|---|
| Ordering-dependent | Yes | No |
| Shares sum to 100% | Exactly | After normalization |
| Consistent with IRF method | Yes (use same method) | Yes |
| Best for | Theory-guided analysis | Exploratory / robustness |
Complete Example¶
import pandas as pd
from panelbox.var import PanelVARData, PanelVAR
# Load data
df = pd.read_csv("macro_panel.csv")
# Estimate Panel VAR
var_data = PanelVARData(
data=df,
endog_vars=["gdp_growth", "inflation", "interest_rate"],
entity_col="country",
time_col="year",
lags=2,
)
model = PanelVAR(data=var_data)
results = model.fit(cov_type="clustered")
# FEVD with economic ordering: interest rate -> inflation -> GDP
fevd = results.fevd(
periods=20,
method="cholesky",
order=["interest_rate", "inflation", "gdp_growth"],
)
# Detailed summary
print(fevd.summary())
# Export to DataFrame for further analysis
df_fevd = fevd.to_dataframe(horizons=[1, 5, 10, 20])
print(df_fevd)
# Visual analysis
fevd.plot(kind="area", backend="matplotlib", theme="academic")
Tutorials¶
| Tutorial | Description | Link |
|---|---|---|
| FEVD Analysis | Variance decomposition workflow |
See Also¶
- Impulse Response Functions -- Dynamic effects (complementary to FEVD)
- Panel VAR Estimation -- Model setup and estimation
- Granger Causality -- Statistical causality tests
- VECM -- FEVD for cointegrated systems
- Forecasting -- Out-of-sample predictions
References¶
- Luetkepohl, H. (2005). New Introduction to Multiple Time Series Analysis. Springer-Verlag, Chapter 2.
- Pesaran, H. H., & Shin, Y. (1998). Generalized impulse response analysis in linear multivariate models. Economics Letters, 58(1), 17-29.
- Sims, C. A. (1980). Macroeconomics and reality. Econometrica, 48(1), 1-48.