pytrendy 1.1.11.dev3__tar.gz → 1.1.11.dev4__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.1.11.dev3 → pytrendy-1.1.11.dev4}/PKG-INFO +13 -12
  2. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/README.md +12 -12
  3. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pyproject.toml +1 -1
  4. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/plot_pytrendy.py +1 -1
  5. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/results_pytrendy.py +12 -6
  6. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_analyse.py +7 -18
  7. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/artifact_cleanup.py +1 -1
  8. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/trend_classify.py +1 -1
  9. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/LICENSE +0 -0
  10. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/__init__.py +0 -0
  11. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/detect_trends.py +0 -0
  12. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/__init__.py +0 -0
  13. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/data/classes_signals.csv +0 -0
  14. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/data/series_synthetic.csv +0 -0
  15. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/io/data_loader.py +0 -0
  16. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/__init__.py +0 -0
  17. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_get.py +0 -0
  18. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/__init__.py +0 -0
  19. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/abrupt_shaving.py +0 -0
  20. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/gradual_expand_contract.py +0 -0
  21. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/segment_grouping.py +0 -0
  22. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/post_processing/segments_refine/update_neighbours.py +0 -0
  23. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/process_signals.py +0 -0
  24. {pytrendy-1.1.11.dev3 → pytrendy-1.1.11.dev4}/pytrendy/simpledtw.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrendy
3
- Version: 1.1.11.dev3
3
+ Version: 1.1.11.dev4
4
4
  Summary: Trend Detection in Python. Applicable for real-world industry use cases in time series.
5
5
  License: MIT License
6
6
 
@@ -106,20 +106,21 @@ The best detected trend is Down between dates 2025-05-09 - 2025-06-17
106
106
 
107
107
  Full Results:
108
108
  -------------------------------------------------------------------------------
109
- direction start end days total_change change_rank
110
- time_index
111
- 1 Up 2025-01-02 2025-01-24 22 14.013348 5
112
- 2 Down 2025-01-25 2025-02-05 11 -13.564214 6
113
- 3 Flat 2025-02-06 2025-02-09 3 NaN 7
114
- 4 Up 2025-02-10 2025-03-14 32 24.632035 3
115
- 5 Flat 2025-03-15 2025-03-17 2 NaN 8
116
- 6 Down 2025-03-18 2025-04-01 14 -22.721861 4
117
- 7 Up 2025-04-02 2025-05-08 36 72.611833 2
118
- 8 Down 2025-05-09 2025-06-17 39 -73.253968 1
119
- 9 Flat 2025-06-18 2025-06-30 12 NaN 9
109
+ direction start end days total_change change_rank trend_class
110
+ time_index
111
+ 1 Up 2025-01-02 2025-01-24 22 14.013348 5 gradual
112
+ 2 Down 2025-01-25 2025-02-05 11 -13.564214 6 gradual
113
+ 3 Flat 2025-02-06 2025-02-09 3 -1.168831 9 NaN
114
+ 4 Up 2025-02-10 2025-03-14 32 24.632035 3 gradual
115
+ 5 Flat 2025-03-15 2025-03-17 2 5.660173 7 NaN
116
+ 6 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
117
+ 7 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
118
+ 8 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
119
+ 9 Flat 2025-06-18 2025-06-30 12 3.910534 8 NaN
120
120
  -------------------------------------------------------------------------------
