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

1""" 

2@file 

3@brief Timeseries plots. 

4""" 

5import calendar 

6import datetime 

7 

8 

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. 

15 

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 

26 

27 .. plot:: 

28 

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 

34 

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()) 

39 

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 

48 

49 def coor(ti): 

50 days = ti.days 

51 x = days 

52 y = ti.seconds 

53 return x, y 

54 

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. 

61 

62 if ax is None: 

63 import matplotlib.pyplot as plt # pylint: disable=C0415 

64 ax = plt.gca() 

65 

66 import matplotlib.patches as patches # pylint: disable=R0402,C0415 

67 

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 

99 

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) 

104 

105 ax.add_patch(rect) 

106 

107 # days border 

108 xticks = [] 

109 if daynames is None: 

110 daynames = list(calendar.day_name) 

111 

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]) 

123 

124 # invert y axis 

125 ax.invert_yaxis() 

126 

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) 

138 

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) 

158 

159 ax.tick_params(axis='x', rotation=30) 

160 

161 # value2 

162 if value2 is not None: 

163 value = value2.copy() 

164 if normalise: 

165 value = value / max_value 

166 

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 

181 

182 x2 = x1 + value[i] * h 

183 

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 = [], [] 

191 

192 xs.append(x2) 

193 ys.append((y1 + y2) / 2) 

194 

195 if len(xs) > 0: 

196 ax.plot(xs, ys, color='orange', linewidth=2) 

197 

198 return ax