pytrendy 1.3.0.dev1__tar.gz → 1.4.0.dev1__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.3.0.dev1 → pytrendy-1.4.0.dev1}/PKG-INFO +2 -2
  2. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/README.md +1 -1
  3. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pyproject.toml +1 -1
  4. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/detect_trends.py +13 -2
  5. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/plot_pytrendy.py +53 -20
  6. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/LICENSE +0 -0
  7. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/__init__.py +0 -0
  8. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/__init__.py +0 -0
  9. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/data/classes_signals.csv +0 -0
  10. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/data/series_synthetic.csv +0 -0
  11. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/data_loader.py +0 -0
  12. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/io/results_pytrendy.py +0 -0
  13. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/__init__.py +0 -0
  14. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_analyse.py +0 -0
  15. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_get.py +0 -0
  16. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/__init__.py +0 -0
  17. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/abrupt_shaving.py +0 -0
  18. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/artifact_cleanup.py +0 -0
  19. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/gradual_expand_contract.py +0 -0
  20. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/segment_grouping.py +0 -0
  21. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/trend_classify.py +0 -0
  22. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/post_processing/segments_refine/update_neighbours.py +0 -0
  23. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/process_signals.py +0 -0
  24. {pytrendy-1.3.0.dev1 → pytrendy-1.4.0.dev1}/pytrendy/simpledtw.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrendy
3
- Version: 1.3.0.dev1
3
+ Version: 1.4.0.dev1
4
4
  Summary: Trend Detection in Python. Applicable for real-world industry use cases in time series.
5
5
  License: MIT License
6
6
 
@@ -83,7 +83,7 @@ PyTrendy is a robust solution for identifying and analysing trends in time serie
83
83
 
84
84
  ## Why PyTrendy?
85
85
 
86
- Most time series tools give you either a "trend component" (via decomposition) or "changepoints" (the moments of shift). PyTrendy is built for **labelled segment analysis**, answering *what trends existed, how strong were they, and when did they start and end?*
86
+ Most time series tools give you either a "trend component" (via decomposition) or "changepoints" (the moments of shift). PyTrendy is built for **automated trend detection**, answering *what trends existed, how strong were they, and when did they start and end?*
87
87
 
88
88
  - **Beyond step changes** - `ruptures` is the gold standard for abrupt shifts, but it doesn't handle gradual slope changes (digital marketing, stocks, energy). PyTrendy detects both in a single run.
89
89
  - **The flat/noise problem** - closest peers (`pytrendseries`, `trendet`, `tstrends`) over-fit trends on flat or noisy periods. PyTrendy's signal-processing and post-processing logic ensures trends are only detected when they are precise and valid.
@@ -20,7 +20,7 @@ PyTrendy is a robust solution for identifying and analysing trends in time serie
20
20
 
21
21
  ## Why PyTrendy?
22
22
 
23
- Most time series tools give you either a "trend component" (via decomposition) or "changepoints" (the moments of shift). PyTrendy is built for **labelled segment analysis**, answering *what trends existed, how strong were they, and when did they start and end?*
23
+ Most time series tools give you either a "trend component" (via decomposition) or "changepoints" (the moments of shift). PyTrendy is built for **automated trend detection**, answering *what trends existed, how strong were they, and when did they start and end?*
24
24
 
25
25
  - **Beyond step changes** - `ruptures` is the gold standard for abrupt shifts, but it doesn't handle gradual slope changes (digital marketing, stocks, energy). PyTrendy detects both in a single run.
26
26
  - **The flat/noise problem** - closest peers (`pytrendseries`, `trendet`, `tstrends`) over-fit trends on flat or noisy periods. PyTrendy's signal-processing and post-processing logic ensures trends are only detected when they are precise and valid.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pytrendy"
3
- version = "1.3.0.dev1"
3
+ version = "1.4.0.dev1"
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" },
@@ -9,7 +9,7 @@ from .post_processing.segments_analyse import analyse_segments
9
9
  from .io.plot_pytrendy import plot_pytrendy
10
10
  from .io.results_pytrendy import PyTrendyResults
11
11
 