121
121
  ```
122
122
 
123
123
  ---
124
124
 
125
125
  **Read more in the full documentation:** [russellsb.github.io/pytrendy/main](https://russellsb.github.io/pytrendy/main/)
126
+
@@ -49,20 +49,20 @@ The best detected trend is Down between dates 2025-05-09 - 2025-06-17
49
49
 
50
50
  Full Results:
51
51
  -------------------------------------------------------------------------------
52
- direction start end days total_change change_rank
53
- time_index
54
- 1 Up 2025-01-02 2025-01-24 22 14.013348 5
55
- 2 Down 2025-01-25 2025-02-05 11 -13.564214 6
56
- 3 Flat 2025-02-06 2025-02-09 3 NaN 7
57
- 4 Up 2025-02-10 2025-03-14 32 24.632035 3
58
- 5 Flat 2025-03-15 2025-03-17 2 NaN 8
59
- 6 Down 2025-03-18 2025-04-01 14 -22.721861 4
60
- 7 Up 2025-04-02 2025-05-08 36 72.611833 2
61
- 8 Down 2025-05-09 2025-06-17 39 -73.253968 1
62
- 9 Flat 2025-06-18 2025-06-30 12 NaN 9
52
+ direction start end days total_change change_rank trend_class
53
+ time_index
54
+ 1 Up 2025-01-02 2025-01-24 22 14.013348 5 gradual
55
+ 2 Down 2025-01-25 2025-02-05 11 -13.564214 6 gradual
56
+ 3 Flat 2025-02-06 2025-02-09 3 -1.168831 9 NaN
57
+ 4 Up 2025-02-10 2025-03-14 32 24.632035 3 gradual
58
+ 5 Flat 2025-03-15 2025-03-17 2 5.660173 7 NaN
59
+ 6 Down 2025-03-18 2025-04-01 14 -22.721861 4 gradual
60
+ 7 Up 2025-04-02 2025-05-08 36 72.611833 2 gradual
61
+ 8 Down 2025-05-09 2025-06-17 39 -73.253968 1 gradual
62
+ 9 Flat 2025-06-18 2025-06-30 12 3.910534 8 NaN
63
63
  -------------------------------------------------------------------------------
64
64
  ```
65
65
 
66
66
  ---
67
67
 
