Projeto Final do Módulo de Estatística I¶

Descrição:¶

Realizar uma análise descritiva de uma base de dados definindo motivação e hipótese.

Regras:¶

  • Tente utilizar todos os tópicos aprendidos em sala de aula.

Grupo composto por:¶

  • Rayssa Vilaça

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.

New-Project-1-2

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¶

In [1]:
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¶

In [2]:
# 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¶

In [3]:
df_video_game = pd.read_csv('assets/video_games_sales.csv')
In [4]:
df_video_game.head(10)
Out[4]:
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
In [5]:
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
In [6]:
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
In [7]:
"""
Analisando ano de lançamento com valor nulo
"""
df_video_game.loc[df_video_game['year'].isna()]
Out[7]:
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

In [8]:
""" 
É 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()]
Out[8]:
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

In [9]:
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
Out[9]:
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
In [10]:
"""
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)
Out[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
In [11]:
# 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%
Out[11]:
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
In [12]:
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
In [13]:
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
In [14]:
df_video_game_filtrado[['na_sales', 'eu_sales', 'jp_sales', 'other_sales', 'global_sales']].describe()
Out[14]:
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
In [15]:
"""
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.]
Out[15]:
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.

In [16]:
df_video_game_filtrado['genre'].value_counts()
Out[16]:
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¶

In [17]:
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()
In [18]:
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.
In [19]:
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

In [20]:
"""
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()
In [21]:
df_video_game_filtrado.describe(percentiles=[.01, 0.05, 0.25, 0.5, 0.75, 0.8, 0.99])
Out[21]:
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
In [22]:
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)]
In [23]:
df_video_game_filtrado_sem_extremos.describe()
Out[23]:
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%

In [24]:
"""
Dados do problema
"""
significancia = 0.05
confianca = 1 - significancia
In [25]:
"""
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']
In [26]:
"""
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()
In [27]:
"""
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.

In [28]:
"""
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
Out[28]:
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
In [29]:
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} $

In [30]:
"""
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.