Pandas – Dataframes


Dataframes do pandas


Um dataframe é uma forma de armazenar dados em forma tabular, como em uma planilha. O dataframe do pandas consiste em uma coleção de Series que são dispostas como suas colunas. A cada linha está associado um índice que serve para ordenar e selecionar dados. Como Series, cada coluna tem um tipo definido. No entanto, não é necessário que todas as colunas tenham o mesmo tipo e portanto dados de tipos diferentes podem ser armazenados.

Muitas operações com dataframes levam em consideração o eixo ou axis. O default é axis = 0 (ou axis = 'index') o que indica operação sobre as linhas. axis = 1 (ou axis = 'column') indica operação realizada sobre as colunas.

O método mais comum de se criar um dataframe consiste em passar um dicionário e uma lista de índices para o construtor.

In [1]: import pandas as pd
In [2]: import numpy as np
In [3]: dados = {
                'nome': ['Pedro', 'Maria', 'Janaina', 'Wong', 'Roberto', 'Marco', 'Paula'],
                'cidade': ['São Paulo', 'São Paulo', 'Rio de Janeiro', 'Brasília',
                           'Salvador', 'Curitiba', 'Belo Horizonte'],
                'idade': [34, 23, 32, 43, 38, 31, 34],
                'nota': [83.0, 59.0, 86.0, 89.0, 98.0, 61.0, 44.0]
                }

In [4]: ids = [10, 11, 12, 13, 14, 15, 16]
In [5]: dfAlunos = pd.DataFrame(data=dados, index=ids)
Out[5]:

nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0

No caso acima usamos um dict onde as chaves são os nomes dos campos ou colunas. À cada chave está associada uma lista cujos valores se tornam os valores das linhas, em cada coluna. A lista de índices foi fornecida separadamente. Se a lista ids não tivesse sido fornecida os índices do dataframe seriam inteiros, começando em 0.

Dataframes possuem a propriedade shape que contém as dimensões do objeto e os métodos head(n) e tail(n) que permitem, respectivamente, a visualização das n primeiras ou últimas linhas. Ao carregar um dataframe é sempre útil visualizar suas primeiras linhas e nomes de colunas. Também pode ser útil visualizar a matriz sob forma transposta, dada por dfAlunos.T.

In [6]: dfAlunos.shape
Out[6]: (7, 4)
# o que significa que temos 7 linhas, com 4 campos ou colunas.

# para visualizar apenas as 2 primeiras linhas
In [7]: dfAlunos.head(2)
Out[7]:
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
# para visualizar apenas as 2 últimas linhas
In [8]: dfAlunos.tail(2)
Out[8]:
nome cidade idade nota
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
# A transposta:
In [9]: dfAlunos.T
Out[9]:
10 11 12 13 14 15 16
nome Pedro Maria Janaina Wong Roberto Marco Paula
cidade São Paulo São Paulo Rio de Janeiro Brasília Salvador Curitiba Belo Horizonte
idade 34 23 32 43 38 31 34
nota 83 59 86 89 98 61 44

Os nomes das colunas podem ser obtidos em uma lista, em um nome específico. Devemos nos lembrar que cada coluna do dataframe é uma Series. Portanto valem para elas os métodos e propriedades das Series.

In[10]: dfAlunos.columns
Out[10]:
Index(['nome', 'cidade', 'idade', 'nota'], dtype='object')

# O nome da segunda coluna (lembrando que se conta a partir de 0)
In [10]: dfAlunos.columns[1]
Out[10]: 'cidade'

# Selecionando a coluna 'cidade'
In [11]: dfAlunos['cidade']
Out[11]:
10         São Paulo
11         São Paulo
12    Rio de Janeiro
13          Brasília
14          Salvador
15          Curitiba
16    Belo Horizonte
Name: cidade, dtype: object

# cada coluna é uma Series
In [12]: type(dfAlunos['cidade'])
Out[12]: pandas.core.series.Series

# os métodos das Series se aplicam
In [13]: dfAlunos['cidade'].value_counts()
Out[13]:
São Paulo         2
Curitiba          1
Rio de Janeiro    1
Belo Horizonte    1
Salvador          1
Brasília          1
Name: cidade, dtype: int64

# valores únicos podem ser obtidos com unique()
In [14]: dfAlunos['cidade'].unique()
Out[14]:
array(['São Paulo', 'Rio de Janeiro', 'Brasília', 'Salvador', 'Curitiba',
       'Belo Horizonte'], dtype=object)

# também podemos transformar esses valores em um set
In [15]: set(dfAlunos['cidade'])
Out[15]:
{'Belo Horizonte',
 'Brasília',
 'Curitiba',
 'Rio de Janeiro',
 'Salvador',
 'São Paulo'}

Observe que dfAlunos['cidade'] retorna uma Series, que é a coluna especificada do DF. Já o comando dfAlunos[['cidade']] retorna um dataframe com uma única coluna. É sempre importante saber com que tipo de objeto estamos lidando. Para isso podemos usar type() para conhecer esse tipo. Por exemplo, type(dfAlunos[['cidade']]) retorna pandas.core.frame.DataFrame . Observe que strings são listadas apenas como objects (sem discriminação de serem strings).

Também se pode usar a notação de ponto, dfAlunos.cidade, para obter a mesma coluna.

Como dissemos, o objeto DataFrame do pandas é formado por colunas que são Series, cada uma delas contendo elementos do mesmo tipo. As linhas podem, portanto, conter elementos de tipos diferentes. Para ver os tipos de cada coluna podemos examinar a propriedade dtype ou o método .info() que fornece uma visão geral sobre os dados, inclusive sobre a existência de valores nulos nos dados.

In [16]: dfAlunos.dtypes
Out[16]:
nome       object
cidade     object
idade       int64
nota      float64
dtype: object

# Uma visão geral sobre os dados pode ser obtido com .info()
In [17]: dfAlunos.info()
Out[17]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 7 entries, 10 to 16
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   nome    7 non-null      object
 1   cidade  7 non-null      object
 2   idade   7 non-null      int64
 3   nota    7 non-null      float64
dtypes: float64(1), int64(1), object(2)
memory usage: 600.0+ bytes

A descrição estatística dos campos numéricos é obtida com .describe() que fornece a contagem de itens, o valor médio, o desvio padrão, os quantis e máximos e mínimos. O método .corr() fornece o Coeficiente de Correlação de Pearson para todas as colunas numéricas da tabela. O resultado é um número no intervalo [-1, 1] que descreve a relação linear entre as variáveis.

# describe: resumo estatístico dos campos numéricos
In [18]: dfAlunos.describe()
Out[18]:
idade nota
count 7.000000 7.000000
mean 33.571429 74.285714
std 6.187545 19.661420
min 23.000000 44.000000
25% 31.500000 60.000000
50% 34.000000 83.000000
75% 36.000000 87.500000
max 43.000000 98.000000
In [19]: dfAlunos.corr()
Out[19]:
idade nota
idade 1.000000 0.564238
nota 0.564238 1.000000

Para acrescentar uma ou mais linhas (registros) ao dataframe podemos criar um novo dataframe com quantas linhas forem necessárias e concatená-lo com o antigo usando o método .concat().

# criamos dataframe para a aluna Juliana e seus dados
In [20]: dfInserir = pd.DataFrame([('Juliana','Curitiba',28,80.0)],
                             columns=['nome','cidade','idade','nota'],
                             index=[100])
In [21]: pd.concat([dfAlunos, dfInserir])
Out[21]:
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
100 Juliana Curitiba 28 80.0

Observe que o dataframe original não foi modificado. Caso se pretenda que modificação se torne permanente você deve atribuir o resultado retornado a uma novo (ou o mesmo) dataframe, como em dfAlunos = pd.concat([dfAlunos, dfInserir]).

Muitas vezes queremos que a novo dataframe criado ignore os índice das duas tabelas concatenadas. Nesse caso podemos ignorar os índices antigos e substituí-los por novos índices fornecidos, ou deixar que sejam preenchidos automaticamente.

In [22]: df = pd.concat([dfAlunos, dfInserir], ignore_index=True)
In [23]: df.index
Out[23]:
RangeIndex(start=0, stop=8, step=1)
# os índices são inteiros de 0 até 8 (exclusive)

