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.
Files changed (24) hide show
  1. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/PKG-INFO +9 -10
  2. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/README.md +8 -9
  3. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pyproject.toml +1 -1
  4. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/abrupt_shaving.py +4 -0
  5. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/process_signals.py +16 -10
  6. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/LICENSE +0 -0
  7. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/__init__.py +0 -0
  8. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/detect_trends.py +0 -0
  9. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/__init__.py +0 -0
  10. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data/classes_signals.csv +0 -0
  11. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data/series_synthetic.csv +0 -0
  12. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/data_loader.py +0 -0
  13. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/plot_pytrendy.py +0 -0
  14. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/io/results_pytrendy.py +0 -0
  15. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/__init__.py +0 -0
  16. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_analyse.py +0 -0
  17. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_get.py +0 -0
  18. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/__init__.py +0 -0
  19. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/artifact_cleanup.py +0 -0
  20. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/gradual_expand_contract.py +0 -0
  21. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/segment_grouping.py +0 -0
  22. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/trend_classify.py +0 -0
  23. {pytrendy-1.4.0.dev2 → pytrendy-1.4.0.dev3}/pytrendy/post_processing/segments_refine/update_neighbours.py +0 -0
  24. {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.dev2
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
- - 3 Flats.
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 9 NaN
130
- 4 Up 2025-02-10 2025-03-14 32 24.632035 3 gradual
131
- 5 Flat 2025-03-15 2025-03-17 2 5.660173 7 NaN
132
- 6 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
133
- 7 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
134
- 8 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
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-14 | gradual | 24.63 | 169.22% | 32 | 24.63 | 18.87 | 3 |
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
- - 3 Flats.
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 9 NaN
67
- 4 Up 2025-02-10 2025-03-14 32 24.632035 3 gradual
68
- 5 Flat 2025-03-15 2025-03-17 2 5.660173 7 NaN
69
- 6 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
70
- 7 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
71
- 8 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
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-14 | gradual | 24.63 | 169.22% | 32 | 24.63 | 18.87 | 3 |
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.dev2"
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
- min_nonzero_std = rolling_std[rolling_std > 0].min()
186
- df.loc[(df['smoothed_std'] <= min_nonzero_std) & (df['noise_flag'] == 0), 'flat_flag'] = 1
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. Uses first derivates of savgol filter (like diff).
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
- # Important condition to establish non-trend segments to avoid detecting trends over
196
- avoid_condition = (df['flat_flag'] == 0) # flat is always avoided
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