dycw-utilities 0.109.20__py3-none-any.whl → 0.109.22__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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.109.20
3
+ Version: 0.109.22
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
- Requires-Dist: hypothesis<6.132,>=6.131.7; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.9; extra == 'test'
10
10
  Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
11
11
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
12
12
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -80,7 +80,7 @@ Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
- Requires-Dist: hypothesis<6.132,>=6.131.7; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.9; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
85
  Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=YYKCLFSAtdNtdzYNktGDJnj2EdGbCnq98VjsZyG6lqI,61
1
+ utilities/__init__.py,sha256=8f1PKZxzDeABQ7A4zZsmHKuHRkJKy1jboB_m42AXKxw,61
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
4
  utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
@@ -46,7 +46,7 @@ utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
46
46
  utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
47
47
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
48
48
  utilities/platform.py,sha256=NU7ycTvAXAG-fdYmDXaM1m4EOml2cGiaYwaUzfzSqyU,1767
49
- utilities/polars.py,sha256=UFrD7smyBROlIBaLD6tUFKGAPtwLfyHPHp6dJI9i-kQ,54961
49
+ utilities/polars.py,sha256=pFUEkCzHh01M84fm4upFc4UKrUFMKz6WeWYvAlFLAPw,58009
50
50
  utilities/polars_ols.py,sha256=efhXf0gjrHUpQrvS6a7g8yJQJWf_ATKtJnqqF2inCOU,5680
51
51
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
52
52
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -68,6 +68,7 @@ utilities/slack_sdk.py,sha256=SeDNMh24IPiEBWoGMdgvrflUaFa9TGlTS03H9-NKaQw,4132
68
68
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
69
69
  utilities/sqlalchemy.py,sha256=GWzp54TP3F2mGhxPTn0c56KxxDeN9VKLMagcRSELhf4,35453
70
70
  utilities/sqlalchemy_polars.py,sha256=oGyMX5gSxuLI3N8mtz_-ml3UdWKcZuj6aFRW6ifI0Kc,15617
71
+ utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
71
72
  utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
72
73
  utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
73
74
  utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
@@ -86,7 +87,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
86
87
  utilities/whenever.py,sha256=TjoTAJ1R27-rKXiXzdE4GzPidmYqm0W58XydDXp-QZM,17786
87
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
88
89
  utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
89
- dycw_utilities-0.109.20.dist-info/METADATA,sha256=m0Lisp8SF8Avj84y8hIcnq3yMx52pFILsF2ycGNZdTM,13005
90
- dycw_utilities-0.109.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
- dycw_utilities-0.109.20.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
92
- dycw_utilities-0.109.20.dist-info/RECORD,,
90
+ dycw_utilities-0.109.22.dist-info/METADATA,sha256=Q17jtIGlAaCIIt5pD-cHWOB9Uvq8i9yRQtNP0q8NmLI,13005
91
+ dycw_utilities-0.109.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.109.22.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.109.22.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.109.20"
3
+ __version__ = "0.109.22"
utilities/polars.py CHANGED
@@ -121,6 +121,7 @@ if TYPE_CHECKING:
121
121
  )
122
122
 
123
123
  from utilities.numpy import NDArrayB, NDArrayF
