1. Introdução

Jogos de cartas colecionáveis (TCGs, Trading Card Games) criam um ecossistema econômico e competitivo interessante. Para manter a base de jogadores engajada ao longo dos anos, é comum adotar uma prática de game design chamada “Power Creep”: novos produtos tornam-se progressivamente mais poderosos e, frequentemente, mais complexos.

O problema central desta análise é verificar se o power creep aparece na complexidade textual das cartas de Yu-Gi-Oh! e como essa complexidade se relaciona com as cartas que a editora decide banir. Em outras palavras, as cartas com textos mais longos são as que mais aparecem na lista de banidas? E quais mecânicas são mais frequentes entre essas cartas?

Um detalhe importante: o poder de uma carta não é medido diretamente nos dados. O banimento em competição funciona como um indicador indireto de poder excessivo, e é esse indicador que será usado para responder às perguntas acima.

Para isso, usei a base com todas as cartas lançadas no jogo (14.653 registros no snapshot utilizado). Apliquei técnicas de manipulação de strings e expressões regulares para transformar o texto das cartas em dados estruturados. Essas informações interessam tanto a designers de jogos, que precisam entender os limites de complexidade de seus sistemas, quanto a jogadores e colecionadores, que acompanham o mercado de cartas.

Uma das cartas mais simples, antigas e populares do jogo
Um exemplo de design de uma carta recente

2. Pacotes Requeridos

Os pacotes abaixo são carregados no início do documento. Para replicar a análise, basta instalá-los antes de renderizar. O código de carregamento é este:

library(readr)
library(dplyr)
library(tidyr)
library(stringr)
library(lubridate)
library(ggplot2)
library(plotly)
library(scales)
library(DT)
library(knitr)

Cada pacote tem o seguinte papel:

  • readr: importação do arquivo CSV com detecção automática de tipos e de encoding UTF-8.
  • dplyr: verbos de manipulação (filter, mutate, group_by, summarise), usados em toda a limpeza.
  • tidyr: pivot_longer, para reorganizar a tabela de mecânicas em formato longo.
  • stringr: funções de texto e expressões regulares, usadas para contar palavras e identificar mecânicas nos efeitos.
  • lubridate: conversão de datas de lançamento e extração do ano.
  • ggplot2: base de todos os gráficos estáticos.
  • plotly: transforma gráficos ggplot2 em gráficos interativos, e combina vários deles com subplot.
  • scales: formatação de percentuais nos eixos e nos textos (percent_format, percent).
  • DT: renderiza tabelas interativas com busca, filtros e paginação.
  • knitr: função kable, usada para as tabelas estáticas de resumo.

3. Preparação, Limpeza e Mineração de Texto

3.1 Origem dos dados

Os dados foram obtidos da API do YGOPRODECK.com, uma base de dados de cartas, baralhos e artigos sobre Yu-Gi-Oh!. Para garantir reprodutibilidade, a análise usa uma cópia estática publicada no Kaggle, Yu-Gi-Oh! Cards Dataset. O arquivo utilizado é cards.csv, que deve estar na mesma pasta do .Rmd para que o documento seja renderizado.

3.2 Descrição dos dados de origem

A base foi criada para documentar cartas do jogo, e não para análises de comportamento de mercado ou de competição. Cada linha corresponde a uma carta, com 29 variáveis: identificação e nome, tipo, texto do efeito, atributos de combate (ataque, defesa, nível), dados de arquétipo e de escala, status de banimento e de staple, datas de lançamento e links de imagem.

  • Data de coleta do snapshot: 08/11/2026. Como a API do YGOPRODECK é atualizada continuamente, o número de cartas pode diferir do valor consultado diretamente na API em outra data.
  • Valores ausentes: cartas de Magia e Armadilha não têm Ataque, Defesa nem Nível. Na base, esses campos estão vazios para 5.002 cartas desse tipo. Campos vazios são lidos como NA pelo R, sem risco de virar zero.
  • Datas de lançamento: 517 cartas não têm data de lançamento no TCG. Em geral, são cartas que nunca foram lançadas nesse formato, e por isso ficam fora da análise.
  • Banimento (ban_tcg): campo com o status atual da carta na lista do TCG. Os valores são Forbidden (proibida, 155 cartas), Limited (limite de uma cópia, 91) e Semi-Limited (limite de duas cópias, 7). Cartas sem restrição ficam vazias. Como o campo mostra o status atual, cartas que já foram banidas e depois liberadas aparecem como permitidas.
  • Staple (staple): campo que marca cartas de uso amplo em diversos tipos de baralho. Ele é preenchido com Yes em 64 cartas.
  • Tipo (type): texto que descreve a categoria da carta, como Spell Card, Trap Card, diferentes tipos de monstro, além de Skill Card (cartas de habilidade) e Token (fichas).

