Gráficos apresentáveis no Matplotlib

  • Python
  • Matplotlib

Criar um gráfico de boa qualidade para publicação não é uma tarefa fácil. Existe uma série de fatores que devem ser levados em consideração para que o gráfico resultante consiga transmitir informação de forma clara, precisa e eficiente.

Dentro desses fatores, pode-se citar alguns de natureza estética:

  1. O tamanho da figura deve se ajustar perfeitamente ao texto do artigo. Por exemplo, para artigos publicados pela American Physical Society (APS) a recomendação é que o tamanho impresso da figura seja de, no máximo, \(\SI{8.5}{\centi\meter}\) para coluna simples;
  2. O tipo e tamanho das fontes devem ser ajustados para aumentar a legibilidade (de acordo com as recomendações da editora).
  3. A espessura dos eixos, linhas, formato da legenda também são fatores que influenciam a aparência final do gráfico.

O biblioteca Matplotlib do Python é uma ferramenta incrivelmente flexível para a criação gráficos 2D. Podemos criar um gráfico no Matplotlib com o código

import numpy as np
import matplotlib.pyplot as plt

def meu_grafico(t):
    '''
    Cria um gráfico de uma série de funções dentro dos valores especificados 
    por um vetor t.
    '''
    fig, ax = plt.subplots(1)
    ax.plot(t, t**3, label='$x^3$')
    ax.plot(t, np.sin(2*t), label='$\sin{(2 x)}$')
    ax.plot(t, np.tanh(5*t), label='$\\tanh{(5 x)}$')

    ax.set_xlabel('$x$')
    ax.set_ylabel('$f(x)$')
    ax.legend()
    fig.tight_layout()
    plt.show()

x = np.linspace(-1, 1, 100)
meu_grafico(x)
plt.close('all')

2019-05-30T2158-mpl_raw_plot.png

Figura 1: Gráfico do Matplotlib com a configuração padrão.

Esse gráfico não é exatamente "bonito", possuindo um aspecto bastante cru, com uma fonte demasiadamente pequena e as linhas muito finas.

No Matplotlib podemos customizar o gráfico dinamicamente através do objeto rcParams. Por exemplo, podemos aumentar a espessura das linhas do nosso gráfico fazendo

import matplotlib as mpl
mpl.rcParams['lines.linewidth'] = 5 # Valor exageradamente alto, apenas 
                                    # para fins ilustrativos

Com essa configuração teremos:

2019-05-30T2158-mpl_linethick_plot.png

Figura 2: Gráfico com a largura de linha modificada.

Assim, vamos olhar alguns aspectos da configuração com o rcParams para deixar o gráfico mais apresentável para publicação. Serão focados três aspectos:

  1. Tamanho da figura,
  2. Configuração da fonte,
  3. Espessura das linhas.

Tamanho da figura

O tamanho da figura é um dos ajustes mais importantes. Um bom ajuste do tamanho da figura garante que você não vai precisar encolhê-la ou aumentá-la quando incluir no texto, garantindo que os tamanhos das fontes serão exatamente os valores definidos.

Vamos otimizar as dimensões da figura para um documento do LaTeX. Precisamos saber a largura (em pontos) do local onde a figura será colocada. Vamos considerar por exemplo a classe article. Através do documento

\documentclass{article} %% Mude para a classe apropriada

\begin{document}
\showthe\textwidth  % <-- Isto mostra a largura do texto
\end{document}

a compilação irá mostrar:

LaTeX2e <2018-12-01>
(/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2018/09/03 v1.4i Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo))
No file teste.aux.
> 345.0pt.
l.4 \showthe\textwidth
% <-- Isto mostra a largura do texto
? 

que nos diz que a largura máxima admitida pelo documento é de 345pt. Vamos utilizar o seguinte código para gerar as dimensões da página de uma forma mais atraente:

def figure_size(width, factor = 0.45):
    '''
    width: Número que o LaTeX nos disse
    factor: Fração da largura da página que se deseja que a figura ocupe
    '''
    fig_width_pt = width * factor
    inches_per_pt = 1.0/72.27
    golden_ratio = (np.sqrt(5) - 1)/2

    fig_width_in  = fig_width_pt * inches_per_pt  # Largura da figura (in)
    fig_height_in = fig_width_in * golden_ratio   # Altura da figura (in)
    return [fig_width_in, fig_height_in] # Dimensões da figura (in)

Assim, se desejarmos gerar uma figura com que ocupe 95% da página (cuja largura é 345pt), as dimensões "ótimas" da figura serão

[4.535076795350768, 2.8028316011177257]

e a figura resultante será

2019-05-30T2158-mpl_resized_plot.png

Figura 3: Gráfico com dimensões otimizadas.

Configuração de fonte

Otimizar o formato das fontes é essencial para que os seus gráficos façam parte do artigo, deixando a leitura mais fluida.

Tamanho da fonte

Como já ajustamos o tamanho da figura, o tamanho da fonte definido aqui será o valor real, já que não há necessidade de encolher ou aumentar a figura. Dessa forma, devemos ajustá-lo de acordo com o documento onde o gráfico será incluído.

Podemos exibir o tamanho da fonte utilizado pelo documento no LaTeX da seguinte forma:

\documentclass{article}
\begin{document}
\makeatletter
\show\f@size
\makeatother
\end{document}

que irá resultar em

This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2018/Arch Linux) (preloaded format=pdflatex)
restricted \write18 enabled.
entering extended mode
(./teste1.tex
LaTeX2e <2018-12-01>
(/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2018/09/03 v1.4i Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo)) (./teste1.aux)
> \f@size=macro:
->10.
l.4 \show\f@size
?

nesse caso, o tamanho da fonte do documento é 10pt.

Apesar de ser uma questão de gosto, eu prefiro ajustar o tamanho da fonte dos gráfico um ponto menor do que o do documento.

No Matplotlib existem três elementos textuais principais na figura: os nomes dos eixos; os rótulos dos ticks e o texto das legendas. Nesse caso podemos ajustá-los da seguinte forma:

rcParams['axes.labelsize'] = 9  
rcParams['xtick.labelsize'] = 9  
rcParams['ytick.labelsize'] = 9  
rcParams['legend.fontsize'] = 9

Tipo da fonte

O padrão do matplotlib não é a mesma fonte do LaTeX. Na verdade, ele nem mesmo usa o LaTeX para escrever o texto! Para mudarmos isso, fazemos:

rcParams['font.family'] = 'serif'  
rcParams['font.serif'] = ['Computer Modern Roman']  
rcParams['text.usetex'] = True  

Embora no meu caso a maioria dos jornais usem a fonte Computer Modern. Usar uma fonte não padrão requer um pouco mais de trabalho.

Espessura da linha

A espessura de linha padrão do Matplotlib fica razoavelmente boa na versão eletrônica, mas às vezes é quase invisível na versão impressa. Por isso normalmente eu aumento um pouco a espessura da linha, fazendo

rcParams['lines.linewidth'] = 1.5
rcParams['axes.linewidth'] = 1.5
rcParams['xtick.major.width'] = 1.7    # major tick width in points
rcParams['xtick.minor.width'] = 1.3 
rcParams['ytick.major.width'] = 1.7    # major tick width in points
rcParams['ytick.minor.width'] = 1.3 

Juntando tudo

Juntando as ideias colocadas acima, temos o seguinte código:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams

def figure_size(width, factor = 0.45):
    '''
    width: Número que o LaTeX nos disse
    factor: Fração da largura da página que se deseja que a figura ocupe
    '''
    fig_width_pt = width * factor
    inches_per_pt = 1.0/72.27
    golden_ratio = (np.sqrt(5) - 1)/2

    fig_width_in  = fig_width_pt * inches_per_pt  # Largura da figura (in)
    fig_height_in = fig_width_in * golden_ratio   # Altura da figura (in)
    return [fig_width_in, fig_height_in] # Dimensões da figura (in)

rcParams['axes.labelsize'] = 9  
rcParams['xtick.labelsize'] = 9  
rcParams['ytick.labelsize'] = 9  
rcParams['legend.fontsize'] = 9
rcParams['font.family'] = 'serif'  
rcParams['font.serif'] = ['Computer Modern Roman']  
rcParams['text.usetex'] = True  
rcParams['lines.linewidth'] = 1.5
rcParams['axes.linewidth'] = 1.5
rcParams['xtick.major.width'] = 1.7    # major tick width in points
rcParams['xtick.minor.width'] = 1.3 
rcParams['ytick.major.width'] = 1.7    # major tick width in points
rcParams['ytick.minor.width'] = 1.3 

def meu_grafico(t):
    '''
    Cria um gráfico de uma série de funções dentro dos valores especificados 
    por um vetor t.
    '''
    fig, ax = plt.subplots(figsize=figure_size(345.0, 0.95))
    ax.plot(t, t**3, label='$x^3$')
    ax.plot(t, np.sin(2*t), label='$\sin{(2 x)}$')
    ax.plot(t, np.tanh(5*t), label='$\\tanh{(5 x)}$')
    ax.set_xlabel('$x$')
    ax.set_ylabel('$f(x)$')
    ax.legend()
    fig.tight_layout()
    plt.show()

x = np.linspace(-1, 1, 100)
meu_grafico(x)
plt.close('all')

2019-05-30T2158-mpl_final_plot.png

Figura 4: Gráfico do Matplotlib customizado

E a figura resultante se encaixará muito melhor no documento!

Estilos

A versão 1.4 do Matplotlib (Agosto de 2014) adicionou um módulo bastante conveniente chamado style, que inclui um conjunto adicional de estilo, assim como a capacidade de criar os seus próprios. Os estilos são formatados de uma maneira semelhante ao arquivo padrão do Matplotlib, o .matplotlibrc, devendo ser nomeados com a extensão .mplstyle.

Os estilos padrão podem ser listados através do comando

import matplotlib.pyplot as plt

print(plt.style.available)

que mostra o resultado

['Solarize_Light2', '_classic_test', 'seaborn-talk', 'fast', 'seaborn-dark', 'seaborn-notebook', 'seaborn-pastel', 'seaborn-ticks', 'fivethirtyeight', 'seaborn-poster', 'seaborn-paper', 'seaborn-muted', 'seaborn-white', 'seaborn-bright', 'bmh', 'ggplot', 'seaborn', 'seaborn-deep', 'seaborn-darkgrid', 'seaborn-whitegrid', 'seaborn-dark-palette', 'grayscale', 'dark_background', 'tableau-colorblind10', 'seaborn-colorblind', 'classic']

e um estilo pode ser selecionado fazendo

plt.style.use('ggplot')

assim, podemos gerar o gráfico com a aparência do ggplot:

2019-05-30T2158-mpl_style_plot.png

Figura 5: Gráfico utilizando estilo não-padrão do Matplotlib.

Essa mudança vale para o resto da sessão. Caso deseje fazer apenas um gráfico dentro de um certo estilo, você pode usar uma modificação por contexto:

with plt.style.context('ggplot'):
    make_a_plot()

Conclusão

Os gráficos padrão do Matplotlib definitivamente não são dos mais belos de se ver, mas a biblioteca disponibiliza uma série de opções para customizar a aparência dos gráficos da forma que o usuário desejar. Mesmo que você não crie o próprio estilo, os que se encontram disponíveis por padrão são extremamente úteis.

Para maiores informações sobre como personalizar a aparência dos gráficos, dê uma olhada na seção do manual do rcParams.