Coverage for /usr/lib/python3/dist-packages/scipy/stats/_relative_risk.py: 26%

57 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1import operator 

2from dataclasses import dataclass 

3import numpy as np 

4from scipy.special import ndtri 

5from ._common import ConfidenceInterval 

6 

7 

8def _validate_int(n, bound, name): 

9 msg = f'{name} must be an integer not less than {bound}, but got {n!r}' 

10 try: 

11 n = operator.index(n) 

12 except TypeError: 

13 raise TypeError(msg) from None 

14 if n < bound: 

15 raise ValueError(msg) 

16 return n 

17 

18 

19@dataclass 

20class RelativeRiskResult: 

21 """ 

22 Result of `scipy.stats.contingency.relative_risk`. 

23 

24 Attributes 

25 ---------- 

26 relative_risk : float 

27 This is:: 

28 

29 (exposed_cases/exposed_total) / (control_cases/control_total) 

30 

31 exposed_cases : int 

32 The number of "cases" (i.e. occurrence of disease or other event 

33 of interest) among the sample of "exposed" individuals. 

34 exposed_total : int 

35 The total number of "exposed" individuals in the sample. 

36 control_cases : int 

37 The number of "cases" among the sample of "control" or non-exposed 

38 individuals. 

39 control_total : int 

40 The total number of "control" individuals in the sample. 

41 

42 Methods 

43 ------- 

44 confidence_interval : 

45 Compute the confidence interval for the relative risk estimate. 

46 """ 

47 

48 relative_risk: float 

49 exposed_cases: int 

50 exposed_total: int 

51 control_cases: int 

52 control_total: int 

53 

54 def confidence_interval(self, confidence_level=0.95): 

55 """ 

56 Compute the confidence interval for the relative risk. 

57 

58 The confidence interval is computed using the Katz method 

59 (i.e. "Method C" of [1]_; see also [2]_, section 3.1.2). 

60 

61 Parameters 

62 ---------- 

63 confidence_level : float, optional 

64 The confidence level to use for the confidence interval. 

65 Default is 0.95. 

66 

67 Returns 

68 ------- 

69 ci : ConfidenceInterval instance 

70 The return value is an object with attributes ``low`` and 

71 ``high`` that hold the confidence interval. 

72 

73 References 

74 ---------- 

75 .. [1] D. Katz, J. Baptista, S. P. Azen and M. C. Pike, "Obtaining 

76 confidence intervals for the risk ratio in cohort studies", 

77 Biometrics, 34, 469-474 (1978). 

78 .. [2] Hardeo Sahai and Anwer Khurshid, Statistics in Epidemiology, 

79 CRC Press LLC, Boca Raton, FL, USA (1996). 

80 

81 

82 Examples 

83 -------- 

84 >>> from scipy.stats.contingency import relative_risk 

85 >>> result = relative_risk(exposed_cases=10, exposed_total=75, 

86 ... control_cases=12, control_total=225) 

87 >>> result.relative_risk 

88 2.5 

89 >>> result.confidence_interval() 

90 ConfidenceInterval(low=1.1261564003469628, high=5.549850800541033) 

91 """ 

92 if not 0 <= confidence_level <= 1: 

93 raise ValueError('confidence_level must be in the interval ' 

94 '[0, 1].') 

95 

96 # Handle edge cases where either exposed_cases or control_cases 

97 # is zero. We follow the convention of the R function riskratio 

98 # from the epitools library. 

99 if self.exposed_cases == 0 and self.control_cases == 0: 

100 # relative risk is nan. 

101 return ConfidenceInterval(low=np.nan, high=np.nan) 

102 elif self.exposed_cases == 0: 

103 # relative risk is 0. 

104 return ConfidenceInterval(low=0.0, high=np.nan) 

105 elif self.control_cases == 0: 

106 # relative risk is inf 

107 return ConfidenceInterval(low=np.nan, high=np.inf) 

108 

109 alpha = 1 - confidence_level 

110 z = ndtri(1 - alpha/2) 

111 rr = self.relative_risk 

112 

113 # Estimate of the variance of log(rr) is 

114 # var(log(rr)) = 1/exposed_cases - 1/exposed_total + 

115 # 1/control_cases - 1/control_total 

116 # and the standard error is the square root of that. 

117 se = np.sqrt(1/self.exposed_cases - 1/self.exposed_total + 

118 1/self.control_cases - 1/self.control_total) 

119 delta = z*se 

120 katz_lo = rr*np.exp(-delta) 

121 katz_hi = rr*np.exp(delta) 

122 return ConfidenceInterval(low=katz_lo, high=katz_hi) 

123 

124 

125def relative_risk(exposed_cases, exposed_total, control_cases, control_total): 

