<jupyter_start><jupyter_text>Customizing Plot Legends Plot legends give meaning to a visualization, assigning meaning to the various plot elements.We previously saw how to create a simple legend; here we'll take a look at customizing the placement and aesthetics of the legend in Matplotlib.The simplest legend can be created with the ``plt.legend()`` command, which automatically creates a legend for any labeled plot elements:<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
x = np.linspace(0, 10, 1000)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), '-b', label='Sine')
ax.plot(x, np.cos(x), '--r', label='Cosine')
ax.axis('equal')
leg = ax.legend();<jupyter_output><empty_output><jupyter_text>But there are many ways we might want to customize such a legend.For example, we can specify the location and turn off the frame:<jupyter_code>ax.legend(loc='upper left', frameon=False)
fig<jupyter_output><empty_output><jupyter_text>We can use the ``ncol`` command to specify the number of columns in the legend:<jupyter_code>ax.legend(frameon=False, loc='lower center', ncol=2)
fig<jupyter_output><empty_output><jupyter_text>We can use a rounded box (``fancybox``) or add a shadow, change the transparency (alpha value) of the frame, or change the padding around the text:<jupyter_code>ax.legend(fancybox=True, framealpha=1, shadow=True, borderpad=1)
fig<jupyter_output><empty_output><jupyter_text>For more information on available legend options, see the ``plt.legend`` docstring.  Choosing Elements for the LegendAs we have already seen, the legend includes all labeled elements by default.If this is not what is desired, we can fine-tune which elements and labels appear in the legend by using the objects returned by plot commands.The ``plt.plot()`` command is able to create multiple lines at once, and returns a list of created line instances.Passing any of these to ``plt.legend()`` will tell it which to identify, along with the labels we'd like to specify:<jupyter_code>y = np.sin(x[:, np.newaxis] + np.pi * np.arange(0, 2, 0.5))
lines = plt.plot(x, y)

# lines is a list of plt.Line2D instances
plt.legend(lines[:2], ['first', 'second']);<jupyter_output><empty_output><jupyter_text>I generally find in practice that it is clearer to use the first method, applying labels to the plot elements you'd like to show on the legend:<jupyter_code>plt.plot(x, y[:, 0], label='first')
plt.plot(x, y[:, 1], label='second')
plt.plot(x, y[:, 2:])
plt.legend(framealpha=1, frameon=True);<jupyter_output><empty_output><jupyter_text>Notice that by default, the legend ignores all elements without a ``label`` attribute set.  Multiple LegendsSometimes when designing a plot you'd like to add multiple legends to the same axes.Unfortunately, Matplotlib does not make this easy: via the standard ``legend`` interface, it is only possible to create a single legend for the entire plot.If you try to create a second legend using ``plt.legend()`` or ``ax.legend()``, it will simply override the first one.We can work around this by creating a new legend artist from scratch, and then using the lower-level ``ax.add_artist()`` method to manually add the second artist to the plot:<jupyter_code>fig, ax = plt.subplots()

lines = []
styles = ['-', '--', '-.', ':']
x = np.linspace(0, 10, 1000)

for i in range(4):
    lines += ax.plot(x, np.sin(x - i * np.pi / 2),
                     styles[i], color='black')
ax.axis('equal')

# specify the lines and labels of the first legend
ax.legend(lines[:2], ['line A', 'line B'],
          loc='upper right', frameon=False)

# Create the second legend and add the artist manually.
from matplotlib.legend import Legend
leg = Legend(ax, lines[2:], ['line C', 'line D'],
             loc='lower right', frameon=False)
