Junção de Dados com Pandas em Python

Junção de Dados com Pandas em Python

O pacote Pandas é uma ferramenta poderosa para manipular e transformar dados em Python, ao trabalhar em uma análise, os dados necessários podem estar em várias tabelas. Mesclar tabelas torna-se uma ação necessária em diversos casos, facilitando a manipulação dos dados.

Em muitas aplicações, os dados podem estar espalhados em vários arquivos ou bancos de dados, ou podem estar organizados em um formato que não seja fácil de analisar.

McKinney, Wes. Python para análise de dados. Novatec Editora.

Uma parte do conteúdo aqui utilizado, caso queira mais detalhes, aconselho também utilizar referências de Manipulação de Dados com Pandas — Parte I como base para entendimento de alguns métodos e mais possibilidades para manipulação dos dados.

Dataset

O futebol americano universitário é uma das fascinações mais duradouras da cultura americana. Seus rankings de TV rotineiramente dominam as programações de TV do outono. . O NCAA tem um site de estatísticas, mas não contém todas as informações da equipe e usa muitas siglas que são obscuras.

Com os dados disponíveis, fui em frente e analisei as estatísticas do time para cada temporada de futebol universitário de 2013 até o presente.

Conteúdo Dentro dos dados estão as estatísticas de todos os times de nível FBS no ano da temporada universitária, incluindo ofensiva, defensiva, turnover, redzone, times especiais, first down, third down e quarta down estatísticas. Existem cerca de 145 estatísticas de equipes diferentes que podem ser usadas.

Todas essas informações são graças ao site de estatísticas da NCAA, que torna os dados fáceis de usar e encontrar. Veja mais aqui: https://www.ncaa.com/stats/football/fbs

Utilizei o ambiente do Colab Google para execução do script.

# instalar o pacote Kaggle que será usado para importar os dados.
!pip install kaggle
# salve o arquivo json com suas credenciais em seu computador e envie esse arquivo para o Colab 
from google.colab import files
files.upload()
# O cliente da API Kaggle espera que o arquivo json esteja na pasta ~ / .kaggle, então vamos criar uma nova pasta e movê-la para dentro
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
# Visualizar asquivo(s) importados
!ls
# Descompactar arquivos zipados
import zipfile
zip_ref = zipfile.ZipFile("college-football-team-stats-2019.zip", "r")
zip_ref.extractall("files")
zip_ref.close()

Importando Bibliotecas

!pip install inflection
import pandas as pd
import inflection
import numpy as np
import math
import seaborn as sns
import matplotlib.pyplot as plt
# Carregar dados
df13 = pd.read_csv("/content/files/cfb13.csv")
df14 = pd.read_csv("/content/files/cfb14.csv")
df15 = pd.read_csv("/content/files/cfb15.csv")
df16 = pd.read_csv("/content/files/cfb16.csv")
df17 = pd.read_csv("/content/files/cfb17.csv")
df18 = pd.read_csv("/content/files/cfb18.csv")
df19 = pd.read_csv("/content/files/cfb19.csv")
df20 = pd.read_csv("/content/files/cfb20.csv")
print(df13.shape, df14.shape, df15.shape, df16.shape, df17.shape, df18.shape, df19.shape, df20.shape)

(111, 146) (113, 146) (115, 146) (116, 152) (129, 152) (129, 152) (130, 152) (127, 152)

Temos oito diferentes DataFrames gerados, um para cada ano de 2013 até 2020. Podemos observar com o método .shape diferens números de linhas e colunas para cada um.

Vamos usar esses datasets como base para a maioria dos exemplos de métodos e manipulações.

df19.head(3)
# Incluir a coluna Year para identificar o ano de origem
df13["Year"] = 2013
df14["Year"] = 2014
df15["Year"] = 2015
df16["Year"] = 2016
df17["Year"] = 2017
df18["Year"] = 2018
df19["Year"] = 2019
df20["Year"] = 2020

Criação de alguns DataFrames com redução de variáveis, para exemplificação e aplicação de métodos, como abaixo criado:

# Selecionar as 5 primeiras colunas de cada datset
df13_test = df13.iloc[:, 0:5]
df13_test

Inner join

A mesclagem pega o primeiro DataFrame com o segundo, o argumento “on” informamos qual a coluna desejada.

# Vamos reduzir 2 Dataframes para o uso desse método:
df13_red = df13.iloc[:, 0:5]
df14_red = df14.iloc[:, 0:5]
df14_red.head()
# Junção com inner join
df_inner_join = df13_red.merge(df14_red, on="Team")
df_inner_join.head()
# Inner join considerando sufixos para a junção das colunas
df_inner_join = df13_red.merge(df14_red, on="Team", suffixes=("_2013", "_2014"))
df_inner_join.head()

O shape do DataFrame passou de 5 para 9 colunas, sendo a “Team” considerada para a junção entre as tabelas.

print(df13_red.shape, df14_red.shape)
print(df_inner_join.shape)
(111, 5) (113, 5)
(101, 9)

Relações

One-to-One — cada registro/linha da tabela à esquerda estáá relacionada a uma única linha da tabela da direito — exemplo anterior inner join

One-to_Many — cada linha da tabela da esquerda está relacionada a uma ou mais linhas da tabela da direita

Mesclar muitos DataFrame

# Criação DataFrame reduzido
df15_red = df15.iloc[:, 0:5]
# Mesclando três DataFrames
df_merg_mult_table = df13_red.merge(df14_red, on="Team", suffixes=("_2013", "_2014")) \
.merge(df15_red,on="Team")
df_merg_mult_table.head()

Observando os DataFrames antes da junção, podemos verificar o comportamento do método.

df13_red.head()
df14_red.head()

Left join

A junção à esquerda retorna todas as linhas de dados da tabela à esquerda e apenas as linhas da tabela à direita onde as colunas principais possuem correspondência.

# Redução dataframe para 5 linhas
df13_red_5row = df13_red.head()
# Criar uma nova coluna Id
df13_red_5row["Id"] = (1, 2, 5, 1, 2)
# Criar nova DataFrame com 2 colunas e mudar valores da coluna Id
df13_red_5rowid = df13_red_5row.loc[: , ["Team", "Id"]]
df13_red_5rowid["Id"] = (5, 5, 3, 4, 5)
# Junção à esquerda
df13red5row_df13red5rowid = df13_red_5row.merge(df13_red_5rowid, on="Id", how="left")
df13red5row_df13red5rowid

Right Join

eft_on e right_on — eles nos permitem dizer à mesclagem quais colunas-chave de cada tabela devem mesclar as tabelas.

# Junção à direita
df13red5rowid_df13red5row = df13_red_5row.merge(df13_red_5rowid, on="Id", how="right", )
df13red5rowid_df13red5row

Outer Join

Uma junção externa retornará todas as linhas de ambas as tabelas, independentemente se houver uma correspondência entre as tabelas.

outer = df13_red_5row.merge(df13_red_5rowid, on="Id", how="outer", suffixes=("_row", "_id"))
outer
# Retorno apenas dos dados de Team_id que são ausentes (NaN)
only_id = outer[outer["Team_id"].isnull()]
only_id

Mesclando índice (index)

merge_index = pd.read_csv("/content/files/cfb13.csv", index_col=["Team"])
merge_index.head()

Mesclando Dados Ordenados e Séries Temporais

# Criando novo conjunto de dados DataFrame
temp1 = [("2019-01-01", 12000), ("2018-01-01", 10000), ("2017-01-01", 12000), ("2016-01-01", 11000)]
df_temp1 = pd.DataFrame(temp1, columns=["date", "value"])
# Convertendo de int64 para datetime
df_temp1["date"] = pd.to_datetime(df_temp1["date"])
df_temp1
# Criando novo conjunto de dados DataFrame
temp2 = [("2021-01-01", 18000), ("2020-01-01", 13000), ("2019-01-01", 21000), ("2018-01-01", 23000), ("2017-01-01", 12000), ("2016-01-01", 11000)]
df_temp2 = pd.DataFrame(temp2, columns=["date", "value"])
# Convertendo de int64 para datetime
df_temp2["date"] = pd.to_datetime(df_temp2["date"])
df_temp2

Método merge_ordered()

  • Método para mesclar séries temporais e outros dados ordenados.
  • O padrão para o método merge é “interno”, e “externo” para o método merge_order
# O argumento sufixos é para determinar a tabela de origem dos dados
temp1_2 = pd.merge_ordered(df_temp1, df_temp2, on="date", suffixes=("_temp1", "_temp2"))
temp1_2

Forward fill

# preenchimento progressivo irá interpolar os dados perdidos preenchendo os valores ausentes com o valor anterior
temp1_2 = pd.merge_ordered(df_temp1, df_temp2, on="date", suffixes=("_temp1", "_temp2"), fill_method="ffill")
temp1_2

Método merge_asof ()

  • Recurso semelhante a merge_ordered (), ao contrário de uma junção à esquerda ordenada, merge_asof () irá corresponder às colunas de valor mais próximas ao invés de valores iguais.
  • Importante — quaisquer colunas nas quais você mesclar devem ser classificadas
  • Usar em um processo que as datas ou horas podem não estar exatamente alinhadas ou um conjunto de treinamento de série temporal.
temp1_asof = [("2019-01-01 10:00:00", 120), ("2019-01-01 12:00:00", 100), ("2019-01-01 14:00:00", 120), ("2019-01-01 16:00:00", 110)]
df_temp1_asof = pd.DataFrame(temp1_asof, columns=["date", "value"])
df_temp1_asof["date"] = pd.to_datetime(df_temp1_asof["date"])
df_temp1_asof
temp2_asof = [("2019-01-01 14:00:00", 180), ("2019-01-01 16:00:00", 130), ("2019-01-01 18:00:00", 210), ("2019-01-01 20:00:00", 230), ("2019-01-01 22:00:00", 120), ("2019-01-01 22:30:00", 110)]
df_temp2_asof = pd.DataFrame(temp2_asof, columns=["date", "value"])
df_temp2_asof["date"] = pd.to_datetime(df_temp2_asof["date"])
df_temp2_asof
# O argumento sufixos é para determinar a tabela de origem dos dados
temp_asof = pd.merge_asof(df_temp2_asof, df_temp1_asof, on="date", suffixes=("_one", "_two"))
temp_asof
# Calcula a diferença de um elemento do Dataframe em comparação com outro
# elemento no Dataframe (o padrão é o elemento na linha anterior).
temp_asof_diffs = temp_asof.diff()
temp_asof_diffs
temp_asof_diffs.plot(y=["value_one", "value_two"])
plt.show()

Seleção de dados com .query()

O método query () aceita uma string de entrada que será usada para selecionar as linhas a serem retornadas do dataset.

df20.head()
# Times com as maiores vitórias e sem derrotas
df20.query('Win > 9 and Loss < 1')
# Times com derrotas menores que 2 ou com jogos menores que 3
df20.query('Loss < 2 or Games < 3')

Quando trata-se de strings o parâmetro todo vem em aspas simples ‘ ‘

e a string (texto) em aspas duplas ” “.

# Buffalo não corresponde a string encontrada
df20.query('Team=="Buffalo" or Team=="Ohio (MAC)"')

Remodelar Dados .melt()

df13_red.head()
# id_vars será a coluna mantida
df13_red_melt = df13_red.melt(id_vars=["Team"])
df13_red_melt.head(10)
# var_name irá nomear a coluna e value_name nomeará a coluna com valores
df13_red_melt = df13_red.melt(id_vars=["Team"], var_name=["Statistics"], value_name="Number")
df13_red_melt.head(10)

Mesclagens e Concateções Avançadas

Concatenando DataFrames — verticalmente

Os dados terão virão de diversas tabelas e será necessário juntos os diversos dataset.

# Cada DataFrame trás seu index, tendo assim duplicidade
df_all = pd.concat([df13, df14, df15, df16, df17, df18, df19, df20])
df_all.shape
(970, 154)
df_all
# O parâmetro ignore_index faz índice sequencial para todo DataFrame
# o parâmetro key nomeia o "multinível" de índice.
df_all2 = pd.concat([df13, df14, df15, df16, df17, df18, df19, df20], 
ignore_index=False,
keys=["2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020"])
df_all2.shape
(970, 154)
df_all2
# Dados com agrupamento por Year
df_all2.groupby("Year")[["Games", "Win", "Loss"]].sum()
# Total de jogos, vitórias e derrotas por ano com algumas estatísticas
df_all2.groupby("Year")[["Games", "Win", "Loss"]].agg([min, max, sum])

Método .append

  • Append é um método concat simplificado, em comparação.
  • Suporta os parâmetros ignore_index e sort.
  • Não oferece suporte a chaves ou junção e a junção (join) é por padrão externo.
df13_append = df13.append([df14, df15, df16, df17, df18, df19, df20],
ignore_index=True)
df13_append

Reformatação com indexação hierárquica

  • stack Faz a “rotação” ou o pivoteamento das colunas dos dados para as linhas.
  • unstack Faz o pivoteamento das linhas para as colunas.
df13_red.stack()
df13_red.unstack()

Aqui atuamos com diversas formas de junção de diferentes datasets, útil para dados de diferentes formatos com ou sem relação entre as variáveis. Entender o relacionamento as as junções possibilitam o agrupamento e manipulação com maiores possibilidades e diversidade.

Referências:

Tags:

Sobre o Autor

Vitor Diego Ramos
Vitor Diego Ramos

Sólida experiência em liderança de equipes, desenvolvimento de negócios e apaixonado por tecnologia - data science e desenvolvimento front-end. Atuo ativamente em causas de voluntariado, projetos de tecnlogia, mentoria de aprendizado e produtividade e organização.

0 Comentários

Deixe uma resposta

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