Uma nova coluna pode ser inserida, inclusive usando valores obtidos nas linhas. Na operação abaixo inserimos o campo ‘calculado’ que é igual à multiplicação dos campos ‘nota’ * ‘idade’, que não tem significado e é feito apenas como demonstração.

In [24]: dfAlunos['calculado']=dfAlunos['nota'] * dfAlunos['idade']
In [25]: dfAlunos
Out[24]:
nome cidade idade nota calculado
10 Pedro São Paulo 34 83.0 2822.0
11 Maria São Paulo 23 59.0 1357.0
12 Janaina Rio de Janeiro 32 86.0 2752.0
13 Wong Brasília 43 89.0 3827.0
14 Roberto Salvador 38 98.0 3724.0
15 Marco Curitiba 31 61.0 1891.0
16 Paula Belo Horizonte 34 44.0 1496.0

Como essa nova coluna não tem nenhum significado vamos apagá-la usando .drop().

# a operação seguinte retorna o dataframe sem a coluna 'calculado', mas não altera a original
In [26]: dfAlunos.drop(['calculado'], axis=1)
# para alterar o dataframe usamos o parâmetro inplace=True
In [27]: dfAlunos.drop(['calculado'], axis=1, inplace=True)

Agora o dataframe tem a mesma estrutura de colunas original. Muitas operações do pandas retornam o resultado sobre o objeto sem alterá-lo. Algumas delas admitem o parâmetro inplace que, se True, faz a alteração do objeto in loco.

Para selecionar mais de uma coluna passamos uma lista com os nomes dos campos entre os colchetes.

In [28]: lista = ['nome','idade']
# a linha abaixo é idêntica à dfAlunos[['nome','idade']]
In [29]: dfAlunos[lista]
Out[29]:
nome idade
10 Pedro 34
11 Maria 23
12 Janaina 32
13 Wong 43
14 Roberto 38
15 Marco 31
16 Paula 34


Podemos obter somas dos termos, tanto no sentido das linhas quanto das colunas, o que servirá como exemplo do uso do parâmetro axis. Relembrando:

axis = 0 (axis = ‘index’) opera sobre todas as linhas de cada coluna
axis = 1 (axis = ‘column’) opera sobre todas as colunas de cada linha

Para mostrar isso vamos construir um dataframe contendo apenas os dados numéricos, com os campos ‘idade’ e ‘nota’. Em seguida aplicamos sum(axis=0) para obter a soma das idades e notas, e sum(axis=1) para a soma
de cada linha.

In [30]: dfNumerico=dfAlunos[['idade', 'nota']]
In [31]: dfNumerico.sum(axis=0)
Out[31]:
idade         235.0
nota          520.0
dtype: float64

In [32]: dfNumerico.sum(axis=1)
Out[32]:
10    117.0
11     82.0
12    118.0
13    132.0
14    136.0
15     92.0
16     78.0
dtype: float64

Importando um arquivo externo

É comum que os dados estejam inicialmente em forma de texto com os dados gravados em linhas e com valores separados por vírgula (um arquivo csv, comma separated values) ou outros separadores, tais como tabulação ou ponto e vírgula (;). Também não é raro que dados importados de outras fontes possam ser convertidos nesse formato.

Suponha que tenhamos no disco, na pasta de trabalho de sua sessão, um arquivo com o seguinte conteúdo:

    id, nome, cidade, idade, nota
    10, Pedro, São Paulo, 34, 83.0
    11, Maria, São Paulo, 23, 59.0
    12, Janaina, Rio de Janeiro, 32, 86.0
    13, Wong, Brasília, 43, 89.0
    14, Roberto, Salvador, 38, 98.0
    15, Marco, Curitiba, 31, 61.0
    16, Paula, Belo Horizonte, 34, 44.0

Não é importante que as colunas estejam organizadas em forma de colunas. Para importar esses dados para dentro de um dataframe usamos o método do pandas .read_csv(arq), onde arq é o nome completo do arquivo a ser lido (inclusive com seu caminho).

In [30]: dfNovoAlunos = pd.read_csv('./alunos.csv')
In [32]: dfNovoAlunos
Out[32]:
id nome cidade idade nota
0 10 Pedro São Paulo 34 83.0
1 11 Maria São Paulo 23 59.0
2 12 Janaina Rio de Janeiro 32 86.0
3 13 Wong Brasília 43 89.0
4 14 Roberto Salvador 38 98.0
5 15 Marco Curitiba 31 61.0
6 16 Paula Belo Horizonte 34 44.0

Vemos que o campo ‘id’ foi lido como um campo comum. Ele pode ser transformado por meio da seguinte operação que transforma esse campo em índice efetivo:

# torne o campo id o índice
In [33]: dfNovoAlunos.set_index('id', inplace=True)
In [34]: dfNovoAlunos.head(2)
Out[34]: 
nome cidade idade nota
id
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0

Alternativamente podemos ler o arquivo csv usando diretamente a primeira coluna como índice, informado pelo parâmetro index_col. Se o arquivo não contiver vírgulas separando os campos e sim outro sinal qualquer, como ; ou tabulações, passamos essa informação usando o parâmetro sep. Na última importação usamos url, a URL completa do arquivo, que pode estar em qualquer ponto disponivel da rede.

# para usar a 1a coluna como índice
dfNovoAlunos = pd.read_csv('./alunos.csv', index_col=0)
# para ler arquivo em url, usando tab como separador
dfOutroDF = pd.read_csv(url, sep='\t')

Vimos que, se nenhum argumento for passado, a primeira linha do arquivo é tomada como contendo os nomes (ou headers) das colunas. Para evitar isso passamos o parâmetro header = None. Nesse caso o nome das colunas é substituído por números.

Suponha que o arquivo nums.csv, com o conteúdo abaixo, esteja gravado no disco.

    11,12,13,14
    21,22,23,24
    31,32,33,34

Ele pode ser lido da seguinte forma:

# informa que 1a linha não é header
In [35]: dfNone = pd.read_csv('./dados/nums.csv', header=None)

# insere o nome ou labels para as colunas
In [36]: dfNames = pd.read_csv('./dados/nums.csv', names=('A', 'B', 'C', 'D'))

# exibe os dois dataframes
In [37]: display('sem headers:', dfNone, 'com headers:', dfNames)
Out[37]:

‘sem headers:’

0 1 2 3
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

‘com headers:’

A B C D
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

Finalmente, se o cabeçalho contendo os títulos das colunas não está na primeira linha podemos passar o parâmetro header=n. A n-ésima linha será tomada como cabeçalho e todas as linhas anteriores serão ignoradas.

In [38]: dfPula2 = pd.read_csv('./dados/nums.csv', header=2)

Gravando o dataframe em arquivos pickle

Após várias manipulações, que podem ser demoradas dependendo do tamanho dos dataframes e complexidade das operações, temos um novo dataframe que, para ser recuperado em uma sessão posterior, deve passar por todas as etapas realizadas. Para evitar isso e garantir o armazenamento desses dados podemos gravá-lo em um pickle

pd.to_pickle(dfNovoAlunos, './dados/Alunos.pkl')
In [39]: del dfNovoAlunos
In [40]: dfLido = pd.read_pickle('./dados/Alunos.pkl')

dfLido será um dataframe idêntico ao dfNovoAlunos gravado em etapa anterior. A pasta de destino deve existir ou uma exceção será lançada.

to_pickle Grava um objeto do pandas em arquivo pickled
read_pickle Ler arquivo pickle recuperando objeto
DataFrame.to_hdf Grava um objeto do pandas em arquivo HDF5
read_hdf Ler arquivo hdf recuperando objeto
DataFrame.to_sql Grava dataframe em um banco de dados sql
read_sql Ler arquivo sql recuperando objeto
DataFrame.to_parquet Grava dataframe em formato parquet binário.
read_parquet Ler arquivo parquet recuperando objeto
🔺Início do artigo

Bibliografia

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

Nesse site:

Introdução ao Pandas – Series

O que é pandas?

Pandas é uma biblioteca do Python, de código aberto e com licença BSD, desenvolvida e mantida pelo PуDаtа Dеvеlорmеnt Tеаm. Ela fornece ferramentas de manipulação e análise estatística de dados, com capacidade de exibição gráfica, com extração de dados análogos (mas não idênticos) aos de consultas sql. [veja nesse site, sql.]

A biblioteca foi construída com Cython e, por isso, é bastante rápida. Ela se destinava inicialmente ao uso no setor financeiro para análise de séries temporiais, tendo se tornado uma ferramenta de uso comum na manipulação de dados, particularmente em data science e machine learning. Ela tem sido usada para substituir as planilhas do Excel, para processar dados sob forma tabular, importando com facilidade dados de arquivos csv ou json.

Os experimentos abaixo foram realizados no Jupyter Notebook. Você encontra nesse site um artigo sobre instalação e uso do Jupyter Notebook. As linhas de código e suas respostas, quando existirem, serão representadas da seguinte forma:

# Linhas de comentários (não serão numeradas)   
In [n]: Linha de input n
Out[n]: Output da linha n

NumPy e matplotlib

NumPy é a abreviação de Numerical Python, a biblioteca base da computação numérica em Python. Ela fornece as estruturas de dados e algoritmos necessários para a maioria das aplicações científicas e de engenharia utilizando cálculo numérico. Entre outros objetos NumPy NumPy fornece

  • o objeto multidimensional ndarray onde se pode aplicar operações vetorializadas rápidas e eficientes,
  • um conjunto de funções para cálculos elementares com vetores e matrizes,
  • ferramentas de leitura e gravação de dados,
  • operações da álgebra linear, transformada de Fourier e geração de números aleatórios,
  • interação com C e C++.

Para dados numéricos as matrizes do NumPy são armazenadas e manipuladas de modo mais eficiente do que as demais estruturas do Python. Além disso códigos escritos em linguagens de baixo nível, como C ou Fortran, podem operar diretamente nos dados armazenados com o NumPy. Por isso muitas ferramentas de computação numérica do Python usam as matrizes NumPy como um estrutura de dados primária.

matplotlib é a biblioteca Python mais popular usada para a produção de gráficos e visualização de dados. Ela pode ser usada na geração de gráficos estáticos ou animados e visualização interativa.

Pandas

O pandas se utiliza basicamente de 3 objetos de armazenamento de dados com as seguintes estuturas:

Estrutura de dados dos objetos do pandas
Nome dimensões tabela
Series 1D coluna (vetor)
DataFrame 2D tabela (matriz)
Panel 3D várias tabelas (matriz multidimensional)

As series e os dataframes são utilizados com maior frequência.

Para usar as bibliotecas dentro do código começamos com as importações

In [1]: import pandas as pd
In [2]: import numpy as np

Series

Uma series é um objeto unidimensional tipo um vetor que contém uma sequência de objetos do mesmo tipo. A essa sequência está associado um outro vetor de labels chamado de índice (index). O método básico de criação de séries é da seguinte forma:

s = pd.Series(data, index=index)

onde data pode ser um dict (um dicionário do Python), uma lista ou ndarray do numPy ou um escalar. index é uma lista de índices que, se omitida, é preenchida com inteiros iniciando em 0.

In [3]: serie1 = pd.Series([-1, 9, 0, 2, 5])
Out[3]:
 0   -1
 1    9
 2    0
 3    2
 4    5

À esquerda estão listados os índices que, por default, são uma lista de inteiros começando por 0. Os valores podem ser listados com .values e os índices com .index.

In [4]: serie1.values
Out[4]: array([-1,  9,  0,  2,  5])

In [5]: serie1.index
Out[5]: RangeIndex(start=0, stop=5, step=1)

Os índices podem ser inseridos manualmente e não precisam ser inteiros. No input [8] um valor da serie é recuperado por seu índice, que é um string.

In [6]: serie2 = pd.Series([4, 7, -5, 3], index=['a', 'b', 'c', 'd'])
In [7]: serie2
Out[7]: 
a    4
b    7
c   -5
d    3
dtype: int64

In [8]: serie2['c']
Out[8]: -5

Uma série pode ser filtrada por meio de um argumento passado como outra serie booleana. Além disso operações podem ser realizadas sobre todos os elementos da série.

# O teste seguinte gera uma série de booleanos
In [9]: serie2 > 3
Out[9]: 
a     True
b     True
c    False
d    False
dtype: bool

# Essa serie de True e False filtra a serie original
In [10]: serie2[serie2 > 3]
Out[10]:
a    4
b    7
dtype: int64

# Operações podem, ser realizadas sobre todos os elementos
In [11]: serie2 * 3
Out[11]: a    12
b    21
c   -15
d     9
dtype: int64

# Numpy tem a função exponencial
In [12]: np.exp(serie2)
Out[12]:
a      54.598150
b    1096.633158
c       0.006738
d      20.085537
dtype: float64

Uma série se comporta, em muitos sentidos, como um dicionário. Uma série pode ser criada passando-se um dicionário como argumento para pd.Series().

In [13]: populacao = {
           'Sudeste': 89012240, 
           'Nordeste': 57374243,
           'Sul': 30192315,
           'Norte': 18672591,
           'Centro-Oeste':16504303
         }

In [14]: serie3 = pd.Series(populacao)

In [15]: serie3
Out[15]:
Sudeste         89012240
Nordeste        57374243
Sul             30192315
Norte           18672591
Centro-Oeste    16504303
dtype: int64 

# A ordem dos itens na série pode ser alterada fornecendo uma lista para o argumento index:
In [16]: ordem_alfabetica = ['Brasil', 'Nordeste', 'Norte', 'Centro-Oeste', 'Sudeste', 'Sul']
In [17]: serie4 = pd.Series(populacao, index=ordem_alfabetica)
In [18]: serie4
Out[18]:
Brasil               NaN
Nordeste        57374243
Norte           18672591
Centro-Oeste    16504303
Sudeste         89012240
Sul             30192315
dtype: int64

# para verificar queis valores são nulos (NaN)
In [19]: pd.isnull(serie4)
Out[19]:
Brasil           True
Nordeste        False
Norte           False
Centro-Oeste    False
Sudeste         False
Sul             False
dtype: bool

# os seguintes registros são NaN
In [20]: serie4[pd.isnull(serie4)]
Out[20]:
Brasil   NaN
dtype: float64

In [21]: serie4[pd.notnull(serie4)]
Out[20]:
Nordeste        57374243.0
Norte           18672591.0
Centro-Oeste    16504303.0
Sudeste         89012240.0
Sul             30192315.0
dtype: float64

In [21]: 'Brasil' in serie4
Out[21]: True
In [22]'USA' in serie4
Out[22] False

In [23]: serie4['USA']
Out[23]: KeyError

