Matplotlib


Quando lidamos com dados precisamos, muitas vezes, visualizar de forma gráfica esses dados. Em muitas tarefas é útil, ou até mesmo essencial, que as conclusões das análises sejam mostradas através de gráficos. Mesmo nas fases iniciais de uma análise, na preparação e limpeza de dados, a visualização é importante visualizar para se compreender padrões, tendências e anomalias, tais como pontos fora da curva. Existem no Python inúmeras bibliotecas para visualização de dados e montagem de gráficos. Matplotlib é o módulo básico para uso em conjunto com o pandas.

A biblioteca é grande, com extensas possibilidades e tem sido usada como base para a elaboração de outros módulos gráficos, como o Seaborn. O aprendizado da biblioteca inteira pode demandar um esforço considerável mas o uso básico, suficiente para muitos projetos, não demanda tanto empenho. Além disso o pandas tem uma vinculação natural com a biblioteca, como veremos.

Instalação

Matplotlib é instalado junto com a distribuição do python via Anaconda. Se você não está usando Anaconda é possível encontrar maiores instruções de instalação no site de Matplotlib.

Um pouco de história

Matplotlib começou a ser desenvolvida em 2003 por John D. Hunter, um neurocientista que usava MATLAB e queria aperfeiçoar a visualização de dados obtidos por meio de EEGs (eletroencelografia) em sua pesquisa sobre epilepsia. Hoje uma a comunidade de desenvolvedores colaboram para manter e aperfeiçoar a biblioteca.

Como muitos usuários e desenvolvedores estavam acostumados ao ambiente do MATLAB, onde todas as funções estão disponíveis globalmente sem a necessidade de importações, o módulo pylab foi desenvolvido. Ele existe para trazer funções e classes do NumPy e matplotlib para o namespace global. Isso significa que o comando from pylab import * em uma sessão significa a importação desses módulos e é desnecessária para quem está acostumado com o estilo do python. Como já vimos a importação de muitos módulos, funções e classes pode provocar conflito entre as partes importadas e os métodos built-in do python.

De fato, o uso de ipython --pylab para quem usa o comando de linha, ou %pylab de dentro do Jupyter, simplesmente faz uma chamada interna à from pylab import *. Nesse sentido se recomenda, para quem trabalha com IPython e Jupiter Notebook, que se use a “mágica” %matplotlib.

Por todos esses motivos usaremos a abordagem usual do python:

» import matplotlib.pyplot as plt
» import numpy as np
» np.random.seed(444)
» # para exibição dos gráficos no ambiente do jupyter notebook usamos
» %matplotlib
» # para exibição incorporada dentro do notebook
» %matplotlib inline
» # ou, para exibição dentro do notebook, com controles de zoom e arraste
» %matplotlib notebook

Numpy será usado para as contruções de arrays e geração de dados aleatórios. A informação de np.random.seed() serve para que os geradores produzam os mesmos números em seções posteriores, para reproducibilidade. A mágica %matplotlib faz com que os gráficos sejam exibidos. Nesse caso uma nova janela é aberta com uma barra de menus com acesso à ampliação, arraste, gravação em vários formatos (como pdf, jpg, png), e parâmetros do gráfico. Já a inserção de %matplotlib inline faz com que os gráficos fiquem embutidos no próprio notebook e sejam gravados com ele. A janela de controle não aparece. Usando %matplotlib notebook temos o gráfico embutido com acesso à controles de zoom, arraste e gravação em arquivo.

Técnica Básica

O matplotlib pode receber como fonte de dados listas, arrays do numpy, Series e dataframes do pandas. Por exemplo, o código seguinte recebe listas gera as figuras 1 e 2 abaixo:

» %matplotlib inline
» plt.plot([0,3,0,5,0,7,0])
» # a figura 1 é gerada

» plt.plot([0,1,2,3,4,5,6],[0,3,0,5,0,7,0])
» # o mesmo que antes (figura 1 é gerada)

» x = np.arange(101)-50
» y = x**2
» plt.plot(x,y)
» # a figura 2 é gerada

Quando apenas uma lista é fornecida plot usa os índices como coordenada horizontal (abcissa) e os valores da lista como coordenadas verticais (ordenadas). Quando duas listas de mesmo tamanho são fornecidas a primeira é usada para os valores das abcissas, a segunda como ordenadas. No segundo exemplo, que gera a figura 2, foram usadas coordenadas (x, x2) (uma parábola) com x variando no intervalo [-50, 50].

Usando %matplotlib notebook o gráfico é exibido inline mas trazendo controles de ajuste da imagem. Ao se clicar no botão azul (pode ter outra cor na sua instalação) os controles desaparecem e a imagem fica estática.

» %matplotlib notebook
» x = np.arange(40)
» plt.plot(x,np.exp(x/10))    # exibe a figura 3

» # outro exemplo: seno
» # dados a plotar 
» x = np.arange(0.0, 2.0, 0.01)
» y = np.sin(2 * np.pi * x)

» fig, ax = plt.subplots()
» ax.plot(x, y)

» ax.set(xlabel='eixo x', ylabel='y = seno(x)', title='Gráfico de seno(2 pi x)')
» ax.grid()

» fig.savefig('seno.jpg')
» plt.show()                  # exibe a figura 4

No primeiro caso plotamos simplesmente o gráfico de y = exp(x/10). No segundo exemplo criamos um array no intervalo [0, 2) em passos de .01. A coordenada é y = sen(2πx). Usamos as funções exponencial e seno do numpy para lidar com a operação vetorializada (que pode ser aplicada sobre todo o array). Já veremos com maiores detalhes os métodos de matplotlib.pyplot.

Hierarquia de objetos do Matplotlib

Mesmo em exemplos simples, como os anteriores, Matplotlib usa uma hierarquia de objetos. Por hierarquia se entende que objetos dependem de outros, como em ramos de uma árvore. Repetindo o gráfico da figura 4 temos:

†: O termo axes do matplotplib não se refere a “eixos” e sim a “figuras” individuais, dentro de uma Figure.

Figure é o objeto básico ou o mais externo de um gráfico. Ele pode conter diversos Axes, que são gráficos ou plotagens individuais. Axes, por sua vêz, podem conter legendas, marcas gráficas, curvas e caixas de texto. Cada um desses elementos são objetos do python com métodos e propriedades que podem ser manipuladas individualmente.

Vamos verificar em código como esses objetos são criados e manipulados.

» # geramos os dados a imprimir
» x = np.arange(0.0, 2.0, 0.01)
» y = np.sin(2 * np.pi * x)

» # O objeto básico do matplotlib.pyplot (aliás plt) é figure
» fig = plt.figure()
» # De figure derivamos um axes (subplot) e plotamos 3 curvas
» ax = fig.add_subplot()

» ax.plot(x, y)
» ax.plot(x, x)
» ax.plot(x, x**2)

» # definimos os labels dos eixos x e y e o título do gráfico
» ax.set(xlabel='eixo x', ylabel='seno(x), x, x^2',
»        title='y = seno, reta e parábola')
» # acrescentamos um quadriculado (grid)
» ax.grid()

» # salvamos a figura no disco
» fig.savefig('seno.jpg')
» # exibimos o resultado
» plt.show()

Ao objeto ax acrescentamos 3 plots (y=sen(2πx), y=x, y=x2), os labels dos eixos, o título do gráfico e o quadriculado de fundo. Opcionalmente a figura pode ser salva. A figura só é exibida quando plt.show() é executado.

O método add_subplot admite diversos parâmetros: fig.add_subplot(m,n,r) significa criar um gráfico em m linhas, n colunas, na posição r. Um subplot significa dividir a região destinada ao gráfico em m×n partes onde se pode colocar sub-gráficos.

Por ex., vamos criar uma figura com 4 subplots, plotando uma curva diferente em cada uma delas.

» x = np.arange(0.0, 2.0, 0.01)-1
» fig = plt.figure()

» ax1 = fig.add_subplot(2,2,1)               # 2 linhas, 2 colunas: a 1ª figura
» ax2 = fig.add_subplot(2,2,2)               # a 2ª figura
» ax3 = fig.add_subplot(2,2,3)
» ax4 = fig.add_subplot(2,2,4)

» ax1.plot(x, np.sin(10*x))
» ax2.plot(x, x)
» ax3.plot(x, x**2)
» ax4.plot(x, x**3)
» fig.savefig('figura5.jpg')

» plt.show()

A mesma figura pode ser obtida fazendo os plots diretamente para os axes:

» fig = plt.figure()
» ax1 = fig.add_subplot(2, 2, 1)
» plt.plot([-1, 0, 1, 2])
» ax2 = fig.add_subplot(2, 2, 2)
» ax3 = fig.add_subplot(2, 2, 3)
» plt.plot(np.random.randn(50).cumsum(), 'k--')
» ax4 = fig.add_subplot(2, 2, 4)
» plt.plot([1.5, 3.5, -2, 1.6])

O método plt.plot(dados) se refere ao eixo ativo que é aquele criado ou usado por último. No caso acima nenhuma figura foi plotada no 2 º retângulo.

O procedimento de criar vários subplots dentro de um mesmo gráfico pode resumido por meio do método
fig, axes = plt.subplots().
subplots() retorna uma tupla onde o 1&orm; elemento é uma Figure, o objeto básico de um plot, e o 2º são os axes que recebem as curvas.

Esses axes podem ser referenciados individualmente pela notação de array. Por ex.: em
fig, axes = plt.subplots(2, 3)
temos axes[0,0] até axes[1,2].

np.random.randn(50).cumsum()
retorna a soma cumulativa dos elementos de um array de 50 elementos “aleatórios”.

pyplot.subplots possui as opções:

nrows número de linhas
ncols número de colunas
sharex todos os subplots devem ter os mesmos “ticks” no eixo x
sharey todos os subplots devem ter os mesmos “ticks” no eixo y
subplot_kw dicionário de chaves para criar cada subplot
**fig_kw chaves adicionais, como plt.subplots(2,2,figsize=(8,6))

Formatação dos gráficos

Tamanho

O tamanho de uma figura, por default dada em polegadas, é definido pelo parâmetro figsize em plt.figure(figsize=(largura, altura)).

» x = np.arange(.1, 10, 0.01)
» largura = 5; altura = 2
» plt.figure(figsize=(largura, altura))
» plt.plot(x, np.log(x))
» plt.show()                       # gerado o gráfico na figura 8

O mesmo gráfico é gerado usando esse parâmetro no construtor da figure, mas desenhado com linhas pontilhadas, devido ao parâmetro ‘k–‘ em ax.plot(x, np.log(x), 'k--').

» x = np.arange(.1, 10, 0.01)
» fig = plt.figure(figsize=(5, 2))
» ax = fig.add_subplot()
» ax.plot(x, np.log(x), 'k--')
» plt.show()                       # gerado o gráfico na figura 9

Espaçamento entre subplots, cores e marcadores

O espaçamento entre figuras de um gráfico com subplots, que por default é um espaço não nulo, pode ser ajustado por meio do método Figure.subplots_adjust(). Por conveniência o mesmo método pode ser acessado diretamente pela função:
subplots_adjust((left=None, bottom=None, right=None, top=None, wspace=None, hspace=None).
wspace e hspace indica quanto espaço percentual em relação à largura e altura da figura, respectivamente.
Por exemplo, para juntar os subplots fazemos ambos igual a zero.

» x = np.arange(.1, 10, 0.01)
» fig, axes = plt.subplots(2, 2)
» axes[0, 0].hist(np.random.randn(1000), bins=100, alpha=.5)
» axes[0, 0].hist(np.random.randn(500), bins=50, color='r', alpha=.5)
» axes[0, 1].hist(np.random.randn(500), bins=50, color='gold', alpha=1)
» axes[1, 0].hist(np.random.randn(500), bins=50, color='#ff0000')
» axes[1, 1].plot(x, 3*np.log(x), color='#55aaff')
» axes[1, 1].plot(x, x, color='#000000')
» plt.subplots_adjust(wspace=0, hspace=0)


Para usar os mesmos eixos em todos os 4 gráficos usamos os parâmetros sharex, sharey, em
fig, axes = plt.subplots(2, 2), sharex=True, sharey=True).

Em hist(np.random.randn(1000), bins=100) traçamos o histograma de dados aletórios (100 números) separados em 100 bins. No 1º axes traçamos 2 histogramas, o 1º com a cor azul default, o 2º com color=’r’, um atalho para ‘red’ ou vermelho, com transparência de 50%, alpha=.5. Uma lista de cores nomeadas, como color=’gold’ pode ser encontrada no site do matplotlib. Também podemos usar o código de cores html que consiste em 3 números hexadecimais de 0 até 255 (ou 00 até ff em hexadecimal), no sistema rgb (vermelho, verde, azul). Diversos editores de imagens ou de código disponilizam um seletor de cores que retorna a cor nesse sistema. O site HTML-COLOR.CODES também tem um seletor online.


O método axes.plot(), além de aceitar arrays para abcissas e coordenadas, pode receber também o string especificador de cor e tipo de linha. Para imprimir em verde (‘green’) com linha tracejada usamos
ax.plot(x, y, 'g--'),
que é uma forma resumida de passar parâmetros. Isso é o mesmo que:
ax.plot(x, y, linestyle='--', color='g').
Considerando que x e y são arrays de mesmo tamanho, alguns exemplos desses parâmetros são:

» plot(x, y)            # plot x, y com linha e estilo default
» plot(x, y, 'bo')      # plot x, y com marcadores azuis circulares
» plot(x, y, 'rv')      # plot x, y com marcadores vermelhos, triângulo para baixo
» plot(y)               # plot y usando seus índices como coordenadas-x
» plot(y, 'r+')         # idem, usando cruzes vermelhas

No Jupyter Notebook use plt.plot? para ver uma lista completa dos parâmetros desse método.

Ao desenhar um gráfico pode ser interessante marcar os pontos sobre as curvas contínuas. Isso é feito com markers ou marcadores.

» from numpy.random import randn
» plt.plot(randn(30).cumsum(), color='green', linestyle='dashed', marker='o')

Uma forma abreviada para o mesmo comando é: plt.plot(randn(30).cumsum(), 'go--'), onde os parâmetros são passados em uma única string, com g para green (verde), o para o marcador circular e -- para o estilo de linha tracejado.

Por default os pontos de um plot são ligados por linhas. Para outro estilo usamos drawstyle:

» data = np.random.randn(20).cumsum()
» plt.plot(data, 'b--', label='default', marker='v')                      # linha azul
» plt.plot(data, 'r-', drawstyle='steps-post', label='passos')            # linha vermelha
» plt.legend(loc='best')


