Gráficos em Python

A matplotlib é uma biblioteca de plotagem para a linguagem de programação Python, com sua extensão matemática numérica NumPy. Além delas, o pandas é muito útil para trabalhar com análise de dados. Para usar esses pacotes, existem diferentes opções para instalação: “sudo pip3 install numpy matplotlib pandas”, “sudo apt-get install python-matplotlib python-numpy python-pandas” ou “conda install matplotlib”.

Anatomia de uma figura matplotlib. Fonte: documentação
Anatomia de uma figura matplotlib. Fonte: documentação

A “anatomia” de uma figura vista segundo o matplotlib pode ser observada acima, para localização dos elementos. Veja um script com alguns exemplos de uso (testado em Python 2.7.6) para plotar uma série temporal de três sequências numéricas (com o eixo x mostrando datas):

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import sys
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as md
import dateutil

# Diretorio atual
path = os.path.dirname(os.path.realpath(__file__))
# Dia com os dados a serem plotados
dia = sys.argv[1:][0]
# Arquivo com dados
arquivo = path + '/dados/indices_' + dia + '.txt'
# Arquivo de imagem com grafico
figname = path + '/dados/Indices_' + dia + '.png'

# Ler arquivo e gravar dados em variavel
dados = pd.read_csv(arquivo, delimiter=',')
# Contar quantos numeros sao positivos
count = sum(x > 0 for x in dados.ss)
# Contar numero de linhas da tabela de dados
total = len(dados)
# Calcular percentual de numeros positivos
percentual = (float(count)*100.0)/float(total)

# String para do grafico
titulo = dia + " Estatistica: " + str(count) + "/" + str(total) + " => " + str(round(percentual,1)) + "%"
# Definir DPI
my_dpi=96
# Definir tamanho da imagem em pixeis
plt.figure(figsize=(1200/my_dpi, 800/my_dpi), dpi=my_dpi)
# Formatar eixo x com data: ano-mes-dia hora
dates = [dateutil.parser.parse(i) for i in dados.data]
xfmt = md.DateFormatter('%Y-%m-%d %Hh')
ax=plt.gca()
ax.xaxis.set_major_formatter(xfmt)
plt.xticks(rotation=30)
# Plotar 3 series de dados em funcao da data (cores definidas automaticamente)
plt.plot(dates, dados.obs, label="observado")
plt.plot(dates, dados.prev, label="previsto")
plt.plot(dates, dados.cor, label="correcao")
# Definir label, titulo e quadro com legenda
plt.ylabel("Variavel")
plt.title(estacao,fontsize=30)
plt.legend()
# Abre janela com grafico para visualizacao
plt.show()
# Salva figura em arquivo
plt.savefig(figname)

## Opcao para eixo com strings ##
# Criar vetor com mesmo tamanho do vetor de nomes
#x = range(0,total)
# Vetor de nomes
#my_xticks = dados.estacao
#ax=plt.gca()
# Ligar grid horizontal e vertical
#ax.yaxis.grid(True)
#ax.xaxis.grid(True)
# Plotar duas series de dados em funcao de sequencia numerica (cores definidas no codigo)
#plt.xticks(x, dados.estacao, rotation=30, fontsize=8)
#plt.plot(x, dados.obs, 'bo-', label="observao")
#plt.plot(x, dados.prev, 'go-', label="previsto")
#plt.plot(x, dados.cor, 'ro-', label="corrigido")

Esse script recebe uma data como argumento ao ser executado na linha de comando. Veja um exemplo:

$ python grafico.py 2016-03-03

Também precisa de um arquivo de dados cuja primeira linha tenha um cabeçalho com os nomes de cada coluna (acessada por “nome_da_tabela.nome_da_coluna”). Note, no calculo percentual realizado, como os números utilizados são inteiros, o resultado da conta será um inteiro também. Para resultar em um número real, os números da conta devem ser convertidos para float. A função “round” arredonda o valor para o número de casas decimais informado.

Veja um exemplo de gráfico gerado com o script acima:

grafico_python

