Coverage for src/ensae_teaching_cs/faq/faq_matplotlib.py: 49%

83 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-04-28 06:23 +0200

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Quelques problèmes récurrents avec `matplotlib <http://matplotlib.org/>`_. 

5""" 

6import numpy 

7 

8 

9def graph_style(style='ggplot'): # pragma: no cover 

10 """ 

11 Changes :epkg:`matplotlib` style. 

12 

13 @param style style 

14 

15 .. faqref:: 

16 :tag: matplotlib 

17 :title: Changer le style de graphique pour ggplot 

18 

19 .. index:: ggplot 

20 

21 Voir `Customizing plots with style sheets <http://matplotlib.org/users/style_sheets.html>`_ 

22 

23 :: 

24 

25 import matplotlib.pyplot as plt 

26 plt.style.use('ggplot') 

27 """ 

28 import matplotlib.pyplot as plt 

29 plt.style.use(style) 

30 

31 

32def close_all(): # pragma: no cover 

33 """ 

34 Closes every graph with :epkg:`matplotlib`. 

35 

36 .. faqref:: 

37 :tag: matplotlib 

38 :title: Plante après plusieurs graphes 

39 

40 Il peut arriver que matplotlib fasse planter python sans qu'aucune exception ne soit générée. 

41 L'article `matplotlib crashing Python <http://stackoverflow.com/questions/26955017/matplotlib-crashing-python>`_ 

42 suggère la solution suivante :: 

43 

44 import matplotlib.pyplot as plt 

45 plt.close('all') 

46 

47 Voir `close <http://matplotlib.org/api/pyplot_api.html?highlight=close#matplotlib.pyplot.close>`_. 

48 """ 

49 import matplotlib.pyplot as plt 

50 plt.close('all') 

51 

52 

53def graph_with_label(x, y, labels, barplot=True, title=None, figsize=(6, 4), style=None, 

54 ax=None, **kwargs): 

55 """ 

56 Creates a graph with :epkg:`matplotlib`. 

57 

58 @param x x 

59 @param y y 

60 @param labels x labels 

61 @param barplot boolean, True, uses bar, plot otherwise 

62 @param title if not None, sets the title 

63 @param figsize only if ax is not None 

64 @param style style 

65 @param ax existing :epkg:`Axes` or None if it must be created 

66 @param kwargs others parameters 

67 @return :epkg:`Axes` 

68 

69 .. faqref:: 

70 :tag: matplotlib 

71 :title: Comment ajuster les labels non numériques d'un graphe ? 

72 

73 .. index:: date, matplotlib 

74 

75 Lorsqu'on trace un graphique et qu'on veut ajouter des labels non numériques 

76 sur l'axe des abscisses (en particulier des dates), *matplotlib* 

77 ne fait pas apparaître tous les labels. Ainsi, si on a 50 points, 

78 50 abscisses et 50 labels, seuls les premiers labels apparaîtront 

79 comme ceci : 

80 

81 .. plot:: 

82 

83 import matplotlib.pyplot as plt 

84 x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 

85 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43] 

86 y = [1, 3, 10, 6, 3, 5, 3, 6, 4, 2, 3, 2, 11, 10, 4, 5, 2, 5, 4, 1, 1, 1, 3, 15, 5, 2, 1, 5, 3, 1, 3, 

87 2, 4, 5, 2, 12, 12, 5, 11, 2, 19, 21, 5, 2] 

88 xl = ['2014-w04', '2014-w05', '2014-w06', '2014-w07', '2014-w08', '2014-w09', 

89 '2014-w10', '2014-w11', 

90 '2014-w12', '2014-w13', '2014-w14', '2014-w15', '2014-w16', 

91 '2014-w17', '2014-w18', '2014-w19', '2014-w20', '2014-w21', '2014-w22', '2014-w23', 

92 '2014-w24', '2014-w25', '2014-w27', 

93 '2014-w29', '2014-w30', '2014-w31', '2014-w32', '2014-w34', '2014-w35', '2014-w36', 

94 '2014-w38', '2014-w39', '2014-w41', 

95 '2014-w42', '2014-w43', '2014-w44', '2014-w45', '2014-w46', '2014-w47', '2014-w48', 

96 '2014-w49', '2014-w50', '2014-w51', '2014-w52'] 

