featuregraph-smoothing-core 1.0.0__tar.gz
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.
- featuregraph_smoothing_core-1.0.0/LICENSE +21 -0
- featuregraph_smoothing_core-1.0.0/PKG-INFO +69 -0
- featuregraph_smoothing_core-1.0.0/README.md +54 -0
- featuregraph_smoothing_core-1.0.0/pyproject.toml +23 -0
- featuregraph_smoothing_core-1.0.0/setup.cfg +4 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/__init__.py +10 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/behaviors/__init__.py +0 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/behaviors/oscillation.py +101 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/datasets/__init__.py +3 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/datasets/_bidmc.py +44 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/operators/__init__.py +0 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/operators/events.py +27 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/operators/measures.py +13 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/operators/states.py +30 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/plot.py +460 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/utils/__init__.py +0 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/utils/_bidmc.py +215 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/utils/_rename_map.py +23 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/validation/__init__.py +15 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/validation/canonical.py +76 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/validation/characterize.py +61 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/validation/exclusions.py +14 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core/validation/matching.py +42 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core.egg-info/PKG-INFO +69 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core.egg-info/SOURCES.txt +28 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core.egg-info/dependency_links.txt +1 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core.egg-info/requires.txt +4 -0
- featuregraph_smoothing_core-1.0.0/src/featuregraph_smoothing_core.egg-info/top_level.txt +1 -0
- featuregraph_smoothing_core-1.0.0/tests/test_oscillation.py +114 -0
- featuregraph_smoothing_core-1.0.0/tests/test_validation.py +174 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nazia Habib
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: featuregraph-smoothing-core
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Minimal, frozen subset of FeatureGraph's state-detection code, extracted to support reproducibility of the smoothing-paper results.
|
|
5
|
+
Author: Nazia Habib
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: pandas>=1.5
|
|
11
|
+
Requires-Dist: numpy>=1.23
|
|
12
|
+
Requires-Dist: matplotlib>=3.5
|
|
13
|
+
Requires-Dist: requests>=2.25
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# featuregraph-smoothing-core
|
|
17
|
+
|
|
18
|
+
Code and data behind "A Compiler-Level Account of Smoothing-Parameter
|
|
19
|
+
Choice." Contains `OscillationConfig` (the construction used throughout
|
|
20
|
+
the paper), a BIDMC data loader, and the validation logic behind the
|
|
21
|
+
paper's human-annotation results.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Reproduce the paper's results
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pytest tests/ -v
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Two tests are skipped by default, since they need a live download from
|
|
36
|
+
PhysioNet. Their skip reasons record the exact confirmed results;
|
|
37
|
+
remove the `@pytest.mark.skip` decorator above either one and rerun to
|
|
38
|
+
reproduce it directly:
|
|
39
|
+
|
|
40
|
+
- `test_reproduces_paper_correlation_and_ratio_range` — the paper's
|
|
41
|
+
population correlation (0.39) and ratio range (1.14x–40.25x) across
|
|
42
|
+
all 53 BIDMC subjects.
|
|
43
|
+
- `test_bidmc_peak_matched_recall_excluding_flagged_subjects` — the
|
|
44
|
+
paper's human-annotation recall results (Table 1), N=32.
|
|
45
|
+
|
|
46
|
+
## Using OscillationConfig directly
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from featuregraph_smoothing_core.behaviors.oscillation import OscillationConfig
|
|
50
|
+
|
|
51
|
+
config = OscillationConfig(signal="respiration", smooth_window=100)
|
|
52
|
+
added = config.add_primitives(df, "subject")
|
|
53
|
+
summary = config.summarize(added, ["subject", config.trough_event_id_col])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## What's here
|
|
57
|
+
|
|
58
|
+
- `src/featuregraph_smoothing_core/` — the package
|
|
59
|
+
- `notebooks/bidmc_visual_demo.ipynb` — the code that produced Figure 1
|
|
60
|
+
- `artifacts/paper/compiler/smoothing.md` — the paper manuscript
|
|
61
|
+
- `tests/` — the full test suite
|
|
62
|
+
|
|
63
|
+
## Citation
|
|
64
|
+
|
|
65
|
+
Software: https://doi.org/10.5281/zenodo.22947447
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# featuregraph-smoothing-core
|
|
2
|
+
|
|
3
|
+
Code and data behind "A Compiler-Level Account of Smoothing-Parameter
|
|
4
|
+
Choice." Contains `OscillationConfig` (the construction used throughout
|
|
5
|
+
the paper), a BIDMC data loader, and the validation logic behind the
|
|
6
|
+
paper's human-annotation results.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install -e .
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Reproduce the paper's results
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pytest tests/ -v
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Two tests are skipped by default, since they need a live download from
|
|
21
|
+
PhysioNet. Their skip reasons record the exact confirmed results;
|
|
22
|
+
remove the `@pytest.mark.skip` decorator above either one and rerun to
|
|
23
|
+
reproduce it directly:
|
|
24
|
+
|
|
25
|
+
- `test_reproduces_paper_correlation_and_ratio_range` — the paper's
|
|
26
|
+
population correlation (0.39) and ratio range (1.14x–40.25x) across
|
|
27
|
+
all 53 BIDMC subjects.
|
|
28
|
+
- `test_bidmc_peak_matched_recall_excluding_flagged_subjects` — the
|
|
29
|
+
paper's human-annotation recall results (Table 1), N=32.
|
|
30
|
+
|
|
31
|
+
## Using OscillationConfig directly
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from featuregraph_smoothing_core.behaviors.oscillation import OscillationConfig
|
|
35
|
+
|
|
36
|
+
config = OscillationConfig(signal="respiration", smooth_window=100)
|
|
37
|
+
added = config.add_primitives(df, "subject")
|
|
38
|
+
summary = config.summarize(added, ["subject", config.trough_event_id_col])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What's here
|
|
42
|
+
|
|
43
|
+
- `src/featuregraph_smoothing_core/` — the package
|
|
44
|
+
- `notebooks/bidmc_visual_demo.ipynb` — the code that produced Figure 1
|
|
45
|
+
- `artifacts/paper/compiler/smoothing.md` — the paper manuscript
|
|
46
|
+
- `tests/` — the full test suite
|
|
47
|
+
|
|
48
|
+
## Citation
|
|
49
|
+
|
|
50
|
+
Software: https://doi.org/10.5281/zenodo.22947447
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "featuregraph-smoothing-core"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Minimal, frozen subset of FeatureGraph's state-detection code, extracted to support reproducibility of the smoothing-paper results."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Nazia Habib" },
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pandas>=1.5",
|
|
17
|
+
"numpy>=1.23",
|
|
18
|
+
"matplotlib>=3.5",
|
|
19
|
+
"requests>=2.25",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from featuregraph_smoothing_core import datasets
|
|
2
|
+
from featuregraph_smoothing_core import validation
|
|
3
|
+
from featuregraph_smoothing_core.plot import (
|
|
4
|
+
plot,
|
|
5
|
+
plot_annotated_oscillation,
|
|
6
|
+
plot_oscillation_panel,
|
|
7
|
+
add_construction_legend,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = ["datasets", "validation", "plot", "plot_annotated_oscillation", "plot_oscillation_panel", "add_construction_legend"]
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from featuregraph_smoothing_core.operators.states import rising_state, falling_state
|
|
3
|
+
from featuregraph_smoothing_core.operators.events import enter_state, exit_state, event_id, event_index
|
|
4
|
+
from featuregraph_smoothing_core.operators.measures import smooth, smooth_grouped
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class OscillationConfig:
|
|
11
|
+
signal: str
|
|
12
|
+
eps: float = 0
|
|
13
|
+
lag: int = 1
|
|
14
|
+
smooth_window: int = 100
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def smooth(self):
|
|
18
|
+
return f'{self.signal}_smooth'
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def rising_col(self):
|
|
22
|
+
return f'{self.signal}_rising'
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def falling_col(self):
|
|
26
|
+
return f'{self.signal}_falling'
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def enter_rising_col(self):
|
|
30
|
+
return f'enter_{self.signal}_rising'
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def exit_rising_col(self):
|
|
34
|
+
return f'exit_{self.signal}_rising'
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def peak_event_id_col(self):
|
|
38
|
+
return f'{self.signal}_peak_event_id'
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def trough_event_id_col(self):
|
|
42
|
+
return f'{self.signal}_trough_event_id'
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def peak_index_col(self):
|
|
46
|
+
return f'{self.signal}_peak_index'
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def trough_index_col(self):
|
|
50
|
+
return f'{self.signal}_trough_index'
|
|
51
|
+
|
|
52
|
+
def add_primitives(self, df: pd.DataFrame, group=None):
|
|
53
|
+
df = df.copy()
|
|
54
|
+
df[self.smooth] = smooth_grouped(df, self.signal, window=self.smooth_window, group=group)
|
|
55
|
+
df[self.rising_col] = rising_state(df[self.smooth])
|
|
56
|
+
df[self.falling_col] = falling_state(df[self.smooth])
|
|
57
|
+
df[self.exit_rising_col] = exit_state(df[self.rising_col], group=df[group] if group else None)
|
|
58
|
+
df[self.enter_rising_col] = enter_state(df[self.rising_col], group=df[group] if group else None)
|
|
59
|
+
df[self.peak_event_id_col] = event_id(df, self.exit_rising_col, group=group)
|
|
60
|
+
df[self.trough_event_id_col] = event_id(df, self.enter_rising_col, group=group)
|
|
61
|
+
df[self.peak_index_col] = event_index(df, self.exit_rising_col, group=group)
|
|
62
|
+
df[self.trough_index_col] = event_index(df, self.enter_rising_col, group=group)
|
|
63
|
+
return df
|
|
64
|
+
|
|
65
|
+
def summarize(self, df: pd.DataFrame, group=None):
|
|
66
|
+
summarydf = df.groupby(group).agg(
|
|
67
|
+
start_index=(self.trough_index_col, 'first'),
|
|
68
|
+
rising_duration=(self.rising_col, 'sum'),
|
|
69
|
+
falling_duration=(self.falling_col, 'sum'),
|
|
70
|
+
peak_index=(self.peak_index_col, 'last'),
|
|
71
|
+
trough_index=(self.trough_index_col, 'first'),
|
|
72
|
+
max_raw_signal=(self.signal, 'max'),
|
|
73
|
+
max_smooth_signal=(self.smooth, 'max'),
|
|
74
|
+
min_raw_signal=(self.signal, 'min'),
|
|
75
|
+
min_smooth_signal=(self.smooth, 'min'),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
summarydf['end_index'] = summarydf.groupby(level=0)['start_index'].shift(-1)
|
|
79
|
+
summarydf['is_complete'] = summarydf['start_index'].notna() & summarydf['end_index'].notna()
|
|
80
|
+
summarydf['duration'] = summarydf['rising_duration'] + summarydf['falling_duration']
|
|
81
|
+
|
|
82
|
+
if not group:
|
|
83
|
+
summarydf['period'] = summarydf['peak_index'].diff()
|
|
84
|
+
else:
|
|
85
|
+
summarydf['period'] = summarydf.groupby(group[0])['peak_index'].diff()
|
|
86
|
+
|
|
87
|
+
net_change_raw = (summarydf['max_raw_signal'] - summarydf['min_raw_signal'])
|
|
88
|
+
net_change_smooth = (summarydf['max_smooth_signal'] - summarydf['min_smooth_signal'])
|
|
89
|
+
|
|
90
|
+
summarydf['amplitude_raw'] = net_change_raw / 2
|
|
91
|
+
summarydf['amplitude_smooth'] = net_change_smooth / 2
|
|
92
|
+
|
|
93
|
+
summarydf['raw_rising_mean_rate'] = (net_change_raw / summarydf['rising_duration']).where(summarydf['rising_duration'] > 0)
|
|
94
|
+
summarydf['raw_falling_mean_rate'] = (net_change_raw / summarydf['falling_duration']).where(summarydf['falling_duration'] > 0)
|
|
95
|
+
|
|
96
|
+
summarydf['smooth_rising_mean_rate'] = (net_change_smooth / summarydf['rising_duration']).where(summarydf['rising_duration'] > 0)
|
|
97
|
+
summarydf['smooth_falling_mean_rate'] = (net_change_smooth / summarydf['falling_duration']).where(summarydf['falling_duration'] > 0)
|
|
98
|
+
|
|
99
|
+
summarydf["temporal_symmetry"] = (1 - (summarydf['rising_duration'] - summarydf['falling_duration']).abs() / summarydf['duration']).where(summarydf['duration'] > 0)
|
|
100
|
+
|
|
101
|
+
return summarydf
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from featuregraph_smoothing_core.utils._bidmc import (
|
|
6
|
+
load_bidmc_breaths,
|
|
7
|
+
load_bidmc_subject,
|
|
8
|
+
)
|
|
9
|
+
from featuregraph_smoothing_core.utils._rename_map import bidmc_map
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def bidmc(
|
|
13
|
+
subject: int = 1,
|
|
14
|
+
*,
|
|
15
|
+
refresh: bool = False,
|
|
16
|
+
) -> pd.DataFrame:
|
|
17
|
+
"""
|
|
18
|
+
Load one BIDMC subject.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
pandas.DataFrame
|
|
23
|
+
Physiological waveform observations with standardized
|
|
24
|
+
FeatureGraph column names.
|
|
25
|
+
"""
|
|
26
|
+
return (
|
|
27
|
+
load_bidmc_subject(
|
|
28
|
+
subject,
|
|
29
|
+
refresh=refresh,
|
|
30
|
+
)
|
|
31
|
+
.rename(columns=bidmc_map)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def bidmc_breaths(
|
|
36
|
+
subject: int = 1,
|
|
37
|
+
*,
|
|
38
|
+
refresh: bool = False,
|
|
39
|
+
) -> pd.DataFrame:
|
|
40
|
+
"""Load the two BIDMC breath-annotation columns for one subject."""
|
|
41
|
+
return load_bidmc_breaths(
|
|
42
|
+
subject,
|
|
43
|
+
refresh=refresh,
|
|
44
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def enter_state(state, group=None):
|
|
5
|
+
x = state.astype(int)
|
|
6
|
+
if group is None:
|
|
7
|
+
return x.diff().eq(1)
|
|
8
|
+
return x.groupby(group).diff().eq(1)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def exit_state(state, group=None):
|
|
12
|
+
x = state.astype(int)
|
|
13
|
+
if group is None:
|
|
14
|
+
return x.diff().eq(-1)
|
|
15
|
+
return x.groupby(group).diff().eq(-1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def event_id(df, enter_col, group=None):
|
|
19
|
+
if group is None:
|
|
20
|
+
return df[enter_col].cumsum()
|
|
21
|
+
return df.groupby(group)[enter_col].cumsum()
|
|
22
|
+
|
|
23
|
+
def event_index(df, event_col, group=None):
|
|
24
|
+
event_positions = pd.Series(np.where(df[event_col], df.index, np.nan), index=df.index)
|
|
25
|
+
if group is None:
|
|
26
|
+
return event_positions.ffill()
|
|
27
|
+
return event_positions.groupby(df[group]).ffill()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
def smooth(signal, window):
|
|
2
|
+
return (
|
|
3
|
+
signal.rolling(center=True, window=window).median().rolling(center=True, window=window).mean()
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
def smooth_grouped(df, signal_col, window, group):
|
|
7
|
+
return df.groupby(group)[signal_col].transform(lambda s: smooth(s, window))
|
|
8
|
+
|
|
9
|
+
def group_transform(df, signal, op, group):
|
|
10
|
+
return df.groupby(group)[signal].transform(op)
|
|
11
|
+
|
|
12
|
+
def group_map(df, signal, op, group, offset=0):
|
|
13
|
+
return df[group].map(df.groupby(group)[signal].agg(op).shift(offset))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
def positive_state(quantity, eps=0):
|
|
2
|
+
return quantity.gt(eps)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def negative_state(quantity, eps=0):
|
|
6
|
+
return quantity.lt(-eps)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def inactive_state(quantity, eps=0):
|
|
10
|
+
return quantity.abs().le(eps)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def rising_state(series, lag=1, eps=0):
|
|
14
|
+
return positive_state(series.diff(lag), eps)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def falling_state(series, lag=1, eps=0):
|
|
18
|
+
return negative_state(series.diff(lag), eps)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def stable_state(series, lag=1, eps=0):
|
|
22
|
+
return inactive_state(series.diff(lag), eps)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def accumulating_state(contribution, eps=0):
|
|
26
|
+
return positive_state(contribution, eps)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def depleting_state(contribution, eps=0):
|
|
30
|
+
return negative_state(contribution, eps)
|