3.3 Limpeza e engenharia de variáveis

A importação está no chunk setup, com encoding UTF-8 (necessário para nomes com acentos) e guess_max alto, para que o tipo das colunas seja detectado corretamente em um arquivo grande.

A limpeza segue cinco etapas, nesta ordem:

  1. Datas: a coluna tcg_date é convertida para data e o ano de lançamento é extraído. Sem isso, não é possível estudar a evolução ao longo do tempo. Cartas sem data, ou com ano fora do intervalo analisado, são removidas.
  2. Categorias: o campo type é agrupado em três categorias principais (Monstro, Magia e Armadilha). Cartas de habilidade e fichas não se encaixam nessas categorias e vão para “Other”, para que possam ser contadas e excluídas das análises por categoria.
  3. Estatísticas de combate: cartas de Magia e Armadilha recebem NA em Ataque, Defesa e Nível. Como esses campos já estão vazios na base, a conversão funciona como garantia: se a base for atualizada e trouxer zeros para essas cartas, os valores continuarão corretos.
  4. Status: banimento e staple viram variáveis lógicas (verdadeiro ou falso). Em is_banned, usei apenas Forbidden, pois Limited e Semi-Limited são restrições de cópias, e não banimentos. As restritas aparecem em is_restrita.
  5. Mineração de texto: a partir do texto do efeito, são criadas as medidas de tamanho (caracteres e palavras) e as flags de mecânicas. Como o padrão de texto do jogo (PSCT) foi adotado em 2011, a análise de efeitos rápidos começa nesse ano (seção 4.3).
# Contagens de exclusão, calculadas antes do filtro
n_raw            <- nrow(cards_raw)
n_sem_data       <- sum(is.na(cards_raw$tcg_date))
n_fora_intervalo <- sum(!is.na(suppressWarnings(year(ymd(as.character(cards_raw$tcg_date))))) &
                        (year(ymd(as.character(cards_raw$tcg_date))) < 1999 |
                         year(ymd(as.character(cards_raw$tcg_date))) > year(Sys.Date())))

cards <- cards_raw %>%
  mutate(
    # 1. Datas
    tcg_date_parsed = suppressWarnings(ymd(as.character(tcg_date))),
    year_lancamento = year(tcg_date_parsed),

    # 2. Categoria principal da carta
    tipo_categoria = case_when(
      str_detect(type, regex("Spell",   ignore_case = TRUE)) ~ "Spell",
      str_detect(type, regex("Trap",    ignore_case = TRUE)) ~ "Trap",
      str_detect(type, regex("Monster", ignore_case = TRUE)) ~ "Monster",
      TRUE ~ "Other"
    ),

    # 3. Atributos de combate só existem para monstros
    atk   = if_else(tipo_categoria %in% c("Spell", "Trap"), NA_real_, as.numeric(atk)),
    def   = if_else(tipo_categoria %in% c("Spell", "Trap"), NA_real_, as.numeric(def)),
    level = if_else(tipo_categoria %in% c("Spell", "Trap"), NA_real_, as.numeric(level)),

    # 4. Status de banimento (apenas "Forbidden" é banimento) e staple
    is_banned   = !is.na(ban_tcg) & ban_tcg == "Forbidden",
    is_restrita = !is.na(ban_tcg) & ban_tcg != "",
    is_staple   = !is.na(staple) & str_to_lower(staple) == "yes",

    # 5. Tamanho do texto e mecânicas via regex
    tamanho_texto = nchar(desc, type = "chars"),
    qtd_palavras  = str_count(desc, "\\S+"),

    # Efeito rápido: marcação explícita ou habilidade ativável no turno do oponente
    eff_quick   = str_detect(desc, regex("\\(Quick Effect\\)|During either player's turn", ignore_case = TRUE)),

    # Invocação Especial, excluindo restrições do tipo "cannot Special Summon"
    eff_special = str_detect(desc, regex("Special Summon", ignore_case = TRUE)) &
                  !str_detect(desc, regex("cannot (be )?Special Summon", ignore_case = TRUE)),

    # Destruição, excluindo cartas de proteção ("cannot be destroyed")
    eff_destroy = str_detect(desc, regex("\\bdestroys?\\b|\\bdestroyed\\b", ignore_case = TRUE)) &
                  !str_detect(desc, regex("cannot be destroyed|can't be destroyed", ignore_case = TRUE)),

    # Busca: a frase fica limitada a uma sentença ([^.]*), para não atravessar frases
    eff_search  = str_detect(desc, regex("add [^.]* to your hand|add [^.]* from your Deck", ignore_case = TRUE)),
    eff_negate  = str_detect(desc, regex("\\bnegate", ignore_case = TRUE)),
    eff_draw    = str_detect(desc, regex("\\bdraw", ignore_case = TRUE))
  ) %>%
  # Mantém apenas cartas com data válida, do início do TCG até o ano atual
  filter(!is.na(year_lancamento),
         year_lancamento >= 1999,
         year_lancamento <= year(Sys.Date()))

