Problema¶
Motivação: gostaria de compreender o impacto do gênero do jogo nas vendas, analisando o total de unidades vendidas desde o ano de lançamento de cada título. Meu objetivo é identificar as tendências entre as diferentes categorias de jogos.
Hipótese: jogos de ação são mais vendidos do que jogos de simulação.
Dados¶
Necessitamos de uma base de dados com a quantidade de unidades vendidas por jogo desde o seu lançamento. Para esta análise, será utilizado o conjunto de dados Video Games Sales. Através do Kaggle, consegui rastrear a fonte desses dados como sendo obtidos pelo vgchartz, que é um site de monitoramento de vendas de jogos eletrônicos. Vale ressaltar que desde 2018 o vgchartz apenas registra dados oficiais de vendas de software disponibilizados por programadores e distribuidores (essa informação pode ser encontrada no site do vgchartz).
A base contém um único arquivo chamado video_games_sales.csv. Este arquivo possui as seguintes colunas:
- rank: posição do jogo em relação ao número total de cópias vendidas
- name: nome do jogo eletrônico
- platform: plataforma utilizada
- year: ano de lançamento
- genre: gênero do jogo
- publisher: nome da empresa responsável pela publicação e distribuição do jogo
- na_sales: número de cópias vendidas na América do Norte em milhões
- eu_sales: número de cópias vendidas na Europa em milhões
- jp_sales: número de cópias vendidas no Japão em milhões
- other_sales: número de cópias vendidas em outras áreas em milhões
- global_sales: número total de cópias vendidas em todas as áreas em milhões
Importações¶
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from scipy.stats import mannwhitneyu, shapiro, normaltest
Configurações iniciais¶
# Semente
RANDOM_STATE = 666
# Cores
BACKGROUND_COLOR = '#191622'
COLOR = '#868686'
BRANCO = '#fff'
# Fonte
FONT_FAMILY = 'Balto'
# Estilo pandas
plt.style.use('fivethirtyeight')
# Estilo gráficos
layout_padrao = dict(
# Gerais
width=1000,
height=600,
font=dict(
color=BRANCO,
family=FONT_FAMILY
),
margin=dict(
l=60,
r=30,
b=80,
t=50,
),
paper_bgcolor=BACKGROUND_COLOR,
plot_bgcolor=BACKGROUND_COLOR,
# Titulo
title_font_size=20,
title_x=0.5,
title_xanchor='center',
# Eixo X
xaxis=dict(
gridcolor=COLOR,
gridwidth=1,
zerolinecolor=COLOR,
zerolinewidth=2,
tickfont_size=14,
title_font_size=16,
),
# Eixo Y
yaxis=dict(
gridcolor=COLOR,
gridwidth=1,
zerolinecolor=COLOR,
zerolinewidth=2,
tickfont_size=14,
title_font_size=16,
)
)
Descrição da base de dados¶
df_video_game = pd.read_csv('assets/video_games_sales.csv')
df_video_game.head(10)
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | Wii Sports | Wii | 2006.0 | Sports | Nintendo | 41.49 | 29.02 | 3.77 | 8.46 | 82.74 |
| 1 | 2 | Super Mario Bros. | NES | 1985.0 | Platform | Nintendo | 29.08 | 3.58 | 6.81 | 0.77 | 40.24 |
| 2 | 3 | Mario Kart Wii | Wii | 2008.0 | Racing | Nintendo | 15.85 | 12.88 | 3.79 | 3.31 | 35.82 |
| 3 | 4 | Wii Sports Resort | Wii | 2009.0 | Sports | Nintendo | 15.75 | 11.01 | 3.28 | 2.96 | 33.00 |
| 4 | 5 | Pokemon Red/Pokemon Blue | GB | 1996.0 | Role-Playing | Nintendo | 11.27 | 8.89 | 10.22 | 1.00 | 31.37 |
| 5 | 6 | Tetris | GB | 1989.0 | Puzzle | Nintendo | 23.20 | 2.26 | 4.22 | 0.58 | 30.26 |
| 6 | 7 | New Super Mario Bros. | DS | 2006.0 | Platform | Nintendo | 11.38 | 9.23 | 6.50 | 2.90 | 30.01 |
| 7 | 8 | Wii Play | Wii | 2006.0 | Misc | Nintendo | 14.03 | 9.20 | 2.93 | 2.85 | 29.02 |
| 8 | 9 | New Super Mario Bros. Wii | Wii | 2009.0 | Platform | Nintendo | 14.59 | 7.06 | 4.70 | 2.26 | 28.62 |
| 9 | 10 | Duck Hunt | NES | 1984.0 | Shooter | Nintendo | 26.93 | 0.63 | 0.28 | 0.47 | 28.31 |
qtd_registros = df_video_game.shape[0]
qtd_atributos = df_video_game.shape[1]
print(f'A base possui {qtd_registros} registros e {qtd_atributos} atributos.\nAs colunas são: {", ".join(df_video_game.columns)}')
A base possui 16598 registros e 11 atributos. As colunas são: rank, name, platform, year, genre, publisher, na_sales, eu_sales, jp_sales, other_sales, global_sales
- A variável rank é uma variável qualitativa ordinal
- A variável name é uma variável qualitativa nominal
- A variável platform é uma variável qualitativa nominal
- A variável year é uma variável quantitativa discreta
- A variável genre é uma variável qualitativa nominal
- A variável publisher é uma variável qualitativa nominal
- A variável na_sales é uma variável quantitativa discreta
- A variável eu_sales é uma variável quantitativa discreta
- A variável jp_sales é uma variável quantitativa discreta
- A variável other_sales é uma variável quantitativa discreta
- A variável global_sales é uma variável quantitativa discreta
df_video_game.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 16598 entries, 0 to 16597 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 rank 16598 non-null int64 1 name 16598 non-null object 2 platform 16598 non-null object 3 year 16327 non-null float64 4 genre 16598 non-null object 5 publisher 16540 non-null object 6 na_sales 16598 non-null float64 7 eu_sales 16598 non-null float64 8 jp_sales 16598 non-null float64 9 other_sales 16598 non-null float64 10 global_sales 16598 non-null float64 dtypes: float64(6), int64(1), object(4) memory usage: 1.4+ MB
"""
Analisando ano de lançamento com valor nulo
"""
df_video_game.loc[df_video_game['year'].isna()]
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 179 | 180 | Madden NFL 2004 | PS2 | NaN | Sports | Electronic Arts | 4.26 | 0.26 | 0.01 | 0.71 | 5.23 |
| 377 | 378 | FIFA Soccer 2004 | PS2 | NaN | Sports | Electronic Arts | 0.59 | 2.36 | 0.04 | 0.51 | 3.49 |
| 431 | 432 | LEGO Batman: The Videogame | Wii | NaN | Action | Warner Bros. Interactive Entertainment | 1.86 | 1.02 | 0.00 | 0.29 | 3.17 |
| 470 | 471 | wwe Smackdown vs. Raw 2006 | PS2 | NaN | Fighting | NaN | 1.57 | 1.02 | 0.00 | 0.41 | 3.00 |
| 607 | 608 | Space Invaders | 2600 | NaN | Shooter | Atari | 2.36 | 0.14 | 0.00 | 0.03 | 2.53 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16307 | 16310 | Freaky Flyers | GC | NaN | Racing | Unknown | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16327 | 16330 | Inversion | PC | NaN | Shooter | Namco Bandai Games | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16366 | 16369 | Hakuouki: Shinsengumi Kitan | PS3 | NaN | Adventure | Unknown | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16427 | 16430 | Virtua Quest | GC | NaN | Role-Playing | Unknown | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16493 | 16496 | The Smurfs | 3DS | NaN | Action | Unknown | 0.00 | 0.01 | 0.00 | 0.00 | 0.01 |
271 rows × 11 columns
"""
É possível observar que alguns jogos incluem um ano em seus títulos. Em jogos de esportes, essa prática é comum e geralmente
o ano no título representa o ano seguinte ao lançamento efetivo do jogo. Por exemplo, o FIFA 2004 foi lançado no final de 2003.
Acredito que essa estratégia seja para manter o jogo atualizado, evitando a impressão de obsolescência. Se o jogo fosse lançado em outubro
de 2003 e o título tivesse o ano de 2003, na virada para 2004, poderia parecer desatualizado, apesar de ter sido lançado apenas alguns
meses atrás. Não sei se há um consenso claro sobre essa prática, mas parece haver uma tendência de atribuir o ano do título de acordo com a
proximidade do lançamento. Ou seja, para títulos lançados no ínicio do ano, utilizar no nome o ano em questão e títulos lançados no final
do ano, usar o ano seguinte.
Podemos verificar esse comportamente ao analisar os registros cujo título contém um ano e comparar com o valor do ano de lançamento.
"""
df_video_game.loc[df_video_game['name'].str.contains('\d{4}') & (df_video_game['genre'] == 'Sports') & ~df_video_game['year'].isna()]
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 238 | 239 | Madden NFL 2005 | PS2 | 2004.0 | Sports | Electronic Arts | 4.18 | 0.26 | 0.01 | 0.08 | 4.53 |
| 249 | 250 | Winning Eleven: Pro Evolution Soccer 2007 | PS2 | 2006.0 | Sports | Konami Digital Entertainment | 0.10 | 2.39 | 1.05 | 0.86 | 4.39 |
| 279 | 280 | Madden NFL 2003 | PS2 | 2002.0 | Sports | Electronic Arts | 3.36 | 0.21 | 0.01 | 0.56 | 4.14 |
| 323 | 324 | Mario & Sonic at the London 2012 Olympic Games | Wii | 2011.0 | Sports | Sega | 1.14 | 1.91 | 0.27 | 0.46 | 3.78 |
| 334 | 335 | FIFA Soccer 2005 | PS2 | 2004.0 | Sports | Electronic Arts | 0.58 | 2.48 | 0.04 | 0.59 | 3.70 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16420 | 16423 | Winning Eleven: Pro Evolution Soccer 2007 | PC | 2006.0 | Sports | Konami Digital Entertainment | 0.00 | 0.01 | 0.00 | 0.00 | 0.01 |
| 16426 | 16429 | ESPN NBA 2Night 2002 | XB | 2002.0 | Sports | Konami Digital Entertainment | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16441 | 16444 | G1 Jockey 4 2007 | PS2 | 2007.0 | Sports | Tecmo Koei | 0.00 | 0.00 | 0.01 | 0.00 | 0.01 |
| 16459 | 16462 | Rugby World Cup 2015 | PC | 2015.0 | Sports | Ubisoft | 0.00 | 0.01 | 0.00 | 0.00 | 0.01 |
| 16558 | 16561 | Pro Evolution Soccer 2008 | PC | 2007.0 | Sports | Konami Digital Entertainment | 0.00 | 0.01 | 0.00 | 0.00 | 0.01 |
511 rows × 11 columns
jogos_esporte_lancamento_nulo = df_video_game.loc[df_video_game['name'].str.contains('\d{4}') & (df_video_game['genre'] == 'Sports') & df_video_game['year'].isna()]
ranks = jogos_esporte_lancamento_nulo['rank'].values
jogos_esporte_lancamento_nulo
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 179 | 180 | Madden NFL 2004 | PS2 | NaN | Sports | Electronic Arts | 4.26 | 0.26 | 0.01 | 0.71 | 5.23 |
| 377 | 378 | FIFA Soccer 2004 | PS2 | NaN | Sports | Electronic Arts | 0.59 | 2.36 | 0.04 | 0.51 | 3.49 |
| 2586 | 2588 | PES 2009: Pro Evolution Soccer | PSP | NaN | Sports | Konami Digital Entertainment | 0.04 | 0.33 | 0.26 | 0.17 | 0.80 |
| 3501 | 3503 | Madden NFL 2002 | XB | NaN | Sports | Unknown | 0.53 | 0.02 | 0.00 | 0.03 | 0.58 |
| 4797 | 4799 | NFL GameDay 2003 | PS2 | NaN | Sports | Unknown | 0.20 | 0.15 | 0.00 | 0.05 | 0.40 |
| 5162 | 5164 | NBA Live 2003 | XB | NaN | Sports | Electronic Arts | 0.31 | 0.04 | 0.00 | 0.01 | 0.36 |
| 5669 | 5671 | All-Star Baseball 2005 | PS2 | NaN | Sports | Unknown | 0.16 | 0.12 | 0.00 | 0.04 | 0.32 |
| 5901 | 5903 | NBA Live 2003 | GC | NaN | Sports | Electronic Arts | 0.23 | 0.06 | 0.00 | 0.01 | 0.30 |
| 8929 | 8931 | All-Star Baseball 2005 | XB | NaN | Sports | Unknown | 0.11 | 0.03 | 0.00 | 0.01 | 0.15 |
| 15739 | 15742 | Football Manager 2007 | X360 | NaN | Sports | Sega | 0.00 | 0.01 | 0.00 | 0.00 | 0.02 |
| 15865 | 15868 | PDC World Championship Darts 2008 | DS | NaN | Sports | Unknown | 0.01 | 0.00 | 0.00 | 0.00 | 0.02 |
| 16057 | 16060 | PDC World Championship Darts 2008 | PSP | NaN | Sports | Oxygen Interactive | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 |
"""
Optei por preencher o ano de valor nulo para os jogos de esportes utilizando o ano anterior ao contido no título. Apenas preencherei
os anos cujo o título possua o ano no formato completo sem abreviar por ser mais fácil de identificar.
"""
def extrair_ano(linha):
if np.isnan(linha['year']) and linha['genre'] == 'Sports':
ano = re.search(r'\b\d{4}\b', linha['name'])
if ano:
return int(ano.group()) - 1
return linha['year']
df_video_game['year'] = df_video_game.apply(extrair_ano, axis=1)
df_video_game[df_video_game['rank'].isin(ranks)].head(10)
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 179 | 180 | Madden NFL 2004 | PS2 | 2003.0 | Sports | Electronic Arts | 4.26 | 0.26 | 0.01 | 0.71 | 5.23 |
| 377 | 378 | FIFA Soccer 2004 | PS2 | 2003.0 | Sports | Electronic Arts | 0.59 | 2.36 | 0.04 | 0.51 | 3.49 |
| 2586 | 2588 | PES 2009: Pro Evolution Soccer | PSP | 2008.0 | Sports | Konami Digital Entertainment | 0.04 | 0.33 | 0.26 | 0.17 | 0.80 |
| 3501 | 3503 | Madden NFL 2002 | XB | 2001.0 | Sports | Unknown | 0.53 | 0.02 | 0.00 | 0.03 | 0.58 |
| 4797 | 4799 | NFL GameDay 2003 | PS2 | 2002.0 | Sports | Unknown | 0.20 | 0.15 | 0.00 | 0.05 | 0.40 |
| 5162 | 5164 | NBA Live 2003 | XB | 2002.0 | Sports | Electronic Arts | 0.31 | 0.04 | 0.00 | 0.01 | 0.36 |
| 5669 | 5671 | All-Star Baseball 2005 | PS2 | 2004.0 | Sports | Unknown | 0.16 | 0.12 | 0.00 | 0.04 | 0.32 |
| 5901 | 5903 | NBA Live 2003 | GC | 2002.0 | Sports | Electronic Arts | 0.23 | 0.06 | 0.00 | 0.01 | 0.30 |
| 8929 | 8931 | All-Star Baseball 2005 | XB | 2004.0 | Sports | Unknown | 0.11 | 0.03 | 0.00 | 0.01 | 0.15 |
| 15739 | 15742 | Football Manager 2007 | X360 | 2006.0 | Sports | Sega | 0.00 | 0.01 | 0.00 | 0.00 | 0.02 |
# Analisar os anos com valores nulos restantes
qtd_dados_ano_nulo = df_video_game.loc[df_video_game['year'].isna()].shape[0]
print(f'Porcentagem de dados com ano nulo: {qtd_dados_ano_nulo}/{qtd_registros} = {qtd_dados_ano_nulo/qtd_registros * 100:.2f}%')
# Para o restante dos registros que contem a coluna 'year' vazia, irei deletar os dados.
df_video_game_filtrado = df_video_game.loc[~df_video_game['year'].isna()].copy()
df_video_game_filtrado.head(10)
Porcentagem de dados com ano nulo: 259/16598 = 1.56%
| rank | name | platform | year | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | Wii Sports | Wii | 2006.0 | Sports | Nintendo | 41.49 | 29.02 | 3.77 | 8.46 | 82.74 |
| 1 | 2 | Super Mario Bros. | NES | 1985.0 | Platform | Nintendo | 29.08 | 3.58 | 6.81 | 0.77 | 40.24 |
| 2 | 3 | Mario Kart Wii | Wii | 2008.0 | Racing | Nintendo | 15.85 | 12.88 | 3.79 | 3.31 | 35.82 |
| 3 | 4 | Wii Sports Resort | Wii | 2009.0 | Sports | Nintendo | 15.75 | 11.01 | 3.28 | 2.96 | 33.00 |
| 4 | 5 | Pokemon Red/Pokemon Blue | GB | 1996.0 | Role-Playing | Nintendo | 11.27 | 8.89 | 10.22 | 1.00 | 31.37 |
| 5 | 6 | Tetris | GB | 1989.0 | Puzzle | Nintendo | 23.20 | 2.26 | 4.22 | 0.58 | 30.26 |
| 6 | 7 | New Super Mario Bros. | DS | 2006.0 | Platform | Nintendo | 11.38 | 9.23 | 6.50 | 2.90 | 30.01 |
| 7 | 8 | Wii Play | Wii | 2006.0 | Misc | Nintendo | 14.03 | 9.20 | 2.93 | 2.85 | 29.02 |
| 8 | 9 | New Super Mario Bros. Wii | Wii | 2009.0 | Platform | Nintendo | 14.59 | 7.06 | 4.70 | 2.26 | 28.62 |
| 9 | 10 | Duck Hunt | NES | 1984.0 | Shooter | Nintendo | 26.93 | 0.63 | 0.28 | 0.47 | 28.31 |
df_video_game_filtrado.info()
<class 'pandas.core.frame.DataFrame'> Index: 16339 entries, 0 to 16597 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 rank 16339 non-null int64 1 name 16339 non-null object 2 platform 16339 non-null object 3 year 16339 non-null float64 4 genre 16339 non-null object 5 publisher 16303 non-null object 6 na_sales 16339 non-null float64 7 eu_sales 16339 non-null float64 8 jp_sales 16339 non-null float64 9 other_sales 16339 non-null float64 10 global_sales 16339 non-null float64 dtypes: float64(6), int64(1), object(4) memory usage: 1.5+ MB
dict_tipos = {
'year': 'int',
}
dict_nomes_coluna = {
'year':'year_of_release'
}
df_video_game_filtrado = df_video_game_filtrado.astype(dict_tipos)
df_video_game_filtrado.rename(columns=dict_nomes_coluna, inplace=True)
df_video_game_filtrado.info()
<class 'pandas.core.frame.DataFrame'> Index: 16339 entries, 0 to 16597 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 rank 16339 non-null int64 1 name 16339 non-null object 2 platform 16339 non-null object 3 year_of_release 16339 non-null int64 4 genre 16339 non-null object 5 publisher 16303 non-null object 6 na_sales 16339 non-null float64 7 eu_sales 16339 non-null float64 8 jp_sales 16339 non-null float64 9 other_sales 16339 non-null float64 10 global_sales 16339 non-null float64 dtypes: float64(5), int64(2), object(4) memory usage: 1.5+ MB
df_video_game_filtrado[['na_sales', 'eu_sales', 'jp_sales', 'other_sales', 'global_sales']].describe()
| na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|
| count | 16339.000000 | 16339.000000 | 16339.000000 | 16339.000000 | 16339.00000 |
| mean | 0.265615 | 0.147653 | 0.078622 | 0.048384 | 0.54055 |
| std | 0.821901 | 0.508883 | 0.311452 | 0.189925 | 1.56578 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.01000 |
| 25% | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.06000 |
| 50% | 0.080000 | 0.020000 | 0.000000 | 0.010000 | 0.17000 |
| 75% | 0.240000 | 0.110000 | 0.040000 | 0.040000 | 0.48000 |
| max | 41.490000 | 29.020000 | 10.220000 | 10.570000 | 82.74000 |
"""
Dado que a coluna 'global_sales' não apresenta valor zero, isso sugere que todos os jogos foram lançados em pelo menos uma região,
o que, à primeira vista, não levanta preocupações sobre a integridade dos dados. Entretanto, é preciso realizar uma análise mais
profunda para ter certeza.
Analisando a falta de vendas em algumas regiões: o motivo pode ser devido à ausência de dados para essas regiões ou
simplesmente porque os jogos em questão não foram lançados nesses locais."
"""
df_video_game_filtrado.loc[df_video_game['na_sales'] == 0.]
| rank | name | platform | year_of_release | genre | publisher | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 214 | 215 | Monster Hunter Freedom 3 | PSP | 2010 | Role-Playing | Capcom | 0.0 | 0.00 | 4.87 | 0.00 | 4.87 |
| 338 | 339 | Friend Collection | DS | 2009 | Misc | Nintendo | 0.0 | 0.00 | 3.67 | 0.00 | 3.67 |
| 383 | 384 | Monster Hunter 4 | 3DS | 2013 | Role-Playing | Capcom | 0.0 | 0.00 | 3.44 | 0.00 | 3.44 |
| 402 | 403 | English Training: Have Fun Improving Your Skills! | DS | 2006 | Misc | Nintendo | 0.0 | 0.99 | 2.32 | 0.02 | 3.33 |
| 426 | 427 | Dragon Quest VI: Maboroshi no Daichi | SNES | 1995 | Role-Playing | Enix Corporation | 0.0 | 0.00 | 3.19 | 0.00 | 3.19 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16587 | 16590 | Mezase!! Tsuri Master DS | DS | 2009 | Sports | Hudson Soft | 0.0 | 0.00 | 0.01 | 0.00 | 0.01 |
| 16589 | 16592 | Chou Ezaru wa Akai Hana: Koi wa Tsuki ni Shiru... | PSV | 2016 | Action | dramatic create | 0.0 | 0.00 | 0.01 | 0.00 | 0.01 |
| 16590 | 16593 | Eiyuu Densetsu: Sora no Kiseki Material Collec... | PSP | 2007 | Role-Playing | Falcom Corporation | 0.0 | 0.00 | 0.01 | 0.00 | 0.01 |
| 16595 | 16598 | SCORE International Baja 1000: The Official Game | PS2 | 2008 | Racing | Activision | 0.0 | 0.00 | 0.00 | 0.00 | 0.01 |
| 16596 | 16599 | Know How 2 | DS | 2010 | Puzzle | 7G//AMES | 0.0 | 0.01 | 0.00 | 0.00 | 0.01 |
4440 rows × 11 columns
Após analisar os títulos e como os dados em vgchartz foram filtrados para a construção desse dataset, pode-se concluir que são jogos lançados exclusivamente em determinadas áreas, geralmente no Japão.
df_video_game_filtrado['genre'].value_counts()
genre Action 3253 Sports 2316 Misc 1710 Role-Playing 1471 Shooter 1282 Adventure 1276 Racing 1226 Platform 876 Simulation 851 Fighting 836 Strategy 671 Puzzle 571 Name: count, dtype: int64
Visualização¶
df_genre_count_jogos = df_video_game_filtrado.groupby('genre', as_index=False)['name'].count().sort_values('name')
cores = df_genre_count_jogos.reset_index()['genre'].apply(lambda x: 'lightslategray' if x != 'Action' and x != 'Simulation' else 'crimson')
fig = go.Figure(
go.Bar(
x=df_genre_count_jogos['name'],
y=df_genre_count_jogos['genre'],
orientation='h',
marker_color=list(cores)
)
)
fig.update_layout(
title_text="Frequência de jogos por gênero",
xaxis_title='Quantidade de jogos',
yaxis_title='Gêneros',
# Padrao
**layout_padrao,
# Eixo Y
yaxis_showgrid=False,
)
fig.show()
qtd_jogos_acao = df_genre_count_jogos.loc[df_genre_count_jogos['genre'] == 'Action', 'name'].iloc[0]
qtd_jogos_simulacao = df_genre_count_jogos.loc[df_genre_count_jogos['genre'] == 'Simulation', 'name'].iloc[0]
proporcao = round(qtd_jogos_acao / qtd_jogos_simulacao)
print(f"A quantidade de jogos do gênero ação é aproximadamente {proporcao}"
+ " vezes maior que a quantidade de jogos de simulação.")
A quantidade de jogos do gênero ação é aproximadamente 4 vezes maior que a quantidade de jogos de simulação.
df_genre_global_sales = df_video_game_filtrado.groupby('genre', as_index=False)['global_sales'].sum().sort_values('global_sales')
cores = df_genre_global_sales.reset_index()['genre'].apply(lambda x: 'lightslategray' if x != 'Action' and x != 'Simulation' else 'crimson')
fig = go.Figure(
go.Bar(
x=df_genre_global_sales['global_sales'],
y=df_genre_global_sales['genre'],
orientation='h',
marker_color=list(cores)
)
)
fig.update_layout(
title_text="Total de unidades vendidas em milhões por gênero",
xaxis_title='Total de unidades vendidas em milhões',
yaxis_title='Gêneros',
# Padrao
**layout_padrao,
# Eixo Y
yaxis_showgrid=False,
)
fig.show()
Houve mais unidades vendidas para jogos do gênero ação, porém a quantidade de jogos desse gênero também é superior a quantidade de jogos de outras categorias
"""
Vamos identificar os outliers
"""
fig = go.Figure(go.Box(y=df_video_game_filtrado['global_sales'], name=''))
fig.update_layout(
title_text="Distribuição da variável global_sales",
yaxis_title='Vendas globais em milhões',
# Padrao
**layout_padrao,
# Eixo X
xaxis_showgrid=False,
)
fig.show()
df_video_game_filtrado.describe(percentiles=[.01, 0.05, 0.25, 0.5, 0.75, 0.8, 0.99])
| rank | year_of_release | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|
| count | 16339.000000 | 16339.000000 | 16339.000000 | 16339.000000 | 16339.000000 | 16339.000000 | 16339.00000 |
| mean | 8291.966828 | 2006.404737 | 0.265615 | 0.147653 | 0.078622 | 0.048384 | 0.54055 |
| std | 4793.413584 | 5.827500 | 0.821901 | 0.508883 | 0.311452 | 0.189925 | 1.56578 |
| min | 1.000000 | 1980.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.01000 |
| 1% | 164.380000 | 1987.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.01000 |
| 5% | 826.900000 | 1996.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.02000 |
| 25% | 4135.500000 | 2003.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.06000 |
| 50% | 8293.000000 | 2007.000000 | 0.080000 | 0.020000 | 0.000000 | 0.010000 | 0.17000 |
| 75% | 12441.500000 | 2010.000000 | 0.240000 | 0.110000 | 0.040000 | 0.040000 | 0.48000 |
| 80% | 13273.400000 | 2011.000000 | 0.310000 | 0.160000 | 0.060000 | 0.050000 | 0.61000 |
| 99% | 16435.620000 | 2016.000000 | 2.820000 | 1.940000 | 1.270000 | 0.656200 | 5.46620 |
| max | 16600.000000 | 2020.000000 | 41.490000 | 29.020000 | 10.220000 | 10.570000 | 82.74000 |
percentile_1 = df_video_game_filtrado['global_sales'].quantile(0.01)
percentile_99 = df_video_game_filtrado['global_sales'].quantile(0.99)
df_video_game_filtrado_sem_extremos = df_video_game_filtrado[(df_video_game_filtrado['global_sales'] >= percentile_1) & (df_video_game_filtrado['global_sales'] <= percentile_99)]
df_video_game_filtrado_sem_extremos.describe()
| rank | year_of_release | na_sales | eu_sales | jp_sales | other_sales | global_sales | |
|---|---|---|---|---|---|---|---|
| count | 16175.000000 | 16175.000000 | 16175.000000 | 16175.000000 | 16175.000000 | 16175.000000 | 16175.000000 |
| mean | 8375.203462 | 2006.417187 | 0.213074 | 0.114984 | 0.063840 | 0.038247 | 0.430428 |
| std | 4745.468402 | 5.808987 | 0.389143 | 0.254557 | 0.207063 | 0.097401 | 0.706215 |
| min | 165.000000 | 1980.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.010000 |
| 25% | 4262.500000 | 2003.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.060000 |
| 50% | 8377.000000 | 2007.000000 | 0.080000 | 0.020000 | 0.000000 | 0.010000 | 0.170000 |
| 75% | 12482.500000 | 2010.000000 | 0.230000 | 0.110000 | 0.030000 | 0.030000 | 0.460000 |
| max | 16600.000000 | 2020.000000 | 4.260000 | 3.750000 | 4.870000 | 2.930000 | 5.460000 |
Teste de hipótese¶
Testar a hipótese de que a venda média dos jogos de ação é maior que a venda média dos jogos do gênero simulação.
Usaremos nível de significância de 5%
"""
Dados do problema
"""
significancia = 0.05
confianca = 1 - significancia
"""
Vamos separar as amostras das duas categorias (variável genre)
"""
unidades_jogos_vendidas_acao = df_video_game_filtrado_sem_extremos.query('genre == "Action"')['global_sales']
unidades_jogos_vendidas_simulacao = df_video_game_filtrado_sem_extremos.query('genre == "Simulation"')['global_sales']
"""
Uma distribuição normal tem o formato característico de um sino. Vamos verificar se as nossas amostras
possuem esse formato.
"""
fig = go.Figure()
trace0 = go.Histogram(
x=unidades_jogos_vendidas_acao,
nbinsx=20,
name='Ação',
histnorm='probability density'
)
trace1 = go.Histogram(
x=unidades_jogos_vendidas_simulacao,
nbinsx=20,
name='Simulação',
histnorm='probability density'
)
fig.add_trace(trace0)
fig.add_trace(trace1)
fig.update_layout(
title_text="Distribuição das vendas globais por gênero",
yaxis_title='Densidade',
xaxis_title='Valores',
# Padrao
**layout_padrao,
)
fig.update_layout(barmode='overlay')
fig.update_traces(opacity=0.85)
fig.show()
"""
Como pode ser visto, a distribuição não possui o formato esperado dado que existe assimetria à direita nos histogramas.
Entretanto, vamos utilizar um teste formal para concluir se a nossa amostra segue ou não uma distribuição normal.
"""
def testar_normalidade(dados, teste='normaltest'):
"""
Esta função realiza o teste de normalidade em uma série de dados utilizando o teste indicado.
Parâmetros:
dados (pandas.Series): a série de dados que deseja verificar a normalidade.
teste (str): o tipo de teste de normalidade a ser realizado. (shapiro ou normaltest[padrão])
Retorna:
None
"""
if teste == 'normaltest':
stat_test, p_valor = normaltest(dados)
print("="*4 + " normaltest " + "="*4)
elif teste == 'shapiro':
stat_test, p_valor = shapiro(dados)
print("="*4 + " shapiro " + "="*4)
print("Estatística de teste:", stat_test)
print("Valor de p:", p_valor)
if p_valor < significancia:
print("Rejeitamos a hipótese nula, logo os dados NÃO seguem uma distribuição normal.")
else:
print("Não rejeitamos a hipótese nula, logo os dados seguem uma distribuição normal.")
print("-"*100)
testar_normalidade(unidades_jogos_vendidas_acao)
testar_normalidade(unidades_jogos_vendidas_acao, teste='shapiro')
testar_normalidade(unidades_jogos_vendidas_simulacao)
testar_normalidade(unidades_jogos_vendidas_simulacao, teste='shapiro')
==== normaltest ==== Estatística de teste: 2316.949288678951 Valor de p: 0.0 Rejeitamos a hipótese nula, logo os dados NÃO seguem uma distribuição normal. ---------------------------------------------------------------------------------------------------- ==== shapiro ==== Estatística de teste: 0.5869715724976616 Valor de p: 3.5220032264332144e-66 Rejeitamos a hipótese nula, logo os dados NÃO seguem uma distribuição normal. ---------------------------------------------------------------------------------------------------- ==== normaltest ==== Estatística de teste: 658.6485246096928 Valor de p: 9.468694925995263e-144 Rejeitamos a hipótese nula, logo os dados NÃO seguem uma distribuição normal. ---------------------------------------------------------------------------------------------------- ==== shapiro ==== Estatística de teste: 0.5879740520944692 Valor de p: 1.1561966132224215e-40 Rejeitamos a hipótese nula, logo os dados NÃO seguem uma distribuição normal. ----------------------------------------------------------------------------------------------------
Como visto, nossas amostras não seguem uma distribuição normal. Logo, a aplicação do teste t de Student não é adequada para essa situação. Portanto, utilizaremos o teste não paramétrico de Mann-Whitney para comparação de amostras independentes.
Dado que estamos utilizando o teste de Mann-Whitney, devemos atualizar a nossa hipótese para comparar não mais a média, mas a mediana entre os dois grupos. Ao buscar mais informações sobre esse teste, descobri que muitos materiais didáticos utilizam a ideia de comparação entre a mediana do grupo A com a mediana do grupo B, mas essa comparação só é de fato feita quando os dois grupos apresentam a mesma distribuição (mesmo formato), sendo a única diferença o deslocamento no gráfico (medianas diferentes). Podemos observar acima que o formato das amostras são parecidos, então usaremos a noção de mediana. Caso os formatos fossem diferentes, pode ser que dizer que a comparação é entre as medianas pode soar um pouco simplório, o correto seria dizer que o que será comparado são as distribuições dos dados entre as duas amostras.
"""
Analisar as medianas das nossas amostras
"""
dados_acao_simulacao = pd.concat(
[unidades_jogos_vendidas_acao.describe(), unidades_jogos_vendidas_simulacao.describe()],
axis=1,
keys=['Acao', 'Simulacao']
)
dados_acao_simulacao
| Acao | Simulacao | |
|---|---|---|
| count | 3225.000000 | 846.000000 |
| mean | 0.454595 | 0.390319 |
| std | 0.733856 | 0.634859 |
| min | 0.010000 | 0.010000 |
| 25% | 0.070000 | 0.050000 |
| 50% | 0.190000 | 0.160000 |
| 75% | 0.490000 | 0.420000 |
| max | 5.300000 | 5.150000 |
fig = go.Figure()
trace0 = go.Box(
y=unidades_jogos_vendidas_acao,
name='Ação',
marker_color = 'indianred',
marker_size=4,
)
trace1 = go.Box(
y=unidades_jogos_vendidas_simulacao,
name='Simulação',
marker_color = 'lightseagreen',
marker_size=4,
)
fig.add_trace(trace0)
fig.add_trace(trace1)
fig.update_layout(
title_text="Distribuição das vendas globais por gênero",
yaxis_title='Unidades vendidas em milhões',
xaxis_title='Gêneros',
# Padrao
**layout_padrao,
)
fig.show()
Para a hipótese nula vamos assumir que as distribuições das duas amostras são iguais. Como a mediana amostral das vendas globais para os jogos vendidos do gênero ação é maior que para o de simulação, vamos formular a hipótese alternativa como a seguir:
$\mu_A \Rightarrow$ Mediana das vendas globais para jogos do gênero ação
$\mu_S \Rightarrow$ Mediana das vendas globais para jogos do gênero simulação
$ \begin{cases} H_0: \mu_A = \mu_S\\ H_1: \mu_A > \mu_S \end{cases} $
"""
Aplicando o teste de Mann-Whitney
"""
u, p_valor = mannwhitneyu(unidades_jogos_vendidas_acao,
unidades_jogos_vendidas_simulacao,
alternative="greater")
if p_valor < significancia:
print(f"Com confiança de {confianca*100}% é possível rejeitar a hipótese nula.\n"
+ "Portanto, com base nos resultados do teste de Mann-Whitney U,\n"
+ "jogos do gênero ação tendem a ter valores significativamente maiores de venda do que jogos de simulação.")
else:
print("Não conseguimos rejeitar a hipótese nula, o que significa que não temos evidências suficientes"
+ " para suportar a hipótese alternativa.")
Com confiança de 95.0% é possível rejeitar a hipótese nula. Portanto, com base nos resultados do teste de Mann-Whitney U, jogos do gênero ação tendem a ter valores significativamente maiores de venda do que jogos de simulação.