Introdução a Bibliotecas para Ciência de Dados - Parte 2 de 3

Introdução a Bibliotecas para Ciência de Dados - Parte 2 de 3

Libraries For Data Science — Pandas

Figura 1: Logo “pandas” I Fonte:

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        
Figura 2: Resultado do código aplicado I Fonte: Dados do Autor

💡 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        
Figura 3: Resultado do código aplicado I Fonte: Dados do Autor

💡 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        
Figura 4: Resultado do código aplicado I Fonte: Dados do Autor

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        
Figura 5: Resultado do código aplicado I Fonte: Dados do Autor

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        
Figura 6: Resultado do código aplicado I Fonte: Dados do Autor

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        
Figura 7: Resultado do código aplicado I Fonte: Dados do Autor

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).

🚨 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/')        
Figura 8: Resultado I Fonte: Dados do Autor

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.


Entre para ver ou adicionar um comentário

Outras pessoas também visualizaram

Conferir tópicos