Skip to content

Commit

Permalink
Merge pull request #44 from predict-idlab/feature_integrations
Browse files Browse the repository at this point in the history
✨ fully support & test seglearn, tsfel, and tsfresh features
  • Loading branch information
jonasvdd authored Nov 16, 2021
2 parents c99265e + 3f116df commit edeb537
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 46 deletions.
70 changes: 35 additions & 35 deletions tests/test_features_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
)
from tsflex.features.integrations import (
seglearn_wrapper,
seglearn_feature_dict_wrapper,
tsfel_feature_dict_wrapper,
tsfresh_combiner_wrapper,
tsfresh_settings_wrapper,
)
Expand All @@ -41,21 +43,17 @@ def test_seglearn_basic_features(dummy_data):
assert res_df.isna().any().any() == False


def test_seglearn_all_features(dummy_data):
def test_seglearn_feature_dict_wrapper(dummy_data):
# Tests if we integrate with ALL seglearn features
all_features = seglearn.feature_functions.all_features

all_feats = MultipleFeatureDescriptors(
functions=[
seglearn_wrapper(f, k) for k, f in all_features().items() if k != "hist4"
]
+ [
seglearn_wrapper(all_features()["hist4"], [f"hist{i}" for i in range(1, 5)])
],
all_seglearn_feats = MultipleFeatureDescriptors(
functions=seglearn_feature_dict_wrapper(all_features()),
series_names=["TMP", "EDA"],
windows="5min",
strides="2min",
strides="5min",
)
feature_collection = FeatureCollection(all_feats)
feature_collection = FeatureCollection(all_seglearn_feats)

res_df = feature_collection.calculate(dummy_data, return_df=True)
assert res_df.shape[1] == (len(all_features()) - 1 + 4) * 2
Expand Down Expand Up @@ -121,7 +119,7 @@ def test_tsfresh_combiner_features(dummy_data):
],
series_names=["ACC_x", "EDA"],
windows="5min",
strides="2min",
strides="5min",
)
feature_collection = FeatureCollection(combiner_feats)

Expand All @@ -130,38 +128,24 @@ def test_tsfresh_combiner_features(dummy_data):
assert res_df.shape[0] > 0
assert res_df.isna().any().any() == False


def test_tsfresh_settings_wrapper(dummy_data):
from tsfresh.feature_extraction.settings import EfficientFCParameters

slow_funcs = [
"matrix_profile",
"number_cwt_peaks",
"augmented_dickey_fuller",
"partial_autocorrelation",
"agg_linear_trend",
"lempel_ziv_complexity",
"benford_correlation",
"ar_coefficient",
"permutation_entropy",
"friedrich_coefficients",
]
# Tests if we integrate with ALL tsfresh features
from tsfresh.feature_extraction.settings import ComprehensiveFCParameters

settings = EfficientFCParameters()
for f in slow_funcs:
del settings[f]
settings = ComprehensiveFCParameters()

efficient_tsfresh_feats = MultipleFeatureDescriptors(
functions=tsfresh_settings_wrapper(settings),
series_names=["ACC_x", "EDA", "TMP"],
windows="30min", strides="15min",
series_names=["EDA", "TMP"],
windows="2.5min", strides="10min",
)
feature_collection = FeatureCollection(efficient_tsfresh_feats)

res_df = feature_collection.calculate(dummy_data, return_df=True)
res_df = feature_collection.calculate(dummy_data.first("15min"), return_df=True)
assert (res_df.shape[0] > 0) and (res_df.shape[1]) > 0



## TSFEL


Expand Down Expand Up @@ -206,7 +190,7 @@ def test_tsfel_basic_features(dummy_data):
functions=basic_funcs,
series_names=["ACC_x", "EDA"],
windows="5min",
strides="2min",
strides="5min",
)
feature_collection = FeatureCollection(basic_feats)

Expand Down Expand Up @@ -273,11 +257,27 @@ def test_tsfel_advanced_features(dummy_data):
],
series_names=["ACC_x", "EDA"],
windows="5min",
strides="2min",
strides="10min",
)
feature_collection = FeatureCollection(advanced_feats)

res_df = feature_collection.calculate(dummy_data, return_df=True)
res_df = feature_collection.calculate(dummy_data.first("15min"), return_df=True)
assert res_df.shape[1] == (5 + 4 + 10 + 2 + 8 + 6 + 8 + 9 + 9 + 4) * 2
assert res_df.shape[0] > 0
assert res_df.isna().any().any() == False