A linha azul é tracejada (‘b–‘), no estilo default e com marcadores ‘v’ (triângulos). O parâmetro label cria legendas, nesse caso indicando o texto ‘defaul’. A linha vermelha tem estilo drawstyle=’steps-post’ (em passos) e é marcada na legenda como ‘passos’. plt.legend(loc='best') informa que o melhor local para colocar essa legenda seja encontrado automaticamente. Outros valores seriam: loc='right', 'center', 'upper right', etc.

Marcas, etiquetas e legendas (ticks, labels, legends)

» dados = np.random.randn(1000)
» cumulativo = dados.cumsum()

» fig = plt.figure()
» ax = fig.add_subplot(1,1,1)
» ax.plot(10*dados + 10)
» ax.plot(cumulativo)


As duas plotagens são sobrepostas no único axes criado. O operação 10*dados+10 serve apenas para efeito estético da apresentação dos dados aleatórios.

Vamos usar os mesmos dados para verificar as propriedades de ajuste do título global do gráfico, labels nos eixos x e y, label do gráfico e ticks para melhorar a apresentação do gráfico anterior.

» fig = plt.figure()
» ax = fig.add_subplot(1,1,1)
» ax.set_title('Alterando eixos com matplotlib')
» ticks = ax.set_xticks([0, 250, 500, 750, 1000])
» ticks = ax.set_yticks([-20, 0, 20, 40, 60, 80])

» labels = ax.set_xticklabels(['seg' ,'ter', 'qua', 'qui', 'sex'],
»                             rotation=45, fontsize='large')
» labels = ax.set_yticklabels(['-A' ,'O', 'A', 'B', 'C','D'],
»                             fontsize='large')

» ax.set_xlabel('Ao longo dos dias...')
» ax.set_ylabel('Observado')

» ax.plot(10*dados, color='#55aaff', alpha=.5, label='dados')
» ax.plot(cumulativo, color='red', label='cumulativo')
» ax.legend(loc='best')

Anotações e desenhos nos subplots.

Diversos tipos de anotações, setas e desenhos podem ser incluídos nos gráficos. Para traçar os gráficos seguintes vamos usar o arquivo .csv do Our World in Data, baixados para a pasta ./dados.

Primeiro importamos o arquivo baixado .csv para um dataframe. Esse arquivo contém dados dos países do mundo, nos anos de 1950 até 2099, contendo número de nascimentos verificados até 2020 e valores interpolados para os anos seguintes. O dataframe original tem o seguinte formato:

Em seguida selecionamos apenas os dados sobre o Brasil.

» dados = pd.read_csv('./dados/number-of-births-per-year.csv')
» dados=dados[(dados['Entity']=='Brazil')]
» dados.head(2)

» # as colunas 3 e 4 têm nomes longos, que vamos renomear
» dados.columns[3],dados.columns[4]
↳ ('Estimates, 1950 - 2020: Annually interpolated demographic indicators - Births (thousands)',
↳  'Medium fertility variant, 2020 - 2099: Annually interpolated demographic indicators - Births (thousands)')

» dados.rename(columns={'Year':'ano',
»                       dados.columns[3]:'nasc',
»                       dados.columns[4]:'inter'}, inplace=True)
» # copiamos os dados da coluna de interpolação, após 2020, para a colunas de nascimentos
» dados.loc[dados['nasc'].isna(), 'nasc'] = dados['inter']

» # vamos mantes apenas as colunas 'ano' e 'nasc'
» dados = dados[['ano', 'nasc']]
» dados.head()
↳         ano          nasc
↳ 4050    1950    2439820.0
↳ 4051    1951    2467186.0
↳ 4052    1952    2523577.0
↳ 4053    1953    2583285.0
↳ 4054    1954    2646311.0

Para usar como anotações no gráfico encontramos os anos em que  nascimentos foram máximo e mínimo, além do ano em que se inicia a interpolação, 2020.

» maior=dados[dados['nasc']==dados['nasc'].max()]
» menor=dados[dados['nasc']==dados['nasc'].min()]

» ano_maior = maior['ano'].values[0]
» nasc_maior = int(maior['nasc'].values[0])

» ano_menor = menor['ano'].values[0]
» nasc_menor = int(menor['nasc'].values[0])

» interX = 2020   # início da interpolação
» interY = int(dados[dados['ano']==2020]['nasc'].values[0])

» txt = ('Máximo de nascimentos:\t {} no ano {}.\n'
»         'Mínimo de nascimentos:\t {} no ano {}.\n'
»         'Início da interpolação:\t {} no ano {}.'
»       )

» print(txt.format(nasc_maior, ano_maior,nasc_menor, ano_menor, interY, interX))
↳ Máximo de nascimentos:	 3929646 no ano 1983.
↳ Mínimo de nascimentos:	 1504597 no ano 2099.
↳ Início da interpolação:	 2859135 no ano 2020.

Com esses dados imprimimos o gráfico (sem muita preocupação estética). Uma primeira curva é traçada em preto, incluindo os anos de 1950 até 2020. A segunda curve se inicia em 2021 até o final e é tracejada em vermelho, para indicar a interpolação. Uma terceira curva tem efeito decorativo, em azul e transparente.

» fig = plt.figure()
» ax = fig.add_subplot()
» ax.set_title('Nascimentos (em milhões), por ano', size=18)
» ax.plot(dados[dados['ano']<2021]['ano'], dados[dados['ano']<2021]['nasc'], color='black', alpha=1)
» ax.plot(dados[dados['ano']>2020]['ano'], dados[dados['ano']>2020]['nasc'], 'r--')
» ax.plot(dados['ano'], dados['nasc'], 'b', linewidth=5, alpha=.2)
» ax.grid(color='grey', alpha=.3 )
» ax.annotate('Máximo', xy=(ano_maior, nasc_maior), size=13)
» ax.annotate('Mínimo', xy=(ano_menor, nasc_menor), size=13)
» ax.annotate('Interpolado', xy=(interX, interY), size=13)
» ax.arrow(ano_maior-10, nasc_maior, 10, 0)
» ax.arrow(interX-10, interY-10, 10, 10)            # a figura 17 abaixo é plotada


Para dar um zoom na figura podemos limitar as faixas de valores no eixo x e eixo y com ax.set_xlim(a,b) e ax.set_xlim(m,n), respectivamente, em torno do ponto de interesse. No exemplo fazemos um zoom em torno do ponto de máximo, obtendo o gráfico 18 acima.

» ax.set_xlim([1980, 1990])
» ax.set_ylim([3.76e6, 3.95e6])
» fig.get_figure()                                  # a figura 18 acima é plotada

Figuras sobre o plot

Diversas formas mais comuns estão disponíveis para inserção nos plots, e são chamadas de patches no matplotlib. Algumas delas estão diretamente em matplotlib.pyplot como retângulos, círculos e polígonos. Muitas outras estão em matplotlib.patches. Para traçar figuras construimos os patches com os métodos apropriados e os acrescentamos ao subplot usando ax.add_patch().

