Skip to content

ENH: Make the ADF t- and z-test critical values and p-values consistent - #1018

Merged
bashtage merged 14 commits into
mainfrom
a90-adf-t-full
Nov 11, 2026
Merged

bashtage merged 14 commits into
mainfrom
a90-adf-t-full

Conversation

@bashtage

@bashtage bashtage commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Makes the ADF t-test and z-test tables consistent with each other and with how the tests look them up:

  1. ADF-t: regenerates the tables from the complete simulation run, 2,000 files per trend with 250,000 draws each. ENH: Fit the ADF t-test critical values to the same simulations as the p-values #1017 used 7,816 of these files and asked for this rerun.
  2. ADF-z: processes its simulations the way ENH: Fit the ADF t-test critical values to the same simulations as the p-values #1017 processes ADF-t. As a result, the ADF-z critical values and p-values are consistent, and the critical values use the nobs that PhillipsPerron passes.
  3. Lookups: checks that every critical-value lookup passes the nobs its table was fit in, and corrects the one docstring that described it wrongly.

1. ADF-t from the complete run

The output barely changes:

  • P-values below 0.15: they change from main's by at most 0.00001.
  • Elsewhere: the largest change is 0.0006, with c, where the cut-off moves from -1.72243 to -1.70276. With the other trends, p changes by at most 0.00004.
  • Critical values: they change by at most 0.00003.

The p-values of the asymptotic 1%, 5% and 10% critical values are within 0.000012 of 1%, 5% and 10%. In the tail, the largest relative error of p-values at or below 0.15 is at most 0.06% (5.7 standard errors).

2. ADF-z made consistent

adf_z_simlation_process.py now works like adf_t_simulation_process.py:

  • Sample size: the columns are labeled nobs = T − 1, the number of observations in the regression. They were labeled with the series length T, but PhillipsPerron (test_type="rho") passes T − 1 to mackinnoncrit. So the critical values were those of a sample one observation shorter, off by up to 0.35 with 25 observations and 0.03 with 100.
  • Critical values: they come from estimate_response_surface, which drops the highest power of 1/nobs while it's insignificant and then refits. On main, estimate_cv_regression had set the 1/T³ terms of the 5% and 10% values with n and c to 0 without refitting.
  • P-value: it's fit to the asymptotic quantiles, the constants of the regressions, in place of the quantiles with T=2000. The piece below the cut-off is now a quartic in log|stat|, in place of a cubic. fit_smooth_pval_model accepts small_order=5, and its junction and monotonicity code already handled any order.
  • Script: the files are sorted, the script takes --data-path, --output and --ex-size, and it prints the errors of the fits.

These are the p-values of the asymptotic 1%, 5% and 10% critical values:

n c ct ctt
main 0.00991, 0.04980, 0.09966 0.00986, 0.04964, 0.09951 0.00975, 0.04933, 0.09907 0.00966, 0.04906, 0.09866
cubic in log|stat| 0.010003, 0.049999, 0.099924 0.010003, 0.049995, 0.099969 0.010002, 0.049995, 0.099984 0.010000, 0.049999, 0.099991
this PR (quartic) 0.010001, 0.049995, 0.100042 0.010001, 0.049996, 0.100002 0.010001, 0.049995, 0.099999 0.010000, 0.049999, 0.100001
  • The p-value fit: the quartic improves the tail for every trend. The largest relative error of p-values at or below 0.15 is at most 0.05% (0.13% with the cubic), against up to 6.0% in 8.0.

    n c ct ctt
    Largest error, p ≤ 0.15: cubic → quartic 2.0e-4 → 6.9e-5 8.4e-5 → 1.4e-5 4.1e-5 → 1.3e-5 2.0e-5 → 8.1e-6
    Largest error, p > 0.15: cubic → quartic 0.0118 → 0.0133 0.0017 → 0.0015 0.0011 → 0.0006 0.0011 → 0.0003

    The only cost is a slightly larger body error with n. The p-values of the asymptotic critical values are within 0.00005 with n and within 0.000005 otherwise. With n this is still looser than ADF-t (0.00002), because of the large-p cubic.

  • The critical-value regressions: they're within 2.6 standard errors of the average simulated quantile at every nobs, except with ctt at nobs = 24, where they're off by up to 0.0075 (7 standard errors at 1%, 4 at 5%). A cubic in 1/nobs doesn't quite follow ctt in the smallest samples.

  • Changes from 8.0 with a constant or a trend:

    • Critical values change by up to 0.49 with 19 observations, by at most 0.032 with 100 or more, and by at most 0.002 asymptotically.
    • P-values at or below 0.15 change by at most 0.0015. The largest change is 0.014, at a p-value near 0.7 with c.
  • Changes with n: the 1%, 5% and 10% values with 100 observations are -13.09, -7.79 and -5.56. The p-value of -8 is 0.0506.

This part is the work of a separate session (branch wip-adf-z-cv), whose commits are cherry-picked here. The quartic was added afterwards.

3. Lookups

Each table and the nobs that is used to look it up:

Table Fit in Looked up with Consistent
ADF-t (ADF, PhillipsPerron tau) regression nobs (T − 1) resols.nobs yes
ADF-z (PhillipsPerron rho) series length T resols.nobs (T − 1) fixed here
DFGLS regression nobs (T − 1) resols.nobs yes
Engle-Granger residual ADF regression nobs resid.shape[0] - lags - 1 yes (docstring corrected)
Phillips-Ouliaris series length T z.shape[0] (T) yes
KPSS, Zivot-Andrews asymptotic only — —

