Skip to content

Commit 7e9f37d

Browse files
committed
Improve estimator
1 parent 5ce1633 commit 7e9f37d

File tree

2 files changed

+93
-28
lines changed

2 files changed

+93
-28
lines changed

arch/covariance/var.py

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Dict, NamedTuple, Optional, Tuple
1+
from typing import Dict, NamedTuple, Optional, Tuple, Type
22

33
import numpy as np
4-
from numpy import ones, zeros
4+
from numpy import zeros
55
from numpy.linalg import lstsq
66
import pandas as pd
77
from statsmodels.tools import add_constant
@@ -25,7 +25,7 @@ def _normalize_name(name: str) -> str:
2525
return name
2626

2727

28-
KERNELS = {}
28+
KERNELS: Dict[str, Type[CovarianceEstimator]] = {}
2929
for name in kernel.__all__:
3030
estimator = getattr(kernel, name)
3131
if issubclass(estimator, kernel.CovarianceEstimator):
@@ -77,7 +77,7 @@ def __init__(
7777
super().__init__(
7878
x, bandwidth=bandwidth, df_adjust=df_adjust, center=center, weights=weights
7979
)
80-
self._kernel = kernel
80+
self._kernel_name = kernel
8181
self._lags = 0
8282
self._diagonal_lags = (0,) * self._x.shape[0]
8383
self._method = method
@@ -100,6 +100,7 @@ def __init__(
100100
f"are:\n\n{available_val}"
101101
)
102102
self._kernel = KERNELS[kernel]
103+
self._kernel_instance: Optional[CovarianceEstimator] = None
103104

104105
# Attach for testing only
105106
self._ics: Dict[Tuple[int, int], float] = {}
@@ -162,8 +163,13 @@ def _ic_from_vars(
162163
c = int(self._center)
163164
nobs, nvar = lhs.shape
164165
_rhs = rhs[:, : (c + full_order * nvar)]
165-
params = lstsq(_rhs, lhs, rcond=None)[0]
166-
resids0 = lhs - _rhs @ params
166+
if _rhs.shape[1] > 0 and lhs.shape[1] > 0:
167+
params = lstsq(_rhs, lhs, rcond=None)[0]
168+
resids0 = lhs - _rhs @ params
169+
else:
170+
# Branch is a workaround of NumPy 1.15
171+
# TODO: Remove after NumPy 1.15 dropped
172+
resids0 = lhs
167173
sigma = resids0.T @ resids0 / nobs
168174
nparam = (c + full_order * nvar) * nvar
169175
ics: Dict[Tuple[int, int], float] = {
@@ -175,8 +181,14 @@ def _ic_from_vars(
175181
purged_indiv_lags = np.empty((nvar, nobs, max_lag - full_order))
176182
for i in range(nvar):
177183
single = indiv_lags[i, :, full_order:]
178-
params = lstsq(_rhs, single, rcond=None)[0]
179-
purged_indiv_lags[i] = single - _rhs @ params
184+
if single.shape[1] > 0 and _rhs.shape[1] > 0:
185+
params = lstsq(_rhs, single, rcond=None)[0]
186+
purged_indiv_lags[i] = single - _rhs @ params
187+
else:
188+
# Branch is a workaround of NumPy 1.15
189+
# TODO: Remove after NumPy 1.15 dropped
190+
purged_indiv_lags[i] = single
191+
180192
for diag_lag in range(1, max_lag - full_order + 1):
181193
resids = self._fit_diagonal(resids0.copy(), diag_lag, purged_indiv_lags)
182194
sigma = resids.T @ resids / nobs
@@ -227,11 +239,17 @@ def _estimate_var(self, full_order: int, diag_order: int) -> VARModel:
227239
ncommon = rhs.shape[1]
228240
for i in range(nvar):
229241
full_rhs = np.hstack([rhs, extra_lags[i]])
230-
single_params = lstsq(full_rhs, lhs[:, i], rcond=None)[0]
231-
params[i, :ncommon] = single_params[:ncommon]
232-
locs = ncommon + i + nvar * np.arange(extra_lags[i].shape[1])
233-
params[i, locs] = single_params[ncommon:]
234-
resids[:, i] = lhs[:, i] - full_rhs @ single_params
242+
if full_rhs.shape[1] > 0:
243+
single_params = lstsq(full_rhs, lhs[:, i], rcond=None)[0]
244+
params[i, :ncommon] = single_params[:ncommon]
245+
locs = ncommon + i + nvar * np.arange(extra_lags[i].shape[1])
246+
params[i, locs] = single_params[ncommon:]
247+
resids[:, i] = lhs[:, i] - full_rhs @ single_params
248+
else:
249+
# Branch is a workaround of NumPy 1.15
250+
# TODO: Remove after NumPy 1.15 dropped
251+
resids[:, i] = lhs[:, i]
252+
235253
return VARModel(resids, params, max_lag, self._center)
236254

237255
def _estimate_sample_cov(self, nvar: int, nlag: int) -> NDArray:
@@ -290,17 +308,23 @@ def _companion_form(
290308

291309
@property
292310
def cov(self) -> CovarianceEstimate:
293-
x = self._x
294311
common, individual = self._select_lags()
295312
self._order = (common, individual)
296313
var_mod = self._estimate_var(common, individual)
297314
resids = var_mod.resids
298315
nobs, nvar = resids.shape
299-
short_run = resids.T @ resids / nobs
316+
self._kernel_instance = self._kernel(
317+
resids, self._bandwidth, 0, False, self._x_weights, self._force_int
318+
)
319+
kern_cov = self._kernel_instance.cov
320+
short_run = kern_cov.short_run
321+
x_orig = self._x_orig
322+
columns = x_orig.columns if isinstance(x_orig, pd.DataFrame) else None
300323
if var_mod.var_order == 0:
301324
# Special case VAR(0)
302325
# TODO: Docs should reflect different DoF adjustment
303-
return CovarianceEstimate(short_run, np.zeros((nvar, nvar)))
326+
oss = kern_cov.one_sided_strict
327+
return CovarianceEstimate(short_run, oss, columns)
304328
comp_coefs, comp_var_cov = self._companion_form(var_mod, short_run)
305329
max_eig = np.abs(np.linalg.eigvals(comp_coefs)).max()
306330
if max_eig >= 1:
@@ -328,7 +352,6 @@ def cov(self) -> CovarianceEstimate:
328352

329353
one_sided = one_sided[:nvar, :nvar]
330354
one_sided_strict = one_sided_strict[:nvar, :nvar]
331-
columns = x.columns if isinstance(x, pd.DataFrame) else None
332355

333356
return CovarianceEstimate(
334357
short_run,
@@ -338,14 +361,29 @@ def cov(self) -> CovarianceEstimate:
338361
one_sided=one_sided,
339362
)
340363

364+
def _ensure_kernel_instantized(self) -> None:
365+
if self._kernel_instance is None:
366+
self.cov
367+
368+
@property
341369
def bandwidth_scale(self) -> float:
342-
return 1.0
370+
self._ensure_kernel_instantized()
371+
assert self._kernel_instance is not None
372+
return self._kernel_instance.bandwidth_scale
343373

374+
@property
344375
def kernel_const(self) -> float:
345-
return 1.0
376+
self._ensure_kernel_instantized()
377+
assert self._kernel_instance is not None
378+
return self._kernel_instance.kernel_const
346379

347380
def _weights(self) -> NDArray:
348-
return ones(0)
381+
self._ensure_kernel_instantized()
382+
assert self._kernel_instance is not None
383+
return self._kernel_instance._weights()
349384

385+
@property
350386
def rate(self) -> float:
351-
return 2 / 9
387+
self._ensure_kernel_instantized()
388+
assert self._kernel_instance is not None
389+
return self._kernel_instance.rate

arch/tests/covariance/test_var.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,25 @@
1111

1212
DATA_PARAMS = list(product([1, 3], [True, False], [0])) # , 1, 3]))
1313
DATA_IDS = [f"dim: {d}, pandas: {p}, order: {o}" for d, p, o in DATA_PARAMS]
14+
KERNELS = [
15+
"Bartlett",
16+
"Parzen",
17+
"ParzenCauchy",
18+
"ParzenGeometric",
19+
"ParzenRiesz",
20+
"TukeyHamming",
21+
"TukeyHanning",
22+
"TukeyParzen",
23+
"QuadraticSpectral",
24+
"Andrews",
25+
"Gallant",
26+
"NeweyWest",
27+
]
28+
29+
30+
@pytest.fixture(params=KERNELS)
31+
def kernel(request):
32+
return request.param
1433

1534

1635
@pytest.fixture(scope="module", params=DATA_PARAMS, ids=DATA_IDS)
@@ -82,9 +101,14 @@ def direct_var(
82101
if diag_order > full_order:
83102
locs[diag_start:] = c + i + nvar * np.arange(full_order, diag_order)
84103
_rhs = rhs[:, locs]
85-
p = np.linalg.lstsq(_rhs, lhs[:, i : i + 1], rcond=None)[0]
86-
params[i : i + 1, locs] = p.T
87-
resids[:, i : i + 1] = lhs[:, i : i + 1] - _rhs @ p
104+
if _rhs.shape[1] > 0:
105+
p = np.linalg.lstsq(_rhs, lhs[:, i : i + 1], rcond=None)[0]
106+
params[i : i + 1, locs] = p.T
107+
resids[:, i : i + 1] = lhs[:, i : i + 1] - _rhs @ p
108+
else:
109+
# Branch is a workaround of NumPy 1.15
110+
# TODO: Remove after NumPy 1.15 dropped
111+
resids[:, i: i + 1] = lhs[:, i: i + 1]
88112
return params, resids
89113

90114

@@ -125,10 +149,11 @@ def test_direct_var(data, const, full_order, diag_order, max_order, ic):
125149
@pytest.mark.parametrize("method", ["aic", "bic", "hqc"])
126150
def test_ic(data, center, diagonal, method):
127151
pwrc = PreWhitenRecoloredCovariance(
128-
data, center=center, diagonal=diagonal, method=method
152+
data, center=center, diagonal=diagonal, method=method, bandwidth=0.0,
129153
)
130154
cov = pwrc.cov
131-
assert isinstance(cov.short_run, np.ndarray)
155+
expected_type = np.ndarray if isinstance(data, np.ndarray) else pd.DataFrame
156+
assert isinstance(cov.short_run, expected_type)
132157
expected_max_lag = int(data.shape[0] ** (1 / 3))
133158
assert pwrc._max_lag == expected_max_lag
134159
expected_ics = {}
@@ -152,7 +177,7 @@ def test_ic(data, center, diagonal, method):
152177
@pytest.mark.parametrize("lags", [0, 1, 3])
153178
def test_short_long_run(data, center, diagonal, method, lags):
154179
pwrc = PreWhitenRecoloredCovariance(
155-
data, center=center, diagonal=diagonal, method=method, lags=lags
180+
data, center=center, diagonal=diagonal, method=method, lags=lags, bandwidth=0.0,
156181
)
157182
cov = pwrc.cov
158183
full_order, diag_order = pwrc._order
@@ -172,7 +197,9 @@ def test_short_long_run(data, center, diagonal, method, lags):
172197

173198
@pytest.mark.parametrize("sample_autocov", [True, False])
174199
def test_data(data, sample_autocov):
175-
pwrc = PreWhitenRecoloredCovariance(data, sample_autocov=sample_autocov)
200+
pwrc = PreWhitenRecoloredCovariance(
201+
data, sample_autocov=sample_autocov, bandwidth=0.0
202+
)
176203
pwrc.cov
177204

178205

0 commit comments

Comments
 (0)