124
+ from utilities.statsmodels import ACFMissing
124
125
  from utilities.types import (
125
126
  Dataclass,
126
127
  MaybeIterable,
@@ -139,6 +140,116 @@ DatetimeUSEastern = Datetime(time_zone="US/Eastern")
139
140
  DatetimeUTC = Datetime(time_zone="UTC")
140
141
  _FINITE_EWM_MIN_WEIGHT = 0.9999
141
142
 
143
+ ##
144
+
145
+
146
+ def ac_halflife(
147
+ series: Series,
148
+ /,
149
+ *,
150
+ adjusted: bool = False,
151
+ fft: bool = True,
152
+ bartlett_confint: bool = True,
153
+ missing: ACFMissing = "none",
154
+ step: float = 0.01,
155
+ ) -> float:
156
+ """Compute the autocorrelation halflife."""
157
+ import utilities.statsmodels
158
+
159
+ array = series.to_numpy()
160
+ return utilities.statsmodels.ac_halflife(
161
+ array,
162
+ adjusted=adjusted,
163
+ fft=fft,
164
+ bartlett_confint=bartlett_confint,
165
+ missing=missing,
166
+ step=step,
167
+ )
168
+
169
+
170
+ ##
171
+
172
+
173
+ def acf(
174
+ series: Series,
175
+ /,
176
+ *,
177
+ adjusted: bool = False,
178
+ nlags: int | None = None,
179
+ qstat: bool = False,
180
+ fft: bool = True,
181
+ alpha: float | None = None,
182
+ bartlett_confint: bool = True,
183
+ missing: ACFMissing = "none",
184
+ ) -> DataFrame:
185
+ """Compute the autocorrelations of a series."""
186
+ from numpy import ndarray
187
+
188
+ import utilities.statsmodels
189
+
190
+ array = series.to_numpy()
191
+ result = utilities.statsmodels.acf(
192
+ array,
193
+ adjusted=adjusted,
194
+ nlags=nlags,
195
+ qstat=qstat,
196
+ fft=fft,
197
+ alpha=alpha,
198
+ bartlett_confint=bartlett_confint,
199
+ missing=missing,
200
+ )
201
+ match result:
202
+ case ndarray() as acfs:
203
+ return _acf_process_acfs(acfs)
204
+ case ndarray() as acfs, ndarray() as confints:
205
+ df_acfs = _acf_process_acfs(acfs)
206
+ df_confints = _acf_process_confints(confints)
207
+ return df_acfs.join(df_confints, on=["lag"])
208
+ case ndarray() as acfs, ndarray() as qstats, ndarray() as pvalues:
209
+ df_acfs = _acf_process_acfs(acfs)
210
+ df_qstats_pvalues = _acf_process_qstats_pvalues(qstats, pvalues)
211
+ return df_acfs.join(df_qstats_pvalues, on=["lag"], how="left")
212
+ case (
213
+ ndarray() as acfs,
214
+ ndarray() as confints,
215
+ ndarray() as qstats,
216
+ ndarray() as pvalues,
217
+ ):
218
+ df_acfs = _acf_process_acfs(acfs)
219
+ df_confints = _acf_process_confints(confints)
220
+ df_qstats_pvalues = _acf_process_qstats_pvalues(qstats, pvalues)
221
+ return join(df_acfs, df_confints, df_qstats_pvalues, on=["lag"], how="left")
222
+ case _ as never:
223
+ assert_never(never)
224
+
225
+
226
+ def _acf_process_acfs(acfs: NDArrayF, /) -> DataFrame:
227
+ return (
228
+ Series(name="autocorrelation", values=acfs, dtype=Float64)
229
+ .to_frame()
230
+ .with_row_index(name="lag")
231
+ )
232
+
233
+
234
+ def _acf_process_confints(confints: NDArrayF, /) -> DataFrame:
235
+ return DataFrame(
236
+ data=confints, schema={"lower": Float64, "upper": Float64}
237
+ ).with_row_index(name="lag")
238
+
239
+
240
+ def _acf_process_qstats_pvalues(qstats: NDArrayF, pvalues: NDArrayF, /) -> DataFrame:
241
+ from numpy import hstack
242
+
243
+ data = hstack([qstats.reshape(-1, 1), pvalues.reshape(-1, 1)])
244
+ return DataFrame(
245
+ data=data, schema={"qstat": Float64, "pvalue": Float64}
246
+ ).with_row_index(name="lag", offset=1)
247
+
248
+
249
+ ##
250
+
251
+
252
+ # def acf_halflife(series: Series,/)
142
253
 
143
254
  ##
144
255
 
@@ -1840,6 +1951,7 @@ __all__ = [
1840
1951
  "SetFirstRowAsColumnsError",
1841
1952
  "StructFromDataClassError",
1842
1953
  "YieldStructSeriesElementsError",
1954
+ "acf",
1843
1955
  "adjust_frequencies",
1844
1956
  "append_dataclass",
1845
1957
  "are_frames_equal",
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Literal, cast, overload
4
+
5
+ import numpy as np
6
+ import statsmodels.tsa.stattools
7
+ from numpy import arange, argmax, interp, nan
8
+
9
+ from utilities.numpy import shift
10
+
11
+ if TYPE_CHECKING:
12
+ from utilities.numpy import NDArrayF
13
+
14
+
15
+ def ac_halflife(
16
+ array: NDArrayF,
17
+ /,
18
+ *,
19
+ adjusted: bool = False,
20
+ fft: bool = True,
21
+ bartlett_confint: bool = True,
22
+ missing: ACFMissing = "none",
23
+ step: float = 0.01,
24
+ ) -> float:
25
+ """Compute the autocorrelation halflife."""
26
+ (n,) = array.shape
27
+ acfs = acf(
28
+ array,
29
+ adjusted=adjusted,
30
+ nlags=n,
31
+ fft=fft,
32
+ bartlett_confint=bartlett_confint,
33
+ missing=missing,
34
+ )
35
+ lags = arange(0, n, step=step)
36
+ interp_acfs = interp(lags, arange(n), acfs)
37
+ is_half = (shift(interp_acfs) > 0.5) & (interp_acfs <= 0.5)
38
+ return lags[argmax(is_half)].item() if np.any(is_half) else nan
39
+
40
+
41
+ ##
42
+
43
+
44
+ type ACFMissing = Literal["none", "raise", "conservative", "drop"]
45
+
46
+
47
+ @overload
48
+ def acf(
49
+ array: NDArrayF,
50
+ /,
51
+ *,
52
+ adjusted: bool = False,
53
+ nlags: int | None = None,
54
+ qstat: Literal[False] = False,
55
+ fft: bool = True,
56
+ alpha: None = None,
57
+ bartlett_confint: bool = True,
58
+ missing: ACFMissing = "none",
59
+ ) -> NDArrayF: ...
60
+ @overload
61
+ def acf(
62
+ array: NDArrayF,
63
+ /,
64
+ *,
65
+ adjusted: bool = False,
66
+ nlags: int | None = None,
67
+ qstat: Literal[False] = False,
68
+ fft: bool = True,
69
+ alpha: float,
70
+ bartlett_confint: bool = True,
71
+ missing: ACFMissing = "none",
72
+ ) -> tuple[NDArrayF, NDArrayF]: ...
73
+ @overload
74
+ def acf(
75
+ array: NDArrayF,
76
+ /,
77
+ *,
78
+ adjusted: bool = False,
79
+ nlags: int | None = None,
80
+ qstat: Literal[True],
81
+ fft: bool = True,
82
+ alpha: float,
83
+ bartlett_confint: bool = True,
84
+ missing: ACFMissing = "none",
85
+ ) -> tuple[NDArrayF, NDArrayF, NDArrayF, NDArrayF]: ...
86
+ @overload
87
+ def acf(
88
+ array: NDArrayF,
89
+ /,
90
+ *,
91
+ adjusted: bool = False,
92
+ nlags: int | None = None,
93
+ qstat: Literal[True],
94
+ fft: bool = True,
95
+ alpha: None = None,
96
+ bartlett_confint: bool = True,
97
+ missing: ACFMissing = "none",
98
+ ) -> tuple[NDArrayF, NDArrayF, NDArrayF]: ...
99
+ @overload
100
+ def acf(
101
+ array: NDArrayF,
102
+ /,
103
+ *,
104
+ adjusted: bool = False,
105
+ nlags: int | None = None,
106
+ qstat: bool = False,
107
+ fft: bool = True,
108
+ alpha: float | None = None,
109
+ bartlett_confint: bool = True,
110
+ missing: ACFMissing = "none",
111
+ ) -> (
112
+ NDArrayF
113
+ | tuple[NDArrayF, NDArrayF]
114
+ | tuple[NDArrayF, NDArrayF, NDArrayF]
115
+ | tuple[NDArrayF, NDArrayF, NDArrayF, NDArrayF]
116
+ ): ...
117
+ def acf(
118
+ array: NDArrayF,
119
+ /,
120
+ *,
121
+ adjusted: bool = False,
122
+ nlags: int | None = None,
123
+ qstat: bool = False,
124
+ fft: bool = True,
125
+ alpha: float | None = None,
126
+ bartlett_confint: bool = True,
127
+ missing: ACFMissing = "none",
128
+ ) -> (
129
+ NDArrayF
130
+ | tuple[NDArrayF, NDArrayF]
131
+ | tuple[NDArrayF, NDArrayF, NDArrayF]
132
+ | tuple[NDArrayF, NDArrayF, NDArrayF, NDArrayF]
133
+ ):
134
+ """Typed version of `acf`."""
135
+ return cast(
136
+ "Any",
137
+ statsmodels.tsa.stattools.acf(
138
+ array,
139
+ adjusted=adjusted,
140
+ nlags=nlags,
141
+ qstat=qstat,
142
+ fft=fft,
143
+ alpha=alpha,
144
+ bartlett_confint=bartlett_confint,
145
+ missing=missing,
146
+ ),
147
+ )
148
+
149
+
150
+ __all__ = ["ACFMissing", "ac_halflife", "acf"]