retângulo: plt.Rectangle((x, y), largura, altura), onde (x, y) são as coordenadas do ponto inferior esquerdo,
círculo: plt.Circle((x_0, y_0), raio), onde (x_0, y_0) são as coordenadas do centro,
polígono: plt.Polygon([[x_0, y_0], [x_1>, y_1],…, [x_n, y_n]).

No caso do polígono a área interna às retas que ligam os pontos é colorida.

» fig = plt.figure()
» ax = fig.add_subplot()
» retangulo = plt.Rectangle((0.2, 0.2), 0.6, 0.4, color='#aabbcc')
» circulo = plt.Circle((0.4, 0.6), 0.3, color='plum', alpha=0.3)
» poligono1 = plt.Polygon([[0.1, 0.1], [0.8, 0.7], [.3,.7], [0.6, 0.1]], color='turquoise', alpha=0.8)
» poligono2 = plt.Polygon([[0.2, 0.2], [0.8, 0.8]], color='red', alpha=0.8)
» ax.add_patch(retangulo)
» ax.add_patch(circulo)
» ax.add_patch(poligono1)
» ax.add_patch(poligono2)

Para o segundo “polígono” apenas dois pontos foram fornecidos e ele é representado pela reta (vermelha) que liga esses pontos.

Claro que gráficos mais elaborados podem ser montados com uma combinação de figuras como retas, polígonos, círculos, etc. No caso abaixo uma cor é escolhida “aleatoriamente” para plotar um série de 2 triângulos, um com um vértice em (0,1), outro em (1,0). Os dois outros vértices dos triângulos são coincidentes, e se deslocam sobre a reta (t,t) com t de 0 a 1, com espaçamento .1. A cor tem transparência alpha=.6 para que as cores apareçam em tons pastéis.

A função cor() retorna uma tupla (r,b,g) onde cada componente representa as cores vermelho, verde e azul, com valores de 0 até 1.

» def cor():
»     return (np.random.random(), np.random.random(), np.random.random())

» fig = plt.figure()
» ax = fig.add_subplot()
» for t in np.linspace(0,1,100):
»     c = cor()
»     poligono1 = plt.Polygon([[0, 1], [t, t], [t+.1, t+.1]], color=c, alpha=.6)
»     poligono2 = plt.Polygon([[1, 0], [t, t], [t+.1, t+.1]], color=c, alpha=.6)
»     ax.add_patch(poligono1)
»     ax.add_patch(poligono2)                       # a figura 20 é gerada


O código abaixo gera círculos de raios aleatórios, espalhados em torno da reta (t,t), afastados dela por uma variacão também aleatória.

» fig = plt.figure()
» ax = fig.add_subplot()
» plt.axis('equal')
» for t in np.linspace(0,1,100):
»     circulo = plt.Circle((t*np.random.random(), t*np.random.random()), np.random.random()/10, color=cor(), alpha=0.5)
»     ax.add_patch(circulo)
» ax.set_xlim([0, 1])
» ax.set_ylim([0, 1])
» plt.savefig('circulos.pdf')                       # a figura 21 é gerada

Ao final a figura gerada é gravada em disco com o formato “pdf”. Outros formatos podem ser escolhidos, como “jpeg”, “png”, “svg”, assim como a resolução em dots per inches, (dpi ), que tem default = 100, além da cor de fundo e bordas.

Configuração do matplotlib

Por padrão o matplotlib possui um esquema de cores e outros parâmetros, como largura e tipo de linhas, previamente definidos e voltados para plotar figuras prontas para publicação. No entanto, vários destes parâmetros podem ser personalizados através de ajustes nos valores globais tais como tamanho, espaçamento entre subplots, cores, família e tamanhos de fonte, estilos de grade, etc.

Uma forma de alterar esses padrões está no método plt.rc (parametro, opcoes) onde parametro é uma string com o nome do parâmetro que se quer modificar, e opcoes é uma sequência de argumentos de palavras-chaves com os novos valores.

Entre outras opções parametro pode ser figure, axis, xtick, ytick, grid, legend. As opções podem ser passadas de várias formas. O exemplo mostra como ajustar todas as figuras de uma sessão para o tamanho 20×15. Depois fazemos ajustes às fontes, usando um dicionário.

» # ajustar tamanho da figura
» plt.rc ('figure', figsize = (20, 15))

» # parâmetros associados às fontes, em um dicionário
» font_options = {'family' : 'monospace', 'weight' : 'bold', 'size' : 'small'}
» plt.rc ('font', **font_options)

Uma personalização mais ampla pode ser feita no arquivo de configurações. Para encontrar esse arquivo use os comandos:

» # no prompt do sistema digite
» python -c "import matplotlib; print(matplotlib.matplotlib_fname())"

» # de dentro do jupyter notebook (ou de qualquer ambiente em que você trabalhe):
» import matplotlib
» print(matplotlib.matplotlib_fname())
↳ /home/usuario/.anaconda3/lib/python3.8/site-packages/matplotlib/mpl-data/matplotlibrc

Esse output é relativo ao sistema e à distribuição que está sendo usada, lembrando que é possível existir mais de uma instalação em um computador. No caso mostrado está em uso o anaconda e jupyter no linux mint. Cada usuário pode encontrar um local diferente. Esse arquivo não deve ser editado diretamente mas copiado para a pasta home do usuário (no linux) com o nome .matplotlibrc. Desta forma ele será carregado durante a inicialização do pacote. A análise desse arquivo é uma boa forma de se conhecer as possibilidades na personalização, sendo que as opções estão comentadas.

As atuais configurações globais podem ser vistas com o comando

» import matplotlib as mpl
» print(mpl.rcParams)

matplotlib.rcParams é usado para alterar esses parâmetros, um de cada vez. matplotlib.rc pode alterar os valores default para vários parâmetros de um grupo específico, como tipos de lihnes, fontes, textos, etc.

» matplotlib.rcParams['lines.markersize'] = 20
» matplotlib.rcParams['font.size'] = '15.0'

matplotlib.rcdefaults() reseta todos os parâmetros para seus valores originais.

Usando matplotlib com o pandas

A própria biblioteca do pandas embute diversas funcionalidades do matplotlib, sem que esse tenha que ser carregado explicitamente. Isso significa que podemos criar gráficos sem passar por todas as etapas de sua construção.

Por exemplo, uma instância de Series possui o método series.plot().

» s1 = pd.Series(np.random.randn(100).cumsum())
» s1.plot(use_index=False)                          # grafico 22-a é plotado

» s2 = pd.Series([x**2 for x in np.arange(-10,10,.1)], index=np.arange(-10,10,.1))
» s2.plot()                                         # grafico 22-b é plotado


Na figura 21 o eixo x recebe valores dos índices da série, que por default vai de 0 até 99. Na segunda o índice que foi declarado é usado. Para evitar o procedimento de usar o índice como ordenada passamos o parâmetro series.plot(use_index=False).

Para um dataframe cada series correspondente a cada coluna é plotada separadamente. Abaixo construimos e plotamos um dataframe de quatro colunas, cada uma delas representando valores de um seno com frequências diferentes.

» s1 = pd.Series([np.sin(x) for x in np.arange(0,10,.1)])
» s2 = pd.Series([np.sin(2*x) for x in np.arange(0,10,.1)])
» s3 = pd.Series([np.sin(3*x) for x in np.arange(0,10,.1)])
» s4 = pd.Series([np.sin(4*x) for x in np.arange(0,10,.1)])

» df=pd.concat({'A': s1, 'B': s2, 'C': s3, 'D': s4} , axis=1)

» df.plot()                                                        # a figura 23 é plotada
» df.plot(color=['k','r','b','y'], alpha=.6, logx=True, grid=True) # a figura 24 é plotada


O dataframe df contém 4 colunas, cada uma com os valores de seno(πx), seno(2πx), seno(3πx), seno(4πx), com x variando de 0 a 10 em passos de 0,1. No segundo gráfico, figura 24, alguns parâmetros foram passados, como uma lista de cores, a existência de quadriculado (grid) e a instrução para usar uma escala logarítmica em x.

A instrução dataframe.plot() é um atalho para dataframe.plot.line() que representa como curvas os pontos passados. Outros parâmetros podem ser passados, exatamente como no uso direto de matplotlib:

Argumento Descrição
label texto para a legenda
ax objeto subplot do matplotlib onde plotar. Se vazio os plots vão para o subplot ativo
style string de estilo, como ‘ko–‘, passado para o matplotlib
alpha opacidade do plot (de 0 to 1)
kind opções: ‘area’, ‘bar’, ‘barh’, ‘density’, ‘hist’, ‘kde’, ‘line’, ‘pie’
logy use escala logaritmica no eixo y
use_index use o index para os labels de x
rot rotação de texto nos labels (0 até 360)
xticks valores a usar para marcas no eixo x
yticks valores a usar para marcas no eixo y
xlim limites para o eixo x (ex.: [0, 10])
ylim limites para o eixo y
grid exibir grade quadriculada de fundo (grid), default=exibir

Alguns parâmetros são específicos para dataframes.

Argumento Descrição
subplots bool. Plota cada coluna em um subplot separado
sharex se subplots=True, use o mesmo eixo x, com marcas e limites
sharey se subplots=True, use o mesmo eixo y, com marcas e limites
figsize tupla indicando tamanho da figura
title texto para o título
legend bool. Inclui legenda do subplot (default=True)
sort_columns plot colunas em ordem alfabética no nome; default= ordem no dataframe

Plotagem em barras

A plotagem em barras (bar plots ) pode ser feita com plot.bar() e plot.barh() (com barras verticais e horizontais).

Para experimentar com esses tipos de plotagens vamos usar os mesmos dados importados anteriormente, que contém uma lista de países com o número de nascimentos por ano de 1950 até 2020, e a estimativa à partir de 2021. Importamos o arquivo .csv para um dataframe e selecionamos apenas as linhas relativas ao ano de 2020. Linhas relativas à continentes e outras partes do mundo que não países possuem coluna Code = NaN e são excluídas. Renomeamos as colunas para mais fácil manuseio e mantemos apenas as colunas relativas ao país, ano e número de nascimentos.

» # importação do csv em um dataframe
» dados = pd.read_csv('./dados/number-of-births-per-year.csv')
» # selecão do ano = 2020 e apenas países
» dados = dados[(dados['Code'].notnull()) & (dados['Year']==2020)]
» # renomeando colunas
» dados = dados.rename(columns={'Entity':'país','Year':'ano', dados.columns[3]:'nasc'})
» # mantendo apenas colunas relevantes
» dados = dados[['país', 'nasc']]

» # use a coluna 'país' como índice
» dados.set_index('país', inplace=True)

» # o dataframe final:
» dados.head(4)
↳                      nasc
↳        país
↳ Afghanistan     1215628.0
↳     Albania       32888.0
↳     Algeria      995368.0
↳      Angola     1311356.0

Imprimimos os 2 tipos de barplot com o código abaixo, usando 10 países para as barras verticais e 20 para as horizontais. Para isso inicializamos uma figura com 1 linha e 2 colunas. O parâmetro figsize=(15, 16) indica que nossa figura terá a largura 15 e altura 6 (em polegadas). Dados os nomes longos de países o gráfico ficou sobreposto, o que seria controlado aumentando-se a separação entre axes.

» fig, axes = plt.subplots(2, 1, figsize=(10, 10))
» dados[:10].plot.bar(ax=axes[0], color=['r','b','g'], rot=30, grid=True)
» dados[:20].plot.barh(ax=axes[1], color='g', alpha=0.7, grid=True)


Gráficos desse tipo são desenhados para series e dataframes com apenas uma coluna. Se o dataframe possui várias colunas o gráfico de barras plota uma barra para cada coluna.

Para o próximo exemplo usaremos os dados disponibilizados no GapMinder, já usados e descritos nesse site. Desses dados manteremos apenas as colunas que renomearemos como “pais”, “ano” e “pop” (população), ficando com um dataframe com os países de mundo e suas populações nos anos listados abaixo.

Dessa coleta de dado separamos apenas os 5 países com maior população no último ano, 2007.

» # baixar dados do gapminder
» url =(
»       'https://raw.githubusercontent.com/jennybc/'
»       'gapminder/master/data-raw/08_gap-every-five-years.tsv'
»        )

» # criamos o dataframe dfPaises. O arquivo importado tem campos separados por tabs
» dfPaises = pd.read_csv(url, sep='\t')

» # para ver colunas e forma geral usamos
» dfPaises.head(2)
↳         country   continent    year    lifeExp        pop     gdpPercap
↳ 0   Afghanistan        Asia    1952     28.801    8425333    779.445314
↳ 1   Afghanistan        Asia    1957     30.332    9240934    820.853030

» # usamos apenas 3 colunas
» dfPaises = dfPaises[['country','year','pop']]
» # e as renomeamos
» dfPaises.rename(columns={'country':'pais', 'year':'ano'}, inplace=True)

» # o resultado é
» dfPaises
↳             pais     ano        pop
↳ 0    Afghanistan    1952    8425333
↳ 1    Afghanistan    1957    9240934
↳ 2    Afghanistan    1962   10267083

» # para ver os anos registrados examinamos o conjunto (set)
» set(dfPaises['ano'])
↳ {1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2007}

» # países mais populosos, em 2007
» dfPaises[dfPaises['ano']==2007] \
»          .sort_values(by=['pop'], axis=0, \
»           ascending=False, inplace=False)['pais'] \
»          .head(5)
↳ 299             China
↳ 707             India
↳ 1619    United States
↳ 719         Indonesia
↳ 179            Brazil

O último comando, para selecionar os países mais populosos, está quebrada pelo caracter \ (back slash) que é ignorado (a linha é executada por inteiro). Essa linha pode ser compreendida assim:

dfPaises[dfPaises['ano']==2007]                         : seleção só de linhas com ano = 2007
.sort_values(by=['pop'], axis=0, ascending=False)       : ordena pela coluna 'pop' em ordem inversa
['pais'].head(5)                                        : só a coluna 'pais', 5 primeiros valores

Em seguida montamos um dataframe para cada desses países e os concatenamos para um dataframe mais geral que contém linhas indexadas pela ano e colunas com o nome do país. (Outras técnicas de agrupamento serão vistas mais tarde.)

» china = dfPaises[dfPaises['pais']=='China'][['ano', 'pop']]\
»         .set_index('ano').rename(columns={'pop':'china'})
» india = dfPaises[dfPaises['pais']=='India'][['ano', 'pop']]\
»         .set_index('ano').rename(columns={'pop':'india'})
» usa = dfPaises[dfPaises['pais']=='United States'][['ano', 'pop']]\
»       .set_index('ano').rename(columns={'pop':'usa'})
» indonesia = dfPaises[dfPaises['pais']=='Indonesia'][['ano', 'pop']]\
»             .set_index('ano').rename(columns={'pop':'indonesia'})
» brasil = dfPaises[dfPaises['pais']=='Brazil'][['ano', 'pop']]\
»          .set_index('ano').rename(columns={'pop':'brasil'})

As linhas de seleção de dados do país podem ser compreendidas assim:

  dfPaises[dfPaises['pais']=='Brazil']      : seleciona apenas linhas relativas ao país 'Brazil'
  [['ano', 'pop']]                          : desse df copia apenas as colunas 'ano' e 'pop'
  .set_index('ano')                         : use a coluna 'ano' como índice
  .rename(columns={'pop':'brasil'})         : renomeie coluna 'pop' para 'brasil'
» # os paises são concatenados em um único df
» df = pd.concat([china, india, usa, indonesia, brasil],  axis=1)
» # o nome da lista de colunas será usado no plot
» df.columns.name = 'População'

» # e o resultado é
» df.head(3)
↳ População     china        india          usa   indonesia      brasil
↳ ano
↳ 1952      556263527    372000000    157553000    82052000    56602560
↳ 1957      637408000    409000000    171984000    90124000    65551171
↳ 1962      665770000    454000000    186538000    99028000    76039390

» # esse dataframe pode ser exibido em gráfico de barras
» cor = ['salmon','gold','teal','plum','powderblue']
» df.plot.bar(figsize=(10,5), grid=True, color = cor, title='Países mais populosos (de 1952 até 2007)', rot=45)

Na plotagem acima uma paleta de cores foi passada para o parâmetro color. Cada uma delas é usada para um país. O nome das colunas, df.columns.name = 'População' é usado como título da legenda.

Para gerar gráficos de barras empilhadas (stacked bar ) passamos o valor stacked=True. Nos exemplos plotamos a versão horizontal e vertical do mesmo gráfico acima.

» df.plot.barh(stacked=True, alpha=.7)         # a figura 27 é plotada

» df.plot.bar(stacked=True, alpha=.7)          # a figura 28 é plotada

Histogramas

Um histograma é uma representação gráfica, similar a um gráfico de barras, de uma distribuição de pontos. Os pontos são distribuídos em faixas igualmente divididas e o gráfico é o conjunto de retângulos com base de tamanho igual à largura das faixas e altura correspondente ao número de pontos em cada faixa.

Criamos uma série com 1000 números aleatórios, multiplicados por 100. O resultado é, no caso dessa execução, um conjunto distribuído entre -315 e 325 (aproximadamente). A partir desses dados traçamos o histograma e o gráfico de densidade ou density plot.

» ser = pd.Series((np.random.randn(1000)*100))
» ser.describe()
↳ count    1000.000000
↳ mean        3.747373
↳ std       102.637489
↳ min      -314.443835
↳ 25%       -65.343082
↳ 50%         4.835384
↳ 75%        73.314604
↳ max       324.011000

» ser.plot.hist(bins=10, grid=True, color='b', alpha=.4) # figura 19

» ser.plot.kde() # o mesmo que ser.plot.density()        # figura 30


O gráfico de densidade consiste na plotagem de uma função de distribuição de probabilidade que poderia ter gerado os dados na series. A técnica usual consiste em usar uma mistura de “núcleos” ou “kernels”. Esses gráficos são também chamados de estimativa de núcleos de densidade (kernel density estimate, KDE ).

Seaborn

Seaborn é outra biblioteca do Python voltada para a visualização de dados, baseada no matplotlib. Ela apresenta uma interface de mais alto nível e aprimoramento da qualidade estética dos gráficos. Com o Seaborn se pode conseguir gráficos bem elaborados e de boa aparência com um número menor de linhas de código.

Para os exemplos com o seaborn vamos usar os dados do Gapminder já descritos. O dataframe importado tem 1704 linhas com dados sobre os países, 6 colunas ‘country’, ‘continent’, ‘year’, ‘lifeExp’, ‘pop’, ‘gdpPercap’, respectivamente ‘pais’, ‘continente’, ‘ano’, ‘Expectativa de vida’, ‘população’, ‘PIB percapita’.

» url =(
»       'https://raw.githubusercontent.com/jennybc/'
»       'gapminder/master/data-raw/08_gap-every-five-years.tsv'
»        )
» # criamos o dataframe dfPaises. O arquivo importado tem campos separados por tabs
» dfPaises = pd.read_csv(url, sep='\t')

» # para restringir o volume dos dados armazenamos as fatias
» df2007 = dfPaises[dfPaises['year']==2007]
» dfBrasil = dfPaises[dfPaises['country']=='Brazil']

O seaborn deve ser importado. Para uma gráfico de barras mais simples informamos e base de dados e os nomes de colunas a usadas como valores para os eixos. Para não congestionar o gráfico usamos apenas os 10 primeiros países.

» import seaborn as sns
» sns.barplot(data=df2007[:10], x='lifeExp', y='gdpPercap')
» # é plotada a figura 31 abaixo

Muitas configuarações podem ser aplicadas sobre esse gráfico básico. Algumas são usadas abaixo, como a orientação das barras, textos e rotação nos eixos x e y.

» sns.set_style('darkgrid')
» graf = sns.barplot(data=df2007[:10], x='lifeExp', y='gdpPercap', orient='h')
» graf.set(xlabel = 'Expectativa de vida (anos)',
                     ylabel = 'PIB percapita', title ='Expectativa de vida x PIB')
» graf.set_xticklabels(labels=df2007[:10]['lifeExp'].round(1))
» graf.set_yticklabels(labels=df2007[:10]['country'], rotation=30)
» # é plotada a figura 32 acima

Valores válidos para sns.set_style() são 'white', 'dark', 'whitegrid', 'darkgrid', 'ticks'.

Esses gráficos plotados não sugerem qualquer relação entre a renda percapita e a expectativa de vida, o que é natural uma vez que escolhemos apenas os primeiros 10 países, em ordem alfabética. Claro que barplots não são apropriados para exibir um número muito grande de dados. Para isso podemos usar seaborn.regplot que plota o gráficos de dispersão (scatter plots ) e uma reta correspondente a um ajuste do modelo de regressão linear. Esse último gráfico mostra que existe correlação entre expectativa de vida e renda percapita.

» sns.regplot(x='lifeExp', y='gdpPercap', data=df2007) # figura 33


É comum em análise de dados que se queira ter uma visão geral de relacionamentos entre as variáveis (ou colunas de um dataframe. Para isso um pairplot faz o cruzamento entre todas as variáveis. O método seaborn.pairplot(), por default, cria uma matriz de Axes comparando aos pares as variáveis numéricas do dataframe usado como fonte de dados. Na diagonal dessa matriz uma distribuição univariada é exibida para mostrar a distribuição dos dados em cada coluna.

» sns.pairplot(df2007[['gdpPercap', 'lifeExp']],
»              diag_kind='kde', plot_kws={'color':'r','alpha': .9})


O parâmetro plot_kws recebe um dicionário de propriedades com valores.

Outro método é seaborn.catplot() que traz diversas funcionalidades para representar relações entre variáveis numéricas ou categóricas. Para uma amostra criamos um dataframe com valores de uma parábola e um seno.

» dfGraf=pd.DataFrame(np.arange(20), columns = ['x'])
» dfGraf['quadrado']=dfGraf['x']**2
» dfGraf['seno']=np.sin(dfGraf['x'])

» sns.catplot(x='x', y='quadrado', kind='strip', data=dfGraf)      # plota a figura 35
» sns.catplot(x='x', y='seno', kind='bar', data=dfGraf)            # plota a figura 36

Bibliografia

Todos os sites acessados em setembro de 2021.

Consulte bibliografia completa em Pandas, Introdução neste site.

Dataframes – Seleções e Ordenamento


Outras formas do construtor de dataframes

dataFrames

Se um dicionário aninhado (onde os valores associados às chaves externas são outros dicionários) é passado no construtor de um DataFrame o pandas interpretará as chaves externas como nomes das colunas e as chaves internas como índices das linhas. Na ausência de um par chave:valor em um ou mais dos dicionários o campo receberá o valor NaN.

» dic = {'Pedro': {'Prova 1': 5.4, 'Prova 3': 7.9},
                'Ana': {'Prova 1': 8.5, 'Prova 2': 9.7, 'Prova 3': 6.6},
               'Luna': {'Prova 2': 5.0, 'Prova 3': 7.0, 'Prova 4': 6.0}
             }
» dfNotas = pd.DataFrame(dic)
» dfNotas
↳
            Pedro    Ana   Luna
  Prova 1     5.4    8.5    NaN
  Prova 3     7.9    6.6    7.0
  Prova 2     NaN    9.7    5.0
  Prova 4     NaN    NaN    6.0

Se os nomes das linhas e das colunas forem fornecidos eles serão exibidos.

» dfNotas.index.name = 'Prova';
» dfNotas.columns.name = 'Aluno'
» dfNotas
↳
  Aluno      Pedro    Ana    Luna
  Prova             
  Prova 1     5.4     8.5     NaN
  Prova 3     7.9     6.6     7.0
  Prova 2     NaN     9.7     5.0
  Prova 4     NaN     NaN     6.0

Com frequência importamos de fontes externas, como faremos abaixo, uma fonte de dados e precisamos verificar sua integridade. Por ex., para encontrar elementos ausentes, preenchidos como NaN, usamos dataFrame.isnull() (o mesmo que pd.isnull(dataFrame)). Para saber quantos valores nulos existem usamos dataFrame.isnull().sum(), que fornece a soma dos campos True para cada campo.

» dfNotas.isnull()    # o mesmo que pd.isnull(dfNotas)
↳ Aluno       Pedro     Ana      Luna
  Prova             
  Prova 1     False     False    True
  Prova 3     False     False    False
  Prova 2     True      False    False
  Prova 4     True      True     False

» dfNotas.isnull().sum()
↳ Aluno
  Pedro    2
  Ana      1
  Luna     1
  dtype: int64

O método dataFrame.notna() (o mesmo que dataFrame.notnull() e o inverso de dataFrame.isnull()) retorna um dataframe booleano com True onde os campos não são nulos. Para inserir manualmente campos nulos usamos a constante pd.NaT e para eliminar linhas (ou colunas) contendo nulos aplicamos dataframe.dropna().

» # para eliminar linhas contendo nulos (o default é axis=0)
» dfNotas.dropna()
↳ Aluno     Pedro     Ana     Luna
  Prova             
  Prova 3     7.9     6.6      7.0
    
» # para eliminar colunas contendo nulos
» dfNotas.dropna(axis=1)
# todas são eliminadas pois existem NaN em todas as colunas

Evidentemente é necessário ter cuidado ao eliminar linhas ou colunas com NaN. Em muitos casos pode ser necessário substituir esses valores por outros, dependendo da aplicação. Para fazer a alteração no próprio frame use o parâmetro inplace = True.

Colunas e índices são objetos do tipo array e podem ser usados com alguns métodos de conjuntos.

» dfNotas.columns
↳ Index(['Pedro', 'Ana', 'Luna'], dtype='object', name='Aluno')

» dfNotas.index
↳ Index(['Prova 1', 'Prova 3', 'Prova 2', 'Prova 4'], dtype='object', name='Prova')

» 'Ana' in dfNotas.columns      # True
» 'Ann' in dfNotas.columns      # False
» 'Prova 5' in dfNotas.index    # False

O mesmo ocorre se o dicionário contiver Series como valores, sendo as chaves usadas como nomes das colunas e os índices das series usados como índices das linhas.

» serie1 = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
» serie2 = pd.Series([5, 6, 7, 8], index=['a', 'b', 'c', 'd'])
» serie3 = pd.Series([9, 0, -1, -2], index=['a', 'b', 'c', 'd'])

» dic = {'A':serie1, 'B':serie2, 'C':serie3 }
» pd.DataFrame(dic)
↳ 
      A   B   C
  a   1   5   9
  b   2   6   0
  c   3   7  -1
  d   4   8  -2

Dataframes podem ser criados recebendo Series no construtor.

» disciplinas = pd.Series(['Matemática', 'Física', 'História', 'Geografia'])
» notas = pd.Series([9.0, 5.4, 7.7, 8.9])
» df = pd.DataFrame({'Disciplina':disciplinas, 'Notas': notas})
» df
↳     Disciplina   Notas
  0   Matemática     9.0
  1       Física     5.4
  2     História     7.7
  3    Geografia     8.9

Outros objetos podem ser passados como argumento no construtor:

  • Ndarray (do NumPy) de 2 dimensões,
  • dicionário de arrays, listas ou tuples (todas as sequências devem ter o mesmo comprimento),
  • dicionários de arrays NumPy, de Series ou de outros dicionários,
  • listas de dicionários, Series, listas ou tuplas,
  • Series ou outro dataframe.

Tratamento de dados usando pandas.dataframe

Para os testes e demonstrações que se seguem vamos usar dados reais para demonstrar algumas funcionalidades úteis dos pandas.dataframes.

Fonte de dados

Para realizar os teste com dataframes vamos utilizar os dados encontrados no Gapminder nessa url: 08_gap-every-five-years.tsv. Esse é um arquivo contendo dados com um registro em cada linha e os valores na linha separados por tabs, (tabulação). Esse arquivo pode ser baixado para o seu computador e depois importado para um dataframe ou, como usamos abaixo, importada diretamente do site de Jennifer Bryan (jennybc): Gapminder, no Github.

O arquivo original tem o seguinte formato,

country      continent       year   lifeExp        pop     gdpPercap
Afghanistan       Asia       1952    28.801    8425333   779.4453145
Afghanistan       Asia       1957    30.332    9240934   820.8530296
Afghanistan       Asia       1962    31.997   10267083     853.10071
...

onde os espaços entre valores são tabulações (\t, no python). A primeira linha contém os ‘headers’ ou títulos das colunas. Traduziremos esses títulos da seguinte forma: country ⟼ pais, continent ⟼ continente, year ⟼; ano, lifeExp ⟼ expVida (expectativa de vida), pop ⟼ populacao, gdpPercap ⟼ pibPercap (produto interno bruto, percapita).

» import pandas as pd
» import numpy as np
» # Usando arquivo encontrado no Gapminder
» url =(
      'https://raw.githubusercontent.com/jennybc/'
      'gapminder/master/data-raw/08_gap-every-five-years.tsv'
       )
» url
↳ 'https://raw.githubusercontent.com/jennybc/gapminder/master/data-raw/08_gap-every-five-years.tsv'

» # criamos o dataframe dfPaises. O arquivo importado tem campos separados por tabs 
» dfPaises = pd.read_csv(url, sep='\t')

» # o dataframe tem 1704 linhas e 6 colunas
» dfPaises.shape
↳ (1704, 6)

» dfPaises.head()
↳
          country   continent    year   lifeExp        pop     gdpPercap
  0   Afghanistan        Asia    1952    28.801    8425333    779.445314
  1   Afghanistan        Asia    1957    30.332    9240934    820.853030
  2   Afghanistan        Asia    1962    31.997    10267083   853.100710
  3   Afghanistan        Asia    1967    34.020    11537966   836.197138
  4   Afghanistan        Asia    1972    36.088    13079460   739.981106
  1704 rows × 6 columns 
» # renomeando os campos para nomes em português
» dfPaises.rename(columns={'country':'pais',
                           'continent':'continente',
                           'year':'ano',
                           'lifeExp':'expVida',
                           'pop':'populacao',
                           'gdpPercap':'pibPercap',
                          }, inplace=True)
» # ficamos assim
» dfPaises.columns
↳ Index(['pais', 'continente', 'ano', 'expVida', 'populacao', 'pibPercap'], dtype='object')

» # para reordenar as colunas em sua exibição
» dfPaises = dfPaises[['continente', 'pais', 'ano', 'expVida', 'populacao', 'pibPercap']]

Podemos obter uma visão geral do conjunto de dados importados usando dois métodos. dataframe.info() retorno os nomes das colunas, quantos valores não nulos, seus dtypes, e memória usada nesse armazenamento. Por aí vemos que nossos dados não possuem valores nulos. Caso esses existissem eles teriam que ser localizados e tratados devidamente. O método df.describe() retorna um dataframe contendo a contagem count dos valores (nesse caso, o número de linhas), a média mean desses valores, o desvio padrão std, o valor mínimo e máximo, min, max e os quartis em 25%, 50%, 75%.

» dfPaises.info()
↳ <class 'pandas.core.frame.DataFrame'>
  RangeIndex: 1704 entries, 0 to 1703
  Data columns (total 6 columns):
   #   Column      Non-Null Count  Dtype  
  ---  ------      --------------  -----  
   0   continente  1704 non-null   object 
   1   pais        1704 non-null   object 
   2   ano         1704 non-null   int64  
   3   expVida     1704 non-null   float64
   4   populacao   1704 non-null   int64  
   5   pibPercap   1704 non-null   float64
  dtypes: float64(2), int64(2), object(2)
  memory usage: 80.0+ KB

» dfPaises.describe()
↳ 
                   ano          expVida         populacao         pibPercap
 count      1704.00000      1704.000000      1.704000e+03       1704.000000
  mean      1979.50000        59.474439      2.960121e+07       7215.327081
  std         17.26533        12.917107      1.061579e+08       9857.454543
  min       1952.00000        23.599000      6.001100e+04        241.165876
  25%       1965.75000        48.198000      2.793664e+06       1202.060309
  50%       1979.50000        60.712500      7.023596e+06       3531.846988
  75%       1993.25000        70.845500      1.958522e+07       9325.462346
  max       2007.00000        82.603000      1.318683e+09     113523.132900

Gravação e recuperação de dados em arquivos pickle

Após a verificação de integridade dos dados e a realização das alterações básicas necessárias é boa ideia salvar em disco o dataframe nesse momento. Para isso usamos pandas.to_pickle(dfFrame, 'nomeArquivo.pkl'), gravando um arquivopickle. Para recuperá-lo em qualquer momento usamos dfPaises = pandas.read_pickle('./dados/dataframePaises.pkl').

» # gravando um arquivo pickle
» pd.to_pickle(dfPaises, './dados/dataframePaises.pkl')    

» # mais tarde esse dataframe pode ser recuperado
» del dfPaises   # para limpar essa variável
» dfPaises = pd.read_pickle('./dados/dataframePaises.pkl')
» # o dataframe é recuperado

Seleção e filtragem

As principais formas de seleção de um ou mais valores de um dataframe são os métodos dataframe.loc(), dataframe.iloc(), dataframe.at e dataframe.iat. Um subconjunto de dados do dataframe, seja por seleção de linhas, colunas ou ambas, é denominado de fatia ou slice.

A principal diferença entre loc (at) e iloc (iat) é a seguinte: loc é baseado em labels ou nomes das linhas ou colunas, enquanto iloc é baseado nos índices numéricos (mesmo que tenham nomes) sempre com base 0.

  • dataframe.at[row_label, column_label]
  • dataframe.iat[row_position, column_position]
  • dataframe.loc[row_label, column_label]
  • dataframe.iloc[row_position, column_position]

Na tabela abaixo nos referiremos a um dataframe nomeado como df. (S) se refere a uma Series retornada, (D) a um dataframe.

Operações df.iat e df.at retorna: (índices são posições linhas/colunas)
df.iat[m,n] elemento da m-ésima linha, n-ésima coluna
df.at[lblLinha, lblColuna] elemento linha/coluna relativas aos labels lblLinha/lblColuna
Operações df.iloc retorna: (índices são posições das linhas/colunas)
df.iloc[n] n-ésima linha (S)
df.iloc[[n]] n-ésima linha (D)
df.iloc[-n] n-ésima linha, contando do final
df.iloc[i,j:, n] linhas i, até j (exclusive), coluna n (S)
df.iloc[[i,j,k]:[m,n,o]] linhas i, j, k, colunas m, n, o
df.iloc[:, n] n-ésima coluna (S)
df.iloc[:, [n]] n-ésima coluna (D)
df.iloc[:,-1] última coluna
df.iloc[i:j,m:n] linhas i até j (exclusive), colunas m até n (inclusive)
Operações df.loc retorna: (índices linhas/colunas se referem aos seus labels)
df.loc[n] linha de índice n (S)
df.loc[[n]] linha de índice n (D)
df.loc[:] todas as linhas e colunas (D)
df.loc[:, 'col'] todas as linhas, coluna ‘col’ (S)
df.loc[:, ['col']] todas as linhas, coluna ‘col’ (D)
df.loc[:, ['col1', 'col2']] todas as linhas, colunas ‘col1’ e ‘col2’ (D)
df.loc[i:j, ['col1', 'col2']] linhas com índices de i até i (inclusive), colunas ‘col1’ e ‘col2’ (D)
df.loc[[i,j,k] , ['col1', 'col2']] linhas com índices i, j, k, colunas ‘col1’ e ‘col2’ (D)
df.loc[i:j, 'col1':'coln']] linhas com índices i até j (inclusive), colunas ‘col1’ até ‘coln’ (inclusive) (D)
Atalhos o mesmo que
df['col1'] ou df.col1 df.loc[:, ‘col1’]] (S)
df[['col1', 'col2']] df.loc[:, [‘col1’, ‘col2’]] (D)

