Introdução a Bibliotecas para Ciência de Dados - Parte 2 de 3
Libraries For Data Science — Pandas
Pandas
No post anterior conversamos sobre a biblioteca Numpy, uma ferramenta fundamental para análise de dados, bastante utilizada no cenário de Inteligência Artificial. Caso não conheça, sugiro a leitura do conteúdo neste link.
Continuando com o nosso estudo, conversaremos agora sobre a biblioteca Pandas, outra ferramenta essencial que deve ser explorada por todos profissionais que atuem ou queiram atuar com ciência de dados.
Segundo a própria documentação, Pandas é uma ferramenta de análise e manipulação de dados de código aberto rápida, poderosa, flexível e fácil de usar, construída sobre a linguagem de programação Python.
A documentação completa pode ser vista em: Documentação Pandas
Semelhante ao que estudamos com Numpy, a biblioteca Pandas pode ser instalada utilizando o Anaconda Distribution:
! conda install pandas
Ou utilizando o pip:
pip install pandas
Caso não queira instalar localmente, você tem a opção de utilizar o Colab, ou “Colaboratory”, que permite que você execute todos os códigos em Python diretamente no seu navegador. Neste link você tem um vídeo introdutório da ferramenta.
Para acessar as funções do Pandas é necessário importar a biblioteca para o código python. Onde pd é apenas uma abreviação para melhor legibilidade do código.
import numpy as np
import pandas as pd
Series
O primeiro objeto que iremos estudar será do tipo Serie. Podemos definir como uma lista de valores, semelhante às arrays estudadas no numpy, com a diferença que trabalhamos agora com estruturas rotuladas, ou seja, temos um rótulo atribuído para cada um dos dados.
O objeto oferece suporte à indexação baseada em rótulos e fornece uma série de métodos para realizar operações envolvendo esse índice.
A Serie pode ser criada através da instrução pd.Series(), veja:
>> arr = np.arange(10) *33
>> nomes = "Adson Bruno Carla Daniel Everton Fabio Gabi Humberto Iago Jose".split()
>> ser = pd.Series(data=arr,index=nomes)
>> ser
Adson 0
Bruno 33
Carla 66
Daniel 99
Everton 132
Fabio 165
Gabi 198
Humberto 231
Iago 264
Jose 297
dtype: int64
Observe que antes de criar a Serie, atribuímos à variável “nomes” uma lista de nomes aleatórios e a variável “arr” uma array de valores escalonados, etapa não obrigatória, porém útil. Caso você não atribua uma lista de rótulos para a variável index, será gerado automaticamente rótulos sequenciais de [0 … n]. No nosso exemplo, os valores contidos na variável nomes serão nossos índices. A seleção dos dados pode ocorrer de duas formas, através dos rótulos atribuídos, neste caso, os nomes ou através dos índices, veja com funciona:
>> ser[1]
33
>> ser["Bruno"]
33
Operações com o objeto do tipo Serie
As operações com os objetos do tipo serie ocorrem respeitando os rótulos pré definidos, independente da posição que se encontram na estrutura. Como exemplo, vamos criar uma nova serie, chamada ser1, e definir uma array de 10 valores inteiros entre [-30,0], chamada dat1. Os valores em nomes1, serão utilizados como indices de ser1.
>> dat1 = np.random.randint(-30,0,10)
>> nomes1 = 'Bruno Adson Carla Everton Humberto Fabio Daniel Gabi Iago Jose'.split()
>> ser1= pd.Series(data=dat1 ,index=nomes1)
>> ser1
Bruno -23
Adson -30
Carla -1
Everton -22
Humberto -13
Fabio -17
Daniel -8
Gabi -20
Iago -2
Jose -29
dtype: int64
Observe que quando realizamos uma simples operação de soma entre ser e ser1, apesar de uma ordenação distinta, o resultado respeita o índice definido, no nosso caso, os nomes. Ou seja, mesmo que os índices estejam fora de ordem não haverá incoerência relacional dos dados.
>> ser + ser1
Adson -30
Bruno 10
Carla 65
Daniel 91
Everton 110
Fabio 148
Gabi 178
Humberto 218
Iago 262
Jose 268
dtype: int64
Caso a soma ocorra entre series com rótulos exclusivos haverá resultado apenas nos rótulos comuns. Exemplo vamos adicionar em ser1 os rótulos A e B com valores aleatórios, veja o resultado:
>> d = np.random.randint(low=-30,high=0,size=12)
>> l = 'A B Bruno Adson Carla Everton Humberto Fabio Daniel Gabi Iago Jose'.split()
>> ser1= pd.Series(data= d,index=l)
>> ser1
A -2
B -30
Bruno -19
Adson -29
Carla -15
Everton -3
Humberto -17
Fabio -9
Daniel -18
Gabi -2
Iago -6
Jose -8
dtype: int64
>> ser
Adson 0
Bruno 33
Carla 66
Daniel 99
Everton 132
Fabio 165
Gabi 198
Humberto 231
Iago 264
Jose 297
dtype: int64
>> ser1 + ser
A NaN
Adson -29.0
B NaN
Bruno 14.0
Carla 51.0
Daniel 81.0
Everton 129.0
Fabio 156.0
Gabi 196.0
Humberto 214.0
Iago 258.0
Jose 289.0
dtype: float64
💡 Observe, a operação não ocorreu nos valores exclusivos (A e B), porém os rótulos foram retornados, sendo atribuído NaN a cada um, referenciando a um dado não numérico.
Concatenação de Serie
Uma ação frequente quando trabalhamos com dados são ações que possibilitem a união em uma única instância do objeto. Vamos aproveitar as series criadas, ser e ser1 para mostrar como a função pandas.concat() pode ser útil nesta ação.
>> df = pd.concat(objs=[ser,ser1],axis=1,sort=True,join="outer")
>> df
💡 Atenção para todos os argumentos utilizados e a sintaxe.
O argumento objs recebe uma lista referente às series que serão concatenadas. Em axis é informado o eixo de referência para a concatenação, sort organização e o join o critério de concatenação utilizado. O tipo do objeto resultante foi alterado, logo quando comparado ser1 e df, encontramos:
>> type(ser1)
pandas.core.series.Series
>> type(df)
pandas.core.frame.DataFrame
Essa nova instância é conhecida como DataFrame, sendo uma estrutura de dados primária do pandas, vamos falar mais sobre ela.
DataFrame
DataFrames são objetos com eixos rotulados (linhas e colunas), úteis para realizar operações aritméticas alinhadas a esses rótulos. Para gerar um DataFrame vamos definir os dados como dt de dimensão (5,4) e rótulos de linha e coluna como ind e col respectivamente, veja abaixo:
>> dt = np.random.randint(0,3000,(5,4))
>> ind= 'Adson Luiz Ana Maria Cris'.split()
>> col= 'SalBase Ferias Com 13º'.split()
>> print(dt)
>> print(ind)
>> print(col)
[[ 37 2117 45 1619]
[1030 1541 2333 2032]
[1573 1733 2559 2776]
[2598 1652 1126 534]
[ 100 1823 252 1909]]
['Adson', 'Luiz', 'Ana', 'Maria', 'Cris']
['SalBase', 'Ferias', 'Com', '13º']
💡 Os valores gerados pelo método random do NumPy provavelmente irão divergir, afinal estamos trabalhando com valores pseudo-randômicos. Para gerar o DataFrame utilizamos a função pandas.DataFrame() atribuindo essas informações, veja:
>> df = pd.DataFrame(data=dt, index=ind, columns=col)
>> df
💡 Note que cada coluna de um DataFrame é uma serie.
Admita neste exemplo, que o DataFrame é uma tabela com informações de funcionários de uma empresa. Para selecionar um dado ou um conjunto de dados desta tabela realizamos o procedimento semelhante a serie, com a inclusão dos métodos .loc() e .iloc() para seleção de linhas, veja:
>> df.loc['Ana'] # apenas 1 dado
SalBase 1573
Ferias 1733
Com 2559
13º 2776
Name: Ana, dtype: int64
>> df.loc[['Cris','Ana']] # filtro múltiplas linhas
Filtros em colunas:
>> df['Com'] # apenas 1 coluna
Adson 45
Luiz 2333
Ana 2559
Maria 1126
Cris 252
Name: Com, dtype: int64
>> df[['13º','Ferias']] # filtro multiplas colunas
Inserção de linha ou coluna
Situações que necessitem da inclusão de dados, poderão facilmente ser realizadas, respeitando a dimensionalidade do DataFrame, veja:
>> df['Desconto'] = np.random.randint(-300,0,5)
>> df
Para inserir uma nova linha de dados, utilizamos o .loc() visto anteriormente, informando o rótulo do conjunto de dados e atribuindo os valores dentro de colchetes, veja:
>> df.loc['Beatriz'] = [3000, 200,100,3000,-500]
>> df
Informações importantes do conjunto de dados, como média, valor máximo, valor mínimo, índice do maior valor, índice do menor valor, desvio padrão, variância, entre outros podem ser retirados facilmente, veja exemplos abaixo:
>> print(df['SalBase'].mean())
>> print(df['SalBase'].max())
>> print(df['SalBase'].min())
>> print(df['SalBase'].argmax())
>> print(df['SalBase'].argmin())
>> print(df['SalBase'].std())
>> print(df['SalBase'].var())
1389.6666666666667
3000
37
5
0
1241.705386420896
1541832.2666666666
🚨 Caso receba o erro Erro -> TypeError: unsupported operand type(s) for +: ‘int’ and ‘str utilize o método pandas.to_numeric() para correção, atenção ao atributo errors utilize ‘coerce’.
Filtros Condicionais
Como visto, a seleção de dados podem ocorrer através dos índices e dos rótulos, porém ainda temos a possibilidades de gerar filtros condicionais baseados nos dados das Series. Abaixo temos alguns exemplos de filtros aplicados em Férias e 13º. Inicialmente apresentamos as colunas completas:
>> print(df['Ferias'])
Adson 2117
Luiz 1541
Ana 1733
Maria 1652
Cris 1823
Beatriz 200
Name: Ferias, dtype: int64
>> print(df['13º'])
Adson 1619
Luiz 2032
Ana 2776
Maria 534
Cris 1909
Beatriz 3000
Name: 13º, dtype: int64
Observe como é feito o filtro dos valores em Férias maior que 2000 e 13º menor que 2000, filtros independentes.
>> print(df[df['Ferias'] > 200])
SalBase Ferias Com 13º Desconto
Adson 37 2117 45 1619 -155
Luiz 1030 1541 2333 2032 -236
Ana 1573 1733 2559 2776 -13
Maria 2598 1652 1126 534 -185
Cris 100 1823 252 1909 -275
>> print(df[df['13º'] < 2000])
SalBase Ferias Com 13º Desconto
Adson 37 2117 45 1619 -155
Maria 2598 1652 1126 534 -185
Cris 100 1823 252 1909 -275
Para aplicar ambos os filtros em uma única seleção, temos:
>> print((df['Ferias'] > 2000) & (df['13º'] < 2000))
Adson True
Luiz False
Ana False
Maria False
Cris False
Beatriz False
dtype: bool
>> print(df[(df['Ferias'] > 2000) & (df['13º'] < 2000)])
SalBase Ferias Com 13º Desconto
Adson 37 2117 45 1619 -155
O retorno de um filtro condicional é uma estrutura booleana (Verdadeiro ou Falso). Essa estrutura quando indexada no DataFrame retorna os dados correspondentes da seleção. Se necessário retorne ao código acima para verificação.
Podemos definir a coluna que deve ser retornada da seleção da seguinte forma, veja:
>> print(df[df['SalBase'] > 1000]['Desconto'])
Luiz -236
Ana -13
Maria -185
Beatriz -500
Name: Desconto, dtype: int64
>> print(df[df['SalBase'] > 1000][['Ferias','Desconto']])
Ferias Desconto
Luiz 1541 -236
Ana 1733 -13
Maria 1652 -185
Beatriz 200 -500
>> print(df[(df['Ferias'] > 2000) & (df['13º'] < 2000)][['Desconto','Com']])
Desconto Com
Adson -155 45
Nos 3 exemplos temos um retorno específico, no primeiro apenas Desconto no segundo temos Ferias e Desconto e no terceiro Desconto e Comissão (Com).
Recomendados pelo LinkedIn
🚨 Atenção para a sintaxe utilizada.
Exclusão de dados
A função .drop() é útil para remover estruturas de dados do nosso DataFrame. Admita que no DataFrame abaixo a intenção seja excluir a coluna 13º, veja:
>> print(df)
SalBase Ferias Com 13º
Adson 2018 1939 2715 515
Luiz 1491 393 1800 1552
Ana 2445 673 1622 1628
Maria 219 301 1432 774
Cris 2416 76 2820 1625
>> df.drop('13º', axis=1, inplace = True)
>> df
SalBase Ferias Com
Adson 2018 1939 2715
Luiz 1491 393 1800
Ana 2445 673 1622
Maria 219 301 1432
Cris 2416 76 2820
Para excluir uma linha, basta alterar o parâmetro axis, veja:
>> df.drop('Adson', axis=0, inplace = True)
SalBase Ferias Com
Luiz 1491 393 1800
Ana 2445 673 1622
Maria 219 301 1432
Cris 2416 76 2820
Manipulação de rótulos
Podemos resetar, alterar e até mesmo criar hierarquias de índices. Admita o DataFrame abaixo:
>> print(df)
SalBase Ferias Com 13º
Adson 2973 1186 2967 1694
Luiz 1413 2512 2691 946
Ana 1353 2405 818 1803
Maria 2724 647 2031 2501
Cris 2950 2888 2878 2242
Para resetar os indices atuais pode ser usado o método .reset_index(), veja o resultado:
>> df.reset_index(inplace=True)
>> df
index SalBase Ferias Com 13º
0 Adson 2973 1186 2967 1694
1 Luiz 1413 2512 2691 946
2 Ana 1353 2405 818 1803
3 Maria 2724 647 2031 2501
4 Cris 2950 2888 2878 2242
Observe que foram atribuídos índices sequenciais [0 … n] enquanto que os antigos índices se tornaram uma coluna de rótulo index.
🚨 O atributo inplace mantém a alteração no DataFrame, logo a sua utilização é opcional.
Para alterar o rótulo das colunas podemos fazer uma atribuição direta, como apresentado abaixo:
>> df.columns = ['Nomes', 'SalBase', 'Ferias', 'Com', '13º']
>> df
Nomes SalBase Ferias Com 13º
0 Adson 2973 1186 2967 1694
1 Luiz 1413 2512 2691 946
2 Ana 1353 2405 818 1803
3 Maria 2724 647 2031 2501
4 Cris 2950 2888 2878 2242
Uma alternativa para atribuir rótulos personalizados é utilizar o método .set_index(), para isso é necessário definir uma nova coluna com essas informações, veja abaixo como é feito:
>> df["index"] = ['ind_1','ind_2','ind_3','ind_4','ind_5']
>> df.set_index("index", inplace=True)
>> df
Nomes SalBase Ferias Com 13º
index
ind_1 Adson 2973 1186 2967 1694
ind_2 Luiz 1413 2512 2691 946
ind_3 Ana 1353 2405 818 1803
ind_4 Maria 2724 647 2031 2501
ind_5 Cris 2950 2888 2878 2242
💡 A atribuição direta também poderá ser usada, para isso substitua o df.columns por df.index e informe uma quantidade adequada de rótulos.
O Pandas permite o uso de múltiplos índices e até mesmo criar uma hierarquia de índices. Por exemplo, podemos criar uma lista de tuplas e utilizar a função MultiIndex.from_tuples() para criar esta estrutura, veja:
>> outside = ['G1', 'G1', 'G1', 'G2', 'G2', 'G2']
>> inside = [1, 2, 3, 1, 2, 3]
>> hier_index = list(zip(outside, inside))
>> print(hier_index)
[('G1', 1), ('G1', 2), ('G1', 3), ('G2', 1), ('G2', 2), ('G2', 3)]
Criando o MultiIndedx:
>> hier_index = pd.MultiIndex.from_tuples(hier_index)
>> hier_index
MultiIndex([('G1', 1),
('G1', 2),
('G1', 3),
('G2', 1),
('G2', 2),
('G2', 3)],
)
Criando o DataFrame com os múltiplos índices:
>> df = pd.DataFrame(np.random.randn(6, 2),
index=hier_index, columns=['A','B'])
>> df
A B
G1 1 0.095707 -0.822854
2 0.032456 -0.083039
3 0.084787 0.190948
G2 1 0.748280 0.539120
2 0.271718 -1.866543
3 -0.712434 0.996676
Os nomes dos índices podem ser lidos e alterados por meio da propriedade: index.names, veja:
>> print(df.index.names)
[None, None]
>> df.index.names = ['Group','Num']
>> print(df)
A B
Group Num
G1 1 0.095707 -0.822854
2 0.032456 -0.083039
3 0.084787 0.190948
G2 1 0.748280 0.539120
2 0.271718 -1.866543
3 -0.712434 0.996676
A indexação nesta estrutura hierárquica pode ser feita por meio do método gs() também, o qual permite escolher o nível da hierarquia por meio do parâmetro level, veja:
>> print(df.xs('G1'))
A B
Num
1 0.095707 -0.822854
2 0.032456 -0.083039
3 0.084787 0.190948
>> print(df.xs(['G1',1]))
A 0.095707
B -0.822854
Name: (G1, 1), dtype: float64
>> print(df.xs(1,level='Num'))
A B
Group
G1 0.095707 -0.822854
G2 0.748280 0.539120
Lidando com informações faltantes
No Pandas, informações faltantes são exibidas como NaN (not a number). Internamente, elas são do tipo np.nan.
>> df = pd.DataFrame({'A' : [1, 2, np.nan],
'B' : [5, np.nan, np.nan],
'C' : [1, 2, 3]})
>> print(df)
A B C
0 1.0 5.0 1
1 2.0 NaN 2
2 NaN NaN 3
Podemos identificar os registros faltantes com o método .isnull():
>> df.isnull()
>> print(df)
A B C
0 1.0 5.0 1
1 2.0 NaN 2
2 NaN NaN 3
Com os métodos .dropna() e .fillna() respectivamente podemos remover todas as linhas que contém ao menos um registro faltante ou substituir dados faltante por um valor padrão qualquer, veja o exemplo:
>> df.dropna()
A B C
0 1.0 5.0 1
>> print(df.dropna(axis=1))
C
0 1
1 2
2 3
>> print(df.dropna(thresh=2))
A B C
0 1.0 5.0 1
1 2.0 NaN 2
>> print(df.fillna(value='algo'))
A B C
0 1.0 5.0 1
1 2.0 algo 2
2 algo algo 3
>> print(df['A'].fillna(value=df['A'].mean()))
0 1.0
1 2.0
2 1.5
Name: A, dtype: float64
Agrupando informações de diferentes DataFrames
Existem diversas maneiras de agrupar informações de diferentes DataFrames em um único objeto. Para administradores de bancos de dados relacionais, cujo trabalho consiste em manter enormes tabelas de dados, este é um trabalho rotineiro, que é possível graças às chaves relacionais. Em Pandas, este mesmo conceito é utilizado para manter a coerência dos dados nestas operações agregativas. Admita os DataFrames:
>> df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=[0, 1, 2, 3])
>> df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
'B': ['B4', 'B5', 'B6', 'B7'],
'C': ['C4', 'C5', 'C6', 'C7'],
'D': ['D4', 'D5', 'D6', 'D7']},
index=[4, 5, 6, 7])
>> df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
'B': ['B8', 'B9', 'B10', 'B11'],
'C': ['C8', 'C9', 'C10', 'C11'],
'D': ['D8', 'D9', 'D10', 'D11']},
index=[8, 9, 10, 11])
Os métodos merge(), join() e concat() são úteis para agrupamento de DataFrames, veja os exemplos:
>> print(pd.concat([df1, df2, df3], axis=0))
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
8 A8 B8 C8 D8
9 A9 B9 C9 D9
10 A10 B10 C10 D10
11 A11 B11 C11 D11
Observe que pela função concat() devemos passar como parâmetro uma lista contendo os DataFrames a serem concatenados. O padrão desta função é seguir a ordem dos índices, colocando nas mesmas colunas os dados com os mesmos rótulos. Porém, podemos subverter esta ordem fornecendo o parâmetro axis=1. Veja como é feito:
>> print(pd.concat([df1, df2, df3], axis=1))
A B C D A B C D A B C D
0 A0 B0 C0 D0 NaN NaN NaN NaN NaN NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN NaN NaN NaN NaN NaN
2 A2 B2 C2 D2 NaN NaN NaN NaN NaN NaN NaN NaN
3 A3 B3 C3 D3 NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN A4 B4 C4 D4 NaN NaN NaN NaN
5 NaN NaN NaN NaN A5 B5 C5 D5 NaN NaN NaN NaN
6 NaN NaN NaN NaN A6 B6 C6 D6 NaN NaN NaN NaN
7 NaN NaN NaN NaN A7 B7 C7 D7 NaN NaN NaN NaN
8 NaN NaN NaN NaN NaN NaN NaN NaN A8 B8 C8 D8
9 NaN NaN NaN NaN NaN NaN NaN NaN A9 B9 C9 D9
10 NaN NaN NaN NaN NaN NaN NaN NaN A10 B10 C10 D10
11 NaN NaN NaN NaN NaN NaN NaN NaN A11 B11 C11 D11
O merge() opera em pares de DataFrames e seleciona quais dados manter de acordo com o tipo de merge: left, right, outer ou inner. Estas opções são exatamente iguais às opções que existem em bancos de dados relacionais do tipo SQL, correspondendo à preservação dos registros com chaves no DataFrame da esquerda (left), da direita (right), que aparecem em ambos os DataFrames obrigatoriamente (inner, ou na intersecção das keys) ou que aparecem em um ou outro dataframe (outter, ou na união das keys). Admita os novos DataFrames:
>> left = pd.DataFrame({'key': ['K1', 'K4', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
>> print(left)
key A B
0 K1 A0 B0
1 K4 A1 B1
2 K2 A2 B2
3 K3 A3 B3
>> print(right)
key C D
0 K0 C0 D0
1 K1 C1 D1
2 K2 C2 D2
3 K3 C3 D3
Veja abaixo alguns resultados, no primeiro temos o retorno de todas as keys de left, no segundo todas de right, no terceiro todas concomitantes, e no quarto todas as keys existentes foram retornadas.
>> print(pd.merge(left, right, how='left', on='key'))
key A B C D
0 K1 A0 B0 C1 D1
1 K4 A1 B1 NaN NaN
2 K2 A2 B2 C2 D2
3 K3 A3 B3 C3 D3
>> print(pd.merge(left, right, how='right', on='key'))
key A B C D
0 K0 NaN NaN C0 D0
1 K1 A0 B0 C1 D1
2 K2 A2 B2 C2 D2
3 K3 A3 B3 C3 D3
>> print(pd.merge(left, right, how='inner', on='key'))
key A B C D
0 K1 A0 B0 C1 D1
1 K2 A2 B2 C2 D2
2 K3 A3 B3 C3 D3
>> print(pd.merge(left, right, how='outer', on='key'))
key A B C D
0 K1 A0 B0 C1 D1
1 K4 A1 B1 NaN NaN
2 K2 A2 B2 C2 D2
3 K3 A3 B3 C3 D3
4 K0 NaN NaN C0 D0
Por fim, o método join é conveniente para combinar as colunas de dois DataFrames cujos índices podem ser diferentes. Admita os novos DataFrames:
>> left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=['K0', 'K1', 'K2'])
>> right = pd.DataFrame({'C': ['C0', 'C2', 'C3'],
'D': ['D0', 'D2', 'D3']},
index=['K0', 'K2', 'K3'])
>> print(left)
A B
K0 A0 B0
K1 A1 B1
K2 A2 B2
>> print(right)
C D
K0 C0 D0
K2 C2 D2
K3 C3 D3
Nesse exemplo vamos manter primeiro todas as keys exclusivas de left, na segunda todas as keys, veja abaixo:
>> print(left.join(right))
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2
>> print(left.join(right, how='outer'))
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2
K3 NaN NaN C3 D3
Operações em DataFrames
Algumas operações de natureza estatística como: unique(), nunique(), value_counts(), sum(), mean() e std() e métodos como describe(), info(), head() e tail() serão abordados nessa seção. Admita o DataFrame abaixo:
>> df = pd.DataFrame({'col1' : [1, 2, 3, 4],
'col2' : [444, 555, 666, 444],
'col3' : ['abc', 'def', 'ghi', 'xyz']})
>> print(df)
col1 col2 col3
0 1 444 abc
1 2 555 def
2 3 666 ghi
3 4 444 xyz
No exemplo abaixo primeiro temos o .unique() que retorna os valores únicos da serie, .nunique() o total de valores únicos existentes e .value_counts() a quantidade de cada valor único.
>> print(df['col2'].unique())
[444 555 666]
print(df['col2'].nunique())
3
>> print(df['col2'].value_counts())
444 2
555 1
666 1
Name: col2, dtype: int64
Outras operações importantes são .sum() realizando a soma da serie, .mean() para a média e std() retornando o desvio padrão dos dados. Um exemplo de condicional múltipla também pode ser observado abaixo:
>> print(df['col1'].sum())
>> print(df['col1'].mean())
>> print(df['col1'].std())
>> newdf = df[(df['col1'] > 2) & (df['col2'] == 444)]
>> print(newdf)
10
2.5
1.2909944487358056
col1 col2 col3
3 4 444 xyz
Métodos como describe(), info(), head() e tail() ajudam também na visualização de parâmetros estatísticos e auxiliam na visualização do dataframe, veja:
>> print(df.describe().T)
count mean std min 25% 50% 75% max
col1 4.0 2.50 1.290994 1.0 1.75 2.5 3.25 4.0
col2 4.0 527.25 106.274409 444.0 444.00 499.5 582.75 666.0
>> print(df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 col1 4 non-null int64
1 col2 4 non-null int64
2 col3 4 non-null object
dtypes: int64(2), object(1)
memory usage: 224.0+ bytes
None
>> print(df.head(2))
col1 col2 col3
0 1 444 abc
1 2 555 def
>> print(df.tail(2))
col1 col2 col3
2 3 666 ghi
3 4 444 xyz
Entrada e saída de dados
Nos exemplos que vimos até o momento, tivemos que digitar os valores que seriam armazenados nas séries ou DataFrames. Obviamente esta não é uma situação real de trabalho, onde muitas vezes os dados são fornecidos por outras ferramentas, tais como gerenciadores de planilhas do tipo Excel, arquivos de dados, páginas da internet ou bancos de dados relacionais. Vamos ver como ler e escrever os dados tanto em planilhas quanto em arquivos do tipo csv (comma separated values — valores separados por vírgula). Veja o exemplo abaixo que realiza a leitura de um arquivo csv, para isso usamos o .read_csv() informando entre parênteses o path do arquivo.
>> df = pd.read_csv('exemplo.csv')
>> df
a b c d
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
Para salvar os dados em formato csv usamos o método to_csv(),veja:
>> df.to_csv('exemplo.csv', index=False)
Para ler e escrever em planilha excel usamos o read_excel() e to_excel(). No entanto, fórmulas e imagens não são lidos, apenas os valores numéricos, veja abaixo:
🚨 A presença de uma imagem em uma planilha pode gerar um erro na leitura do arquivo.
>> pd.read_excel('Excel_Sample.xlsx', sheet_name='Sheet1')
Unnamed: 0 a b c d
0 0 0 1 2 3
1 1 4 5 6 7
2 2 8 9 10 11
3 3 12 13 14 15
>> df.to_excel('Excel_Sample.xlsx',sheet_name='Sheet1')
O pandas pode ler informações de tabelas no formato HTML, por exemplo a leitura dos dados de um site do governo americano onde constam informações sobre bancos falidos pode ser obtida por meio da função read_html(), veja abaixo:
>> pd.read_html('https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list/')
Parabéns por ter chegado ao final de mais um conteúdo, em breve sairá a Parte 3 de 3 do post, explorando a biblioteca Matplotlib e Seaborn.
Até lá =D.
Adson Nogueira Alves, Pequisador de Inteligência Artificial no FIT, Instituto de Tecnologia, Doutorando em Ciências da Computação, Mestre em Engenharia Elétrica (2021), Engenheiro de Controle e Automação (2016). Atuação em Sistemas Embarcados, Robótica, Inteligência Artificial, Sistemas distribuídos, Automação e Controle
Apoio: Ministério da Ciência, Tecnologia e Inovações, com recursos da Lei nº 8.248, de 23 de outubro de 1991, no âmbito do PPI-SOFTEX, coordenado pela Softex e publicado Residência em TIC 03 — Aditivo, DOU 01245.013770/2020–64.