97 plt.close('all') 

98 fig,ax = plt.subplots(nrows=1,ncols=1,figsize=(10,4)) 

99 ax.bar( x,y ) 

100 ax.set_xticklabels( xl ) 

101 ax.grid(True) 

102 ax.set_title("commits") 

103 plt.show() 

104 

105 Or c'est cela qu'on veut : 

106 

107 .. plot:: 

108 

109 import matplotlib.pyplot as plt 

110 x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 

111 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43] 

112 y = [1, 3, 10, 6, 3, 5, 3, 6, 4, 2, 3, 2, 11, 10, 4, 5, 2, 5, 4, 1, 1, 1, 3, 15, 5, 2, 1, 5, 

113 3, 1, 3, 2, 4, 5, 2, 12, 12, 5, 11, 2, 19, 21, 5, 2] 

114 xl = ['2014-w04', '2014-w05', '2014-w06', '2014-w07', '2014-w08', '2014-w09', 

115 '2014-w10', '2014-w11', '2014-w12', '2014-w13', '2014-w14', 

116 '2014-w15', '2014-w16', '2014-w17', '2014-w18', '2014-w19', 

117 '2014-w20', '2014-w21', '2014-w22', '2014-w23', '2014-w24', '2014-w25', 

118 '2014-w27', '2014-w29', '2014-w30', '2014-w31', '2014-w32', '2014-w34', 

119 '2014-w35', '2014-w36', '2014-w38', '2014-w39', '2014-w41', 

120 '2014-w42', '2014-w43', '2014-w44', '2014-w45', '2014-w46', '2014-w47', 

121 '2014-w48', '2014-w49', '2014-w50', '2014-w51', '2014-w52'] 

122 plt.close('all') 

123 fig,ax = plt.subplots(nrows=1,ncols=1,figsize=(10,4)) 

124 ax.bar( x,y ) 

125 tig = ax.get_xticks() 

126 labs = [ ] 

127 for t in tig: 

128 if t in x: labs.append(xl[x.index(t)]) 

129 else: labs.append("") 

130 ax.set_xticklabels( labs ) 

131 ax.grid(True) 

132 ax.set_title("commits") 

133 plt.show() 

134 

135 Pour cela il faut d'abord utiliser la méthode 

136 `get_xticks <http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.get_xticks>`_ 

137 pour récupérer d'abord les graduations et n'afficher les labels que 

138 pour celles-ci 

139 (voir aussi `Custom ticks autoscaled when using imshow? 

140 <http://stackoverflow.com/questions/13409006/custom-ticks-autoscaled-when-using-imshow>`_). 

141 Voici un exemple de code :: 

142 

143 import matplotlib.pyplot as plt 

144 x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 

145 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43] 

146 y = [1, 3, 10, 6, 3, 5, 3, 6, 4, 2, 3, 2, 11, 10, 4, 5, 2, 5, 4, 1, 1, 1, 3, 15, 5, 2, 1, 5, 3, 1, 3, 2, 

147 4, 5, 2, 12, 12, 5, 11, 2, 19, 21, 5, 2] 

148 xl = ['2014-w04', '2014-w05', '2014-w06', '2014-w07', '2014-w08', '2014-w09', '2014-w10', '2014-w11', '2014-w12', '2014-w13', 

149 '2014-w14', '2014-w15', '2014-w16', '2014-w17', '2014-w18', '2014-w19', '2014-w20', '2014-w21', 

150 '2014-w22', '2014-w23', '2014-w24', '2014-w25', 

151 '2014-w27', '2014-w29', '2014-w30', '2014-w31', '2014-w32', '2014-w34', '2014-w35', '2014-w36', 

152 '2014-w38', '2014-w39', '2014-w41', '2014-w42', 

153 '2014-w43', '2014-w44', '2014-w45', '2014-w46', '2014-w47', '2014-w48', '2014-w49', 

154 '2014-w50', '2014-w51', '2014-w52'] 

155 plt.close('all') 

156 fig,ax = plt.subplots(nrows=1,ncols=1,figsize=(10,4)) 

157 ax.bar( x,y ) 

158 tig = ax.get_xticks() 

