|
| 1 | +""" |
| 2 | +======================= |
| 3 | +The Lifecycle of a Plot |
| 4 | +======================= |
| 5 | +
|
| 6 | +This tutorial aims to show the beginning, middle, and end of a single |
| 7 | +visualization using Matplotlib. We'll begin with some raw data and |
| 8 | +end by saving a figure of a customized visualization. Along the way we'll try |
| 9 | +to highlight some neat features and best-practices using Matplotlib. |
| 10 | +
|
| 11 | +.. currentmodule:: matplotlib |
| 12 | +
|
| 13 | +.. note:: |
| 14 | +
|
| 15 | + This tutorial is based off of |
| 16 | + `this excellent blog post <http://pbpython.com/effective-matplotlib.html>`_ |
| 17 | + by Chris Moffitt. It was transformed into this tutorial by Chris Holdgraf. |
| 18 | +
|
| 19 | +A note on the Object-Oriented API vs Pyplot |
| 20 | +=========================================== |
| 21 | +
|
| 22 | +Matplotlib has two interfaces. The first is an object-oriented (OO) |
| 23 | +interface. In this case, we utilize an instance of :class:`axes.Axes` |
| 24 | +in order to render visualizations on an instance of :class:`figure.Figure`. |
| 25 | +
|
| 26 | +The second is based on MATLAB and uses |
| 27 | +a state-based interface. This is encapsulated in the :mod:`pyplot` |
| 28 | +module. See the :ref:`pyplot tutorials |
| 29 | +<sphx_glr_tutorials_01_introductory_pyplot.py>` |
| 30 | +for a more in-depth look at the pyplot interface. |
| 31 | +
|
| 32 | +Most of the terms are straightforward but the main thing to remember |
| 33 | +is that: |
| 34 | +
|
| 35 | +* The Figure is the final image that may contain 1 or more Axes. |
| 36 | +* The Axes represent an individual plot (don't confuse this with the word |
| 37 | + "axis", which refers to the x/y axis of a plot). |
| 38 | +
|
| 39 | +We call methods that do the plotting directly from the Axes, which gives |
| 40 | +us much more flexibility and power in customizing our plot. See the |
| 41 | +:ref:`object-oriented examples <api_examples>` for many examples of how |
| 42 | +this approach is used. |
| 43 | +
|
| 44 | +.. note:: |
| 45 | +
|
| 46 | + In general, try to use the object-oriented interface over the pyplot |
| 47 | + interface. |
| 48 | +
|
| 49 | +Our data |
| 50 | +======== |
| 51 | +
|
| 52 | +We'll use the data from the post from which this tutorial was derived. |
| 53 | +It contains sales information for a number of companies. |
| 54 | +""" |
| 55 | + |
| 56 | +# sphinx_gallery_thumbnail_number = 10 |
| 57 | +import numpy as np |
| 58 | +import matplotlib.pyplot as plt |
| 59 | +from matplotlib.ticker import FuncFormatter |
| 60 | + |
| 61 | +data = {'Barton LLC': 109438.50, |
| 62 | + 'Frami, Hills and Schmidt': 103569.59, |
| 63 | + 'Fritsch, Russel and Anderson': 112214.71, |
| 64 | + 'Jerde-Hilpert': 112591.43, |
| 65 | + 'Keeling LLC': 100934.30, |
| 66 | + 'Koepp Ltd': 103660.54, |
| 67 | + 'Kulas Inc': 137351.96, |
| 68 | + 'Trantow-Barrows': 123381.38, |
| 69 | + 'White-Trantow': 135841.99, |
| 70 | + 'Will LLC': 104437.60} |
| 71 | +group_data = list(data.values()) |
| 72 | +group_names = list(data.keys()) |
| 73 | +group_mean = np.mean(group_data) |
| 74 | + |
| 75 | +############################################################################### |
| 76 | +# Getting started |
| 77 | +# =============== |
| 78 | +# |
| 79 | +# This data is naturally visualized as a barplot, with one bar per |
| 80 | +# group. To do this with the object-oriented approach, we'll first generate |
| 81 | +# an instance of :class:`figure.Figure` and |
| 82 | +# :class:`axes.Axes`. The Figure is like a canvas, and the Axes |
| 83 | +# is a part of that canvas on which we will make a particular visualization. |
| 84 | +# |
| 85 | +# .. note:: |
| 86 | +# |
| 87 | +# Figures can have multiple axes on them. For information on how to do this, |
| 88 | +# see the :ref:`Tight Layout tutorial |
| 89 | +# <sphx_glr_tutorials_02_intermediate_tight_layout_guide.py>`. |
| 90 | + |
| 91 | +fig, ax = plt.subplots() |
| 92 | + |
| 93 | +############################################################################### |
| 94 | +# Now that we have an Axes instance, we can plot on top of it. |
| 95 | + |
| 96 | +fig, ax = plt.subplots() |
| 97 | +ax.barh(group_names, group_data) |
| 98 | + |
| 99 | +############################################################################### |
| 100 | +# Controlling the style |
| 101 | +# ===================== |
| 102 | +# |
| 103 | +# There are many styles available in Matplotlib in order to let you tailor |
| 104 | +# your visualization to your needs. To see a list of styles, we can use |
| 105 | +# :mod:`pyplot.style`. |
| 106 | + |
| 107 | +print(plt.style.available) |
| 108 | + |
| 109 | +############################################################################### |
| 110 | +# You can activate a style with the following: |
| 111 | + |
| 112 | +plt.style.use('fivethirtyeight') |
| 113 | + |
| 114 | +############################################################################### |
| 115 | +# Now let's remake the above plot to see how it looks: |
| 116 | + |
| 117 | +fig, ax = plt.subplots() |
| 118 | +ax.barh(group_names, group_data) |
| 119 | + |
| 120 | +############################################################################### |
| 121 | +# The style controls many things, such as color, linewidths, backgrounds, |
| 122 | +# etc. |
| 123 | +# |
| 124 | +# Customizing the plot |
| 125 | +# ==================== |
| 126 | +# |
| 127 | +# Now we've got a plot with the general look that we want, so let's fine-tune |
| 128 | +# it so that it's ready for print. First let's rotate the labels on the x-axis |
| 129 | +# so that they show up more clearly. We can gain access to these labels |
| 130 | +# with the :meth:`axes.Axes.get_xticklabels` method: |
| 131 | + |
| 132 | +fig, ax = plt.subplots() |
| 133 | +ax.barh(group_names, group_data) |
| 134 | +labels = ax.get_xticklabels() |
| 135 | + |
| 136 | +############################################################################### |
| 137 | +# If we'd like to set the property of many items at once, it's useful to use |
| 138 | +# the :func:`pyplot.setp` function. This will take a list (or many lists) of |
| 139 | +# Matplotlib objects, and attempt to set some style element of each one. |
| 140 | + |
| 141 | +fig, ax = plt.subplots() |
| 142 | +ax.barh(group_names, group_data) |
| 143 | +labels = ax.get_xticklabels() |
| 144 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 145 | + |
| 146 | +############################################################################### |
| 147 | +# It looks like this cut off some of the labels on the bottom. We can |
| 148 | +# tell Matplotlib to automatically make room for elements in the figures |
| 149 | +# that we create. To do this we'll set the ``autolayout`` value of our |
| 150 | +# rcParams. For more information on controlling the style, layout, and |
| 151 | +# other features of plots with rcParams, see |
| 152 | +# :ref:`sphx_glr_tutorials_01_introductory_customizing.py`. |
| 153 | + |
| 154 | +plt.rcParams.update({'figure.autolayout': True}) |
| 155 | + |
| 156 | +fig, ax = plt.subplots() |
| 157 | +ax.barh(group_names, group_data) |
| 158 | +labels = ax.get_xticklabels() |
| 159 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 160 | + |
| 161 | +############################################################################### |
| 162 | +# Next, we'll add labels to the plot. To do this with the OO interface, |
| 163 | +# we can use the :meth:`axes.Axes.set` method to set properties of this |
| 164 | +# Axes object. |
| 165 | + |
| 166 | +fig, ax = plt.subplots() |
| 167 | +ax.barh(group_names, group_data) |
| 168 | +labels = ax.get_xticklabels() |
| 169 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 170 | +ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', |
| 171 | + title='Company Revenue') |
| 172 | + |
| 173 | +############################################################################### |
| 174 | +# We can also adjust the size of this plot using the :func:`pyplot.subplots` |
| 175 | +# function. We can do this with the ``figsize`` kwarg. |
| 176 | +# |
| 177 | +# .. note:: |
| 178 | +# |
| 179 | +# While indexing in NumPy follows the form (row, column), the figsize |
| 180 | +# kwarg follows the form (width, height). This follows conventions in |
| 181 | +# visualization, which unfortunately are different from those of linear |
| 182 | +# algebra. |
| 183 | + |
| 184 | +fig, ax = plt.subplots(figsize=(8, 4)) |
| 185 | +ax.barh(group_names, group_data) |
| 186 | +labels = ax.get_xticklabels() |
| 187 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 188 | +ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', |
| 189 | + title='Company Revenue') |
| 190 | + |
| 191 | +############################################################################### |
| 192 | +# For labels, we can specify custom formatting guidelines in the form of |
| 193 | +# functions by using the :class:`ticker.FuncFormatter` class. Below we'll |
| 194 | +# define a function that takes an integer as input, and returns a string |
| 195 | +# as an output. |
| 196 | + |
| 197 | + |
| 198 | +def currency(x, pos): |
| 199 | + """The two args are the value and tick position""" |
| 200 | + if x >= 1e6: |
| 201 | + s = '${:1.1f}M'.format(x*1e-6) |
| 202 | + else: |
| 203 | + s = '${:1.0f}K'.format(x*1e-3) |
| 204 | + return s |
| 205 | + |
| 206 | +formatter = FuncFormatter(currency) |
| 207 | + |
| 208 | +############################################################################### |
| 209 | +# We can then apply this formatter to the labels on our plot. To do this, |
| 210 | +# we'll use the ``xaxis`` attribute of our axis. This lets you perform |
| 211 | +# actions on a specific axis on our plot. |
| 212 | + |
| 213 | +fig, ax = plt.subplots(figsize=(6, 8)) |
| 214 | +ax.barh(group_names, group_data) |
| 215 | +labels = ax.get_xticklabels() |
| 216 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 217 | + |
| 218 | +ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', |
| 219 | + title='Company Revenue') |
| 220 | +ax.xaxis.set_major_formatter(formatter) |
| 221 | + |
| 222 | +############################################################################### |
| 223 | +# Combining multiple visualizations |
| 224 | +# ================================= |
| 225 | +# |
| 226 | +# It is possible to draw multiple plot elements on the same instance of |
| 227 | +# :class:`axes.Axes`. To do this we simply need to call another one of |
| 228 | +# the plot methods on that axes object. |
| 229 | + |
| 230 | +fig, ax = plt.subplots(figsize=(8, 8)) |
| 231 | +ax.barh(group_names, group_data) |
| 232 | +labels = ax.get_xticklabels() |
| 233 | +plt.setp(labels, rotation=45, horizontalalignment='right') |
| 234 | + |
| 235 | +# Add a vertical line, here we set the style in the function call |
| 236 | +ax.axvline(group_mean, ls='--', color='r') |
| 237 | + |
| 238 | +# Annotate new companies |
| 239 | +for group in [3, 5, 8]: |
| 240 | + ax.text(145000, group, "New Company", fontsize=10, |
| 241 | + verticalalignment="center") |
| 242 | + |
| 243 | +# Now we'll move our title up since it's getting a little cramped |
| 244 | +ax.title.set(y=1.05) |
| 245 | + |
| 246 | +ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', |
| 247 | + title='Company Revenue') |
| 248 | +ax.xaxis.set_major_formatter(formatter) |
| 249 | +ax.set_xticks([0, 25e3, 50e3, 75e3, 100e3, 125e3]) |
| 250 | +fig.subplots_adjust(right=.1) |
| 251 | + |
| 252 | +plt.show() |
| 253 | + |
| 254 | +############################################################################### |
| 255 | +# Saving our plot |
| 256 | +# =============== |
| 257 | +# |
| 258 | +# Now that we're happy with the outcome of our plot, we want to save it to |
| 259 | +# disk. There are many file formats we can save to in Matplotlib. To see |
| 260 | +# a list of available options, use: |
| 261 | + |
| 262 | +print(fig.canvas.get_supported_filetypes()) |
| 263 | + |
| 264 | +############################################################################### |
| 265 | +# We can then use the :meth:`figure.Figure.savefig` in order to save the figure |
| 266 | +# to disk. Note that there are several useful flags we'll show below: |
| 267 | +# |
| 268 | +# * ``transparent=True`` makes the background of the saved figure transparent |
| 269 | +# if the format supports it. |
| 270 | +# * ``dpi=80`` controls the resolution (dots per square inch) of the output. |
| 271 | +# * ``bbox_inches="tight"`` fits the bounds of the figure to our plot. |
| 272 | + |
| 273 | +# Uncomment this line to save the figure. |
| 274 | +# fig.savefig('sales.png', transparent=False, dpi=80, bbox_inches="tight") |
0 commit comments