Coverage for src / ts_stat_tests / heteroscedasticity / tests.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-01 09:48 +0000

1# ============================================================================ # 

2# # 

3# Title: Heteroscedasticity Tests # 

4# Purpose: Implementation of heteroscedasticity test wrappers. # 

5# # 

6# ============================================================================ # 

7 

8 

9# ---------------------------------------------------------------------------- # 

10# # 

11# Overview #### 

12# # 

13# ---------------------------------------------------------------------------- # 

14 

15 

16# ---------------------------------------------------------------------------- # 

17# Description #### 

18# ---------------------------------------------------------------------------- # 

19 

20 

21""" 

22!!! note "Summary" 

23 This module provides wrapper functions for various heteroscedasticity tests including: 

24 - ARCH Test 

25 - Breusch-Pagan Test 

26 - Goldfeld-Quandt Test 

27 - White's Test 

28""" 

29 

30 

31# ---------------------------------------------------------------------------- # 

32# # 

33# Setup #### 

34# # 

35# ---------------------------------------------------------------------------- # 

36 

37 

38# ---------------------------------------------------------------------------- # 

39# Imports #### 

40# ---------------------------------------------------------------------------- # 

41 

42 

43# ## Python StdLib Imports ---- 

44from typing import Any, Callable, Union 

45 

46# ## Python Third Party Imports ---- 

47from numpy.typing import ArrayLike 

48from statsmodels.regression.linear_model import ( 

49 RegressionResults, 

50 RegressionResultsWrapper, 

51) 

52from typeguard import typechecked 

53 

54# ## Local First Party Imports ---- 

55from ts_stat_tests.heteroscedasticity.algorithms import arch, bpl, gq, wlm 

56from ts_stat_tests.utils.errors import generate_error_message 

57 

58 

59# ---------------------------------------------------------------------------- # 

60# Exports #### 

61# ---------------------------------------------------------------------------- # 

62 

63 

64__all__: list[str] = ["heteroscedasticity", "is_heteroscedastic"] 

65 

66 

67# ---------------------------------------------------------------------------- # 

68# # 

69# Tests #### 

70# # 

71# ---------------------------------------------------------------------------- # 

72 

73 

74@typechecked 

75def heteroscedasticity( 

76 res: Union[RegressionResults, RegressionResultsWrapper], 

77 algorithm: str = "bp", 

78 **kwargs: Union[float, int, str, bool, ArrayLike, None], 

79) -> tuple[Any, ...]: 

80 """ 

81 !!! note "Summary" 

82 Perform a heteroscedasticity test on a fitted regression model. 

83 

84 ???+ abstract "Details" 

85 This function is a convenience wrapper around four underlying algorithms:<br> 

86 - [`arch()`][ts_stat_tests.heteroscedasticity.algorithms.arch]<br> 

87 - [`bp()`][ts_stat_tests.heteroscedasticity.algorithms.bpl]<br> 

88 - [`gq()`][ts_stat_tests.heteroscedasticity.algorithms.gq]<br> 

89 - [`white()`][ts_stat_tests.heteroscedasticity.algorithms.wlm] 

90 

91 Params: 

92 res (Union[RegressionResults, RegressionResultsWrapper]): 

93 The fitted regression model to be checked. 

94 algorithm (str): 

95 Which heteroscedasticity algorithm to use.<br> 

96 - `arch()`: `["arch", "engle"]`<br> 

97 - `bp()`: `["bp", "breusch-pagan", "breusch-pagan-lagrange-multiplier"]`<br> 

98 - `gq()`: `["gq", "goldfeld-quandt"]`<br> 

99 - `white()`: `["white"]`<br> 

100 Default: `"bp"` 

101 kwargs (Union[float, int, str, bool, ArrayLike, None]): 

102 Additional keyword arguments passed to the underlying test function. 

103 

104 Raises: 

105 (ValueError): 

106 When the given value for `algorithm` is not valid. 

107 

108 Returns: 

109 (Union[tuple[float, float, float, float], tuple[float, float, str], ResultsStore]): 

110 The results of the heteroscedasticity test. The return type depends on the chosen algorithm and `kwargs`. 

111 

112 !!! success "Credit" 

113 Calculations are performed by `statsmodels`. 

114 

115 ???+ example "Examples" 

116 

117 ```pycon {.py .python linenums="1" title="Setup"} 

118 >>> import statsmodels.api as sm 

119 >>> from ts_stat_tests.heteroscedasticity.tests import heteroscedasticity 

120 >>> from ts_stat_tests.utils.data import data_line, data_random 

121 >>> X = sm.add_constant(data_line) 

122 >>> y = 2 * data_line + data_random 

123 >>> res = sm.OLS(y, X).fit() 

124 

125 ``` 

126 

127 ```pycon {.py .python linenums="1" title="Example 1: Breusch-Pagan test"} 

128 >>> result = heteroscedasticity(res, algorithm="bp") 

129 >>> print(f"p-value: {result[1]:.4f}") 

130 p-value: 0.2461 

131 

132 ``` 

133 

134 ```pycon {.py .python linenums="1" title="Example 2: ARCH test"} 

135 >>> lm, lmp, f, fp = heteroscedasticity(res, algorithm="arch") 

136 >>> print(f"ARCH p-value: {lmp:.4f}") 

137 ARCH p-value: 0.9124 

138 

139 ``` 

140 """ 

