Hellow, I’m Gabriel, but you may call me Yeye for convenience, and I would prefer you to call me that :)
In this portofolio builder, I would like to to analyze Yu-Gi-Oh! card game, spesifically the TCG (English) version. YGO is in my kind of biased opinion, the best card game I’ve ever played, and I stumbled upon this dataset by chance.
Sadly though, I have left playing it since long ago because of money issues. If you don’t know, a single card can cost a maximum of Rp 1.000.000, or maybe more.
And also, for simplicity, I’ll refer to Yu-Gi-Oh! simply as YGO.
In this portofolio, I want to explore tidyverse, which is R package (more like a bundle), that can generalize the style of data analysis using R, since many people have different coding styles / approach, using tidyverse makes the code style uniform and much more simple and easy to understand.
library(dplyr)
library(lemon)
library(data.table)
library(ggplot2)The datasets are provided by James Palmer from kaggle.com, and the data itself was scraped from Yu-Gi-Oh! Fandom Wiki. It consist of 4 files, which upon slight inspection, it’s actually quite similar.
yugi <- read.csv("dataset/yugi.csv")
View(yugi)names(yugi)## [1] "X" "Name"
## [3] "Card.type" "Attribute"
## [5] "Monster.Type" "Level.Rank"
## [7] "ATK.DEF" "Passcode"
## [9] "Materials...Ritual.spell" "Effect.type"
## [11] "Effect" "Spell.Trap.type"
## [13] "TCG.sets" "Ban_list"
## [15] "Number.of.sets" "Link.Arrows"
## [17] "Pendulum.Scale" "Rarities"
## [19] "Set.Name" "Release.Date"
head(yugi, 10)In YGO, the first 2 points are related to each other. The current META (Most Effective Tactic Available) is determined by these 2 points. Since the last time I played the game, the game has already moved into a very fast paced Monsters Special Summoning spam. Whoever sets their field with their desired Monster combos will likely have higher chance to win the game. And also, I want to point out, KONAMI will have a higher chance to create new cards related to the current META, the reason was obviously for marketting. By introducing a banlist and certain cards, and then creating new cards that can “hopefully” be a substitute, players will have to have higher tendency buy them if they want their deck to stay on META. That’s why we can infer what’s in the META currently by just taking a look at the how the Monster Types & Attributes are currently spread out.
In YGO, there’s also something like a family of monsters, that can be grouped together, creating the best combo synergy when the player deck consist of only those monster groups:
These family of monsters are called Archetype. Even without us inferring which specific Archetype is the current trending topic, we can infer them by their Type and Attribute. Although there are also Archetype that are Non-Monster based, the Monster based Archetype are most favorable, since not only they carry effects, but can give influence in Battle Phase, while Non-Monster based only relies on effects.
Since we want to gain information of meta from monsters, we don’t need to include other Card types. And since the card name is unique by nature, we most likely will only need:
Why do we need Banlist? Let me explain a little bit. Cards that have absolutely God-like effects, will most likely enter the Banlist, either they’re Limited or even Forbidden. Here’s a summary of how banlist works:
Most of the time, cards are in the banlist because they either: - Have the effect of easily changing the game - Have the effect of making the duel ends in a DRAW (neither player win/lose) - Have a really good effect but simple cost - That particular card in an Archetype tremendously improves the synergy & combo on their corresponding Arhcetype
I can give you 1 example of 1 card, that immediately not long after it was released, ENTERS THE FORBIDDEN LIST.
Image source: https://yugipedia.com
The problem with this card is it’s second effect: “If a monster this card points to is destroyed by battle or sent to the GY: You can Special Summon 1 monster from your hand”
This effect does not have a constraint, meaning, it can activate at any point in time, whenever monsters pointed by the Link-Arrow is sent to the Graveyard, and also can activate however many times the owner wants, meaning, you can get 1 free Monster summon each time a monster You or your opponent controls that are pointed by this card is sent to GY.
Sent to GY is the second problem. Sent to GY is a little bit broad, since a monster can be send to GY by many means:
This means, if you just sacrificed a Monster on the field for some effect, you can immediately summon another Monster to replace that fodder (term: sacrificed monster), and you won’t lose any advantages. Moreover, there are many monsters that triggers their effect when They are summonned.
This effect is simple, but can be deadly, as it tremendously boost the combo of ANY DECK that can use Summon Firewall Dragon.
Want to know something more? You only need to sacrifice ANY 4 monsters to get this card to play. And I should tell you, Summoning 4 monsters in a single turn is an easy matter at the current state of YGO game. When Firewall Dragon was released, players all around the world have been able to pull a combo of > 20 Special Summons in a single turn, only because this card existed.
From the explanation above, I most likely will need things that only exist on Monster Card type as follows:
Let’s also include a conditional subsetting using dplyr::filter() to pick Card.type of Monsters only. To do this, we need to Card.type to factor type first.
df_main <- yugi
df_main$Card.type = factor(df_main$Card.type)
levels(df_main$Card.type)## [1] "Monster " "Spell " "Trap "
Hmm weird, the categorical for some reason has a blank whitespace at the end of it, this made me go visible confusion for 1 hours LOL.
Next, the main subsetting fun. This process is done in a piping style, as recommended by tidyverse.
monsters <- df_main
monsters %>%
dplyr::filter(Card.type == "Monster ") %>%
dplyr::select(
Monster.Type,
Attribute,
Level.Rank,
Effect.type,
Ban_list
) -> monsters
head(monsters)For transforming, I want to do quite a lot of things:
Arranging
Monster.Type , Attribute, Effect.type and Banlist are all categorical, which means we need to transform them so it becomes a factor. We’ll keep the rest as is.
Also I want to arrange the dataframe in way like this, starting from left-side:
Determine if it is Non-Effect Monster
And also, I want to filter out the Non-Effect monsters, we can do this by checking whether the Effect.type parameter is empty or not.
monsters_transmuted <- monsters
monsters_transmuted %>%
dplyr::filter(Effect.type != "") %>%
dplyr::transmute(
Attribute = factor(Attribute),
Level.Rank = Level.Rank,
Monster.Type = factor(Monster.Type),
Ban_list = Ban_list
) -> monsters_transmuted
head(monsters_transmuted)Now the dataset is quite tidied up, but there’s still 2 major issues. The first one, The Monster.Type, is not specified as it’s real monster type! The true monster type are currently only:
And the “Monster Card” types (which can be determined from the card color), are only:
We have already filtered out the Normal Monsters out when we check the Effect.type is empty or not. But we have not “bisect” the rest. And I need to point out that the “Effect” monster replace term will have to come FIRST, otherwise all types will be replaced as “Effect” hehehe :D
You might be wondering types like Union or Gemini monsters and such, I consider this to be a “secondary” type and only signifies that it’s effect has a certain pattern.
The final concern, I just noticed, that the banlist, is not particularly clean, I don’t know if it’s a mistake on the raw data, but for some reason, there are “Effect Texts” mixed in one of the Ban_list data.
So we need to fix all of that. Firstly, for convenience we can check this using %like% from data.table library, and using conditional ifelse(). Also, to be on the safe side, let’s create a new column first.
I will create a new vector of those “true monster types”, “true monster card types”, and “Ban” type, and then iterate through it.
monster_types <- c(
"Aqua",
"Beast" ,
"Beast-Warrior",
"Cyberse" ,
"Dinosaur",
"Divine-Beast" ,
"Dragon",
"Fairy" ,
"Fiend",
"Fish" ,
"Insect",
"Machine" ,
"Plant",
"Psychic" ,
"Pyro",
"Reptile" ,
"Rock",
"Sea Serpent" ,
"Spellcaster",
"Thunder" ,
"Warrior",
"Winged Beast" ,
"Wyrm",
"Zombie"
)
monster_card_types <- c(
"Effect",
"Ritual",
"Fusion",
"Synchro",
"Xyz",
"Pendulum",
"Link"
)
ban_type <- c(
"Unlimited",
"Forbidden",
"Limited",
"Semi-Limited"
)
# head(monsters_transmuted$Monster.Type)
# Fill with a copy of data
monsters_transmuted$Type <- monsters_transmuted$Monster.Type
monsters_transmuted$Card.type <- monsters_transmuted$Monster.Type
monsters_transmuted$Banlist <- monsters_transmuted$Ban_list
# iterate through the monster_types vector
for (type in monster_types) {
monsters_transmuted$Type <- ifelse(
monsters_transmuted$Monster.Type %like% type,
type,
monsters_transmuted$Type
)
}
# iterate through the monster_card_types vector
for (card_type in monster_card_types) {
monsters_transmuted$Card.type <- ifelse(
monsters_transmuted$Monster.Type %like% card_type,
card_type,
monsters_transmuted$Card.type
)
}
for (type in ban_type) {
monsters_transmuted$Banlist <- ifelse(
monsters_transmuted$Ban_list %like% type,
type,
monsters_transmuted$Banlist
)
}
# Let's not forget to change it to categorical type
monsters_transmuted$Type = factor(monsters_transmuted$Type)
head(monsters_transmuted)Cleaning the broken banlists
monsters_fixed_banlist <- monsters_transmuted
# levels(factor(monsters_fixed_banlist$Banlist))
monsters_fixed_banlist[which(
!(
monsters_fixed_banlist$Banlist == "Forbidden" |
monsters_fixed_banlist$Banlist == "Limited" |
monsters_fixed_banlist$Banlist == "Semi-Limited" |
monsters_fixed_banlist$Banlist == "Unlimited"
)
), "Banlist"] <- "Unlimited"
monsters_fixed_banlist$Banlist = factor(monsters_fixed_banlist$Banlist)
levels(monsters_fixed_banlist$Banlist)## [1] "Forbidden" "Limited" "Semi-Limited" "Unlimited"
Cleaning the broken Card.type
monsters_fixed_card_type <- monsters_fixed_banlist
# Change to character first, we'll change it again later
monsters_fixed_card_type$Card.type <- as.character(monsters_fixed_card_type$Card.type)
monsters_fixed_card_type[which(
!(
monsters_fixed_card_type$Card.type == "Effect" |
monsters_fixed_card_type$Card.type == "Fusion" |
monsters_fixed_card_type$Card.type == "Link" |
monsters_fixed_card_type$Card.type == "Pendulum" |
monsters_fixed_card_type$Card.type == "Ritual" |
monsters_fixed_card_type$Card.type == "Synchro" |
monsters_fixed_card_type$Card.type == "Xyz"
)
), "Card.type"] <- "Effect"
# let's not forget to change it back to factor
monsters_fixed_card_type$Card.type <- factor(monsters_fixed_card_type$Card.type)
levels(monsters_fixed_card_type$Card.type)## [1] "Effect" "Fusion" "Link" "Pendulum" "Ritual" "Synchro" "Xyz"
Now, we can drop the un-needed column, since we don’t need it anymore. Also I’m clumsy I know, I have to re-arrange the columns again. :)
df_final <- monsters_fixed_card_type
df_final %>%
dplyr::transmute(
Card.type = df_final$Card.type,
Attribute = df_final$Attribute,
Type = df_final$Type,
Level.Rank = df_final$Level.Rank,
Banlist = df_final$Banlist
) -> df_final
head(df_final)And finally, we can change the NA values from Level.Rank, to level 0. This also signifies that that monster is a Link Monster.
df_final$Level.Rank <- ifelse(
is.na(df_final$Level.Rank),
0,
df_final$Level.Rank
)
head(df_final)I will display the summary using lemon. I like to see the summary with better visual :D
# Overwrites the default printing style from knit
knit_print.table <- lemon_print
summary(df_final)| Card.type | Attribute | Type | Level.Rank | Banlist |
|---|---|---|---|---|
| Effect :9952 | DARK :3833 | Warrior :2204 | Min. : 0.000 | Forbidden : 138 |
| Fusion : 666 | DIVINE : 23 | Machine :1578 | 1st Qu.: 3.000 | Limited : 123 |
| Link : 456 | EARTH :3011 | Spellcaster:1249 | Median : 4.000 | Semi-Limited: 7 |
| Pendulum: 386 | FIRE :1002 | Fiend :1235 | Mean : 4.467 | Unlimited :12567 |
| Ritual : 145 | LIGHT :2822 | Dragon :1207 | 3rd Qu.: 6.000 | |
| Synchro : 639 | WATER :1005 | Fairy : 812 | Max. :12.000 | |
| Xyz : 591 | WIND :1139 | (Other) :4550 |
theme_set(theme_bw())
ggplot(df_final) +
labs(title="Monsters Attribute & Type Distribution") +
geom_bar(aes(Type, fill=Attribute), width=0.7) +
theme(axis.text.x = element_text(angle=90, vjust=0.5)) Quite a lot we can gain here. Firstly, surprise! Dragon-type is not the winner. I see that KONAMI actually prefers Warrior-Type a lot, with it’s notable Attributes: DARK, LIGHT, and EARTH. Some of you may already know what’s the biggest, best, the most famous warrior Archetype. Yes, the “HERO” Archetype.
Some of the “HERO” sub-archetype that I can think of are:
And here’s some more notable mentions that are considered META on their prime era:
And then the second highest is Machine-type. I think I can already infer that the reason behind this high number of Machine & Warrior-type monsters. Possibly it’s because:
Often, demands for supporting cards for Archetypes used by main characters are high. No wonder KONAMI still releases overly abundant Warrior & Machine type monsters up to this day.
Let’s take a look, which are more favourable, is it still Xyz monsters? I will filter out the ordinary Effect monsters, since they are always present in every deck.
card_selection <- dplyr::filter(df_final, Card.type != "Effect")
card_selection$Level.Rank <- factor(card_selection$Level.Rank)
ggplot(card_selection) +
labs(title="Monster Card Type") +
geom_bar(aes(Card.type, fill=Level.Rank))NOTE: I can’t do much about the Link monsters being level 0, the dataset does not provide their Link rating.
Now we see that the Xyz rank 4 is still favorable as always. I remembered the reason was because Rank-4 Xyz has really generic material, and many decks that run Level-4 Monsters can easily utilize them.
To my surprise, fusion monsters are still high. Possibly this is also the side-effects of HERO monsters. Remember, HERO Archetype are fusion-oriented.
The Synchro monsters are still high probably because Synchro based archetype are also very common. During the 5D’s era, KONAMI drops a bombardment of new Archetypes that are synchro related. And recently, I heard recently KONAMI drops another support for Stardust Dragon (Yusei Fudo’s main monster), which means that Synchro oriented decks aren’t going down soon.
And finally, Link vs Pendulum, I see that people actually prefers Link monsters! Glad to hear that, I always find the ruling of Pendulum monsters quite confusing. But there’s also this second reason…
Link monsters are a MUST if your deck relies on Extra Decks (either Xyz, Synchro, and Fusion monsters). This is because if you don’t use Link monsters as per the latest Master Rule, you can only have 1 monster that are summoned from Extra Deck. Read more about this rule
This is probably why KONAMI decides to drop a bombardment of Link monsters, despite Pendulum monsters came first.
Now, let’s take a look at the distribution of the Attributes itself.
ggplot(df_final) +
labs(title="Monster Attributes Distribution") +
geom_bar(aes(Attribute, fill=Attribute))As we can see, the number of the Effect monsters are ranked as follows:
There’s only 1 possibility why DARK & LIGHT are at the top 3. It’s because “Chaos” deck playstyle exist. This so-called “Chaos” deck are decks that relies on various DARK & LIGHT monsters, often coming from different Archetype / Type! And what’s more amazing are, the “Chaos” deck is considered to be very versatile, any player with decks that can utilize DARK & LIGHT Monsters, have “Chaos” playstyle as one of their options!
This is why KONAMI did not stop creating supports for DARK & LIGHT monsters.
theme_set(theme_bw())
level_banlist_density <- ggplot(df_final) +
geom_density(aes(Level.Rank, fill=Banlist, alpha=0.2)) +
labs(title="Monsters Density by Banlist")
level_banlist_densityThe reason why I visualize the Level / Rank and Banlist together is, because this particular rule: “Monsters of level 5 or higher requires tributing (sacrificing) a monster”.
Think of it, monsters are mostly banned because their effects can easily change the tide of the duel. How scary it is to summon those monsters without tribute?
Look at the density of monsters ranging from Level 2-4. Monsters labeled as Forbidden & Limited are obviously gathering there. This means that the most “favourable” monsters (favourable in YGO can be translated as = soon to be banned LMAO), are those that Can be summoned easily and because they are in the banlist, this means they also have good effects.
Here are some notable monsters on the banlist: