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.
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.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.
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.
NA
pelo R, sem risco de virar zero.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): campo que marca
cartas de uso amplo em diversos tipos de baralho. Ele é preenchido com
Yes em 64 cartas.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).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:
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.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.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.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.# 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.
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)")| 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 |
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á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 |
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.squaredEm 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)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.
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.
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.
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"
)| 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_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.
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))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.
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.
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.
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.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.