Coverage for src / ts_stat_tests / linearity / tests.py: 100%
34 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
1# ============================================================================ #
2# #
3# Title: Linearity Tests #
4# Purpose: Convenience functions for linearity algorithms. #
5# #
6# ============================================================================ #
9# ---------------------------------------------------------------------------- #
10# #
11# Overview ####
12# #
13# ---------------------------------------------------------------------------- #
16# ---------------------------------------------------------------------------- #
17# Description ####
18# ---------------------------------------------------------------------------- #
21"""
22!!! note "Summary"
23 This module contains convenience functions and tests for linearity measures, allowing for easy access to different linearity algorithms.
24"""
27# ---------------------------------------------------------------------------- #
28# #
29# Setup ####
30# #
31# ---------------------------------------------------------------------------- #
34# ---------------------------------------------------------------------------- #
35# Imports ####
36# ---------------------------------------------------------------------------- #
39# ## Python StdLib Imports ----
40from typing import Callable, Union
42# ## Python Third Party Imports ----
43import numpy as np
44from numpy.typing import ArrayLike
45from statsmodels.regression.linear_model import (
46 RegressionResults,
47 RegressionResultsWrapper,
48)
49from statsmodels.stats.contrast import ContrastResults
50from typeguard import typechecked
52# ## Local First Party Imports ----
53from ts_stat_tests.linearity.algorithms import (
54 hc as _hc,
55 lm as _lm,
56 rb as _rb,
57 rr as _rr,
58)
59from ts_stat_tests.utils.errors import generate_error_message
62# ---------------------------------------------------------------------------- #
63# Exports ####
64# ---------------------------------------------------------------------------- #
67__all__: list[str] = ["linearity", "is_linear"]
70# ---------------------------------------------------------------------------- #
71# #
72# Tests ####
73# #
74# ---------------------------------------------------------------------------- #
77@typechecked
78def linearity(
79 res: Union[RegressionResults, RegressionResultsWrapper],
80 algorithm: str = "rr",
81 **kwargs: Union[float, int, str, bool, ArrayLike, None],
82) -> Union[tuple[float, ...], ContrastResults]:
83 """
84 !!! note "Summary"
85 Perform a linearity test on a fitted regression model.
87 ???+ abstract "Details"
88 This function is a convenience wrapper around four underlying algorithms:<br>
89 - [`hc()`][ts_stat_tests.linearity.algorithms.hc]<br>
90 - [`lm()`][ts_stat_tests.linearity.algorithms.lm]<br>
91 - [`rb()`][ts_stat_tests.linearity.algorithms.rb]<br>
92 - [`rr()`][ts_stat_tests.linearity.algorithms.rr]
94 Params:
95 res (Union[RegressionResults, RegressionResultsWrapper]):
96 The fitted regression model to be checked.
97 algorithm (str):
98 Which linearity algorithm to use.<br>
99 - `hc()`: `["hc", "harvey", "harvey-collier"]`<br>
100 - `lm()`: `["lm", "lagrange", "lagrange-multiplier"]`<br>
101 - `rb()`: `["rb", "rainbow"]`<br>
102 - `rr()`: `["rr", "reset", "ramsey-reset"]`<br>
103 Default: `"rr"`
104 kwargs (Union[float, int, str, bool, ArrayLike, None]):
105 Additional keyword arguments passed to the underlying test function.
107 Raises:
108 (ValueError):
109 When the given value for `algorithm` is not valid.
111 Returns:
112 (Union[tuple[float, float], tuple[float, float, float, float], ContrastResults]):
113 - For `"hc"`: `(statistic, pvalue)`
114 - For `"lm"`: `(lm_stat, lm_pval, f_stat, f_pval)`
115 - For `"rb"`: `(statistic, pvalue)`
116 - For `"rr"`: `ContrastResults` object
118 !!! success "Credit"
119 Calculations are performed by `statsmodels`.
121 ???+ example "Examples"
123 ```pycon {.py .python linenums="1" title="Setup"}
124 >>> import statsmodels.api as sm
125 >>> from ts_stat_tests.linearity.tests import linearity
126 >>> from ts_stat_tests.utils.data import data_line, data_random
127 >>> x = sm.add_constant(data_line)
128 >>> y = 3 + 2 * data_line + data_random
129 >>> res = sm.OLS(y, x).fit()
131 ```
133 ```pycon {.py .python linenums="1" title="Example 1: Ramsey RESET test"}
134 >>> result = linearity(res, algorithm="rr")
135 >>> print(f"p-value: {result.pvalue:.4f}")
136 p-value: 0.9912
138 ```
140 ```pycon {.py .python linenums="1" title="Example 2: Rainbow test"}
141 >>> stat, pval = linearity(res, algorithm="rb")
142 >>> print(f"Rainbow p-value: {pval:.4f}")
143 Rainbow p-value: 0.5391
145 ```
146 """
147 options: dict[str, tuple[str, ...]] = {
148 "hc": ("hc", "harvey", "harvey-collier"),
149 "lm": ("lm", "lagrange", "lagrange-multiplier"),
150 "rb": ("rb", "rainbow"),
151 "rr": ("rr", "reset", "ramsey-reset"),
152 }
154 # Internal helper to handle kwargs casting for ty
155 def _call(
156 func: Callable[..., Union[tuple[float, ...], ContrastResults]],
157 **args: Union[
158 float,
159 int,
160 str,
161 bool,
162 ArrayLike,
163 None,
164 RegressionResults,
165 RegressionResultsWrapper,
166 ],
167 ) -> Union[tuple[float, ...], ContrastResults]:
168 """
169 !!! note "Summary"
170 Internal helper to handle keyword arguments types.
172 Params:
173 func (Callable[..., Union[tuple[float, ...], ContrastResults]]):
174 The function to call.
175 args (Union[float, int, str, bool, ArrayLike, None, RegressionResults, RegressionResultsWrapper]):
176 The arguments to pass.
178 Returns:
179 (Union[tuple[float, ...], ContrastResults]):
180 The result of the function call.
182 ???+ example "Examples"
183 ```python
184 # Internal use only
185 ```
186 """
187 return func(**args)
189 if algorithm in options["hc"]:
190 return _call(_hc, res=res, **kwargs)
192 if algorithm in options["lm"]:
193 return _call(_lm, resid=res.resid, exog=res.model.exog, **kwargs)
195 if algorithm in options["rb"]:
196 return _call(_rb, res=res, **kwargs)
198 if algorithm in options["rr"]:
199 return _call(_rr, res=res, **kwargs)
201 raise ValueError(
202 generate_error_message(
203 parameter_name="algorithm",
204 value_parsed=algorithm,
205 options=options,
206 )
207 )
210@typechecked
211def is_linear(
212 res: Union[RegressionResults, RegressionResultsWrapper],
213 algorithm: str = "rr",
214 alpha: float = 0.05,
215 **kwargs: Union[float, int, str, bool, ArrayLike, None],
216) -> dict[str, Union[str, float, bool, None]]:
217 """
218 !!! note "Summary"
219 Test whether the relationship in a fitted model is `linear` or not.
221 ???+ abstract "Details"
222 This function returns a dictionary containing the test results and a boolean indicating whether the null hypothesis of linearity is rejected at the given significance level.
224 Params:
225 res (Union[RegressionResults, RegressionResultsWrapper]):
226 The fitted regression model to be checked.
227 algorithm (str):
228 Which linearity algorithm to use. See [`linearity()`][ts_stat_tests.linearity.tests.linearity] for options.
229 Default: `"rr"`
230 alpha (float):
231 The significance level for the test.
232 Default: `0.05`
233 kwargs (Union[float, int, str, bool, ArrayLike, None]):
234 Additional keyword arguments passed to the underlying test function.
236 Returns:
237 (dict[str, Union[str, float, bool, None]]):
238 A dictionary with the following keys:
239 - `"algorithm"` (str): The name of the algorithm used.
240 - `"statistic"` (float): The test statistic.
241 - `"pvalue"` (float): The p-value of the test.
242 - `"alpha"` (float): The significance level used.
243 - `"result"` (bool): Whether the relationship is linear (i.e., p-value > alpha).
245 ???+ example "Examples"
247 ```pycon {.py .python linenums="1" title="Setup"}
248 >>> import statsmodels.api as sm
249 >>> from ts_stat_tests.linearity.tests import is_linear
250 >>> from ts_stat_tests.utils.data import data_line, data_random
251 >>> x = sm.add_constant(data_line)
252 >>> y = 3 + 2 * data_line + data_random
253 >>> res = sm.OLS(y, x).fit()
255 ```
257 ```pycon {.py .python linenums="1" title="Example 1: Check linearity with RR"}
258 >>> result = is_linear(res, algorithm="rr")
259 >>> print(result["result"])
260 True
262 ```
263 """
264 options: dict[str, tuple[str, ...]] = {
265 "hc": ("hc", "harvey", "harvey-collier"),
266 "lm": ("lm", "lagrange", "lagrange-multiplier"),
267 "rb": ("rb", "rainbow"),
268 "rr": ("rr", "reset", "ramsey-reset"),
269 }
271 raw_res = linearity(res=res, algorithm=algorithm, **kwargs)
273 if algorithm in options["hc"] or algorithm in options["rb"]:
274 # raw_res is tuple[float, float]
275 stat, pvalue = raw_res[0], raw_res[1] # type: ignore
276 elif algorithm in options["lm"]:
277 # raw_res is tuple[float, float, float, float]
278 stat, pvalue = raw_res[0], raw_res[1] # type: ignore
279 else:
280 # raw_res is ContrastResults (Ramsey RESET)
281 stat = float(getattr(raw_res, "statistic", getattr(raw_res, "fvalue", np.nan)))
282 pvalue = float(getattr(raw_res, "pvalue", np.nan))
284 return {
285 "algorithm": algorithm,
286 "statistic": float(stat),
287 "pvalue": float(pvalue),
288 "alpha": alpha,
289 "result": bool(pvalue > alpha),
290 }