ax.add_artist(leg);<jupyter_output><empty_output><jupyter_text>This is a peek into the low-level artist objects that comprise any Matplotlib plot.If you examine the source code of ``ax.legend()`` (recall that you can do this with within the Jupyter notebook using ``ax.legend??``) you'll see that the function simply consists of some logic to create a suitable ``Legend`` artist, which is then saved in the ``legend_`` attribute and added to the figure when the plot is drawn.  Customizing Ticks Matplotlib's default tick locators and formatters are designed to be generally sufficient in many common situations, but are in no way optimal for every plot. This section will give several examples of adjusting the tick locations and formatting for the particular plot type you're interested in.Before we go into examples, it will be best for us to understand further the object hierarchy of Matplotlib plots.Matplotlib aims to have a Python object representing everything that appears on the plot: for example, recall that the ``figure`` is the bounding box within which plot elements appear.Each Matplotlib object can also act as a container of sub-objects: for example, each ``figure`` can contain one or more ``axes`` objects, each of which in turn contain other objects representing plot contents.The tick marks are no exception. Each ``axes`` has attributes ``xaxis`` and ``yaxis``, which in turn have attributes that contain all the properties of the lines, ticks, and labels that make up the axes.  Major and Minor TicksWithin each axis, there is the concept of a *major* tick mark, and a *minor* tick mark. As the names would imply, major ticks are usually bigger or more pronounced, while minor ticks are usually smaller. By default, Matplotlib rarely makes use of minor ticks, but one place you can see them is within logarithmic plots:<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
ax = plt.axes(xscale='log', yscale='log')
ax.grid();<jupyter_output><empty_output><jupyter_text>We see here that each major tick shows a large tickmark and a label, while each minor tick shows a smaller tickmark with no label.These tick properties—locations and labels—that is, can be customized by setting the ``formatter`` and ``locator`` objects of each axis. Let's examine these for the x axis of the just shown plot:<jupyter_code>print(ax.xaxis.get_major_locator())
print(ax.xaxis.get_minor_locator())
print(ax.xaxis.get_major_formatter())
print(ax.xaxis.get_minor_formatter())<jupyter_output><empty_output><jupyter_text>We see that both major and minor tick labels have their locations specified by a ``LogLocator`` (which makes sense for a logarithmic plot). Minor ticks, though, have their labels formatted by a ``NullFormatter``: this says that no labels will be shown.We'll now show a few examples of setting these locators and formatters for various plots.  Hiding Ticks or LabelsPerhaps the most common tick/label formatting operation is the act of hiding ticks or labels.This can be done using ``plt.NullLocator()`` and ``plt.NullFormatter()``, as shown here:<jupyter_code>ax = plt.axes()
ax.plot(np.random.rand(50))

ax.yaxis.set_major_locator(plt.NullLocator())
ax.xaxis.set_major_formatter(plt.NullFormatter())<jupyter_output><empty_output><jupyter_text>Notice that we've removed the labels (but kept the ticks/gridlines) from the x axis, and removed the ticks (and thus the labels as well) from the y axis.Having no ticks at all can be useful in many situations—for example, when you want to show a grid of images.  Reducing or Increasing the Number of TicksOne common problem with the default settings is that smaller subplots can end up with crowded labels.We can see this in the plot grid shown here:<jupyter_code>fig, ax = plt.subplots(4, 4, sharex=True, sharey=True)<jupyter_output><empty_output><jupyter_text>Particularly for the x ticks, the numbers nearly overlap and make them quite difficult to decipher.We can fix this with the ``plt.MaxNLocator()``, which allows us to specify the maximum number of ticks that will be displayed.Given this maximum number, Matplotlib will use internal logic to choose the particular tick locations:<jupyter_code># For every axis, set the x and y major locator
for axi in ax.flat:
    axi.xaxis.set_major_locator(plt.MaxNLocator(3))
    axi.yaxis.set_major_locator(plt.MaxNLocator(3))
fig<jupyter_output><empty_output><jupyter_text>This makes things much cleaner. If you want even more control over the locations of regularly-spaced ticks, you might also use ``plt.MultipleLocator``, which we'll discuss in the following section.  Fancy Tick FormatsMatplotlib's default tick formatting can leave a lot to be desired: it works well as a broad default, but sometimes you'd like do do something more.Consider this plot of a sine and a cosine:<jupyter_code># Plot a sine and cosine curve
fig, ax = plt.subplots()
x = np.linspace(0, 3 * np.pi, 1000)
ax.plot(x, np.sin(x), lw=3, label='Sine')
ax.plot(x, np.cos(x), lw=3, label='Cosine')