The engle_granger_cv docstring said that nobs is the number of observations in the time series. It's the number of observations in the ADF regression of the residuals, which engle_granger passes.

  • Not changed, Engle-Granger: the simulation computes the t statistic with the residual variance divided by n, but engle_granger uses ADF, which divides by n − k. In small samples this makes the critical values a little too far from 0, about 2% of the statistic with 24 observations.
  • Not changed, DFGLS: the simulation divides by the regression's nobs, with a # DOF correction? comment in the code. DFGLS divides by nobs − 1.
  • Fixing either: it needs new simulations, so it isn't done here.

Checks

  • Data: all 8,000 ADF-t files load as 124 × 31 arrays. They are finite and increase with the probability, and no two are identical. So do the 8,000 ADF-z files (2,000 per trend).
  • Processing: running adf_z_simlation_process.py and then adf_t_simulation_process.py on this branch reproduces dickey_fuller.py byte for byte.
  • Release note: the numbers in the ADF-z note were rechecked against main and 8.0.
  • Tests:
    • The unit root tests and test_lower_case_options.py pass (8,300).
    • The full suite passes with --skip-slow (31,582 passed, 812 skipped).
    • The docstring examples of arch.unitroot pass.
    • black, isort, ruff and flake8 pass, mypy finds no new errors, and docutils parses the release notes with no messages.

🤖 Generated with Claude Code

Run adf_t_simulation_process.py on all 8,000 simulation files, 2,000 per
trend with 250,000 draws each. P-values below 0.15 change by at most
0.00001 and the critical values by at most 0.00003. With "c" the cut-off
moves from -1.72243 to -1.70276, which changes p by up to 0.0006.
@bashtage
bashtage marked this pull request as ready for review November 11, 2026 13:50
Regress every simulated quantile of the ADF z-statistic on a constant and
powers of 1/nobs, where nobs = T - 1 is the number of observations in the
regression, which ADF and PhillipsPerron pass to mackinnoncrit. The
simulations were labeled with the series length T, so the critical values
were those of a sample one observation smaller. The regressions of the 1%,
5% and 10% quantiles are the critical values, adf_z_cv_approx. The
p-value is fit to the constants of all 124 regressions, the asymptotic
quantiles, in place of the quantiles with T=2000, so the p-values of the
asymptotic critical values are their sizes.

Use estimate_response_surface, which drops the highest power of 1/nobs
while it is insignificant and fits the regression again, in place of
estimate_cv_regression, which set the 1/T**3 terms of the 5% and 10%
critical values with "n" and "c" to 0 without refitting the others. The
files are sorted so that the averages do not depend on the order of the
directory, and the script takes --data-path, --output and --ex-size, and
prints the errors of the fits, as adf_t_simulation_process.py does.
Run adf_z_simlation_process.py on the 2,000 simulation files of each
trend (250,000 draws each). The critical values are fit in 1/nobs, and
the p-value to the asymptotic quantiles, so the p-values of the
asymptotic critical values are within 0.00008 of 1%, 5% and 10%, where
they were as low as 0.0097, 0.0491 and 0.0987.
Check that the p-values of the asymptotic critical values are 1%, 5% and
10%, and that the critical values match the simulated quantiles with 24,
99 and infinitely many observations in the regression. The p-value
reference quantiles are now the asymptotic quantiles.
The ADF z-test critical values are now fit to the same simulations as the
p-values, and the p-values of the asymptotic critical values are 1%, 5%
and 10%, as for the ADF t-test with one unit root.
The critical values are now fit to the same simulations as the p-values,
in powers of 1/nobs, and the p-value to the asymptotic quantiles. Update
the numbers of the ADF-Z entries, which compare with 8.0.
The Engle-Granger critical values are fit to simulations in the number of
observations of the ADF regression of the residuals, which engle_granger
passes. The docstring said the number of observations in the time series.
@bashtage bashtage changed the title ENH: Regenerate the ADF t-test tables from the complete simulation run ENH: Make the ADF t- and z-test critical values and p-values consistent Nov 11, 2026
small_order may be 5. The junction constraints and the monotonicity
checks already handle any order. Test a quartic fit and its constraints,
and that orders 2 and 6 are rejected.
With a cubic, the p-values of the asymptotic 10% critical values were off
by up to 7.6e-5. With a quartic they are within 4.2e-5 of 1%, 5% and 10%
with "n" and within 5e-6 otherwise. The largest error of p-values below
0.15 falls from 2.0e-4 to 6.9e-5 with "n" and from 8.4e-5 to 1.4e-5 or
less otherwise. In the body the largest error is 0.013 with "n" (0.012
with the cubic) and at most 0.0015 otherwise (0.0017). The critical
values are unchanged.
@codecov

codecov Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.44%. Comparing base (67045ff) to head (176e243).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1018   +/-   ##
=======================================
  Coverage   99.44%   99.44%           
=======================================
  Files         153      153           
  Lines       34259    34278   +19     
  Branches     2594     2595    +1     
=======================================
+ Hits        34069    34088   +19     
  Misses        114      114           
  Partials       76       76           
Flag Coverage Δ
adder 99.39% <100.00%> (+<0.01%) ⬆️
subtractor 99.39% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bashtage
bashtage merged commit b7c5046 into main Nov 11, 2026
28 checks passed
@bashtage
bashtage deleted the a90-adf-t-full branch November 14, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant