MatplotLibAPI 2.0.2__tar.gz → 3.0.1__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.
- matplotlibapi-3.0.1/MatplotLibAPI/Bubble.py +127 -0
- matplotlibapi-3.0.1/MatplotLibAPI/Composite.py +78 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI/Network.py +61 -32
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI/Pivot.py +84 -94
- matplotlibapi-3.0.1/MatplotLibAPI/Style.py +171 -0
- matplotlibapi-3.0.1/MatplotLibAPI/Table.py +65 -0
- matplotlibapi-3.0.1/MatplotLibAPI/Timeserie.py +97 -0
- matplotlibapi-3.0.1/MatplotLibAPI/__init__.py +13 -0
- matplotlibapi-3.0.1/MatplotLibAPI/pdAccessor.py +125 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1/MatplotLibAPI.egg-info}/PKG-INFO +1 -1
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI.egg-info/SOURCES.txt +3 -2
- {MatplotLibAPI-2.0.2/MatplotLibAPI.egg-info → matplotlibapi-3.0.1}/PKG-INFO +1 -1
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/setup.py +1 -1
- MatplotLibAPI-2.0.2/MatplotLibAPI/Bubble.py +0 -108
- MatplotLibAPI-2.0.2/MatplotLibAPI/Composite.py +0 -109
- MatplotLibAPI-2.0.2/MatplotLibAPI/Table.py +0 -70
- MatplotLibAPI-2.0.2/MatplotLibAPI/TimeSeries.py +0 -136
- MatplotLibAPI-2.0.2/MatplotLibAPI/Utils.py +0 -204
- MatplotLibAPI-2.0.2/MatplotLibAPI/__init__.py +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/LICENSE +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI.egg-info/dependency_links.txt +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI.egg-info/requires.txt +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/MatplotLibAPI.egg-info/top_level.txt +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/README.md +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/pyproject.toml +0 -0
- {MatplotLibAPI-2.0.2 → matplotlibapi-3.0.1}/setup.cfg +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Hint for Visual Code Python Interactive window
|
|
2
|
+
# %%
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
from matplotlib.axes import Axes
|
|
7
|
+
import seaborn as sns
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from .Style import DynamicFuncFormatter, StyleTemplate, generate_ticks, _validate_panda, string_formatter, bmk_formatter, percent_formatter, format_func
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
BUBBLE_STYLE_TEMPLATE = StyleTemplate(
|
|
13
|
+
format_funcs={"label": string_formatter,
|
|
14
|
+
"x": bmk_formatter,
|
|
15
|
+
"y": percent_formatter,
|
|
16
|
+
"label": string_formatter,
|
|
17
|
+
"z": bmk_formatter},
|
|
18
|
+
yscale="log",
|
|
19
|
+
y_ticks=8,
|
|
20
|
+
x_ticks=8
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def plot_bubble(
|
|
25
|
+
pd_df: pd.DataFrame,
|
|
26
|
+
label: str,
|
|
27
|
+
x: str,
|
|
28
|
+
y: str,
|
|
29
|
+
z: str,
|
|
30
|
+
title: Optional[str] = "Test",
|
|
31
|
+
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
32
|
+
max_values: int = BUBBLE_STYLE_TEMPLATE,
|
|
33
|
+
center_to_mean: bool = False,
|
|
34
|
+
sort_by: Optional[str] = None,
|
|
35
|
+
ascending: bool = False,
|
|
36
|
+
ax: Optional[Axes] = None):
|
|
37
|
+
|
|
38
|
+
_validate_panda(pd_df, cols=[label, x, y, z], sort_by=sort_by)
|
|
39
|
+
style.format_funcs = format_func(
|
|
40
|
+
style.format_funcs, label=label, x=x, y=y, z=z)
|
|
41
|
+
if not sort_by:
|
|
42
|
+
sort_by = z
|
|
43
|
+
|
|
44
|
+
plot_df = pd_df[[label, x, y, z]].sort_values(
|
|
45
|
+
by=sort_by, ascending=ascending).head(max_values)
|
|
46
|
+
if center_to_mean:
|
|
47
|
+
x_col_mean = plot_df[x].mean()
|
|
48
|
+
plot_df[x] = plot_df[x] - x_col_mean
|
|
49
|
+
plot_df['quintile'] = pd.qcut(
|
|
50
|
+
plot_df[z], 5, labels=False)
|
|
51
|
+
|
|
52
|
+
# styling
|
|
53
|
+
|
|
54
|
+
plot_df["fontsize"] = plot_df['quintile'].map(style.font_mapping)
|
|
55
|
+
|
|
56
|
+
if not ax:
|
|
57
|
+
ax = plt.gca()
|
|
58
|
+
|
|
59
|
+
ax = sns.scatterplot(
|
|
60
|
+
data=plot_df,
|
|
61
|
+
x=x,
|
|
62
|
+
y=y,
|
|
63
|
+
size=z,
|
|
64
|
+
hue='quintile',
|
|
65
|
+
sizes=(100, 2000),
|
|
66
|
+
legend=False,
|
|
67
|
+
palette=sns.color_palette(style.palette, as_cmap=True),
|
|
68
|
+
edgecolor=style.background_color,
|
|
69
|
+
ax=ax)
|
|
70
|
+
ax.set_facecolor(style.background_color)
|
|
71
|
+
if style.xscale:
|
|
72
|
+
ax.set(xscale=style.xscale)
|
|
73
|
+
if style.yscale:
|
|
74
|
+
ax.set(yscale=style.yscale)
|
|
75
|
+
|
|
76
|
+
x_min = pd_df[x].min()
|
|
77
|
+
x_max = pd_df[x].max()
|
|
78
|
+
x_mean = pd_df[x].mean()
|
|
79
|
+
ax.set_xticks(generate_ticks(x_min, x_max, num_ticks=style.x_ticks))
|
|
80
|
+
ax.xaxis.grid(True, "major", linewidth=.5, color=style.font_color)
|
|
81
|
+
if style.format_funcs.get("x"):
|
|
82
|
+
ax.xaxis.set_major_formatter(
|
|
83
|
+
DynamicFuncFormatter(style.format_funcs.get("x")))
|
|
84
|
+
|
|
85
|
+
y_min = pd_df[y].min()
|
|
86
|
+
y_max = pd_df[y].max()
|
|
87
|
+
y_mean = pd_df[y].mean()
|
|
88
|
+
ax.set_yticks(generate_ticks(y_min, y_max, num_ticks=style.y_ticks))
|
|
89
|
+
ax.yaxis.grid(True, "major", linewidth=.5, color=style.font_color)
|
|
90
|
+
if style.format_funcs.get("y"):
|
|
91
|
+
ax.yaxis.set_major_formatter(
|
|
92
|
+
DynamicFuncFormatter(style.format_funcs.get("y")))
|
|
93
|
+
|
|
94
|
+
ax.tick_params(axis='both',
|
|
95
|
+
which='major',
|
|
96
|
+
colors=style.font_color,
|
|
97
|
+
labelsize=style.font_size)
|
|
98
|
+
|
|
99
|
+
ax.vlines(x=x_mean,
|
|
100
|
+
ymin=y_min,
|
|
101
|
+
ymax=y_max,
|
|
102
|
+
linestyle='--',
|
|
103
|
+
colors=style.font_color)
|
|
104
|
+
ax.hlines(y=y_mean,
|
|
105
|
+
xmin=x_min,
|
|
106
|
+
xmax=x_max,
|
|
107
|
+
linestyle='--',
|
|
108
|
+
colors=style.font_color)
|
|
109
|
+
|
|
110
|
+
for index, row in plot_df.iterrows():
|
|
111
|
+
x_value = row[x]
|
|
112
|
+
y_value = row[y]
|
|
113
|
+
s_value = str(row[label])
|
|
114
|
+
if style.format_funcs.get("label"):
|
|
115
|
+
s_value = style.format_funcs.get("label")(s_value)
|
|
116
|
+
fs = row["fontsize"]
|
|
117
|
+
ax.text(x_value,
|
|
118
|
+
y_value,
|
|
119
|
+
s_value,
|
|
120
|
+
horizontalalignment='center',
|
|
121
|
+
fontdict={'color': style.font_color, 'fontsize': fs})
|
|
122
|
+
if title:
|
|
123
|
+
ax.set_title(title, color=style.font_color, fontsize=style.font_size*2)
|
|
124
|
+
return ax
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# endregion
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Hint for Visual Code Python Interactive window
|
|
2
|
+
# %%
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from matplotlib.figure import Figure
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from .Bubble import plot_bubble, BUBBLE_STYLE_TEMPLATE
|
|
7
|
+
from .Table import plot_table
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
from .Style import StyleTemplate, _validate_panda,format_func
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def plot_composite_bubble(
|
|
13
|
+
pd_df: pd.DataFrame,
|
|
14
|
+
label: str,
|
|
15
|
+
x: str,
|
|
16
|
+
y: str,
|
|
17
|
+
z: str,
|
|
18
|
+
title: Optional[str] = "Test",
|
|
19
|
+
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
20
|
+
max_values: int = 50,
|
|
21
|
+
center_to_mean: bool = False,
|
|
22
|
+
sort_by: Optional[str] = None,
|
|
23
|
+
ascending: bool = False,
|
|
24
|
+
table_rows: int = 10,
|
|
25
|
+
figsize: Tuple[float, float] = (19.2, 10.8)) -> Figure:
|
|
26
|
+
|
|
27
|
+
_validate_panda(pd_df, cols=[label, x, y, z], sort_by=sort_by)
|
|
28
|
+
style.format_funcs=format_func(style.format_funcs,label=label,x=x,y=y)
|
|
29
|
+
fig = plt.figure(figsize=figsize)
|
|
30
|
+
fig.patch.set_facecolor("black")
|
|
31
|
+
grid = plt.GridSpec(2, 2, height_ratios=[2, 1], width_ratios=[1, 1])
|
|
32
|
+
ax = fig.add_subplot(grid[0, 0:])
|
|
33
|
+
ax = plot_bubble(pd_df=pd_df,
|
|
34
|
+
label=label,
|
|
35
|
+
x=x,
|
|
36
|
+
y=y,
|
|
37
|
+
z=z,
|
|
38
|
+
title=title,
|
|
39
|
+
style=style,
|
|
40
|
+
max_values=max_values,
|
|
41
|
+
center_to_mean=center_to_mean,
|
|
42
|
+
sort_by=sort_by,
|
|
43
|
+
ascending=ascending,
|
|
44
|
+
ax=ax)
|
|
45
|
+
|
|
46
|
+
if "label" in style.format_funcs:
|
|
47
|
+
style.format_funcs[label] = style.format_funcs["label"]
|
|
48
|
+
if "x" in style.format_funcs:
|
|
49
|
+
style.format_funcs[x] = style.format_funcs["x"]
|
|
50
|
+
if "y" in style.format_funcs:
|
|
51
|
+
style.format_funcs[y] = style.format_funcs["y"]
|
|
52
|
+
if "z" in style.format_funcs:
|
|
53
|
+
style.format_funcs[z] = style.format_funcs["z"]
|
|
54
|
+
|
|
55
|
+
ax2 = fig.add_subplot(grid[1, 0])
|
|
56
|
+
ax2 = plot_table(
|
|
57
|
+
pd_df=pd_df,
|
|
58
|
+
cols=[label, z, y, x],
|
|
59
|
+
title=f"Top {table_rows}",
|
|
60
|
+
ax=ax2,
|
|
61
|
+
sort_by=sort_by,
|
|
62
|
+
ascending=False,
|
|
63
|
+
max_values=table_rows,
|
|
64
|
+
style=style
|
|
65
|
+
)
|
|
66
|
+
ax3 = fig.add_subplot(grid[1, 1])
|
|
67
|
+
ax3 = plot_table(
|
|
68
|
+
pd_df=pd_df,
|
|
69
|
+
cols=[label, z, y, x],
|
|
70
|
+
title=f"Worst {table_rows}",
|
|
71
|
+
ax=ax3,
|
|
72
|
+
sort_by=sort_by,
|
|
73
|
+
ascending=True,
|
|
74
|
+
max_values=table_rows,
|
|
75
|
+
style=style
|
|
76
|
+
)
|
|
77
|
+
fig.tight_layout()
|
|
78
|
+
return fig
|
|
@@ -4,19 +4,27 @@ from collections.abc import Iterable
|
|
|
4
4
|
from typing import Any, Dict, List, Optional, Tuple
|
|
5
5
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
|
+
from matplotlib.axes import Axes
|
|
8
|
+
import seaborn as sns
|
|
7
9
|
import networkx as nx
|
|
8
10
|
import numpy as np
|
|
9
11
|
import pandas as pd
|
|
10
12
|
from networkx import Graph
|
|
11
13
|
from networkx.classes.graph import Graph
|
|
12
14
|
|
|
15
|
+
|
|
16
|
+
from .Style import StyleTemplate, string_formatter, _validate_panda,format_func
|
|
17
|
+
|
|
18
|
+
NETWORK_STYLE_TEMPLATE = StyleTemplate(
|
|
19
|
+
)
|
|
20
|
+
|
|
13
21
|
DEFAULT = {"MAX_EDGES": 100,
|
|
14
22
|
"MAX_NODES": 30,
|
|
15
23
|
"MIN_NODE_SIZE": 100,
|
|
16
24
|
"MAX_NODE_SIZE": 2000,
|
|
17
25
|
"MAX_EDGE_WIDTH": 10,
|
|
18
26
|
"GRAPH_SCALE": 2,
|
|
19
|
-
"MAX_FONT_SIZE":
|
|
27
|
+
"MAX_FONT_SIZE": 20,
|
|
20
28
|
"MIN_FONT_SIZE": 8
|
|
21
29
|
}
|
|
22
30
|
|
|
@@ -25,7 +33,7 @@ def softmax(x):
|
|
|
25
33
|
return (np.exp(x - np.max(x)) / np.exp(x - np.max(x)).sum())
|
|
26
34
|
|
|
27
35
|
|
|
28
|
-
def scale_weights(weights, scale_min=0,scale_max=1):
|
|
36
|
+
def scale_weights(weights, scale_min=0, scale_max=1):
|
|
29
37
|
deciles = np.percentile(weights, [10, 20, 30, 40, 50, 60, 70, 80, 90])
|
|
30
38
|
outs = np.searchsorted(deciles, weights)
|
|
31
39
|
return [out * (scale_max-scale_min)/len(deciles)+scale_min for out in outs]
|
|
@@ -160,8 +168,9 @@ class Graph(nx.Graph):
|
|
|
160
168
|
def subgraphX(self, node_list=None, max_edges: int = DEFAULT["MAX_EDGES"]):
|
|
161
169
|
if node_list is None:
|
|
162
170
|
node_list = self.nodes.sort("weight")[:DEFAULT["MAX_NODES"]]
|
|
163
|
-
connected_subgraph_nodes=list(self.find_connected_subgraph())
|
|
164
|
-
node_list = [
|
|
171
|
+
connected_subgraph_nodes = list(self.find_connected_subgraph())
|
|
172
|
+
node_list = [
|
|
173
|
+
node for node in node_list if node in connected_subgraph_nodes]
|
|
165
174
|
|
|
166
175
|
subgraph = nx.subgraph(
|
|
167
176
|
self, nbunch=node_list)
|
|
@@ -169,30 +178,39 @@ class Graph(nx.Graph):
|
|
|
169
178
|
subgraph = subgraph.edge_subgraph(list(edges)[:max_edges])
|
|
170
179
|
return subgraph
|
|
171
180
|
|
|
172
|
-
def plotX(self
|
|
181
|
+
def plotX(self,
|
|
182
|
+
title: str = "Test",
|
|
183
|
+
style: StyleTemplate = NETWORK_STYLE_TEMPLATE,
|
|
184
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
173
185
|
"""
|
|
174
186
|
Plots the degree distribution of the graph, including a degree rank plot and a degree histogram.
|
|
175
187
|
"""
|
|
176
188
|
degree_sequence = sorted([d for n, d in self.degree()], reverse=True)
|
|
177
189
|
dmax = max(degree_sequence)
|
|
178
|
-
|
|
179
|
-
|
|
190
|
+
sns.set_palette(style.palette)
|
|
191
|
+
if ax is None:
|
|
192
|
+
ax = plt.gca()
|
|
180
193
|
|
|
181
194
|
node_sizes, edge_widths, font_sizes = self.layout(
|
|
182
|
-
DEFAULT["
|
|
195
|
+
min_node_size=DEFAULT["MIN_NODE_SIZE"]/5,
|
|
196
|
+
max_node_size=DEFAULT["MAX_NODE_SIZE"],
|
|
197
|
+
max_edge_width=DEFAULT["MAX_EDGE_WIDTH"],
|
|
198
|
+
min_font_size=style.font_mapping.get(0),
|
|
199
|
+
max_font_size=style.font_mapping.get(4))
|
|
183
200
|
pos = nx.spring_layout(self, k=1)
|
|
184
201
|
# nodes
|
|
185
202
|
nx.draw_networkx_nodes(self,
|
|
186
203
|
pos,
|
|
187
204
|
ax=ax,
|
|
188
205
|
node_size=list(node_sizes),
|
|
189
|
-
|
|
190
|
-
cmap=plt.cm.
|
|
206
|
+
node_color=node_sizes,
|
|
207
|
+
cmap=plt.cm.get_cmap(style.palette))
|
|
191
208
|
# edges
|
|
192
209
|
nx.draw_networkx_edges(self,
|
|
193
210
|
pos,
|
|
194
211
|
ax=ax,
|
|
195
|
-
|
|
212
|
+
edge_color=style.font_color,
|
|
213
|
+
edge_cmap=plt.cm.get_cmap(style.palette),
|
|
196
214
|
width=edge_widths)
|
|
197
215
|
# labels
|
|
198
216
|
for font_size, nodes in font_sizes.items():
|
|
@@ -201,16 +219,13 @@ class Graph(nx.Graph):
|
|
|
201
219
|
pos,
|
|
202
220
|
ax=ax,
|
|
203
221
|
font_size=font_size,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
ax.set_title(
|
|
222
|
+
font_color=style.font_color,
|
|
223
|
+
labels={n: string_formatter(n) for n in nodes})
|
|
224
|
+
ax.set_facecolor(style.background_color)
|
|
225
|
+
ax.set_title(title, color=style.font_color, fontsize=style.font_size*2)
|
|
208
226
|
ax.set_axis_off()
|
|
209
227
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
fig.tight_layout()
|
|
213
|
-
return fig
|
|
228
|
+
return ax
|
|
214
229
|
|
|
215
230
|
def analysis(self, node_list: Optional[List] = None,
|
|
216
231
|
scale: int = DEFAULT["GRAPH_SCALE"],
|
|
@@ -242,12 +257,14 @@ class Graph(nx.Graph):
|
|
|
242
257
|
for node in list(H.nodes):
|
|
243
258
|
if H.degree(node) < 2:
|
|
244
259
|
# Remove the node and its incident edges
|
|
245
|
-
logging.info(
|
|
260
|
+
logging.info(
|
|
261
|
+
f'Removing the {node} node and its incident edges')
|
|
246
262
|
H.remove_node(node)
|
|
247
263
|
removed_node = True
|
|
248
264
|
break
|
|
249
265
|
|
|
250
266
|
return H
|
|
267
|
+
|
|
251
268
|
def top_k_edges(self, attribute: str, reverse: bool = True, k: int = 5) -> Dict[Any, List[Tuple[Any, Dict]]]:
|
|
252
269
|
"""
|
|
253
270
|
Returns the top k edges per node based on the given attribute.
|
|
@@ -275,10 +292,10 @@ class Graph(nx.Graph):
|
|
|
275
292
|
return top_list
|
|
276
293
|
|
|
277
294
|
@staticmethod
|
|
278
|
-
def from_pandas_edgelist(df,
|
|
279
|
-
source:
|
|
280
|
-
target:
|
|
281
|
-
weight:
|
|
295
|
+
def from_pandas_edgelist(df: pd.DataFrame,
|
|
296
|
+
source: str = "source",
|
|
297
|
+
target: str = "target",
|
|
298
|
+
weight: str = "weight"):
|
|
282
299
|
"""
|
|
283
300
|
Initialize netX instance with a simple dataframe
|
|
284
301
|
|
|
@@ -291,7 +308,7 @@ class Graph(nx.Graph):
|
|
|
291
308
|
G = Graph()
|
|
292
309
|
G = nx.from_pandas_edgelist(
|
|
293
310
|
df, source=source, target=target, edge_attr=weight, create_using=G)
|
|
294
|
-
G=G.find_connected_subgraph()
|
|
311
|
+
G = G.find_connected_subgraph()
|
|
295
312
|
|
|
296
313
|
edge_aggregates = G.top_k_edges(attribute=weight, k=10)
|
|
297
314
|
node_aggregates = {}
|
|
@@ -308,11 +325,23 @@ class Graph(nx.Graph):
|
|
|
308
325
|
G = G.edge_subgraph(edges=G.top_k_edges(attribute=weight))
|
|
309
326
|
return G
|
|
310
327
|
|
|
311
|
-
def plot_network(data:pd.DataFrame):
|
|
312
|
-
graph = Graph.from_pandas_edgelist(data)
|
|
313
|
-
graph = graph.subgraphX()
|
|
314
|
-
return graph.analysis()
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
328
|
|
|
329
|
+
def plot_network(pd_df: pd.DataFrame,
|
|
330
|
+
source: str = "source",
|
|
331
|
+
target: str = "target",
|
|
332
|
+
weight: str = "weight",
|
|
333
|
+
title: str = "Test",
|
|
334
|
+
style: StyleTemplate = NETWORK_STYLE_TEMPLATE,
|
|
335
|
+
sort_by: Optional[str] = None,
|
|
336
|
+
ascending: bool = False,
|
|
337
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
338
|
+
|
|
339
|
+
_validate_panda(pd_df, cols=[source, target, weight], sort_by=sort_by)
|
|
340
|
+
|
|
341
|
+
graph = Graph.from_pandas_edgelist(pd_df,
|
|
342
|
+
source=source,
|
|
343
|
+
target=target,
|
|
344
|
+
weight=weight)
|
|
345
|
+
return graph.plotX(title=title,
|
|
346
|
+
style=style,
|
|
347
|
+
ax=ax)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# Hint for Visual Code Python Interactive window
|
|
2
|
+
# %%
|
|
1
3
|
from typing import List, Optional, Union
|
|
2
4
|
|
|
3
5
|
import pandas as pd
|
|
@@ -7,113 +9,101 @@ import matplotlib.pyplot as plt
|
|
|
7
9
|
from matplotlib.axes import Axes
|
|
8
10
|
from matplotlib.dates import DateFormatter, MonthLocator
|
|
9
11
|
|
|
10
|
-
from .Utils import (PIVOTBARS_STYLE_TEMPLATE, PIVOTLINES_STYLE_TEMPLATE,
|
|
11
|
-
DynamicFuncFormatter, StyleTemplate, generate_ticks)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
from .Style import DynamicFuncFormatter, StyleTemplate, generate_ticks, string_formatter, _validate_panda, percent_formatter,format_func
|
|
15
|
+
|
|
16
|
+
PIVOTBARS_STYLE_TEMPLATE = StyleTemplate(
|
|
17
|
+
background_color='black',
|
|
18
|
+
fig_border='darkgrey',
|
|
19
|
+
font_color='white',
|
|
20
|
+
palette='magma',
|
|
21
|
+
format_funcs={"y": percent_formatter,
|
|
22
|
+
"label": string_formatter}
|
|
23
|
+
)
|
|
24
|
+
PIVOTLINES_STYLE_TEMPLATE = StyleTemplate(
|
|
25
|
+
background_color='white',
|
|
26
|
+
fig_border='lightgrey',
|
|
27
|
+
palette='viridis',
|
|
28
|
+
format_funcs={"y": percent_formatter, "label": string_formatter}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def plot_pivotbar(pd_df: pd.DataFrame,
|
|
32
|
+
label: str,
|
|
33
|
+
x: str,
|
|
34
|
+
y: str,
|
|
35
|
+
agg: str = "sum",
|
|
36
|
+
style: StyleTemplate = PIVOTBARS_STYLE_TEMPLATE,
|
|
37
|
+
title: Optional[str] = None,
|
|
38
|
+
sort_by: Optional[str] = None,
|
|
39
|
+
ascending: bool = False,
|
|
40
|
+
ax: Optional[Axes] = None):
|
|
41
|
+
|
|
42
|
+
_validate_panda(pd_df, cols=[label, x, y], sort_by=sort_by)
|
|
43
|
+
style.format_funcs=format_func(style.format_funcs,label=label,x=x,y=y)
|
|
44
|
+
pivot_df = pd.pivot_table(pd_df, values=y, index=[
|
|
45
|
+
x], columns=[label], aggfunc=agg)
|
|
46
|
+
# Reset index to make x a column again
|
|
47
|
+
pivot_df = pivot_df.reset_index()
|
|
48
|
+
|
|
49
|
+
if not ax:
|
|
50
|
+
ax = plt.gca()
|
|
51
|
+
|
|
52
|
+
# Plot each label's data
|
|
53
|
+
for column in pivot_df.columns[1:]:
|
|
54
|
+
_label = column
|
|
55
|
+
if style.format_funcs.get(column):
|
|
56
|
+
_label = style.format_funcs[column](column)
|
|
57
|
+
ax.bar(x=pivot_df[x],
|
|
58
|
+
height=pivot_df[column],
|
|
59
|
+
label=_label, alpha=0.7)
|
|
33
60
|
|
|
34
61
|
# Set labels and title
|
|
35
|
-
ax.set_ylabel(
|
|
36
|
-
ax.
|
|
37
|
-
|
|
62
|
+
ax.set_ylabel(string_formatter(y))
|
|
63
|
+
ax.set_xlabel(string_formatter(x))
|
|
64
|
+
if title:
|
|
65
|
+
ax.set_title(f'{title}')
|
|
66
|
+
ax.legend(fontsize=style.font_size-2,
|
|
67
|
+
title_fontsize=style.font_size+2,
|
|
68
|
+
labelcolor='linecolor',
|
|
69
|
+
facecolor=style.background_color)
|
|
38
70
|
|
|
39
71
|
ax.tick_params(axis='x', rotation=90)
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def plot_lines(ax: Axes,
|
|
44
|
-
data: pd.DataFrame,
|
|
45
|
-
x_col: str,
|
|
46
|
-
y_col: Union[str, List[str]],
|
|
47
|
-
style: Optional[StyleTemplate] = None,
|
|
48
|
-
fig_title: Optional[str] = None,
|
|
49
|
-
n_top: int = 4,
|
|
50
|
-
z_col: str = "browser") -> Axes:
|
|
51
|
-
"""
|
|
52
|
-
This function plots time series lines for the top n elements in the specified dimension.
|
|
53
|
-
|
|
54
|
-
Parameters:
|
|
55
|
-
ax (matplotlib.axes._axes.Axes): The ax to plot on.
|
|
56
|
-
data (pd.DataFrame): The data to plot.
|
|
57
|
-
metrics (Union[str, List[str]]): The column name(s) in data to plot.
|
|
58
|
-
date_col (str): The column name containing the date information.
|
|
59
|
-
... (other parameters): Various parameters to customize the plot.
|
|
60
|
-
date_format (str): The format of the date to display on the x-axis.
|
|
61
|
-
date_locator (matplotlib.dates.Locator): Locator object to determine the date ticks on the x-axis.
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
ax (matplotlib.axes._axes.Axes): The ax with the plot.
|
|
65
|
-
"""
|
|
72
|
+
return ax
|
|
66
73
|
|
|
67
|
-
# Validate inputs
|
|
68
|
-
if x_col not in data.columns:
|
|
69
|
-
raise ValueError(f"'{x_col}' column not found in the data")
|
|
70
|
-
if not isinstance(y_col, list) and not isinstance(y_col, str):
|
|
71
|
-
raise TypeError("'metrics' should be a string or a list of strings")
|
|
72
|
-
if isinstance(y_col, list) and not len(y_col) >= 2:
|
|
73
|
-
raise ValueError(
|
|
74
|
-
f"metrics should be 2 of lengths column not found in the data")
|
|
75
|
-
ax.clear()
|
|
76
|
-
if fig_title is not None:
|
|
77
|
-
ax.set_title(fig_title)
|
|
78
|
-
if style is None:
|
|
79
|
-
style = PIVOTLINES_STYLE_TEMPLATE
|
|
80
|
-
ax.figure.set_facecolor(style.fig_background_color)
|
|
81
|
-
ax.figure.set_edgecolor(style.fig_border)
|
|
82
74
|
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
def plot_lines(
|
|
76
|
+
data: pd.DataFrame,
|
|
77
|
+
label: str,
|
|
78
|
+
x: str,
|
|
79
|
+
y: Union[str, List[str]],
|
|
80
|
+
title: Optional[str] = None,
|
|
81
|
+
style: Optional[StyleTemplate] = PIVOTBARS_STYLE_TEMPLATE,
|
|
82
|
+
max_values: int = 4,
|
|
83
|
+
sort_by: Optional[str] = None,
|
|
84
|
+
ascending: bool = False,
|
|
85
|
+
ax: Optional[Axes] = None
|
|
86
|
+
) -> Axes:
|
|
87
|
+
|
|
88
|
+
if title is not None:
|
|
89
|
+
ax.set_title(title)
|
|
90
|
+
ax.figure.set_facecolor(style.background_color)
|
|
91
|
+
ax.figure.set_edgecolor(style.fig_border)
|
|
85
92
|
# Get the top n elements in the specified z
|
|
86
93
|
top_elements = data.groupby(
|
|
87
|
-
|
|
88
|
-
top_elements_df = data[data[
|
|
94
|
+
label)[y].sum().nlargest(max_values).index.tolist()
|
|
95
|
+
top_elements_df = data[data[label].isin(top_elements)]
|
|
89
96
|
y_min = 0
|
|
90
97
|
# Plot the time series lines for each of the top elements
|
|
91
98
|
for element in top_elements:
|
|
92
|
-
subset = top_elements_df[top_elements_df[
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
line_style = '-'
|
|
96
|
-
color = 'green'
|
|
97
|
-
elif element == "Android Webview":
|
|
98
|
-
line_style = '--'
|
|
99
|
-
color = 'green'
|
|
100
|
-
elif element == "Safari":
|
|
101
|
-
line_style = '-'
|
|
102
|
-
color = 'red'
|
|
103
|
-
elif element == "Safari (in-app)":
|
|
104
|
-
line_style = '--'
|
|
105
|
-
color = 'red'
|
|
106
|
-
else:
|
|
107
|
-
line_style = '-'
|
|
108
|
-
color = 'black'
|
|
109
|
-
y_min = min(y_min, subset[display_metric].min())
|
|
110
|
-
|
|
111
|
-
ax.plot(subset[x_col], subset[display_metric], label=element)
|
|
99
|
+
subset = top_elements_df[top_elements_df[label] == element]
|
|
100
|
+
y_min = min(y_min, subset[y].min())
|
|
101
|
+
ax.plot(subset[x], subset[y], label=element)
|
|
112
102
|
|
|
113
103
|
# Set x-axis date format and locator
|
|
114
104
|
if style.x_formatter is not None:
|
|
115
|
-
x_min = data[
|
|
116
|
-
x_max = data[
|
|
105
|
+
x_min = data[x].min()
|
|
106
|
+
x_max = data[x].max()
|
|
117
107
|
|
|
118
108
|
if style.x_formatter == "year_month_formatter":
|
|
119
109
|
ax.xaxis.set_major_locator(plt.matplotlib.dates.MonthLocator())
|
|
@@ -126,11 +116,11 @@ def plot_lines(ax: Axes,
|
|
|
126
116
|
ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
|
|
127
117
|
|
|
128
118
|
# Set title and labels
|
|
129
|
-
ax.set_xlabel(
|
|
130
|
-
y_max = data[
|
|
119
|
+
ax.set_xlabel(x)
|
|
120
|
+
y_max = data[y].dropna().quantile(0.95)
|
|
131
121
|
|
|
132
122
|
ax.set_ylim(y_min, y_max)
|
|
133
|
-
ax.set_ylabel(
|
|
123
|
+
ax.set_ylabel(y)
|
|
134
124
|
if style.y_formatter is not None:
|
|
135
125
|
ax.yaxis.set_major_formatter(
|
|
136
126
|
DynamicFuncFormatter(style.y_formatter))
|