Game over ou hit? O que faz um jogo vender

O que 16 mil jogos lançados entre 1980 e 2016 revelam sobre sucesso comercial, notas e mercados

Lucas Paiva Ferruccio · Bacharelado em Ciência da Computação · UFRPE
Computação para Análise de Dados (2026.2) · Prof. Ermeson Andrade

1. Introdução

1.1 O problema

Em 2016, ano em que termina a base deste estudo, o mercado mundial de games movimentou cerca de US$ 99,6 bilhões (Newzoo, via CNBC). Por trás desse número, cada jogo lançado é uma aposta: anos de desenvolvimento, equipes de centenas de pessoas e orçamentos milionários, para um produto que pode virar um fenômeno como GTA V ou desaparecer das prateleiras em poucas semanas.

Quem joga costuma ter respostas prontas sobre o que separa um sucesso de um fracasso: “jogo bom vende”, “todo mundo gosta de jogo de tiro”, “o PlayStation sempre dominou”. Este relatório coloca essas certezas à prova com dados. A pergunta que organiza todo o trabalho é:

O que faz um jogo vender? O sucesso comercial depende da qualidade do jogo, do gênero, da plataforma, do mercado onde ele é vendido ou de nenhum deles isoladamente?

Por que isso interessa? Porque errar o diagnóstico custa caro. Uma publicadora que acredita que “nota alta garante vendas” pode gastar todo o orçamento em polimento e esquecer a distribuição; uma que lança o mesmo jogo da mesma forma em todos os países pode estar deixando dinheiro na mesa.

1.2 O que eu esperava encontrar

Antes de abrir uma única linha do banco de dados, anotei o que eu esperava encontrar, com base no senso comum de quem joga. Cada suposição vira uma pergunta na Seção 4, e no fim confiro quais sobreviveram aos dados.

Minha suposição inicial
H1 Um jogo bem feito vende razoavelmente bem: as vendas se espalham entre muitos jogos.
H2 O gênero mais popular (Ação, Tiro) é o mesmo no mundo inteiro.
H3 A Sony (PlayStation) sempre dominou o mercado.
H4 Jogo com nota alta vende mais, e jogo com nota baixa fracassa.
H5 Crítica especializada e público concordam sobre o que é um bom jogo.
H6 A classificação etária não faz diferença nas vendas.
H7 Lançar em várias plataformas só divide o público, sem ganho real por versão.

1.3 Como pretendo abordar

  • Dados: a base Video Game Sales with Ratings (Kaggle), com as vendas de 16.719 jogos (VGChartz) e suas notas no Metacritic, complementada por uma tabela de plataformas raspada da Wikipédia.
  • Metodologia: importar e limpar a base, padronizar os valores ausentes, criar variáveis (fabricante, geração, faixas de nota e de vendas), mesclar as tabelas e cruzar vendas com gênero, região, plataforma, notas e classificação etária.
  • Técnica: como as vendas são muito assimétricas, comparo medianas e proporções de hits em vez de médias, e uso testes não paramétricos (Spearman e Kruskal-Wallis) para verificar se as relações observadas são estatisticamente significativas.

1.4 Como isso ajuda os potenciais clientes

  • Publicadoras e estúdios: indica em que gênero apostar para cada mercado, quanto a qualidade (nota) realmente pesa e se vale lançar em várias plataformas.
  • Investidores do setor: mostra o quanto o mercado é concentrado em poucos sucessos, ou seja, o risco de cada aposta.
  • Imprensa e jogadores: revela onde a crítica especializada e o público discordam.

2. Pacotes requeridos

Todos os pacotes são carregados aqui, de uma vez. Para replicar a análise, instale-os antes com:

install.packages(c("readr", "dplyr", "tidyr", "stringr", "forcats", "rvest",
                   "ggplot2", "scales", "plotly", "DT", "kableExtra", "rmdformats"))
library(readr)       # importar os arquivos CSV
library(dplyr)       # filtrar, criar variáveis, agrupar e juntar tabelas
library(tidyr)       # reformatar dados (pivot_longer / pivot_wider)
library(stringr)     # tratar textos
library(forcats)     # manipular fatores
library(rvest)       # raspar páginas da Wikipédia
library(ggplot2)     # gráficos estáticos
library(scales)      # formatar eixos (milhões, %, log)
library(plotly)      # gráficos interativos
library(DT)          # tabelas interativas
library(kableExtra)  # tabelas estáticas formatadas
Pacote Para que serve neste projeto
readr Importar o CSV de vendas e a tabela de plataformas
dplyr Limpeza, criação de variáveis, agregações e junção das tabelas
tidyr Converter as vendas por região do formato largo para o longo
stringr Padronizar textos e extrair informações das páginas da Wikipédia
forcats Recodificar e reordenar fatores (gênero, classificação etária)
rvest Raspar o quadro de informações (infobox) de cada console na Wikipédia
ggplot2 Construir os gráficos estáticos
scales Formatar números no padrão brasileiro (milhões, %, escala log)
plotly Gráficos com zoom, filtro por legenda e detalhes ao passar o mouse
DT Tabelas com busca e ordenação
kableExtra Tabelas estáticas formatadas
rmdformats Tema readthedown do relatório
# Funções e cores usadas em todo o relatório
br  <- function(x, acc = 1) number(x, accuracy = acc, big.mark = ".", decimal.mark = ",")
pct <- function(x, acc = 1) percent(x, accuracy = acc, decimal.mark = ",")

cinza    <- "#BDBDBD"   # contexto
azul     <- "#2C5F8A"   # informação principal
destaque <- "#C0392B"   # o insight que o leitor deve ver

# Paleta segura para daltonismo (Okabe-Ito) para os fabricantes
cores_fab <- c(Nintendo = "#D55E00", Sony = "#0072B2", Microsoft = "#009E73",
               Sega = "#56B4E9", PC = "#999999", Outros = "#E69F00")

tema <- theme_minimal(base_size = 13) +
  theme(plot.title = element_text(face = "bold", size = 15),
        plot.subtitle = element_text(color = "grey35"),
        panel.grid.minor = element_blank(),
        legend.position = "top")

