Pandas – Dataframes


Dataframes do pandas

Usamos a marcação:
» # 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
🔺Início do artigo

Bibliografia

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

Nesse site:

Deixe um comentário

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