# Subconjunto usado nas tendências: exclui o ano corrente, que é parcial
cards_completos <- cards %>%
  filter(year_lancamento < year(Sys.Date()))

n_removidas <- n_raw - nrow(cards)

Após a limpeza, a base tem 14.133 cartas válidas. Foram removidas 520 cartas: 517 sem data de lançamento no TCG e as demais por terem ano fora do intervalo analisado (3 cartas, sendo as mais antigas anteriores a 1999 e as mais recentes com data futura). Dentro da base, 155 cartas estão proibidas, 252 têm alguma restrição de cópias e 64 são staples. Cartas de habilidade e fichas, que ficaram em “Other”, somam 151. O texto médio tem 56.4 palavras.

3.4 Visão do dataset limpo

A base final tem 14133 linhas e 43 colunas. Para não imprimir todas as linhas, a tabela abaixo mostra apenas as 8 primeiras cartas e as colunas usadas nas análises.

cards %>%
  select(name, type, tipo_categoria, year_lancamento, atk, def,
         qtd_palavras, is_banned, is_staple) %>%
  head(8) %>%
  kable(caption = "Primeiras 8 cartas do dataset limpo (colunas selecionadas)")
Primeiras 8 cartas do dataset limpo (colunas selecionadas)
name type tipo_categoria year_lancamento atk def qtd_palavras is_banned is_staple
“A Case for K9” Spell Card Spell 2025 NA NA 92 FALSE FALSE
“A” Cell Breeding Device Spell Card Spell 2007 NA NA 16 FALSE FALSE
“A” Cell Incubator Spell Card Spell 2007 NA NA 32 FALSE FALSE
“A” Cell Recombination Device Spell Card Spell 2016 NA NA 66 FALSE FALSE
“A” Cell Scatter Burst Spell Card Spell 2007 NA NA 22 FALSE FALSE
“Infernoble Arms - Almace” Spell Card Spell 2023 NA NA 91 FALSE FALSE
“Infernoble Arms - Durendal” Spell Card Spell 2020 NA NA 95 FALSE FALSE
“Infernoble Arms - Hauteclere” Spell Card Spell 2020 NA NA 91 FALSE FALSE

3.5 Resumo das variáveis de interesse

A tabela abaixo resume as variáveis usadas nas análises, com tipo, descrição e percentual de valores ausentes.

vars_interesse <- c("type", "tipo_categoria", "atk", "def", "level",
                    "year_lancamento", "qtd_palavras", "tamanho_texto",
                    "is_banned", "is_restrita", "is_staple",
                    "eff_quick", "eff_special", "eff_destroy",
                    "eff_search", "eff_negate", "eff_draw")

descricoes <- c(
  "Tipo textual original da carta",
  "Categoria principal: Monster, Spell, Trap ou Other",
  "Pontos de ataque (apenas monstros)",
  "Pontos de defesa (apenas monstros)",
  "Nível ou rank do monstro (apenas monstros)",
  "Ano de lançamento no TCG",
  "Palavras no texto do efeito",
  "Caracteres no texto do efeito",
  "Verdadeiro se a carta está proibida (Forbidden) no TCG",
  "Verdadeiro se a carta tem limite de cópias (Limited ou Semi-Limited)",
  "Verdadeiro se a carta é uma staple",
  "Detecta efeito rápido",
  "Detecta Invocação Especial",
  "Detecta destruição",
  "Detecta busca por cartas",
  "Detecta negação",
  "Detecta compra de cartas"
)

resumo_vars <- tibble(
  Variável   = vars_interesse,
  Tipo       = vapply(cards[vars_interesse], function(x) class(x)[1], character(1)),
  `% NA`     = round(vapply(cards[vars_interesse], function(x) mean(is.na(x)) * 100, numeric(1)), 1),
  Descrição  = descricoes
)

kable(resumo_vars, caption = "Variáveis de interesse no dataset limpo")
Variáveis de interesse no dataset limpo
Variável Tipo % NA Descrição
type character 0.0 Tipo textual original da carta
tipo_categoria character 0.0 Categoria principal: Monster, Spell, Trap ou Other
atk numeric 35.7 Pontos de ataque (apenas monstros)
def numeric 39.0 Pontos de defesa (apenas monstros)
level numeric 38.2 Nível ou rank do monstro (apenas monstros)
year_lancamento numeric 0.0 Ano de lançamento no TCG
qtd_palavras integer 0.0 Palavras no texto do efeito
tamanho_texto integer 0.0 Caracteres no texto do efeito
is_banned logical 0.0 Verdadeiro se a carta está proibida (Forbidden) no TCG
is_restrita logical 0.0 Verdadeiro se a carta tem limite de cópias (Limited ou Semi-Limited)
is_staple logical 0.0 Verdadeiro se a carta é uma staple
eff_quick logical 0.0 Detecta efeito rápido
eff_special logical 0.0 Detecta Invocação Especial
eff_destroy logical 0.0 Detecta destruição
eff_search logical 0.0 Detecta busca por cartas
eff_negate logical 0.0 Detecta negação
eff_draw logical 0.0 Detecta compra de cartas

4. Análise Exploratória

4.1 O texto das cartas está ficando mais longo?

Para observar a tendência, agrupei as cartas por ano de lançamento e calculei a média de palavras de cada ano. Os anos completos são usados, para que o ano corrente (ainda parcial) não distorça a curva.

yearly <- cards_completos %>%
  group_by(year_lancamento) %>%
  summarise(n_cards          = n(),
            media_palavras   = mean(qtd_palavras,  na.rm = TRUE),
            media_chars      = mean(tamanho_texto, na.rm = TRUE),
            mediana_palavras = median(qtd_palavras, na.rm = TRUE),
            .groups = "drop")
p_palavras <- ggplot(yearly, aes(year_lancamento, media_palavras)) +
  geom_line(color = "#2c7fb8", linewidth = 1) +
  geom_point(size = 2) +
  geom_smooth(method = "lm", se = TRUE, linetype = "dashed", color = "firebrick") +
  labs(x = "Ano de lançamento (TCG)",
       y = "Média de palavras por carta",
       title = "Complexidade textual dos efeitos ao longo do tempo") +
  theme_minimal(base_size = 12)

ggplotly(p_palavras)
modelo_tendencia <- lm(qtd_palavras ~ year_lancamento, data = cards_completos)
coef_tend <- summary(modelo_tendencia)$coefficients["year_lancamento", ]
ci_tend   <- confint(modelo_tendencia)["year_lancamento", ]
r2_tend   <- summary(modelo_tendencia)$r.squared

Em um modelo linear simples, cada ano de lançamento está associado a um aumento médio de 2.89 palavras no texto da carta (IC 95%: 2.84 a 2.95). O ano explica 46.0% da variação no número de palavras. Ou seja, a tendência existe, mas o tempo não explica a maior parte da variação entre cartas: há cartas longas e curtas no mesmo período.

Quanto aos caracteres, o padrão é semelhante e está no gráfico abaixo.

p_chars <- ggplot(yearly, aes(year_lancamento, media_chars)) +
  geom_line(color = "#31a354", linewidth = 1) +
  geom_point(size = 2) +
  geom_smooth(method = "lm", se = TRUE, linetype = "dashed", color = "firebrick") +
  labs(x = "Ano de lançamento (TCG)",
       y = "Média de caracteres por carta",
       title = "Comprimento do texto de efeito ao longo do tempo") +
  theme_minimal(base_size = 12)