dt_pt <- list(url = "//cdn.datatables.net/plug-ins/1.10.11/i18n/Portuguese-Brasil.json")

3. Preparação dos dados

3.1 Fonte original

  • Conjunto principal: Video Game Sales with Ratings, publicado no Kaggle por Rush Kirubi. O autor estendeu uma raspagem do site VGChartz (vendas) com uma segunda raspagem do Metacritic (notas da crítica e dos usuários). A coleta foi feita em 22 de dezembro de 2016.
  • Arquivo utilizado: Video_Games_Sales_as_at_22_Dec_2016.csv
  • Tabela de apoio: páginas de cada console na Wikipédia em inglês, raspadas com rvest (Seção 3.4).

3.2 O que são os dados originais

# Leitura "crua": deixo o readr adivinhar os tipos para enxergar os problemas
jogos_brutos <- read_csv("dados/Video_Games_Sales_as_at_22_Dec_2016.csv",
                         show_col_types = FALSE)

Propósito. Cada linha é um jogo em uma plataforma: o mesmo título lançado para PS3 e Xbox 360 aparece em duas linhas. As vendas são acumuladas até a data da coleta, em milhões de unidades, e separadas por região.

Dimensão e período. O arquivo tem 16.719 linhas e 16 variáveis, com jogos lançados de 1980 a 2016.

contar_marcadores <- function(x) {
  sum(is.na(x)) + sum(x %in% c("N/A", "tbd", "Unknown"))
}

data.frame(
  `Variável original` = names(jogos_brutos),
  `Descrição` = c(
    "Nome do jogo", "Plataforma (sigla)", "Ano de lançamento", "Gênero",
    "Publicadora", "Vendas na América do Norte (mi)", "Vendas na Europa (mi)",
    "Vendas no Japão (mi)", "Vendas no resto do mundo (mi)", "Vendas globais (mi)",
    "Nota da crítica (0–100)", "Nº de críticos", "Nota dos usuários (0–10)",
    "Nº de usuários", "Desenvolvedora", "Classificação etária (ESRB)"),
  `Tipo lido` = sapply(jogos_brutos, function(x) class(x)[1]),
  `Valores distintos` = sapply(jogos_brutos, n_distinct),
  `Ausentes*` = sapply(jogos_brutos, contar_marcadores),
  check.names = FALSE
) |>
  kbl(row.names = FALSE, format.args = list(big.mark = ".")) |>
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) |>
  row_spec(c(3, 13), bold = TRUE, color = destaque) |>
  footnote(general = "*Inclui células vazias e os textos 'N/A', 'tbd' e 'Unknown'.",
           general_title = "")
Variável original Descrição Tipo lido Valores distintos Ausentes*
Name Nome do jogo character 11.563 2
Platform Plataforma (sigla) character 31 0
Year_of_Release Ano de lançamento character 40 269
Genre Gênero character 13 2
Publisher Publicadora character 581 255
NA_Sales Vendas na América do Norte (mi) numeric 402 0
EU_Sales Vendas na Europa (mi) numeric 307 0
JP_Sales Vendas no Japão (mi) numeric 244 0
Other_Sales Vendas no resto do mundo (mi) numeric 155 0
Global_Sales Vendas globais (mi) numeric 629 0
Critic_Score Nota da crítica (0–100) numeric 83 8.582
Critic_Count Nº de críticos numeric 107 8.582
User_Score Nota dos usuários (0–10) character 97 9.129
User_Count Nº de usuários numeric 889 9.129
Developer Desenvolvedora character 1.697 6.623
Rating Classificação etária (ESRB) character 9 6.769
*Inclui células vazias e os textos ‘N/A’, ‘tbd’ e ‘Unknown’.

Peculiaridades (a base não tem dicionário oficial; as interpretações são minhas):

  • O “dado ausente” aparece de quatro formas: célula vazia, o texto "N/A" (269 anos e 54 publicadoras), "Unknown" (201 publicadoras) e "tbd" (to be determined, nota ainda não calculada) em 2.425 notas de usuário. Por isso, ano e nota do usuário (em vermelho) foram lidos como texto, e não como número.
  • As notas estão em escalas diferentes: crítica de 0 a 100, usuários de 0 a 10.
  • As vendas estão em formato largo: uma coluna por região.
  • A plataforma é só uma sigla (PS2, X360, GEN…), sem fabricante nem geração.
  • Há registros corrompidos e duplicados (dois jogos sem nome e um Madden NFL 13 repetido).

E havia uma surpresa maior, que mudou o desenho da análise:

jogos_brutos |>
  mutate(ano = suppressWarnings(as.integer(Year_of_Release))) |>
  filter(!is.na(ano), ano <= 2016) |>
  group_by(ano) |>
  summarise(pct_nota = mean(!is.na(Critic_Score))) |>
  ggplot(aes(ano, pct_nota, fill = ano >= 2000)) +
  geom_col(width = 0.85) +
  scale_fill_manual(values = c(`FALSE` = cinza, `TRUE` = azul), guide = "none") +
  annotate("text", x = 1989, y = 0.55, size = 4.2, color = "grey30",
           label = "Antes de 2000:\nquase nenhum jogo tem nota") +
  scale_y_continuous(labels = pct, limits = c(0, 0.85)) +
  labs(title = "O Metacritic só passa a avaliar os jogos a partir de 2000",
       subtitle = "% de jogos com nota da crítica, por ano de lançamento",
       x = NULL, y = NULL) +
  tema

Consequência. Cerca de metade dos jogos não tem nota, e quase todos os anteriores a 2000 ficam de fora do Metacritic. Por isso, as análises que envolvem notas usam só jogos de 2000 a 2016, enquanto as análises de vendas usam a base inteira.

3.3 Importação e limpeza

Cada aba abaixo é um passo da limpeza, na ordem em que foi executado.

Passo 1 · Nomes

Renomeio as colunas para português e no estilo snake_case, para deixar o código legível.