def test_tsfel_feature_dict_wrapper(dummy_data):
# Tests if we integrate with ALL tsfel features
from tsfel.feature_extraction import get_features_by_domain

all_tsfel_feats = MultipleFeatureDescriptors(
functions=tsfel_feature_dict_wrapper(get_features_by_domain()),
series_names=["TMP", "EDA"],
windows="5min",
strides="10min",
)
feature_collection = FeatureCollection(all_tsfel_feats)

res_df = feature_collection.calculate(dummy_data.first("15min"), return_df=True)
assert (res_df.shape[0] > 0) and (res_df.shape[1]) > 0
149 changes: 138 additions & 11 deletions tsflex/features/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


# ------------------------------------- SEGLEARN -------------------------------------
def seglearn_wrapper(func: Callable, output_names: Optional[str] = None) -> FuncWrapper:
def seglearn_wrapper(func: Callable, func_name: Optional[str] = None) -> FuncWrapper:
"""Wrapper enabling compatibility with seglearn functions.
As [seglearn feature-functions](https://github.com/dmbee/seglearn/blob/master/seglearn/feature_functions.py)
Expand All @@ -24,8 +24,9 @@ def seglearn_wrapper(func: Callable, output_names: Optional[str] = None) -> Func
----------
func: Callable
The seglearn function.
output_names: str, optional
The output name for the function its output.
func_name: str, optional
The name for the passed function. This will be used when constructing the output
names.
Returns
-------
Expand All @@ -38,10 +39,135 @@ def wrap_func(x: np.ndarray):
return out.flatten()

wrap_func.__name__ = "[seglearn_wrapped]__" + _get_name(func)
output_names = _get_name(func) if output_names is None else output_names
output_names = _get_name(func) if func_name is None else func_name
# A bit hacky (hard coded), bc hist is only func that returns multiple values
if hasattr(func, "bins"):
output_names = [output_names+f"_bin{idx}" for idx in range(1, func.bins+1)]
return FuncWrapper(wrap_func, output_names=output_names)


def seglearn_feature_dict_wrapper(features_dict: Dict) -> List[Callable]:
"""Wrapper enabling compatibility with seglearn feature dictionaries.
seglearn represents a collection of features as a dictionary.
By using this wrapper, we can plug in the features (that are present in the
dictionary) in a tsflex ``FeatureCollection``.
This enables to easily extract (a collection of) seglearn features while leveraging
the flexibility of tsflex.
.. Note::
This wrapper wraps the output of seglearn functions that return feature
dictionaries;
- [base_features()](https://dmbee.github.io/seglearn/feature_functions.html#seglearn.feature_functions.base_features)
- [emg_features()](https://dmbee.github.io/seglearn/feature_functions.html#seglearn.feature_functions.emg_features)
- [hudgins_features()](https://dmbee.github.io/seglearn/feature_functions.html#seglearn.feature_functions.hudgins_features)
- [all_features()](https://dmbee.github.io/seglearn/feature_functions.html#seglearn.feature_functions.all_features)
Example
-------
```python
from tsflex.features import FeatureCollection, MultipleFeatureDescriptors
from tsflex.features.integrations import seglearn_feature_dict_wrapper
from seglearn.feature_functions import base_features
basic_seglearn_feats = MultipleFeatureDescriptors(
functions=seglearn_feature_dict_wrapper(base_features()),
series_names=["sig_0", "sig_1"], # list of signal names
windows="15min", strides="2min",
)
fc = FeatureCollection(basic_seglearn_feats)
fc.calculate(data) # calculate the features on your data
```
Parameters
----------
features_dict: Dictionary
The seglearn collection of features (which is a dict).
Returns
-------
List[Callable]
List of the (wrapped) seglearn functions that are now directly compatible with
with tsflex.
"""
return [seglearn_wrapper(func) for func in features_dict.values()]


# -------------------------------------- TSFEL --------------------------------------
def tsfel_feature_dict_wrapper(features_dict: Dict) -> List[Callable]:
"""Wrapper enabling compatibility with tsfel feature extraction configurations.
tsfel represents a collection of features as a dictionary, see more [here](https://tsfel.readthedocs.io/en/latest/descriptions/get_started.html#set-up-the-feature-extraction-config-file).
By using this wrapper, we can plug in the features (that are present in the
tsfel feature extraction configuration) in a tsflex ``FeatureCollection``.
This enables to easily extract (a collection of) tsfel features while leveraging
the flexibility of tsflex.
.. Note::
This wrapper wraps the output of tsfel its `get_features_by_domain` or
`get_features_by_tag`. <br>
Se more [here](https://github.com/fraunhoferportugal/tsfel/blob/master/tsfel/feature_extraction/features_settings.py).
Example
-------
```python
from tsflex.features import FeatureCollection, MultipleFeatureDescriptors
from tsflex.features.integrations import tsfel_feature_dict_wrapper
from tsfel.feature_extraction import get_features_by_domain
stat_tsfel_feats = MultipleFeatureDescriptors(
functions=tsfel_feature_dict_wrapper(get_features_by_domain("statistical")),
series_names=["sig_0", "sig_1"], # list of signal names
windows="15min", strides="2min",
)
fc = FeatureCollection(stat_tsfel_feats)
fc.calculate(data) # calculate the features on your data
```
Parameters
----------
features_dict: Dictionary
The tsfel collection of features (which is a dict).
Returns
-------
List[Callable]
List of the (wrapped) tsfel functions that are now directly compatible with
with tsflex.
"""
def get_output_names(config: dict):
"""Create the output_names based on the configuration."""
nb_outputs = config["n_features"]
func_name = config["function"].split(".")[-1]
if isinstance(nb_outputs, str) and isinstance(config["parameters"][nb_outputs], int):
nb_outputs = config["parameters"][nb_outputs]
if func_name == "lpcc": # Because https://github.com/fraunhoferportugal/tsfel/issues/103
nb_outputs += 1
if isinstance(nb_outputs, int):
if nb_outputs == 1:
return func_name
else:
return [func_name+f"_{idx}" for idx in range(1,nb_outputs+1)]
output_param = eval(config["parameters"][nb_outputs])
return [func_name+f"_{nb_outputs}=v" for v in output_param]

functions = []
tsfel_mod = importlib.import_module("tsfel.feature_extraction")
for donain_feats in features_dict.values(): # Iterate over feature domains
for config in donain_feats.values(): # Iterate over function configs
func = getattr(tsfel_mod, config["function"].split(".")[-1])
params = config["parameters"] if config["parameters"] else {}
output_names = get_output_names(config)
functions.append(FuncWrapper(func, output_names, **params))
return functions


# ------------------------------------- TSFRESH -------------------------------------
def tsfresh_combiner_wrapper(func: Callable, param: List[Dict]) -> FuncWrapper:
"""Wrapper enabling compatibility with tsfresh combiner functions.
Expand Down Expand Up @@ -82,24 +208,25 @@ def wrap_func(x: Union[np.ndarray, pd.Series]):
)


# from tsfresh.feature_extraction.settings import PickeableSettings
# TODO: update this to PicklableSettings, once they approve my PR


def tsfresh_settings_wrapper(settings) -> List[Callable]:
def tsfresh_settings_wrapper(settings: Dict) -> List[Callable]:
"""Wrapper enabling compatibility with tsfresh feature extraction settings.
[tsfresh feature extraction settings](https://tsfresh.readthedocs.io/en/latest/text/feature_extraction_settings.html)
is how tsfresh represents a collection of features.<br>
is how tsfresh represents a collection of features (as a dict).<br>
By using this wrapper, we can plug in the features (that are present in the
tsfresh feature extraction settings) in a tsflex ``FeatureCollection``.
This enables to easily extract (a collection of) tsfresh features while leveraging
the flexibility of tsflex.
.. Note::
This wrapper wraps the output of tsfresh its `MinimalFCParameters()`,
`EfficientFCParameters()`, `IndexBasedFCParameters()`,
`TimeBasedFCParameters()`, or `ComprehensiveFCParameters()`. <br>
Se more [here](https://github.com/blue-yonder/tsfresh/blob/main/tsfresh/feature_extraction/settings.py).
Example
-------
```python
from tsflex.features import FeatureCollection, MultipleFeatureDescriptors
from tsflex.features.integrations import tsfresh_settings_wrapper
Expand Down

0 comments on commit edeb537

Please sign in to comment.