126 """ 

127 Compute the relative risk (also known as the risk ratio). 

128 

129 This function computes the relative risk associated with a 2x2 

130 contingency table ([1]_, section 2.2.3; [2]_, section 3.1.2). Instead 

131 of accepting a table as an argument, the individual numbers that are 

132 used to compute the relative risk are given as separate parameters. 

133 This is to avoid the ambiguity of which row or column of the contingency 

134 table corresponds to the "exposed" cases and which corresponds to the 

135 "control" cases. Unlike, say, the odds ratio, the relative risk is not 

136 invariant under an interchange of the rows or columns. 

137 

138 Parameters 

139 ---------- 

140 exposed_cases : nonnegative int 

141 The number of "cases" (i.e. occurrence of disease or other event 

142 of interest) among the sample of "exposed" individuals. 

143 exposed_total : positive int 

144 The total number of "exposed" individuals in the sample. 

145 control_cases : nonnegative int 

146 The number of "cases" among the sample of "control" or non-exposed 

147 individuals. 

148 control_total : positive int 

149 The total number of "control" individuals in the sample. 

150 

151 Returns 

152 ------- 

153 result : instance of `~scipy.stats._result_classes.RelativeRiskResult` 

154 The object has the float attribute ``relative_risk``, which is:: 

155 

156 rr = (exposed_cases/exposed_total) / (control_cases/control_total) 

157 

158 The object also has the method ``confidence_interval`` to compute 

159 the confidence interval of the relative risk for a given confidence 

160 level. 

161 

162 See Also 

163 -------- 

164 odds_ratio 

165 

166 Notes 

167 ----- 

168 The R package epitools has the function `riskratio`, which accepts 

169 a table with the following layout:: 

170 

171 disease=0 disease=1 

172 exposed=0 (ref) n00 n01 

173 exposed=1 n10 n11 

174 

175 With a 2x2 table in the above format, the estimate of the CI is 

176 computed by `riskratio` when the argument method="wald" is given, 

177 or with the function `riskratio.wald`. 

178 

179 For example, in a test of the incidence of lung cancer among a 

180 sample of smokers and nonsmokers, the "exposed" category would 

181 correspond to "is a smoker" and the "disease" category would 

182 correspond to "has or had lung cancer". 

183 

184 To pass the same data to ``relative_risk``, use:: 

185 

186 relative_risk(n11, n10 + n11, n01, n00 + n01) 

187 

188 .. versionadded:: 1.7.0 

189 

190 References 

191 ---------- 

192 .. [1] Alan Agresti, An Introduction to Categorical Data Analysis 

193 (second edition), Wiley, Hoboken, NJ, USA (2007). 

194 .. [2] Hardeo Sahai and Anwer Khurshid, Statistics in Epidemiology, 

195 CRC Press LLC, Boca Raton, FL, USA (1996). 

196 

197 Examples 

198 -------- 

199 >>> from scipy.stats.contingency import relative_risk 

200 

201 This example is from Example 3.1 of [2]_. The results of a heart 

202 disease study are summarized in the following table:: 

203 

204 High CAT Low CAT Total 

205 -------- ------- ----- 

206 CHD 27 44 71 

207 No CHD 95 443 538 

208 

209 Total 122 487 609 

210 

211 CHD is coronary heart disease, and CAT refers to the level of 

212 circulating catecholamine. CAT is the "exposure" variable, and 

213 high CAT is the "exposed" category. So the data from the table 

214 to be passed to ``relative_risk`` is:: 

215 

216 exposed_cases = 27 

217 exposed_total = 122 

218 control_cases = 44 

219 control_total = 487 

220 

221 >>> result = relative_risk(27, 122, 44, 487) 

222 >>> result.relative_risk 

223 2.4495156482861398 

224 

225 Find the confidence interval for the relative risk. 

226 

227 >>> result.confidence_interval(confidence_level=0.95) 

228 ConfidenceInterval(low=1.5836990926700116, high=3.7886786315466354) 

229 

230 The interval does not contain 1, so the data supports the statement 

231 that high CAT is associated with greater risk of CHD. 

232 """ 

233 # Relative risk is a trivial calculation. The nontrivial part is in the 

234 # `confidence_interval` method of the RelativeRiskResult class. 

235 

236 exposed_cases = _validate_int(exposed_cases, 0, "exposed_cases") 

237 exposed_total = _validate_int(exposed_total, 1, "exposed_total") 

238 control_cases = _validate_int(control_cases, 0, "control_cases") 

239 control_total = _validate_int(control_total, 1, "control_total") 

240 

241 if exposed_cases > exposed_total: 

242 raise ValueError('exposed_cases must not exceed exposed_total.') 

243 if control_cases > control_total: 

244 raise ValueError('control_cases must not exceed control_total.') 

245 

246 if exposed_cases == 0 and control_cases == 0: 

247 # relative risk is 0/0. 

248 rr = np.nan 

249 elif exposed_cases == 0: 

250 # relative risk is 0/nonzero 

251 rr = 0.0 

252 elif control_cases == 0: 

253 # relative risk is nonzero/0. 

254 rr = np.inf 

255 else: 

256 p1 = exposed_cases / exposed_total 

257 p2 = control_cases / control_total 

258 rr = p1 / p2 

259 return RelativeRiskResult(relative_risk=rr, 

260 exposed_cases=exposed_cases, 

261 exposed_total=exposed_total, 

262 control_cases=control_cases, 

263 control_total=control_total)