jogos <- jogos_brutos |>
  rename(nome = Name, plataforma = Platform, ano = Year_of_Release,
         genero = Genre, publicadora = Publisher,
         vendas_na = NA_Sales, vendas_eu = EU_Sales, vendas_jp = JP_Sales,
         vendas_outros = Other_Sales, vendas_global = Global_Sales,
         nota_critica = Critic_Score, n_criticos = Critic_Count,
         nota_usuario = User_Score, n_usuarios = User_Count,
         desenvolvedora = Developer, classificacao = Rating)

Passo 2 · Ausentes e tipos

Converto os marcadores "N/A", "tbd" e "Unknown" em NA e transformo ano e nota do usuário em números. O stopifnot garante que nenhum valor válido se perdeu na conversão.

jogos <- jogos |>
  mutate(
    ano          = as.integer(na_if(ano, "N/A")),
    nota_usuario = as.numeric(na_if(nota_usuario, "tbd")),
    publicadora  = publicadora |> na_if("N/A") |> na_if("Unknown")
  )

# Nenhum ano ou nota válidos devem ter virado NA na conversão
stopifnot(sum(is.na(jogos$ano)) == sum(jogos_brutos$Year_of_Release == "N/A"))

Passo 3 · Inválidos

Removo os 2 registros sem nome nem gênero (dados corrompidos de um jogo de Mega Drive de 1993) e a duplicata de Madden NFL 13 (PS3), mantendo o registro com mais vendas.

jogos |>
  filter(!is.na(nome)) |>
  group_by(nome, plataforma, ano) |>
  filter(n() > 1) |>
  select(nome, plataforma, ano, vendas_global) |>
  kbl(caption = "Registro duplicado") |>
  kable_styling(full_width = FALSE)
Registro duplicado
nome plataforma ano vendas_global
Madden NFL 13 PS3 2012 2.56
Madden NFL 13 PS3 2012 0.01
jogos <- jogos |>
  filter(!is.na(nome)) |>
  arrange(desc(vendas_global)) |>
  distinct(nome, plataforma, ano, .keep_all = TRUE)

Passo 4 · Período

Removo os jogos sem ano e os de 2017 e 2020: a coleta terminou em dezembro de 2016, então esses poucos registros são pré-lançamentos sem vendas reais.

jogos <- jogos |> filter(!is.na(ano), ano <= 2016)

Passo 5 · Fatores

Traduzo os gêneros e recodifico a classificação etária: K-A (Kids to Adults) é o nome antigo de E (Everyone), e EC, RP e AO, com 1 a 8 jogos cada, viram “Outras”.

traducao_generos <- c(
  "Action" = "Ação", "Sports" = "Esportes", "Shooter" = "Tiro",
  "Role-Playing" = "RPG", "Platform" = "Plataforma", "Misc" = "Diversos",
  "Racing" = "Corrida", "Fighting" = "Luta", "Simulation" = "Simulação",
  "Puzzle" = "Quebra-cabeça", "Adventure" = "Aventura", "Strategy" = "Estratégia"
)

jogos <- jogos |>
  mutate(
    genero = factor(traducao_generos[genero]),
    classificacao = classificacao |>
      fct_recode(E = "K-A") |>
      fct_other(keep = c("E", "E10+", "T", "M"), other_level = "Outras") |>
      fct_relevel("E", "E10+", "T", "M", "Outras")
  )

Passo 6 · Escala das notas

Multiplico a nota dos usuários por 10 para que as duas notas fiquem de 0 a 100 e possam ser comparadas.

jogos <- jogos |> mutate(nota_usuario_100 = nota_usuario * 10)

3.4 Mesclagem com a Wikipédia

A base só traz siglas de plataforma. Para analisar fabricante, geração e tipo (mesa ou portátil), raspo o quadro de informações (infobox) da página de cada console na Wikipédia e junto à base. A junção acontece em duas etapas: sigla do VGChartz → página da Wikipédia → dados do console.

Tabela-chave

O VGChartz usa siglas próprias (X360, GEN, SCD) que não existem na Wikipédia. Esta tabela liga cada sigla ao título da página do console.

chave_plataformas <- tribble(
  ~sigla, ~pagina_wiki,
  "2600", "Atari_2600",            "NES",  "Nintendo_Entertainment_System",
  "SNES", "Super_Nintendo_Entertainment_System",
  "GEN",  "Sega_Genesis",          "NG",   "Neo_Geo_(system)",
  "TG16", "TurboGrafx-16",         "SCD",  "Sega_CD",
  "GB",   "Game_Boy",              "GG",   "Game_Gear",
  "PS",   "PlayStation_(console)", "N64",  "Nintendo_64",
  "SAT",  "Sega_Saturn",           "3DO",  "3DO_Interactive_Multiplayer",
  "PCFX", "PC-FX",                 "WS",   "WonderSwan",
  "PS2",  "PlayStation_2",         "XB",   "Xbox_(console)",
  "GC",   "GameCube",              "DC",   "Dreamcast",
  "GBA",  "Game_Boy_Advance",      "PS3",  "PlayStation_3",
  "X360", "Xbox_360",              "Wii",  "Wii",
  "DS",   "Nintendo_DS",           "PSP",  "PlayStation_Portable",
  "PS4",  "PlayStation_4",         "XOne", "Xbox_One",
  "WiiU", "Wii_U",                 "3DS",  "Nintendo_3DS",
  "PSV",  "PlayStation_Vita"
)

Raspagem

A função raspar_infobox() lê o quadro de informações de cada página e extrai fabricante, tipo, geração e data de lançamento. Depois, limpar_infobox() transforma esses textos em variáveis: “Sixth generation” vira 6, “Sony Computer Entertainment” vira “Sony”. Se a raspagem falhar (sem internet, por exemplo), o relatório usa a cópia salva em dados/plataformas.csv.

# Lê o infobox de uma página da Wikipédia e devolve os campos de interesse
raspar_infobox <- function(pagina) {
  endereco <- paste0("https://en.wikipedia.org/wiki/", pagina)
  conexao  <- url(endereco, headers = c("User-Agent" = "Projeto academico CPAD (R/rvest)"))
  html     <- read_html(conexao)

  linhas  <- html |> html_element("table.infobox") |> html_elements("tr")
  rotulos <- linhas |> html_element("th") |> html_text2()
  valores <- linhas |> html_element("td") |> html_text2()

  campo <- function(termo) {
    i <- which(str_detect(rotulos, regex(paste0("^", termo), ignore_case = TRUE)))
    if (length(i) == 0) NA_character_ else valores[i[1]]
  }

  tibble(pagina_wiki = pagina, fabricante_txt = campo("Manufacturer"),
         tipo_txt = campo("Type"), geracao_txt = campo("Generation"),
         lancamento_txt = campo("Release"))
}

