pytrendy 1.4.0.dev2__tar.gz → 1.4.0.dev3__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.
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/PKG-INFO +9 -10
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/README.md +8 -9
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pyproject.toml +1 -1
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/abrupt_shaving.py +4 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/process_signals.py +16 -10
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/LICENSE +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/__init__.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/detect_trends.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/__init__.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data/classes_signals.csv +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data/series_synthetic.csv +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data_loader.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/plot_pytrendy.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/results_pytrendy.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/__init__.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_analyse.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_get.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/__init__.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/artifact_cleanup.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/gradual_expand_contract.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/segment_grouping.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/trend_classify.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/update_neighbours.py +0 -0
- {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/simpledtw.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrendy
|
|
3
|
-
Version: 1.4.0.
|
|
3
|
+
Version: 1.4.0.dev3
|
|
4
4
|
Summary: Trend Detection in Python. Applicable for real-world industry use cases in time series.
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -115,7 +115,7 @@ results.print_summary()
|
|
|
115
115
|
Detected:
|
|
116
116
|
- 3 Uptrends.
|
|
117
117
|
- 3 Downtrends.
|
|
118
|
-
-
|
|
118
|
+
- 2 Flats.
|
|
119
119
|
- 0 Noise.
|
|
120
120
|
|
|
121
121
|
The best detected trend is Down between dates 2025-05-09 - 2025-06-17
|
|
@@ -126,13 +126,12 @@ Full Results:
|
|
|
126
126
|
time_index
|
|
127
127
|
1 Up 2025-01-02 2025-01-24 22 14.013348 5 gradual
|
|
128
128
|
2 Down 2025-01-25 2025-02-05 11 -13.564214 6 gradual
|
|
129
|
-
3 Flat 2025-02-06 2025-02-09 3 -1.168831
|
|
130
|
-
4 Up 2025-02-10 2025-03-
|
|
131
|
-
5
|
|
132
|
-
6
|
|
133
|
-
7
|
|
134
|
-
8
|
|
135
|
-
9 Flat 2025-06-18 2025-06-30 12 3.910534 8 NaN
|
|
129
|
+
3 Flat 2025-02-06 2025-02-09 3 -1.168831 8 NaN
|
|
130
|
+
4 Up 2025-02-10 2025-03-17 35 26.190476 3 gradual
|
|
131
|
+
5 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
|
|
132
|
+
6 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
|
|
133
|
+
7 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
|
|
134
|
+
8 Flat 2025-06-18 2025-06-30 12 3.910534 7 NaN
|
|
136
135
|
-------------------------------------------------------------------------------
|
|
137
136
|
```
|
|
138
137
|
|
|
@@ -144,7 +143,7 @@ results.filter_segments(direction='Up', sort_by='change_rank')[:3]
|
|
|
144
143
|
| time_index | direction | start | end | trend_class | change | pct_change | days | total_change | SNR | change_rank |
|
|
145
144
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
146
145
|
| 7 | Up | 2025-04-02 | 2025-05-08 | gradual | 72.61 | 367.50% | 36 | 72.61 | 21.70 | 2 |
|
|
147
|
-
| 4 | Up | 2025-02-10 | 2025-03-
|
|
146
|
+
| 4 | Up | 2025-02-10 | 2025-03-17 | gradual | 26.19 | 179.93% | 35 | 26.19 | 18.61 | 3 |
|
|
148
147
|
| 1 | Up | 2025-01-02 | 2025-01-24 | gradual | 14.01 | 104.41% | 22 | 14.01 | 22.21 | 5 |
|
|
149
148
|
|
|
150
149
|
`filter_segments` ranks segments by magnitude (`change_rank`). See the [API reference](https://russellsb.github.io/pytrendy/main/reference/pytrendy/io/results_pytrendy/#pytrendy.io.results_pytrendy.PyTrendyResults.filter_segments) for all filter and sort options.
|
|
@@ -52,7 +52,7 @@ results.print_summary()
|
|
|
52
52
|
Detected:
|
|
53
53
|
- 3 Uptrends.
|
|
54
54
|
- 3 Downtrends.
|
|
55
|
-
-
|
|
55
|
+
- 2 Flats.
|
|
56
56
|
- 0 Noise.
|
|
57
57
|
|
|
58
58
|
The best detected trend is Down between dates 2025-05-09 - 2025-06-17
|
|
@@ -63,13 +63,12 @@ Full Results:
|
|
|
63
63
|
time_index
|
|
64
64
|
1 Up 2025-01-02 2025-01-24 22 14.013348 5 gradual
|
|
65
65
|
2 Down 2025-01-25 2025-02-05 11 -13.564214 6 gradual
|
|
66
|
-
3 Flat 2025-02-06 2025-02-09 3 -1.168831
|
|
67
|
-
4 Up 2025-02-10 2025-03-
|
|
68
|
-
5
|
|
69
|
-
6
|
|
70
|
-
7
|
|
71
|
-
8
|
|
72
|
-
9 Flat 2025-06-18 2025-06-30 12 3.910534 8 NaN
|
|
66
|
+
3 Flat 2025-02-06 2025-02-09 3 -1.168831 8 NaN
|
|
67
|
+
4 Up 2025-02-10 2025-03-17 35 26.190476 3 gradual
|
|
68
|
+
5 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
|
|
69
|
+
6 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
|
|
70
|
+
7 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
|
|
71
|
+
8 Flat 2025-06-18 2025-06-30 12 3.910534 7 NaN
|
|
73
72
|
-------------------------------------------------------------------------------
|
|
74
73
|
```
|
|
75
74
|
|
|
@@ -81,7 +80,7 @@ results.filter_segments(direction='Up', sort_by='change_rank')[:3]
|
|
|
81
80
|
| time_index | direction | start | end | trend_class | change | pct_change | days | total_change | SNR | change_rank |
|
|
82
81
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
83
82
|
| 7 | Up | 2025-04-02 | 2025-05-08 | gradual | 72.61 | 367.50% | 36 | 72.61 | 21.70 | 2 |
|
|
84
|
-
| 4 | Up | 2025-02-10 | 2025-03-
|
|
83
|
+
| 4 | Up | 2025-02-10 | 2025-03-17 | gradual | 26.19 | 179.93% | 35 | 26.19 | 18.61 | 3 |
|
|
85
84
|
| 1 | Up | 2025-01-02 | 2025-01-24 | gradual | 14.01 | 104.41% | 22 | 14.01 | 22.21 | 5 |
|
|
86
85
|
|
|
87
86
|
`filter_segments` ranks segments by magnitude (`change_rank`). See the [API reference](https://russellsb.github.io/pytrendy/main/reference/pytrendy/io/results_pytrendy/#pytrendy.io.results_pytrendy.PyTrendyResults.filter_segments) for all filter and sort options.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pytrendy"
|
|
3
|
-
version = "1.4.0.
|
|
3
|
+
version = "1.4.0.dev3"
|
|
4
4
|
description = "Trend Detection in Python. Applicable for real-world industry use cases in time series."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Russell Sammut Bonnici", email = "r.sammutbonnici@gmail.com" },
|
|
@@ -134,6 +134,10 @@ def shave_abrupt_trends(df: pd.DataFrame, value_col: str, segments: list[dict],
|
|
|
134
134
|
if segment['direction'] not in ['Up', 'Down'] or segment['trend_class'] != 'abrupt':
|
|
135
135
|
continue
|
|
136
136
|
|
|
137
|
+
# Skip segments already padded in first pass; .get returns False when key absent (unpadded segments)
|
|
138
|
+
if second_pass and segment.get('padded', False):
|
|
139
|
+
continue
|
|
140
|
+
|
|
137
141
|
abrupt_start = pd.to_datetime(segment['start'])
|
|
138
142
|
abrupt_end = pd.to_datetime(segment['end'])
|
|
139
143
|
|
|
@@ -53,6 +53,7 @@ def process_signals(df: pd.DataFrame, value_col: str, method_params: dict, debug
|
|
|
53
53
|
|
|
54
54
|
THRESHOLD_NOISE = 2.5 # Sensitivity to detecting noise (recommended 0-10)
|
|
55
55
|
THRESHOLD_SMOOTH = 0.001 # Sensitivity to detecting trends as fraction of iqr
|
|
56
|
+
THRESHOLD_FLAT = 0.835 # Sensitivity to detecting flats as a fraction of min std (non-zero)
|
|
56
57
|
|
|
57
58
|
# 1. Noise detection via SNR.
|
|
58
59
|
# 1.1 Compute the SNR
|
|
@@ -174,31 +175,36 @@ def process_signals(df: pd.DataFrame, value_col: str, method_params: dict, debug
|
|
|
174
175
|
|
|
175
176
|
# 3. Flat detection using rolling std of savgol filter.
|
|
176
177
|
# with leading and trailing to cater for periods centred windows doesnt cover
|
|
178
|
+
|
|
179
|
+
# 3.1 Compute smoothed signal rolling std to be used by following logic
|
|
177
180
|
df['smoothed'] = savgol_filter(df['value_cleaned'], window_length=WINDOW_SMOOTH, polyorder=1)
|
|
178
181
|
df['smoothed_std'] = df['smoothed'].rolling(WINDOW_FLAT, center=True).std()
|
|
179
182
|
df['smoothed_std_leading'] = df['smoothed'].iloc[::-1].rolling(window=WINDOW_FLAT).std().iloc[::-1]
|
|
180
183
|
df['smoothed_std_trailing'] = df['smoothed'].rolling(WINDOW_FLAT).std()
|
|
181
184
|
df['smoothed_std'] = df['smoothed_std'].fillna(df['smoothed_std_leading']).fillna(df['smoothed_std_trailing'])
|
|
182
185
|
|
|
186
|
+
# 3.2 Compute derivative early so flat detection can check both smoothness and motion.
|
|
187
|
+
derivative_limit = abs(iqr(df[value_col])) * THRESHOLD_SMOOTH
|
|
188
|
+
df['smoothed_deriv'] = savgol_filter(df[value_col], window_length=WINDOW_SMOOTH, polyorder=1, deriv=1)
|
|
189
|
+
|
|
190
|
+
# 3.3 Compute Flat Flag using both std and deriv limits
|
|
183
191
|
df['flat_flag'] = 0
|
|
184
192
|
rolling_std = df['value_cleaned'].rolling(WINDOW_FLAT, center=True).std()
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
nonzero_std = rolling_std[rolling_std > 0]
|
|
194
|
+
min_nonzero_std = nonzero_std.min() if not nonzero_std.empty else 0.0
|
|
195
|
+
derivative_near_zero = df['smoothed_deriv'].abs() <= derivative_limit
|
|
196
|
+
extremely_smooth = df['smoothed_std'] < (min_nonzero_std * THRESHOLD_FLAT)
|
|
197
|
+
df.loc[(df['smoothed_std'] <= min_nonzero_std) & (df['noise_flag'] == 0) & (derivative_near_zero | extremely_smooth), 'flat_flag'] = 1
|
|
187
198
|
|
|
188
|
-
# 4. Detect up/down trend.
|
|
189
|
-
# Savgol filter (rolling avg improvement). Caters for seasonality with tightness to day.
|
|
190
|
-
# Results in signal that's uptrend > 0, else down. As long as its not on a flat or noise.
|
|
199
|
+
# 4. Detect up/down trend.
|
|
191
200
|
df['trend_flag'] = 0
|
|
192
201
|
df.loc[df['flat_flag'] == 1, 'trend_flag'] = -2
|
|
193
202
|
df.loc[df['noise_flag'] == 1, 'trend_flag'] = -3
|
|
194
203
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if method_params['avoid_noise']: # noise can be optionally avoided, up to the user
|
|
204
|
+
avoid_condition = (df['flat_flag'] == 0)
|
|
205
|
+
if method_params['avoid_noise']:
|
|
198
206
|
avoid_condition &= (df['noise_flag'] == 0)
|
|
199
207
|
|
|
200
|
-
derivative_limit = abs(iqr(df[value_col])) * THRESHOLD_SMOOTH
|
|
201
|
-
df['smoothed_deriv'] = savgol_filter(df[value_col], window_length=WINDOW_SMOOTH, polyorder=1, deriv=1)
|
|
202
208
|
df.loc[(df['smoothed_deriv'] >= derivative_limit) & avoid_condition, 'trend_flag'] = 1
|
|
203
209
|
df.loc[(df['smoothed_deriv'] < -derivative_limit) & avoid_condition, 'trend_flag'] = -1
|
|
204
210
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|