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
« 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
9def graph_style(style='ggplot'): # pragma: no cover
10 """
11 Changes :epkg:`matplotlib` style.
13 @param style style
15 .. faqref::
16 :tag: matplotlib
17 :title: Changer le style de graphique pour ggplot
19 .. index:: ggplot
21 Voir `Customizing plots with style sheets <http://matplotlib.org/users/style_sheets.html>`_
23 ::
25 import matplotlib.pyplot as plt
26 plt.style.use('ggplot')
27 """
28 import matplotlib.pyplot as plt
29 plt.style.use(style)
32def close_all(): # pragma: no cover
33 """
34 Closes every graph with :epkg:`matplotlib`.
36 .. faqref::
37 :tag: matplotlib
38 :title: Plante après plusieurs graphes
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 ::
44 import matplotlib.pyplot as plt
45 plt.close('all')
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')
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`.
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`
69 .. faqref::
70 :tag: matplotlib
71 :title: Comment ajuster les labels non numériques d'un graphe ?
73 .. index:: date, matplotlib
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 :
81 .. plot::
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()
105 Or c'est cela qu'on veut :
107 .. plot::
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()
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 ::
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))
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
200def change_legend_location(ax, new_location="lower center"):
201 """
202 Changes the location of the legend.
204 @param ax :epkg:`Axes`
205 @param new_location new_location, see method :epkg:`legend`
206 @return ax
208 .. faqref::
209 :tag: matplotlib
210 :title: Comment changer l'emplacement de la légende ?
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 :
219 ::
221 handles, labels = ax.get_legend_handles_labels()
222 ax.legend(handles, labels, loc="lower center")
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
232def avoid_overlapping_dates(fig, **options):
233 """
234 Avoids overlapping dates by calling method
235 :epkg:`autofmt_xdate`.
237 .. faqref::
238 :tag: matplotlib
239 :title: Comment éviter les dates qui se superposent ?
241 La méthode :epkg:`autofmt_xdate`
242 permet d'éviter les problèmes de dates
243 qui se superposent.
245 ::
247 fig, ax = plt.subplots(...)
248 # ...
249 fig.autofmt_xdate()
250 """
251 fig.autofmt_xdate(**options)
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>`_.
259 .. runpython::
260 :showcode:
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"]
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.
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
294 Additional parameters:
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
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:
308 ::
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())
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')
327 xx = list(df[names[0]])
328 yy = list(df[names[1]])
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
340 import cartopy.feature as cfeature
341 for land in landscape:
342 attr = getattr(cfeature, land)
343 ax.add_feature(attr)
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')
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])
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