ggplotly(p_chars)

4.2 Cartas banidas e staples: diferem no tamanho do texto?

Comparei as médias de palavras entre cartas proibidas e permitidas, e entre staples e demais cartas.

media_status <- cards %>%
  mutate(Status = if_else(is_banned, "Banida", "Permitida")) %>%
  group_by(Status) %>%
  summarise(n = n(), media_palavras = mean(qtd_palavras), .groups = "drop")

media_staple <- cards %>%
  mutate(Status = if_else(is_staple, "Staple", "Comum")) %>%
  group_by(Status) %>%
  summarise(n = n(), media_palavras = mean(qtd_palavras), .groups = "drop")

p_banidas <- ggplot(media_status, aes(x = Status, y = media_palavras, fill = Status)) +
  geom_col(show.legend = FALSE) +
  scale_fill_manual(values = c("Banida" = "firebrick", "Permitida" = "gray70")) +
  labs(x = "", y = "Média de palavras", title = "Banidas vs permitidas") +
  theme_minimal(base_size = 12)

p_staples <- ggplot(media_staple, aes(x = Status, y = media_palavras, fill = Status)) +
  geom_col(show.legend = FALSE) +
  scale_fill_manual(values = c("Staple" = "#e7da00", "Comum" = "gray70")) +
  labs(x = "", y = "Média de palavras", title = "Staples vs comuns") +
  theme_minimal(base_size = 12)

subplot(ggplotly(p_banidas), ggplotly(p_staples), nrows = 1, margin = 0.05) %>%
  layout(title = "Média de palavras por status", showlegend = FALSE)

As médias brutas são: banidas 60.4 palavras contra 56.3 das permitidas; staples 51.7 contra 56.4 das comuns. A diferença bruta entre banidas e permitidas é de 4 palavras. Como o texto cresce com o tempo (seção 4.1), parte dessa diferença poderia vir da época de lançamento das cartas. Por isso, a comparação a seguir é feita ano a ano.

comp_ano <- cards_completos %>%
  mutate(Status = if_else(is_banned, "Banida", "Permitida")) %>%
  group_by(year_lancamento, Status) %>%
  summarise(media = mean(qtd_palavras), n = n(), .groups = "drop") %>%
  filter(n >= 5)  # descarta anos com poucas cartas de um dos grupos

p_comp <- ggplot(comp_ano, aes(year_lancamento, media, color = Status)) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.8) +
  scale_color_manual(values = c("Banida" = "firebrick", "Permitida" = "gray60")) +
  labs(x = "Ano de lançamento (TCG)",
       y = "Média de palavras",
       color = "Status",
       title = "Banidas vs permitidas, ano a ano") +
  theme_minimal(base_size = 12)

ggplotly(p_comp)
# Regressão com controle para o ano e para o status de staple
modelo_ajustado <- lm(qtd_palavras ~ year_lancamento + is_banned + is_staple,
                      data = cards_completos)
coef_ban_aj <- summary(modelo_ajustado)$coefficients["is_bannedTRUE", ]
ci_ban_aj   <- confint(modelo_ajustado)["is_bannedTRUE", ]

Com o ano controlado, a diferença associada ao banimento é de 7.09 palavras (IC 95%: 3.67 a 10.51). Esse é o valor mais informativo desta seção, porque compara cartas lançadas na mesma época. Ele é maior que a diferença bruta, o que mostra que o controle pelo ano não explica essa diferença. Ainda assim, ele descreve uma associação, e não uma causa. Outros fatores, como o tipo da carta e o momento em que a editora decidiu banir, não entram no modelo.

Para as staples, o número de casos é pequeno (64 cartas), então a estimativa é menos precisa. A densidade abaixo mostra a distribuição completa, e não só a média.

p_dens_ban <- cards %>%
  mutate(Status = if_else(is_banned, "Banida", "Permitida")) %>%
  ggplot(aes(x = qtd_palavras, fill = Status)) +
  geom_density(alpha = 0.6, color = NA) +
  scale_fill_manual(values = c("Banida" = "firebrick", "Permitida" = "gray70")) +
  labs(x = "Palavras no texto", y = "Densidade", fill = "Status",
       title = "Banidas vs permitidas") +
  theme_minimal(base_size = 12)