# Converte o texto do infobox em variáveis limpas
ordinais <- c(first = 1, second = 2, third = 3, fourth = 4, fifth = 5,
              sixth = 6, seventh = 7, eighth = 8, ninth = 9)

limpar_infobox <- function(bruto) {
  bruto |>
    mutate(
      geracao = unname(ordinais[str_extract(str_to_lower(geracao_txt),
                                            paste(names(ordinais), collapse = "|"))]),
      tipo = if_else(str_detect(tipo_txt, regex("handheld", ignore_case = TRUE)),
                     "Portátil", "Mesa"),
      ano_lancamento = as.integer(str_extract(lancamento_txt, "(19|20)\\d{2}")),
      fabricante = case_when(
        str_detect(fabricante_txt, "Sony")      ~ "Sony",
        str_detect(fabricante_txt, "Nintendo")  ~ "Nintendo",
        str_detect(fabricante_txt, "Microsoft") ~ "Microsoft",
        str_detect(fabricante_txt, "Sega")      ~ "Sega",
        str_detect(fabricante_txt, "Atari")     ~ "Atari",
        str_detect(fabricante_txt, "SNK")       ~ "SNK",
        str_detect(fabricante_txt, "NEC")       ~ "NEC",
        str_detect(fabricante_txt, "Bandai")    ~ "Bandai",
        str_detect(fabricante_txt, "Panasonic") ~ "Panasonic",
        TRUE ~ word(fabricante_txt, 1)
      )
    )
}

caminho_backup <- "dados/plataformas.csv"

# Tenta raspar; se a internet ou a página falhar, usa o backup salvo
plataformas <- tryCatch({
  bruto <- bind_rows(lapply(chave_plataformas$pagina_wiki, function(p) {
    Sys.sleep(0.3)   # pausa curta entre requisições
    raspar_infobox(p)
  }))
  raspado <- chave_plataformas |>
    left_join(limpar_infobox(bruto), by = "pagina_wiki") |>
    mutate(nome_plataforma = pagina_wiki |> str_replace_all("_", " ") |>
             str_remove(" \\(.*\\)")) |>
    select(sigla, nome_plataforma, fabricante, geracao, tipo, ano_lancamento) |>
    add_row(sigla = "PC", nome_plataforma = "Computador pessoal",
            fabricante = "PC", geracao = NA, tipo = "PC", ano_lancamento = NA)
  write_csv(raspado, caminho_backup)
  raspado
}, error = function(e) {
  message("Raspagem falhou (", conditionMessage(e), "). Usando o backup.")
  read_csv(caminho_backup, show_col_types = FALSE)
})

Resultado e junção

A tabela raspada é unida à base pela sigla com left_join(). Antes, o anti_join() confere se alguma plataforma ficou sem par.

# Alguma sigla da base ficou sem correspondência?
sem_par <- jogos |> anti_join(plataformas, by = c("plataforma" = "sigla")) |> nrow()

jogos <- jogos |> left_join(plataformas, by = c("plataforma" = "sigla"))

plataformas |>
  arrange(geracao, fabricante) |>
  datatable(rownames = FALSE,
            colnames = c("Sigla", "Plataforma", "Fabricante", "Geração", "Tipo", "Lançamento"),
            options = list(pageLength = 8, dom = "tp", language = dt_pt))

Siglas sem correspondência após a junção: 0. Todas as 31 plataformas foram enriquecidas.

3.5 Variáveis criadas

principais <- c("Nintendo", "Sony", "Microsoft", "Sega", "PC")

jogos <- jogos |>
  mutate(
    fabricante_grupo = factor(if_else(fabricante %in% principais, fabricante, "Outros"),
                              levels = c(principais, "Outros")),
    hit = vendas_global > 1,
    faixa_vendas = cut(vendas_global, breaks = c(0, 0.1, 0.5, 1, Inf),
                       labels = c("Fracasso (< 100 mil)", "Moderado (100–500 mil)",
                                  "Sucesso (500 mil–1 mi)", "Hit (> 1 mi)")),
    faixa_nota = cut(nota_critica, breaks = c(0, 49, 69, 79, 89, 100),
                     labels = c("< 50", "50–69", "70–79", "80–89", "90+")),
    dif_notas = nota_critica - nota_usuario_100
  ) |>
  group_by(nome) |>
  mutate(n_plataformas = n_distinct(plataforma)) |>
  ungroup()

# Recorte para as análises de nota: 2000+ e notas com base mínima de avaliações
jogos_avaliados <- jogos |>
  filter(ano >= 2000, !is.na(nota_critica), !is.na(nota_usuario),
         n_criticos >= 5, n_usuarios >= 10)
Variável Como foi criada
fabricante, geracao, tipo Junção com a tabela raspada da Wikipédia
fabricante_grupo Fabricantes com poucos jogos agrupados em ‘Outros’
hit TRUE se o jogo vendeu mais de 1 milhão de unidades
faixa_vendas Faixas de vendas: fracasso, moderado, sucesso, hit
faixa_nota Faixas da nota da crítica: < 50, 50–69, 70–79, 80–89, 90+
nota_usuario_100 Nota do usuário × 10 (mesma escala da crítica)
dif_notas Nota da crítica − nota do usuário
n_plataformas Em quantas plataformas o mesmo título foi lançado

3.6 O conjunto de dados final

O conjunto limpo tem 16.443 linhas e 28 variáveis, e o recorte com notas (jogos_avaliados) tem 5.264 jogos. Em vez de imprimir a base inteira, mostro os 200 jogos mais vendidos, com as principais colunas:

