bs-python-utils 0.0.1__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.
- bs_python_utils/Timer.py +76 -0
- bs_python_utils/__init__.py +0 -0
- bs_python_utils/bs_altair.py +927 -0
- bs_python_utils/bs_logging.py +100 -0
- bs_python_utils/bs_mathstr.py +130 -0
- bs_python_utils/bs_mem.py +148 -0
- bs_python_utils/bs_opt.py +518 -0
- bs_python_utils/bs_plots.py +2 -0
- bs_python_utils/bs_seaborn.py +174 -0
- bs_python_utils/bs_sparse_gaussian.py +46 -0
- bs_python_utils/bsmplutils.py +34 -0
- bs_python_utils/bsnputils.py +957 -0
- bs_python_utils/bssputils.py +79 -0
- bs_python_utils/bsstats.py +463 -0
- bs_python_utils/bsutils.py +363 -0
- bs_python_utils/distance_covariances.py +258 -0
- bs_python_utils/example_opt.py +71 -0
- bs_python_utils/examples_altair.py +195 -0
- bs_python_utils/examples_distance_covariances.py +32 -0
- bs_python_utils/examples_mem.py +25 -0
- bs_python_utils/examples_seaborn.py +37 -0
- bs_python_utils/examples_sklearn.py +33 -0
- bs_python_utils/pandas_utils.py +239 -0
- bs_python_utils/sklearn_utils.py +74 -0
- bs_python_utils-0.0.1.dist-info/LICENSE +21 -0
- bs_python_utils-0.0.1.dist-info/METADATA +71 -0
- bs_python_utils-0.0.1.dist-info/RECORD +28 -0
- bs_python_utils-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains various utilities programs.
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from io import TextIOBase
|
|
8
|
+
from math import exp, factorial, log, sqrt
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable, Iterable, cast
|
|
11
|
+
|
|
12
|
+
TwoFloats = tuple[float, float]
|
|
13
|
+
ThreeFloats = tuple[float, float, float]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def printargs(func: Callable) -> Callable:
|
|
17
|
+
"""
|
|
18
|
+
Decorator that reports the arguments of the function
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
func: the decorated function
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@wraps(func)
|
|
25
|
+
def wrapper(*args, **kwargs):
|
|
26
|
+
print(
|
|
27
|
+
f"Function {func.__name__} called with args = {args} and kwargs = {kwargs}"
|
|
28
|
+
)
|
|
29
|
+
return func(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
return wrapper
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bs_name_func(back: int = 2) -> str:
|
|
35
|
+
"""
|
|
36
|
+
get the name of the current function, or further back in stack
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
back: 2 is current function, 3 the function that called it etc
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
the name of the function
|
|
43
|
+
"""
|
|
44
|
+
stack = traceback.extract_stack()
|
|
45
|
+
*_, func_name, _ = stack[-back]
|
|
46
|
+
return cast(str, func_name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def bs_error_abort(msg: str = "error, aborting") -> None:
|
|
50
|
+
"""
|
|
51
|
+
report error and abort
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
msg: a message
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
prints the message and exits with code 1
|
|
58
|
+
"""
|
|
59
|
+
print_stars(f"{bs_name_func(3)}: {msg}")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def bs_switch(
|
|
64
|
+
match: str, dico: dict, strict: bool = True, default: Any = "no match"
|
|
65
|
+
) -> Any:
|
|
66
|
+
"""
|
|
67
|
+
a switch statement that allows for partial matches if strict is False
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
match: what we look for in the keys
|
|
71
|
+
dico: a dictionary with string keys
|
|
72
|
+
strict: if `False`, we accept a partial match
|
|
73
|
+
default: what we return if no match is found
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
the value for the match, or `default`
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
calc_dict = {
|
|
80
|
+
"plus": lambda x, y: x + y,
|
|
81
|
+
"minus": lambda x, y: x - y
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
plus = bs_switch('plus', calc_dict, default="unintended function")
|
|
85
|
+
minus = bs_switch('min', calc_dict, strict=False, default="unintended function")
|
|
86
|
+
|
|
87
|
+
plus(6, 4)
|
|
88
|
+
>> 10
|
|
89
|
+
minus(6, 4)
|
|
90
|
+
>> 2
|
|
91
|
+
|
|
92
|
+
bs_switch('plu', calc_dict)
|
|
93
|
+
>> "no match""
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
if strict:
|
|
97
|
+
for key in dico:
|
|
98
|
+
if match == key:
|
|
99
|
+
return dico.get(key)
|
|
100
|
+
else:
|
|
101
|
+
for key in dico:
|
|
102
|
+
if match in key:
|
|
103
|
+
return dico.get(key)
|
|
104
|
+
return default
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def find_first(iterable: Iterable, condition: Callable = lambda x: True) -> Any:
|
|
108
|
+
"""
|
|
109
|
+
Returns the index of and the first item in the `iterable` that
|
|
110
|
+
satisfies the `condition`.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
iterable: where to look
|
|
114
|
+
condition: must return a boolean
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
If the condition is not given, returns 0 and the first item of
|
|
118
|
+
the iterable.
|
|
119
|
+
|
|
120
|
+
Raises `StopIteration` if no item satisfyng the condition is found.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
>>> find_first( (1,2,3), condition=lambda x: x % 2 == 0)
|
|
124
|
+
(1, 2)
|
|
125
|
+
>>> find_first(range(3, 100))
|
|
126
|
+
(0, 3)
|
|
127
|
+
>>> find_first( () )
|
|
128
|
+
Traceback (most recent call last):
|
|
129
|
+
...
|
|
130
|
+
StopIteration
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
return next((i, x) for i, x in enumerate(iterable) if condition(x))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def print_stars(title: str = None, n: int = 70) -> None:
|
|
138
|
+
"""
|
|
139
|
+
prints a title within stars
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
title: title
|
|
143
|
+
n: number of stars on line
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
prints a starred line, or two around the title
|
|
147
|
+
"""
|
|
148
|
+
line_stars = "*" * n
|
|
149
|
+
print()
|
|
150
|
+
print(line_stars)
|
|
151
|
+
if title:
|
|
152
|
+
print(title.center(n))
|
|
153
|
+
print(line_stars)
|
|
154
|
+
print()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def file_print_stars(file_handle: TextIOBase, title: str = None, n: int = 70) -> None:
|
|
158
|
+
"""
|
|
159
|
+
prints to a file a title within stars
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
file_handle: file handle
|
|
163
|
+
title: title
|
|
164
|
+
n: length of line
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
prints a starred line to the file, or two around the title
|
|
168
|
+
"""
|
|
169
|
+
line_stars = "*" * n
|
|
170
|
+
file_handle.write("\n")
|
|
171
|
+
file_handle.write(line_stars)
|
|
172
|
+
file_handle.write("\n")
|
|
173
|
+
if title:
|
|
174
|
+
file_handle.write(title.center(n))
|
|
175
|
+
file_handle.write("\n")
|
|
176
|
+
file_handle.write(line_stars)
|
|
177
|
+
file_handle.write("\n")
|
|
178
|
+
file_handle.write("\n")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def mkdir_if_needed(p: Path | str) -> Path:
|
|
182
|
+
"""
|
|
183
|
+
create the directory if it does not exist
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
p: a path
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
the directory Path
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
q = Path(p)
|
|
194
|
+
except OSError:
|
|
195
|
+
bs_error_abort(f"{p} is not a path")
|
|
196
|
+
if not q.exists():
|
|
197
|
+
q.mkdir(parents=True)
|
|
198
|
+
return q
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def bscomb(n: int, k: int) -> int:
|
|
202
|
+
"""
|
|
203
|
+
number of combinations of k among n `{n \\choose k}`
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
n:
|
|
207
|
+
k: should be smaller than n
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
`{n \\choose k}`
|
|
211
|
+
"""
|
|
212
|
+
if not isinstance(n, int):
|
|
213
|
+
bs_error_abort(f"n should be an integer, not {n}")
|
|
214
|
+
if not isinstance(k, int):
|
|
215
|
+
bs_error_abort(f"k should be an integer, not {k}")
|
|
216
|
+
if n < k:
|
|
217
|
+
bs_error_abort(f"k={k} should not be larger than n={n}")
|
|
218
|
+
return factorial(n) // (factorial(k) * factorial(n - k))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def bslog(
|
|
222
|
+
x: float, eps: float = 1e-30, deriv: int = 0
|
|
223
|
+
) -> float | TwoFloats | ThreeFloats:
|
|
224
|
+
"""
|
|
225
|
+
extends the logarithm below `eps` by taking a second-order approximation
|
|
226
|
+
perhaps with derivatives
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
x: argument
|
|
230
|
+
eps: lower bound
|
|
231
|
+
deriv: if 1, also return first derivative; if 2, the first two derivatives
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
`\\ln(x)` `C^2`-extended below `eps`, perhaps with derivatives
|
|
235
|
+
"""
|
|
236
|
+
if deriv not in [0, 1, 2]:
|
|
237
|
+
bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
|
|
238
|
+
if x > eps:
|
|
239
|
+
logx = log(x)
|
|
240
|
+
if deriv == 0:
|
|
241
|
+
return logx
|
|
242
|
+
dlogx = 1.0 / x
|
|
243
|
+
if deriv == 1:
|
|
244
|
+
return logx, dlogx
|
|
245
|
+
d2logx = -dlogx * dlogx
|
|
246
|
+
return logx, dlogx, d2logx
|
|
247
|
+
else:
|
|
248
|
+
dx = 1.0 - x / eps
|
|
249
|
+
log_smaller = log(eps) - dx - dx * dx / 2.0
|
|
250
|
+
if deriv == 0:
|
|
251
|
+
return log_smaller
|
|
252
|
+
dlog_smaller = (1.0 + dx) / eps
|
|
253
|
+
if deriv == 1:
|
|
254
|
+
return log_smaller, dlog_smaller
|
|
255
|
+
d2log_smaller = -1.0 / eps / eps
|
|
256
|
+
return log_smaller, dlog_smaller, d2log_smaller
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def bsxlogx(
|
|
260
|
+
x: float, eps: float = 1e-30, deriv: int = 0
|
|
261
|
+
) -> float | TwoFloats | ThreeFloats:
|
|
262
|
+
"""
|
|
263
|
+
extends `x \\ln(x)` below `eps` by making it go to zero in a `C^2` extension
|
|
264
|
+
perhaps with derivatives
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
x: argument
|
|
268
|
+
eps: lower bound
|
|
269
|
+
deriv: if 1, also return first derivative; if 2, the first two derivatives
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
`x \\ln(x)` `C^2`-extended below `eps`, perhaps with derivatives
|
|
273
|
+
"""
|
|
274
|
+
if deriv not in [0, 1, 2]:
|
|
275
|
+
bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
|
|
276
|
+
if x > eps:
|
|
277
|
+
logx = log(x)
|
|
278
|
+
if deriv == 0:
|
|
279
|
+
return x * logx
|
|
280
|
+
if deriv == 1:
|
|
281
|
+
return x * logx, 1.0 + logx
|
|
282
|
+
return x * logx, 1.0 + logx, 1.0 / x
|
|
283
|
+
else:
|
|
284
|
+
logeps = log(eps)
|
|
285
|
+
dx = x / eps
|
|
286
|
+
log_smaller = x * logeps - eps / 2.0 + x * dx / 2.0
|
|
287
|
+
if deriv == 0:
|
|
288
|
+
return log_smaller
|
|
289
|
+
if deriv == 1:
|
|
290
|
+
return log_smaller, logeps + dx
|
|
291
|
+
return log_smaller, logeps + dx, 1.0 / eps
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _bsexp_extend(x: float, deriv: int, limx: float) -> float | TwoFloats | ThreeFloats:
|
|
295
|
+
"""extends the exponential C^2-wise beyond limx"""
|
|
296
|
+
elimx = exp(limx)
|
|
297
|
+
dx = x - limx
|
|
298
|
+
exp_extend = elimx * (1.0 + dx * (1.0 + 0.5 * dx))
|
|
299
|
+
if deriv == 0:
|
|
300
|
+
return exp_extend
|
|
301
|
+
dexp_extend = elimx * (1.0 + dx)
|
|
302
|
+
if deriv == 1:
|
|
303
|
+
return exp_extend, dexp_extend
|
|
304
|
+
# deriv = 2
|
|
305
|
+
return exp_extend, dexp_extend, elimx
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def bsexp(
|
|
309
|
+
x: float,
|
|
310
|
+
bigx: float = 50.0,
|
|
311
|
+
lowx: float = -50.0,
|
|
312
|
+
deriv: int = 0,
|
|
313
|
+
) -> float | TwoFloats | ThreeFloats:
|
|
314
|
+
"""
|
|
315
|
+
`C^2`-extends the exponential above `bigx` and below `lowx`
|
|
316
|
+
perhaps with derivatives
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
x: argument
|
|
320
|
+
bigx: upper bound
|
|
321
|
+
lowx: lower bound
|
|
322
|
+
deriv: if 1, also return first derivative; if 2, the first two derivatives
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
the exponential `C^2`-extended above `bigx` and below `lowx`
|
|
326
|
+
perhaps with derivatives
|
|
327
|
+
"""
|
|
328
|
+
if deriv not in [0, 1, 2]:
|
|
329
|
+
bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
|
|
330
|
+
if lowx < x < bigx:
|
|
331
|
+
expx = exp(x)
|
|
332
|
+
if deriv == 0:
|
|
333
|
+
return expx
|
|
334
|
+
if deriv == 1:
|
|
335
|
+
return expx, expx
|
|
336
|
+
return expx, expx, expx
|
|
337
|
+
elif x < lowx:
|
|
338
|
+
return _bsexp_extend(x, deriv, lowx)
|
|
339
|
+
else:
|
|
340
|
+
return _bsexp_extend(x, deriv, bigx)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def bs_projection_point(
|
|
344
|
+
x: float, y: float, a: float, b: float, c: float
|
|
345
|
+
) -> tuple[float, float, float]:
|
|
346
|
+
"""
|
|
347
|
+
projection of point (x,y) on line ax+by+c=0
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
x: y: coordinates
|
|
351
|
+
a: b: c: line parameters (as in ax+by+c=0)
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
x_proj: y_proj: coordinates of projection point
|
|
355
|
+
dist: distance of point from line
|
|
356
|
+
"""
|
|
357
|
+
a2b2 = a * a + b * b
|
|
358
|
+
denom = sqrt(a2b2)
|
|
359
|
+
value = a * x + b * y + c
|
|
360
|
+
x_proj = x - a * value / a2b2
|
|
361
|
+
y_proj = y - b * value / a2b2
|
|
362
|
+
dist = abs(value) / denom
|
|
363
|
+
return x_proj, y_proj, dist
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
distance covariance and partial distance covariance (Szekely and Rizzo)
|
|
3
|
+
evaluation and tests of independence and conditional independence
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from math import sqrt
|
|
8
|
+
from typing import cast
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from bs_python_utils.bsnputils import check_square, check_vector_or_matrix
|
|
13
|
+
from bs_python_utils.bsutils import bs_error_abort
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DcovResults:
|
|
18
|
+
dcov: float
|
|
19
|
+
dcov_stat: float
|
|
20
|
+
dcor: float
|
|
21
|
+
X_dd: np.ndarray
|
|
22
|
+
Y_dd: np.ndarray
|
|
23
|
+
unbiased: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class PdcovResults:
|
|
28
|
+
pdcov: float
|
|
29
|
+
pdcov_stat: float
|
|
30
|
+
pdcor: float
|
|
31
|
+
X_dd: np.ndarray
|
|
32
|
+
Y_dd: np.ndarray
|
|
33
|
+
Z_dd: np.ndarray
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _compute_distances(T: np.ndarray) -> np.ndarray:
|
|
37
|
+
"""
|
|
38
|
+
compute the Euclidian norms (or absolute values)
|
|
39
|
+
of all row differences `T_k - T_l`
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
T: a vector or a matrix
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
the matrix of norms of differences
|
|
46
|
+
"""
|
|
47
|
+
ndims_T = check_vector_or_matrix(T, "_compute_distances")
|
|
48
|
+
if ndims_T == 1:
|
|
49
|
+
return cast(np.ndarray, np.abs(np.subtract.outer(T, T)))
|
|
50
|
+
else:
|
|
51
|
+
n, nv = T.shape
|
|
52
|
+
A = np.zeros((n, n))
|
|
53
|
+
for iv in range(nv):
|
|
54
|
+
Tiv = T[:, iv]
|
|
55
|
+
Aiv = np.subtract.outer(Tiv, Tiv)
|
|
56
|
+
A += Aiv * Aiv
|
|
57
|
+
return np.sqrt(A)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _double_decenter(A: np.ndarray, unbiased: bool = False) -> np.ndarray:
|
|
61
|
+
"""
|
|
62
|
+
does double decentering on a square matrix A
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
A: a matrix
|
|
66
|
+
unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
the doubly decentered matrix
|
|
70
|
+
"""
|
|
71
|
+
n = check_square(A, "_double_decenter")
|
|
72
|
+
A_1 = np.sum(A, 0)
|
|
73
|
+
A_2 = np.sum(A, 1).reshape((-1, 1))
|
|
74
|
+
A_0 = np.sum(A_1)
|
|
75
|
+
fac2 = (n - 2) if unbiased else n
|
|
76
|
+
fac1 = (n - 1) if unbiased else n
|
|
77
|
+
A_dd = A - A_1 / fac2 - A_2 / fac2 + A_0 / (fac1 * fac2)
|
|
78
|
+
if unbiased:
|
|
79
|
+
np.fill_diagonal(A_dd, np.zeros(n))
|
|
80
|
+
return cast(np.ndarray, A_dd)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _dcov_prod(A: np.ndarray, B: np.ndarray, unbiased: bool = False) -> float:
|
|
84
|
+
n = check_square(A, "_dcov_prod")
|
|
85
|
+
m = check_square(B, "_dcov_prod")
|
|
86
|
+
if m == n:
|
|
87
|
+
fac3 = (n - 3) if unbiased else n
|
|
88
|
+
return cast(float, np.sum(A * B) / (n * fac3))
|
|
89
|
+
else:
|
|
90
|
+
bs_error_abort("A and B should be square matrices of the same size")
|
|
91
|
+
return 0.0 # for mypy
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def dcov_dcor(X: np.ndarray, Y: np.ndarray, unbiased: bool = False) -> DcovResults:
|
|
95
|
+
"""
|
|
96
|
+
evaluate the distance covariance and correlation of `X` and `Y`
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
X: `n` observations of a random variable or vector
|
|
100
|
+
Y: `n` observations of a random variable or vector
|
|
101
|
+
unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
`dCov^2(X,Y)` and `dCor^2(X,Y)`
|
|
105
|
+
"""
|
|
106
|
+
X_dist = _compute_distances(X)
|
|
107
|
+
n = X_dist.shape[0]
|
|
108
|
+
X_dd = _double_decenter(X_dist, unbiased)
|
|
109
|
+
Y_dist = _compute_distances(Y)
|
|
110
|
+
Y_dd = _double_decenter(Y_dist, unbiased)
|
|
111
|
+
dcov2 = _dcov_prod(X_dd, Y_dd, unbiased)
|
|
112
|
+
dcor2 = dcov2 / sqrt(
|
|
113
|
+
_dcov_prod(X_dd, X_dd, unbiased) * _dcov_prod(Y_dd, Y_dd, unbiased)
|
|
114
|
+
)
|
|
115
|
+
return DcovResults(
|
|
116
|
+
dcov=dcov2,
|
|
117
|
+
dcor=dcor2,
|
|
118
|
+
X_dd=X_dd,
|
|
119
|
+
Y_dd=Y_dd,
|
|
120
|
+
unbiased=unbiased,
|
|
121
|
+
dcov_stat=n * dcov2,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _dcov_bootstrap(
|
|
126
|
+
X_dd: np.ndarray,
|
|
127
|
+
Y_dd: np.ndarray,
|
|
128
|
+
unbiased: bool = False,
|
|
129
|
+
ndraws: int = 199,
|
|
130
|
+
) -> np.ndarray:
|
|
131
|
+
"""
|
|
132
|
+
use bootstrap on the test statistics of independence
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
X_dd: the doubly decentered distances for `X`
|
|
136
|
+
Y_dd: the doubly decentered distances for `Y`
|
|
137
|
+
unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
|
|
138
|
+
ndraws: number of permutations
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
the values of the `ndraws` bootstrapped test stats
|
|
142
|
+
"""
|
|
143
|
+
n = X_dd.shape[0]
|
|
144
|
+
dcov_stats_boot = np.zeros(ndraws)
|
|
145
|
+
for idraw in range(ndraws):
|
|
146
|
+
draws = np.random.choice(np.arange(n), n)
|
|
147
|
+
X_ddi = X_dd[draws, :][:, draws]
|
|
148
|
+
Y_ddi = Y_dd[draws, :][:, draws]
|
|
149
|
+
if idraw % 50 == 0:
|
|
150
|
+
print(f" bootstrap draw {idraw}")
|
|
151
|
+
dcov_stats_boot[idraw] = _dcov_prod(X_ddi, Y_ddi, unbiased)
|
|
152
|
+
return cast(np.ndarray, n * dcov_stats_boot)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def pvalue_dcov(dcov_results: DcovResults, ndraws: int = 199) -> float:
|
|
156
|
+
"""
|
|
157
|
+
test of no dependence between `X` and `Y` given `Z`
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
dcov_results: results from `dcov_dcor`
|
|
161
|
+
ndraws: the number of draws we use
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
the bootstrapped p-value of the test
|
|
165
|
+
"""
|
|
166
|
+
X_dd = dcov_results.X_dd
|
|
167
|
+
Y_dd = dcov_results.Y_dd
|
|
168
|
+
dcov_stat = dcov_results.dcov_stat
|
|
169
|
+
unbiased = dcov_results.unbiased
|
|
170
|
+
dcov_stats_boot = _dcov_bootstrap(X_dd, Y_dd, unbiased, ndraws)
|
|
171
|
+
sum_small = cast(int, np.sum(dcov_stat < dcov_stats_boot))
|
|
172
|
+
return (1.0 + sum_small) / (1.0 + ndraws)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def pdcov_pdcor(X: np.ndarray, Y: np.ndarray, Z: np.ndarray) -> PdcovResults:
|
|
176
|
+
"""
|
|
177
|
+
evaluate the partial distance covariance and correlation of `X` and `Y` given `Z`
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
X: `n` observations of a random variable or vector
|
|
181
|
+
Y: `n` observations of a random variable or vector
|
|
182
|
+
Z: `n` observations of a random variable or vector
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
a `PdcovResults` instance
|
|
186
|
+
"""
|
|
187
|
+
unbiased = True
|
|
188
|
+
X_dist = _compute_distances(X)
|
|
189
|
+
X_dd = _double_decenter(X_dist, unbiased)
|
|
190
|
+
Y_dist = _compute_distances(Y)
|
|
191
|
+
Y_dd = _double_decenter(Y_dist, unbiased)
|
|
192
|
+
Z_dist = _compute_distances(Z)
|
|
193
|
+
Z_dd = _double_decenter(Z_dist, unbiased)
|
|
194
|
+
C_XX = _dcov_prod(X_dd, X_dd, unbiased)
|
|
195
|
+
C_XY = _dcov_prod(X_dd, Y_dd, unbiased)
|
|
196
|
+
C_YY = _dcov_prod(Y_dd, Y_dd, unbiased)
|
|
197
|
+
C_XZ = _dcov_prod(X_dd, Z_dd, unbiased)
|
|
198
|
+
C_YZ = _dcov_prod(Y_dd, Z_dd, unbiased)
|
|
199
|
+
C_ZZ = _dcov_prod(Z_dd, Z_dd, unbiased)
|
|
200
|
+
pdcov = C_XY - (C_XZ * C_YZ) / C_ZZ
|
|
201
|
+
pdcor = pdcov / sqrt((C_XX - C_XZ * C_XZ / C_ZZ) * (C_YY - C_YZ * C_YZ / C_ZZ))
|
|
202
|
+
n = X.shape[0]
|
|
203
|
+
return PdcovResults(
|
|
204
|
+
pdcov=pdcov, pdcor=pdcor, pdcov_stat=n * pdcov, X_dd=X_dd, Y_dd=Y_dd, Z_dd=Z_dd
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _pdcovs_bootstrap(
|
|
209
|
+
X_dd: np.ndarray, Y_dd: np.ndarray, Z_dd: np.ndarray, ndraws: int = 199
|
|
210
|
+
) -> np.ndarray:
|
|
211
|
+
"""
|
|
212
|
+
use permutations and recompute the test statistics of independence
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
X_dd: the doubly decentered distances for `X`
|
|
216
|
+
Y_dd: the doubly decentered distances for `Y`
|
|
217
|
+
Z_dd: the doubly decentered distances for `Y`
|
|
218
|
+
ndraws: the number of draws we use
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
the `ndraws` values of `pdCov(X,Y ; Z)`
|
|
222
|
+
"""
|
|
223
|
+
pdcov_stats_boot = np.zeros(ndraws)
|
|
224
|
+
unbiased = True
|
|
225
|
+
n = X_dd.shape[0]
|
|
226
|
+
for idraw in range(ndraws):
|
|
227
|
+
if idraw % 50 == 0:
|
|
228
|
+
print(f"pdcov test: bootstrap draw {idraw}")
|
|
229
|
+
draws = np.random.choice(np.arange(n), n)
|
|
230
|
+
X_ddi = X_dd[draws, :][:, draws]
|
|
231
|
+
Y_ddi = Y_dd[draws, :][:, draws]
|
|
232
|
+
Z_ddi = Z_dd[draws, :][:, draws]
|
|
233
|
+
C_XY = _dcov_prod(X_ddi, Y_ddi, unbiased)
|
|
234
|
+
C_XZ = _dcov_prod(X_ddi, Z_ddi, unbiased)
|
|
235
|
+
C_YZ = _dcov_prod(Y_ddi, Z_ddi, unbiased)
|
|
236
|
+
C_ZZ = _dcov_prod(Z_ddi, Z_ddi, unbiased)
|
|
237
|
+
pdcov_stats_boot[idraw] = C_XY - (C_XZ * C_YZ) / C_ZZ
|
|
238
|
+
return n * pdcov_stats_boot
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def pvalue_pdcov(pdcov_results: PdcovResults, ndraws: int = 199) -> float:
|
|
242
|
+
"""
|
|
243
|
+
test of no dependence between `X` and `Y` given `Z`
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
pdcov_results: the results of `pdcov_pdcor`
|
|
247
|
+
ndraws: the number of draws we use
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
the bootstrapped p-value of the test
|
|
251
|
+
"""
|
|
252
|
+
X_dd = pdcov_results.X_dd
|
|
253
|
+
Y_dd = pdcov_results.Y_dd
|
|
254
|
+
Z_dd = pdcov_results.Z_dd
|
|
255
|
+
pdcov_stat = pdcov_results.pdcov_stat
|
|
256
|
+
pdcov_stats_boot = _pdcovs_bootstrap(X_dd, Y_dd, Z_dd, ndraws)
|
|
257
|
+
sum_small = cast(int, np.sum(pdcov_stat < pdcov_stats_boot))
|
|
258
|
+
return (1.0 + sum_small) / (1.0 + ndraws)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""examples of optimization"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from bs_python_utils.bsutils import print_stars
|
|
6
|
+
from bs_python_utils.bssputils import describe_array
|
|
7
|
+
from bs_python_utils.bs_opt import (
|
|
8
|
+
acc_grad_descent,
|
|
9
|
+
minimize_some_fixed,
|
|
10
|
+
armijo_alpha,
|
|
11
|
+
barzilai_borwein_alpha,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
print_stars("Testing acc_grad_descent")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def grad_f(x, p):
|
|
19
|
+
xp = x - p[0]
|
|
20
|
+
return 4.0 * xp * xp * xp
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
x_init = np.random.normal(size=10000)
|
|
24
|
+
|
|
25
|
+
p = 1.0
|
|
26
|
+
x_conv, ret_code = acc_grad_descent(
|
|
27
|
+
grad_f, x_init, other_params=np.array([p]), tol=1e-12, verbose=False
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
describe_array(x_conv - p, "x-p should be close to zero")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def obj(x, args):
|
|
34
|
+
res = x - args
|
|
35
|
+
return np.sum(res * res)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def grad_obj(x, args):
|
|
39
|
+
res = x - args
|
|
40
|
+
return 2.0 * res
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
n = 5
|
|
44
|
+
x_init = np.full(n, 0.5)
|
|
45
|
+
args = np.arange(n)
|
|
46
|
+
bounds = [(-10.0, 10.0) for _ in range(n)]
|
|
47
|
+
|
|
48
|
+
fixed_vars = [1, 3]
|
|
49
|
+
fixed_vals = -np.ones(2)
|
|
50
|
+
|
|
51
|
+
resopt = minimize_some_fixed(
|
|
52
|
+
obj,
|
|
53
|
+
grad_obj,
|
|
54
|
+
x_init,
|
|
55
|
+
args,
|
|
56
|
+
fixed_vars=fixed_vars,
|
|
57
|
+
fixed_vals=fixed_vals,
|
|
58
|
+
bounds=bounds,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
print(resopt)
|
|
62
|
+
|
|
63
|
+
# test the step routines
|
|
64
|
+
g = grad_obj(x_init, args)
|
|
65
|
+
alpha_a = armijo_alpha(obj, x_init, -g, args)
|
|
66
|
+
print(f"\nArmijo alpha={alpha_a}")
|
|
67
|
+
|
|
68
|
+
alpha_b, g_b = barzilai_borwein_alpha(grad_obj, x_init, args)
|
|
69
|
+
print(f"\nBarzilai-Borwein alpha={alpha_a}")
|
|
70
|
+
print("g and g_b:")
|
|
71
|
+
print(np.column_stack((g, g_b)))
|