68
- **Read more in the full documentation:** [russellsb.github.io/pytrendy/main](https://russellsb.github.io/pytrendy/main/)
68
+ **Read more in the full documentation:** [russellsb.github.io/pytrendy/main](https://russellsb.github.io/pytrendy/main/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pytrendy"
3
- version = "1.1.11.dev3"
3
+ version = "1.1.11.dev4"
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" },
@@ -111,7 +111,7 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
111
111
  ax.fill_between(df.index[mask], ymin, ymax, color=color, alpha=0.4)
112
112
 
113
113
  # Add ranking if up/down trend
114
- if 'change_rank' in seg:
114
+ if 'change_rank' in seg and seg['direction'] in ['Up', 'Down']:
115
115
  mid_date = start + (end - start) / 2
116
116
  y_pos = ymax - (ymax - ymin) * 0.05
117
117
  ax.text(mid_date, y_pos, str(seg['change_rank']), fontsize=12,
@@ -23,6 +23,8 @@ class PyTrendyResults:
23
23
  List of dictionaries representing individual trend segments.
24
24
  """
25
25
  self.segments = segments
26
+ self.trend_segments = [seg for seg in self.segments if 'trend_class' in seg] # Get segments that are trends (exclude flats and noise)
27
+
26
28
  self.set_best()
27
29
  self.set_df()
28
30
  self.set_summary()
@@ -35,10 +37,10 @@ class PyTrendyResults:
35
37
  - Identifies the best trend segment based on steepness and duration.
36
38
  - The segment with the lowest `change_rank` is selected as the best.
37
39
  """
38
- if len(self.segments) == 0 or not any('change_rank' in segment for segment in self.segments):
40
+ if len(self.trend_segments) == 0:
39
41
  self.best = None
40
42
  return
41
- self.best = min(self.segments, key=lambda x: x.get('change_rank', math.inf))
43
+ self.best = min(self.trend_segments, key=lambda x: x.get('change_rank', math.inf))
42
44
 
43
45
  def set_summary(self) -> None:
44
46
  """
@@ -54,19 +56,23 @@ class PyTrendyResults:
54
56
  summary['df'] = pd.DataFrame()
55
57
  return
56
58
 
59
+ # Count the number of segments per direction type (Up, Down, Flat, Noise)
57
60
  direction_counts = Counter(seg["direction"] for seg in self.segments)
58
61
  summary["direction_counts"] = dict(direction_counts)
59
62
 
60
- trend_class_counts = Counter(seg["trend_class"] for seg in self.segments if "trend_class" in seg)
63
+ # Count number of segments per trend classs (abrupt, gradual)
64
+ trend_class_counts = Counter(seg["trend_class"] for seg in self.trend_segments)
61
65
  summary["trend_class_counts"] = dict(trend_class_counts)
62
66
 
63
- changes = [seg.get("total_change", 0) for seg in self.segments if "total_change" in seg]
67
+ # Get array of total change from trends and get max (best) total change
68
+ changes = [seg.get("total_change", 0) for seg in self.trend_segments]
64
69
  summary['highest_total_change'] = np.max(changes) if len(changes) > 0 else None
65
70
 
66
71
  # Set summary df (without extra details)
67
72
  df = pd.DataFrame(self.segments)
68
- cols = ['time_index', 'direction', 'start', 'end', 'days']
69
- if len(changes) > 1: cols += ['total_change', 'change_rank', 'trend_class']
73
+ cols = ['time_index', 'direction', 'start', 'end', 'days', 'total_change', 'change_rank']
74
+ if len(changes) > 1: # only include trend_class if atleast one trend exists
75
+ cols += ['trend_class']
70
76
  df = df[cols]
71
77
 
72
78
  df = df.set_index('time_index')
@@ -16,7 +16,7 @@ def analyse_segments(df: pd.DataFrame, value_col: str, segments: list[dict]) ->
16
16
 
17
17
  Metrics added include:
18
18
 
19
- - Absolute and percent change (based on min/max values)
19
+ - Absolute and percent change (based on start/end values)
20
20
 
21
21
  - Duration in days
22
22
 
@@ -47,19 +47,10 @@ def analyse_segments(df: pd.DataFrame, value_col: str, segments: list[dict]) ->
47
47
  df_segment = df.loc[segment['start']:segment['end']]
48
48
 
49
49
  # Calculate absolute and relative change from first point to last point of trend.
50
- # (Using min/max instead of first/last to be more robust to noise.)
51
- val_min = df_segment[value_col].min()
52
- val_max = df_segment[value_col].max()
53
- if segment['direction'] == 'Up': # max - min
54
- segment_enhanced['change'] = float(val_max - val_min)
55
- segment_enhanced['pct_change'] = (
56
- float(val_max / val_min - 1) if val_min != 0 else np.nan
57
- )
58
- elif segment['direction'] == 'Down': # min - max
59
- segment_enhanced['change'] = float(val_min - val_max)
60
- segment_enhanced['pct_change'] = (
61
- float(val_min / val_max - 1) if val_max != 0 else np.nan
62
- )
50
+ val_start = df_segment[value_col].iloc[0]
51
+ val_end = df_segment[value_col].iloc[-1]
52
+ segment_enhanced['change'] = float(val_end - val_start)
53
+ segment_enhanced['pct_change'] = (float(val_end / val_start - 1) if val_start != 0 else np.nan)
63
54
 
64
55
  # Calculate days & cumulative total change
65
56
  days = (pd.to_datetime(segment['end']) - pd.to_datetime(segment['start'])).days
@@ -68,8 +59,7 @@ def analyse_segments(df: pd.DataFrame, value_col: str, segments: list[dict]) ->
68
59
  segment_enhanced['days'] = days # set days
69
60
 
70
61
  # Calculate cumulative total change
71
- if segment['direction'] in ['Up', 'Down']:
72
- segment_enhanced['total_change'] = float(df_segment[value_col].diff().sum())
62
+ segment_enhanced['total_change'] = float(df_segment[value_col].diff().sum())
73
63
 
74
64
  # Calculate Signal to Noise Ratio
75
65
  signal_power = np.mean(df_segment['signal']**2)
@@ -83,8 +73,7 @@ def analyse_segments(df: pd.DataFrame, value_col: str, segments: list[dict]) ->
83
73
 
84
74
  # Rank change, by steepest to shallowest change
85
75
  sorted_segments = sorted(segments_enhanced, key=lambda x: abs(x.get('total_change', 0)), reverse=True)
86
- sorted_trends = [seg for seg in sorted_segments if 'total_change' in seg and abs(seg['total_change']) > 0]
87
- for i, seg in enumerate(sorted_trends):
76
+ for i, seg in enumerate(sorted_segments):
88
77
  j = seg['time_index'] - 1
89
78
  segments_enhanced[j]['change_rank'] = int(i+1)
90
79
 
@@ -380,4 +380,4 @@ def fill_in_flats(df: pd.DataFrame, segments: list[dict]) -> list[dict]:
380
380
  direction='Flat'
381
381
  ))
382
382
 
383
- return segments_refined
383
+ return segments_refined
@@ -75,4 +75,4 @@ def classify_trends(df: pd.DataFrame, value_col: str, segments: list[dict]) -> l
75
75
  if segment_length < 3:
76
76
  segments_classified[i]['trend_class'] = 'abrupt'
77
77
 
78
- return segments_classified
78
+ return segments_classified
File without changes