A opção comentada mais abaixo no código tem um truque para imprimir o eixo x com strings. Como só dá pra entrar com números nos eixos, cria-se um vetor numérico ordenado de 0 até o mesmo tamanho do vetor de nomes. Depois, basta criar a variável “my_xticks” para receber o vetor de nomes e usar a função “xticks”.

Segue outro script para gerar um gráfico de três séries temporais. Nesse caso, serão utilizados dois eixos y (também está comentado no código para usar somente um eixo y).

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as dates
from datetime import datetime

# Definir arquivos e dados
path = os.path.dirname(os.path.realpath(__file__))
lugar = sys.argv[1:][0]
arquivo = path + '/output/prev_' + lugar + '.csv'
dados = pd.read_csv(arquivo, delimiter=',')

# converter data+hora para formato do matplotlib
x_orig = dados.tempo
x = [datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in x_orig]

# Definir tamanho da figura
my_dpi=96
fig = plt.figure(figsize=(1200/my_dpi, 800/my_dpi), dpi=my_dpi)

fig, ax1 = plt.subplots()
plt.gca().xaxis.set_major_formatter(dates.DateFormatter('%d/%m-%Hh'))
plt.gca().xaxis.set_major_locator(dates.DayLocator())

## Opção com 1 eixo y
#plt.plot(x,dados.fsi, marker='o', linestyle='')
#plt.plot(x,dados.vis, marker='o', linestyle='')
#plt.plot(x,dados.h, marker='o', linestyle='')
#plt.minorticks_on()
#plt.grid(which='major', linestyle='--')
#plt.grid(which='minor', linestyle=':')
#plt.gcf().autofmt_xdate()

## Opção com 2 eixos y
ax1.set_ylabel('VIS,H', color='r')
ax1.tick_params('y', colors='r')
lns1 = plt.plot(x,dados.vis, marker='o', linestyle='', color='r')
lns2 = plt.plot(x,dados.h, marker='o', linestyle='', color='y')
plt.minorticks_on()
plt.grid(which='major', linestyle='--')
plt.grid(which='minor', linestyle=':')
plt.gcf().autofmt_xdate()
ax2 = ax1.twinx()
lns3 = ax2.plot(x,dados.fsi, marker='o', linestyle='', color='b')
ax2.set_ylabel('FSI', color='b')
ax2.tick_params('y', colors='b')

## Legenda abaixo do gráfico
#plt.legend(['FSI', 'VIS', 'H'], loc='upper right')
#lns = lns1+lns2+lns3
#labs = [l.get_label() for l in lns]
#ax.legend(lns, labs, loc=0)
ax1.legend(loc='upper left', bbox_to_anchor=(0.1, -0.15), shadow=True, ncol=5)
ax2.legend(loc='upper left', bbox_to_anchor=(0.6, -0.15), shadow=True, ncol=5)

## Título
titulo = lugar + ' - teste'
plt.suptitle(titulo, fontsize=18)

## Salvar figura
figname = path + '/output/prev_' + lugar + '.png'
plt.savefig(figname)

#plt.show()
#sys.exit("fim de teste")

Outra diferença para o gráfico gerado com o outro script é que ele possui grades (principal e secundária), pontos em vez de linhas e a legenda em quadros fora da área de plotagem (está comentada uma opção para colocar todas as variáveis em um mesmo bloco, mas precisa definir um eixo ax comum para todas as séries). Veja a figura gerada:

Para gerar imagens sem que uma janela apareça, a maneira mais fácil de fazer isso é usar um back-end não interativo, como Agg (para PNGs), PDF, SVG ou PS. Em seu script, apenas chame a diretiva matplotlib.use() antes de importar pylab ou pyplot:

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

Esse tipo de solução é extremamente útil ao deixar um script rodando automaticamente e/ou em computador acessado remotamente – evita o erro do tipo XIO: fatal IO error 25 (Inappropriate ioctl for device) on X server “localhost:10.0”.

Mais opções podem ser vistas na documentação do matplotlib.

One comment

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.