p_dens_staple <- cards %>%
  mutate(Status = if_else(is_staple, "Staple", "Comum")) %>%
  ggplot(aes(x = qtd_palavras, fill = Status)) +
  geom_density(alpha = 0.6, color = NA) +
  scale_fill_manual(values = c("Staple" = "#e7da00", "Comum" = "gray70")) +
  labs(x = "Palavras no texto", y = "Densidade", fill = "Status",
       title = "Staples vs comuns") +
  theme_minimal(base_size = 12)

subplot(ggplotly(p_dens_ban), ggplotly(p_dens_staple), nrows = 1, margin = 0.05) %>%
  layout(title = "Distribuição do tamanho do texto", showlegend = FALSE)

A densidade mostra onde está a maior parte das cartas em cada grupo. Ela ajuda a verificar se a média é representativa ou se é puxada por poucas cartas muito longas.

4.3 Mecânicas de interação: os efeitos rápidos estão se espalhando?

Efeitos rápidos (Quick Effects) podem ser usados mesmo no turno do oponente, o que aumenta a interação durante a partida. Para observar a evolução, calculei a proporção de monstros com esse tipo de efeito a partir de 2011, ano em que o jogo adotou o padrão PSCT de descrição de efeitos (seção 3.3).

quick_effects_tbl <- cards_completos %>%
  filter(tipo_categoria == "Monster", year_lancamento >= 2011) %>%
  group_by(year_lancamento) %>%
  summarise(n_monsters = n(),
            n_quick    = sum(eff_quick, na.rm = TRUE),
            pct_quick  = n_quick / n_monsters,
            .groups = "drop")

p_quick <- ggplot(quick_effects_tbl, aes(x = year_lancamento, y = pct_quick)) +
  geom_line(color = "#9b59b6", linewidth = 1.2) +
  geom_point(color = "black", size = 2) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  labs(x = "Ano de lançamento (TCG)",
       y = "% de monstros com efeito rápido",
       title = "Monstros com efeitos rápidos ao longo do tempo") +
  theme_minimal(base_size = 12)

ggplotly(p_quick)
quick_ini <- quick_effects_tbl %>% slice_min(year_lancamento, n = 1)
quick_fim <- quick_effects_tbl %>% slice_max(year_lancamento, n = 1)

Em 2011, 5.2% dos monstros lançados tinham efeito rápido. Em 2025, a proporção chegou a 30.4%. A tendência geral é de crescimento, com algumas quedas pontuais de um ano para o outro.

4.4 Quais mecânicas aparecem mais nas cartas banidas?

Proporção de banidas por ano

banned_tbl <- cards_completos %>%
  group_by(year_lancamento) %>%
  summarise(n_total  = n(),
            n_banned = sum(is_banned),
            pct      = n_banned / n_total,
            .groups = "drop")

p_ban_ano <- ggplot(banned_tbl, aes(year_lancamento, pct)) +
  geom_col(fill = "firebrick") +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  labs(x = "Ano de lançamento (TCG)",
       y = "% de cartas banidas",
       title = "Proporção de cartas banidas por ano") +
  theme_minimal(base_size = 12)

ggplotly(p_ban_ano)

A proporção de banidas é baixa em todos os anos e varia bastante. Ela depende tanto do número de cartas lançadas quanto do tempo que cada carta teve para ser avaliada pela editora. Como o campo ban_tcg mostra apenas o status atual, cartas recentes ainda não tiveram tempo de ser banidas, o que é outra razão para tratar os últimos anos com cautela.

Mecânicas: presença entre banidas e permitidas

Uma mecânica ser comum entre banidas não basta para afirmar que ela aumenta a chance de banimento: ela também pode ser comum entre todas as cartas. Por isso, a comparação relevante é a razão entre a proporção da mecânica nas banidas e a proporção nas permitidas. Razão maior que 1 indica que a mecânica aparece mais entre as banidas; menor que 1, que aparece menos.

mecanicas <- c(
  "Efeito Rápido"      = "eff_quick",
  "Invocação Especial" = "eff_special",
  "Destruição"         = "eff_destroy",
  "Busca no Deck"      = "eff_search",
  "Negação"            = "eff_negate",
  "Comprar Cartas"     = "eff_draw"
)

