tempdisagg 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {tempdisagg-0.2.0/tempdisagg.egg-info → tempdisagg-0.2.2}/PKG-INFO +2 -3
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/pyproject.toml +6 -8
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tempdisagg/__init__.py +6 -5
- {tempdisagg-0.2.0 → tempdisagg-0.2.2/tempdisagg.egg-info}/PKG-INFO +2 -3
- tempdisagg-0.2.2/tempdisagg.egg-info/SOURCES.txt +13 -0
- tempdisagg-0.2.0/tempdisagg/core/__init__.py +0 -9
- tempdisagg-0.2.0/tempdisagg/core/numeric_utils.py +0 -151
- tempdisagg-0.2.0/tempdisagg/core/rho_optimizer.py +0 -212
- tempdisagg-0.2.0/tempdisagg/core/temporal_aggregator.py +0 -82
- tempdisagg-0.2.0/tempdisagg/model/__init__.py +0 -4
- tempdisagg-0.2.0/tempdisagg/model/models_handler.py +0 -515
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_adjuster.py +0 -115
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_base.py +0 -120
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_core.py +0 -292
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_ensemble.py +0 -247
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_fitter.py +0 -190
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_model.py +0 -299
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_summary.py +0 -212
- tempdisagg-0.2.0/tempdisagg/model/tempdisagg_visualizer.py +0 -69
- tempdisagg-0.2.0/tempdisagg/postprocessing/__init__.py +0 -7
- tempdisagg-0.2.0/tempdisagg/postprocessing/ensemble_prediction.py +0 -298
- tempdisagg-0.2.0/tempdisagg/postprocessing/post_estimation.py +0 -245
- tempdisagg-0.2.0/tempdisagg/preprocessing/__init__.py +0 -11
- tempdisagg-0.2.0/tempdisagg/preprocessing/conversion_matrix_builder.py +0 -150
- tempdisagg-0.2.0/tempdisagg/preprocessing/disagg_input_preparer.py +0 -134
- tempdisagg-0.2.0/tempdisagg/preprocessing/input_preprocessor.py +0 -115
- tempdisagg-0.2.0/tempdisagg/preprocessing/timeseries_completer.py +0 -268
- tempdisagg-0.2.0/tempdisagg/utils/__init__.py +0 -6
- tempdisagg-0.2.0/tempdisagg/utils/logging_utils.py +0 -68
- tempdisagg-0.2.0/tempdisagg.egg-info/SOURCES.txt +0 -62
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/LICENSE +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/README.md +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/setup.cfg +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tempdisagg.egg-info/dependency_links.txt +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tempdisagg.egg-info/requires.txt +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tempdisagg.egg-info/top_level.txt +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tests/test_numeric_utils.py +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tests/test_prediction.py +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tests/test_rho_optimizer.py +0 -0
- {tempdisagg-0.2.0 → tempdisagg-0.2.2}/tests/test_summary_visualize.py +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempdisagg
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Temporal disaggregation models in Python
|
|
5
5
|
Author-email: Jaime Vera-Jaramillo <jaimevera1107@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/jaimevera1107/tempdisagg
|
|
8
8
|
Keywords: temporal disaggregation,time series,econometrics,interpolation
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
@@ -4,18 +4,17 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tempdisagg"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Temporal disaggregation models in Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Jaime Vera-Jaramillo", email = "jaimevera1107@gmail.com" }
|
|
12
12
|
]
|
|
13
|
-
license =
|
|
13
|
+
license = "MIT"
|
|
14
14
|
keywords = ["temporal disaggregation", "time series", "econometrics", "interpolation"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Intended Audience :: Science/Research",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
18
|
"Programming Language :: Python :: 3",
|
|
20
19
|
"Programming Language :: Python :: 3.8",
|
|
21
20
|
"Operating System :: OS Independent"
|
|
@@ -32,12 +31,11 @@ dependencies = [
|
|
|
32
31
|
[project.urls]
|
|
33
32
|
Homepage = "https://github.com/jaimevera1107/tempdisagg"
|
|
34
33
|
|
|
35
|
-
[tool.setuptools
|
|
36
|
-
|
|
37
|
-
include = ["tempdisagg*"]
|
|
34
|
+
[tool.setuptools]
|
|
35
|
+
packages = ["tempdisagg"]
|
|
38
36
|
|
|
39
37
|
[tool.setuptools.package-data]
|
|
40
38
|
tempdisagg = ["py.typed"]
|
|
41
39
|
|
|
42
|
-
[tool.setuptools]
|
|
43
|
-
|
|
40
|
+
[tool.setuptools.exclude-package-data]
|
|
41
|
+
"*" = ["notebooks*", "example*", "tests*"]
|
|
@@ -4,11 +4,6 @@
|
|
|
4
4
|
tempdisagg: Temporal disaggregation of low-frequency time series using various statistical methods.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
# Versioning and metadata
|
|
8
|
-
__version__ = "0.2.0"
|
|
9
|
-
__author__ = "Jaime Vera-Jaramillo"
|
|
10
|
-
__license__ = "MIT"
|
|
11
|
-
|
|
12
7
|
# Public API
|
|
13
8
|
from .model.tempdisagg_model import TempDisaggModel
|
|
14
9
|
|
|
@@ -31,3 +26,9 @@ __all__ = [
|
|
|
31
26
|
"EnsemblePrediction",
|
|
32
27
|
"TemporalAggregator",
|
|
33
28
|
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Versioning and metadata
|
|
32
|
+
__version__ = "0.2.2"
|
|
33
|
+
__author__ = "Jaime Vera-Jaramillo"
|
|
34
|
+
__license__ = "MIT"
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempdisagg
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Temporal disaggregation models in Python
|
|
5
5
|
Author-email: Jaime Vera-Jaramillo <jaimevera1107@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/jaimevera1107/tempdisagg
|
|
8
8
|
Keywords: temporal disaggregation,time series,econometrics,interpolation
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
tempdisagg/__init__.py
|
|
5
|
+
tempdisagg.egg-info/PKG-INFO
|
|
6
|
+
tempdisagg.egg-info/SOURCES.txt
|
|
7
|
+
tempdisagg.egg-info/dependency_links.txt
|
|
8
|
+
tempdisagg.egg-info/requires.txt
|
|
9
|
+
tempdisagg.egg-info/top_level.txt
|
|
10
|
+
tests/test_numeric_utils.py
|
|
11
|
+
tests/test_prediction.py
|
|
12
|
+
tests/test_rho_optimizer.py
|
|
13
|
+
tests/test_summary_visualize.py
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from scipy.linalg import inv, pinv
|
|
3
|
-
import warnings
|
|
4
|
-
|
|
5
|
-
class NumericUtils:
|
|
6
|
-
"""
|
|
7
|
-
Utility class for numerical operations used in temporal disaggregation models.
|
|
8
|
-
Includes methods for constructing power matrices and Q matrices with autocorrelation structure.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
@staticmethod
|
|
12
|
-
def power_matrix(n):
|
|
13
|
-
"""
|
|
14
|
-
Create a matrix where each element (i, j) is |i - j|.
|
|
15
|
-
|
|
16
|
-
Parameters
|
|
17
|
-
----------
|
|
18
|
-
n : int
|
|
19
|
-
Size of the square matrix to generate.
|
|
20
|
-
|
|
21
|
-
Returns
|
|
22
|
-
-------
|
|
23
|
-
numpy.ndarray
|
|
24
|
-
A square matrix of shape (n, n) with absolute differences of indices.
|
|
25
|
-
"""
|
|
26
|
-
# Validate that n is a positive integer
|
|
27
|
-
if not isinstance(n, int) or n <= 0:
|
|
28
|
-
raise ValueError("Parameter 'n' must be a positive integer.")
|
|
29
|
-
|
|
30
|
-
# Construct the power matrix using broadcasting
|
|
31
|
-
return np.abs(np.subtract.outer(np.arange(n), np.arange(n)))
|
|
32
|
-
|
|
33
|
-
@staticmethod
|
|
34
|
-
def q_matrix(rho, power_matrix, epsilon=1e-6):
|
|
35
|
-
"""
|
|
36
|
-
Construct a Q matrix for Chow-Lin type models using a power matrix.
|
|
37
|
-
|
|
38
|
-
Parameters
|
|
39
|
-
----------
|
|
40
|
-
rho : float
|
|
41
|
-
Autocorrelation parameter, expected to be between -1 and 1.
|
|
42
|
-
power_matrix : numpy.ndarray
|
|
43
|
-
Matrix of absolute differences in time indices.
|
|
44
|
-
epsilon : float, optional
|
|
45
|
-
Small constant added for numerical stability (default is 1e-6).
|
|
46
|
-
|
|
47
|
-
Returns
|
|
48
|
-
-------
|
|
49
|
-
numpy.ndarray
|
|
50
|
-
Q matrix capturing autocorrelation structure.
|
|
51
|
-
"""
|
|
52
|
-
# Clip rho to avoid instability
|
|
53
|
-
rho = np.clip(rho, -0.99, 0.99)
|
|
54
|
-
|
|
55
|
-
# Validate input power matrix
|
|
56
|
-
if not isinstance(power_matrix, np.ndarray):
|
|
57
|
-
raise TypeError("Parameter 'power_matrix' must be a NumPy ndarray.")
|
|
58
|
-
|
|
59
|
-
if power_matrix.shape[0] != power_matrix.shape[1]:
|
|
60
|
-
raise ValueError("Parameter 'power_matrix' must be a square matrix.")
|
|
61
|
-
|
|
62
|
-
# Compute the Q matrix based on rho and power_matrix
|
|
63
|
-
return (1 / (1 - rho ** 2 + epsilon)) * (rho ** power_matrix)
|
|
64
|
-
|
|
65
|
-
@staticmethod
|
|
66
|
-
def q_litterman(X, rho=0):
|
|
67
|
-
"""
|
|
68
|
-
Construct the Q matrix for Litterman's method.
|
|
69
|
-
|
|
70
|
-
Parameters
|
|
71
|
-
----------
|
|
72
|
-
X : numpy.ndarray
|
|
73
|
-
Regressor matrix with shape (n, k).
|
|
74
|
-
rho : float, optional
|
|
75
|
-
Autocorrelation parameter (default is 0).
|
|
76
|
-
|
|
77
|
-
Returns
|
|
78
|
-
-------
|
|
79
|
-
numpy.ndarray
|
|
80
|
-
The inverted Q matrix used for GLS estimation.
|
|
81
|
-
"""
|
|
82
|
-
# Validate input matrix
|
|
83
|
-
if not isinstance(X, np.ndarray):
|
|
84
|
-
raise TypeError("Parameter 'X' must be a NumPy ndarray.")
|
|
85
|
-
|
|
86
|
-
if X.ndim != 2:
|
|
87
|
-
raise ValueError("Parameter 'X' must be a 2D array.")
|
|
88
|
-
|
|
89
|
-
# Get the number of observations
|
|
90
|
-
n = X.shape[0]
|
|
91
|
-
|
|
92
|
-
# Construct H and D matrices for differencing
|
|
93
|
-
H = np.eye(n) - np.diag(np.ones(n - 1), -1) * rho
|
|
94
|
-
D = np.eye(n) - np.diag(np.ones(n - 1), -1)
|
|
95
|
-
|
|
96
|
-
# Compute Q_Litterman
|
|
97
|
-
Q_lit = D.T @ H.T @ H @ D
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
# Attempt to invert Q_Lit with a small ridge for stability
|
|
101
|
-
return inv(Q_lit + np.eye(n) * 1e-8)
|
|
102
|
-
except np.linalg.LinAlgError:
|
|
103
|
-
# Fallback to pseudoinverse if singular
|
|
104
|
-
warnings.warn("Q_Lit matrix is singular. Using pseudo-inverse instead.", RuntimeWarning)
|
|
105
|
-
return pinv(Q_lit)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@staticmethod
|
|
109
|
-
def validate_symmetric_positive_definite(matrix, tol=1e-8):
|
|
110
|
-
"""
|
|
111
|
-
Validate whether a matrix is symmetric and positive definite.
|
|
112
|
-
|
|
113
|
-
Parameters
|
|
114
|
-
----------
|
|
115
|
-
matrix : numpy.ndarray
|
|
116
|
-
The matrix to validate.
|
|
117
|
-
tol : float
|
|
118
|
-
Tolerance for symmetry check (default is 1e-8).
|
|
119
|
-
|
|
120
|
-
Returns
|
|
121
|
-
-------
|
|
122
|
-
bool
|
|
123
|
-
True if the matrix is symmetric and positive definite.
|
|
124
|
-
|
|
125
|
-
Raises
|
|
126
|
-
------
|
|
127
|
-
TypeError
|
|
128
|
-
If input is not a NumPy array.
|
|
129
|
-
ValueError
|
|
130
|
-
If the matrix is not square or not symmetric within tolerance.
|
|
131
|
-
np.linalg.LinAlgError
|
|
132
|
-
If the matrix is not positive definite (Cholesky fails).
|
|
133
|
-
"""
|
|
134
|
-
# Ensure matrix is a NumPy array
|
|
135
|
-
if not isinstance(matrix, np.ndarray):
|
|
136
|
-
raise TypeError("Input must be a NumPy ndarray.")
|
|
137
|
-
|
|
138
|
-
# Ensure matrix is square
|
|
139
|
-
if matrix.shape[0] != matrix.shape[1]:
|
|
140
|
-
raise ValueError("Matrix must be square.")
|
|
141
|
-
|
|
142
|
-
# Check symmetry within tolerance
|
|
143
|
-
if not np.allclose(matrix, matrix.T, atol=tol):
|
|
144
|
-
raise ValueError("Matrix is not symmetric within the given tolerance.")
|
|
145
|
-
|
|
146
|
-
try:
|
|
147
|
-
# Try Cholesky decomposition to verify positive definiteness
|
|
148
|
-
_ = np.linalg.cholesky(matrix)
|
|
149
|
-
return True
|
|
150
|
-
except np.linalg.LinAlgError as e:
|
|
151
|
-
raise np.linalg.LinAlgError("Matrix is not positive definite.") from e
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import warnings
|
|
4
|
-
|
|
5
|
-
from scipy.linalg import pinv
|
|
6
|
-
from scipy.optimize import minimize_scalar
|
|
7
|
-
from scipy.stats import norm
|
|
8
|
-
|
|
9
|
-
from tempdisagg.utils.logging_utils import VerboseLogger
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class RhoOptimizer:
|
|
14
|
-
"""
|
|
15
|
-
Optimizes the autocorrelation parameter (rho) for temporal disaggregation models.
|
|
16
|
-
|
|
17
|
-
Supports:
|
|
18
|
-
- 'maxlog': Maximum likelihood estimation.
|
|
19
|
-
- 'minrss': Minimum residual sum of squares.
|
|
20
|
-
|
|
21
|
-
Returns full statistical outputs: rho, beta, residuals, Q matrix, variance-covariance matrix, and summary.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, rho_min=-0.9, rho_max=0.99, verbose=False):
|
|
25
|
-
"""
|
|
26
|
-
Initialize the optimizer with bounds and verbosity.
|
|
27
|
-
|
|
28
|
-
Parameters
|
|
29
|
-
----------
|
|
30
|
-
rho_min : float
|
|
31
|
-
Lower bound for rho (default = -0.9).
|
|
32
|
-
rho_max : float
|
|
33
|
-
Upper bound for rho (default = 0.99).
|
|
34
|
-
verbose : bool
|
|
35
|
-
Whether to enable informative logging.
|
|
36
|
-
"""
|
|
37
|
-
self.rho_min = rho_min
|
|
38
|
-
self.rho_max = rho_max
|
|
39
|
-
self.verbose = verbose
|
|
40
|
-
|
|
41
|
-
# Create logger with verbosity control
|
|
42
|
-
self.logger = VerboseLogger(f"{__name__}.{id(self)}", verbose=self.verbose).get_logger()
|
|
43
|
-
|
|
44
|
-
def power_matrix(self, n):
|
|
45
|
-
"""
|
|
46
|
-
Construct a power matrix of absolute differences |i - j|.
|
|
47
|
-
|
|
48
|
-
Parameters
|
|
49
|
-
----------
|
|
50
|
-
n : int
|
|
51
|
-
Dimension of the square matrix.
|
|
52
|
-
|
|
53
|
-
Returns
|
|
54
|
-
-------
|
|
55
|
-
numpy.ndarray
|
|
56
|
-
Power matrix of shape (n, n).
|
|
57
|
-
"""
|
|
58
|
-
return np.abs(np.subtract.outer(np.arange(n), np.arange(n)))
|
|
59
|
-
|
|
60
|
-
def q_matrix(self, rho, power_matrix, epsilon=1e-6):
|
|
61
|
-
"""
|
|
62
|
-
Generate Q matrix using autocorrelation parameter rho.
|
|
63
|
-
|
|
64
|
-
Parameters
|
|
65
|
-
----------
|
|
66
|
-
rho : float
|
|
67
|
-
Autocorrelation coefficient.
|
|
68
|
-
power_matrix : numpy.ndarray
|
|
69
|
-
Power matrix from self.power_matrix().
|
|
70
|
-
epsilon : float
|
|
71
|
-
Stability constant to avoid division by zero.
|
|
72
|
-
|
|
73
|
-
Returns
|
|
74
|
-
-------
|
|
75
|
-
numpy.ndarray
|
|
76
|
-
Q matrix used for GLS estimation.
|
|
77
|
-
"""
|
|
78
|
-
rho = np.clip(rho, -0.99, 0.99)
|
|
79
|
-
return (1 / (1 - rho**2 + epsilon)) * (rho ** power_matrix)
|
|
80
|
-
|
|
81
|
-
def optimize(self, y_l, X, C, method="maxlog"):
|
|
82
|
-
"""
|
|
83
|
-
Optimize rho and estimate model parameters.
|
|
84
|
-
|
|
85
|
-
Parameters
|
|
86
|
-
----------
|
|
87
|
-
y_l : array-like
|
|
88
|
-
Low-frequency response variable (n_low x 1).
|
|
89
|
-
X : array-like
|
|
90
|
-
High-frequency explanatory variable (n_high x 1).
|
|
91
|
-
C : array-like
|
|
92
|
-
Conversion matrix (n_low x n_high).
|
|
93
|
-
method : str
|
|
94
|
-
Optimization method: 'maxlog' or 'minrss'.
|
|
95
|
-
|
|
96
|
-
Returns
|
|
97
|
-
-------
|
|
98
|
-
dict
|
|
99
|
-
Dictionary with rho, Q, beta, residuals, std_errors, t_stats, p_values, and summary.
|
|
100
|
-
"""
|
|
101
|
-
try:
|
|
102
|
-
# Reshape and validate inputs
|
|
103
|
-
y_l = np.atleast_2d(y_l).reshape(-1, 1)
|
|
104
|
-
X = np.atleast_2d(X).reshape(-1, 1)
|
|
105
|
-
C = np.atleast_2d(C)
|
|
106
|
-
|
|
107
|
-
if C.shape[1] != X.shape[0]:
|
|
108
|
-
raise ValueError("C columns must match X rows.")
|
|
109
|
-
if C.shape[0] != y_l.shape[0]:
|
|
110
|
-
raise ValueError("C rows must match y_l rows.")
|
|
111
|
-
|
|
112
|
-
# Compute low-frequency regressor
|
|
113
|
-
X_l = C @ X
|
|
114
|
-
|
|
115
|
-
# Precompute power matrix
|
|
116
|
-
power_mat = self.power_matrix(X.shape[0])
|
|
117
|
-
|
|
118
|
-
# Store best result during optimization
|
|
119
|
-
results = {}
|
|
120
|
-
|
|
121
|
-
def objective(rho):
|
|
122
|
-
"""
|
|
123
|
-
Objective function to minimize (depends on method).
|
|
124
|
-
"""
|
|
125
|
-
if not (self.rho_min < rho < self.rho_max):
|
|
126
|
-
return np.inf
|
|
127
|
-
|
|
128
|
-
try:
|
|
129
|
-
# Compute Q matrix and covariance
|
|
130
|
-
Q = self.q_matrix(rho, power_mat)
|
|
131
|
-
vcov = C @ Q @ C.T + np.eye(C.shape[0]) * 1e-8
|
|
132
|
-
inv_vcov = pinv(vcov)
|
|
133
|
-
|
|
134
|
-
# Estimate beta
|
|
135
|
-
XTX = X_l.T @ inv_vcov @ X_l
|
|
136
|
-
if XTX.shape[0] != XTX.shape[1]:
|
|
137
|
-
return np.inf
|
|
138
|
-
|
|
139
|
-
beta = pinv(XTX) @ X_l.T @ inv_vcov @ y_l
|
|
140
|
-
residuals = y_l - X_l @ beta
|
|
141
|
-
|
|
142
|
-
# Evaluate objective
|
|
143
|
-
if method == "maxlog":
|
|
144
|
-
det = np.linalg.det(vcov)
|
|
145
|
-
if det <= 0:
|
|
146
|
-
return np.inf
|
|
147
|
-
loglike = -0.5 * (np.log(det + 1e-8) + residuals.T @ inv_vcov @ residuals)
|
|
148
|
-
value = -loglike.item()
|
|
149
|
-
elif method == "minrss":
|
|
150
|
-
value = (residuals.T @ inv_vcov @ residuals).item()
|
|
151
|
-
else:
|
|
152
|
-
raise ValueError("Invalid method: use 'maxlog' or 'minrss'.")
|
|
153
|
-
|
|
154
|
-
# Update best result
|
|
155
|
-
if "best_val" not in results or value < results["best_val"]:
|
|
156
|
-
results.update({
|
|
157
|
-
"rho": rho,
|
|
158
|
-
"Q": Q,
|
|
159
|
-
"vcov": vcov,
|
|
160
|
-
"beta": beta,
|
|
161
|
-
"residuals": residuals,
|
|
162
|
-
"XTX": XTX,
|
|
163
|
-
"inv_vcov": inv_vcov,
|
|
164
|
-
"X_l": X_l,
|
|
165
|
-
"best_val": value
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
return value
|
|
169
|
-
|
|
170
|
-
except Exception as e:
|
|
171
|
-
warnings.warn(f"Optimization failed at rho={rho:.4f}: {e}", RuntimeWarning)
|
|
172
|
-
return np.inf
|
|
173
|
-
|
|
174
|
-
# Minimize scalar function within bounds
|
|
175
|
-
result = minimize_scalar(objective, bounds=(self.rho_min, self.rho_max), method="bounded")
|
|
176
|
-
|
|
177
|
-
if not result.success:
|
|
178
|
-
raise RuntimeError(f"Rho optimization failed: {result.message}")
|
|
179
|
-
|
|
180
|
-
if self.verbose:
|
|
181
|
-
self.logger.info(f"Optimal rho found: {results['rho']:.6f}")
|
|
182
|
-
|
|
183
|
-
# Compute inference statistics
|
|
184
|
-
XTX_inv = pinv(results["XTX"])
|
|
185
|
-
std_errors = np.sqrt(np.diag(XTX_inv))
|
|
186
|
-
t_stats = results["beta"].flatten() / std_errors
|
|
187
|
-
p_values = 2 * (1 - norm.cdf(np.abs(t_stats)))
|
|
188
|
-
significance = ["***" if p < 0.01 else "**" if p < 0.05 else "*" if p < 0.1 else "" for p in p_values]
|
|
189
|
-
|
|
190
|
-
summary_df = pd.DataFrame({
|
|
191
|
-
"Coef.": results["beta"].flatten(),
|
|
192
|
-
"Std.Err.": std_errors,
|
|
193
|
-
"t-stat": t_stats,
|
|
194
|
-
"P>|t|": p_values,
|
|
195
|
-
"Signif.": significance
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
"rho": results["rho"],
|
|
200
|
-
"Q": results["Q"],
|
|
201
|
-
"vcov": results["vcov"],
|
|
202
|
-
"beta": results["beta"],
|
|
203
|
-
"residuals": results["residuals"],
|
|
204
|
-
"std_errors": std_errors,
|
|
205
|
-
"t_stats": t_stats,
|
|
206
|
-
"p_values": p_values,
|
|
207
|
-
"significance": significance,
|
|
208
|
-
"summary": summary_df
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
raise RuntimeError(f"Optimization process failed: {e}")
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import logging
|
|
4
|
-
from tempdisagg.utils.logging_utils import VerboseLogger
|
|
5
|
-
|
|
6
|
-
class TemporalAggregator:
|
|
7
|
-
"""
|
|
8
|
-
Aggregates a high-frequency time series into a low-frequency series
|
|
9
|
-
using one of four supported rules: sum, average, first, or last.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
def __init__(self, conversion="sum", freq_ratio=None, grain_col="Grain", index_col="Index", verbose=False):
|
|
13
|
-
"""
|
|
14
|
-
Parameters
|
|
15
|
-
----------
|
|
16
|
-
conversion : str
|
|
17
|
-
Aggregation rule: 'sum', 'average', 'first', or 'last'.
|
|
18
|
-
freq_ratio : int
|
|
19
|
-
Number of high-frequency units per low-frequency unit.
|
|
20
|
-
grain_col : str
|
|
21
|
-
Column name for high-frequency grain.
|
|
22
|
-
index_col : str
|
|
23
|
-
Column name for low-frequency index.
|
|
24
|
-
verbose : bool
|
|
25
|
-
Enable logging messages.
|
|
26
|
-
"""
|
|
27
|
-
self.conversion = conversion
|
|
28
|
-
self.freq_ratio = freq_ratio
|
|
29
|
-
self.grain_col = grain_col
|
|
30
|
-
self.index_col = index_col
|
|
31
|
-
self.verbose = verbose
|
|
32
|
-
self.logger = VerboseLogger(f"{__name__}.{id(self)}", verbose=self.verbose).get_logger()
|
|
33
|
-
|
|
34
|
-
valid = ["sum", "average", "first", "last"]
|
|
35
|
-
if self.conversion not in valid:
|
|
36
|
-
raise ValueError(f"Invalid conversion: {self.conversion}. Must be one of {valid}.")
|
|
37
|
-
|
|
38
|
-
def aggregate(self, df, value_col):
|
|
39
|
-
"""
|
|
40
|
-
Aggregate the high-frequency series to low-frequency.
|
|
41
|
-
|
|
42
|
-
Parameters
|
|
43
|
-
----------
|
|
44
|
-
df : pd.DataFrame
|
|
45
|
-
Input DataFrame containing high-frequency data.
|
|
46
|
-
value_col : str
|
|
47
|
-
Name of the value column to aggregate.
|
|
48
|
-
|
|
49
|
-
Returns
|
|
50
|
-
-------
|
|
51
|
-
pd.DataFrame
|
|
52
|
-
Low-frequency aggregated series.
|
|
53
|
-
"""
|
|
54
|
-
if not isinstance(df, pd.DataFrame):
|
|
55
|
-
raise TypeError("Input must be a pandas DataFrame.")
|
|
56
|
-
if value_col not in df.columns:
|
|
57
|
-
raise ValueError(f"Column '{value_col}' not found in input DataFrame.")
|
|
58
|
-
|
|
59
|
-
if self.index_col not in df.columns or self.grain_col not in df.columns:
|
|
60
|
-
raise ValueError("Both 'index_col' and 'grain_col' must exist in the DataFrame.")
|
|
61
|
-
|
|
62
|
-
df = df.copy()
|
|
63
|
-
df = df.sort_values([self.index_col, self.grain_col])
|
|
64
|
-
|
|
65
|
-
if self.verbose:
|
|
66
|
-
self.logger.info(f"Aggregating using rule: {self.conversion}")
|
|
67
|
-
|
|
68
|
-
if self.conversion == "sum":
|
|
69
|
-
agg_func = "sum"
|
|
70
|
-
elif self.conversion == "average":
|
|
71
|
-
agg_func = "mean"
|
|
72
|
-
elif self.conversion == "first":
|
|
73
|
-
agg_func = "first"
|
|
74
|
-
elif self.conversion == "last":
|
|
75
|
-
agg_func = "last"
|
|
76
|
-
else:
|
|
77
|
-
raise ValueError(f"Unsupported conversion: {self.conversion}")
|
|
78
|
-
|
|
79
|
-
result = df.groupby(self.index_col, as_index=False)[value_col].agg(agg_func)
|
|
80
|
-
result.rename(columns={value_col: f"{value_col}_agg"}, inplace=True)
|
|
81
|
-
|
|
82
|
-
return result
|