Tutorial: Limpeza e Análise de Dados com Python

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!

5 comentários em “Tutorial: Limpeza e Análise de Dados com Python”

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *