tsfel 0.1.7__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.
- tests/__init__.py +0 -0
- tests/benchmark.py +160 -0
- tests/complexity_measures_intuition.py +93 -0
- tests/datasets/__init__.py +4 -0
- tests/datasets/empirical1000_dataset.py +188 -0
- tests/performance_tests_acf.py +46 -0
- tests/test_calc_features.py +151 -0
- tests/test_features.py +2204 -0
- tests/test_features_settings.py +29 -0
- tests/tests_tools/__init__.py +0 -0
- tests/tests_tools/test_personal_features.py +76 -0
- tsfel/__init__.py +3 -0
- tsfel/constants.py +1 -0
- tsfel/feature_extraction/__init__.py +4 -0
- tsfel/feature_extraction/calc_features.py +540 -0
- tsfel/feature_extraction/features.json +666 -0
- tsfel/feature_extraction/features.py +1975 -0
- tsfel/feature_extraction/features_settings.py +139 -0
- tsfel/feature_extraction/features_utils.py +616 -0
- tsfel/utils/__init__.py +5 -0
- tsfel/utils/add_personal_features.py +113 -0
- tsfel/utils/calculate_complexity.py +156 -0
- tsfel/utils/progress_bar.py +108 -0
- tsfel/utils/signal_processing.py +97 -0
- tsfel-0.1.7.dist-info/AUTHORS.rst +48 -0
- tsfel-0.1.7.dist-info/LICENSE.txt +29 -0
- tsfel-0.1.7.dist-info/METADATA +194 -0
- tsfel-0.1.7.dist-info/RECORD +30 -0
- tsfel-0.1.7.dist-info/WHEEL +5 -0
- tsfel-0.1.7.dist-info/top_level.txt +2 -0
tests/__init__.py
ADDED
|
File without changes
|
tests/benchmark.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import seaborn as sns
|
|
6
|
+
from datasets.empirical1000_dataset import Empirical1000Dataset
|
|
7
|
+
|
|
8
|
+
import tsfel
|
|
9
|
+
|
|
10
|
+
DOMAINS = [
|
|
11
|
+
"statistical",
|
|
12
|
+
"temporal",
|
|
13
|
+
"spectral",
|
|
14
|
+
] # It specifies the domains included in the analysis.
|
|
15
|
+
|
|
16
|
+
emp_dataset = Empirical1000Dataset()
|
|
17
|
+
|
|
18
|
+
#########################################################################################################
|
|
19
|
+
# Experiment 1 - Overall executing time benchmarking per domain #
|
|
20
|
+
#########################################################################################################
|
|
21
|
+
execution_times_global = pd.DataFrame(
|
|
22
|
+
{
|
|
23
|
+
"Domain": pd.Series(dtype="str"),
|
|
24
|
+
"Execution Time (s)": pd.Series(dtype="int"),
|
|
25
|
+
"Normalized Execution Time (s/feature)": pd.Series(dtype="float32"),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
for domain in DOMAINS:
|
|
30
|
+
n_features_domain = len(tsfel.get_features_by_domain(domain)[domain])
|
|
31
|
+
|
|
32
|
+
init_time = time.perf_counter()
|
|
33
|
+
for _id, ts in emp_dataset.raw.items():
|
|
34
|
+
_ = tsfel.time_series_features_extractor(
|
|
35
|
+
tsfel.get_features_by_domain(domain),
|
|
36
|
+
ts.sig,
|
|
37
|
+
fs=100,
|
|
38
|
+
verbose=False,
|
|
39
|
+
)
|
|
40
|
+
end_time = time.perf_counter()
|
|
41
|
+
total_time = end_time - init_time
|
|
42
|
+
|
|
43
|
+
execution_times_global = pd.concat(
|
|
44
|
+
[
|
|
45
|
+
execution_times_global,
|
|
46
|
+
pd.DataFrame(
|
|
47
|
+
{
|
|
48
|
+
"Domain": [domain],
|
|
49
|
+
"Execution Time (s)": [int(total_time)],
|
|
50
|
+
"Normalized Execution Time (s/feature)": [
|
|
51
|
+
total_time / n_features_domain,
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
ignore_index=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
#########################################################################################################
|
|
60
|
+
# Experiment 2 - Detailed time benchmarking as function of time series length grouped by feature domain #
|
|
61
|
+
#########################################################################################################
|
|
62
|
+
execution_times_individual = pd.DataFrame(
|
|
63
|
+
{
|
|
64
|
+
"Domain": pd.Series(dtype="str"),
|
|
65
|
+
"ID": pd.Series(dtype="int"),
|
|
66
|
+
"Execution Time (s)": pd.Series(dtype="float32"),
|
|
67
|
+
"Length": pd.Series(dtype="int"),
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
for domain in ["statistical", "temporal", "spectral"]:
|
|
72
|
+
for id, ts in emp_dataset.raw.items():
|
|
73
|
+
init_time = time.perf_counter()
|
|
74
|
+
_ = tsfel.time_series_features_extractor(
|
|
75
|
+
tsfel.get_features_by_domain(domain),
|
|
76
|
+
ts.sig,
|
|
77
|
+
fs=100,
|
|
78
|
+
verbose=False,
|
|
79
|
+
)
|
|
80
|
+
end_time = time.perf_counter()
|
|
81
|
+
total_time = end_time - init_time
|
|
82
|
+
|
|
83
|
+
execution_times_individual = pd.concat(
|
|
84
|
+
[
|
|
85
|
+
execution_times_individual,
|
|
86
|
+
pd.DataFrame(
|
|
87
|
+
{
|
|
88
|
+
"Domain": [domain],
|
|
89
|
+
"ID": [id],
|
|
90
|
+
"Execution Time (s)": [total_time],
|
|
91
|
+
"Length": [ts.len],
|
|
92
|
+
},
|
|
93
|
+
),
|
|
94
|
+
],
|
|
95
|
+
ignore_index=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Some lengths have a low number of time series for statistical analysis (< 25 samples). We only consider lengths that
|
|
99
|
+
# have at least 25 samples for the analysis. For each length, we calculate the average and standard deviation and plot
|
|
100
|
+
# them on normal and log-log scales.
|
|
101
|
+
length_counts = execution_times_individual["Length"].value_counts()
|
|
102
|
+
valid_lengths = length_counts[length_counts >= 25].index
|
|
103
|
+
filtered_df = execution_times_individual[execution_times_individual["Length"].isin(valid_lengths)]
|
|
104
|
+
grouped_stats = filtered_df.groupby(["Length", "Domain"])["Execution Time (s)"].agg(["mean", "std"]).reset_index()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
fig, (ax0, ax1) = plt.subplots(ncols=2)
|
|
108
|
+
for domain, marker in zip(DOMAINS, ["o", "v", "^"]):
|
|
109
|
+
_data_slice = grouped_stats[grouped_stats["Domain"] == domain]
|
|
110
|
+
ax0.errorbar(
|
|
111
|
+
_data_slice["Length"],
|
|
112
|
+
_data_slice["mean"],
|
|
113
|
+
yerr=_data_slice["std"],
|
|
114
|
+
label=domain,
|
|
115
|
+
marker=marker,
|
|
116
|
+
capsize=4,
|
|
117
|
+
)
|
|
118
|
+
ax1.errorbar(
|
|
119
|
+
_data_slice["Length"],
|
|
120
|
+
_data_slice["mean"],
|
|
121
|
+
yerr=_data_slice["std"],
|
|
122
|
+
label=domain,
|
|
123
|
+
marker=marker,
|
|
124
|
+
capsize=4,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
[ax.set(xlabel="Length (#)", ylabel="Execution Time (s)") for ax in [ax0, ax1]]
|
|
128
|
+
[ax.spines[loc].set_visible(False) for ax in [ax0, ax1] for loc in ["top", "right"]]
|
|
129
|
+
[ax.legend() for ax in [ax0, ax1]]
|
|
130
|
+
ax1.set_xscale("log")
|
|
131
|
+
ax1.set_yscale("log")
|
|
132
|
+
|
|
133
|
+
#########################################################################################################
|
|
134
|
+
# Experiment 4 - Measure the overall execution time per feature #
|
|
135
|
+
#########################################################################################################
|
|
136
|
+
execution_times_feature = pd.DataFrame(columns=["Domain", "Feature_Name", "Execution_Time"])
|
|
137
|
+
cfg = tsfel.get_features_by_domain()
|
|
138
|
+
for domain in DOMAINS:
|
|
139
|
+
for feature in cfg[domain]:
|
|
140
|
+
print(domain, feature)
|
|
141
|
+
init_time = time.perf_counter()
|
|
142
|
+
for _, ts in emp_dataset.raw.items():
|
|
143
|
+
_ = tsfel.time_series_features_extractor(
|
|
144
|
+
{domain: {feature: cfg[domain][feature]}},
|
|
145
|
+
ts.sig,
|
|
146
|
+
fs=100,
|
|
147
|
+
verbose=False,
|
|
148
|
+
)
|
|
149
|
+
execution_time = time.perf_counter() - init_time
|
|
150
|
+
|
|
151
|
+
execution_times_feature.loc[len(execution_times_feature)] = {
|
|
152
|
+
"Domain": domain,
|
|
153
|
+
"Feature_Name": feature,
|
|
154
|
+
"Execution_Time": execution_time,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fig, ax = plt.subplots(1, 1)
|
|
158
|
+
sns.barplot(execution_times_feature, x="Feature_Name", y="Execution_Time", hue="Domain", ax=ax)
|
|
159
|
+
ax.tick_params(axis="x", rotation=90)
|
|
160
|
+
plt.show(block=False)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""This module offers helper functions designed to facilitate testing and
|
|
2
|
+
enhance understanding of features that quantify repetitiveness in dynamic
|
|
3
|
+
systems."""
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import neurokit2 as nk
|
|
7
|
+
import numpy as np
|
|
8
|
+
from statsmodels.graphics.tsaplots import plot_acf
|
|
9
|
+
from statsmodels.tsa.stattools import acf
|
|
10
|
+
|
|
11
|
+
from tests.tests_tools.test_data.complexity_datasets import load_complexities_datasets
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def plot_dfa(info, scale, fluctuations, ax):
|
|
15
|
+
"""Plot log-log visualization of the detrended fluctuation analysis
|
|
16
|
+
(DFA)."""
|
|
17
|
+
|
|
18
|
+
polyfit = np.polyfit(np.log2(scale), np.log2(fluctuations), 1)
|
|
19
|
+
fluctfit = 2 ** np.polyval(polyfit, np.log2(scale))
|
|
20
|
+
ax.loglog(scale, fluctuations, "o", c="#90A4AE")
|
|
21
|
+
ax.loglog(
|
|
22
|
+
scale,
|
|
23
|
+
fluctfit,
|
|
24
|
+
c="#E91E63",
|
|
25
|
+
label=r"$\alpha$ = {:.3f}".format(info["Alpha"]),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return ax
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def plot_multiscale_entropy(info, ax):
|
|
32
|
+
"""Plots the entropy values for each scale factor."""
|
|
33
|
+
|
|
34
|
+
ax.plot(
|
|
35
|
+
info["Scale"][np.isfinite(info["Value"])],
|
|
36
|
+
info["Value"][np.isfinite(info["Value"])],
|
|
37
|
+
color="#FF9800",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return ax
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_first_acf_time_constant(signal):
|
|
44
|
+
"""Captures the approximate scale of the autocorrelation function by
|
|
45
|
+
measuring the first time lag at which the autocorrelation function (acf)
|
|
46
|
+
drops below 1/e = 0.3679."""
|
|
47
|
+
n = signal.size
|
|
48
|
+
maxlag = int(n / 4)
|
|
49
|
+
threshold = 0.36787944117144233 # 1 / np.exp(1)
|
|
50
|
+
|
|
51
|
+
acf_vec = acf(signal, adjusted=True, fft=n > 400, nlags=maxlag)[
|
|
52
|
+
1:
|
|
53
|
+
] # n > 400 empirically selected based on performance tests
|
|
54
|
+
idxs = np.where(acf_vec < threshold)[0]
|
|
55
|
+
first_lag = idxs[0] + 1 if idxs.size > 0 else maxlag
|
|
56
|
+
|
|
57
|
+
return first_lag
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
dataset = load_complexities_datasets()
|
|
61
|
+
|
|
62
|
+
f, ax = plt.subplots(4, len(dataset), figsize=(15, 5))
|
|
63
|
+
for i, (k, _v) in enumerate(dataset.items()):
|
|
64
|
+
signal = dataset[k]
|
|
65
|
+
|
|
66
|
+
# First row (raw data)
|
|
67
|
+
ax[0, i].plot(signal, "k")
|
|
68
|
+
ax[0, i].set_title(k)
|
|
69
|
+
ax[0, i].spines[["left", "top", "right"]].set_visible(False)
|
|
70
|
+
ax[0, i].tick_params(left=False, right=False, labelleft=False)
|
|
71
|
+
|
|
72
|
+
# Second row (first_acf_time_constant)
|
|
73
|
+
plot_acf(signal, ax[1, i], adjusted=True, fft=len(signal) > 1250, title="")
|
|
74
|
+
ax[1, i].axhline(y=(1 / np.e), color="r", linestyle="--")
|
|
75
|
+
ax[1, i].spines[["top", "right"]].set_visible(False)
|
|
76
|
+
ax[1, i].set_title("$\\tau = %d$" % get_first_acf_time_constant(signal))
|
|
77
|
+
|
|
78
|
+
# Third row (dfa)
|
|
79
|
+
# TODO: Change to our own implementation instead of relying on neurokit2
|
|
80
|
+
_, info = nk.fractal_dfa(signal)
|
|
81
|
+
plot_dfa(info, info["scale"], info["Fluctuations"], ax[2, i])
|
|
82
|
+
ax[2, i].set_title("$\\alpha = %0.2f$" % info["Alpha"])
|
|
83
|
+
ax[2, i].set_axis_off()
|
|
84
|
+
|
|
85
|
+
# Forth row (mse)
|
|
86
|
+
mse, info = nk.entropy_multiscale(signal)
|
|
87
|
+
plot_multiscale_entropy(info, ax[3, i])
|
|
88
|
+
ax[3, i].set_title("mse = %0.2f" % mse)
|
|
89
|
+
ax[3, i].set_ylim([0, 2.5])
|
|
90
|
+
ax[3, i].spines[["top", "right"]].set_visible(False)
|
|
91
|
+
|
|
92
|
+
f.tight_layout()
|
|
93
|
+
plt.show(block=False)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import zipfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import requests
|
|
10
|
+
import scipy.io as sio
|
|
11
|
+
from tqdm import tqdm
|
|
12
|
+
|
|
13
|
+
module_path = os.path.dirname(__file__)
|
|
14
|
+
data_path = os.path.join(module_path, "cached_datasets")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Empirical1000Dataset:
|
|
18
|
+
"""A convenience class to access the Empirical1000 dataset.
|
|
19
|
+
|
|
20
|
+
When using one this dataset, please cite [1]_.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
use_cache : bool (default: True)
|
|
25
|
+
Whether a cached version of the dataset should be used, if one is found.
|
|
26
|
+
The dataset is always cached upon loading, and this parameter only determines
|
|
27
|
+
whether the cached version shall be refreshed upon loading.
|
|
28
|
+
|
|
29
|
+
extract_features : bool (default: False)
|
|
30
|
+
Whether a feature extraction routine should be called after loading the data files.
|
|
31
|
+
TODO: Implement this functionality.
|
|
32
|
+
|
|
33
|
+
Notes
|
|
34
|
+
-----
|
|
35
|
+
To optimize running times it is recommended to use `use_cache=True` (default) in order
|
|
36
|
+
to only experience a time once downloading and work on a cached version of the dataset afterward.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
References
|
|
40
|
+
----------
|
|
41
|
+
.. [1] Fulcher, B. D., Lubba, C. H., Sethi, S. S., & Jones, N. S. (2020).
|
|
42
|
+
A self-organizing, living library of time-series data. Scientific data, 7(1), 213.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, use_cache=True, extract_features=False):
|
|
46
|
+
self.use_cache = use_cache
|
|
47
|
+
|
|
48
|
+
self.raw = {}
|
|
49
|
+
self.features = {} # An add-on to this class will contain a featurized data representation.
|
|
50
|
+
|
|
51
|
+
self.__dataset_url = "https://figshare.com/ndownloader/articles/5436136/versions/10"
|
|
52
|
+
self.cache_folder_path = data_path
|
|
53
|
+
self.__empirical1000_folder_path = os.path.join(
|
|
54
|
+
self.cache_folder_path,
|
|
55
|
+
"Empirical1000",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not os.path.exists(self.cache_folder_path) or not os.listdir(self.cache_folder_path) or not use_cache:
|
|
59
|
+
print("Cache folder is empty. Downloading the Empirical1000 dataset...")
|
|
60
|
+
Path(os.path.join(self.cache_folder_path, "Empirical1000")).mkdir(
|
|
61
|
+
parents=True,
|
|
62
|
+
exist_ok=True,
|
|
63
|
+
)
|
|
64
|
+
self.__download_dataset()
|
|
65
|
+
|
|
66
|
+
self.__data_files = sio.loadmat(
|
|
67
|
+
os.path.join(self.__empirical1000_folder_path, "INP_1000ts.mat"),
|
|
68
|
+
)
|
|
69
|
+
self.metadata = pd.read_csv(
|
|
70
|
+
os.path.join(self.__empirical1000_folder_path, "hctsa_timeseries-info.csv"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
for i, name in enumerate(self.metadata["Name"]):
|
|
74
|
+
self.raw[i] = EmpiricalTimeSeries(
|
|
75
|
+
name,
|
|
76
|
+
self.__data_files["timeSeriesData"][0][i].flatten(),
|
|
77
|
+
)
|
|
78
|
+
if extract_features:
|
|
79
|
+
tqdm_iterator = tqdm(
|
|
80
|
+
total=len(self.metadata["Name"]),
|
|
81
|
+
desc="Extracting features.",
|
|
82
|
+
)
|
|
83
|
+
tqdm_iterator.update(1)
|
|
84
|
+
|
|
85
|
+
def __download_dataset(self):
|
|
86
|
+
try:
|
|
87
|
+
# Send a request to the dataset URL with allow_redirects=False to handle redirection
|
|
88
|
+
response = requests.get(
|
|
89
|
+
self.__dataset_url,
|
|
90
|
+
stream=True,
|
|
91
|
+
allow_redirects=False,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Check if the request was successful (status code 200)
|
|
95
|
+
if response.status_code == 200:
|
|
96
|
+
total_size = int(response.headers.get("content-length", 0)) or None
|
|
97
|
+
filename = self.__extract_filename(response)
|
|
98
|
+
|
|
99
|
+
with tqdm(
|
|
100
|
+
total=total_size,
|
|
101
|
+
unit="B",
|
|
102
|
+
unit_scale=True,
|
|
103
|
+
desc="Downloading Empirical1000 dataset",
|
|
104
|
+
dynamic_ncols=True,
|
|
105
|
+
) as pbar:
|
|
106
|
+
with open(
|
|
107
|
+
os.path.join(self.cache_folder_path, filename),
|
|
108
|
+
"wb",
|
|
109
|
+
) as out_file:
|
|
110
|
+
shutil.copyfileobj(response.raw, out_file)
|
|
111
|
+
pbar.update(os.path.getsize(out_file.name))
|
|
112
|
+
|
|
113
|
+
zip_file_path = os.path.join(self.cache_folder_path, filename)
|
|
114
|
+
with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
|
|
115
|
+
zip_ref.extractall(self.__empirical1000_folder_path)
|
|
116
|
+
|
|
117
|
+
# A sanitizing routine that deletes non-relevant files.
|
|
118
|
+
os.remove(zip_file_path)
|
|
119
|
+
extracted_files = os.listdir(self.__empirical1000_folder_path)
|
|
120
|
+
timeseries_files = [
|
|
121
|
+
file for file in extracted_files if "timeseries-info" in file.lower() or "inp" in file.lower()
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for file in extracted_files:
|
|
125
|
+
if file not in timeseries_files:
|
|
126
|
+
os.remove(os.path.join(self.__empirical1000_folder_path, file))
|
|
127
|
+
|
|
128
|
+
print(f"Dataset downloaded and saved to: {self.cache_folder_path}")
|
|
129
|
+
else:
|
|
130
|
+
print(
|
|
131
|
+
f"Failed to download dataset. Status code: {response.status_code}",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"An error occurred: {e}")
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def __extract_filename(response):
|
|
139
|
+
# Try to extract the filename from the Content-Disposition header.
|
|
140
|
+
# If the header is not present, extract the filename from the URL
|
|
141
|
+
content_disposition = response.headers.get("Content-Disposition")
|
|
142
|
+
if content_disposition and "filename=" in content_disposition:
|
|
143
|
+
filename = content_disposition.split("filename=")[1].strip("\";'")
|
|
144
|
+
else:
|
|
145
|
+
filename = os.path.basename(urlparse(response.url).path)
|
|
146
|
+
|
|
147
|
+
return filename
|
|
148
|
+
|
|
149
|
+
def get_by_len(self, l):
|
|
150
|
+
query = np.nonzero(self.metadata["Length"] == l)[0]
|
|
151
|
+
filtered_data = {key: self.raw[key] for key in query if key in self.raw}
|
|
152
|
+
|
|
153
|
+
return filtered_data
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class EmpiricalTimeSeries:
|
|
157
|
+
"""The object representing a time series from the Empirical1000 dataset.
|
|
158
|
+
|
|
159
|
+
Attributes:
|
|
160
|
+
name (str): The name of the time series.
|
|
161
|
+
sig (array-like): The time series data.
|
|
162
|
+
len (int): The length of the time series.
|
|
163
|
+
|
|
164
|
+
Methods:
|
|
165
|
+
__init__(self, name, sig):
|
|
166
|
+
Initializes a new EmpiricalTimeSeries object.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, name, sig):
|
|
170
|
+
"""Initializes a new EmpiricalTimeSeries object.
|
|
171
|
+
|
|
172
|
+
Parameters:
|
|
173
|
+
name (str): The name or identifier of the time series.
|
|
174
|
+
sig (list or array-like): The time series data.
|
|
175
|
+
"""
|
|
176
|
+
self.name = name
|
|
177
|
+
self.sig = sig
|
|
178
|
+
self.len = len(sig)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class FeaturizedEmpiricalTimeSeries(EmpiricalTimeSeries):
|
|
182
|
+
def __init__(self, empirical_time_series):
|
|
183
|
+
super().__init__(empirical_time_series.name, empirical_time_series.sig)
|
|
184
|
+
self.example_feature = self.get_feature()
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def get_feature(self):
|
|
188
|
+
return -1
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import timeit
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
6
|
+
from statsmodels.tsa.stattools import acf
|
|
7
|
+
|
|
8
|
+
use_fft = [False, True]
|
|
9
|
+
signal_lengths = np.hstack((np.arange(15, 515, 15), np.arange(500, 5500, 500)))
|
|
10
|
+
results = [[], []]
|
|
11
|
+
|
|
12
|
+
for i, use_fft in enumerate([False, True]):
|
|
13
|
+
for length in signal_lengths:
|
|
14
|
+
signal = np.random.randn(length)
|
|
15
|
+
|
|
16
|
+
def measure_acf(signal, use_fft):
|
|
17
|
+
return acf(signal, adjusted=True, fft=use_fft, nlags=int(len(signal) / 4))[1:]
|
|
18
|
+
|
|
19
|
+
cpu_time = timeit.timeit("measure_acf()", globals=globals(), number=10)
|
|
20
|
+
mean_cpu_time = cpu_time / 10 # Average time per execution
|
|
21
|
+
|
|
22
|
+
results[i].append(mean_cpu_time * 1000)
|
|
23
|
+
results = np.array(results)
|
|
24
|
+
|
|
25
|
+
fig, ax = plt.subplots(1, 1)
|
|
26
|
+
ax.plot(signal_lengths, results[0], "o-", label="full convolution")
|
|
27
|
+
ax.plot(signal_lengths, results[1], "o-", label="fft")
|
|
28
|
+
ax.set_ylabel("CPU time / ms")
|
|
29
|
+
ax.set_xlabel("Signal size / #")
|
|
30
|
+
ax.spines[["top", "right"]].set_visible(False)
|
|
31
|
+
ax.legend()
|
|
32
|
+
|
|
33
|
+
ax_inset = inset_axes(ax, width="30%", height="30%", loc=1) # loc=2 is for upper left
|
|
34
|
+
ax_inset.plot(signal_lengths, results[0], "o-", label="full convolution")
|
|
35
|
+
ax_inset.plot(signal_lengths, results[1], "o-", label="FFT")
|
|
36
|
+
ax_inset.set_xlim(0, 500)
|
|
37
|
+
ax_inset.set_ylim(0, 0.10)
|
|
38
|
+
ax_inset.yaxis.set_visible(False)
|
|
39
|
+
ax_inset.yaxis.set_ticks([])
|
|
40
|
+
ax_inset.axvline(
|
|
41
|
+
x=signal_lengths[np.where(results[0] > results[1])[0][0]],
|
|
42
|
+
color="r",
|
|
43
|
+
linestyle="--",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
plt.show(block=False)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# import tsfel
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from tsfel.feature_extraction.calc_features import dataset_features_extractor, time_series_features_extractor
|
|
10
|
+
from tsfel.feature_extraction.features_settings import get_features_by_domain, get_features_by_tag
|
|
11
|
+
from tsfel.utils.add_personal_features import add_feature_json
|
|
12
|
+
from tsfel.utils.signal_processing import merge_time_series, signal_window_splitter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Example of user preprocess sensor data
|
|
16
|
+
def pre_process(sensor_data):
|
|
17
|
+
if "Accelerometer" in sensor_data:
|
|
18
|
+
sensor_data["Accelerometer"].iloc[:, 1] = sensor_data["Accelerometer"].iloc[:, 1] * 10
|
|
19
|
+
return sensor_data
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# DATASET DIR
|
|
23
|
+
main_directory = os.path.join("tests", "tests_tools", "test_dataset", "")
|
|
24
|
+
|
|
25
|
+
# JSON DIR
|
|
26
|
+
# tsfel_path_json = tsfel.__path__[0] + os.sep + "feature_extraction" + os.sep + "features.json"
|
|
27
|
+
personal_path_json = os.path.join("tests", "tests_tools", "test_features.json")
|
|
28
|
+
personal_features_path = os.path.join(
|
|
29
|
+
"tests",
|
|
30
|
+
"tests_tools",
|
|
31
|
+
"test_personal_features.py",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# DEFAULT PARAM for testing
|
|
35
|
+
time_unit = 1e9 # seconds
|
|
36
|
+
resample_rate = 30 # resample sampling frequency
|
|
37
|
+
window_size = 100 # number of points
|
|
38
|
+
overlap = 0 # varies between 0 and 1
|
|
39
|
+
search_criteria = ["Accelerometer.txt", "Gyroscope.txt"]
|
|
40
|
+
output_directory = str(Path.home()) + os.sep + "Documents" + os.sep + "tsfel_output" + os.sep
|
|
41
|
+
sensor_data = {}
|
|
42
|
+
key = "Accelerometer"
|
|
43
|
+
|
|
44
|
+
folders = list(glob.glob(main_directory + "**/", recursive=True))
|
|
45
|
+
sensor_data[key] = pd.read_csv(folders[-1] + key + ".txt", header=None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Features Dictionary
|
|
49
|
+
# settings0 = json.load(open(tsfel_path_json))
|
|
50
|
+
settings2 = get_features_by_domain("statistical")
|
|
51
|
+
settings3 = get_features_by_domain("temporal")
|
|
52
|
+
settings4 = get_features_by_domain("spectral")
|
|
53
|
+
settings5 = get_features_by_domain("fractal")
|
|
54
|
+
settings6 = get_features_by_domain()
|
|
55
|
+
settings8 = get_features_by_tag("inertial")
|
|
56
|
+
settings10 = get_features_by_tag()
|
|
57
|
+
|
|
58
|
+
# Signal processing
|
|
59
|
+
data_new = merge_time_series(sensor_data, resample_rate, time_unit)
|
|
60
|
+
data_new = data_new[data_new.columns[:-1]]
|
|
61
|
+
windows = signal_window_splitter(data_new, window_size, overlap)
|
|
62
|
+
|
|
63
|
+
n_jobs = 1
|
|
64
|
+
# time_series_features_extractor
|
|
65
|
+
|
|
66
|
+
# multi windows and multi axis
|
|
67
|
+
# input: list
|
|
68
|
+
features0 = time_series_features_extractor(
|
|
69
|
+
settings6,
|
|
70
|
+
windows,
|
|
71
|
+
fs=resample_rate,
|
|
72
|
+
n_jobs=n_jobs,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# multiple windows and single axis
|
|
76
|
+
# input: np.array
|
|
77
|
+
features1 = time_series_features_extractor(
|
|
78
|
+
settings6,
|
|
79
|
+
data_new.values[:, 0],
|
|
80
|
+
fs=resample_rate,
|
|
81
|
+
n_jobs=n_jobs,
|
|
82
|
+
window_size=window_size,
|
|
83
|
+
overlap=overlap,
|
|
84
|
+
)
|
|
85
|
+
# input: pd.series
|
|
86
|
+
features2 = time_series_features_extractor(
|
|
87
|
+
settings6,
|
|
88
|
+
data_new.iloc[:, 0],
|
|
89
|
+
fs=resample_rate,
|
|
90
|
+
n_jobs=n_jobs,
|
|
91
|
+
window_size=window_size,
|
|
92
|
+
overlap=overlap,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# single window and multi axis
|
|
96
|
+
# input: pd.DataFrame
|
|
97
|
+
features3 = time_series_features_extractor(
|
|
98
|
+
settings6,
|
|
99
|
+
data_new,
|
|
100
|
+
fs=resample_rate,
|
|
101
|
+
n_jobs=n_jobs,
|
|
102
|
+
)
|
|
103
|
+
# input: np.array
|
|
104
|
+
features4 = time_series_features_extractor(
|
|
105
|
+
settings4,
|
|
106
|
+
data_new.values,
|
|
107
|
+
fs=resample_rate,
|
|
108
|
+
n_jobs=n_jobs,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# single window and single axis
|
|
112
|
+
# input: pd.Series
|
|
113
|
+
features5 = time_series_features_extractor(
|
|
114
|
+
settings2,
|
|
115
|
+
data_new.iloc[:, 0],
|
|
116
|
+
fs=resample_rate,
|
|
117
|
+
n_jobs=n_jobs,
|
|
118
|
+
)
|
|
119
|
+
# input: np.array
|
|
120
|
+
features6 = time_series_features_extractor(
|
|
121
|
+
settings5,
|
|
122
|
+
data_new.values[:, 0],
|
|
123
|
+
fs=resample_rate,
|
|
124
|
+
n_jobs=n_jobs,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# personal features
|
|
128
|
+
settings1 = json.load(open(personal_path_json))
|
|
129
|
+
add_feature_json(personal_features_path, personal_path_json)
|
|
130
|
+
features7 = time_series_features_extractor(
|
|
131
|
+
settings1,
|
|
132
|
+
data_new.values[:, 0],
|
|
133
|
+
fs=resample_rate,
|
|
134
|
+
n_jobs=n_jobs,
|
|
135
|
+
features_path=personal_features_path,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Dataset features extractor
|
|
140
|
+
data = dataset_features_extractor(
|
|
141
|
+
main_directory,
|
|
142
|
+
settings4,
|
|
143
|
+
search_criteria=search_criteria,
|
|
144
|
+
time_unit=time_unit,
|
|
145
|
+
resample_rate=resample_rate,
|
|
146
|
+
window_size=window_size,
|
|
147
|
+
overlap=overlap,
|
|
148
|
+
pre_process=pre_process,
|
|
149
|
+
output_directory=output_directory,
|
|
150
|
+
)
|
|
151
|
+
print("-----------------------------------OK-----------------------------------")
|