Em todos esses métodos uma exceção de KeyError é levantada se um índice ou label não existir na dataframe.

Se o index da linha coincidir com sua posição então df.loc[n] e df.iloc[n] serão as mesmas linhas. Isso nem sempre é verdade, como se verá abaixo com o reordenamento das linhas.

São incorretas as sintaxes: df.loc[-n], df.loc[:, n], df.loc[:, [n]] com n numérico pois os labels devem ser fornecidos.

Exemplos de consultas e seleções

dataframe.iloc()

Para outros exemplos vamos usar o dataframe já carregado, dfPaises, para fazer consultas e seleções, primeiro usando dataframe.iloc(). Lembramos que a contagem de índices sempre se inicia em 0:

» # lembrando que dfPaises.iloc[[0]] é um dataframe, dfPaises.iloc[0] é uma Series
» # primeira linha, pelo índice    
» dfPaises.iloc[[0]]
↳    continente            pais    ano    expVida    populacao     pibPercap
  0        Asia     Afghanistan   1952     28.801      8425333    779.445314

» # última linha, pelo índice    
» dfPaises.iloc[[-1]]
↳         continente          pais     ano    expVida    populacao     pibPercap
  1703        Africa      Zimbabwe    2007     43.487     12311143    469.709298    

» # linhas 15 até 20 (exclusive), colunas 2 até 5 (exclusive)
» dfPaises.iloc[15:20, 2:5]
↳      ano   expVida  populacao
  15  1967     66.22    1984060
  16  1972     67.69    2263554
  17  1977     68.93    2509048
  18  1982     70.42    2780097
  19  1987     72.00    3075321

