Libraries For Data Science - Pandas
By Adson Nogueira Alves
Introdução a Bibliotecas para Ciência de Dados - Parte 2 de 3
Fonte: Pandas.pydata.org
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:
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 pandas as pd
Série
O primeiro objeto que iremos estudar será a Serie. Podemos definir como uma lista de valores, semelhante as arrays estudadas no numpy, com a diferença que trabalhamos agora com estruturas rotuladas, ou seja, temos um rótulo atribuido 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, atribuimos a variável nomes uma lista de nomes aleatórios e a variável arr uma array de valor escalonados, etapa não obrigatória, porém útil.
Caso você não atribua uma lista de rótulos para a váriavel index, será gerado automaticamente rótulos sequênciais de [0 … n]. No nosso exemplo, os valores contidos na variável nomes serão nosso indices.
A seleção dos dados pode ocorrer de duas formas, através dos rótulos atribuidos, 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é definido, 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 -1
Carla -25
Everton -30
Humberto -9
Fabio -10
Daniel -20
Gabi -19
Iago -21
Jose -9
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 indice definido, no nosso caso, os nomes. Ou seja, mesmo que os indice esteja fora de ordem não haverá incoerência relacional dos dados.
>> ser + ser1
Adson -1
Bruno 10
Carla 41
Daniel 79
Everton 102
Fabio 155
Gabi 179
Humberto 222
Iago 243
Jose 288
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 -14
B -7
Bruno -20
Adson -10
Carla -16
Everton -24
Humberto -3
Fabio -24
Daniel -22
Gabi -7
Iago -2
Jose -21
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 -10.0
B NaN
Bruno 13.0
Carla 50.0
Daniel 77.0
Everton 108.0
Fabio 141.0
Gabi 191.0
Humberto 228.0
Iago 262.0
Jose 276.0
dtype: float64
💡 Observe, a operação não ocorreu nos valores exclusivos (A e B), porém os rótulos foram retornados, sendo atribuido NaN a cada um, referênciando 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
0 1
A NaN -14
Adson 0.0 -10
B NaN -7
Bruno 33.0 -20
Carla 66.0 -16
Daniel 99.0 -22
Everton 132.0 -24
Fabio 165.0 -24
Gabi 198.0 -7
Humberto 231.0 -3
Iago 264.0 -2
Jose 297.0 -21
💡 Atenção para todos os argumentos utilizados e a sintaxe.
O argumento objs, recebe uma lista referente as 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), útil 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)
[[1341 250 1038 2981]
[ 797 2050 1513 197]
[1756 1736 239 2390]
[1262 1394 661 866]
[2985 1444 1474 798]]
['Adson', 'Luiz', 'Ana', 'Maria', 'Cris']
['SalBase', 'Ferias', 'Com', '13º']
💡 Os valores gerados pelo método random do NumPy provavelmente iram divergir, afinal estamos trabalhando com valores pseudo-randômicos. Para gerar o DataFrame utilizamos a função pandas.DataFrame() atribuindo essas infomações, veja:
>> df = pd.DataFrame(data=dt, index=ind, columns=col)
>> df
SalBase Ferias Com 13º
Adson 1341 250 1038 2981
Luiz 797 2050 1513 197
Ana 1756 1736 239 2390
Maria 1262 1394 661 866
Cris 2985 1444 1474 798
💡 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 metódos .loc() e .iloc() para seleção de linhas, veja:
>> df.loc['Ana'] # apenas 1 dado
SalBase Ferias Com 13º
Ana 1756 1736 239 2390
>> df.loc[['Cris','Ana']] # filtro multiplas linhas
SalBase Ferias Com 13º
Cris 2985 1444 1474 798
Ana 1756 1736 239 2390
Filtros em colunas:
>> df['Com'] # apenas 1 coluna
>> df[['13º','Ferias']] # filtro multiplas colunas
13º Ferias
Adson 2981 250
Luiz 197 2050
Ana 2390 1736
Maria 866 1394
Cris 798 1444
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
SalBase Ferias Com 13º Desconto
Adson 1341 250 1038 2981 -64
Luiz 797 2050 1513 197 -269
Ana 1756 1736 239 2390 -217
Maria 1262 1394 661 866 -29
Cris 2985 1444 1474 798 -66
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
SalBase Ferias Com 13º Desconto
Adson 1341 250 1038 2981 -64
Luiz 797 2050 1513 197 -269
Ana 1756 1736 239 2390 -217
Maria 1262 1394 661 866 -29
Cris 2985 1444 1474 798 -66
Beatriz 3000 200 100 3000 -500
Informações importantes do conjunto de dados, como média, valor máximo, valor minimo, indice do maior valor, indice do menor valor, desvio padrão, variância, entre outros podem ser retirados facilmente, veja exemplos abaixo:
>> df['SalBase'].mean()
1856.8333333333333
>> df['SalBase'].max()
3000
>> df['SalBase'].min()
797
>> df['SalBase'].argmax()
5
>> df['SalBase'].argmin()
1
>> df['SalBase'].std()
930.9043810546101
>> df['SalBase'].var()
866582.9666666668
🚨 Caso receba o erro Erro -> TypeError: unsupported operand type(s) for +: ‘int’ and ‘str utilize o metodo 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 Ferias e 13º. Inicialmente apresentamos as colunas completas
>> print(df['Ferias'])
Adson 250
Luiz 2050
Ana 1736
Maria 1394
Cris 1444
Beatriz 200
Name: Ferias, dtype: int64
>> print(df['13º'])
Adson 2981
Luiz 197
Ana 2390
Maria 866
Cris 798
Beatriz 3000
Name: 13º, dtype: int64
Observe como é feito o filtro dos valores em Ferias maior que 2000 e 13º menor que 2000, filtros independentes.
>> print(df[df['Ferias'] > 200])
SalBase Ferias Com 13º Desconto
Luiz 797 2050 1513 197 -269
>> print(df[df['13º'] < 2000])
SalBase Ferias Com 13º Desconto
Luiz 797 2050 1513 197 -269
Maria 1262 1394 661 866 -29
Cris 2985 1444 1474 798 -66
Para aplicar ambos os filtros em uma única seleção, temos:
>> print((df['Ferias'] > 2000) & (df['13º'] < 2000))
Adson False
Luiz True
Ana False
Maria False
Cris False
Beatriz False
dtype: bool
>> print(df[(df['Ferias'] > 2000) & (df['13º'] < 2000)])
SalBase Ferias Com 13º Desconto
Luiz 797 2050 1513 197 -269
💡 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:
>> df[df['SalBase'] > 1000]['Desconto']
Adson -64
Ana -217
Maria -29
Cris -66
Beatriz -500
Name: Desconto, dtype: int64
>> df[df['SalBase'] > 1000][['Ferias','Desconto']]
Ferias Desconto
Adson 250 -64
Ana 1736 -217
Maria 1394 -29
Cris 1444 -66
Beatriz 200 -500
>> df[(df['Ferias'] > 2000) & (df['13º'] < 2000)][['Desconto','Com']]
Desconto Com
Luiz -269 1513
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).
🚨 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:
>> df
SalBase Ferias Com 13º
Adson 246 2717 2188 2221
Luiz 2289 1423 1963 781
Ana 1483 1893 2648 1865
Maria 2578 74 1500 1494
Cris 2957 1423 2071 950
>> df.drop('13º', axis=1, inplace = True)
SalBase Ferias Com
Adson 246 2717 2188
Luiz 2289 1423 1963
Ana 1483 1893 2648
Maria 2578 74 1500
Cris 2957 1423 2071
Para excluir uma linha, basta alterar o parâmetro axis, veja:
>> df
SalBase Ferias Com
Adson 246 2717 2188
Luiz 2289 1423 1963
Ana 1483 1893 2648
Maria 2578 74 1500
Cris 2957 1423 2071
>> df.drop('Adson', axis=0, inplace = True)
SalBase Ferias Com
Luiz 2289 1423 1963
Ana 1483 1893 2648
Maria 2578 74 1500
Cris 2957 1423 2071
Manipulação de rotulos
Podemos resetar, alterar e até mesmo criar hierarquias de índices. Admita o DataFrame abaixo:
>> df
SalBase Ferias Com 13º
Adson 246 2717 2188 2221
Luiz 2289 1423 1963 781
Ana 1483 1893 2648 1865
Maria 2578 74 1500 1494
Cris 2957 1423 2071 950
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 1320 1343 20 2952
1 Luiz 298 356 2609 112
2 Ana 2688 1914 770 1369
3 Maria 1485 1292 2735 1569
4 Cris 472 471 2745 2751
Observe que foram atribuidos indices sequênciais [0 … n] enquanto que os antigos indices se tornaram uma coluna de rótulo index 🚨 O atributo inplace mantem 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 1320 1343 20 2952
1 Luiz 298 356 2609 112
2 Ana 2688 1914 770 1369
3 Maria 1485 1292 2735 1569
4 Cris 472 471 2745 2751
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 1320 1343 20 2952
ind_2 Luiz 298 356 2609 112
ind_3 Ana 2688 1914 770 1369
ind_4 Maria 1485 1292 2735 1569
ind_5 Cris 472 471 2745 2751
💡 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 multiplos indices:
>> df = pd.DataFrame(np.random.randn(6, 2),
index=hier_index, columns=['A','B'])
>> df
A B
G1 1 0.302665 1.693723
2 -1.706086 -1.159119
3 -0.134841 0.390528
G2 1 0.166905 0.184502
2 0.807706 0.072960
3 0.638787 0.329646
Os nomes dos índices podem ser lidos e alterados por meio da propriedade: index.names, veja:
>> df.index.names
FrozenList([None, None])
>> df.index.names = ['Group','Num']
>> df
A B
Group Num
G1 1 0.302665 1.693723
2 -1.706086 -1.159119
3 -0.134841 0.390528
G2 1 0.166905 0.184502
2 0.807706 0.072960
3 0.638787 0.329646
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:
>> df.xs('G1')
A B
Group Num
G1 1 0.302665 1.693723
2 -1.706086 -1.159119
3 -0.134841 0.390528
>> df.xs(['G1',1])
A B
Group Num
G1 1 0.302665 1.693723
>> df.xs(1,level='Num')
A B
Group Num
G1 1 0.302665 1.693723
G2 1 0.166905 0.184502
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]})
>> 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()
>> df
A B C
0 False False False
1 False True False
2 True True False
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
>> df.dropna(axis=1)
C
0 1
1 2
2 3
>> df.dropna(thresh=2)
A B C
0 1.0 5.0 1
1 2.0 NaN 2
>> df.fillna(value='algo')
A B C
0 1.0 5.0 1
1 2.0 algo 2
2 algo algo 3
>> 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:
>> 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
>> 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
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 acima como é feito:
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 obrigatóriamente (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']})
>> left
key A B
0 K1 A0 B0
1 K4 A1 B1
2 K2 A2 B2
3 K3 A3 B3
>> 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.
>> 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
>> pd.merge(left, right, how='right', 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
3 K0 NaN NaN C0 D0
>> 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
>> 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'])
>> left
A B
K0 A0 B0
K1 A1 B1
K2 A2 B2
>> 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:
>> left.join(right)
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2
>> 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']})
>> 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.
>> df['col2'].unique()
array([444, 555, 666], dtype=int64)
>> df['col2'].nunique()
3
>> 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:
>> df['col1'].sum()
10
>> df['col1'].mean()
2.5
>> df['col1'].std()
1.2909944487358056
>> newdf = df[(df['col1'] > 2) & (df['col2'] == 444)]
>> newdf
col1 col2 col3
3 4 444 xyz
Métodos como describe(), info(), head() e tail() ajudam também na visualização de parâmetros estatisticos e auxiliam na visualização do dataframe, veja:
>> 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
>> 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
>> df.head(2)
col1 col2 col3
0 1 444 abc
1 2 555 def
>> 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:
>> df = pd.read_html('http://www.fdic.gov/bank/individual/failed/banklist.html')
>> df[0].head()
Bank Name City ST CERT Acquiring Institution Closing Date
0 Ericson State Bank Ericson NE 18265 Farmers and Merchants Bank February 14 2020
1 City National Bank of New Jersey Newark NJ 21111 Industrial Bank November 1 2019
2 Resolute Bank Maumee OH 58317 Buckeye State Bank October 25 2019
3 Louisa Community Bank Louisa KY 58112 K. F. Bank Corporation October 25 2019
4 The Enloe State Bank Cooper TX 10716 Legend Bank, N. A May 31 2019
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.