159 labs = [ ] 

160 for t in tig: 

161 if t in x: 

162 labs.append(xl[x.index(t)]) 

163 else: 

164 # une graduation peut être en dehors des labels proposés 

165 labs.append("") 

166 ax.set_xticklabels( labs ) 

167 ax.grid(True) 

168 ax.set_title("commits") 

169 plt.show() 

170 """ 

171 import matplotlib.pyplot as plt 

172 if ax is None: 

173 _, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 4)) 

174 

175 if barplot: 

176 if style is None: 

177 ax.bar(x, y, **kwargs) 

178 else: 

179 ax.bar(x, y, style=style, **kwargs) 

180 else: 

181 if style is None: 

182 ax.plot(x, y, **kwargs) 

183 else: 

184 ax.plot(x, y, style=style, **kwargs) 

185 tig = ax.get_xticks() 

186 xl = labels 

187 labs = [] 

188 for t in tig: 

189 if t in x: 

190 labs.append(xl[x.index(t)]) 

191 else: 

192 labs.append("") 

193 ax.set_xticklabels(labs) 

194 ax.grid(True) 

195 if title is not None: 

196 ax.set_title(title) 

197 return ax 

198 

199 

200def change_legend_location(ax, new_location="lower center"): 

201 """ 

202 Changes the location of the legend. 

203 

204 @param ax :epkg:`Axes` 

205 @param new_location new_location, see method :epkg:`legend` 

206 @return ax 

207 

208 .. faqref:: 

209 :tag: matplotlib 

210 :title: Comment changer l'emplacement de la légende ? 

211 

212 On cherche ici à changer l'emplacement de la légende alors que celle-ci a déjà été 

213 définie par ailleurs. C'est pratique lorsque celle-ci cache une partie du graphe 

214 qu'on veut absolument montrer. 

215 On ne dispose que de l'objet *ax* de type :epkg:`Axes`. 

216 On utilise pour cela la méthode :epkg:`legend` 

217 et le code suivant : 

218 

219 :: 

220 

221 handles, labels = ax.get_legend_handles_labels() 

222 ax.legend(handles, labels, loc="lower center") 

223 

224 Les différentes options pour le nouvel emplacement sont énoncées 

225 dans l'aide associée à la méthode :epkg:`legend`. 

226 """ 

227 handles, labels = ax.get_legend_handles_labels() 

228 ax.legend(handles, labels, loc=new_location) 

229 return ax 

230 

231 

232def avoid_overlapping_dates(fig, **options): 

233 """ 

234 Avoids overlapping dates by calling method 

235 :epkg:`autofmt_xdate`. 

236 

237 .. faqref:: 

238 :tag: matplotlib 

239 :title: Comment éviter les dates qui se superposent ? 

240 

241 La méthode :epkg:`autofmt_xdate` 

242 permet d'éviter les problèmes de dates 

243 qui se superposent. 

244 

245 :: 

246 

247 fig, ax = plt.subplots(...) 

248 # ... 

249 fig.autofmt_xdate() 

250 """ 

251 fig.autofmt_xdate(**options) 

252 

253 

254def graph_cities_default_lands(): 

255 """ 

256 Returns the default list of elements which can be added to a map. 

257 See `Features <https://scitools.org.uk/cartopy/docs/v0.15/matplotlib/feature_interface.html#cartopy.feature.GSHHSFeature>`_. 

258 

259 .. runpython:: 

260 :showcode: 

261 

262 from ensae_teaching_cs.faq.faq_matplotlib import graph_cities_default_lands 

263 print(graph_cities_default_lands()) 

264 """ 

265 return ["BORDERS", "COASTLINE", "LAKES", "LAND", "OCEAN", "RIVERS"] 

266 

267 

268def graph_cities(df, names=("Longitude", "Latitude", "City"), ax=None, linked=False, 

269 fLOG=None, loop=False, many=False, 

270 draw_coastlines=True, draw_countries=True, 

271 fill_continents=True, draw_parallels=True, 

272 draw_meridians=True, draw_map_boundary=True, 

273 **params): 

274 """ 

275 Plots the cities on a map with :epkg:`cartopy`. 

276 Only not empty names are displayed on the graph. 

277 

278 @param df dataframe 