12
- def detect_trends(df: pd.DataFrame, date_col: str, value_col: str, plot=True, method_params: dict=None, debug: bool=False ) -> PyTrendyResults:
12
+ def detect_trends(df: pd.DataFrame, date_col: str, value_col: str, plot=True, method_params: dict=None, plot_params: dict=None, debug: bool=False ) -> PyTrendyResults:
13
13
  """
14
14
  This is the main function that runs trend detection end-to-end.
15
15
 
@@ -42,6 +42,17 @@ def detect_trends(df: pd.DataFrame, date_col: str, value_col: str, plot=True, me
42
42
 
43
43
  - **abrupt_padding** (`int`): Number of days to pad around abrupt transitions. Defaults to `0`.
44
44
  - **avoid_noise** (`bool`): Whether to avoid noisy segments in trend detection. Defaults to `True`.
45
+ plot_params (dict, optional):
46
+ Optional dict to customise plot appearance. Only used when `plot` is `True`. Supported keys:
47
+ - **figsize** (`tuple`): Figure size as (width, height). Defaults to (20, 5).
48
+ - **title** (`str`): Plot title. Defaults to "PyTrendy Detection".
49
+ - **xlabel** (`str`): X-axis label. Defaults to "Date".
50
+ - **ylabel** (`str`): Y-axis label. Defaults to "Value".
51
+ - **colors** (`dict`): Dictionary mapping direction ('Up', 'Down', 'Flat', 'Noise') to matplotlib colors. Defaults to light variants.
52
+ - **alpha** (`float`): Transparency level for shaded regions. Defaults to 0.4.
53
+ - **grid** (`dict`): Grid configuration with keys 'visible' (bool), 'which' (str), 'color' (str), 'alpha' (float).
54
+ - **legend_loc** (`str`): Legend location. Defaults to "upper right".
55
+ - **legend_bbox_to_anchor** (`tuple`): Legend box anchor position. Defaults to (1, 1.15).
45
56
  debug (bool, optional):
46
57
  If `True` will run in debug mode, outputting various additional plots and print statements. Only recommended for developers of pytrendy.
47
58
  Defaults to `False`.
@@ -79,7 +90,7 @@ def detect_trends(df: pd.DataFrame, date_col: str, value_col: str, plot=True, me
79
90
  segments = get_segments(df)
80
91
  segments = refine_segments(df, value_col, segments, method_params)
81
92
  segments = analyse_segments(df, value_col, segments)
82
- if plot: plot_pytrendy(df, value_col, segments)
93
+ if plot: plot_pytrendy(df, value_col, segments, plot_params=plot_params)
83
94
 
84
95
  results = PyTrendyResults(segments)
85
96
  return results
@@ -5,7 +5,7 @@ import matplotlib.pyplot as plt
5
5
  import matplotlib.dates as mdates
6
6
  import matplotlib.patches as mpatches
7
7
 
8
- def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict], suppress_show: bool = False) -> plt.Figure:
8
+ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict], suppress_show: bool = False, plot_params: dict = None) -> plt.Figure:
9
9
  """
10
10
  Visualizes detected trend segments over the original time series signal.
11
11
 
@@ -21,21 +21,54 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
21
21
  List of segment dictionaries containing keys like `'start'`, `'end'`, `'direction'`, `'trend_class'`, and `'change_rank'`.
22
22
  suppress_show (bool, optional):
23
23
  If True, suppresses the automatic display of the plot with plt.show(). Defaults to False.
24
+ plot_params (dict, optional):
25
+ Optional dict to customise plot appearance. Supported keys:
26
+ - **figsize** (`tuple`): Figure size as (width, height). Defaults to (20, 5).
27
+ - **title** (`str`): Plot title. Defaults to "PyTrendy Detection".
28
+ - **xlabel** (`str`): X-axis label. Defaults to "Date".
29
+ - **ylabel** (`str`): Y-axis label. Defaults to "Value".
30
+ - **colors** (`dict`): Dictionary mapping direction ('Up', 'Down', 'Flat', 'Noise') to matplotlib colors. Defaults to light variants.
31
+ - **alpha** (`float`): Transparency level for shaded regions. Defaults to 0.4.
32
+ - **grid** (`dict`): Grid configuration with keys 'visible' (bool), 'which' (str), 'color' (str), 'alpha' (float).
33
+ - **legend_loc** (`str`): Legend location. Defaults to "upper right".
34
+ - **legend_bbox_to_anchor** (`tuple`): Legend box anchor position. Defaults to (1, 1.15).
24
35
 
25
36
  Returns:
26
37
  matplotlib.figure.Figure:
27
38
  The figure object containing the plot. Can be displayed with `plt.show()` or saved.
28
39
  """
29
40
 
30
- # Define colours
31
- color_map = {
32
- 'Up': 'lightgreen',
33
- 'Down': 'lightcoral',
34
- 'Flat': 'lightblue',
35
- 'Noise': 'lightgray',
41
+ # Default plotting params
42
+ default_params = {
43
+ 'figsize': (20, 5),
44
+ 'title': "PyTrendy Detection",
45
+ 'xlabel': "Date",
46
+ 'ylabel': "Value",
47
+ 'colors': {
48
+ 'Up': 'lightgreen',
49
+ 'Down': 'lightcoral',
50
+ 'Flat': 'lightblue',
51
+ 'Noise': 'lightgray',
52
+ },
53
+ 'alpha': 0.4,
54
+ 'grid': {'visible': True, 'which': 'major', 'color': 'gray', 'alpha': 0.3},
55
+ 'legend_loc': 'upper right',
56
+ 'legend_bbox_to_anchor': (1, 1.15)
36
57
  }
58
+ if plot_params:
59
+ plot_params = dict(plot_params) # avoid mutating caller's dict
60
+ custom_colors = plot_params.pop('colors', None)
61
+ custom_grid = plot_params.pop('grid', None)
62
+ default_params.update(plot_params)
63
+ if custom_colors:
64
+ default_params['colors'].update(custom_colors)
65
+ if custom_grid:
66
+ default_params['grid'].update(custom_grid)
37
67
 
38
- fig, ax = plt.subplots(figsize=(20, 5))
68
+ # Define colors
69
+ color_map = default_params['colors']
70
+
71
+ fig, ax = plt.subplots(figsize=default_params['figsize'])
39
72
 
40
73
  # Plot the value line
41
74
  ax.plot(df.index, df[value_col], color='black', lw=1)
@@ -84,7 +117,7 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
84
117
  prev_new_end = (prev_end + pd.Timedelta(days=1)).strftime('%Y-%m-%d')
85
118
  mask = (df.index >= prev_end) & (df.index <= prev_new_end)
86
119
  prev_color = color_map.get(segments_enhanced[i-1]['direction'], 'gray')
87
- ax.fill_between(df.index[mask], ymin, ymax, color=prev_color, alpha=0.4)
120
+ ax.fill_between(df.index[mask], ymin, ymax, color=prev_color, alpha=default_params['alpha'])
88
121
 
89
122
 
90
123
  # Adjust ends when appropriate
@@ -108,7 +141,7 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
108
141
  end = end
109
142
 
110
143
  mask = (df.index >= start) & (df.index <= end)
111
- ax.fill_between(df.index[mask], ymin, ymax, color=color, alpha=0.4)
144
+ ax.fill_between(df.index[mask], ymin, ymax, color=color, alpha=default_params['alpha'])
112
145
 
113
146
  # Add ranking if up/down trend
114
147
  if 'change_rank' in seg and seg['direction'] in ['Up', 'Down']:
@@ -140,21 +173,21 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
140
173
  plt.setp(ax.get_xticklabels(), rotation=90, ha='right')
141
174
 
142
175
  # Optional: show grid lines for both
143
- ax.grid(True, which='major', color='gray', alpha=0.3)
176
+ ax.grid(default_params['grid']['visible'], **{k: v for k, v in default_params['grid'].items() if k != 'visible'})
144
177
 
145
- ax.set_title("PyTrendy Detection", fontsize=20)
146
- ax.set_xlabel("Date")
147
- ax.set_ylabel("Value")
178
+ ax.set_title(default_params['title'], fontsize=20)
179
+ ax.set_xlabel(default_params['xlabel'])
180
+ ax.set_ylabel(default_params['ylabel'])
148
181
 
149
182
  # Create custom legend handles (colored boxes)
150
183
  legend_handles = [
151
- mpatches.Patch(color='lightgreen', alpha=0.4, label='Up'),
152
- mpatches.Patch(color='lightcoral', alpha=0.4, label='Down'),
153
- mpatches.Patch(color='lightblue', alpha=0.4, label='Flat'),
154
- mpatches.Patch(color='lightgray', alpha=0.4, label='Noise'),
184
+ mpatches.Patch(color=default_params['colors']['Up'], alpha=default_params['alpha'], label='Up'),
185
+ mpatches.Patch(color=default_params['colors']['Down'], alpha=default_params['alpha'], label='Down'),
186
+ mpatches.Patch(color=default_params['colors']['Flat'], alpha=default_params['alpha'], label='Flat'),
187
+ mpatches.Patch(color=default_params['colors']['Noise'], alpha=default_params['alpha'], label='Noise'),
155
188
  ]
156
- ax.legend(handles=legend_handles, loc='upper right',
157
- bbox_to_anchor=(1, 1.15), ncol=4, frameon=True)
189
+ ax.legend(handles=legend_handles, loc=default_params['legend_loc'],
190
+ bbox_to_anchor=default_params['legend_bbox_to_anchor'], ncol=4, frameon=True)
158
191
 
159
192
  plt.tight_layout()
160
193
  if not suppress_show:
File without changes