jogos |>
  slice_max(vendas_global, n = 200) |>
  select(nome, plataforma, fabricante, ano, genero, vendas_global,
         nota_critica, nota_usuario_100, classificacao) |>
  datatable(rownames = FALSE,
            colnames = c("Jogo", "Plataforma", "Fabricante", "Ano", "Gênero",
                         "Vendas (mi)", "Nota crítica", "Nota usuário", "Classificação"),
            options = list(pageLength = 8, scrollX = TRUE, language = dt_pt))

3.7 Resumo das variáveis de interesse

Resumo consolidado das variáveis de interesse
Variável Resumo
genero 12 gêneros; o mais frequente é Ação (20,1% dos jogos)
plataforma / fabricante 31 plataformas de 13 fabricantes; Sony tem mais jogos (6.636)
ano de 1980 a 2016; pico de lançamentos em 2008
vendas_global mediana de 170 mil unidades; média de 536 mil; máximo de 82,5 mi (Wii Sports): muito assimétrica
nota_critica 51% ausente; mediana de 71 pontos
nota_usuario_100 55% ausente; mediana de 75 pontos
classificacao 41% ausente; a mais comum é E (livre)

O retrato: uma base com vendas extremamente assimétricas. A média é mais de três vezes a mediana, e 35% dos jogos não chegam a 100 mil cópias. Por isso, ao longo da análise, comparo medianas e proporções de hits em vez de médias.

4. Análise exploratória

Cada seção a partir da 4.2 testa uma das suposições da Seção 1.2. Primeiro, o panorama.

4.1 Panorama: o mercado cresceu até 2008 e depois encolheu

por_ano <- jogos |>
  group_by(ano, fabricante_grupo) |>
  summarise(vendas = sum(vendas_global), lancamentos = n(), .groups = "drop")

pico <- jogos |> group_by(ano) |> summarise(v = sum(vendas_global), n = n()) |> slice_max(v)

g <- ggplot(por_ano, aes(ano, vendas, fill = fabricante_grupo,
                         text = paste0("<b>", fabricante_grupo, " · ", ano, "</b><br>",
                                       br(vendas, 0.1), " mi de unidades<br>",
                                       lancamentos, " lançamentos"))) +
  geom_col(width = 0.9) +
  scale_fill_manual(values = cores_fab) +
  scale_y_continuous(labels = label_number(suffix = " mi")) +
  labs(title = paste0("As vendas atingem o pico em ", pico$ano, " e depois caem"),
       x = NULL, y = "Unidades vendidas", fill = NULL) +
  tema

ggplotly(g, tooltip = "text") |>
  layout(legend = list(orientation = "h", y = 1.08))

Insight. Em 2008, os jogos lançados somam 672 milhões de unidades, em 1.427 lançamentos, no auge da 7ª geração (Wii, DS, Xbox 360 e PS3). A queda posterior não significa que as pessoas pararam de jogar: a base só registra vendas físicas, e o mercado migrou para downloads e celulares, que não aparecem aqui. Clique nos nomes da legenda para isolar cada fabricante.

4.2 Um mercado de hits: 10% dos jogos ficam com quase 60% das vendas

Eu esperava (H1): que um jogo bem feito vendesse razoavelmente bem, com as vendas espalhadas entre muitos jogos.

concentracao <- jogos |>
  arrange(desc(vendas_global)) |>
  mutate(pct_jogos = row_number() / n(),
         pct_vendas = cumsum(vendas_global) / sum(vendas_global))
top1  <- concentracao |> filter(pct_jogos <= 0.01) |> pull(pct_vendas) |> max()
top10 <- concentracao |> filter(pct_jogos <= 0.10) |> pull(pct_vendas) |> max()

ggplot(concentracao, aes(pct_jogos, pct_vendas)) +
  annotate("rect", xmin = 0, xmax = 0.10, ymin = 0, ymax = top10,
           fill = destaque, alpha = 0.12) +
  geom_abline(linetype = "dotted", color = "grey60") +
  geom_line(color = azul, linewidth = 1.3) +
  annotate("point", x = 0.10, y = top10, size = 3.5, color = destaque) +
  annotate("text", x = 0.13, y = top10 - 0.05, hjust = 0, color = destaque,
           fontface = "bold", size = 4.5,
           label = paste0("Os 10% mais vendidos = ", pct(top10), " de todas as vendas")) +
  annotate("text", x = 0.84, y = 0.80, angle = 35, color = "grey55",
           label = "se todos vendessem igual") +
  scale_x_continuous(labels = pct) +
  scale_y_continuous(labels = pct) +
  labs(title = paste0("10% dos jogos ficam com ", pct(top10), " de todas as vendas"),
       subtitle = "Curva de concentração: jogos ordenados do mais vendido ao menos vendido",
       x = "% acumulado de jogos", y = "% acumulado das vendas") +
  tema

Os 20 maiores sucessos da base, somando as versões de todas as plataformas:

jogos |>
  group_by(nome) |>
  summarise(plataformas = paste(sort(unique(plataforma)), collapse = ", "),
            ano = min(ano), genero = first(genero),
            publicadora = first(publicadora), vendas = sum(vendas_global)) |>
  slice_max(vendas, n = 20) |>
  datatable(rownames = FALSE,
            colnames = c("Jogo", "Plataformas", "Ano", "Gênero", "Publicadora", "Vendas (mi)"),
            options = list(pageLength = 10, dom = "tp", language = dt_pt)) |>
  formatRound("vendas", 1, mark = ".", dec.mark = ",") |>
  formatStyle("vendas", background = styleColorBar(c(0, 85), "#C9D8E8"),
              backgroundSize = "95% 70%", backgroundRepeat = "no-repeat",
              backgroundPosition = "center")

Insight. O mercado de games é um mercado de hits: 1% dos jogos responde por 21% das vendas, e 10% respondem por 59%. Na outra ponta, 35% dos jogos (1 em cada 3) vendem menos de 100 mil cópias. O topo é dominado pela Nintendo e por franquias consolidadas (Mario, Pokémon, Call of Duty). Para quem investe, cada lançamento precisa ser visto como parte de um portfólio de risco: poucos acertos pagam muitos fracassos.

❌ H1 refutada

4.3 Não existe gênero universal: no Japão, o RPG reina

Eu esperava (H2): que o gênero mais popular fosse o mesmo no mundo inteiro.

genero_regiao <- jogos |>
  select(genero, vendas_na, vendas_eu, vendas_jp, vendas_outros) |>
  pivot_longer(starts_with("vendas_"), names_to = "regiao",
               names_prefix = "vendas_", values_to = "vendas") |>
  mutate(regiao = recode(regiao, na = "América do Norte", eu = "Europa",
                         jp = "Japão", outros = "Outras regiões") |>
           factor(levels = c("América do Norte", "Europa", "Japão", "Outras regiões"))) |>
  group_by(regiao, genero) |>
  summarise(vendas = sum(vendas), .groups = "drop_last") |>
  mutate(part = vendas / sum(vendas)) |>
  ungroup()

part <- function(g, r) genero_regiao |> filter(genero == g, regiao == r) |> pull(part)

Japão × América do Norte

comparacao <- genero_regiao |>
  filter(regiao %in% c("América do Norte", "Japão")) |>
  select(-vendas) |>
  pivot_wider(names_from = regiao, values_from = part) |>
  rename(na = `América do Norte`, jp = `Japão`) |>
  mutate(destacar = genero %in% c("RPG", "Tiro"),
         genero = fct_reorder(genero, jp))

ggplot(comparacao, aes(y = genero)) +
  geom_segment(aes(x = na, xend = jp, yend = genero, color = destacar), linewidth = 1.4) +
  geom_point(aes(x = na), size = 3.5, color = "grey45") +
  geom_point(aes(x = jp), size = 3.5, color = destaque) +
  scale_color_manual(values = c(`FALSE` = "grey85", `TRUE` = "#E8A39C"), guide = "none") +
  annotate("text", x = part("RPG", "Japão"), y = "RPG", label = "  Japão",
           hjust = 0, color = destaque, fontface = "bold") +
  annotate("text", x = part("RPG", "América do Norte"), y = "RPG",
           label = "América do Norte  ", hjust = 1, color = "grey35", fontface = "bold") +
  scale_x_continuous(labels = pct, expand = expansion(mult = c(0.02, 0.12))) +
  labs(title = paste0("No Japão, RPG vende ",
                      br(part("RPG", "Japão") / part("RPG", "América do Norte"), 0.1),
                      "× mais que na América do Norte"),
       subtitle = "Participação de cada gênero nas vendas da região",
       x = "% das vendas da região", y = NULL) +
  tema

Todas as regiões (interativo)

ordem <- genero_regiao |> filter(regiao == "América do Norte") |> arrange(part) |> pull(genero)

g <- genero_regiao |>
  mutate(genero = factor(genero, levels = ordem)) |>
  ggplot(aes(regiao, genero, fill = part,
             text = paste0("<b>", genero, " · ", regiao, "</b><br>",
                           pct(part, 0.1), " das vendas da região<br>",
                           br(vendas, 0.1), " mi de unidades"))) +
  geom_tile(color = "white") +
  geom_text(aes(label = pct(part)), size = 3.5) +
  scale_fill_gradient(low = "#F1F5FB", high = azul, labels = pct) +
  labs(title = "Participação de cada gênero nas vendas de cada região",
       x = NULL, y = NULL, fill = "% da região") +
  tema + theme(panel.grid = element_blank())

ggplotly(g, tooltip = "text")

Insight. Cada mercado tem seu próprio gosto. Na América do Norte, na Europa e no resto do mundo, Ação, Esportes e Tiro lideram. No Japão, o cenário se inverte: RPG é o gênero nº 1, com 27% das vendas, contra 8% na América do Norte. Com Tiro acontece o contrário: 13% do mercado norte-americano e só 3% do japonês. Não existe “melhor gênero”: o gênero certo depende do mercado-alvo.

❌ H2 refutada

4.4 A cada geração, o trono dos consoles pode mudar de dono

Eu esperava (H3): que a Sony (PlayStation) sempre tivesse dominado o mercado.

por_geracao <- jogos |>
  filter(geracao >= 5, fabricante %in% c("Sony", "Nintendo", "Microsoft")) |>
  group_by(geracao, fabricante) |>
  summarise(vendas = sum(vendas_global), .groups = "drop") |>
  group_by(geracao) |>
  mutate(part = vendas / sum(vendas)) |>
  ungroup()

ggplot(por_geracao, aes(factor(geracao), part, color = fabricante, group = fabricante)) +
  geom_line(linewidth = 1.4) +
  geom_point(size = 3.5) +
  geom_text(data = filter(por_geracao, geracao == 8), aes(label = fabricante),
            hjust = -0.25, fontface = "bold", size = 4.3) +
  annotate("label", x = 3, y = 0.62, label = "Wii + DS:\na Nintendo assume a liderança",
           size = 3.8, color = cores_fab[["Nintendo"]], label.size = 0) +
  scale_color_manual(values = cores_fab, guide = "none") +
  scale_x_discrete(labels = c("5ª\n(PS1, N64)", "6ª\n(PS2, Xbox, GC)",
                              "7ª\n(Wii, PS3, X360)", "8ª\n(PS4, XOne, WiiU)"),
                   expand = expansion(add = c(0.4, 0.9))) +
  scale_y_continuous(labels = pct, limits = c(0, 0.8)) +
  labs(title = "A cada geração de consoles, o trono pode mudar de dono",
       subtitle = "Participação nas vendas de jogos de cada geração (Sony, Nintendo e Microsoft)",
       x = "Geração", y = NULL) +
  tema

Insight. A Sony liderou com folga a 5ª e a 6ª gerações (PS1 e PS2), mas a Nintendo virou o jogo na 7ª com Wii e DS, consoles que atraíram um público casual e familiar. A Sony só voltou à frente na 8ª, com o PS4, e a Microsoft se firmou como terceira força. Apostar na plataforma certa exige olhar a geração atual, não o histórico.

⚠️ H3 parcialmente refutada

4.5 Nota importa, e muito, mas não garante nada

Eu esperava (H4): que jogos com nota alta vendessem mais e que jogos com nota baixa fracassassem.

por_faixa <- jogos_avaliados |>
  group_by(faixa_nota) |>
  summarise(mediana = median(vendas_global), pct_hit = mean(hit), n = n())

razao    <- por_faixa$mediana[5] / por_faixa$mediana[1]
spearman <- cor.test(jogos_avaliados$nota_critica, jogos_avaliados$vendas_global,
                     method = "spearman", exact = FALSE)
kruskal  <- kruskal.test(vendas_global ~ faixa_nota, data = jogos_avaliados)

Vendas por faixa de nota

ggplot(por_faixa, aes(faixa_nota, mediana, fill = faixa_nota == "90+")) +
  geom_col(width = 0.65) +
  geom_text(aes(label = paste0(br(mediana * 1000), " mil")), vjust = -0.5, size = 4.2) +
  scale_fill_manual(values = c(`FALSE` = cinza, `TRUE` = azul), guide = "none") +
  scale_y_continuous(labels = label_number(scale = 1000, suffix = " mil", big.mark = "."),
                     expand = expansion(mult = c(0, 0.12))) +
  labs(title = paste0("Jogos nota 90+ vendem ", round(razao), "× mais que os de nota abaixo de 50"),
       subtitle = "Vendas medianas por faixa de nota da crítica (jogos de 2000 a 2016)",
       x = "Nota da crítica (Metacritic)", y = "Vendas medianas") +
  tema

Explorar os jogos (interativo)

g <- ggplot(jogos_avaliados,
            aes(nota_critica, vendas_global,
                text = paste0("<b>", nome, "</b> (", plataforma, ", ", ano, ")<br>",
                              "Nota: ", nota_critica, "<br>Vendas: ",
                              br(vendas_global, 0.01), " mi"))) +
  geom_point(alpha = 0.35, color = azul, size = 1.4) +
  scale_y_log10(labels = label_number(suffix = " mi", accuracy = 0.01)) +
  labs(title = "Cada ponto é um jogo: passe o mouse para ver qual",
       x = "Nota da crítica", y = "Vendas globais (escala log)") +
  tema

ggplotly(g, tooltip = "text")

Hits com nota mediana

jogos_avaliados |>
  filter(nota_critica < 70) |>
  slice_max(vendas_global, n = 8) |>
  transmute(Jogo = nome, Plataforma = plataforma, `Nota da crítica` = nota_critica,
            `Vendas (mi)` = vendas_global) |>
  kbl(caption = "Os maiores sucessos com nota abaixo de 70") |>
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Os maiores sucessos com nota abaixo de 70
Jogo Plataforma Nota da crítica Vendas (mi)
Wii Play Wii 58 28.92
Kinect Adventures! X360 61 21.81
Wii Party Wii 68 8.38
Mario Party 8 Wii 62 8.27
Just Dance Wii 49 7.20
Cooking Mama DS 67 5.63
Crash Bandicoot: The Wrath of Cortex PS2 66 5.42
Medal of Honor: Rising Sun PS2 68 5.13

Insight. A nota pesa mais do que eu imaginava: 63% dos jogos nota 90+ passam de 1 milhão de cópias, contra 5% dos jogos abaixo de 50. A relação é estatisticamente significativa e moderada (Spearman ρ = 0,37, p < 0,001), e as faixas de nota diferem entre si (Kruskal-Wallis, p < 0,001). Mesmo assim, títulos casuais e familiares do Wii, como Wii Play e Kinect Adventures!, venderam dezenas de milhões com notas entre 50 e 60: acertar o público pode valer tanto quanto agradar a crítica.

Cuidado. Correlação não é causalidade. Jogos de grandes franquias recebem mais orçamento, o que tende a melhorar a nota e o marketing ao mesmo tempo.

⚠️ H4 parcialmente confirmada

4.6 Crítica e público: um divórcio que começa em 2010

Eu esperava (H5): que crítica e público concordassem sobre o que é um bom jogo.

Ao longo do tempo

dif_ano <- jogos_avaliados |> group_by(ano) |> summarise(dif = mean(dif_notas))

ggplot(dif_ano, aes(ano, dif)) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 0, ymax = Inf, fill = destaque, alpha = 0.06) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0, fill = azul, alpha = 0.06) +
  geom_hline(yintercept = 0, color = "grey40") +
  geom_line(linewidth = 1.3, color = "grey20") +
  geom_point(aes(color = dif > 0), size = 3) +
  scale_color_manual(values = c(`FALSE` = azul, `TRUE` = destaque), guide = "none") +
  annotate("text", x = 2000.2, y = 5.3, hjust = 0, color = destaque, fontface = "bold",
           label = "Crítica avalia MELHOR que o público") +
  annotate("text", x = 2016, y = -5.6, hjust = 1, color = azul, fontface = "bold",
           label = "Público avalia MELHOR que a crítica") +
  scale_x_continuous(breaks = seq(2000, 2016, 2)) +
  labs(title = "Desde 2010, crítica e público estão cada vez mais distantes",
       subtitle = "Diferença média entre a nota da crítica e a do usuário (ambas de 0 a 100)",
       x = NULL, y = "Crítica − público (pontos)") +
  tema

Por gênero

dif_genero <- jogos_avaliados |>
  group_by(genero) |>
  summarise(dif = mean(dif_notas)) |>
  mutate(genero = fct_reorder(genero, dif))

ggplot(dif_genero, aes(dif, genero, fill = dif > 0)) +
  geom_col(width = 0.7) +
  geom_vline(xintercept = 0, color = "grey40") +
  scale_fill_manual(values = c(`FALSE` = azul, `TRUE` = destaque), guide = "none") +
  labs(title = "Em Esportes, a crítica gosta bem mais que o público",
       subtitle = "Diferença média crítica − público, por gênero (pontos)",
       x = "Crítica − público (pontos)", y = NULL) +
  tema

Insight. As duas notas andam juntas de forma apenas moderada (r = 0,57), e a relação se inverteu com o tempo: até 2008 o público era mais generoso que a crítica; desde 2011, a crítica dá em média cerca de 5 pontos a mais. O maior desencontro é em Esportes (FIFA, Madden), cujo público tende a punir lançamentos anuais parecidos entre si. A crítica sozinha não representa o jogador.