279 @param names names of the column Latitude, Longitude, City 

280 @param ax existing ax 

281 @param linked draw lines between points 

282 @param loop add a final line to link the first point to the final one 

283 @param fLOG logging function 

284 @param params see below 

285 @param many change the return 

286 @param draw_coastlines draw coast lines 

287 @param draw_countries draw borders 

288 @param draw_map_boundary draw boundaries 

289 @param draw_meridians draw meridians 

290 @param draw_parallels draw parallels 

291 @param fill_continents fill continents 

292 @return *ax* or *fig, ax, m* if *many* is True 

293 

294 Additional parameters: 

295 

296 * projection: see `projections <https://scitools.org.uk/cartopy/docs/v0.15/crs/projections.html>`_, 

297 only used is *ax* is None 

298 * bounds: something like ``[lon1, lon2, lat1, lat2]`` 

299 * landscape: a list of strings about what needs to be on the map, 

300 see @see fn graph_cities_default_lands. 

301 * style, markersize, fontname, fontcolor, fontsize, fontweight, fontvalign 

302 

303 If the function returns the following error 

304 ``'AxesSubplot' object has no attribute 'add_feature'``, 

305 it means no projection was added to the axis. 

306 The function currently creates the following way: 

307 

308 :: 

309 

310 import cartopy.crs as ccrs 

311 import matplotlib.pyplot as plt 

312 projection = params.pop('projection', ccrs.PlateCarree()) 

313 fig = plt.figure(**params) 

314 ax = fig.add_subplot(1, 1, 1, projection) 

315 """ 

316 bounds = params.pop("bounds", None) 

317 landscape = params.pop("landscape", graph_cities_default_lands()) 

318 

319 style = params.pop('style', 'ro') 

320 markersize = params.pop('markersize', 6) 

321 fontname = params.pop('fontname', 'Arial') 

322 fontsize = str(params.pop('fontsize', '16')) 

323 fontcolor = params.pop('fontcolor', 'black') 

324 fontweight = params.pop('fontweight', 'normal') 

325 fontvalign = params.pop('fontvalign', 'bottom') 

326 

327 xx = list(df[names[0]]) 

328 yy = list(df[names[1]]) 

329 

330 if ax is None: 

331 import cartopy.crs as ccrs 

332 import matplotlib.pyplot as plt 

333 projection = params.pop( # pylint: disable=E0110 

334 'projection', ccrs.PlateCarree()) # pylint: disable=E0110 

335 fig = plt.figure(**params) 

336 ax = fig.add_subplot(1, 1, 1, projection=projection) 

337 else: 

338 fig = None 

339 

340 import cartopy.feature as cfeature 

341 for land in landscape: 

342 attr = getattr(cfeature, land) 

343 ax.add_feature(attr) 

344 

345 if linked and "-" not in style: 

346 style += "-" 

347 ax.plot(df[names[0]], df[names[1]], style, markersize=markersize) 

348 ax.set_title('France') 

349 

350 minx, maxx = min(xx), max(xx) 

351 miny, maxy = min(yy), max(yy) 

352 avex, avey = numpy.mean(xx), numpy.mean(yy) 

353 if fLOG: 

354 mes = "[graph_cities] Lon:[{0}, {1}] x Lat:[{2}, {3}] - mean={4}, {5} - linked={6}" 

355 fLOG(mes.format(minx, maxx, miny, maxy, avex, avey, linked)) 

356 if bounds: 

357 dx = (maxx - minx) / 10 

358 dy = (maxy - miny) / 10 

359 minx -= dx 

360 maxx += dx 

361 miny -= dy 

362 maxy += dy 

363 ax.set_extent(bounds) 

364 else: 

365 ax.set_extent([minx, maxx, miny, maxy]) 

366 if fLOG: 

367 fLOG("[graph_cities] ", [minx, maxx, miny, maxy]) 

368 

369 view = df[list(names)] 

370 for x, y, t in view.itertuples(index=False): 

371 if t is None or len(t) == 0: 

372 continue 

373 ax.text(x, y, t, 

374 fontname=fontname, size=fontsize, 

375 color=fontcolor, weight=fontweight, 

376 verticalalignment=fontvalign) 

377 return fig, ax