Coverage for mlinsights/timeseries/plotting.py: 100%
115 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-28 08:46 +0100
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-28 08:46 +0100
1"""
2@file
3@brief Timeseries plots.
4"""
5import calendar
6import datetime
9def plot_week_timeseries(time, value, normalise=True,
10 label=None, h=0.85, value2=None,
11 label2=None, daynames=None,
12 xfmt="%1.0f", ax=None):
13 """
14 Shows a timeseries dispatched by days as bars.
16 :param time: dates
17 :param value: values to display as bars.
18 :param normalise: normalise data before showing it
19 :param label: label of the series
20 :param values2: second series to show as a line
21 :param label2: label of the second series
22 :param daynames: names to use for week day names (default is English)
23 :param xfmt: format number of the X axis
24 :param ax: existing axis
25 :return: axis
27 .. plot::
29 import datetime
30 import matplotlib.pyplot as plt
31 from mlinsights.timeseries.datasets import artificial_data
32 from mlinsights.timeseries.agg import aggregate_timeseries
33 from mlinsights.timeseries.plotting import plot_week_timeseries
35 dt1 = datetime.datetime(2019, 8, 1)
36 dt2 = datetime.datetime(2019, 9, 1)
37 data = artificial_data(dt1, dt2, minutes=15)
38 print(data.head())
40 agg = aggregate_timeseries(data, per='week')
41 plot_week_timeseries(
42 agg['weektime'], agg['y'], label="y",
43 value2=agg['y']/2, label2="y/2", normalise=False)
44 plt.show()
45 """
46 if time.shape[0] != value.shape[0]:
47 raise AssertionError("Dimension mismatch") # pragma: no cover
49 def coor(ti):
50 days = ti.days
51 x = days
52 y = ti.seconds
53 return x, y
55 max_value = value.max()
56 if value2 is not None:
57 max_value = max(max_value, value2.max())
58 value2 = value2 / max_value
59 value = value / max_value
60 input_maxy = 1.
62 if ax is None:
63 import matplotlib.pyplot as plt # pylint: disable=C0415
64 ax = plt.gca()
66 import matplotlib.patches as patches # pylint: disable=R0402,C0415
68 # bars
69 delta = None
70 maxx, maxy = None, None
71 first = True
72 for i in range(time.shape[0]):
73 ti = time[i]
74 if i < time.shape[0] - 1:
75 ti1 = time[i + 1]
76 delta = (ti1 - ti) if delta is None else min(delta, ti1 - ti)
77 if delta == 0:
78 raise RuntimeError( # pragma: no cover
79 "The timeseries contains duplicated time values.")
80 else:
81 ti1 = ti + delta
82 x1, y1 = coor(ti)
83 x2, y2 = coor(ti1)
84 if y2 < y1:
85 x2, y2 = coor(ti + delta)
86 y2 = y1 + (y2 - y1) * h
87 if first and label:
88 ax.plot([x1, x1 + value[i] * 0.8], [y1, y1],
89 'b', alpha=0.5, label=label)
90 first = False
91 if maxx is None:
92 maxx = (x1, x1 + input_maxy)
93 maxy = (y1, y2)
94 else:
95 maxx = (min(x1, maxx[0]), # pylint: disable=E1136
96 max(x1 + input_maxy, maxx[1])) # pylint: disable=E1136
97 maxy = (min(y1, maxy[0]), # pylint: disable=E1136
98 max(y2, maxy[1])) # pylint: disable=E1136
100 rect = patches.Rectangle((x1, y1), value[i] * h, y2 - y1,
101 linewidth=1, edgecolor=None,
102 facecolor='b', fill=True,
103 alpha=0.5)
105 ax.add_patch(rect)
107 # days border
108 xticks = []
109 if daynames is None:
110 daynames = list(calendar.day_name)
112 maxx = [(maxx[0] // 7) * 7, maxx[1]]
113 new_ymin = maxy[0] - (maxy[1] * 0.025 + maxy[0] * 0.975 - maxy[0])
114 for i in range(int(maxx[0]), int(maxx[1] + 0.1)):
115 x1i = maxx[0] + input_maxy * i
116 x2i = x1i + input_maxy
117 xticks.append(x1i)
118 ax.plot([x1i, x1i + input_maxy], [new_ymin, new_ymin], 'k', alpha=0.5)
119 ax.plot([x1i, x1i + input_maxy], [maxy[1], maxy[1]], 'k', alpha=0.5)
120 ax.plot([x1i, x1i], [maxy[0], maxy[1]], 'k', alpha=0.5)
121 ax.plot([x2i, x2i], [maxy[0], maxy[1]], 'k', alpha=0.5)
122 ax.text(x1i, new_ymin, daynames[i])
124 # invert y axis
125 ax.invert_yaxis()
127 # change y labels
128 nby = len(ax.get_yticklabels())
129 ys = ax.get_yticks()
130 ylabels = []
131 for i in range(nby):
132 dh = ys[i]
133 dt = datetime.timedelta(seconds=dh)
134 tx = "%dh%02d" % (dt.seconds // 3600,
135 60 * (dt.seconds / 3600 - dt.seconds // 3600))
136 ylabels.append(tx)
137 ax.set_yticklabels(ylabels)
139 # change x labels
140 xs = ax.get_xticks()
141 xticks = []
142 xlabels = []
143 for i in range(0, len(xs) - 1):
144 if xs[i] < 0:
145 continue
146 dx = xs[i] - int(xs[i] / input_maxy) * input_maxy
147 xlabels.append(dx if normalise else (dx * max_value))
148 xticks.append(xs[i])
149 dx = (xs[i] + xs[i + 1]) / 2
150 dx = dx - int(dx / input_maxy) * input_maxy
151 xlabels.append(dx if normalise else (dx * max_value))
152 xticks.append((xs[i] + xs[i + 1]) / 2)
153 if len(xticks) < len(xlabels):
154 xticks.append(xs[-1])
155 ax.set_xticks(xticks)
156 ax.set_xticklabels(
157 [xfmt % x for x in xlabels] if xfmt else xlabels)
159 ax.tick_params(axis='x', rotation=30)
161 # value2
162 if value2 is not None:
163 value = value2.copy()
164 if normalise:
165 value = value / max_value
167 first = True
168 xs = []
169 ys = []
170 for i in range(time.shape[0]):
171 ti = time[i]
172 if i < time.shape[0] - 1:
173 ti1 = time[i + 1]
174 else:
175 ti1 = ti + delta
176 x1, y1 = coor(ti)
177 x2, y2 = coor(ti1)
178 if y2 < y1:
179 x2, y2 = coor(ti + delta)
180 y2 = y1 + (y2 - y1) * h
182 x2 = x1 + value[i] * h
184 if len(ys) > 0 and y2 < ys[-1]:
185 if first and label2 is not None:
186 ax.plot(xs, ys, color='orange', linewidth=2, label=label2)
187 first = False
188 else:
189 ax.plot(xs, ys, color='orange', linewidth=2)
190 xs, ys = [], []
192 xs.append(x2)
193 ys.append((y1 + y2) / 2)
195 if len(xs) > 0:
196 ax.plot(xs, ys, color='orange', linewidth=2)
198 return ax