mec_tbl <- tibble(Mecanica = names(mecanicas), coluna = unname(mecanicas)) %>%
  mutate(
    pct_banidas    = vapply(coluna, function(col) mean(cards[[col]][cards$is_banned]),  numeric(1)),
    pct_permitidas = vapply(coluna, function(col) mean(cards[[col]][!cards$is_banned]), numeric(1)),
    razao          = pct_banidas / pct_permitidas
  )

# Função auxiliar para citar a razão de uma mecânica no texto
razao_de <- function(nome) round(mec_tbl$razao[mec_tbl$Mecanica == nome], 2)

kable(
  mec_tbl %>%
    transmute(Mecânica = Mecanica,
              `% nas banidas`    = percent(pct_banidas, accuracy = 0.1),
              `% nas permitidas` = percent(pct_permitidas, accuracy = 0.1),
              `Razão (banidas / permitidas)` = round(razao, 2)),
  caption = "Presença de cada mecânica entre cartas banidas e permitidas"
)
Presença de cada mecânica entre cartas banidas e permitidas
Mecânica % nas banidas % nas permitidas Razão (banidas / permitidas)
Efeito Rápido 16.1% 9.4% 1.72
Invocação Especial 41.9% 41.8% 1.00
Destruição 21.9% 30.4% 0.72
Busca no Deck 16.1% 17.2% 0.94
Negação 14.8% 11.0% 1.35
Comprar Cartas 11.0% 6.9% 1.60
p_razao <- ggplot(mec_tbl, aes(x = reorder(Mecanica, razao), y = razao)) +
  geom_col(fill = "#3b82f6") +
  geom_hline(yintercept = 1, linetype = "dashed", color = "firebrick") +
  coord_flip() +
  labs(x = "Mecânica",
       y = "Razão de presença (banidas / permitidas)",
       title = "Quais mecânicas são mais frequentes entre as banidas?",
       subtitle = "Linha tracejada: razão igual a 1 (mesma frequência nos dois grupos)") +
  theme_minimal(base_size = 12)

ggplotly(p_razao)

Três mecânicas aparecem mais entre as banidas: efeito rápido (razão de 1.72), compra de cartas (1.6) e negação (1.35). Invocação Especial aparece na mesma proporção nos dois grupos (1), e Destruição aparece menos entre as banidas (0.72). Esse resultado contraria a hipótese de que mecânicas de aceleração, como Invocação Especial e Destruição, dominariam a lista de banidas. Além disso, efeitos rápidos cresceram com o tempo (seção 4.3), então a razão para essa mecânica também pode refletir a época das cartas banidas. Os padrões de regex são aproximações, e os resultados devem ser lidos como indícios.

Staples por categoria

staples_tbl <- cards %>%
  filter(is_staple) %>%
  count(tipo_categoria, sort = TRUE) %>%
  mutate(pct = n / sum(n))

p_staples_cat <- ggplot(staples_tbl, aes(x = reorder(tipo_categoria, -pct), y = pct, fill = tipo_categoria)) +
  geom_col(show.legend = FALSE) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  scale_fill_manual(values = c("Spell" = "#1b9e77", "Trap" = "#d95f02", "Monster" = "#7570b3", "Other" = "gray60")) +
  labs(x = "Categoria da carta",
       y = "Proporção entre as staples",
       title = "Distribuição de staples por categoria") +
  theme_minimal(base_size = 12)

ggplotly(p_staples_cat)

Entre as 64 staples, a categoria mais frequente é a indicada no gráfico acima. Com um número tão pequeno de casos, esta distribuição descreve apenas essas cartas e não deve ser generalizada.

4.5 Tabela de auditoria: cartas banidas com texto longo

A tabela abaixo reúne as cartas proibidas com mais de 100 palavras no efeito. Ela permite conferir individualmente os casos que puxam as médias da seção 4.2. O texto foi truncado para facilitar a leitura.

cards %>%
  filter(is_banned, qtd_palavras > 100) %>%
  mutate(desc = str_trunc(desc, 120, ellipsis = "…")) %>%
  select(name, type, tipo_categoria, year_lancamento,
         qtd_palavras, tamanho_texto, desc) %>%
  datatable(caption = "Cartas proibidas com mais de 100 palavras",
            filter = "top", rownames = FALSE,
            options = list(pageLength = 5, scrollX = TRUE))

5. Conclusões e Limitações

5.1 Problema

