Dataframes do pandas
» # linhas de comentários
» linhas de código (input)
↳ linhas de output
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.
O pandas usa muitos dos conceitos de programação do NumPy. A diferença principal entre eles está em que um array do NumPy usa dados homogêneos (todos do mesmo tipo) enquanto os dataframes do pandas podem conter dados de tipos diferentes.
Assim como o NumPy, 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.
» import pandas as pd » import numpy as np » 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] } » ids = [10, 11, 12, 13, 14, 15, 16] » dfAlunos = pd.DataFrame(data=dados, index=ids) ↳
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
.
» dfAlunos.shape ↳ (7, 4) » # o que significa que temos 7 linhas, com 4 campos ou colunas. » # para visualizar apenas as 2 primeiras linhas » dfAlunos.head(2) ↳
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 » dfAlunos.tail(2) ↳
nome | cidade | idade | nota | |
---|---|---|---|---|
15 | Marco | Curitiba | 31 | 61.0 |
16 | Paula | Belo Horizonte | 34 | 44.0 |
# A transposta: » dfAlunos.T ↳
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.
» dfAlunos.columns ↳ Index(['nome', 'cidade', 'idade', 'nota'], dtype='object') » # O nome da segunda coluna (lembrando que se conta a partir de 0) » dfAlunos.columns[1] ↳ 'cidade' » # Selecionando a coluna 'cidade' » dfAlunos['cidade'] ↳ 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 » type(dfAlunos['cidade']) ↳ pandas.core.series.Series » # os métodos das Series se aplicam » dfAlunos['cidade'].value_counts() ↳ 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() » dfAlunos['cidade'].unique() ↳ array(['São Paulo', 'Rio de Janeiro', 'Brasília', 'Salvador', 'Curitiba', 'Belo Horizonte'], dtype=object) » # também podemos transformar esses valores em um set » set(dfAlunos['cidade']) ↳ {'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.
» dfAlunos.dtypes ↳ nome object cidade object idade int64 nota float64 dtype: object » # Uma visão geral sobre os dados pode ser obtido com .info() » dfAlunos.info() ↳ <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 » dfAlunos.describe() ↳
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 |
» dfAlunos.corr() ↳
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 » dfInserir = pd.DataFrame([('Juliana','Curitiba',28,80.0)], columns=['nome','cidade','idade','nota'], index=[100]) » pd.concat([dfAlunos, dfInserir]) ↳
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.
» df = pd.concat([dfAlunos, dfInserir], ignore_index=True) » df.index ↳ 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.
» dfAlunos['calculado']=dfAlunos['nota'] * dfAlunos['idade'] » dfAlunos ↳
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 » dfAlunos.drop(['calculado'], axis=1) » # para alterar o dataframe usamos o parâmetro inplace=True » 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.
» lista = ['nome','idade'] » # a linha abaixo é idêntica à dfAlunos[['nome','idade']] » dfAlunos[lista] ↳
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.
» dfNumerico=dfAlunos[['idade', 'nota']] » # a soma dos elementos em cada coluna » dfNumerico.sum() # o mesmo que dfNumerico.sum(axis=0) ↳ idade 235.0 nota 520.0 dtype: float64 » # a soma dos elementos em cada linha » dfNumerico.sum(axis=1) ↳ 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 ocorre que a exportação de outros aplicativos, como o Excel, possa ser feita nesse formato ou ser nele convertido.
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 bem alinhadas. 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).
» dfNovoAlunos = pd.read_csv('./alunos.csv') » dfNovoAlunos ↳
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 em um pindice id
efetivo por meio do método dataFrame.set_index
:
» # torne o campo id o índice » dfNovoAlunos.set_index('id', inplace=True) » dfNovoAlunos.head(2) ↳
nome | cidade | idade | nota | |
---|---|---|---|---|
id | ||||
10 | Pedro | São Paulo | 34 | 83.0 |
11 | Maria | São Paulo | 23 | 59.0 |
Observe que o índice é apresentado na segunda linha do cabeçalho para indicar que não é um campo comum.
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 » dfNone = pd.read_csv('./dados/nums.csv', header=None) » # inserindo o nome ou labels para as colunas » dfNames = pd.read_csv('./dados/nums.csv', names=('A', 'B', 'C', 'D')) » # exibe os dois dataframes » display('sem headers:', dfNone, 'com headers:', dfNames) ↳
‘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.
» dfPula2 = pd.read_csv('./dados/nums.csv', header=2)
Importação de csv com linhas de comprimentos desiguais
Pode ocorrer que um arquivo importado precise de alguma forma de tratamento para a montagem adequada de suas colunas, quando importado em um dataframe. Suponha, por exemplo, que temos em disco um arquivo de texto como o seguinte conteúdo:
palavras a;1;2;3;6;7;9;10;17;121;131;138;252;463 aba;146 abafa;125;127;129 abaixado;1;125;127;130 abastecer;121;146;150;250;354;358 abatido;1;124;125;127;129;130;140;143;358;360 aberto;13;22;125;126;131;132;138;11;250;252 abismei;125;126 abra;14;22;125;126;131;132;138;11;250;252 abriga;125;126;131;137 acabrunha;143;150 acalmar;1;124;125;126;140;142;143;253 acaso;125;126;131;135;253
Essa é uma seleção muito pequena, para fim de demonstração da técnica, do extenso dicionário LIWC que associa palavras da língua portuguêsa à um conjunto de códigos associados à características linguísticas e cognitivas. Queremos classificar palavras de acordo com esse dataset.
Não podemos importar diretamente esses dados usando pd.read_csv(arquivo, sep=';')
pois o comprimento irregular dos códigos geraria erro na importação. Então nos resta importar todas as linhas em uma coluna e tratá-las depois, de modo adequado.
# o arquivo csv está em arquivo='./dados/liwc.csv' # importamos com separador que não existe no texto para obter apenas 1 coluna dfPalavras = pd.read_csv(arquivo, sep='#') dfPalavras palavras 0 a;1;2;3;6;7;9;10;17;121;131;138;252;463 1 aba;146 2 abafa;125;127;129 3 abaixado;1;125;127;130 4 abastecer;121;146;150;250;354;358 5 abatido;1;124;125;127;129;130;140;143;358;360 6 aberto;13;22;125;126;131;132;138;11;250;252 7 abismei;125;126 8 abra;14;22;125;126;131;132;138;11;250;252 9 abriga;125;126;131;137 10 acabrunha;143;150 11 acalmar;1;124;125;126;140;142;143;253 12 acaso;125;126;131;135;253 # cada linha pode se quebrada em ; retornando listas dfPalavras.palavras.str.split(';').head(3) 0 [a, 1;2;3;6;7;9;10;17;121;131;138;252;463] 1 [aba, 146] 2 [abafa, 125;127;129]
O próprio método split
pode ser usado com os parâmetros str.split(sep, n, expand)
, que quebra a string em sep, usando apenas as primeiras n partes, e montando um dataframe se expand=True. Montamos um dataframe vazio df
, separamos as linhas em 2 partes e as aplicamos em df
, com as partes palavra e codigo separadas.
df = pd.DataFrame({}) df[['palavra','codigo']] = dfPalavras.palavras.str.split(';', n=1, expand=True) # o seguinte dataframe é gerado df palavra codigo 0 a 1;2;3;6;7;9;10;17;121;131;138;252;463 1 aba 146 2 abafa 125;127;129 3 abaixado 1;125;127;130 4 abastecer 121;146;150;250;354;358 5 abatido 1;124;125;127;129;130;140;143;358;360 6 aberto 13;22;125;126;131;132;138;11;250;252 7 abismei 125;126 8 abra 14;22;125;126;131;132;138;11;250;252 9 abriga 125;126;131;137 10 acabrunha 143;150 11 acalmar 1;124;125;126;140;142;143;253 12 acaso 125;126;131;135;253 # podemos encontrar as palavras associadas ao codigo = 127 df[df.codigo.str.contains('127')]['palavra'] 2 abafa 3 abaixado 5 abatido # ou os codigos associados à palavra 'abafa' df[df.palavra == 'abafa']['codigo'].values[0] '125;127;129'
Na última linha df[df.palavra == 'abafa']['codigo']
é uma Series. Para extrair seu valor usamos .values[0]
. Nesse caso o número de códigos é muito varíavel e não seria prático construir uma coluna para cada código.
Se for necessário ter colunas correspondendo à cada código teríamos que preencher os campos vazio com algum valor, como 0 ou NaN.
Gravando o dataframe em arquivos pickle
Ao términdo de cada fase de depuração, manipulação e ajustes do dataframe, podemos gravá-lo em disco em um arquivo pickle. O arquivo gravado pode ser lido e o dataframe recuperado em nova sessão de código, sem que tenhamos que repetir as etapas já realizadas.
» pd.to_pickle(dfNovoAlunos, './dados/Alunos.pkl') » del dfNovoAlunos » 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:
Achei muito útil esse artigo pra quem tá aprendendo o usoo do SQLite.