pytrendy 1.3.1__tar.gz → 1.4.0.dev2__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.3.1 → pytrendy-1.4.0.dev2}/PKG-INFO +1 -1
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pyproject.toml +1 -1
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/detect_trends.py +14 -2
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/plot_pytrendy.py +63 -21
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/LICENSE +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/README.md +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/__init__.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/__init__.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/data/classes_signals.csv +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/data/series_synthetic.csv +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/data_loader.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/io/results_pytrendy.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/__init__.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_analyse.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_get.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/__init__.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/abrupt_shaving.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/artifact_cleanup.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/gradual_expand_contract.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/segment_grouping.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/trend_classify.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/update_neighbours.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/process_signals.py +0 -0
- {pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/simpledtw.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pytrendy"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.4.0.dev2"
|
|
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,18 @@ 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
|
+
|
|
48
|
+
- **figsize** (`tuple`): Figure size as (width, height). Defaults to (20, 5).
|
|
49
|
+
- **title** (`str`): Plot title. Defaults to "PyTrendy Detection".
|
|
50
|
+
- **xlabel** (`str`): X-axis label. Defaults to "Date".
|
|
51
|
+
- **ylabel** (`str`): Y-axis label. Defaults to "Value".
|
|
52
|
+
- **colors** (`dict`): Dictionary mapping direction ('Up', 'Down', 'Flat', 'Noise') to matplotlib colors. Defaults to light variants.
|
|
53
|
+
- **alpha** (`float`): Transparency level for shaded regions. Defaults to 0.4.
|
|
54
|
+
- **grid** (`dict`): Grid configuration with keys 'visible' (bool), 'which' (str), 'color' (str), 'alpha' (float).
|
|
55
|
+
- **legend_loc** (`str`): Legend location. Defaults to "upper right".
|
|
56
|
+
- **legend_bbox_to_anchor** (`tuple`): Legend box anchor position. Defaults to (1, 1.15).
|
|
45
57
|
debug (bool, optional):
|
|
46
58
|
If `True` will run in debug mode, outputting various additional plots and print statements. Only recommended for developers of pytrendy.
|
|
47
59
|
Defaults to `False`.
|
|
@@ -79,7 +91,7 @@ def detect_trends(df: pd.DataFrame, date_col: str, value_col: str, plot=True, me
|
|
|
79
91
|
segments = get_segments(df)
|
|
80
92
|
segments = refine_segments(df, value_col, segments, method_params)
|
|
81
93
|
segments = analyse_segments(df, value_col, segments)
|
|
82
|
-
if plot: plot_pytrendy(df, value_col, segments)
|
|
94
|
+
if plot: plot_pytrendy(df, value_col, segments, plot_params=plot_params)
|
|
83
95
|
|
|
84
96
|
results = PyTrendyResults(segments)
|
|
85
97
|
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,59 @@ 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
|
+
|
|
27
|
+
- **figsize** (`tuple`): Figure size as (width, height). Defaults to (20, 5).
|
|
28
|
+
- **title** (`str`): Plot title. Defaults to "PyTrendy Detection".
|
|
29
|
+
- **xlabel** (`str`): X-axis label. Defaults to "Date".
|
|
30
|
+
- **ylabel** (`str`): Y-axis label. Defaults to "Value".
|
|
31
|
+
- **colors** (`dict`): Dictionary mapping direction ('Up', 'Down', 'Flat', 'Noise') to matplotlib colors. Defaults to light variants.
|
|
32
|
+
- **alpha** (`float`): Transparency level for shaded regions. Defaults to 0.4.
|
|
33
|
+
- **grid** (`dict`): Grid configuration with keys 'visible' (bool), 'which' (str), 'color' (str), 'alpha' (float).
|
|
34
|
+
- **legend_loc** (`str`): Legend location. Defaults to "upper right".
|
|
35
|
+
- **legend_bbox_to_anchor** (`tuple`): Legend box anchor position. Defaults to (1, 1.15).
|
|
24
36
|
|
|
25
37
|
Returns:
|
|
26
38
|
matplotlib.figure.Figure:
|
|
27
39
|
The figure object containing the plot. Can be displayed with `plt.show()` or saved.
|
|
28
40
|
"""
|
|
29
41
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
42
|
+
# Default plotting params
|
|
43
|
+
default_params = {
|
|
44
|
+
'figsize': (20, 5),
|
|
45
|
+
'title': "PyTrendy Detection",
|
|
46
|
+
'xlabel': "Date",
|
|
47
|
+
'ylabel': "Value",
|
|
48
|
+
'colors': {
|
|
49
|
+
'Up': 'lightgreen',
|
|
50
|
+
'Down': 'lightcoral',
|
|
51
|
+
'Flat': 'lightblue',
|
|
52
|
+
'Noise': 'lightgray',
|
|
53
|
+
},
|
|
54
|
+
'alpha': 0.4,
|
|
55
|
+
'grid': {'visible': True, 'which': 'major', 'color': 'gray', 'alpha': 0.3},
|
|
56
|
+
'legend_loc': 'upper right',
|
|
57
|
+
'legend_bbox_to_anchor': (1, 1.15)
|
|
36
58
|
}
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
if plot_params:
|
|
60
|
+
plot_params = dict(plot_params) # avoid mutating caller's dict
|
|
61
|
+
has_custom_legend_loc = 'legend_loc' in plot_params
|
|
62
|
+
has_custom_legend_anchor = 'legend_bbox_to_anchor' in plot_params
|
|
63
|
+
custom_colors = plot_params.pop('colors', None)
|
|
64
|
+
custom_grid = plot_params.pop('grid', None)
|
|
65
|
+
default_params.update(plot_params)
|
|
66
|
+
if custom_colors:
|
|
67
|
+
default_params['colors'].update(custom_colors)
|
|
68
|
+
if custom_grid:
|
|
69
|
+
default_params['grid'].update(custom_grid)
|
|
70
|
+
if has_custom_legend_loc and not has_custom_legend_anchor:
|
|
71
|
+
default_params['legend_bbox_to_anchor'] = None
|
|
72
|
+
|
|
73
|
+
# Define colors
|
|
74
|
+
color_map = default_params['colors']
|
|
75
|
+
|
|
76
|
+
fig, ax = plt.subplots(figsize=default_params['figsize'])
|
|
39
77
|
|
|
40
78
|
# Plot the value line
|
|
41
79
|
ax.plot(df.index, df[value_col], color='black', lw=1)
|
|
@@ -84,7 +122,7 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
|
|
|
84
122
|
prev_new_end = (prev_end + pd.Timedelta(days=1)).strftime('%Y-%m-%d')
|
|
85
123
|
mask = (df.index >= prev_end) & (df.index <= prev_new_end)
|
|
86
124
|
prev_color = color_map.get(segments_enhanced[i-1]['direction'], 'gray')
|
|
87
|
-
ax.fill_between(df.index[mask], ymin, ymax, color=prev_color, alpha=
|
|
125
|
+
ax.fill_between(df.index[mask], ymin, ymax, color=prev_color, alpha=default_params['alpha'])
|
|
88
126
|
|
|
89
127
|
|
|
90
128
|
# Adjust ends when appropriate
|
|
@@ -108,7 +146,7 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
|
|
|
108
146
|
end = end
|
|
109
147
|
|
|
110
148
|
mask = (df.index >= start) & (df.index <= end)
|
|
111
|
-
ax.fill_between(df.index[mask], ymin, ymax, color=color, alpha=
|
|
149
|
+
ax.fill_between(df.index[mask], ymin, ymax, color=color, alpha=default_params['alpha'])
|
|
112
150
|
|
|
113
151
|
# Add ranking if up/down trend
|
|
114
152
|
if 'change_rank' in seg and seg['direction'] in ['Up', 'Down']:
|
|
@@ -140,21 +178,25 @@ def plot_pytrendy(df: pd.DataFrame, value_col: str, segments_enhanced: list[dict
|
|
|
140
178
|
plt.setp(ax.get_xticklabels(), rotation=90, ha='right')
|
|
141
179
|
|
|
142
180
|
# Optional: show grid lines for both
|
|
143
|
-
|
|
181
|
+
grid_params = default_params['grid']
|
|
182
|
+
if grid_params['visible']:
|
|
183
|
+
ax.grid(True, **{k: v for k, v in grid_params.items() if k != 'visible'})
|
|
184
|
+
else:
|
|
185
|
+
ax.grid(False, which='both')
|
|
144
186
|
|
|
145
|
-
ax.set_title(
|
|
146
|
-
ax.set_xlabel(
|
|
147
|
-
ax.set_ylabel(
|
|
187
|
+
ax.set_title(default_params['title'], fontsize=20)
|
|
188
|
+
ax.set_xlabel(default_params['xlabel'])
|
|
189
|
+
ax.set_ylabel(default_params['ylabel'])
|
|
148
190
|
|
|
149
191
|
# Create custom legend handles (colored boxes)
|
|
150
192
|
legend_handles = [
|
|
151
|
-
mpatches.Patch(color='
|
|
152
|
-
mpatches.Patch(color='
|
|
153
|
-
mpatches.Patch(color='
|
|
154
|
-
mpatches.Patch(color='
|
|
193
|
+
mpatches.Patch(color=default_params['colors']['Up'], alpha=default_params['alpha'], label='Up'),
|
|
194
|
+
mpatches.Patch(color=default_params['colors']['Down'], alpha=default_params['alpha'], label='Down'),
|
|
195
|
+
mpatches.Patch(color=default_params['colors']['Flat'], alpha=default_params['alpha'], label='Flat'),
|
|
196
|
+
mpatches.Patch(color=default_params['colors']['Noise'], alpha=default_params['alpha'], label='Noise'),
|
|
155
197
|
]
|
|
156
|
-
ax.legend(handles=legend_handles, loc='
|
|
157
|
-
bbox_to_anchor=
|
|
198
|
+
ax.legend(handles=legend_handles, loc=default_params['legend_loc'],
|
|
199
|
+
bbox_to_anchor=default_params['legend_bbox_to_anchor'], ncol=4, frameon=True)
|
|
158
200
|
|
|
159
201
|
plt.tight_layout()
|
|
160
202
|
if not suppress_show:
|
|
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.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/abrupt_shaving.py
RENAMED
|
File without changes
|
{pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/artifact_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/segment_grouping.py
RENAMED
|
File without changes
|
{pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/trend_classify.py
RENAMED
|
File without changes
|
{pytrendy-1.3.1 → pytrendy-1.4.0.dev2}/pytrendy/post_processing/segments_refine/update_neighbours.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|