Como não existe no dicionário um valor para o índice ‘Brasil’ a série atribuiu o valor NaN (Not a Number para essa chave, a forma de NumPy e pandas parea indicar a ausência de um valor. O método retorna True ou False para cada item da série e pd.notnull() o seu inverso booleano. Alternativamente se pode usar o método das séries serie4.isnull().

Em diversos aspectos uma serie se parece com um dict. Por ex., podemos testar a existência de uma chave usando o comando in, como em In[21]. A tentativa de recuperar um valor com índice não existente, em In [23], gera uma exceção (um erro do Python).

Observe que uma series tem propriedades de numpy ndarray, mas é um objeto de tipo diferente. Se um ndarray é necessário use .to_numpy().

In [24]: type(serie4)
Out[24]: pandas.core.series.Series

In [25]: type(serie4.to_numpy())
Out[25]: numpy.ndarray

Series podem ser fatiadas com a notação serie[i:f] onde serão retornadas a i-ésima linha até a f-ésima, exclusive. Se i for omitido a lista se inicia em 0, se f for omitido ela termina no final.

In [26]: serie4
Out[26]:
Brasil                 NaN
Nordeste        57374243.0
Norte           18672591.0
Centro-Oeste    16504303.0
Sudeste         89012240.0
Sul             30192315.0
dtype: float64

In [27]: serie4[2:5]
Out[27]:
Norte           18672591.0
Centro-Oeste    16504303.0
Sudeste         89012240.0
dtype: float64

In [28]: serie4[:2]
Out[28]: 
Brasil             NaN
Nordeste    57374243.0
dtype: float64

In [29]: serie4[4:]
Out[29]: 
Sudeste    89012240.0
Sul        30192315.0
dtype: float64

O resultado de operações envolvendo mais de uma serie alinha os valores por chaves (como uma UNION). Valores não presentes em um dos operandos terá NaN como resultado.

In [29]: serie5 = pd.Series([2, -1, -2, 1], index=['a', 'b', 'c', 'd'])
In [29]: serie6 = pd.Series([3, 4, 7, -1], index=['e', 'c', 'b', 'f'])
Out[29]: display(serie5, serie6)
a    2
b   -1
c   -2
d    1
dtype: int64

e    3
c    4
b    7
f   -1
dtype: int64

In [29]: serie5 + serie6
Out[29]:
a    NaN
b    6.0
c    2.0
d    NaN
e    NaN
f    NaN
dtype: float64

Series possuem a propriedade name que pode ser atribuída na construção ou posteriormente com .rename().

In [30]: serie7 = pd.Series(np.random.randn(5), name='randomica')
In [31]: serie7
Out[31]:
0   -1.703662
1    1.406167
2    0.966557
3   -0.557846
4   -0.264914
Name: randomica, dtype: float64

In [32]: serie7.name
Out[32]:
'randomica'

In [33]: serie7= serie7.rename('aleatoria')
In [34]: serie7.name
Out[34]:
'aleatoria'

O nome de uma série se torna seu índice ou nome de coluna caso ela seja usada para formar um DataFrame.

Atributos e Métodos das Series

Os atributos e métodos mais comuns e úteis das Series estão listados abaixo. Para uma lista completa consulte pandas.Series: API Reference.

Atributos

Atributo Descrição
at Accesso a um valor específico em uma par linha/coluna
attrs Retorna ditionario de atributos globais da series
axes Retorna lista de labels do eixo das linhas
dtype Retorna o tipo (dtype) dos objetos armazenados
flags Lista as propriedades do objeto
hasnans Informa se existem NaNs
iat Acessa valor usando um inteiro como índice
iloc Seleciona por posição usando índice inteiro
index Retorna lita de índices
is_monotonic Booleano: True se valores crescem de forma monotônica
is_monotonic_decreasing Booleano: True se valores decrescem de forma monotônica
is_unique Booleano: True se valores na series são únicos
loc Acessa linhas e colunas por labels oo array booleano
name O nome da Series
nbytes Número de bytes nos dados armazenados
shape Retorna uma tuple com forma (dimensões) dos dados
size Número de elementos nos dados
values Retorna Series como ndarray

Métodos

Método Descrição
abs() Retorna a Series com valor absoluto para cada elemento
add(serie2) Soma a serie com serie2, elemento a elemento
add_prefix(‘prefixo’) Adiciona prefixo aos labels com string ‘prefixo’
add_suffix(‘sufixo’) Adiciona sufixo aos labels com string ‘sufixo’
agg([func, axis]) Agrega usando func sobre o eixo especificado
align(serie2) Alinha 2 objetos em seus eixos usando método especificado
all([axis, bool_only, skipna, level]) Retorna se todos os elementos são True
any([axis, bool_only, skipna, level]) Retorna se algum elemento é True
append(to_append[, ignore_index, …]) Concatena 2 ou mais Series
apply(func[, convert_dtype, args]) Aplica func sobre todos os valores da series
argmax([axis, skipna]) Retorna posição (índice inteiro) do valor mais alto da Series.
argmin([axis, skipna]) Retorna posição (índice inteiro) do menor valor da Series.
argsort([axis, kind, order]) Retorna índices inteiros que ordenam valores da Series
asfreq(freq) Converte TimeSeries para frequência especificada.
asof(where[, subset]) Retorna última linha anterores à ocorrência de NaNs antes de ‘where’
astype(dtype[, copy, errors]) Transforma objeto object para um dtype
at_time(time[, asof, axis]) Seleciona valores em determinada hora (ex., 9:30AM)
backfill([axis, inplace, limit, downcast]) Alias para DataFrame.fillna() usando method=’bfill’
between(min, max) Retorna Series booleana satisfazendo min <= series <= max.
between_time(inicio, fim) Seleciona valores com tempo entre inicio e fim
bfill([axis, inplace, limit, downcast]) Alias para DataFrame.fillna() usando method=’bfill’
clip([min, max, axis, inplace]) Inclui apenas valores no intervalo
combine(serie2, func[, fill_value]) Combina a Series com serie2 ou escalar, usando func
compare(serie2[, align_axis, keep_shape, …]) Compara com serie2 exibindo differenças
copy([deep]) Faça uma cópia desse objeto, índices e valores
corr(serie2) Calcula a correlação com serie2, excluindo NaNs
count([level]) Retorna número de observações na Series, excluindo NaN/nulls
cov(serie2[, min_periods, ddof]) Calcula covariância da Series, excluindo NaN/nulls
cummax([axis, skipna]) Retorna máximo cumulativo
cummin([axis, skipna]) Retorna mínimo cumulativo
cumprod([axis, skipna]) Retorna o produto cumulativo
cumsum([axis, skipna]) Retorna a soma cumulativa
describe([percentiles, include, exclude, …]) Gera descrição estatística
div(serie2) Retorna divisão de ponto flutuante da series por serie2, elemento a elemento
divmod(serie2) Retorna divisão inteira e módulo da séries por serie2, elemento a elemento
dot(serie2) Calcula o produto interno entre a Series e serie2
drop([labels]) Retorna Series com labels removidos
drop_duplicates([keep, inplace]) Retorna Series após remoção de valores duplicados
dropna() Retorna Series após remoçaõ de valores faltantes
duplicated([keep]) Mostra os valores duplicados na Series
eq(serie2) Retorna Series de boolenos comparando elemento a elemento com serie2
equals(serie2) Boolenao: True se os dois objetos contém os mesmos elementos
ewm([com, span, halflife, alpha, …]) Calcula exponencial com peso
explode([ignore_index]) Transforma cada elemento de um objeto tipo lista em uma linha
fillna([value, method, axis, inplace, …]) Substitui valores NA/NaN usando método especificado
first(offset) Seleciona período inicial de uma série temporal usando offset.
first_valid_index() Retorna o índice do primeiro valor não NA/null
floordiv(serie2) Retorna divisão inteira da series por serie2, elemento a elemento
ge(serie2) Retorna serie booleana comparando se series é maior ou igual a serie2, elemento a elemento
get(key) Retorna item correspondente à key
groupby([by, axis, level, as_index, sort, …]) Agrupa a Series
gt(serie2[, level, fill_value, axis]) Retorna serie booleana comparando se series é maior que serie2, elemento a elemento
head([n]) Retorna os n primeiros valores
hist() Plota histograma da series usando matplotlib.
idxmax([axis, skipna]) Retorna o label do item de maior valor
idxmin([axis, skipna]) Retorna o label do item de menor valor
interpolate([method, axis, limit, inplace, …]) Preenche valores NaN usando metodo de interpolação
isin(valores) Booleano, se elementos da Series estão contidos em valores
isna() Booleano, se existem valores ausentes
isnull() Booleano, se existem valores nulos
item() Retorna primeiro elemento dos dados como escalar do Python
items() Iteração (lazy) sobre a tupla (index, value)
iteritems() Iteração (lazy) sobre a tupla (index, value)
keys() Alias de index
kurt([axis, skipna, level, numeric_only]) Retorna kurtosis imparcial
kurtosis([axis, skipna, level, numeric_only]) Retorna kurtosis imparcial
last(offset) Seleciona período final de uma série temporal usando offset
last_valid_index() Retorna o índice do último valor não NA/null
le(serie2) Retorna serie booleana comparando se series é menor ou igual a serie2, elemento a elemento
lt(serie2[, level, fill_value, axis]) Retorna serie booleana comparando se series é menor que serie2, elemento a elemento
mad([axis, skipna, level]) Retorna o desvio médio absoluto dos valores da series
mask(cond[, serie2, inplace, axis, level, …]) Substitui valores sob condição dada
max([axis, skipna, level, numeric_only]) Retorna o valor máximo
mean([axis, skipna, level, numeric_only]) Retorna a média dos valores
median([axis, skipna, level, numeric_only]) Retorna a mediana dos valores
memory_usage([index, deep]) Retorna a memória usada pela Series
min([axis, skipna, level, numeric_only]) Retorna o menor dos valores da series
mod(serie2[, level, fill_value, axis]) Retorna Modulo de series por serie2, elemento a elemento
mode([dropna]) Retorna a moda da Series
mul(serie2[, level, fill_value, axis]) Retorna a multiplicação de series por serie2, elemento a elemento
multiply(serie2[, level, fill_value, axis]) Retorna a multiplicação de series por serie2, elemento a elemento
ne(serie2[, level, fill_value, axis]) Retorna comparação booleana de series com serie2, elemento a elemento
nlargest([n, keep]) Retorna os n maiores elementos
notna() Booleano, se existem valores não faltantes ou nulos
notnull() Idem
nsmallest([n, keep]) Retorna os n menores elementos
nunique([dropna]) Retorna quantos elementos únicos existem na series
pad([axis, inplace, limit, downcast]) O mesmo que DataFrame.fillna() usando method=’ffill’
plot O mesmo que pandas.plotting._core.PlotAccessor
pop(item) Elimina valor da série com indice=item e retorna valor eliminado
pow(serie2) Retorna exponential de series por serie2, elemento a elemento
prod([axis, skipna, level, numeric_only, …]) Retorna o produto dos elemetos da series
product([axis, skipna, level, numeric_only, …]) Idem
quantile([q, interpolation]) Retorna valor na quantil dado
ravel([order]) Retorna os dados como um ndarray
rdiv(serie2[, level, fill_value, axis]) Executa a divisão de ponto flutuante de series por serie2, elemento a elemento
rdivmod(serie2) Retorna divisão inteira e módulo de series por serie2, elemento a elemento
reindex([index]) Ajusta a Series ao novo índice
reindex_like(serie2[, method, copy, limit, …]) Retorna um objeto com índices em acordo com serie2
rename([index, axis, copy, inplace, level, …]) Altera o nome ou labels dos índices
reorder_levels(order) Reajusta níveis de índices usando order
repeat(repeats[, axis]) Repete elementos da Series
replace([to_replace, value, inplace, limit, …]) Substitui valores em to_replace por value
reset_index([level, drop, name, inplace]) Gera nova Series reinicializando os índices
rfloordiv(serie2[, level, fill_value, axis]) Retorna divisão inteira de series por serie2, elemento a elemento
rmod(serie2[, level, fill_value, axis]) Retorna Modulo da divisão da series por serie2, elemento a elemento
rmul(serie2[, level, fill_value, axis]) Retorna multiplicação da series por serie2, elemento a elemento
round([n]) Arredonda valores da Series para n casas decimais.
rpow(serie2[, level, fill_value, axis]) Retorna a exponential de series por serie2, elemento a elemento
rsub(serie2[, level, fill_value, axis]) Retorna subtraction da series por serie2, elemento a elemento
rtruediv(serie2[, level, fill_value, axis]) Retorna divisão de ponto flutuante de series por serie2, elemento a elemento
sample([n, frac, replace, weights, …]) Retorna uma amostra randomizada de items da Series
searchsorted(value[, side, sorter]) Encontra índices de elementos de devem ser inseridos para manter a ordem
sem([axis, skipna, level, ddof, numeric_only]) Retorna erro padrão imparcial da média
skew([axis, skipna, level, numeric_only]) Retorna inclinação imparcial
sort_index([axis, level, ascending, …]) Reorganiza Series usando os labels dos índices
sort_values([axis, ascending, inplace, …]) Reorganiza Series usando seus valores
std([axis, skipna, level, ddof, numeric_only]) Retorna o desvio padrão da amostra
str Utiliza funções de string sobre elementos das series (se string). Ex. series.str.split(“-“)
sub(serie2) Retorna subtração de series por serie2, elemento a elemento
subtract(serie2) Idem
sum([axis, skipna, level, numeric_only, …]) Retorna soma dos valores da Series
tail([n]) Retorna os últimos n elemetnos
to_clipboard([excel, sep]) Copia o object para o clipboard do sistema
to_csv([path_or_buf, sep, na_rep, …]) Grava o object como arquivo csv (valores separados por vírgula)
to_dict([into]) Converte a Series para dicionário {label -> value}
to_excel(excel_writer[, sheet_name, na_rep, …]) Grava objeto como uma planilha do Excel
to_frame([name]) Converte Series em DataFrame
to_hdf(path_or_buf, key[, mode, complevel, …]) Grava dados da Series em arquivo HDF5 usando HDFStore
to_json([path_or_buf, orient, date_format, …]) Converte o objeto em uma string JSON
to_latex([buf, columns, col_space, header, …]) Renderiza objeto para LaTeX tabular, longtable, ou table/tabular aninhado.
to_lista() Retorna a lista dos valores
to_markdown([buf, mode, index, storage_options]) Escreve a Series em formato Markdown
to_numpy([dtype, copy, na_value]) Converte a series em NumPy ndarray
to_pickle(path[, compression, protocol, …]) Grava objeto serializado em arquivo Pickle
to_sql(name, con[, schema, if_exists, …]) Grava elementos em forma de um database SQL
to_string([buf, na_rep, float_format, …]) Constroi uma representação string da Series
tolist() Retorna uma lista dos valores
transform(func[, axis]) Executa func sobre elementos da serie
truediv(serie2) Retorna divisão de ponto flutuante de series por serie2, elemento a elemento
truncate([before, after, axis, copy]) Trunca a Series antes e após índices dados
unique() Retorna os valores da Series, sem repetições
update(serie2) Modifica a Series usando valores em serie2, sob índices especificados
value_counts([normalize, sort, ascending, …]) Retorna Series com a contagem de valores únicos
var([axis, skipna, level, ddof, numeric_only]) Retorna a variância imparcial dos valores da Series
view([dtype]) Cria uma nova “view” da Series
where(cond[, serie2, inplace, axis, level, …]) Substitui valores a condição cond é False
🔺Início do artigo

Bibliografia

  • Blair,Steve: Python Data Science, The Ultimate Handbook for Beginners on How to Explore NumPy for Numerical Data, Pandas for Data Analysis, IPython, Scikit-Learn and Tensorflow for Machine Learning and Business, edição do autor disponível na Amazon, 2019.
  • Harrison, Matt: Learning Pandas, Python Tools for Data Munging, Data Analysis, and Visualization,
    edição do autor, 2016.
  • McKinney, Wes: pandas: powerful Python data analysistoolkit, Release 1.2.1
    publicação do Pandas Development Team, revisado em 2021.
  • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
    O’Reilly Media, 2018.
  • Pandas: página oficial, acessada em janeiro de 2021.
  • Pandas User Guide, acessada em fevereiro de 2021.
  • Miller, Curtis: On Data Analysis with NumPy and pandas, Packt Publishing, Birmingham, 2018.

pandas e sql comparados



Esse texto descreve os dataframes do pandas em comparação com as consultas SQL (Structured Query Language), explorando similaridades e diferenças entre os dois sistemas de consulta e extração de dados. Ele serve para descrever as funcionalidades de busca e edição do pandas e pode ser particularmente útil para aqueles que conhecem SQL e pretendem usar o pandas.

Para realizar os experimentos abaixo usamos o Jupyter Notebook, um aplicativo que roda dentro de um navegador, que pode ser facilmente instalado e permite a reprodução se todo o código aqui descrito. Você pode ler mais sobre Jupyter Notebook e Linguagem de Consultas SQL nesse site.

Esse texto é baseado em parte do manual do pandas e expandido. Ele usa um conjunto de dados baixados do github renomeado aqui para dfGorjeta. Nomes e valores dos campos foram traduzidos para o português.

# importar as bibliotecas necessárias
import pandas as pd
import numpy as np

url = "https://raw.github.com/pandas-dev/pandas/master/pandas/tests/io/data/csv/tips.csv"

dfGorjeta = pd.read_csv(url)
dfGorjeta.head()
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

Para efeito de testar os comandos do dataframe vamos alterar os nomes dos campos e traduzir os conteúdos dos dados. Para descobrir quais são os valores dos campos, sem repetições, transformamos as séries em sets, uma vez que valores de um set (conjunto) não se repetem.

print(set(dfGorjeta["sexo"]))
print(set(dfGorjeta["fumante"]))
print(set(dfGorjeta["dia"]))
print(set(dfGorjeta["hora"]))
{‘Male’, ‘Female’}
{‘No’, ‘Yes’}
{‘Sat’, ‘Sun’, ‘Fri’, ‘Thur’}
{‘Lunch’, ‘Dinner’}

No código seguinte alteramos os nomes de campos e traduzimos o conteúdo. A sintaxe da operação de edição do dataframe será discutida mais tarde no artigo:

# muda os nomes dos campos
dfGorjeta.rename(columns={"total_bill":"valor_conta", "tip":"gorjeta",
                        "smoker":"fumante", "sex":"sexo","day":"dia",
                        "time":"hora","size":"pessoas"}, inplace=True)

# traduzindo os valores dos campos:
dfGorjeta.loc[dfGorjeta["fumante"] == "No", "fumante"] = "não"
dfGorjeta.loc[dfGorjeta["fumante"] == "Yes", "fumante"] = "sim"
dfGorjeta.loc[dfGorjeta["sexo"] == "Female", "sexo"] = "mulher"
dfGorjeta.loc[dfGorjeta["sexo"] == "Male", "sexo"] = "homem"
dfGorjeta.loc[dfGorjeta["hora"] == "Dinner", "hora"] = "jantar"
dfGorjeta.loc[dfGorjeta["hora"] == "Lunch", "hora"] = "almoço"
dfGorjeta.loc[dfGorjeta["dia"] == "Fri", "dia"] = "sex"
dfGorjeta.loc[dfGorjeta["dia"] == "Sat", "dia"] = "sab"
dfGorjeta.loc[dfGorjeta["dia"] == "Sun", "dia"] = "dom"
dfGorjeta.loc[dfGorjeta["dia"] == "Thur", "dia"] = "qui"

# Temos agora o seguinte dataframe
dfGorjeta
valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
1 10.34 1.66 homem não dom jantar 3
2 21.01 3.50 homem não dom jantar 3
3 23.68 3.31 homem não dom jantar 2
4 24.59 3.61 mulher não dom jantar 4
239 29.03 5.92 homem não sab jantar 3
240 27.18 2.00 mulher sim sab jantar 2
241 22.67 2.00 homem sim sab jantar 2
242 17.82 1.75 homem não sab jantar 2
243 18.78 3.00 mulher não qui jantar 2

As consultas SQL realizadas a seguir pressupõe a existência de um banco de dados com o mesmo nome, a mesma estrutura e dados que o dataframe dfGorjetas.

SELECT

Nas consultas SQL as seleções são feitas com uma lista de nomes de campos que se deseja retornar, separados por vírgula, ou através do atalho * (asterisco) para selecionar todas as colunas. No pandas a seleção de colunas é feita passando-se uma lista de nomes de campos para o DataFrame. Uma chamada ao dataframe sem uma lista de nomes de colunas resulta no retorno de todas as colunas, da mesma forma que usar * no SQL.

–– sql: consulta (query) usando select
SELECT valor_conta, gorjeta, fumante, hora FROM dfGorjeta LIMIT 5;
# pandas:
dfGorjeta[["valor_conta", "gorjeta", "hora"]].head()
valor_conta gorjeta hora
0 16.99 1.01 jantar
1 10.34 1.66 jantar
2 21.01 3.50 jantar
3 23.68 3.31 jantar
4 24.59 3.61 jantar

O método head(n) limita o retorno do dataframe às n primeiras linhas. n = 5 é o default. Para listar as últimas linhas usamos tail(n). Linhas também podem ser selecionadas por chamadas ao sei indice.

# Para acessar as últimas linhas do dataframe podemos usar
# dfGorjeta[["valor_conta", "gorjeta", "hora"]].tail()

# selecionando linhas por meio de seu índice.
dfGorjeta.iloc[[1,239,243]]
valor_conta gorjeta sexo fumante dia hora pessoas
1 10.34 1.66 homem não dom jantar 3
239 29.03 5.92 homem não sab jantar 3
243 18.78 3.00 mulher não qui jantar 2

Os dataframes possuem a propriedade shape que contém sua dimensionalidade. No nosso caso temos

dfGorjeta.shape
(244, 7)

o que significa que são 244 linhas em 7 campos.

No SQL você pode retornar uma coluna resultado de um cálculo usando elementos de outras colunas. No pandas podemos usar o método assign() para inserir uma coluna calculada:

–– sql:
SELECT *, gorjeta/valor_conta*100 as percentual FROM dfGorjeta LIMIT 4;
# pandas: método assign()
dfGorjeta.assign(percentual = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta" *100]).head(4)
valor_conta gorjeta sexo fumante dia hora pessoas percentual
0 16.99 1.01 mulher não dom jantar 2 5.944673
1 10.34 1.66 homem não dom jantar 3 16.054159
2 21.01 3.50 homem não dom jantar 3 16.658734
3 23.68 3.31 homem não dom jantar 2 13.978041

Essa coluna é retornada mas não fica anexada ao dataframe. Para anexar uma coluna ao dataframe podemos atribuir o resultado do cálculo a uma nova coluna:

dfGorjeta["percentual"] = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta"] * 100
print("Nessa estapa temos as colunas:\n", dfGorjeta.columns)

# Vamos apagar a coluna recém criada para manter a simplicidade da tabela
dfGorjeta.drop(["percentual"], axis=1, inplace=True)
Nessa estapa temos as colunas:
Index([‘valor_conta’, ‘gorjeta’, ‘sexo’, ‘fumante’, ‘dia’, ‘hora’, ‘pessoas’, ‘percentual’],
dtype=’object’)

WHERE


Filtragem de dados em consultas SQL são feitas através da cláusula WHERE. DataFrames podem ser filtrados de várias formas diferentes. O indexamento com valores booleanos é provavelmente o mais simples:

–– cláusula WHERE do sql
SELECT * FROM dfGorjeta WHERE hora = "jantar" LIMIT 5;
# filtragem por indexamento no pandas
dfGorjeta[dfGorjeta["hora"] == "jantar"].head(5)
valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
1 10.34 1.66 homem não dom jantar 3
2 21.01 3.50 homem não dom jantar 3
3 23.68 3.31 homem não dom jantar 2
4 24.59 3.61 mulher não dom jantar 4

A consulta acima funciona da seguinte forma:

# is_jantar é uma série contendo True e False (True para jantares, False para almoços)
is_jantar = dfGorjeta["hora"] == "jantar"
# usamos display para exibir a contagem de falsos/verdadeiros
display("Quantos jantares/almoços:", is_jantar.value_counts())

# para negar a série inteira, invertendo True ↔ False usamos ~ (til)
# a linha abaixo imprime o número de almoços na tabela
print("A lista contém %d almoços" % dfGorjeta[~is_jantar]["hora"].count())

# também podemos obter a lista das entradas que não correspondem a "jantar" usando
# dfGorjeta[dfGorjeta["hora"] != "jantar"]
‘Quantos jantares/almoços:’
True 176
False 68
Name: hora, dtype: int64A lista contém 68 almoços

Quando essa série é passada para o dataframe apenas as linhas correspondentes à True são retornados. A última consulta é equivalente à dfGorjeta[~is_jantar].head().

No SQL podemos procurar por partes de uma string com a cláusula LIKE. No pandas transformamos o campo dfGorjeta["sexo"]em uma string que possui o método startswith("string").

–– sql: SELECT TOP 2 sexo, valor_conta FROM dfGorjeta WHERE sexo LIKE 'ho%';
dfGorjeta.loc[dfGorjeta['sexo'].str.startswith('ho'),['sexo','valor_conta']].head(2)

que retorna as 2 primeiras linhas em que o campo sexo começa com o texto “ho”.

Também podemos procurar por campos que estão incluidos em um conjunto de valores:

–– sql:
SELECT * FROM dfGorjeta WHERE dia IN ('sab', 'dom');
dfGorjeta.loc[dfGorjeta['dia'].isin(["dom", "sab"])]

que retorna todas as linhas em que o campo dia é “dom” ou “sab”.

Assim como se pode usar operadores lógicos AND e OR nas consultas SQL para inserir múltiplas condições, o mesmo pode ser feito com dataframes usando | (OR) e & (AND). Por ex., para listar as gorjetas com valor superior à $5.00 dadas em jantares:

–– SQL: múltiplas condições em WHERE
SELECT * FROM dfGorjeta WHERE hora = 'jantar' AND gorjeta > 6.00;
# no pandas
dfGorjeta[(dfGorjeta["hora"] == "jantar") & (dfGorjeta["gorjeta"] > 6.00)]
valor_conta gorjeta sexo fumante dia hora pessoas
23 39.42 7.58 homem não sab jantar 4
59 48.27 6.73 homem não sab jantar 4
170 50.81 10.00 homem sim sab jantar 3
183 23.17 6.50 homem sim dom jantar 4
212 48.33 9.00 homem não sab jantar 4
214 28.17 6.50 mulher sim sab jantar 3

Podemos obter uma lista dos dados correspondentes a gorjetas dadas por grupos com 5 ou mais pessoas ou com contas de valor acima de $45.00, limitada aos 4 primeiros registros:

–– SQL:
SELECT * FROM dfGorjeta WHERE pessoas >= 5 OR valor_conta > 45 LIMIT 4;
# pandas
dfGorjeta[(dfGorjeta["pessoas"] >= 5) | (dfGorjeta["valor_conta"] > 45)].head(4)
valor_conta gorjeta sexo fumante dia hora pessoas
59 48.27 6.73 homem não sab jantar 4
125 29.80 4.20 mulher não qui almoço 6
141 34.30 6.70 homem não qui almoço 6
142 41.19 5.00 homem não qui almoço 5

Dados ausentes são representados por NULL no, uma marca especial para indicar que um valor não existe no banco de dados. Nos dataframes do pandas o mesmo papel é desempenhado por NaN (Not a Number). Esses marcadores podem surgir, por ex., na leitura de um arquivo csv (valores separados por vírgulas) quando um valor está ausente ou não é um valor numérico em uma coluna de números. Para verificar o comportamento do pandas com NaN criamos um dataframe com valores ausentes. Verificações de nulos é feita com os métodos notna() e isna().

frame = pd.DataFrame({"col1": ["A", "B", np.NaN, "C", "D"], "col2": ["F", np.NaN, "G", "H", "I"]})
frame
col1 col2
0 A F
1 B NaN
2 NaN G
3 C H
4 D I

Se temos um banco de dados SQLcom essa estrutura e conteúdo podemos extrair as linhas onde col2 é NULL usando a consulta:

–– sql
SELECT * FROM frame WHERE col2 IS NULL;
# no case do pandas usamos
frame[frame["col2"].isna()]
col1 col2
1 B NaN

De forma análoga, podemos extrair as linhas para as quais col1 não é NULL. No pandas usamos notna().

–– sql
SELECT * FROM frame WHERE col1 IS NOT NULL;
# pandas: linhas em que col1 não é nula
frame[frame["col1"].notna()]
col1 col2
0 A F
1 B NaN
3 C H
4 D I

GROUP BY


No SQL consultas com agrupamentos são feitas usando-se as operações GROUP BY. No pandas existe o método groupby() que tipicamente particiona o conjunto de dados em grupos e aplica alguma função (em geral de agregamento), combinando depois os grupos resultantes.

Um exemplo comum é o de particionar os dados em grupos menores e contar os elementos desses grupos. Voltando ao nosso dataframe dfGorjeta podemos consultar quantas gorjetas foram dadas por grupos de cada sexo:

–– sql
SELECT sexo, count(*) FROM dfGorjeta GROUP BY sexo;
# o equivalente em pandas seria
dfGorjeta.groupby("sexo").size()
sexo
mulher 87
homem 157
dtype: int64

O resultado é uma series cujos valores podem ser retornados por seu nome de index ou pelo número desse indice.

print("A lista contém %d homens" % dfGorjeta.groupby("sexo").size()[0])
print("\t\t e %d mulheres" % dfGorjeta.groupby("sexo").size()["mulher"])
A lista contém 157 homens
e 87 mulheres

É possível aplicar o método count() para cada coluna, individualmente:

dfGorjeta.groupby("sexo").count()
valor_conta gorjeta fumante almoço hora pessoas
sexo
mulher 87 87 87 87 87 87
homem 157 157 157 157 157 157

Observe que no código do pandas usamos size() e não count(). Isso foi feito porque o método count() é aplicado sobre cada coluna e retorna tantos valores quantas colunas existem, com valores não null.

Também se pode aplicar o método count() para uma coluna específica:

# para contar valores em uma única coluna primeiro ela é selecionada, depois contada
dfGorjeta.groupby("sexo")["valor_conta"].count()
sexo
mulher 87
homem 157
Name: valor_conta, dtype: int64

Existem diversas funções de agregamento. São elas:

função descrição
mean() calcula médias para cada grupo
sum() soma dos valores do grupo
size() *tamanhos dos grupos
count() número de registros no grupo
std() desvio padrão dos grupos
var() variância dos grupos
sem() erro padrão da média dos grupos
describe() gera estatísticas descritivas
first() primeiro valor no grupo
last() último valor no grupo
nth() n-ésimo valor (ou subconjunto se n for uma lista)
min() valor mínimo no grupo
max() valor máximo no grupo

* A função size() retorna o número de linhas em uma serie e o número de linhas × colunas em dataframes.

Para obter um resumo estatístico relativo ao campo gorjeta, agrupado pelo campo sexo podemos usar:

dfGorjeta.groupby("sexo")["gorjeta"].describe()
count mean std min 25% 50% 75% max
sexo
homem 157.0 3.089618 1.489102 1.0 2.0 3.00 3.76 10.0
mulher 87.0 2.833448 1.159495 1.0 2.0 2.75 3.50 6.5

Múltiplas funções podem ser aplicadas de uma vez. Suponha que queremos determinar como os valores das gorjetas variam por dia da semana. O método agg() (de agregar) permite que se passe um dicionário para o dataframe agrupado, indicando que função deve ser aplicada a cada coluna.

–– sql (agrupe os dados por dia, calcule a média para cada dia e o número de entradas contadas)
SELECT dia, AVG(gorjeta), COUNT(*) FROM dfGorjeta GROUP BY dia;
# na pandas, use mean no campo gorjeta, size no campo dia
dfGorjeta.groupby("dia").agg({"gorjeta": np.mean, "dia": np.size})
gorjeta dia
dia
dom 3.255132 76
qui 2.771452 62
sab 2.993103 87
sex 2.734737 19

Também é possível realizar o agrupamento por mais de uma coluna. Para fazer isso passamos uma lista de colunas para o método groupby().

–– agrupe primeiro por "fumante", depois por "dia"
–– realize a contagem dos registros e a média das gorjetas
SELECT fumante, dia, COUNT(*), AVG(gorjeta) FROM dfGorjeta GROUP BY fumante, dia;
# no pandas
dfGorjeta.groupby(["fumante", "dia"]).agg({"gorjeta": [np.size, np.mean]})
gorjeta
size mean
fumante dia
não dom 57.0 3.167895
qui 45.0 2.673778
sab 45.0 3.102889
sex 4.0 2.812500
sim dom 19.0 3.516842
qui 17.0 3.030000
sab 42.0 2.875476
sex 15.0 2.714000

JOIN

No SQL tabelas podem ser juntadas ou agrupadas através da cláusula JOIN. Junções podem ser LEFT, RIGHT, INNER, FULL. No pandas se usa os métodos join() ou merge(). Por defaultjoin() juntará os DataFrames por seus índices. Cada método tem parâmetros que permitem escolher o tipo da junção (LEFT, RIGHT, INNER, FULL), ou as colunas que devem ser juntadas (por nome das colunas ou índices). [Linguagem de Consultas SQL]

# para os exercícios que se seguem criamos os dataframes
df1 = pd.DataFrame({"key": ["A", "B", "C", "D"], "value":  [11, 12, 13, 14]})
df2 = pd.DataFrame({"key": ["B", "D", "D", "E"], "value":  [21, 22, 23, 24]})
# para exibir esses dataframes com formatação usamos display()
display(df1)
display(df2)
key value
0 A 11
1 B 12
2 C 13
3 D 14
key value
0 B 21
1 D 22
2 D 23
3 E 24

Como antes supomos a existência de duas tabelas de dados sql como as mesmas estruturas e dados para considerarmos as várias formas de JOINs.

INNER JOIN

–– junção das duas tabelas ligadas por suas chaves - key
SELECT * FROM df1 INNER JOIN df2 ON df1.key = df2.key;
# por default merge() faz um INNER JOIN
pd.merge(df1, df2, on="key")
key value_x value_y
0 B 12 21
1 D 14 22
2 D 14 23

O método merge() também oferece parâmetros para que sejam feitas junções de uma coluna de um dataframe com o índice de outro dataframe. Para ver isso vamos criar outro dataframe a partir de df2, usando o campo key como índice.

# novo dataframe tem campo "key" como índice
df2_indice = df2.set_index("key")
display(df2_indice)
pd.merge(df1, df2_indice, left_on="key", right_index=True)
value
key
B 21
D 22
D 23
E 24
key value_x value_y
1 B 12 21
3 D 14 22
3 D 14 23

LEFT OUTER JOIN

A junção LEFT OUTER JOIN recupera todos as campos à esquerda, existindo ou não uma linha correspondente à direita. O parâmetro how="left" é o equivalente no pandas.

–– sql: recupera todos os valores de df1 existindo ou não correspondente em df2
SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="left" equivale a LEFT OUTER JOIN
pd.merge(df1, df2, on="key", how="left")
key value_x value_y
0 A 11 NaN
1 B 12 21
2 C 13 NaN
3 D 14 22
4 D 14 23

Observe que df2 não possui campos com key = "A" ou key = "C" e, por isso o dataframe resultante tem NaN nessas entradas. key = "A". Como df2 tem 2 linhas para key = "D" a linha aparece duplicada para essa key em df1.

RIGHT JOIN

A junção RIGH OUTER JOIN recupera todos as campos à direita, existindo ou não uma linha correspondente à esquerda. O parâmetro how="right" é o equivalente no pandas.

–– sql: recupera todos os registros em df2
SELECT * FROM df1 RIGHT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="right" equivale a RIGHT OUTER JOIN
pd.merge(df1, df2, on="key", how="right")
key value_x value_y
0 B 12 21
1 D 14 22
2 D 14 23
3 E NaN 24

FULL JOIN

A junção FULL OUTER JOIN recupera todos as campos à direita ou à esquerda, representando como NaN os valores ausentes em uma ou outra. Todos as linhas das duas tabelas são retornadas com junção onde a campo key existe em ambas. O parâmetro how="outer" é o equivalente no pandas. Observe que nem todos os gerenciadores de bancos de dados permitem essa operação.

–– sql: retorna todos os registros em ambas as tabelas
SELECT * FROM df1 FULL OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="outer" é o equivalente em dataframes
pd.merge(df1, df2, on="key", how="outer")
key value_x value_y
0 A 11 NaN
1 B 12 21
2 C 13 NaN
3 D 14 22
4 D 14 23
5 E NaN 24

UNION

Para os exemplos seguintes definimos mais 2 dataframes:

df3 = pd.DataFrame({"cidade": ["Rio de Janeiro", "São Paulo", "Belo Horizonte"], "nota": [1, 2, 3]})
df4 = pd.DataFrame({"cidade": ["Rio de Janeiro", "Curitiba", "Brasília"], "nota": [1, 4, 5]})

No SQL a clásula UNION ALL é usada para juntar as linhas retornadas em dois (ou mais) instruções de SELECT. Linhas duplicadas são mantidas. O mesmo efeito pode ser conseguido no pandas usando-se o método concat().

–– sql: UNION ALL
SELECT city, rank FROM df3 UNION ALL SELECT cidade, nota FROM df4;
# pandas: concat
pd.concat([df3, df4])
cidade nota
0 Rio de Janeiro 1
1 São Paulo 2
2 Belo Horizonte 3
0 Rio de Janeiro 1
1 Curitiba 14
2 Brasília 5

No SQL a cláusula UNION tem o mesmo efeito que UNION ALL mas remove as linhas duplicadas. No pandas isso pode ser conseguido se fazendo a conactenação concat() seguida de drop_duplicates().

–– SQL UNION
SELECT city, rank FROM df1 UNION SELECT city, rank FROM df2;
–– o registro duplicado no Rio de Janeiro fica excluído
# pandas: concat() seguido de drop_duplicates()
pd.concat([df1, df2]).drop_duplicates()
cidade nota
0 Rio de Janeiro 1
1 São Paulo 2
2 Belo Horizonte 3
1 Curitiba 14
2 Brasília 5

Outras funções analíticas e de agregamento

Para os próximos exemplos vamos retornar ao nosso dataframe dfGorjeta: para listar as 5 gorjetas mais altas, no MySQL (a sintaxe varia de um para outro gerenciador).

–– MySQL: retorna todos os campos em ordem decrescente, 5 linhas
SELECT * FROM dfGorjeta ORDER BY gorjeta DESC LIMIT 10 OFFSET 5;
# pandas: seleciona 15 maiores e exibe as 10 de menor valor
dfGorjeta.nlargest(15, columns="gorjeta").tail(10)
valor_conta gorjeta sexo fumante dia hora pessoas
183 23.17 6.50 homem sim Dom jantar 4
214 28.17 6.50 mulher sim sab jantar 3
47 32.40 6.00 homem não Dom jantar 4
239 29.03 5.92 homem não sab jantar 3
88 24.71 5.85 homem não Thur almoço 2
181 23.33 5.65 homem sim Dom jantar 2
44 30.40 5.60 homem não Dom jantar 4
52 34.81 5.20 mulher não Dom jantar 4
85 34.83 5.17 mulher não Thur almoço 4
211 25.89 5.16 homem sim sab jantar 4

UPDATE

Há muitas formas de alterar um valor em um campo de um dataframe. Por exemplo, abaixo realizamos uma alteração em todos os valores de gorjeta sempre que gorjeta < 2.

–– sql: em todas as linhas duplique a gorjeta se gorjeta for menor que 1.1
UPDATE dfGorjeta SET gorjeta = gorjeta*2 WHERE gorjeta < 1.1;
# pandas: o mesmo resultado pode ser obtido da aseguinte forma
# dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2

Para explicar com mais detalhes o funcionamento deste código, armazenamos abaixo a lista dos índices das linhas de gorjetas mais baixas e exibimos essas linhas. Em seguida multiplicamos apenas as gorjetas dessas linhas por 2 e examinamos o resultado:

indices = dfGorjeta[dfGorjeta["gorjeta"] < 1.1].index
print("Índices de gorjetas < 1.1:", indices)
display("Lista de gorjetas < 1.1", dfGorjeta.iloc[indices])
# multiplica essas gorjetas por 2
dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2
# lista as mesmas linhas após a operação
display("Gorjetas após a operação:", dfGorjeta.iloc[indices])
Índices de gorjetas < 1.1: Int64
Index([0, 67, 92, 111, 236], dtype=’int64′)
‘Lista de gorjetas < 1.1’

valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
67 3.07 1.00 mulher sim sab jantar 1
92 5.75 1.00 mulher sim sex jantar 2
111 7.25 1.00 mulher não sab jantar 1
236 12.60 1.00 homem sim sab jantar 2

‘Gorjetas após a operação:’

valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 2.02 mulher não dom jantar 2
67 3.07 2.00 mulher sim sab jantar 1
92 5.75 2.00 mulher sim sex jantar 2
111 7.25 2.00 mulher não sab jantar 1
236 12.60 2.00 homem sim sab jantar 2
–– sql: alterar um campo de uma linha específica (supondo a existência de um campo id)
UPDATE dfGorjeta SET sexo = 'NI' WHERE id = 239
# para alterar o campo sexo para 'NI' (não informado)
dfGorjeta.loc[239, 'sexo'] ='NI'

DELETE

Existem muitas formas de se excluir linhas de um dataframe mas é comum a prática de selecionar as linhas que devem ser mantidas e copiar para um novo dataframe.

–– sql: linhas são apagadas sob um certo critério
DELETE FROM dfGorjeta WHERE gorjeta > 9;
# pandas: como novo dataframe tem o mesmo nome do original, o antigo é sobrescrito e perdido
dfTop = dfGorjeta.loc[dfGorjeta["gorjeta"] > 9]
dfTop
valor_conta gorjeta sexo fumante dia hora pessoas
170 50.81 10.0 homem sim sab jantar 3

Também é possível apagar linhas usando seu índice:

# apagar linha com index = 4, inplace para substituir o dataframe
dfGorjeta.drop(index=4, inplace=True)
# apagar linhas com index = 0 até 3
dfGorjeta.drop(index=[0, 1, 2, 3], inplace=True)
dfGorjeta.head()
valor_conta gorjeta sexo fumante dia hora pessoas
5 25.29 4.71 homem não dom jantar 4
6 8.77 12.00 homem não dom jantar 2
7 26.88 3.12 homem não dom jantar 4
8 15.04 1.96 homem não dom jantar 2
9 14.78 3.23 homem não dom jantar 2
🔺Início do artigo

Bibliografia

  • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
    O’Reilly Media, 2018.
  • Pandas: página oficial, acessada em janeiro de 2021.