# Set up grid, legend, and limits
ax.grid(True)
ax.legend(frameon=False)
ax.axis('equal')
ax.set_xlim(0, 3 * np.pi);<jupyter_output><empty_output><jupyter_text>There are a couple changes we might like to make. First, it's more natural for this data to space the ticks and grid lines in multiples of $\pi$. We can do this by setting a ``MultipleLocator``, which locates ticks at a multiple of the number you provide. For good measure, we'll add both major and minor ticks in multiples of $\pi/4$:<jupyter_code>ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 4))
fig<jupyter_output><empty_output><jupyter_text>But now these tick labels look a little bit silly: we can see that they are multiples of $\pi$, but the decimal representation does not immediately convey this.To fix this, we can change the tick formatter. There's no built-in formatter for what we want to do, so we'll instead use ``plt.FuncFormatter``, which accepts a user-defined function giving fine-grained control over the tick outputs:<jupyter_code>def format_func(value, tick_number):
    # find number of multiples of pi/2
    N = int(np.round(2 * value / np.pi))
    if N == 0:
        return "0"
    elif N == 1:
        return r"$\pi/2$"
    elif N == 2:
        return r"$\pi$"
    elif N % 2 > 0:
        return r"${0}\pi/2$".format(N)
    else:
        return r"${0}\pi$".format(N // 2)

ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
fig<jupyter_output><empty_output><jupyter_text>This is much better! Notice that we've made use of Matplotlib's LaTeX support, specified by enclosing the string within dollar signs. This is very convenient for display of mathematical symbols and formulae: in this case, ``"$\pi$"`` is rendered as the Greek character $\pi$.The ``plt.FuncFormatter()`` offers extremely fine-grained control over the appearance of your plot ticks, and comes in very handy when preparing plots for presentation or publication.  Summary of Formatters and LocatorsWe've mentioned a couple of the available formatters and locators.We'll conclude this section by briefly listing all the built-in locator and formatter options. For more information on any of these, refer to the docstrings or to the Matplotlib online documentaion.Each of the following is available in the ``plt`` namespace:Locator class        | Description---------------------|-------------``NullLocator``      | No ticks``FixedLocator``     | Tick locations are fixed``IndexLocator``     | Locator for index plots (e.g., where x = range(len(y)))``LinearLocator``    | Evenly spaced ticks from min to max``LogLocator``       | Logarithmically ticks from min to max``MultipleLocator``  | Ticks and range are a multiple of base``MaxNLocator``      | Finds up to a max number of ticks at nice locations``AutoLocator``      | (Default.) MaxNLocator with simple defaults.``AutoMinorLocator`` | Locator for minor ticksFormatter Class       | Description----------------------|---------------``NullFormatter``     | No labels on the ticks``IndexFormatter``    | Set the strings from a list of labels``FixedFormatter``    | Set the strings manually for the labels``FuncFormatter``     | User-defined function sets the labels``FormatStrFormatter``| Use a format string for each value``ScalarFormatter``   | (Default.) Formatter for scalar values``LogFormatter``      | Default formatter for log axes  Customizing Matplotlib: Configurations and Stylesheets Matplotlib's default plot settings are often the subject of complaint among its users.While much is slated to change in the 2.0 Matplotlib release in late 2016, the ability to customize default settings helps bring the package inline with your own aesthetic preferences.Here we'll walk through some of Matplotlib's runtime configuration (rc) options, and take a look at the newer *stylesheets* feature, which contains some nice sets of default configurations.  Plot Customization by HandThrough this chapter, we've seen how it is possible to tweak individual plot settings to end up with something that looks a little bit nicer than the default.It's possible to do these customizations for each individual plot.For example, here is a fairly drab default histogram:<jupyter_code>import matplotlib.pyplot as plt
plt.style.use('classic')
import numpy as np

%matplotlib inline
x = np.random.randn(1000)
plt.hist(x);<jupyter_output><empty_output><jupyter_text>We can adjust this by hand to make it a much more visually pleasing plot:<jupyter_code># use a gray background
ax = plt.axes()
ax.set_facecolor('#E6E6E6')
ax.set_axisbelow(True)

# draw solid white grid lines
plt.grid(color='w', linestyle='solid')

# hide axis spines
for spine in ax.spines.values():
    spine.set_visible(False)
    
# hide top and right ticks
ax.xaxis.tick_bottom()
ax.yaxis.tick_left()

# lighten ticks and labels
ax.tick_params(colors='gray', direction='out')
for tick in ax.get_xticklabels():
    tick.set_color('gray')
for tick in ax.get_yticklabels():
    tick.set_color('gray')
    
# control face and edge color of histogram
ax.hist(x, edgecolor='#E6E6E6', color='#EE6666');
plt.show()<jupyter_output><empty_output><jupyter_text>This looks better, and you may recognize the look as inspired by the look of the R language's ggplot visualization package.But this took a whole lot of effort!We definitely do not want to have to do all that tweaking each time we create a plot.Fortunately, there is a way to adjust these defaults once in a way that will work for all plots.  Changing the Defaults: ``rcParams``Each time Matplotlib loads, it defines a runtime configuration (rc) containing the default styles for every plot element you create.This configuration can be adjusted at any time using the ``plt.rc`` convenience routine.Let's see what it looks like to modify the rc parameters so that our default plot will look similar to what we did before.We'll start by saving a copy of the current ``rcParams`` dictionary, so we can easily reset these changes in the current session:<jupyter_code>IPython_default = plt.rcParams.copy()<jupyter_output><empty_output><jupyter_text>Now we can use the ``plt.rc`` function to change some of these settings:<jupyter_code>from matplotlib import cycler
colors = cycler('color',
                ['#EE6666', '#3388BB', '#9988DD',
                 '#EECC55', '#88BB44', '#FFBBBB'])
plt.rc('axes', facecolor='#E6E6E6', edgecolor='none',
       axisbelow=True, grid=True, prop_cycle=colors)
plt.rc('grid', color='w', linestyle='solid')
plt.rc('xtick', direction='out', color='gray')
plt.rc('ytick', direction='out', color='gray')
plt.rc('patch', edgecolor='#E6E6E6')
plt.rc('lines', linewidth=2)<jupyter_output><empty_output><jupyter_text>With these settings defined, we can now create a plot and see our settings in action:<jupyter_code>plt.hist(x);<jupyter_output><empty_output><jupyter_text>Let's see what simple line plots look like with these rc parameters:<jupyter_code>for i in range(4):
    plt.plot(np.random.rand(10))<jupyter_output><empty_output><jupyter_text>StylesheetsThe version 1.4 release of Matplotlib in August 2014 added a very convenient ``style`` module, which includes a number of new default stylesheets, as well as the ability to create and package your own styles. These stylesheets are formatted similarly to the *.matplotlibrc* files mentioned earlier, but must be named with a *.mplstyle* extension.Even if you don't create your own style, the stylesheets included by default are extremely useful.The available styles are listed in ``plt.style.available``—here I'll list only the first five for brevity:<jupyter_code>plt.style.available[:5]<jupyter_output><empty_output><jupyter_text>The basic way to switch to a stylesheet is to call``` pythonplt.style.use('stylename')```But keep in mind that this will change the style for the rest of the session!Alternatively, you can use the style context manager, which sets a style temporarily:``` pythonwith plt.style.context('stylename'):    make_a_plot()``` Let's create a function that will make two basic types of plot:<jupyter_code>def hist_and_lines():
    np.random.seed(0)
    fig, ax = plt.subplots(1, 2, figsize=(11, 4))
    ax[0].hist(np.random.randn(1000))
    for i in range(3):
        ax[1].plot(np.random.rand(10))
    ax[1].legend(['a', 'b', 'c'], loc='lower left')<jupyter_output><empty_output><jupyter_text>We'll use this to explore how these plots look using the various built-in styles.  Default styleThe default style is what we've been seeing so far throughout the book; we'll start with that.First, let's reset our runtime configuration to the notebook default:<jupyter_code># reset rcParams
plt.rcParams.update(IPython_default);<jupyter_output><empty_output><jupyter_text>Now let's see how it looks:<jupyter_code>hist_and_lines()<jupyter_output><empty_output><jupyter_text>FiveThiryEight styleThe ``fivethirtyeight`` style mimics the graphics found on the popular [FiveThirtyEight website](https://fivethirtyeight.com).As you can see here, it is typified by bold colors, thick lines, and transparent axes:<jupyter_code>with plt.style.context('fivethirtyeight'):
    hist_and_lines()<jupyter_output><empty_output><jupyter_text>ggplotThe ``ggplot`` package in the R language is a very popular visualization tool.Matplotlib's ``ggplot`` style mimics the default styles from that package:<jupyter_code>with plt.style.context('ggplot'):
    hist_and_lines()<jupyter_output><empty_output><jupyter_text>*Bayesian Methods for Hackers( styleThere is a very nice short online book called [*Probabilistic Programming and Bayesian Methods for Hackers*](http://camdavidsonpilon.github.io/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/); it features figures created with Matplotlib, and uses a nice set of rc parameters to create a consistent and visually-appealing style throughout the book.This style is reproduced in the ``bmh`` stylesheet:<jupyter_code>with plt.style.context('bmh'):
    hist_and_lines()<jupyter_output><empty_output><jupyter_text>Dark backgroundFor figures used within presentations, it is often useful to have a dark rather than light background.The ``dark_background`` style provides this:<jupyter_code>with plt.style.context('dark_background'):
    hist_and_lines()<jupyter_output><empty_output><jupyter_text>GrayscaleSometimes you might find yourself preparing figures for a print publication that does not accept color figures.For this, the ``grayscale`` style, shown here, can be very useful:<jupyter_code>with plt.style.context('grayscale'):
    hist_and_lines()<jupyter_output><empty_output><jupyter_text>Seaborn styleMatplotlib also has stylesheets inspired by the Seaborn library (discussed more fully in [Visualization With Seaborn](04.14-Visualization-With-Seaborn.ipynb)).As we will see, these styles are loaded automatically when Seaborn is imported into a notebook.I've found these settings to be very nice, and tend to use them as defaults in my own data exploration.<jupyter_code>import seaborn
hist_and_lines()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09524768425291985, 'fraction_numerical': 0.006947241240434957, 'mean_word_length': 5.282416192283365, 'pattern_counts': {'":': 0, '<': 125, '<?xml version=': 0, '>': 126, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '133212', 'license': 'Unlicense', 'n_tokens_mistral': 5960, 'n_tokens_neox': 5650, 'n_words': 2371, 'path': 'day2/12. matplotlib - customizing legends, ticks, style.ipynb', 'repo_name': 'ubutnux/bosscha-python-workshop-2022', 'size': '19864'}