» # linhas 1, 3, 5 , colunas 2, 5
» dfPaises.iloc[[1,3,5],[2,5]]
↳     ano   pibPercap
  1  1957   820.853030
  3  1967   836.197138
  5  1977   786.113360

» # linhas 1, 3, 5, última coluna
» dfPaises.iloc[[1,3,5],-1]
↳ 1    820.853030
  3    836.197138
  5    786.113360
  Name: pibPercap, dtype: float64

» # todas as linhas, coluna 3
» dfPaises.iloc[:, [3]].head()
↳     expVida
  0    28.801
  1    30.332
  2    31.997
  3    34.020
  4    36.088

» # linhas 0, 3, 6, 24; colunas 0, 3, 5
» dfPaises.iloc[[0,3,6,24], [0,3,5]]
↳    continente   expVida    pibPercap
  0        Asia    28.801   779.445314
  3        Asia    34.020   836.197138
  6        Asia    39.854   978.011439
  24     Africa    43.077  2449.008185

A seleção das linhas nos dois métodos é diferente. Em dataframe.loc[m,n] linhas com labels de m até n (inclusive) são selecionadas. Em dataframe.iloc[m,n] são selecionadas linhas com índices (numéricos) de m até n (exclusive).

» # iloc[m,n] exibe linhas m até n (exclusive)
» dfPaises.iloc[1:2]
↳   continente         pais   ano  expVida  populacao   pibPercap
  1       Asia  Afghanistan  1957   30.332    9240934   820.85303    

» # loc[m:n] exibe linhas m até n (inclusive)
» dfPaises.loc[1:2]
↳     continente          pais    ano   expVida   populacao    pibPercap
  1         Asia   Afghanistan   1957    30.332     9240934    820.85303
  2         Asia   Afghanistan   1962    31.997    10267083    853.10071
dataframe.loc()

Os próximos testes são feitos com dataframe.loc(), que deve receber os labels como índices.

» # todas as linhas, só colunas 'ano' e 'populacao' (limitadas por head())
» dfPaises.loc[:,['ano','populacao']].head()
↳        ano     populacao
  0     1952       8425333
  1     1957       9240934
  2     1962      10267083
  3     1967      11537966
  4     1972      13079460

» # linhas 3 até 6 (inclusive), só colunas 'ano' e 'expVida'
» dfPaises.loc[3:6,['ano', 'expVida']]
↳        ano     expVida
  3     1967      34.020
  4     1972      36.088
  5     1977      38.438
  6     1982      39.854

» # todas as linhas, só colunas 'ano' (restritas por head())
» dfPaises.loc[:, 'ano'].head()
↳ 0    1952
  1    1957
  2    1962
  3    1967
  4    1972

Métodos df.loc, df.iloc, df.at e df.iat

Para explorar um pouco mais a diferença no uso de df.loc e df.iloc vamos criar um dataframe bem simples e sem valores nulos.

» dic = {'Pedro': {'Prova 1': 5.4, 'Prova 2': 6.2, 'Prova 3': 7.9},
         'Ana':  {'Prova 1': 8.5, 'Prova 2': 9.7, 'Prova 3': 6.6},
         'Luna': {'Prova 1': 5.0, 'Prova 2': 7.0, 'Prova 3': 4.3}
        }
» dfNotas = pd.DataFrame(dic)
» dfNotas
↳           Pedro     Ana    Luna
  Prova 1     5.4     8.5     5.0
  Prova 2     6.2     9.7     7.0
  Prova 3     7.9     6.6     4.3

df.loc e df.at usa labels de linhas e colunas.
df.iloc e df.iat usa números (índices) de linhas e colunas.

Nos comentários listamos seleções usando df.iloc para se obter o mesmo retorno.

» dfNotas.loc['Prova 1','Luna']             # dfNotas.iloc[0,2]
↳ 5.0

» dfNotas.loc['Prova 1']                    # dfNotas.iloc[0] (Series)
↳ Pedro    5.4
  Ana      8.5
  Luna     5.0

» dfNotas.loc[['Prova 1']]                  # dfNotas.iloc[[0]] (dataframe)
↳           Pedro   Ana   Luna
  Prova 1     5.4   8.5    5.0

» dfNotas.loc[['Prova 1','Prova 2']]        # dfNotas.iloc[0:2] (dataframe)
↳           Pedro    Ana   Luna
  Prova 1     5.4    8.5    5.0
  Prova 2     6.2    9.7    7.0

» dfNotas.loc['Prova 1': 'Prova 3']         # dfNotas.iloc[0:3] (dataframe)
↳           Pedro    Ana   Luna
  Prova 1     5.4    8.5    5.0
  Prova 2     6.2    9.7    7.0
  Prova 3     7.9    6.6    4.3

» dfNotas.loc[['Prova 1'],['Ana','Luna']]   # dfNotas.iloc[[0],[1,2]]  (dataframe)
↳           Ana   Luna
  Prova 1   8.5    5.0

» dfNotas.loc['Prova 1':'Prova 3', 'Pedro':'Luna']   # dfNotas.iloc[0:3,0:3] (dataframe)
↳           Pedro    Ana    Luna
  Prova 1     5.4    8.5     5.0
  Prova 2     6.2    9.7     7.0
  Prova 3     7.9    6.6     4.3

» dfNotas.loc[:,['Luna']]                   # dfNotas.iloc[:,[2]]
↳           Luna
  Prova 1    5.0
  Prova 2    7.0
  Prova 3    4.3

Observe que em dfNotas.iloc[0:3,0:3] são selecionadas as linhas de índices 0, 1 e 2 e colunas 0, 1 e 2.

Análogos à df.loc e df.iloc temos, respectivamente, df.at[lblLinha, lblColuna] e df.iat[m,n] onde lblLinha, lblColuna se referem aos labels e m, n aos índices das linhas/colunas. Ambos recebem um par e retornam um único valor do dataframe. Quando aplicados em uma Series iat e at recebem um único índice/label localizador de posição.

» dfNotas.iat[2,1]
↳ 6.6
» dfNotas.iloc[0].iat[1]                 # o mesmo que dfNotas.loc['Prova 1'].iat[1]
↳ 8.5
» dfNotas.at['Prova 1', 'Luna']
↳ 5.0
» dfNotas.loc['Prova 1'].at['Ana']       # o mesmo que dfNotas.loc['Prova 1'].iat[1]
↳ 8.5

Nenhuma das duas formas de seleção de uma slice (.loc ou .iloc) copiam um dataframe por referência, como ocorre com numPy.ndarrays. Por exemplo, df = dfNotas.iloc[:,[2]] é uma cópia da 3ª coluna, e não uma referência ou view. Ela pode ser alterada sem que o dataframe original seja modificado. Se um novo valor for atribuído ao slice diretamente, no entanto, o dataframe fica alterado.

» df = dfNotas.iloc[:,[2]]
» df.Luna = 10
» display(df,dfNotas)
↳           Luna
  Prova 1     10
  Prova 2     10
  Prova 3     10

↳           Pedro   Ana   Luna
  Prova 1     5.4   8.5    5.0
  Prova 2     6.2   9.7    7.0
  Prova 3     7.9   6.6    4.3

» # no entanto se o slice receber atribuição direta o dataframe fica alterado
» dfNotas.iloc[:,[2]] = 10

» dfNotas
↳           Pedro    Ana    Luna
  Prova 1     5.4    8.5    10.0
  Prova 2     6.2    9.7    10.0
  Prova 3     7.9    6.6    10.0

» # para inserir valores diferentes outro dataframe de ser atribuído ao slice
» dic = {'Luna': {'Prova 1': 8.5, 'Prova 2': 7.9, 'Prova 3': 10}}
» dfLuna = pd.DataFrame(dic)
» dfNotas.iloc[:,[2]] = dfLuna

» dfNotas
↳           Pedro    Ana   Luna
  Prova 1     5.4    8.5    8.5
  Prova 2     6.2    9.7    7.9
  Prova 3     7.9    6.6   10.0

» # alternativamente, um np.array com shape apropriado pode ser atribuído ao slice
» arrLuna =np.array([2.3, 4.5, 5.6]).reshape(3,1)
» dfNotas.iloc[:,[2]] = arrLuna
» dfNotas
↳          Pedro   Ana   Luna
  Prova 1    5.4   8.5    2.3
  Prova 2    6.2   9.7    4.5
  Prova 3    7.9   6.6    5.6

Na atribuição dfNotas.iloc[:,[2]] = 10 houve o broadcasting de 10 para uma forma compatível com o slice.

Para que a atribuição seja bem sucedida, sem necessidade de broadcasting, um objeto de mesmo formato deve ser atribuído. No caso dfNotas.iloc[:,[2]].shape = dfLuna.shape = (3, 1) (3 linhas, 1 coluna). O mesmo ocorre com a atribuição de um array do numpy.

Manipulando linhas e colunas

Um array booleano pode ser passado como índice de um dataframe. Apenas as linhas correspondentes ao índice True será exibida. Alguns métodos de string estão disponíveis para testes em campos, como df['campo'].str.startswith('str') e df['campo'].str.endswith('str') (começa e termina com). O teste df['campo'].isin(['valor1', 'valor2'])] retorna True se os campos estão contidos na lista.

Para os exemplos usamos o dataframe dfPaises.

» # seleção por array booleano
» dfPaises.loc[dfPaises['ano'] == 2002].head(3)
↳      continente         pais   ano   expVida   populacao    pibPercap
  10         Asia  Afghanistan   2002   42.129    25268405   726.734055
  22       Europe      Albania   2002   75.651     3508512  4604.211737
  34       Africa      Algeria   2002   70.994    31287142  5288.040382

» # quais os paises tem nome começados com 'Al'
» dfPaises.loc[dfPaises['pais'].str.startswith('Al')]['pais'].unique()
↳ array(['Albania', 'Algeria'], dtype=object)

» # quais os paises tem nome terminados em 'm'
» dfPaises.loc[dfPaises['pais'].str.endswith('m')]['pais'].unique()
↳ array(['Belgium', 'United Kingdom', 'Vietnam'], dtype=object)

» # quantas linhas se referem à 'Europe' e 'Africa'
» dfPaises.loc[dfPaises['continente'].isin(['Europe', 'Africa'])].shape[0]
↳ 984

» dfPaises.loc[(dfPaises['continente']=='Africa') & (dfPaises['ano']==1957)].head(4)
↳       continente         pais     ano    expVida    populacao      pibPercap
  25        Africa     Algeria    1957     45.685     10270856    3013.976023
  37        Africa      Angola    1957     31.999      4561361    3827.940465
  121       Africa       Benin    1957     40.358      1925173     959.601080
  157       Africa    Botswana    1957     49.618       474639     918.232535


» # paises e anos com população < 7000 ou expectativa de vida > 82
» dfPaises.loc[(dfPaises['populacao'] < 70000) | (dfPaises['expVida'] > 82)][['ano','pais']]
↳          ano    pais
  420     1952    Djibouti
  671     2007    Hong Kong, China
  803     2007    Japan
  1296    1952    Sao Tome and Principe
  1297    1957    Sao Tome and Principe
  1298    1962    Sao Tome and Principe
Operador significa
& and, e
| or, ou
~ not, negação

O método arr.unique() acima foi aplicado para ver quais os países satisfazem as condições, sem repetições. arr.shape é uma tupla (número linhas, número colunas). Os últimos exemplos fazem testes compostos usando os operadores & (and, e lógico) e | (or, ou lógico).

Se nenhum campo for submetido ao teste lógico todos os valores do dataframe são usados. O mesmo ocorre com a aplicação de uma função, como mostrado para uma função lambda.

» # novos teste com loc e iloc
» dic = {'Pedro': {'Prova 1': 5.4, 'Prova 2': 6.2, 'Prova 3': 7.9},
         'Ana':  {'Prova 1': 8.5, 'Prova 2': 9.7, 'Prova 3': 6.6},
         'Luna': {'Prova 1': 5.0, 'Prova 2': 7.0, 'Prova 3': 4.3}
          }
» dfNotas = pd.DataFrame(dic)
» dfNotas
↳           Pedro     Ana    Luna
  Prova 1     5.4     8.5     5.0
  Prova 2     6.2     9.7     7.0
  Prova 3     7.9     6.6     4.3

» # o teste retorna um df com o mesmo shape que dfNotas
» dfNotas > 6
↳             Pedro    Ana    Luna
  Prova 1     False   True   False
  Prova 2      True   True    True
  Prova 3      True   True   False

» # os campos do df são filtrados pelo df booleano
» dfNotas[dfNotas > 6]
↳           Pedro     Ana    Luna
  Prova 1     NaN     8.5     NaN
  Prova 2     6.2     9.7     7.0
  Prova 3     7.9     6.6     NaN

Funções lambda

Uma função pode ser aplicada sobre elementos de uma coluna específica ou sobre todas as colunas. Veremos mais tarde detalhes sobre o uso de dataframe.apply().

» dfNotas
↳           Pedro     Ana    Luna
  Prova 1     5.4     8.5     5.0
  Prova 2     6.2     9.7     7.0
  Prova 3     7.9     6.6     4.3

» # uma função aplicada à todos os elementos do df
» dfNotas.apply(lambda x: x**2)
↳            Pedro     Ana     Luna
  Prova 1    29.16   72.25    25.00
  Prova 2    38.44   94.09    49.00
  Prova 3    62.41   43.56    18.49

Funções lambda que retornam valores booleanos podem ser usadas para filtragem dos campos de um dataframe. No exemplo dfPaises['pais'].apply(lambda x: len(x)) == 4 retorna True para as linhas onde o campo pais tem comprimento de 4 letras.

» dfPaises.loc[dfPaises['pais'].apply(lambda x: len(x)) == 4].head(2)
↳      continente    pais    ano   expVida   populacao     pibPercap
  264      Africa    Chad   1952    38.092     2682462   1178.665927
  265      Africa    Chad   1957    39.881     2894855   1308.495577