❌ H5 refutada

4.7 Jogos para adultos viram hit com o dobro de frequência (preliminar)

Eu esperava (H6): que a classificação etária não fizesse diferença nas vendas.

por_class <- jogos |>
  filter(ano >= 2000, classificacao %in% c("E", "E10+", "T", "M")) |>
  group_by(classificacao) |>
  summarise(pct_hit = mean(hit), n = n())

ggplot(por_class, aes(classificacao, pct_hit, fill = classificacao == "M")) +
  geom_col(width = 0.6) +
  geom_text(aes(label = pct(pct_hit)), vjust = -0.5, size = 4.5) +
  scale_fill_manual(values = c(`FALSE` = cinza, `TRUE` = destaque), guide = "none") +
  scale_x_discrete(labels = c("E\n(Livre)", "E10+\n(10+)", "T\n(Adolescente)", "M\n(Adulto 17+)")) +
  scale_y_continuous(labels = pct, expand = expansion(mult = c(0, 0.15))) +
  labs(title = "Jogos para adultos têm quase o dobro de chance de virar um hit",
       subtitle = "% de jogos com mais de 1 milhão de cópias, por classificação ESRB (2000–2016)",
       x = NULL, y = NULL) +
  tema

Insight. 22% dos jogos com classificação M viram hit, contra cerca de 12% nas demais classificações.

Cuidado. A classificação provavelmente não “causa” as vendas: o público adulto concentra as grandes franquias de alto orçamento (GTA, Call of Duty). Na versão final, vou comparar dentro de cada gênero para separar esses efeitos.

❌ H6 refutada

4.8 Quanto mais plataformas, mais vende cada versão (preliminar)

Eu esperava (H7): que lançar em várias plataformas só dividisse o público, sem ganho por versão.

por_np <- jogos |>
  mutate(grupo = factor(if_else(n_plataformas >= 5, "5+", as.character(n_plataformas)),
                        levels = c("1", "2", "3", "4", "5+"))) |>
  group_by(grupo) |>
  summarise(mediana = median(vendas_global))

ggplot(por_np, aes(grupo, mediana, fill = grupo == "1")) +
  geom_col(width = 0.6) +
  geom_text(aes(label = paste0(br(mediana * 1000), " mil")), vjust = -0.5, size = 4.2) +
  scale_fill_manual(values = c(`FALSE` = azul, `TRUE` = cinza), guide = "none") +
  scale_y_continuous(labels = label_number(scale = 1000, suffix = " mil"),
                     expand = expansion(mult = c(0, 0.15))) +
  labs(title = "Quanto mais plataformas, mais vende CADA versão do jogo",
       subtitle = "Vendas medianas por versão, segundo o número de plataformas do título",
       x = "Número de plataformas do título", y = NULL) +
  tema

Insight. Em vez de dividir o público, os jogos multiplataforma vendem mais por versão: a mediana sai de 130 mil (exclusivos) para 390 mil (5 ou mais plataformas).

Cuidado. Só grandes produções costumam sair em todas as plataformas, então o efeito pode refletir o orçamento, e não a estratégia de lançamento em si.

❌ H7 refutada

4.9 Placar: o que sobrou das minhas suposições

O que eu achava O que os dados mostraram Veredito
H1 Vendas bem distribuídas 10% dos jogos = 59% das vendas ❌ Refutada
H2 Gênero universal RPG: 27% no Japão × 8% na América do Norte ❌ Refutada
H3 Sony sempre na frente Nintendo liderou a 7ª geração ⚠️ Parcial
H4 Nota alta vende, nota baixa fracassa 90+ vende 10× mais, mas há hits com nota 50–60 ⚠️ Parcial
H5 Crítica e público concordam Divergência crescente desde 2010 ❌ Refutada
H6 Classificação etária não importa Jogos M: 22% viram hit ❌ Refutada
H7 Multiplataforma divide o público Mais plataformas = mais vendas por versão ❌ Refutada

Das 7 suposições do senso comum, nenhuma sobreviveu intacta. É justamente aí que a análise de dados se paga.

5. Conclusões (preliminar)

5.1 O problema

Investiguei o que está associado ao sucesso comercial de um jogo: a qualidade (nota), o gênero, a plataforma, o mercado, a classificação etária ou a estratégia de lançamento.

5.2 Como abordei

Usei a base Video Game Sales with Ratings (16.719 registros, VGChartz + Metacritic). Padronizei quatro formas diferentes de dado ausente, corrigi tipos, removi registros inválidos, recodifiquei fatores, raspei a Wikipédia para obter fabricante e geração de cada console e mesclei as tabelas. Depois, testei sete suposições do senso comum com medianas, proporções, gráficos e testes não paramétricos.

5.3 O que descobri

  • É um mercado de hits: 10% dos jogos concentram 59% das vendas, e 1 em cada 3 não passa de 100 mil cópias.
  • O gênero depende do mercado: RPG domina o Japão; Tiro e Ação dominam o Ocidente.
  • A liderança dos consoles muda de geração em geração.
  • A nota importa muito (90+ vende cerca de 10× mais), mas não é obrigatória nem suficiente.
  • Crítica e público divergem cada vez mais, sobretudo em franquias anuais.

5.4 Implicações para os potenciais clientes

  • Publicadoras: pensar em portfólio, escolher o gênero junto com o mercado-alvo e acompanhar a geração atual de consoles.
  • Estúdios: investir em qualidade, mas também em acertar o público: jogos casuais com nota mediana estão entre os maiores sucessos.
  • Imprensa e jogadores: considerar a nota do público ao lado da nota da crítica.

5.5 Limitações e como evoluir

  • A base cobre só vendas físicas até 2016: downloads e jogos de celular, hoje dominantes, não aparecem.
  • As vendas do VGChartz são estimativas, não números oficiais.
  • Correlação não é causalidade, e faltam variáveis como orçamento e marketing.
  • Próximos passos: comparar as classificações etárias dentro de cada gênero, analisar as publicadoras e incluir dados de vendas digitais.

Referências