Pandas – Dataframes


🔻Final do artigo

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:

Deixe uma resposta

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