# são os países com nomes de 4 letras:
» set(dfPaises.loc[dfPaises['pais'].apply(lambda x: len(x)) == 4]['pais'])
↳ {'Chad', 'Cuba', 'Iran', 'Iraq', 'Mali', 'Oman', 'Peru', 'Togo'}

# o mesmo que
# dfPaises.loc[dfPaises['pais'].apply(lambda x: len(x)) == 4]['pais'].unique()  # (um array)

O seletor pode ser composto de mais testes, ligados pelos operadores lógicos & e |.

» # paises/anos com nomes compostos por mais de 2 palavras e população acima de 50 milhões
» dfPaises.loc[(dfPaises['pais'].apply(lambda x: len(x.split(' '))) > 2) &
               (dfPaises['populacao']>50_000_000)]

↳      continente    pais                  ano   expVida   populacao    pibPercap
  334      Africa    Congo, Dem. Rep.     2002    44.966    55379852   241.165876
  335      Africa    Congo, Dem. Rep.     2007    46.462    64606759   277.551859

Ordenamento com Sort

Para ordenar um dataframe podemos usar o método sort, com a seguinte sintaxe:

dataframe.sort_values(by=['campo'], axis=0, ascending=True, inplace=False)
onde
by pode ser uma string ou lista com o nome ou nomes dos campos, na prioridade de ordenamento,
axis{0 ou ‘index’, 1 ou ‘columns’} default 0, indica o eixo a ordenar,
ascending=True/False se ordenamento é crescente/decrescente.

Existem vários outros parâmetros para o controle de ordenamentos, como pode ser lido no API reference do pandas.

Muitas informações importantes sobre um conjunto de dados podem ser obtidas apenas pela inspecção dos dados. Por exemplo, podemos encontrar respostas para:

  • que país do mundo teve, em qualquer ano, o PIB percapita mais elevado?
  • no ano de 2007 (o último de nossa lista), quais são os 5 países com maior população, e quais são os 5 com PIB mais baixo, no mundo?
  • no ano de 2002, quantos países no mundo tinham PIB percapita acima e abaixo da média?
# encontramos o maior pib percapita e a linha que corresponde a ele   
» dfMax = dfPaises[dfPaises['pibPercap']==dfPaises['pibPercap'].max()]
» dfMax
↳     continente    pais   ano   expVida  populacao    pibPercap
  853       Asia  Kuwait  1957    58.033     212846  113523.1329

» # para formatar uma resposta amigável
» ano = dfMax['ano'].values[0]
» pais = dfMaxPib['pais'].values[0]
» pibP = dfMaxPib['pibPercap'].values[0]

» print('O PIB percapita máximo foi de {} e ocorreu no {} em {}.'.format(pibP, pais, ano))
↳ O PIB percapita máximo foi de 113523.1329 e ocorreu no Kuwait em 1957.

» # ordenando em ordem decrescente
» dfPaises.sort_values(by=['pibPercap'], ascending=False).iloc[[0]]
↳      continente     pais   ano  expVida  populacao     pibPercap
  853        Asia   Kuwait  1957   58.033     212846   113523.1329

Observe que dfMax['ano'] é uma Series que, se exposta diretamente, não contém apenas o ano. Por isso extraimos dele o valor, 1º campo: dfMax['ano'].value[0]. Idem para pais e pibPercap.

Claro que podemos também ordenar o dataframe em ordem descrecente no campo pibPercap e pegar apenas a 1ª linha.
dataframe.iloc[[0]] foi usado para pegar a 1ª linha, cujo índice é 853. A mesma linha seria retornada com dataframe.loc[[853]], o que mostra, mais uma vez, a diferença entre df.loc e df.iloc.

Para encontrar os 5 países com maior população em 2007 usamos a mesma técnica de ordenamento. Primeiro filtramos pelo ano = 2007, ordenamos por população, ordem inversa, e pegamos os 5 primeiros. Para exibir o resultado podemos transformar o dataframe em string, sem os índices.

Para encontrar os 5 países com maior população em 2007, e os 5 com menor PIB:

» # dataframe com 5 maiores populações em 2007
» popMax = dfPaises[dfPaises['ano']==2007].sort_values(by=['populacao'], ascending=False).head()

» print(popMax[['pais','populacao']].to_string(index=False))
↳          pais  populacao
          China 1318683096
          India 1110396331
  United States  301139947
      Indonesia  223547000
         Brazil  190010647

» # o 5 países com menor pib:
» # criamos um dataframe apenas do ano 2007 e acrescentamos o campo pib
» # pib = pibPercap * populacao
» df2007 = dfPaises[dfPaises['ano']==2007]
» df2007['pib'] = df2007['pibPercap'] * df2007['populacao']       

» # são os países com menor pib em 2007
» df2007.sort_values(by=['pib']).head()['pais']
↳ 1307    Sao Tome and Principe
  323                   Comoros
  635             Guinea-Bissau
  431                  Djibouti
  563                    Gambia
  Name: pais, dtype: object

# se não precisamos mais do df, podemos apagá-lo
» del df2007

Para saber quantos países tem PIB percapita acima e abaixo da média em 2002 primeiro encontramos essa média. Depois selecionamos as linhas que satisfazem com pibPercap >= media e pibPercap < media. Para saber quantas linhas restaram contamos, por exemplo, quantos elementos existem em seu index.

» # média do pibPercap em 2002 (um escalar)
» media2002 = dfPaises[dfPaises.ano==2002]['pibPercap'].mean()
» acima = dfPaises[(dfPaises.ano==2002) & (dfPaises.pibPercap ≥= media2002)].index.size
» abaixo = dfPaises[(dfPaises.ano==2002) & (dfPaises.pibPercap < media2002)].index.size

» print('[Dos {} países, {} tem PIB percapita acima da média, {} abaixo da média.'.format(acima+abaixo, acima, abaixo))
↳ Dos 142 países, 44 tem PIB percapita acima da média, 98 abaixo da média.

Obtenção e análise de um slice : Brasil

Em diversas circunstâncias queremos fazer análise de apenas um slice da dataframe geral. Além de simplificar o conjunto de campos podemos conseguir com isso um uso menor de espaço em memória e maior velocidade de processamento.
Podemos, por ex., obter um dataframe separado apenas com a os dados referentes ao Brasil. Passando como índice o array booleano dfPaises['pais'] == 'Brazil' apenas as linhas relativas a esse país serão retornadas.

» dfBrasil = dfPaises[dfPaises['pais'] == 'Brazil'][['ano', 'expVida', 'populacao', 'pibPercap']]
» dfBrasil.head()
↳ 
         ano  expVida   populacao     pibPercap
  168   1952   50.917    56602560   2108.944355
  169   1957   53.285    65551171   2487.365989
  170   1962   55.665    76039390   3336.585802
  171   1967   57.632    88049823   3429.864357
  172   1972   59.504   100840058   4985.711467

O dataframe dfBrasil tem os mesmos índices que aos do segmento de dfPaises, de onde ele foi retirado. Para restabelecer esses índices usamos dataFrame.reset_index(). Se utilizado com o parâmetro drop=True o índice antigo é excluído (e perdido), caso contrário é copiado como uma coluna do dataframe. Para atribuir um nome para o índice usamos dataframe.index.rename('novoNome', inplace=True).

» # os índices iniciais são
» dfBrasil.index
↳ Int64Index([168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179], dtype='int64')

» # resetamos os índices, abandonando a coluna de índices iniciais
» dfBrasil.reset_index(drop=True, inplace=True)
» # novos índices
» dfBrasil.index
↳ RangeIndex(start=0, stop=12, step=1)

» dfBrasil.index.rename('id', inplace=True)
» # o dataframe fica assim:
» dfBrasil.head(3)
↳       ano     expVida     populacao       pibPercap
  id
  0    1952      50.917      56602560     2108.944355
  1    1957      53.285      65551171     2487.365989
  2    1962      55.665      76039390     3336.585802

Podemos usar um campo qualquer como index, com qualquer dtype. No caso abaixo usamos o campo ano como índice.

» # vamos usar o campo ano como index
» dfBrasil.set_index('ano', inplace=True)
» dfBrasil.head(3)
↳           expVida      populacao       pibPercap
  ano             
  1952.0     50.917     56602560.0     2108.944355
  1957.0     53.285     65551171.0     2487.365989
  1962.0     55.665     76039390.0     3336.585802

» # agora os índices passam a ser o ano
» dfBrasil.loc[1997]            # é uma Series
↳ expVida      6.938800e+01
  populacao    1.685467e+08
  pibPercap    7.957981e+03
  Name: 1997.0, dtype: float64

» # dfBrasil.loc[[1997]]            # é um dataframe

Para restaurar a coluna ano copiamos o índice para essa coluna e restauramos o índice.

» # restauramos a coluna ano
» dfBrasil['ano'] = dfBrasil.index
» # e resetamos o indice
» dfBrasil.reset_index(drop=True, inplace=True)

» dfBrasil.head(3)
↳    expVida   populacao     pibPercap    ano
  0   50.917    56602560   2108.944355   1952
  1   53.285    65551171   2487.365989   1957
  2   55.665    76039390   3336.585802   1962

Linhas podem ser inseridas de várias formas. Um delas consiste em criar novos dataframes com as linhas a inserir e concatenar como a dataframe inicial. Para isso usamos pandas.concat(): pd.concat([dfInicio, dfFinal]).
Vamos inserir linhas com dados fictícios, apenas para efeito de aprendizado.

» colunas = ['expVida','populacao','pibPercap','ano']      # nomes das colunas, na ordem dos dados
» valores1 = [48.0,45000000,2000.0,1951]                   # valores a inserir no ínicio (ano 1951)
» valores2 = [75.0, 200000000, 9500.0, 2008]               # valores a inserir no final (ano 2008)
» dfP = pd.DataFrame([valores1], columns=colunas)          # df a inserir no ínicio
» dfU = pd.DataFrame([valores2], columns=colunas)          # df a inserir no final
» dfBrasil = pd.concat([dfP, dfBrasil])                    # 1ª linha + dfBrasil
» dfBrasil = pd.concat([dfBrasil, dfU])                    # dfBrasil + última linha

» # agora a 1ª linha é
» dfBrasil.iloc[[0]]
↳    expVida   populacao   pibPercap   ano
  0     48.0   45000000       2000.0  1951

» # a última linha é
» dfBrasil.iloc[[-1]]
↳    expVida   populacao  pibPercap   ano
  0     75.0   200000000     9500.0  2008

» # como os índices ficaram duplicados e desordenados fazemos um reordenamento
» dfBrasil.reset_index(drop=True, inplace=True)

» dfBrasil
↳      expVida     populacao       pibPercap      ano
  0     48.000      45000000     2000.000000     1951
  1     50.917      56602560     2108.944355     1952
  2     53.285      65551171     2487.365989     1957
  ------------ linhas 3 até 11 omitidas ----------------
  12    72.390     190010647     9065.800825     2007
  13    75.000     200000000     9500.000000     2008

Como essas linhas não contém dados corretos, vamos apagá-las. Para usamos dataframe.drop(linha, axis=0, inplace = True), onde linha é o label, que pode não ser numérico) da linha ou seu índice (numérico). Várias linhas podem ser apagadas com dataframe.drop([linha0,...,linhan], axis=0, inplace = True).

» # apagar linhas 0 e 13: axis = 0 se refere às linhas
» dfBrasil.drop([0,13], axis=0, inplace = True)

» # para reordenar os índices
» dfBrasil.reset_index(drop=True, inplace=True)

» # recolocar a coluna 'ano' no início
» dfBrasil = dfBrasil[['ano', 'expVida', 'populacao', 'pibPercap']]
» dfBrasil

# o estado do dataframe agora é
↳ dfBrasil
        ano    expVida     populacao       pibPercap
  0    1952     50.917      56602560     2108.944355
  1    1957     53.285      65551171     2487.365989
  2    1962     55.665      76039390     3336.585802
  ------------ linhas 3 até 8 omitidas ----------------
  9    1997     69.388     168546719     7957.980824
  10   2002     71.006     179914212     8131.212843
  11   2007     72.390     190010647     9065.800825

Vamos inserir uma coluna, atribuindo a ela um escalar (um valor único). Aqui ocorre, como nas Series, o broadcasting, onde o escalar é transformado em uma Series de tamanho apropriado antes de ser inserido na nova coluna. Todas as linhas terão o valor 42 no campo “novoCampo”.

Em seguida alteramos o valor dessa coluna em uma linha específica, usando dataframe.loc(númeroLinha, nomeColuna) ou dataframe.iloc(numeroLinha, numeroColuna). Depois, como essa é uma coluna indesejada, nos a apagamos usando dataframe.drop('nomeColuna', axis=1, inplace=True).

» dfBrasil['novoCampo'] = 42
» dfBrasil.head(3)
↳        ano     expVida     populacao       pibPercap   novoCampo
  0     1952      50.917      56602560     2108.944355          42
  1     1957      53.285      65551171     2487.365989          42
  2     1962      55.665      76039390     3336.585802          42

» # alteramos o 'novoCampo' na linha 1 (usando loc)
» # e a coluna 4 ('novoCampo') na linha 2 (usando iloc, fornecendo o índice)
» dfBrasil.loc[1,'novoCampo'] = 123456
» dfBrasil.iloc[2,4] = 22222

» dfBrasil.head(3)
↳      ano     expVida     populacao       pibPercap     novoCampo
  0   1952      50.917      56602560     2108.944355     42
  1   1957      53.285      65551171     2487.365989     123456
  2   1962      55.665      76039390     3336.585802     22222

» # apagamos essa coluna com drop
» dfBrasil.drop('novoCampo', axis=1, inplace=True)
» # o dataframe fica como no início

Um campo pode ser inserido como resultado de operações entre outros campos. No caso abaixo criamos uma coluna pib que é o produto das colunas populacao × pibPercap. O resultado é aplicado, em cada linha, à nova coluna, em notação científica. Na 1ª linha pib = 1.193716 × 1011.

Outra coluna marca a passagem de quando a expectativa de vida do brasileiro ultrapassa os 60 anos.

» dfBrasil.loc[:,'pib'] = dfBrasil['pibPercap'] * dfBrasil['populacao']
» dfBrasil.head(4)
↳        ano    expVida       populacao       pibPercap     pib
  0     1952     50.917      56602560.0     2108.944355     1.193716e+11
  1     1957     53.285      65551171.0     2487.365989     1.630498e+11
  2     1962     55.665      76039390.0     3336.585802     2.537119e+11
  3     1967     57.632      88049823.0     3429.864357     3.019989e+11

