dasycaus 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dasycaus/__init__.py +70 -0
- dasycaus/bootstrap.py +346 -0
- dasycaus/core.py +483 -0
- dasycaus/data_transform.py +179 -0
- dasycaus/lag_selection.py +280 -0
- dasycaus/utils.py +386 -0
- dasycaus-1.0.0.dist-info/METADATA +440 -0
- dasycaus-1.0.0.dist-info/RECORD +11 -0
- dasycaus-1.0.0.dist-info/WHEEL +5 -0
- dasycaus-1.0.0.dist-info/licenses/LICENSE +48 -0
- dasycaus-1.0.0.dist-info/top_level.txt +1 -0
dasycaus/__init__.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic Asymmetric Causality Tests (DASYCAUS)
|
|
3
|
+
|
|
4
|
+
A Python library for Dynamic Asymmetric and Symmetric Causality Tests
|
|
5
|
+
based on Hatemi-J (2012, 2021) methodology.
|
|
6
|
+
|
|
7
|
+
Author: Dr. Merwan Roudane
|
|
8
|
+
Email: merwanroudane920@gmail.com
|
|
9
|
+
GitHub: https://github.com/merwanroudane/dasycaus
|
|
10
|
+
|
|
11
|
+
Based on:
|
|
12
|
+
- Hatemi-J, A. (2021). Dynamic Asymmetric Causality Tests with an Application.
|
|
13
|
+
Papers 2106.07612, arXiv.org.
|
|
14
|
+
- Hatemi-J, A. (2012). Asymmetric Causality Tests with an Application.
|
|
15
|
+
Empirical Economics, 43, 447-456.
|
|
16
|
+
- Hacker, S. and Hatemi-J, A. (2006). Tests for causality between integrated
|
|
17
|
+
variables using asymptotic and bootstrap distributions: theory and application.
|
|
18
|
+
Applied Economics, 38(13), 1489-1500.
|
|
19
|
+
- Hacker, S. and Hatemi-J, A. (2012). A bootstrap test for causality with
|
|
20
|
+
endogenous lag length choice: theory and application in finance.
|
|
21
|
+
Journal of Economic Studies, 39(2), 144-160.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__version__ = "1.0.0"
|
|
25
|
+
__author__ = "Dr. Merwan Roudane"
|
|
26
|
+
__email__ = "merwanroudane920@gmail.com"
|
|
27
|
+
|
|
28
|
+
from .core import (
|
|
29
|
+
dynamic_asymmetric_causality_test,
|
|
30
|
+
dynamic_symmetric_causality_test,
|
|
31
|
+
asymmetric_causality_test,
|
|
32
|
+
symmetric_causality_test
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .data_transform import (
|
|
36
|
+
transform_to_cumulative_components,
|
|
37
|
+
compute_positive_negative_shocks
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from .bootstrap import (
|
|
41
|
+
bootstrap_critical_values,
|
|
42
|
+
leveraged_bootstrap
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
from .lag_selection import (
|
|
46
|
+
select_optimal_lag,
|
|
47
|
+
information_criteria
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from .utils import (
|
|
51
|
+
generate_subsamples,
|
|
52
|
+
compute_test_ratio,
|
|
53
|
+
plot_dynamic_causality
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
'dynamic_asymmetric_causality_test',
|
|
58
|
+
'dynamic_symmetric_causality_test',
|
|
59
|
+
'asymmetric_causality_test',
|
|
60
|
+
'symmetric_causality_test',
|
|
61
|
+
'transform_to_cumulative_components',
|
|
62
|
+
'compute_positive_negative_shocks',
|
|
63
|
+
'bootstrap_critical_values',
|
|
64
|
+
'leveraged_bootstrap',
|
|
65
|
+
'select_optimal_lag',
|
|
66
|
+
'information_criteria',
|
|
67
|
+
'generate_subsamples',
|
|
68
|
+
'compute_test_ratio',
|
|
69
|
+
'plot_dynamic_causality'
|
|
70
|
+
]
|
dasycaus/bootstrap.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bootstrap module for generating critical values with leverage adjustments.
|
|
3
|
+
|
|
4
|
+
This module implements the bootstrap procedure described in Hacker and Hatemi-J (2006, 2012).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def bootstrap_critical_values(
|
|
13
|
+
data: np.ndarray,
|
|
14
|
+
lag_order: int,
|
|
15
|
+
integration_order: int,
|
|
16
|
+
num_simulations: int = 1000,
|
|
17
|
+
significance_level: float = 0.05,
|
|
18
|
+
random_seed: Optional[int] = None,
|
|
19
|
+
leverage_adjustment: bool = True
|
|
20
|
+
) -> float:
|
|
21
|
+
"""
|
|
22
|
+
Compute bootstrap critical values for causality tests with leverage adjustments.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
data : np.ndarray
|
|
27
|
+
Data matrix with shape (T, n).
|
|
28
|
+
lag_order : int
|
|
29
|
+
Lag order for VAR model.
|
|
30
|
+
integration_order : int
|
|
31
|
+
Integration order (for adding extra lags).
|
|
32
|
+
num_simulations : int, default=1000
|
|
33
|
+
Number of bootstrap simulations.
|
|
34
|
+
significance_level : float, default=0.05
|
|
35
|
+
Significance level for critical value.
|
|
36
|
+
random_seed : int, optional
|
|
37
|
+
Random seed for reproducibility.
|
|
38
|
+
leverage_adjustment : bool, default=True
|
|
39
|
+
Whether to apply leverage adjustments to residuals.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
float
|
|
44
|
+
Bootstrap critical value at the specified significance level.
|
|
45
|
+
"""
|
|
46
|
+
if random_seed is not None:
|
|
47
|
+
np.random.seed(random_seed)
|
|
48
|
+
|
|
49
|
+
T, n = data.shape
|
|
50
|
+
total_lags = lag_order + integration_order
|
|
51
|
+
|
|
52
|
+
# Estimate restricted VAR model (under null hypothesis)
|
|
53
|
+
Y_restricted, X_restricted, residuals_restricted = _estimate_restricted_var(
|
|
54
|
+
data, total_lags, n
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Apply leverage adjustment to residuals if requested
|
|
58
|
+
if leverage_adjustment:
|
|
59
|
+
residuals_adjusted = _leverage_adjust_residuals(
|
|
60
|
+
residuals_restricted, X_restricted
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
residuals_adjusted = residuals_restricted
|
|
64
|
+
|
|
65
|
+
# Bootstrap simulation
|
|
66
|
+
test_statistics = np.zeros(num_simulations)
|
|
67
|
+
|
|
68
|
+
for i in range(num_simulations):
|
|
69
|
+
# Generate bootstrap data
|
|
70
|
+
Y_bootstrap = _generate_bootstrap_data(
|
|
71
|
+
Y_restricted, X_restricted, residuals_adjusted, total_lags, n
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Create full bootstrap data matrix
|
|
75
|
+
bootstrap_data = _reconstruct_data(Y_bootstrap, data, total_lags)
|
|
76
|
+
|
|
77
|
+
# Compute Wald statistic on bootstrap sample
|
|
78
|
+
try:
|
|
79
|
+
test_statistics[i] = _compute_wald_statistic_restricted(
|
|
80
|
+
bootstrap_data, lag_order, integration_order
|
|
81
|
+
)
|
|
82
|
+
except np.linalg.LinAlgError:
|
|
83
|
+
# Handle singular matrix
|
|
84
|
+
test_statistics[i] = 0
|
|
85
|
+
warnings.warn(f"Singular matrix in bootstrap iteration {i}")
|
|
86
|
+
|
|
87
|
+
# Compute critical value as the (1-alpha) quantile
|
|
88
|
+
critical_value = np.percentile(test_statistics, (1 - significance_level) * 100)
|
|
89
|
+
|
|
90
|
+
return float(critical_value)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def leveraged_bootstrap(
|
|
94
|
+
data: np.ndarray,
|
|
95
|
+
lag_order: int,
|
|
96
|
+
integration_order: int,
|
|
97
|
+
num_simulations: int = 1000,
|
|
98
|
+
random_seed: Optional[int] = None
|
|
99
|
+
) -> np.ndarray:
|
|
100
|
+
"""
|
|
101
|
+
Perform leveraged bootstrap and return all test statistics.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
data : np.ndarray
|
|
106
|
+
Data matrix with shape (T, n).
|
|
107
|
+
lag_order : int
|
|
108
|
+
Lag order for VAR model.
|
|
109
|
+
integration_order : int
|
|
110
|
+
Integration order.
|
|
111
|
+
num_simulations : int, default=1000
|
|
112
|
+
Number of bootstrap simulations.
|
|
113
|
+
random_seed : int, optional
|
|
114
|
+
Random seed for reproducibility.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
np.ndarray
|
|
119
|
+
Array of bootstrap test statistics.
|
|
120
|
+
"""
|
|
121
|
+
if random_seed is not None:
|
|
122
|
+
np.random.seed(random_seed)
|
|
123
|
+
|
|
124
|
+
T, n = data.shape
|
|
125
|
+
total_lags = lag_order + integration_order
|
|
126
|
+
|
|
127
|
+
# Estimate restricted VAR model
|
|
128
|
+
Y_restricted, X_restricted, residuals_restricted = _estimate_restricted_var(
|
|
129
|
+
data, total_lags, n
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Apply leverage adjustment
|
|
133
|
+
residuals_adjusted = _leverage_adjust_residuals(
|
|
134
|
+
residuals_restricted, X_restricted
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Bootstrap simulation
|
|
138
|
+
test_statistics = np.zeros(num_simulations)
|
|
139
|
+
|
|
140
|
+
for i in range(num_simulations):
|
|
141
|
+
Y_bootstrap = _generate_bootstrap_data(
|
|
142
|
+
Y_restricted, X_restricted, residuals_adjusted, total_lags, n
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
bootstrap_data = _reconstruct_data(Y_bootstrap, data, total_lags)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
test_statistics[i] = _compute_wald_statistic_restricted(
|
|
149
|
+
bootstrap_data, lag_order, integration_order
|
|
150
|
+
)
|
|
151
|
+
except np.linalg.LinAlgError:
|
|
152
|
+
test_statistics[i] = 0
|
|
153
|
+
|
|
154
|
+
return test_statistics
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _estimate_restricted_var(
|
|
158
|
+
data: np.ndarray,
|
|
159
|
+
total_lags: int,
|
|
160
|
+
n: int
|
|
161
|
+
) -> tuple:
|
|
162
|
+
"""
|
|
163
|
+
Estimate restricted VAR model under null hypothesis of no causality.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
data : np.ndarray
|
|
168
|
+
Data matrix with shape (T, n).
|
|
169
|
+
total_lags : int
|
|
170
|
+
Total number of lags (including extra lags for unit roots).
|
|
171
|
+
n : int
|
|
172
|
+
Number of variables.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
tuple
|
|
177
|
+
(Y, X, residuals) from restricted VAR estimation.
|
|
178
|
+
"""
|
|
179
|
+
T = data.shape[0]
|
|
180
|
+
|
|
181
|
+
# Create Y and X matrices
|
|
182
|
+
Y = data[total_lags:, :]
|
|
183
|
+
|
|
184
|
+
# Create X matrix with restrictions
|
|
185
|
+
# Under null: variable 2 does not cause variable 1
|
|
186
|
+
# So we exclude lags of variable 2 from equation 1
|
|
187
|
+
X_list = [np.ones((T - total_lags, 1))]
|
|
188
|
+
|
|
189
|
+
for lag in range(1, total_lags + 1):
|
|
190
|
+
X_list.append(data[total_lags - lag : T - lag, :])
|
|
191
|
+
|
|
192
|
+
X = np.hstack(X_list)
|
|
193
|
+
|
|
194
|
+
# For restricted model, we estimate coefficients
|
|
195
|
+
# Equation 1: only includes lags of variable 1
|
|
196
|
+
# Equation 2: includes lags of both variables
|
|
197
|
+
|
|
198
|
+
Y1 = Y[:, 0]
|
|
199
|
+
Y2 = Y[:, 1] if n > 1 else None
|
|
200
|
+
|
|
201
|
+
# Build restricted X for equation 1
|
|
202
|
+
X1_list = [np.ones((T - total_lags, 1))]
|
|
203
|
+
for lag in range(1, total_lags + 1):
|
|
204
|
+
X1_list.append(data[total_lags - lag : T - lag, 0:1])
|
|
205
|
+
X1 = np.hstack(X1_list)
|
|
206
|
+
|
|
207
|
+
# Estimate equation 1
|
|
208
|
+
beta1 = np.linalg.lstsq(X1, Y1, rcond=None)[0]
|
|
209
|
+
residuals1 = Y1 - X1 @ beta1
|
|
210
|
+
|
|
211
|
+
# Estimate equation 2 (unrestricted)
|
|
212
|
+
if n > 1:
|
|
213
|
+
beta2 = np.linalg.lstsq(X, Y2, rcond=None)[0]
|
|
214
|
+
residuals2 = Y2 - X @ beta2
|
|
215
|
+
residuals = np.column_stack([residuals1, residuals2])
|
|
216
|
+
else:
|
|
217
|
+
residuals = residuals1.reshape(-1, 1)
|
|
218
|
+
|
|
219
|
+
return Y, X, residuals
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _leverage_adjust_residuals(
|
|
223
|
+
residuals: np.ndarray,
|
|
224
|
+
X: np.ndarray
|
|
225
|
+
) -> np.ndarray:
|
|
226
|
+
"""
|
|
227
|
+
Apply leverage adjustments to residuals.
|
|
228
|
+
|
|
229
|
+
This follows the procedure in Davison and Hinkley (1999) and
|
|
230
|
+
Hacker and Hatemi-J (2006).
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
residuals : np.ndarray
|
|
235
|
+
Residuals from VAR model with shape (T, n).
|
|
236
|
+
X : np.ndarray
|
|
237
|
+
Regressor matrix with shape (T, k).
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
np.ndarray
|
|
242
|
+
Leverage-adjusted residuals.
|
|
243
|
+
"""
|
|
244
|
+
T = residuals.shape[0]
|
|
245
|
+
|
|
246
|
+
# Compute hat matrix diagonal (leverages)
|
|
247
|
+
H = X @ np.linalg.inv(X.T @ X) @ X.T
|
|
248
|
+
h = np.diag(H)
|
|
249
|
+
|
|
250
|
+
# Adjust residuals
|
|
251
|
+
adjusted_residuals = residuals / np.sqrt(1 - h).reshape(-1, 1)
|
|
252
|
+
|
|
253
|
+
return adjusted_residuals
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _generate_bootstrap_data(
|
|
257
|
+
Y: np.ndarray,
|
|
258
|
+
X: np.ndarray,
|
|
259
|
+
residuals: np.ndarray,
|
|
260
|
+
total_lags: int,
|
|
261
|
+
n: int
|
|
262
|
+
) -> np.ndarray:
|
|
263
|
+
"""
|
|
264
|
+
Generate bootstrap data using residual resampling.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
Y : np.ndarray
|
|
269
|
+
Dependent variable matrix.
|
|
270
|
+
X : np.ndarray
|
|
271
|
+
Regressor matrix.
|
|
272
|
+
residuals : np.ndarray
|
|
273
|
+
Adjusted residuals.
|
|
274
|
+
total_lags : int
|
|
275
|
+
Total number of lags.
|
|
276
|
+
n : int
|
|
277
|
+
Number of variables.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
np.ndarray
|
|
282
|
+
Bootstrap dependent variable matrix.
|
|
283
|
+
"""
|
|
284
|
+
T = residuals.shape[0]
|
|
285
|
+
|
|
286
|
+
# Random sampling with replacement
|
|
287
|
+
bootstrap_indices = np.random.choice(T, size=T, replace=True)
|
|
288
|
+
bootstrap_residuals = residuals[bootstrap_indices, :]
|
|
289
|
+
|
|
290
|
+
# Mean-adjust bootstrap residuals
|
|
291
|
+
bootstrap_residuals = bootstrap_residuals - np.mean(bootstrap_residuals, axis=0)
|
|
292
|
+
|
|
293
|
+
# Estimate coefficients from restricted model
|
|
294
|
+
# For simplicity, we use the full X matrix here
|
|
295
|
+
# In practice, this should respect the restrictions
|
|
296
|
+
beta = np.linalg.lstsq(X, Y, rcond=None)[0]
|
|
297
|
+
|
|
298
|
+
# Generate bootstrap Y
|
|
299
|
+
Y_bootstrap = X @ beta + bootstrap_residuals
|
|
300
|
+
|
|
301
|
+
return Y_bootstrap
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _reconstruct_data(
|
|
305
|
+
Y_bootstrap: np.ndarray,
|
|
306
|
+
original_data: np.ndarray,
|
|
307
|
+
total_lags: int
|
|
308
|
+
) -> np.ndarray:
|
|
309
|
+
"""
|
|
310
|
+
Reconstruct full data matrix from bootstrap Y and original initial values.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
Y_bootstrap : np.ndarray
|
|
315
|
+
Bootstrap dependent variables.
|
|
316
|
+
original_data : np.ndarray
|
|
317
|
+
Original data (to get initial values).
|
|
318
|
+
total_lags : int
|
|
319
|
+
Number of lags.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
np.ndarray
|
|
324
|
+
Full bootstrap data matrix.
|
|
325
|
+
"""
|
|
326
|
+
# Use original data for initial lags
|
|
327
|
+
initial_data = original_data[:total_lags, :]
|
|
328
|
+
|
|
329
|
+
# Combine initial data and bootstrap data
|
|
330
|
+
full_bootstrap_data = np.vstack([initial_data, Y_bootstrap])
|
|
331
|
+
|
|
332
|
+
return full_bootstrap_data
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _compute_wald_statistic_restricted(
|
|
336
|
+
data: np.ndarray,
|
|
337
|
+
lag_order: int,
|
|
338
|
+
integration_order: int
|
|
339
|
+
) -> float:
|
|
340
|
+
"""
|
|
341
|
+
Compute Wald test statistic (same as in core module).
|
|
342
|
+
|
|
343
|
+
This is a simplified version for bootstrap.
|
|
344
|
+
"""
|
|
345
|
+
from .core import _compute_wald_statistic
|
|
346
|
+
return _compute_wald_statistic(data, lag_order, integration_order)
|