rstatistics 0.1.0__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.
rstat/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """rstat: R's probability and statistics functions in Python.
2
+
3
+ The names are R's, with `_` for `.` (`t.test` is `t_test`); arguments are R's
4
+ too (`lower.tail` is `lower_tail`). Printed results look as in R. The package
5
+ is written in rupy's typed subset of Python, so rupy compiles it to Rust or
6
+ WebAssembly as well."""
7
+
8
+ from rstat.distributions import (
9
+ dbeta, dbinom, dcauchy, dchisq, dexp, df, dgamma, dgeom, dhyper, dlnorm, dlogis, dnbinom, dnorm, dpois, dt,
10
+ dunif, dweibull, pbeta, pbinom, pcauchy, pchisq, pexp, pf, pgamma, pgeom, phyper, plnorm, plogis, pnbinom,
11
+ pnorm, ppois, pt, punif, pweibull, qbeta, qbinom, qcauchy, qchisq, qexp, qf, qgamma, qgeom, qlnorm, qlogis,
12
+ qnorm, qpois, qt, qunif, qweibull,
13
+ )
14
+ from rstat.describe import IQR, Named, Summary, cor, cov, fivenum, mad, mean, median, quantile, rank, scale, sd, summary, var, weighted_mean
15
+ from rstat.htest import HTest, binom_test, chisq_test, chisq_test_table, cor_test, prop_test, t_test, var_test
16
+ from rstat.rformat import format_vec, format_vec as format, format_num, format_pval, print_vector, signif
17
+ from rstat.rng import RNG, set_seed
18
+ from rstat.special import beta, choose, gamma, lbeta, lchoose, lgamma
19
+
20
+ __version__ = "0.1.0"
rstat/describe.py ADDED
@@ -0,0 +1,313 @@
1
+ """Descriptive statistics with R's algorithms (two-pass means and variances,
2
+ the nine quantile types) and R's printed form (named vectors, `summary`)."""
3
+
4
+ import math
5
+
6
+ from rstat.rformat import format_vec, format_num, print_named, signif
7
+
8
+
9
+ class Named:
10
+ """A named numeric vector, printed as R prints one."""
11
+
12
+ def __init__(self, names: list[str], values: list[float], digits: int = 7) -> None:
13
+ self.names = names
14
+ self.values = values
15
+ self.digits = digits
16
+
17
+ def __str__(self) -> str:
18
+ return print_named(self.names, format_vec(self.values, self.digits))
19
+
20
+ def __repr__(self) -> str:
21
+ return self.__str__()
22
+
23
+ def __getitem__(self, i: int) -> float:
24
+ return self.values[i]
25
+
26
+ def __len__(self) -> int:
27
+ return len(self.values)
28
+
29
+ def get(self, name: str) -> float:
30
+ """The value named `name`."""
31
+ return self.values[self.names.index(name)]
32
+
33
+
34
+ def _check(x: list[float]) -> None:
35
+ if len(x) == 0:
36
+ raise ValueError("'x' must have at least one element")
37
+
38
+
39
+ def mean(x: list[float]) -> float:
40
+ """R's mean: the sum over n, corrected by a second pass."""
41
+ _check(x)
42
+ n = float(len(x))
43
+ s = 0.0
44
+ for v in x:
45
+ s += v
46
+ s /= n
47
+ if not math.isinf(s) and not math.isnan(s):
48
+ t = 0.0
49
+ for v in x:
50
+ t += v - s
51
+ s += t / n
52
+ return s
53
+
54
+
55
+ def var(x: list[float]) -> float:
56
+ """The sample variance (denominator n - 1)."""
57
+ n = len(x)
58
+ if n < 2:
59
+ return math.nan
60
+ m = mean(x)
61
+ s = 0.0
62
+ for v in x:
63
+ s += (v - m) * (v - m)
64
+ return s / float(n - 1)
65
+
66
+
67
+ def sd(x: list[float]) -> float:
68
+ """The sample standard deviation."""
69
+ return math.sqrt(var(x))
70
+
71
+
72
+ def cov(x: list[float], y: list[float]) -> float:
73
+ """The sample covariance."""
74
+ if len(x) != len(y):
75
+ raise ValueError("incompatible dimensions")
76
+ n = len(x)
77
+ if n < 2:
78
+ return math.nan
79
+ mx = mean(x)
80
+ my = mean(y)
81
+ s = 0.0
82
+ for i in range(n):
83
+ s += (x[i] - mx) * (y[i] - my)
84
+ return s / float(n - 1)
85
+
86
+
87
+ def rank(x: list[float]) -> list[float]:
88
+ """Ranks, ties getting their average rank (R's `rank`)."""
89
+ n = len(x)
90
+ order = sorted(range(n), key=lambda i: x[i])
91
+ r = [0.0] * n
92
+ i = 0
93
+ while i < n:
94
+ j = i
95
+ while j + 1 < n and x[order[j + 1]] == x[order[i]]:
96
+ j += 1
97
+ avg = (float(i) + float(j)) / 2.0 + 1.0
98
+ for k in range(i, j + 1):
99
+ r[order[k]] = avg
100
+ i = j + 1
101
+ return r
102
+
103
+
104
+ def cor(x: list[float], y: list[float], method: str = "pearson") -> float:
105
+ """The correlation of x and y: "pearson", "spearman" or "kendall" (tau-b)."""
106
+ if len(x) != len(y):
107
+ raise ValueError("incompatible dimensions")
108
+ if method == "spearman":
109
+ return cor(rank(x), rank(y), "pearson")
110
+ if method == "kendall":
111
+ n = len(x)
112
+ conc = 0.0
113
+ tx = 0.0
114
+ ty = 0.0
115
+ for i in range(n):
116
+ for j in range(i + 1, n):
117
+ a = x[i] - x[j]
118
+ b = y[i] - y[j]
119
+ sa = 1.0 if a > 0.0 else (-1.0 if a < 0.0 else 0.0)
120
+ sb = 1.0 if b > 0.0 else (-1.0 if b < 0.0 else 0.0)
121
+ conc += sa * sb
122
+ tx += sa * sa
123
+ ty += sb * sb
124
+ return conc / math.sqrt(tx * ty)
125
+ mx = mean(x)
126
+ my = mean(y)
127
+ sxy = 0.0
128
+ sxx = 0.0
129
+ syy = 0.0
130
+ for i in range(len(x)):
131
+ dx = x[i] - mx
132
+ dy = y[i] - my
133
+ sxy += dx * dy
134
+ sxx += dx * dx
135
+ syy += dy * dy
136
+ r = sxy / math.sqrt(sxx * syy)
137
+ return max(-1.0, min(1.0, r))
138
+
139
+
140
+ def median(x: list[float]) -> float:
141
+ """The median."""
142
+ _check(x)
143
+ s = sorted(x)
144
+ n = len(s)
145
+ half = (n + 1) // 2
146
+ if n % 2 == 1:
147
+ return s[half - 1]
148
+ return mean([s[half - 1], s[half]])
149
+
150
+
151
+ def quantile(x: list[float], probs: list[float] | None = None, type: int = 7) -> Named:
152
+ """Sample quantiles of the nine types of R's `quantile` (default 7; probs
153
+ default 0, 0.25, 0.5, 0.75, 1), named as R names them ("25%")."""
154
+ _check(x)
155
+ ps = [0.0, 0.25, 0.5, 0.75, 1.0] if probs is None else probs
156
+ s = sorted(x)
157
+ n = len(s)
158
+ out: list[float] = []
159
+ eps = 2.220446049250313e-16
160
+ for p in ps:
161
+ if p < 0.0 or p > 1.0:
162
+ raise ValueError("'probs' outside [0,1]")
163
+ if type == 7:
164
+ index = 1.0 + float(max(n - 1, 0)) * p
165
+ lo = int(math.floor(index))
166
+ hi = int(math.ceil(index))
167
+ q = s[lo - 1]
168
+ h = index - float(lo)
169
+ if index > float(lo) and s[hi - 1] != q:
170
+ q = (1.0 - h) * q + h * s[hi - 1]
171
+ out.append(q)
172
+ continue
173
+ if type <= 3:
174
+ nppm = float(n) * p - 0.5 if type == 3 else float(n) * p
175
+ j = int(math.floor(nppm + 4.0 * eps))
176
+ if type == 1:
177
+ h = 1.0 if nppm > float(j) else 0.0
178
+ elif type == 2:
179
+ h = ((1.0 if nppm > float(j) else 0.0) + 1.0) / 2.0
180
+ else:
181
+ h = 1.0 if (nppm != float(j) or j % 2 == 1) else 0.0
182
+ else:
183
+ a = 0.0
184
+ b = 1.0
185
+ if type == 5:
186
+ a = 0.5
187
+ b = 0.5
188
+ elif type == 6:
189
+ a = 0.0
190
+ b = 0.0
191
+ elif type == 8:
192
+ a = 1.0 / 3.0
193
+ b = 1.0 / 3.0
194
+ elif type == 9:
195
+ a = 3.0 / 8.0
196
+ b = 3.0 / 8.0
197
+ fuzz = 4.0 * eps
198
+ nppm = a + p * (float(n) + 1.0 - a - b)
199
+ j = int(math.floor(nppm + fuzz))
200
+ h = nppm - float(j)
201
+ if abs(h) < fuzz:
202
+ h = 0.0
203
+ # x padded as R pads it: x[1], x[1], x..., x[n], x[n]
204
+ xs = [s[0], s[0]] + s + [s[n - 1], s[n - 1]]
205
+ lo_v = xs[j + 1]
206
+ hi_v = xs[j + 2]
207
+ if h == 1.0:
208
+ q = hi_v
209
+ elif 0.0 < h < 1.0 and lo_v != hi_v:
210
+ q = (1.0 - h) * lo_v + h * hi_v
211
+ else:
212
+ q = lo_v
213
+ out.append(q)
214
+ names = [_trim_pct(100.0 * p) for p in ps]
215
+ return Named(names, out)
216
+
217
+
218
+ def _trim_pct(v: float) -> str:
219
+ """`paste0(formatC(100 * probs, format = "fg", width = 1, digits = 7), "%")`."""
220
+ s = format_num(v, 7).strip()
221
+ if "." in s and "e" not in s:
222
+ s = s.rstrip("0").rstrip(".")
223
+ return s + "%"
224
+
225
+
226
+ def IQR(x: list[float], type: int = 7) -> float:
227
+ """The interquartile range."""
228
+ q = quantile(x, [0.25, 0.75], type)
229
+ return q[1] - q[0]
230
+
231
+
232
+ def mad(x: list[float], constant: float = 1.4826) -> float:
233
+ """The median absolute deviation (scaled to estimate σ of normal data)."""
234
+ m = median(x)
235
+ return constant * median([abs(v - m) for v in x])
236
+
237
+
238
+ def fivenum(x: list[float]) -> list[float]:
239
+ """Tukey's five numbers: minimum, lower hinge, median, upper hinge, maximum."""
240
+ _check(x)
241
+ s = sorted(x)
242
+ n = len(s)
243
+ n4 = float(math.floor((float(n) + 3.0) / 2.0)) / 2.0
244
+ d = [1.0, n4, (float(n) + 1.0) / 2.0, float(n) + 1.0 - n4, float(n)]
245
+ out: list[float] = []
246
+ for v in d:
247
+ lo = int(math.floor(v))
248
+ hi = int(math.ceil(v))
249
+ out.append(0.5 * (s[lo - 1] + s[hi - 1]))
250
+ return out
251
+
252
+
253
+ def scale(x: list[float]) -> list[float]:
254
+ """x centered by its mean and divided by its standard deviation."""
255
+ m = mean(x)
256
+ s = sd(x)
257
+ return [(v - m) / s for v in x]
258
+
259
+
260
+ def weighted_mean(x: list[float], w: list[float]) -> float:
261
+ """The weighted mean."""
262
+ sw = 0.0
263
+ swx = 0.0
264
+ for i in range(len(x)):
265
+ sw += w[i]
266
+ swx += w[i] * x[i]
267
+ return swx / sw
268
+
269
+
270
+ class Summary:
271
+ """`summary(x)` of a numeric vector, printed as R prints it."""
272
+
273
+ def __init__(self, values: list[float]) -> None:
274
+ self.names = ["Min.", "1st Qu.", "Median", "Mean", "3rd Qu.", "Max."]
275
+ self.values = values
276
+
277
+ def __str__(self) -> str:
278
+ # R's format.summaryDefault: 4 significant digits, after zapping
279
+ # values tiny next to the largest
280
+ digits = 4
281
+ mx = max(abs(v) for v in self.values)
282
+ zap = [v for v in self.values]
283
+ if mx > 0.0:
284
+ lim = 10.0 ** (float(math.floor(math.log10(mx))) - 7.0 - digits + 1.0)
285
+ zap = [0.0 if abs(v) < lim else v for v in self.values]
286
+ return print_named(self.names, format_vec(zap, digits))
287
+
288
+ def __repr__(self) -> str:
289
+ return self.__str__()
290
+
291
+ def get(self, name: str) -> float:
292
+ return self.values[self.names.index(name)]
293
+
294
+
295
+ def summary(x: list[float]) -> Summary:
296
+ """Minimum, quartiles, mean and maximum (R's `summary` of a numeric vector)."""
297
+ q = quantile(x)
298
+ return Summary([q[0], q[1], q[2], mean(x), q[3], q[4]])
299
+
300
+
301
+ def range_(x: list[float]) -> list[float]:
302
+ """`range(x)`: the minimum and the maximum."""
303
+ return [min(x), max(x)]
304
+
305
+
306
+ def round_(x: float, digits: int = 0) -> float:
307
+ """R's `round` (to even, at the decimal digit)."""
308
+ return round(x, digits)
309
+
310
+
311
+ def signif_(x: float, digits: int = 6) -> float:
312
+ """R's `signif`."""
313
+ return signif(x, digits)