» # inserindo coluna 'acima60'(†)
» dfBrasil.loc[:,'acima60'] = dfBrasil['expVida'] > 60
» dfBrasil.loc[3:6,['ano','expVida','acima60']]
↳      ano   expVida  acima60
  3   1967    57.632    False
  4   1972    59.504    False
  5   1977    61.489     True
  6   1982    63.336     True
  
» dfBrasil[dfBrasil['acima60']]
» # todas as linhas com expVida > 60 são exibidas (output omitido)

» # as colunas podem ser removidas (para ficarmos com o dataframe original)
» dfBrasil.drop(['acima60', 'pib'], axis=1, inplace=True)

() dfBrasil['expVida'] > 60 é uma Series booleana.

Objetos de índices

Em um dataframe, assim como nas Series, a informação relativa aos índices e seus nomes (labels ), assim como os nomes dos eixos, são armazenados em objetos Index (índice). O objeto Index é imutável (não pode ser alterado após a construção).

» pdSerie = pd.Series(range(4), index=['a1', 'a2', 'a3', 'a4'])
» index = pdSerie.index
» index
↳ Index(['a1', 'a2', 'a3', 'a4'], dtype='object')
» # o índice é uma sequência (pode ser lido em slices)
» index[2]
↳ 'a3'
» index[2:]
↳ Index(['a3', 'a4'], dtype='object')

» # o index é imutável
» index[0] = 'A'
↳ TypeError: Index does not support mutable operations

» # já vimos que índices não fornecidos são preenchidos como um range
» pd.Series(range(4)).index
↳ RangeIndex(start=0, stop=4, step=1)
Uma UA é a distância média da Terra ao Sol.
1 UA ≈ 149,6 × 109 m.

No exemplo abaixo construimos primeiro um objeto Index usando pandas.Index(lista). Em seguida construimos uma Series usando esse index, contendo como valores as distâncias dos planeta até o Sol, em unidaddes astronômicas (UA). Com a Series inicializamos um dataframe com o mesmo index.

» # objeto index
» labels = pd.Index(np.array(['mercurio', 'venus', 'terra']))
» labels
↳ Index(['mercurio', 'venus', 'terra'], dtype='object')

» # Serie construída com esse index
» planetas = pd.Series([0.387, 0.723, 1], index=labels)
» planetas
↳ mercurio    0.387
  venus       0.723
  terra       1.000
  dtype: float64

» # o index da Series é o mesmo objeto que labels
» planetas.index is labels
↳ True

» # essa Series pode ser usada para construir um dataframe
» dfPlanetas = pd.DataFrame(planetas)
» dfPlanetas
↳             0
  mercurio    0.387
  venus       0.723
  terra       1.000

» # o index do dataframe é o mesmo que o da Series
» dfPlanetas.index is labels
↳ True

» # alteramos o nome da coluna
» dfPlanetas.rename(columns={0:'distancia'}, inplace=True)
» dfPlanetas
↳           distancia
  mercurio      0.387
  venus         0.723
  terra         1.000

Podemos inserir uma coluna, por exemplo, relativa ao diâmetro dos planetas (comparados ao diâmetro da Terra), atribuindo valores à uma nova coluna de nome ‘diametro’. O objeto atribuído deve ter o mesmo shape (ou passar por broadcasting). Alterar a ordem das colunas, o que pode ser feito com df.reindex(listaColunas), altera todo o dataframe (embora não inplace). O objeto retornado se ajusta de acordo com os índices fornecidos.

» # inserir uma nova coluna
» dfPlanetas['diametro'] = pd.Series([0.382, 0.949, 1], index=labels)
» dfPlanetas
↳         distancia   diametro
  mercurio    0.387      0.382
  venus       0.723      0.949
  terra       1.000      1.000

» # as colunas estão em um objeto Index
» dfPlanetas.columns
↳ Index(['distancia', 'diametro'], dtype='object')

» type(dfPlanetas.columns)
↳ pandas.core.indexes.base.Index

» 'distancia' in dfPlanetas.columns
↳ True

» # podemos alterar a ordem das colunas com reindex
» dfPlanetas.reindex(['venus','terra','mercurio'])
↳       distancia    diametro
  venus     0.723       0.949
  terra     1.000       1.000
  mercurio  0.387       0.382

» # podemos ordenar os índices para ordenar o dataframe
» idx = dfPlanetas.index
» idx = idx.sort_values()
» idx
↳ Index(['mercurio', 'terra', 'venus'], dtype='object')

» dfPlanetas.reindex(idx)
↳         distancia   diametro
  mercurio    0.387      0.382
  terra       1.000      1.000
  venus       0.723      0.949