141 options: dict[str, tuple[str, ...]] = { 

142 "arch": ("arch", "engle"), 

143 "bp": ("bp", "breusch-pagan", "breusch-pagan-lagrange-multiplier"), 

144 "gq": ("gq", "goldfeld-quandt"), 

145 "white": ("white",), 

146 } 

147 

148 # Internal helper to handle kwargs casting for ty 

149 def _call( 

150 func: Callable[..., Any], 

151 **args: Any, 

152 ) -> tuple[Any, ...]: 

153 """ 

154 !!! note "Summary" 

155 Internal helper to handle keyword arguments types. 

156 

157 Params: 

158 func (Callable[..., Any]): 

159 The function to call. 

160 args (Any): 

161 The keyword arguments to pass. 

162 

163 Returns: 

164 (tuple[Any, ...]): 

165 The function output. 

166 

167 ???+ example "Examples" 

168 This is an internal function and is not intended to be called directly. 

169 """ 

170 return func(**args) 

171 

172 if algorithm in options["arch"]: 

173 return _call(arch, resid=res.resid, **kwargs) 

174 

175 if algorithm in options["bp"]: 

176 return _call(bpl, resid=res.resid, exog_het=res.model.exog, **kwargs) 

177 

178 if algorithm in options["gq"]: 

179 return _call(gq, y=res.model.endog, x=res.model.exog, **kwargs) 

180 

181 if algorithm in options["white"]: 

182 return _call(wlm, resid=res.resid, exog_het=res.model.exog, **kwargs) 

183 

184 raise ValueError( 

185 generate_error_message( 

186 parameter_name="algorithm", 

187 value_parsed=algorithm, 

188 options=options, 

189 ) 

190 ) 

191 

192 

193@typechecked 

194def is_heteroscedastic( 

195 res: Union[RegressionResults, RegressionResultsWrapper], 

196 algorithm: str = "bp", 

197 alpha: float = 0.05, 

198 **kwargs: Union[float, int, str, bool, ArrayLike, None], 

199) -> dict[str, Union[str, float, bool, None]]: 

200 """ 

201 !!! note "Summary" 

202 Test whether a given model's residuals exhibit `heteroscedasticity` or not. 

203 

204 ???+ abstract "Details" 

205 This function checks the results of a heteroscedasticity test against a significance level `alpha`. The null hypothesis ($H_0$) for all supported tests is homoscedasticity (constant variance). If the p-value is less than `alpha`, the null hypothesis is rejected in favor of heteroscedasticity. 

206 

207 Params: 

208 res (Union[RegressionResults, RegressionResultsWrapper]): 

209 The fitted regression model to be checked. 

210 algorithm (str): 

211 Which heteroscedasticity algorithm to use. See [`heteroscedasticity()`][ts_stat_tests.heteroscedasticity.tests.heteroscedasticity] for options. 

212 Default: `"bp"` 

213 alpha (float): 

214 The significance level for the test. 

215 Default: `0.05` 

216 kwargs (Union[float, int, str, bool, ArrayLike, None]): 

217 Additional keyword arguments passed to the underlying test function. 

218 

219 Returns: 

220 (dict[str, Union[str, float, bool, None]]): 

221 A dictionary containing: 

222 - `"result"` (bool): Indicator if the residuals are heteroscedastic (i.e., p-value < alpha). 

223 - `"statistic"` (float): The test statistic. 

224 - `"pvalue"` (float): The p-value of the test. 

225 - `"alpha"` (float): The significance level used. 

226 - `"algorithm"` (str): The algorithm used. 

227 

228 ???+ example "Examples" 

229 

230 ```pycon {.py .python linenums="1" title="Setup"} 

231 >>> import statsmodels.api as sm 

232 >>> from ts_stat_tests.heteroscedasticity.tests import is_heteroscedastic 

233 >>> from ts_stat_tests.utils.data import data_line, data_random 

234 >>> X = sm.add_constant(data_line) 

235 >>> y = 2 * data_line + data_random 

236 >>> res = sm.OLS(y, X).fit() 

237 

238 ``` 

239 

240 ```pycon {.py .python linenums="1" title="Example 1: Check heteroscedasticity with Breusch-Pagan"} 

241 >>> res_check = is_heteroscedastic(res, algorithm="bp") 

242 >>> print(res_check["result"]) 

243 False 

244 

245 ``` 

246 """ 

247 raw_res = heteroscedasticity(res=res, algorithm=algorithm, **kwargs) 

248 

249 # All heteroscedasticity algorithms return a tuple 

250 # (lm, lmpval, fval, fpval) or (fval, pval, ...) 

251 stat = float(raw_res[0]) 

252 pvalue = float(raw_res[1]) 

253 

254 return { 

255 "algorithm": algorithm, 

256 "statistic": float(stat), 

257 "pvalue": float(pvalue), 

258 "alpha": alpha, 

259 "result": bool(pvalue < alpha), 

260 }