A análise procurou verificar se o power creep aparece na complexidade textual das cartas de Yu-Gi-Oh! e se o texto e as mecânicas estão associados ao banimento.

5.2 Abordagem

Usei a base de cartas do YGOPRODECK, em cópia publicada no Kaggle. Após a limpeza, a análise trabalhou com 14.133 cartas. O texto de cada efeito foi transformado em variáveis numéricas (palavras e caracteres) e em indicadores de mecânicas, por meio de expressões regulares. As comparações foram feitas com médias, densidades, uma regressão linear simples para a tendência, uma regressão com controle para o ano de lançamento e razões de presença por mecânica.

5.3 Principais resultados

  • O texto das cartas cresce com o ano de lançamento, em média 2.89 palavras por ano, mas o ano explica apenas 46.0% da variação entre cartas.
  • Cartas proibidas têm, em média, textos mais longos que as permitidas. Quando o ano de lançamento é controlado, a diferença é de 7.09 palavras. Essa diferença descreve uma associação, e não uma causa.
  • Entre as mecânicas, efeitos rápidos (razão de 1.72), compra de cartas (1.6) e negação (1.35) aparecem mais entre as banidas. Destruição aparece menos (0.72) e Invocação Especial tem frequência semelhante (1).
  • Staples têm textos mais curtos que as demais cartas, em média 51.7 contra 56.4 palavras.

5.4 Implicações

Para game designers, a análise sugere que efeitos rápidos, compra de cartas e negação aparecem com mais frequência entre as cartas banidas. Isso não prova que essas mecânicas causam o banimento, mas pode servir como ponto de atenção ao revisar cartas novas.

Para jogadores e colecionadores, a análise descreve padrões históricos de banimento. Ela não contém dados de preço, de uso em torneios nem de mercado, e por isso não permite prever valorização ou depreciação de cartas. Nenhuma conclusão deste relatório deve ser usada como base para decisões de compra ou venda.

5.5 Limitações e próximos passos

  • Viés de época: a comparação entre banidas e permitidas foi ajustada apenas para o ano de lançamento, e as razões das mecânicas não foram ajustadas. Como efeitos rápidos aumentaram com o tempo, a razão observada para essa mecânica pode refletir a época das cartas banidas. Outros fatores, como o tipo da carta, também ficam de fora.
  • Status atual de banimento: o campo ban_tcg mostra apenas o status atual. Cartas banidas no passado e depois liberadas aparecem como permitidas, o que subestima o banimento nas cartas mais antigas. Um histórico de listas por data permitiria corrigir isso.
  • Banimento como proxy: o banimento indica que a editora considerou a carta excessiva, mas não mede poder de forma direta. Cartas podem ser banidas por motivos que não aparecem no texto, e podem ser fortes sem serem banidas.
  • Limites de cópias: cartas Limited e Semi-Limited foram tratadas como não banidas. Como elas também indicam preocupação da editora, uma análise com as três categorias juntas seria um complemento útil.
  • Precisão do regex: os padrões identificam mecânicas por palavras-chave. Não foi feita uma validação manual por amostra, e alguns efeitos podem ser classificados de forma errada. Uma validação com amostra aleatória de cartas é um próximo passo natural.
  • Dados sem uso competitivo: a análise inclui todas as cartas, inclusive as pouco usadas em torneios. A média de complexidade pode não representar bem o metajogo. A ideia inicial era usar a frequência de cartas em listas de torneios, mas essa informação não está disponível na API do YGOPRODECK. Combinar a base com listas de torneios de outras fontes seria o próximo passo.
  • Escopo da banlist: a análise usa apenas a lista do TCG. Versões do jogo com outras regras, como o OCG, não são consideradas.
  • Observações não independentes: várias cartas pertencem à mesma expansão ou ao mesmo período de design, o que viola a suposição de independência da regressão. Os intervalos de confiança apresentados são, portanto, otimistas. Um modelo multinível ou o agrupamento por expansão seria mais adequado.
  • Categorias fora da análise: cartas de habilidade e fichas ficam em “Other” e não são analisadas separadamente.

Leitura cuidadosa dos resultados: os dados mostram associações entre o texto das cartas, suas mecânicas e o banimento. Eles não permitem afirmar que um texto longo ou uma mecânica específica causam o banimento, nem prever o comportamento de mercado das cartas.