Diferentes de um conjunto (set) objetos Index podem ter índices repetidos. Se índices inseridos não correspondem à dados existentes estes são preenchidos com NaN. Os parâmetros method='bfill' (ou “ffill” forçam as colunas (ou linhas) com NaN a serem preenchidos com valores das colunas (ou linhas) anteriores ou posteriores. Claro que reindexações podem ser também obtidas com df.loc e df.iloc.

» # índices de linhas repetidos
» duplicados = pd.Index(['mercurio', 'venus', 'terra', 'mercurio', 'marte'])
» duplicados
↳ Index(['mercurio', 'venus', 'terra', 'mercurio', 'marte'], dtype='object')

» dfPlanetas.reindex(duplicados)   # default é axis = 0
↳         distancia   diametro
  mercurio    0.387      0.382
  venus       0.723      0.949
  terra       1.000      1.000
  mercurio    0.387      0.382
  marte         NaN        NaN  

» # índices de colunas repetidos
» duplicados = pd.Index(['distancia', 'diametro', 'diametro', 'distancia', 'massa'])
» dfPlanetas.reindex(duplicados, axis=1)   # sobre colunas
↳          distancia  diametro  diametro  distancia  massa
  mercurio     0.387     0.382     0.382      0.387    NaN
  venus        0.723     0.949     0.949      0.723    NaN
  terra        1.000     1.000     1.000      1.000    NaN

» # method='bfill' lê valor da coluna anterior
» dfPlanetas.reindex(duplicados, axis=1, method='bfill')
↳          distancia  diametro  diametro  distancia    massa
  mercurio     0.387     0.382     0.382      0.387    0.387
  venus        0.723     0.949     0.949      0.723    0.723
  terra        1.000     1.000     1.000      1.000    1.000

» # use method='ffill' para copiar coluna posterior

» # reindexação com loc
» nCol = pd.Index(['diametro', 'distancia'])
» dfPlanetas.loc[['venus','terra'], ['diametro', 'distancia']]
↳        diametro   distancia
  venus     0.949       0.723
  terra     1.000       1.000
» # nCol pode ser uma lista: nCol = ['diametro', 'distancia']

De posse dos índices das linhas e colunas qualquer uma delas pode ser apagada com df.drop(lista, axis). As operações retornam o dataframe modificado, sem alterar o original, a menos que seja marcado o parâmetro inplace=True. Nesse caso os dados removidos serão perdidos.

» dfPlanetas
↳          distancia   diametro
  mercurio     0.387      0.382
  venus        0.723      0.949
  terra        1.000      1.000

» # apagando linhas (axis = 0 é default)
» dfPlanetas.drop(['venus', 'mercurio'])
↳     distancia     diametro
  terra     1.0         1.0

» # apagando colunas
» dfPlanetas.drop(['distancia'], axis=1)
↳           diametro
  mercurio     0.382
  venus        0.949
  terra        1.000

Os seguintes argumentos são usados com reindex

Argumento descrição
index Index ou sequência a ser usada como index,
method forma de interpolação: ‘ffill’ preenche com valor posterior, ‘bfill’ com valor anterior,
fill_value valor a usar quando dados não existentes são introduzidos por reindexing (ao invés de NaN),
limit quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher (em número de elementos),
tolerance quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher para valores inexatos (em distância numérica),
level combina Index simples no caso de MultiIndex; caso contrário seleciona subset,
copy se True, copia dados mesmo que novo índice seja equivalente ao índice antigo; se False, não copia dados quando índices são equivalentes.

Métodos e propriedades de Index

Método descrição
append concatena outro objeto Index objects, gerando novo Index
difference calcula a diferença de conjunto como um Index
intersection calcula intersecção de conjunto
union calcula união de conjunto
isin retorna array booleano indicando se cada valor está na coleção passada
delete apaga índice, recalculando Index
drop apaga índices passados, recalculando Index
insert insere índice, recalculando Index
is_monotonic retorna True se indices crescem de modo monotônico
is_unique returns True se não existem valores duplicados no Index
unique retorna índices sem repetições
🔺Início do artigo

Bibliografia

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site:

Dataframes: Resumo


Atributos e métodos do pandas.dataFrame


Nas tabelas usamos df para referenciar um dataframe do pandas. Quando necessária a interação com um segundo dataframe ele é designado por dfOutro. A abreviação e/e significa “elemento a elemento”, utilizada quando a operação é aplicada entre todos os elementos de um e outro dataframe, usando elementos na posição (linha, coluna).

Atributos dos dataframes

Atributo descrição
df.at(m,n) valor correspondente à linha=m, coluna=n
df.axes lista representando os eixos do df
df.columns rótulos das colunas do df
df.dtypes dtypes no df
df.flags propriedades associadas ao df
df.iat valor para um par linha/coluna
df.iloc indexação baseada em localização puramente inteira para seleção por posição
df.loc grupo de linhas e colunas por rótulo(s) ou matriz booleana
df.ndim número (int) de eixos/dimensões da matriz
df.shape tupla com a dimensionalidade do df
df.size número (int) de elementos no objeto
df.style um objeto Styler
df.values representação Numpy do df

Métodos dos dataframes

Exibição e consulta ao dataframe

df.at(m,n) valor correspondente à linha=m, coluna=n
df.attrs dict de atributos globais do objeto (experimental)
df.head([n]) as primeiras n linhas
df.info([verbose, buf, max_cols, memory_usage,…]) resumo conciso do dataframe
df.tail([n]) últimas n linhas
df.items() iteração sobre (nome da coluna, Series)
df.iteritems() iteração sobre (nome da coluna, Series)
df.iterrows() iteração sobre linhas de df como pares (index, Series)
df.itertuples([index, nome]) iteração sobre linhas do df como pares nomeados
df.memory_usage([index, deep]) uso de memória de cada coluna em bytes
df.nlargest(n, columns[, keep]) n primeiras linhas ordenadas por colunas em ordem decrescente
df.nsmallest(n, colunas[, manter]) n primeiras n linhas ordenadas por colunas em ordem crescente
df.nunique([axis, dropna]) número de elementos distintos no eixo especificado

Operações matemáticas

Método descrição
df.abs() valor absoluto de cada elemento
df.add(dfOutro[, axis, level, fill_value]) adição de df e dfOutro
df.apply(func[, axis, raw, result_type, args]) aplica função ao longo de um eixo do df
df.applymap(func[, na_action]) aplica uma função a um df e/e
df.div(dfOutro[, axis, level, fill_value]) divisão flutuante de df por dfOutro
df.divide(dfOutro[, axis, level, fill_value]) divisão flutuante de df por dfOutro
df.dot(dfOutro) multiplicação de df por dfOutro
df.eval(expr[, inplace]) avalia a string ‘expr’ contendo operações sobre colunas do df
df.ewm([com, span, halflife, alpha,…]) função exponencial ponderada (EW)
df.floordiv(dfOutro[, axis, level, fill_value]) divisão inteira do df por dfOutro
df.mod(dfOutro[, axis, level, fill_value]) módulo de df por dfOutro e/e
df.mul(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro, e/e
df.multiply(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro, e/e
df.pow(dfOutro[, axis, level, fill_value]) exponencial do df por dfOutro e/e
df.prod([axis, skipna, level, numeric_only, ...]) produto dos valores sobre o eixo especificado
df.product([axis, skipna, level, numeric_only, ...]) produto dos valores sobre o eixo especificado
df.radd(dfOutro[, axis, level, fill_value]) adição de df e dfOutro e/e, com suporte a fill_na
df.rdiv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e
df.rfloordiv(dfOutro[, axis, level, fill_value]) divisão inteira do df e dfOutro e/e
df.rmod(dfOutro[, axis, level, fill_value]) módulo de df e dfOutro e/e
df.rmul(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro e/e
df.round([decimals]) arredonda elementos de df para um número dado de casas decimais
df.rpow(dfOutro[, axis, level, fill_value]) exponencial do df por dfOutro e/e
df.rsub(dfOutro[, axis, level, fill_value]) subtração de df e dfOutro e/e
df.rtruediv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e
df.sub(dfOutro[, axis, level, fill_value]) subtração de df por dfOutro e/e
df.sum([axis, skipna, level, numeric_only, ...]) soma dos valores sobre o eixo especificado
df.transform(func[, axis]) executa func em elementos de df, sobre eixo especificado
df.truediv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e

Formatação, Transposição, Ordenação

Método descrição
Transposição Tij = Mji
df.T a transposta de df
df.transpose(* args[, copiar]) transpor índices e colunas (obter a transposta)
df.droplevel(nível[, axis]) remove colunas com index ou níveis solicitados
df.melt([id_vars, value_vars, var_name,…]) Unpivot dataFrame para um formato largo
df.pivot([index, columns, values]) refaz df organizado por valores de index/column fornecidos
df.pivot_table([values, index, columns,…]) cria tabela dinâmica no estilo planilha como um df
df.sort_index([axis, level, ascendente, ...]) classifica objeto por rótulos (ao longo de um eixo)
df.sort_values ​​(by[, axis, ascending, inplace, ...]) ordena por valores ao longo do eixo especificado
df.stack([level, dropna]) empilha os níveis prescritos das colunas para o índice
df.swapaxes(axis1, axis2[, copy]) inverte eixos com seus valores respectivos
df.swaplevel([i, j, axis]) inverte níveis i e j em umMultiIndex
df.unstack([level, fill_value]) pivot um level dos rótulos de índice (necessariamente hierárquicos)
df.explode(column[, ignore_index]) transforma cada elemento de uma lista em uma linha, mantendo os índices
df.squeeze([axis]) comprime valores de eixo unidimensional para escalares

Métodos estatísticos

Método descrição
df.corr([method, min_periods]) calcula correlação de pares de colunas, excluindo valores NA/nulos
df.corrwith(dfOutro[, axis, drop, method]) calcula correlação de pares
df.count([axis, level, numeric_only]) quantos valores não NA para cada coluna ou linha
df.cov([min_periods, ddof]) calcula covariância de pares de colunas, excluindo NA/nulos
df.cummax([axis, skipna]) máximo cumulativo em um df
df.cummin([axis, skipna]) mínimo cumulativo sobre um eixo do df
df.cumprod([axis, skipna]) produto cumulativo sobre um eixo do df
df.cumsum([axis, skipna]) soma cumulativa sobre eixo do df
df.describe([percentis, include, exclude, ...]) gera descrição estatística
df.diff([points, axis]) primeira diferença discreta do elemento
df.hist([column, by, grid, xlabelsize, xrot,…]) histograma das colunas do df
df.info([verbose, buf, max_cols, memory_usage,…]) resumo conciso do dataframe
df.kurt([axis, skipna, level, numeric_only]) curtose imparcial sobre o eixo especificado
df.kurtosis([axis, skipna, level, numeric_only]) curtose imparcial sobre o eixo especificado
df.mad([axis, skipna, level]) desvio absoluto médio sobre especificado
df.max([axis, skipna, level, numeric_only]) máximo dos valores sobre o eixo especificado
df.mean([axis, skipna, level, numeric_only]) média dos valores sobre o eixo especificado
df.median([axis, skipna, level, numeric_only]) mediana dos valores sobre o eixo especificado
df.min([axis, skipna, level, numeric_only]) mínimo dos valores sobre o eixo especificado
df.mode([axis, numeric_only, dropna]) moda(s) dos elemento ao longo do eixo selecionado
df.pct_change([periods, method, limit, frequency]) alteração percentual entre o elemento atual e o anterior
df.quantil([q, axis, numeric_only, interpolateion]) valores no quantil dado sobre o eixo especificado
df.sample([n, frac, replace, weight,…]) amostra aleatória de itens de um eixo
df.sem([axis, skipna, level, ddof, numeric_only]) erro padrão imparcial da média sobre o eixo especificado
df.std([axis, skipna, level, ddof, numeric_only]) desvio padrão da amostra sobre o eixo especificado
df.var([axis, skipna, level, ddof, numeric_only]) variação imparcial sobre o eixo especificado

Gerenciamento, filtragem e consulta ao dataframe

Método descrição
df.add_prefix(prefixo) acrecenta prefixo nos rótulos (labels)
df.add_suffix(sufixo) acrecenta sufixo nos rótulos (labels)
df.agg([function, axis]) agregar usando function no eixo especificado
df.aggregate([função, axis]) agregar usando function no eixo especificado
df.align(dfOutro[, junção, axis, nível, cópia, ...]) alinha dois objetos em seus eixos com o método especificado
df.append(dfOutro[, ignore_index,…]) anexa linhas de dfOutro ao final de df
df.asof(where[, subset]) última(s) linha(s) sem NaN antes de where
df.assign(** kwargs) atribui novas colunas a um df
df.clip([lower, upper, axis, inplace]) corta (trim) os valores no(s) limite(s) dados
df.combine(dfOutro, func[, fill_value, ...]) combina colunas com dfOutro
df.combine_first(dfOutro) atualiza elementos nulos com elementos de dfOutro, na mesma posição
df.compare(dfOutro[, align_axis, keep_shape,…]) compara com dfOutro> e exibe diferenças
df.copy([deep]) faz cópia do objeto com índices e dados
df.drop([labels, axis, index, columns, level, ...]) remove linhas ou colunas com os rótulos especificados
df.drop_duplicates([subset, keep, inplace, ...]) remove linhas duplicadas
df.expanding([min_periods, center, axis, method]) aplica transformações de expansão
df.filter([itens, like, regex, axis]) filtra linhas ou colunas do df de acordo com os índices especificados
df.first_valid_index() índice do primeiro valor não NA; None se nenhum for encontrado
df.get(key[, default]) item do dataframe correspondente à chave fornecida
df.groupby([by, axis, level, as_index, ...]) agrupa df usando um mapper ou Series de colunas
df.infer_objects() tentativa de inferir dtypes para colunas do objeto
df.insert(loc, column, value[, allow_duplicates]) insere coluna no df no local especificado
df.join(dfOutro[, on, how, lsuffix, rsuffix, sort]) junta colunas de df e dfOutro
df.lookup(row_labels, col_labels) (descontinuado) “indexação extravagante” baseada em rótulos para df
df.mask(cond[, dfOutro, local, axis, level, ...]) substitui valores onde a condição é True
df.merge(right[, how, on, left_on, …]) mesclar df usando database de estilo
df.pipe(func, * args, ** kwargs) aplicação de func(self, * args, ** kwargs)
df.pop(item) remove e retorna o item removido
df.query(expr[, inplace]) consulta colunas de df com uma expressão booleana
df.rank([axis, method, numeric_only, ...]) classificações de dados numéricos (1 a n) ao longo do eixo
df.replace([to_replace, value, inplace, limit,…]) substitui valores em to_replace com value
df.rolling(window[, min_periods, center,…]) cálculos de “rolling window”
df.select_dtypes([include, exclude]) subset de colunas do df com base nos dtypes da coluna
df.skew([axis, skipna, level, numeric_only]) inclinação imparcial sobre o eixo especificado
df.slice_shift([perids, axis]) (descontinuado) Equivalente a shift sem copiar dados
df.sparse alias de pandas.core.arrays.sparse.accessor.SparseFrameAccessor
df.take(indices[, axis, is_copy]) elementos nos índices posicionais fornecidos ao longo de um eixo
df.truncate([before, after, axis, copy]) truncar df antes e depois de valores de índices
df.update(dfOutro[, juntar, substituir,…]) modifique “no local” usando valores não NA de dfOutro
df.value_counts([subset, normalize, …]) retorna série contendo número de linhas exclusivas no df
df.where(cond[, dfOutro, inplace, axis, level, ...]) substitui valores em que a condição é falsa
df.xs(key[, axis, level, drop_level]) seção transversal de df

Testes e comparações

Método descrição
df.empty booleano: se o df está vazio
df.all([axis, bool_only, skipna, nível]) booleano: se todos os elementos são verdadeiros, sobre um eixo (se especificado)
df.any([axis, bool_only, skipna, nível]) booleano: se algum elemento é verdadeiro, sobre um eixo (se especificado)
df.bool() booleano, se um único elemento do df é True/False
df.duplicated([subset, keep]) série booleana marcando linhas duplicadas
df.eq(dfOutro[, axis, level]) boolena: se elementos são iguais a um escalar ou dfOutro, e/e
df.equal(dfOutro) booleano: se dois objetos contêm os mesmos elementos
df.ge(dfOutro[, axis, level]) dataframe booleano, maior ou igual entre df e dfOutro, e/e
df.gt(dfOutro[, axis, level]) dataframe booleano, maior que, entre df e dfOutro, e/e
df.isin(values) booleano: se cada elemento no df está contido em values
df.le(dfOutro[, axis, level]) booleano: menor ou igual entre elementos de df e dfOutro, e/e
df.lt(dfOutro[, axis, level]) booleano: menor dos elementos de df e dfOutro, e/e
df.ne(dfOutro[, axis, level]) booleano, diferente entre df e dfOutro, e/e

Operações com rótulos (labels) e índices

df.index índices do df
df.keys() retorna o index
df.last_valid_index() índice do último valor não NA; None se nenhum valor NA for encontrado
df.reindex([labels, index, columns, axis, ...]) substitue índices de df, com lógica de preenchimento opcional
df.reindex_like(dfOutro[, method, copy, limit, ...]) retorna objeto com índices correspondentes à dfOutro
df.rename([mapper, index, columns, axis, copy, ...]) renomear rótulos dos eixos
df.rename_axis([mapper, index, columns, axis, ...]) define o nome do eixo para o índices ou colunas
df.reorder_levels(order[, axis]) reorganiza níveis de índices usando a ordem em order
df.reset_index([level, drop, inplace, ...]) redefine um índice ou seu nível
df.set_axis(rótulos[, axis, local]) atribui o índice desejado a determinado eixo
df.set_flags(*[, copy, allow_duplicated_labels]) novo objeto com sinalizadores (flags) atualizados
df.set_index(keys[, drop, append, inplace, ...]) define índices de df usando colunas existentes
df.idxmax([axis, skipna]) índice da primeira ocorrência do máximo sobre o eixo especificado
df.idxmin([axis, skipna]) índice da primeira ocorrência do mínimo sobre o eixo especificado

Plots

Método descrição
df.boxplot([column, by, ax, font-size, rot, ...]) traça gráfico de caixa usando as colunas do df
df.plot o mesmo que pandas.plotting._core.PlotAccessor
df.hist([column, by, grid, xlabelsize, xrot,…]) histograma das colunas do df

Serialização e conversões

Método descrição
df.astype(dtype[, copy, errors]) transforma objeto para um dtype especificado
df.convert_dtypes([infer_objects,…]) converte colunas para os melhores dtypes usando dtypes com suporte parapd.NA
df.from_dict(data[, orient, dtype, columns]) constroi df a partir de dict ou similar
df.from_records(data[, index, exclusion,…]) converte ndarray ou dados estruturados em dataframe
df.to_clipboard([excel, sep]) copia objeto para a área de transferência do sistema
df.to_csv([path_or_buf, sep, na_rep,…]) grava objeto em um arquivo de valores separados por vírgula (csv)
df.to_dict([orient, into]) converte df em um dicionário
df.to_excel(excel_writer[, sheet_name, na_rep,…]) grava objeto em uma planilha Excel
df.to_feather(path, **kwargs) grava df no formato binário Feather
df.to_gbq(destination_table[, project_id,…]) grava df em uma tabela Google BigQuery
df.to_hdf(path_or_buf, key[, mode, complevel,…]) grava objeto em um arquivo HDF5 usando HDFStore
df.to_html([buf, columns, col_space, header,…]) renderiza df como uma tabela HTML
df.to_json([path_or_buf, orient, date_format,…]) converte objeto em uma string JSON
df.to_latex([buf, colunas, col_space, cabeçalho,…]) renderiza objeto em uma tabela LaTeX
df.to_markdown([buf, mode, index, ...]) imprima df em formato Markdown
df.to_numpy([dtype, copy, na_value]) converte df em uma matriz NumPy
df.to_parquet([path, engine, ...]) grave df em formato binário parquet
df.to_period([freq, axis, copy]) converte df de DatetimeIndex para PeriodIndex
df.to_pickle(path[, compression, protocol, ...]) serializa o objeto para o arquivo pickle
df.to_records([index, column_dtypes, index_dtypes]) converte df em uma matriz de registro NumPy
df.to_sql(name, con[, schema, if_exists,…]) grava registros em df em um banco de dados SQL
df.to_stata(path[, convert_dates, write_index,…]) exporta df para o formato Stata dta
df.to_string([buf, columns, col_space, header, ...]) renderiza df em uma saída tabular compatível com o console
df.to_timestamp([freq, how, axis, copy]) converte para DatetimeIndex de timestamps, no início do período
df.to_xarray() converte para objeto xarray
df.to_xml([path_or_buffer, index, root_name,…]) renderiza df em um documento XML

Gerenciamento de valores ausentes

Método descrição
df.backfill([axis, inplace, limit, reduction]) o mesmo que df.fillna() com method = 'bfill'
df.bfill([axis, inplace, limit, downcast]) o mesmo que df.fillna() com method = 'bfill'
df.dropna([axis, how, treshold, subset, inplece]) remove os valores ausentes
df.ffill([axis, inplace, limit, reduction]) o mesmo que df.fillna() com method = 'ffill'
df.fillna([value, method, axis, local, ...]) preenche campos com NA/NaN usando o método especificado
df.interpolate([method, axis, limit, inplace, ...]) substitui valores NaN usando método de interpolação
df.isna() detecta valores ausentes
df.isnull() detecta valores ausentes
df.notna() valores existentes (não ausentes)
df.notnull() valores existentes (não ausentes)
df.pad([axis, inplace, limit, downcast]) o mesmo que df.fillna() com method = 'ffill'

Séries temporais e dados com hora/data

Método descrição
df.asfreq(freq[, método, como, normalizar, ...]) converte série temporal para a frequência especificada
df.at_time(hour[, asof, axis]) seleciona valores em um determinado horário do dia
df.between_time(start_time, end_time[,…]) seleciona valores entre horários especificados
df.first(offset) seleciona períodos iniciais em série temporal usando deslocamento (offset)
df.last(offset) selecione períodos finais da série temporal com deslocamento (offset)
df.resample(rule[, axis, closed, label, ...]) reamostrar os dados de série temporal
df.shift([periods, freq, axis, fill_value]) desloca índices por número desejado de períodos com uma frequância opcional
df.tshift([períodos, freq, axis]) (descontinuado) altera índice de tempo, usando a frequência do índice, se dispolevel
df.tz_convert(tz[, axis, level, copy]) converte o eixo com reconhecimento de tz em fuso horário de destino
df.tz_localize(tz[, axis, level, copy, ...]) localiza índice tz-naive de df para o fuso horário de destino

Ordenamento com dataframes.sort_values

Para ordenar um dataframe podemos usar o método sort, com a seguinte sintaxe:

dataframe.sort_values(by=['campo'], axis=0, ascending=True, inplace=False)
onde
by pode ser uma string ou lista com o nome ou nomes dos campos, na prioridade de ordenamento,
axis{0 ou ‘index’, 1 ou ‘columns’} default 0, indica o eixo a ordenar,
ascending=True/False se ordenamento é crescente/decrescente.

dataframe.reindex

Alterações da ordem dos índices de um dataframe podem ser obtidos com:
dataframe.reindex(listaDeCampos)
Os seguintes argumentos são usados com reindex

Argumento descrição
index Index ou sequência a ser usada como index,
method forma de interpolação: ‘ffill’ preenche com valor posterior, ‘bfill’ com valor anterior,
fill_value valor a usar quando dados não existentes são introduzidos por reindexing (ao invés de NaN),
limit quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher (em número de elementos),
tolerance quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher para valores inexatos (em distância numérica),
level combina Index simples no caso de MultiIndex; caso contrário seleciona subset,
copy se True, copia dados mesmo que novo índice seja equivalente ao índice antigo; se False, não copia dados quando índices são equivalentes.

Métodos e propriedades de Index

Método descrição
append concatena outro objeto Index objects, gerando novo Index
difference calcula a diferença de conjunto como um Index
intersection calcula intersecção de conjunto
union calcula união de conjunto
isin retorna array booleano indicando se cada valor está na coleção passada
delete apaga índice, recalculando Index
drop apaga índices passados, recalculando Index
insert insere índice, recalculando Index
is_monotonic retorna True se indices crescem de modo monotônico
is_unique returns True se não existem valores duplicados no Index
unique retorna índices sem repetições

Bibliografia

  • Pandas Pydata.org pandas docs, acessado em julho de 2021.