skfolio 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.
- skfolio/__init__.py +29 -0
- skfolio/cluster/__init__.py +8 -0
- skfolio/cluster/_hierarchical.py +387 -0
- skfolio/datasets/__init__.py +20 -0
- skfolio/datasets/_base.py +389 -0
- skfolio/datasets/data/__init__.py +0 -0
- skfolio/datasets/data/factors_dataset.csv.gz +0 -0
- skfolio/datasets/data/sp500_dataset.csv.gz +0 -0
- skfolio/datasets/data/sp500_index.csv.gz +0 -0
- skfolio/distance/__init__.py +26 -0
- skfolio/distance/_base.py +55 -0
- skfolio/distance/_distance.py +574 -0
- skfolio/exceptions.py +30 -0
- skfolio/measures/__init__.py +76 -0
- skfolio/measures/_enums.py +355 -0
- skfolio/measures/_measures.py +607 -0
- skfolio/metrics/__init__.py +3 -0
- skfolio/metrics/_scorer.py +121 -0
- skfolio/model_selection/__init__.py +18 -0
- skfolio/model_selection/_combinatorial.py +407 -0
- skfolio/model_selection/_validation.py +194 -0
- skfolio/model_selection/_walk_forward.py +221 -0
- skfolio/moments/__init__.py +41 -0
- skfolio/moments/covariance/__init__.py +29 -0
- skfolio/moments/covariance/_base.py +101 -0
- skfolio/moments/covariance/_covariance.py +1108 -0
- skfolio/moments/expected_returns/__init__.py +21 -0
- skfolio/moments/expected_returns/_base.py +31 -0
- skfolio/moments/expected_returns/_expected_returns.py +415 -0
- skfolio/optimization/__init__.py +36 -0
- skfolio/optimization/_base.py +147 -0
- skfolio/optimization/cluster/__init__.py +13 -0
- skfolio/optimization/cluster/_nco.py +348 -0
- skfolio/optimization/cluster/hierarchical/__init__.py +13 -0
- skfolio/optimization/cluster/hierarchical/_base.py +440 -0
- skfolio/optimization/cluster/hierarchical/_herc.py +406 -0
- skfolio/optimization/cluster/hierarchical/_hrp.py +368 -0
- skfolio/optimization/convex/__init__.py +16 -0
- skfolio/optimization/convex/_base.py +1944 -0
- skfolio/optimization/convex/_distributionally_robust.py +392 -0
- skfolio/optimization/convex/_maximum_diversification.py +417 -0
- skfolio/optimization/convex/_mean_risk.py +974 -0
- skfolio/optimization/convex/_risk_budgeting.py +560 -0
- skfolio/optimization/ensemble/__init__.py +6 -0
- skfolio/optimization/ensemble/_base.py +87 -0
- skfolio/optimization/ensemble/_stacking.py +326 -0
- skfolio/optimization/naive/__init__.py +3 -0
- skfolio/optimization/naive/_naive.py +173 -0
- skfolio/population/__init__.py +3 -0
- skfolio/population/_population.py +883 -0
- skfolio/portfolio/__init__.py +13 -0
- skfolio/portfolio/_base.py +1096 -0
- skfolio/portfolio/_multi_period_portfolio.py +610 -0
- skfolio/portfolio/_portfolio.py +842 -0
- skfolio/pre_selection/__init__.py +7 -0
- skfolio/pre_selection/_pre_selection.py +342 -0
- skfolio/preprocessing/__init__.py +3 -0
- skfolio/preprocessing/_returns.py +114 -0
- skfolio/prior/__init__.py +18 -0
- skfolio/prior/_base.py +63 -0
- skfolio/prior/_black_litterman.py +238 -0
- skfolio/prior/_empirical.py +163 -0
- skfolio/prior/_factor_model.py +268 -0
- skfolio/typing.py +50 -0
- skfolio/uncertainty_set/__init__.py +23 -0
- skfolio/uncertainty_set/_base.py +108 -0
- skfolio/uncertainty_set/_bootstrap.py +281 -0
- skfolio/uncertainty_set/_empirical.py +237 -0
- skfolio/utils/__init__.py +0 -0
- skfolio/utils/bootstrap.py +115 -0
- skfolio/utils/equations.py +350 -0
- skfolio/utils/sorting.py +117 -0
- skfolio/utils/stats.py +466 -0
- skfolio/utils/tools.py +567 -0
- skfolio-0.0.1.dist-info/LICENSE +29 -0
- skfolio-0.0.1.dist-info/METADATA +568 -0
- skfolio-0.0.1.dist-info/RECORD +79 -0
- skfolio-0.0.1.dist-info/WHEEL +5 -0
- skfolio-0.0.1.dist-info/top_level.txt +1 -0
skfolio/utils/tools.py
ADDED
@@ -0,0 +1,567 @@
|
|
1
|
+
"""Tools module"""
|
2
|
+
|
3
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
4
|
+
# License: BSD 3 clause
|
5
|
+
|
6
|
+
from collections.abc import Callable, Iterator
|
7
|
+
from enum import Enum
|
8
|
+
from functools import wraps
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import numpy.typing as npt
|
12
|
+
import pandas as pd
|
13
|
+
import sklearn as sk
|
14
|
+
import sklearn.base as skb
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"AutoEnum",
|
18
|
+
"cached_property_slots",
|
19
|
+
"cache_method",
|
20
|
+
"input_to_array",
|
21
|
+
"args_names",
|
22
|
+
"format_measure",
|
23
|
+
"bisection",
|
24
|
+
"safe_split",
|
25
|
+
"fit_single_estimator",
|
26
|
+
"fit_and_predict",
|
27
|
+
"deduplicate_names",
|
28
|
+
"default_asset_names",
|
29
|
+
"check_estimator",
|
30
|
+
]
|
31
|
+
|
32
|
+
GenericAlias = type(list[int])
|
33
|
+
|
34
|
+
|
35
|
+
class AutoEnum(str, Enum):
|
36
|
+
"""Base Enum class used in `skfolio`"""
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def _generate_next_value_(
|
40
|
+
name: str, start: int, count: int, last_values: any
|
41
|
+
) -> str:
|
42
|
+
"""Overriding `auto()`"""
|
43
|
+
return name.lower()
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def has(cls, value: str) -> bool:
|
47
|
+
"""Check if a value is in the Enum.
|
48
|
+
|
49
|
+
Parameters
|
50
|
+
----------
|
51
|
+
value : str
|
52
|
+
Input value.
|
53
|
+
|
54
|
+
Returns
|
55
|
+
-------
|
56
|
+
x : bool
|
57
|
+
True if the value is in the Enum, False otherwise.
|
58
|
+
"""
|
59
|
+
return value in cls._value2member_map_
|
60
|
+
|
61
|
+
def __repr__(self) -> str:
|
62
|
+
"""Representation of the Enum"""
|
63
|
+
return self.name
|
64
|
+
|
65
|
+
|
66
|
+
# noinspection PyPep8Naming
|
67
|
+
class cached_property_slots:
|
68
|
+
"""Cached property decorator for slots"""
|
69
|
+
|
70
|
+
def __init__(self, func):
|
71
|
+
self.func = func
|
72
|
+
self.public_name = None
|
73
|
+
self.private_name = None
|
74
|
+
self.__doc__ = func.__doc__
|
75
|
+
|
76
|
+
def __set_name__(self, owner, name):
|
77
|
+
self.public_name = name
|
78
|
+
self.private_name = f"_{name}"
|
79
|
+
|
80
|
+
def __get__(self, instance, owner=None):
|
81
|
+
if instance is None:
|
82
|
+
return self
|
83
|
+
if self.private_name is None:
|
84
|
+
raise TypeError(
|
85
|
+
"Cannot use cached_property instance without calling __set_name__"
|
86
|
+
" on it."
|
87
|
+
)
|
88
|
+
try:
|
89
|
+
value = getattr(instance, self.private_name)
|
90
|
+
except AttributeError:
|
91
|
+
value = self.func(instance)
|
92
|
+
setattr(instance, self.private_name, value)
|
93
|
+
return value
|
94
|
+
|
95
|
+
def __set__(self, instance, owner=None):
|
96
|
+
raise AttributeError(
|
97
|
+
f"'{type(instance).__name__}' object attribute '{self.public_name}' is"
|
98
|
+
" read-only"
|
99
|
+
)
|
100
|
+
|
101
|
+
__class_getitem__ = classmethod(GenericAlias)
|
102
|
+
|
103
|
+
|
104
|
+
def _make_key(args, kwds) -> int:
|
105
|
+
"""Make a cache key from optionally typed positional and keyword arguments"""
|
106
|
+
key = args
|
107
|
+
if kwds:
|
108
|
+
for item in kwds.items():
|
109
|
+
key += item
|
110
|
+
return hash(key)
|
111
|
+
|
112
|
+
|
113
|
+
def cache_method(cache_name: str) -> Callable:
|
114
|
+
"""Decorator that caches class methods results into a class dictionary.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
cache_name : str
|
119
|
+
Name of the dictionary class attribute.
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
func : Callable
|
124
|
+
Decorating function that caches class methods.
|
125
|
+
"""
|
126
|
+
|
127
|
+
# To avoid memory leakage and proper garbage collection, self should not be part of
|
128
|
+
# the cache key.
|
129
|
+
# This is a known issue when we use functools.lru_cache on class methods.
|
130
|
+
def decorating_function(method):
|
131
|
+
@wraps(method)
|
132
|
+
def wrapper(self, *args, **kwargs):
|
133
|
+
func_name = method.__name__
|
134
|
+
key = _make_key(args, kwargs)
|
135
|
+
try:
|
136
|
+
cache = getattr(self, cache_name)
|
137
|
+
except AttributeError:
|
138
|
+
raise AttributeError(
|
139
|
+
"You first need to create a dictionary class attribute named "
|
140
|
+
f"'{cache_name}'"
|
141
|
+
) from None
|
142
|
+
if not isinstance(cache, dict):
|
143
|
+
raise AttributeError(
|
144
|
+
f"'The cache named '{cache_name}' must be a "
|
145
|
+
f"dictionary, got {type(cache)}"
|
146
|
+
)
|
147
|
+
if func_name not in cache:
|
148
|
+
cache[func_name] = {}
|
149
|
+
c = cache[func_name]
|
150
|
+
if key not in c:
|
151
|
+
c[key] = method(self, *args, **kwargs)
|
152
|
+
return c[key]
|
153
|
+
|
154
|
+
return wrapper
|
155
|
+
|
156
|
+
return decorating_function
|
157
|
+
|
158
|
+
|
159
|
+
def args_names(func: object) -> list[str]:
|
160
|
+
"""Returns the argument names of a function.
|
161
|
+
|
162
|
+
Parameters
|
163
|
+
----------
|
164
|
+
func : object
|
165
|
+
Function.
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
args : list[str]
|
170
|
+
The list of function arguments.
|
171
|
+
"""
|
172
|
+
return [
|
173
|
+
v for v in func.__code__.co_varnames[: func.__code__.co_argcount] if v != "self"
|
174
|
+
]
|
175
|
+
|
176
|
+
|
177
|
+
def check_estimator(
|
178
|
+
estimator: skb.BaseEstimator | None, default: skb.BaseEstimator, check_type: any
|
179
|
+
):
|
180
|
+
"""Check the estimator type and returns its cloned version it provided, otherwise
|
181
|
+
return the default estimator.
|
182
|
+
|
183
|
+
Parameters
|
184
|
+
----------
|
185
|
+
estimator : BaseEstimator, optional
|
186
|
+
Estimator.
|
187
|
+
|
188
|
+
default : BaseEstimator
|
189
|
+
Default estimator to return when `estimator` is `None`.
|
190
|
+
|
191
|
+
check_type : any
|
192
|
+
Expected type of the estimator to check against.
|
193
|
+
|
194
|
+
Returns
|
195
|
+
-------
|
196
|
+
estimator: Estimator
|
197
|
+
The checked estimator or the default.
|
198
|
+
"""
|
199
|
+
|
200
|
+
if estimator is None:
|
201
|
+
return default
|
202
|
+
if not isinstance(estimator, check_type):
|
203
|
+
raise TypeError(f"Expected type {check_type}, got {type(estimator)}")
|
204
|
+
return sk.clone(estimator)
|
205
|
+
|
206
|
+
|
207
|
+
def input_to_array(
|
208
|
+
items: dict | npt.ArrayLike,
|
209
|
+
n_assets: int,
|
210
|
+
fill_value: any,
|
211
|
+
dim: int,
|
212
|
+
assets_names: np.ndarray | None,
|
213
|
+
name: str,
|
214
|
+
) -> np.ndarray:
|
215
|
+
"""Convert a collection of items (array-like or dictionary) into
|
216
|
+
a numpy array and verify its shape.
|
217
|
+
|
218
|
+
Parameters
|
219
|
+
----------
|
220
|
+
items : np.ndarray | dict | list
|
221
|
+
Items to verify and convert to array.
|
222
|
+
|
223
|
+
n_assets : int
|
224
|
+
Expected number of assets.
|
225
|
+
Used to verify the shape of the converted array.
|
226
|
+
|
227
|
+
fill_value : any
|
228
|
+
When `items` is a dictionary, elements that are not in `asset_names` are filled
|
229
|
+
with `fill_value` in the converted array.
|
230
|
+
|
231
|
+
dim : int
|
232
|
+
Dimension of the final array.
|
233
|
+
Possible values are `1` or `2`.
|
234
|
+
|
235
|
+
assets_names : ndarray, optional
|
236
|
+
Asset names used when `items` is a dictionary.
|
237
|
+
|
238
|
+
name : str
|
239
|
+
Name of the items used for error messages.
|
240
|
+
|
241
|
+
Returns
|
242
|
+
-------
|
243
|
+
values : ndarray of shape (n_assets) for dim=1 or (n_groups, n_assets) for dim=2
|
244
|
+
Converted array.
|
245
|
+
"""
|
246
|
+
if dim not in [1, 2]:
|
247
|
+
raise ValueError(f"dim must be 1 or 2, got {dim}")
|
248
|
+
if isinstance(items, dict):
|
249
|
+
if assets_names is None:
|
250
|
+
raise ValueError(
|
251
|
+
f"If `{name}` is provided as a dictionary, you must input `X` as a"
|
252
|
+
" DataFrame with assets names in columns"
|
253
|
+
)
|
254
|
+
if dim == 1:
|
255
|
+
arr = np.array([items.get(asset, fill_value) for asset in assets_names])
|
256
|
+
else:
|
257
|
+
# add assets and convert dict to ordered array
|
258
|
+
arr = {}
|
259
|
+
for asset in assets_names:
|
260
|
+
elem = items.get(asset)
|
261
|
+
if elem is None:
|
262
|
+
elem = [asset]
|
263
|
+
elif np.isscalar(elem):
|
264
|
+
elem = [asset, elem]
|
265
|
+
else:
|
266
|
+
elem = [asset, *elem]
|
267
|
+
arr[asset] = elem
|
268
|
+
arr = (
|
269
|
+
pd.DataFrame.from_dict(arr, orient="index")
|
270
|
+
.loc[assets_names]
|
271
|
+
.to_numpy()
|
272
|
+
.T
|
273
|
+
)
|
274
|
+
else:
|
275
|
+
arr = np.asarray(items)
|
276
|
+
|
277
|
+
if arr.ndim != dim:
|
278
|
+
raise ValueError(f"`{name}` must be a {dim}D array, got a {arr.ndim}D array")
|
279
|
+
|
280
|
+
if not isinstance(fill_value, str) and np.isnan(arr).any():
|
281
|
+
raise ValueError(f"`{name}` contains NaN")
|
282
|
+
|
283
|
+
if arr.shape[-1] != n_assets:
|
284
|
+
if dim == 1:
|
285
|
+
s = "(n_assets,)"
|
286
|
+
else:
|
287
|
+
s = "(n_groups, n_assets)"
|
288
|
+
raise ValueError(
|
289
|
+
f"`{name}` must be a of shape {s} with n_assets={n_assets}, "
|
290
|
+
f"got {arr.shape[0]}"
|
291
|
+
)
|
292
|
+
return arr
|
293
|
+
|
294
|
+
|
295
|
+
def format_measure(x: float, percent: bool = False) -> str:
|
296
|
+
"""Format a measure number into a user-friendly string.
|
297
|
+
|
298
|
+
Parameters
|
299
|
+
----------
|
300
|
+
x : float
|
301
|
+
Number to format.
|
302
|
+
|
303
|
+
percent : bool, default=False
|
304
|
+
If this is set to True, the number is formatted in percentage.
|
305
|
+
|
306
|
+
Returns
|
307
|
+
-------
|
308
|
+
formatted : str
|
309
|
+
Formatted string.
|
310
|
+
"""
|
311
|
+
if np.isnan(x):
|
312
|
+
return str(x)
|
313
|
+
if percent:
|
314
|
+
xn = x * 100
|
315
|
+
f = "%"
|
316
|
+
else:
|
317
|
+
xn = x
|
318
|
+
f = "f"
|
319
|
+
if xn == 0:
|
320
|
+
n = 0
|
321
|
+
else:
|
322
|
+
n = min(6, max(int(-np.log10(abs(xn))) + 2, 2))
|
323
|
+
return "{value:{fmt}}".format(value=x, fmt=f".{n}{f}")
|
324
|
+
|
325
|
+
|
326
|
+
def bisection(x: list[np.ndarray]) -> Iterator[list[np.ndarray, np.ndarray]]:
|
327
|
+
"""Generator to bisect a list of array.
|
328
|
+
|
329
|
+
Parameters
|
330
|
+
----------
|
331
|
+
x : list[ndarray]
|
332
|
+
A list of array.
|
333
|
+
|
334
|
+
Yields
|
335
|
+
------
|
336
|
+
arr : Iterator[list[ndarray, ndarray]]
|
337
|
+
Bisected array.
|
338
|
+
"""
|
339
|
+
for e in x:
|
340
|
+
n = len(e)
|
341
|
+
if n > 1:
|
342
|
+
mid = n // 2
|
343
|
+
yield [e[0:mid], e[mid:n]]
|
344
|
+
|
345
|
+
|
346
|
+
def safe_indexing(
|
347
|
+
X: npt.ArrayLike | pd.DataFrame, indices: npt.ArrayLike | None, axis: int = 0
|
348
|
+
):
|
349
|
+
"""
|
350
|
+
Return rows, items or columns of X using indices.
|
351
|
+
|
352
|
+
Parameters
|
353
|
+
----------
|
354
|
+
X : array-like
|
355
|
+
Data from which to sample rows.
|
356
|
+
|
357
|
+
indices : array-like, optional
|
358
|
+
Indices of rows or columns.
|
359
|
+
The default (`None`) is to select the entire data.
|
360
|
+
|
361
|
+
axis : int, default=0
|
362
|
+
The axis along which `X` will be sub-sampled. `axis=0` will select
|
363
|
+
rows while `axis=1` will select columns.
|
364
|
+
|
365
|
+
Returns
|
366
|
+
-------
|
367
|
+
subset :
|
368
|
+
Subset of X on axis 0.
|
369
|
+
"""
|
370
|
+
if indices is None:
|
371
|
+
return X
|
372
|
+
if hasattr(X, "iloc"):
|
373
|
+
return X.take(indices, axis=axis)
|
374
|
+
if axis == 0:
|
375
|
+
return X[indices]
|
376
|
+
return X[:, indices]
|
377
|
+
|
378
|
+
|
379
|
+
def safe_split(
|
380
|
+
X: npt.ArrayLike,
|
381
|
+
y: npt.ArrayLike | None = None,
|
382
|
+
indices: np.ndarray | None = None,
|
383
|
+
axis: int = 0,
|
384
|
+
):
|
385
|
+
"""Create subset of dataset.
|
386
|
+
|
387
|
+
Slice X, y according to indices for cross-validation.
|
388
|
+
|
389
|
+
Parameters
|
390
|
+
----------
|
391
|
+
X : array-like
|
392
|
+
Data to be indexed.
|
393
|
+
|
394
|
+
y : array-like
|
395
|
+
Data to be indexed.
|
396
|
+
|
397
|
+
indices : ndarray of int, optional
|
398
|
+
Rows or columns to select from X and y.
|
399
|
+
The default (`None`) is to select the entire data.
|
400
|
+
|
401
|
+
axis : int, default=0
|
402
|
+
The axis along which `X` will be sub-sampled. `axis=0` will select
|
403
|
+
rows while `axis=1` will select columns.
|
404
|
+
|
405
|
+
Returns
|
406
|
+
-------
|
407
|
+
X_subset : array-like
|
408
|
+
Indexed data.
|
409
|
+
|
410
|
+
y_subset : array-like
|
411
|
+
Indexed targets.
|
412
|
+
"""
|
413
|
+
|
414
|
+
X_subset = safe_indexing(X, indices=indices, axis=axis)
|
415
|
+
if y is not None:
|
416
|
+
y_subset = safe_indexing(y, indices=indices, axis=axis)
|
417
|
+
else:
|
418
|
+
y_subset = None
|
419
|
+
return X_subset, y_subset
|
420
|
+
|
421
|
+
|
422
|
+
def fit_single_estimator(
|
423
|
+
estimator: any,
|
424
|
+
X: npt.ArrayLike,
|
425
|
+
y: npt.ArrayLike | None = None,
|
426
|
+
indices: np.ndarray | None = None,
|
427
|
+
axis: int = 0,
|
428
|
+
):
|
429
|
+
"""function used to fit an estimator within a job.
|
430
|
+
|
431
|
+
Parameters
|
432
|
+
----------
|
433
|
+
estimator : estimator object implementing 'fit' and 'predict'
|
434
|
+
The object to use to fit the data.
|
435
|
+
|
436
|
+
X : array-like of shape (n_observations, n_assets)
|
437
|
+
The data to fit.
|
438
|
+
|
439
|
+
y : array-like of shape (n_observations, n_targets), optional
|
440
|
+
The target array if provided.
|
441
|
+
|
442
|
+
indices : ndarray of int, optional
|
443
|
+
Rows or columns to select from X and y.
|
444
|
+
The default (`None`) is to select the entire data.
|
445
|
+
|
446
|
+
axis : int, default=0
|
447
|
+
The axis along which `X` will be sub-sampled. `axis=0` will select
|
448
|
+
rows while `axis=1` will select columns.
|
449
|
+
|
450
|
+
Returns
|
451
|
+
-------
|
452
|
+
fitted_estimator : estimator
|
453
|
+
The fitted estimator.
|
454
|
+
"""
|
455
|
+
|
456
|
+
X, y = safe_split(X, y, indices=indices, axis=axis)
|
457
|
+
estimator.fit(X, y)
|
458
|
+
return estimator
|
459
|
+
|
460
|
+
|
461
|
+
def fit_and_predict(
|
462
|
+
estimator: any,
|
463
|
+
X: npt.ArrayLike,
|
464
|
+
y: npt.ArrayLike | None,
|
465
|
+
train: np.ndarray,
|
466
|
+
test: np.ndarray | list[np.ndarray],
|
467
|
+
fit_params: dict,
|
468
|
+
method: str,
|
469
|
+
column_indices: np.ndarray | None = None,
|
470
|
+
) -> npt.ArrayLike | list[npt.ArrayLike]:
|
471
|
+
"""Fit the estimator and predict values for a given dataset split.
|
472
|
+
|
473
|
+
Parameters
|
474
|
+
----------
|
475
|
+
estimator : estimator object implementing 'fit' and 'predict'
|
476
|
+
The object to use to fit the data.
|
477
|
+
|
478
|
+
X : array-like of shape (n_observations, n_assets)
|
479
|
+
The data to fit.
|
480
|
+
|
481
|
+
y : array-like of shape (n_observations, n_factors) or None
|
482
|
+
The factor array if provided
|
483
|
+
|
484
|
+
train : ndarray of int of shape (n_train_observations,)
|
485
|
+
Indices of training samples.
|
486
|
+
|
487
|
+
test : ndarray of int of shape (n_test_samples,) or list of ndarray
|
488
|
+
Indices of test samples or list of indices.
|
489
|
+
|
490
|
+
fit_params : dict
|
491
|
+
Parameters that will be passed to ``estimator.fit``.
|
492
|
+
|
493
|
+
method : str
|
494
|
+
Invokes the passed method name of the passed estimator.
|
495
|
+
|
496
|
+
column_indices : ndarray, optional
|
497
|
+
Indices of columns to select.
|
498
|
+
The default (`None`) is to select all columns.
|
499
|
+
|
500
|
+
Returns
|
501
|
+
-------
|
502
|
+
predictions : array-like or list of array-like
|
503
|
+
If `test` is an array, it returns the array-like result of calling
|
504
|
+
'estimator.method' on `test`.
|
505
|
+
Otherwise, if `test` is a list of arrays, it returns the list of array-like
|
506
|
+
results of calling 'estimator.method' on each test set in `test`.
|
507
|
+
"""
|
508
|
+
fit_params = fit_params if fit_params is not None else {}
|
509
|
+
X, y = safe_split(X, y, indices=column_indices, axis=1)
|
510
|
+
X_train, y_train = safe_split(X, y, indices=train, axis=0)
|
511
|
+
if y_train is None:
|
512
|
+
estimator.fit(X_train, **fit_params)
|
513
|
+
else:
|
514
|
+
estimator.fit(X_train, y_train, **fit_params)
|
515
|
+
func = getattr(estimator, method)
|
516
|
+
|
517
|
+
if isinstance(test, list):
|
518
|
+
predictions = []
|
519
|
+
for t in test:
|
520
|
+
X_test, _ = safe_split(X, indices=t, axis=0)
|
521
|
+
predictions.append(func(X_test))
|
522
|
+
else:
|
523
|
+
X_test, _ = safe_split(X, indices=test, axis=0)
|
524
|
+
predictions = func(X_test)
|
525
|
+
|
526
|
+
return predictions
|
527
|
+
|
528
|
+
|
529
|
+
def default_asset_names(n_assets: int) -> np.ndarray:
|
530
|
+
"""Default asset names are `["x0", "x1", ..., "x(n_assets - 1)"]`
|
531
|
+
|
532
|
+
Parameters
|
533
|
+
----------
|
534
|
+
n_assets : int
|
535
|
+
Number of assets.
|
536
|
+
|
537
|
+
Returns
|
538
|
+
-------
|
539
|
+
asset_names : ndarray of str
|
540
|
+
Default assets names.
|
541
|
+
"""
|
542
|
+
return np.asarray([f"x{i}" for i in range(n_assets)], dtype=object)
|
543
|
+
|
544
|
+
|
545
|
+
def deduplicate_names(names: npt.ArrayLike) -> list[str]:
|
546
|
+
"""Rename duplicated names by appending "_{duplicate_nb}" at the end.
|
547
|
+
|
548
|
+
This function is inspired by the pandas function `_maybe_dedup_names`.
|
549
|
+
|
550
|
+
Parameters
|
551
|
+
----------
|
552
|
+
names : array-like of shape (n_names,)
|
553
|
+
List of names.
|
554
|
+
|
555
|
+
Returns
|
556
|
+
-------
|
557
|
+
names : list[str]
|
558
|
+
Deduplicate names.
|
559
|
+
"""
|
560
|
+
names = list(names)
|
561
|
+
counts = {}
|
562
|
+
for i, col in enumerate(names):
|
563
|
+
cur_count = counts.get(col, 0)
|
564
|
+
if cur_count > 0:
|
565
|
+
names[i] = f"{col}_{cur_count}"
|
566
|
+
counts[col] = cur_count + 1
|
567
|
+
return names
|
@@ -0,0 +1,29 @@
|
|
1
|
+
BSD 3-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2007-2023 The skfolio developers.
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
10
|
+
list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
17
|
+
contributors may be used to endorse or promote products derived from
|
18
|
+
this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|