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:
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 esteja gravado no disco.
11,12,13,14
21,22,23,24
31,32,33,34
# 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 |
Bibliografia
Consulte bibliografia completa em Pandas, Introdução neste site.
Nesse site: