Hoje vamos aprender algumas coisas que podem ser feitas quando se trabalha com dataframes no Python. Como filtrar uma base? Como converter textos para números? Como extrair um valor de moeda no formato texto para o formato numérico? Como obter as estatísticas descritivas? Como criar novas colunas? Como traçar um histograma? Como localizar valores nulos e preenchê-los com a média da coluna? Tudo isso e muito mais no post abaixo!
Primeiro, baixe a base de dados neste link Kaggle – Fifa 19.
Agora, vamos começar a nossa análise. Os comentários serão curtos, pois penso que o código é auto-explicativo e colocarei alguns prints para facilitar o entendimento do que está sendo feito. Por ser um post longo não será possível também comentar cada detalhe. Fique à vontade para me escrever caso surja qualquer dúvida.
Primeiro, você pode verificar em qual diretório do seu computador você está trabalhando. Isto é, se você for salvar um gráfico, por exemplo, sem passar endereço algum, só passando o nome do arquivo, você irá salvar nesse diretório que chamamos de working directory e você pode verificar utilizando a função getcwd() do pacote os:
import os os.getcwd()
Se você não estiver trabalhando no diretório que gostaria, utilize chdir() para trocar o endereço:
os.chdir("/Users/nomedoseuusuario/DataScience/Fifa19")
Em seguida, importamos o arquivo Fifa19.csv utilizando a biblioteca Pandas:
# carrega biblioteca Pandas import pandas as pd # carrega o arquivo com o nome df (abreviacao de dataframe) df = pd.read_csv('fifa19.csv', header=0, index_col=0) # verifica as primeiras 5 linhas do arquivo df.head(5)
Você pode começar utilizando a função describe() para ter uma ideia do que está na sua base. Essa função retorna algumas estatísticas descritivas para cada campo:
df.describe()
Se você quisesse saber somente os formatos de cada campo, poderia utilizar o info():
df.info()
Veja que a coluna peso está aparentemente no formato texto e possui as letras “lbs” em frente ao número. Queremos converter este valor para o formato numérico. Para isso, eu escolhi criar uma nova coluna chamada Weight_New, excluindo as letras “lbs”. Em seguida, converti para o formato numérico. Veja na imagem o que foi feito e em seguida o código:
# CONVERTE COLUNA PESO PARA NUMERO (PRECISA EXCLUIR O LBS DA CELULA) # Ve qual tipo da variavel da coluna df['Weight'].dtypes # Converte para formato string df['Weight'] = df['Weight'].astype('str') # Cria uma coluna nova para peso df['Weight'].head() # Apaga 'lbs' da coluna peso # precisa de uma funcao da biblioteca re import re df['Weight_New'] = df['Weight'].map(lambda x: re.sub(r'lbs', '', x)) # Coloca 99999 para quem tem nan (depois ver o que fazer com esses casos) df['Weight_New'] = df['Weight_New'].map(lambda x: re.sub(r'nan', '99999', x)) # Converte Weight_New de string pra numeric df['Weight_New']=pd.to_numeric(df.Weight_New) df['Weight_New'].dtypes
Agora, precisamos também tratar o campo “Release Clause“, pois quero utilizá-lo como sendo o valor de mercado do atleta. Confesso que essa parte deu um certo trabalho, então é válido também tentar encontrar um meio mais eficiente. Bom, o que eu fiz exatamente?
Eu criei duas colunas, Valor_M e Valor_K e se o campo Valor fosse em milhões, eu passava para Valor_M, se fosse em milhares, passava o valor para Valor_K. Os locais não preenchidos, eu colocava 0. Em seguida, somava os valores da coluna Valor_M*1000 com Valor_K e criava o campo Valor_Final(K), pois sempre teríamos uma coluna preenchida e uma outra recebendo 0. Então, criei um campo numérico chamado Valor_Final(K) que era o valor da Release Clause, em milhares de euros.
# CRIA UMA COLUNA CHAMADA VALOR_FINAL(K) COM OS VALORES DA RELEASE CLAUSE EM MILHARES # Codigo para filtrar e conferir se é K minúsculo ou maiúsculo (verifico se contem k minusculo ou maiusculo) df[df['Release Clause'].str.contains("K",na = False)] # Converte o campo Release Clause para string (sim, precisa fazer isso) df['Release Clause'] = df['Release Clause'].astype('str') # Se for valor em M, coluna milhoes recebe o valor e a coluna mil nao df.loc[df['Release Clause'].str.contains('M'), 'Valor_M'] = df['Release Clause'].map(lambda x: re.sub(r'M', '', x)) df.loc[~df['Release Clause'].str.contains('M'), 'Valor_M'] = 0 # Agora faz o contrario e cria coluna Valor_K df.loc[df['Release Clause'].str.contains('K'), 'Valor_K'] = df['Release Clause'].map(lambda x: re.sub(r'K', '', x)) df.loc[~df['Release Clause'].str.contains('K'), 'Valor_K'] = 0 # Converte os campos criados para string (sim, precisa fazer isso) df['Valor_M'] = df['Valor_M'].astype('str') df['Valor_K'] = df['Valor_K'].astype('str') # Agora, tira o símbolo da moeda das colunas df['Valor_M'] = df['Valor_M'].map(lambda x: re.sub(r'€', '', x)) df['Valor_K'] = df['Valor_K'].map(lambda x: re.sub(r'€', '', x)) # Converte as duas colunas para numero df['Valor_M']=pd.to_numeric(df.Valor_M) df['Valor_K']=pd.to_numeric(df.Valor_K) # Agora, soma Valor_M * 1000 com Valor_K df['Valor_Final(K)'] = df['Valor_M']*1000 + df['Valor_K'] # Verifica os ultimos valores da tabela, para validar df.tail()
Como eu vou calcular algumas correlações, preciso separar uma base só com as variáveis numéricas. Caso contrário, a função corr() vai retornar um erro:
# Seleciona somente as colunas numéricas numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] df_numeric = df.select_dtypes(include=numerics) df_numeric = df_numeric.drop(['ID'], axis=1) # verifica quais sao as colunas da nova base list(df_numeric.columns.values)
Agora sim vamos começar analisando os jogadores. Primeiro, quero entender qual característica do jogador tem maior peso no Overall. Vou então pegar as correlações cujo módulo seja maior que 0.25 e ordená-las:
# Verifica correlacao de valor final com as demais corr = df_numeric[list(df_numeric.columns.values)].corrwith(df_numeric['Overall']) # Verifica quais variaveis tem correlacao modulo acima de 0.25 # Ou seja, maior que 0.25 ou menor que -0.25 corr[abs(corr) > 0.25] # ordena as correlacoes encontradas acima corr[abs(corr) > 0.25].sort_values(kind="quicksort")
Curiosamente, Reactions, Composure, Potential e Special tem alta correlação com Overall. Acredito que sejam características importantes para o jogador, mas aí precisa entender o jogo.
Fiquei curioso para saber no que o peso do jogador impacta:
corr = df_numeric[list(df_numeric.columns.values)].corrwith(df_numeric['Weight_New']) corr[abs(corr) > 0.25].sort_values(kind="quicksort")
Bom, o peso é bom para a força, mas para a maioria das características não, principalmente no que diz respeito à agilidade.
Para criar uma matriz de correlação bem organizada, utilize o seguinte script:
# PARA CRIAR UMA MATRIZ DE CORREL # Coloca limite para o numero de linhas que aparece pd.options.display.max_rows = None # Matriz de Correlacao corr = df_numeric.corr() corr.shape # Coloca NA na diagonal e no triang inferior corr_triu = corr.where(~np.tril(np.ones(corr.shape)).astype(np.bool)) corr_triu # Seleciona só correlações acima de 0.3 corr_triu = corr_triu.stack() corr_triu[corr_triu > 0.3] # Se quiser deixar melhor visualmente corr_triu.name = 'Pearson Correlation Coefficient' corr_triu[corr_triu > 0.3].to_frame()
O comando pd.options.display.max_rows = None serve para indicar o limite de linhas que você quer mostrar. Se você colocar None, nenhuma linha será ocultada. De forma análoga, pd.options.display.max_columns = None faz com que nenhuma coluna seja ocultada.
Podemos ainda criar o famoso mapa de calor (heatmap):
# Cria HeatMap %matplotlib notebook import matplotlib.pyplot as plt from pandas import DataFrame import numpy as np import seaborn as sns plt.figure() # plota heatmap ax = sns.heatmap(corr.T) # turn the axis label for item in ax.get_yticklabels(): item.set_rotation(0) for item in ax.get_xticklabels(): item.set_rotation(90) plt.savefig("heatmap.png") # show figure plt.show()
Atenção: Tome cuidado se for somente copiar e colar o código, pois o espaçamento de cada for pode ser perdida e causar um erro.
Além disso, veja que a legenda do mapa de calor ficou cortada por conta do tamanho. Para alterar o tamanho de um gráfico, use o figsize, por exemplo: plt.figure(figsize=(12,12))
Agora, vamos analisar os clubes.
Eu gostaria de saber quais são os clubes mais fortes de acordo com o Overall. Ou seja, vou ver quais são os 10 clubes com maior média de Overall. O transform(‘size’) serve para trazer somente clubes com mais de 1 jogador (não faz sentido agora, mas vai fazer quando analisarmos os países)
# Clubes mais fortes df[df.groupby('Club')['Overall'].transform('size') > 1].groupby(['Club'])['Overall'].mean().nlargest(10)
Será que os clubes mais fortes são os mais caros?
Bom, podemos verificar isso somando os valores dos jogadores e agrupando por clube:
# Clubes mais caros df[df.groupby('Club')['Overall'].transform('size') > 1].groupby(['Club'])['Valor_Final(K)'].sum().nlargest(10)
Interessante notar que os clubes mais fortes não são os melhores.
Você poderia ainda contar quantos jogadores há em cada clube, pois esse poderia ser o motivo de alguns clubes serem mais caros, mesmo não sendo melhores. Como muitos clubes tem o mesmo número de jogadores, não vou verificar somente os 10 primeiros, mas sim os 50:
# Qtd de jogadores por clube df[df.groupby('Club')['Overall'].transform('size') > 1].groupby(['Club'])['Valor_Final(K)'].count().nlargest(50)
Como você pode ver não é o caso de Chelsea e outros clubes caros terem mais jogadores. Todos os clubes dos rankings anteriores têm 33 jogadores.
Bom, agora vamos filtrar somente os 10 melhores clubes – a nova base se chamará df_club:
# Filtra clubes top 10 club = df[df.groupby('Club')['Overall'].transform('size') > 1].groupby(['Club'])['Overall'].mean().nlargest(10) df_club = df.loc[df['Club'].isin(club.reset_index()['Club'])] # Verifica se filtrou da forma correta df_club.Club.unique()
Em seguida, vamos ver qual é o melhor em cada característica:
pd.options.display.max_columns = None pd.options.display.max_rows = None # Verifica a media de cada caracteristica por clube df_club.groupby('Club').mean() # Obtem o melhor em cada criterio df_club.groupby('Club').mean().idxmax()
Parece que a Juventus se sobressai em quase tudo.
Agora, vamos analisar os países. Neste caso, temos países com poucos jogadores e isso pode distorcer o ranking e prejudicar países como o Brasil que tem vários jogadores, dentre eles, vários pernas de pau. Portanto, vou trazer o ranking inluindo somente países com mais de 250 jogadores:
# Paises com maior media Overall (incluindo soh quem tem mais de 250 jogadores) df[df.groupby('Nationality')['Overall'].transform('size') > 250].groupby(['Nationality'])['Overall'].mean().nlargest(25)
Veja que surpresa a Turquia estar no ranking e a Inglaterra não. Isso me incomodou. Imagino que o problema seja a distribuição dos jogadores ingleses, deve ter alguns pernas de pau puxando a média do país para baixo. Uma forma de verificar isso é usando os quartis:
print("Quartile England") print(df[df.Nationality=='England'].Overall.quantile([.1, .25, .5, .75, .99])) print("") print("Quartile Turkey") print(df[df.Nationality=='Turkey'].Overall.quantile([.1, .25, .5, .75, .99]))
Ou então verificando o desvio padrão do Overall:
# Desv Padrao do Overall df[df.groupby('Nationality')['Overall'].transform('size') > 250].groupby(['Nationality'])['Overall'].std().nlargest(25)
Ao que tudo indica, Inglaterra realmente possui valores mais dispersos para o Overall. Podemos fazer uma checagem olhando para o seu gráfico também:
%matplotlib notebook import matplotlib.pyplot as plt import numpy as np plt.figure() plt.title("Histograma Inglaterra") df[df.Nationality=='England'].Overall.hist(bins=20) plt.savefig("histEngland.png")
plt.figure() plt.title("Histograma Turquia") df[df.Nationality=='Turkey'].Overall.hist(bins=20) plt.savefig("histTurkey.png") plt.show()
Bem, realmente é isso. A Inglaterra consegue formar um grupo de 11 jogadores mais forte que a Turquia. Porém, quando olhamos para todos os ingleses, a média acaba sendo pior.
Eu não tratei os valores nulos (missings) como deveria lá no começo. Deixei inclusive um trecho “em espera”. Nunca faça isso. Inclusive, para não passar em branco, há duas coisas muito simples que você pode fazer no que diz respeito a valores nulos. Primeiro, calcular quantos existem em cada coluna:
df.isnull().sum
Em seguida, para valores contínuos, preencher esses campos nulos com a média da coluna. Veja o exemplo abaixo e replique para as demais variáveis:
df['ShortPassing'].fillna(df['ShortPassing'].mean(), inplace = True) df['Volleys'].fillna(df['Volleys'].mean(), inplace = True) df['Dribbling'].fillna(df['Dribbling'].mean(), inplace = True) df['Curve'].fillna(df['Curve'].mean(), inplace = True) df['FKAccuracy'].fillna(df['FKAccuracy'], inplace = True)
Creio que para um primeiro momento isso é tudo. Já foi possível ter vários insights com o que fizemos até aqui. A base está pronta para você fazer quaisquer análises, traçar outros gráficos e até criar alguns modelos. Espero que tenham aproveitado!
UPDATE: Encontrei um método melhor para extrair “lbs” de Weight:
def extract_value_from(value): out = value.replace('lbs', '') return float(out) df['Weight'] = df['Weight'].apply(lambda x : extract_value_from(x))
Pessoal, a formatação do WordPress é bem ruim para os códigos de Python. Peguei vários erros aqui na hora de colar e fiz uma checagem no fim, mas nunca se sabe o que pode ter passado. Portanto, se aparecer algum caractere esquisito no meio dos códigos, eu ficaria muito grato de ser alertado. Adicionalmente, TENHAM CUIDADO COM A IDENTAÇÃO (ESPAÇAMENTO!). Se der erro, tente não copiar e colar, mas escreva você o código para que o Python dê os devidos espaços quando necessário.
Gostou do conteúdo? Se inscreva para receber as novidades! Deixe seu e-mail em INSCREVA-SE na barra à direita, logo abaixo de pesquisar. E, por favor, não deixe de comentar, dar seu feedback e compartilhar com seus amigos. De verdade, isso faz toda a diferença. Você também pode acompanhar mais do meu trabalho seguindo a conta de Twitter @EstatSite ou por alguma das redes que você encontra em Sobre o Estatsite / Contato, como meu canal de Youtube Canal do Yukio.
Bons estudos!
Arquivo de dados tá com erro
Obrigado, Saulo. Atualizei o link e agora está correto. Forte abraço!
Muito legal, mano!
Parabéns pelo trabalho no site 🙂
Valeu, man! Se der, segue lá no Twitter @unidosdados e/ou no insta @universidadedosdados. Abraços!