AI, Cluster Analysis of Categorical Data (Part I) | by Michelangiolo Mazzeschi | Towards Data Science

***The code begins after the Clash Royale introduction: skip, if you are not interested
Full code available at my repository. The notebook is called: CRL_Clustering.
In this article I will create an AI that, given a dataset of Clash Royale matches, will identify what are the main clusters. All data is categorical, hence I won’t be using a traditional factor analysis technique, but rather a K-Node algorithm.
Before explaining the code behind the AI I am going to create, I need to clarify what are meta-decks and how they relate to this project. Professional Videogame players have turned the study of competitive videogames into a real subject.
In videogames where you need to prepare your deck from a pool of cards, there is a gigantic number of possible combinations, and from this sea of possibilities emerge a small batch that helps players gain a competitive advantage: the so-called meta-decks.
I will be creating an AI that can find which are the meta-decks used in the competitive game called Clash Royale, extracting them from raw data.
In this game there is a pool of 98 cards (up to May 2020, every few months the game adds another card). Every player has to compose a deck of 8 cards, to play against other players.
The number of possible decks is 6345015248033280, from this pool of combinations, top players find out which are the meta-decks. The meta-decks keep changing now and then, so pros need to stay updated and keep studying the game.
Unfortunately, there is no mathematical way to determine which are the best decks. The only possibility that champions have is to try out many of them and see which one plays out better against other players.
Essentially, through continuous iterations, the meta-decks are the ones that allow the top players to win with consistency, hence allowed them to climb the hierarchy of competitive players up to the top spots. Therefore, these specific decks survived a selection of millions of matches fought among the community.
As you can see, being excellent at something, even at videogames, takes a lot of effort, commitment, and study.
Finally, how does the community of players (not only the top 100) find out the meta decks of every season? Thanks to the use of Youtube, Facebook groups, and statistics websites of Clash Royale such as www.statsroyale.com, people can see what the top players play and their win rate. The decks are then copied and used by all the players in the community, average or expert, up to the pro level.
Let us now assume that we do not know anything about this videogame and which are the main decks of this season. I have in my possession almost 700 matches (669) played by the top players.
What my AI will do is finding out which are the decks that are played more consistently and isolate them.
Let’s remember than anybody can edit their deck, so players can easily create a unique variation of a meta-deck that we do not want to take into account to the calculation, so we need an algorithm to figure out which are the original decks rather than the variations. We assume that the variations will be played with less frequency, leaving the original decks with the highest frequency.
Within the hierarchy of meta-decks, the level of variations can reach a depth of hundreds due to the frequency of possible changes and experiments from millions of players.
Entire Procedure
Downloading match datasets with APIPre-processing: personalized sorting of every rowPre-processing: label encoding of the entire datasetPre-processing: one_hot encoding of the entire datasetPerform cluster analysisTrain K-Node algorithmTuning number of clusters to get an accurate resultEstimate the top meta-decks
Downloading match datasets with API
Pre-processing: personalized sorting of every row
Pre-processing: label encoding of the entire dataset
Pre-processing: one_hot encoding of the entire dataset
Perform cluster analysis
Train K-Node algorithm
Tuning number of clusters to get an accurate result
Estimate the top meta-decks
In this article I will cover up to step 3: label encoding the entire dataset. In the next part of the tutorial I will finalize the AI.
For your convenience, I already downloaded and prepared the dataset we are going to use for applying clustering on categorical data. If you wish to download your own dataset of Clash Royale matches, please follow this complete guide on how to create an API that downloads the data.
X = pd.read_csv('/content/drive/My Drive/Colab Notebooks/CRL daily collector/CRL (1).csv')
At the moment, the dataset contains too much information: we will need to get rid of all the columns that do not contain data about player n. 2. The reason is that I downloaded the most recent 25 games of several players selected as player n.1. One player usually plays a single archetype, and if the proportion of our entire dataset that contains repetitively the deck played by a single user is too high, the variance of meta-decks in our dataset would be highly decreased.
X = X[['p2.card_1', 'p2.card_2', 'p2.card_3', 'p2.card_4', 'p2.card_5', 'p2.card_6', 'p2.card_7', 'p2.card_8']]X
We will only be looking at a list of decks played by player n. 2.
***In my case I imported the data connecting it with my Google Drive. You should edit the first line depending on where you have stored your .csv file.
Unfortunately, the data we collected is very chaotic. The order of the cards in every row corresponds to the order of the cards played in one match. A player could start using a Troop, but it may also start using a Spell. To see things clearly, we will need to sort the cards in a specific order.
***The order of the features do not impact the final results. However, I believe this step is necessary to see the final results clearly and in a comprehensive way.
In the game, there are essentially four types of cards. They have been conceptualized in this order to allow people to create proper decks.
Win conditions
Troops
Buildings
Spells
Because the dataset in my possession does not show the type of cards, we will need to create an algorithm capable of converting every card to its corresponding type.
This is what I mean by mapping every card to its type: with the following mock list:
['Zap', 'Knight', 'Giant', 'Earthquake', 'Musketeer', 'Ice Spirit', 'Skeletons']
...the corresponding types will be:
['Spell', 'Troop', 'Troop', 'Win Condition', 'Spell', 'Troop',   'Troop', 'Troop']
I have created a .csv file that maps every existing card with its corresponding Type called CRL_Cards_Classification.csv.
#https://docs.google.com/spreadsheets/d/1wIH69gfuCCoPRoWOIyUqQHMKkUfpb4GAxUbBYX8PwRc/edit#gid=1477229539C = pd.read_csv('/content/drive/My Drive/Colab Notebooks/CRL daily collector/CRL_Cards_Classification.csv')cards_classifier = C.copy()#getting rid of the first column, uselesscards_classifier.pop('Type_1')cards_classifier = cards_classifier.values.transpose()cards_classifierdef order_row(list1):  #mapping input list made of cards with their type  card_dictionary = dict(zip(cards_classifier[0],   cards_classifier[1]))  card_dictionarylist_tot_classification = list()  for m in list1:    a = card_dictionary.get(m)    list_tot_classification.append(a)  list_tot_classification
For every deck in the dataset I want all cards sorted in the order: Win Condition, Troop, Building, Spell.
To do so, we will first assign an ascending value to each one of the types, so that we know which type comes first:
class_dict = {'Win Condition': 0, 'Troop': 1, 'Building': 2, 'Spell': 3}  list_tot_num = list()  for m in list_tot_classification:    a = class_dict.get(m)    list_tot_num.append(a)ordered_list = [x for _,x in sorted(zip(list_tot_num, list1))]  return ordered_list
list1 = ['Arrows', 'Baby Dragon',  'Bomber', 'Golem',  'Lightning',  'Mega Minion',  'Mini P.E.K.K.A', 'Night Witch', 'Cannon']ordered_list = order_row(list1)
will become:
ordered_list['Golem',  'Baby Dragon',  'Bomber',  'Mega Minion',  'Mini P.E.K.K.A',  'Night Witch',  'Cannon',  'Arrows',  'Lightning']
#sort each row in the dataset based on type orderfor c in range(len(X.values)):X.values[c] = order_row(X.values[c])X = pd.DataFrame(e.X.values)
***If instead, we wanted to order the lists alphabetically, we would use the following code rather than using order_now
X.values[c] = sorted(X.values[c])
Now that we have sorted our dataset based on a hierarchy we can comprehend easily, we can start pre-processing it. We will now label the entire dataset, you will understand why later on, and that will be more problematic that labeling a single row.
Usually, engineers use scikit-learn tools to easily label categorical data. Unfortunately, scikit-learn and other tools only allow the labeling of a single column at a time. This is an example of why it can represent a problem for us:
As you can see, in the first column b = 1, in the second column b = 0. The reason is that the algorithm is applying labeling to each individual column without considering the values in the other columns.
To have accurate labeling, we will need to flat everything to one column, then apply the labeling and rebuild the initial dataset:
This will be performed using the code below:
#flat everything in one listone_row = list()for k in e.X.values:for n in k:one_row.append(n)one_row = pd.DataFrame(one_row)one_row#labeled listfrom sklearn.preprocessing import LabelEncoderle = LabelEncoder()le.fit(one_row[0])one_row_labeled = le.transform(one_row[0])one_row_labeled#rebuild the original dataset and convert it to DataFrameimport numpyX_labeled = numpy.array_split(one_row_labeled, (len(one_row)/8))X_labeled = pd.DataFrame(X_labeled)X_labeled
Now that we managed to label correctly each of our features, we can perform one_hot encoding for feeding our dataset into the AI.
In my next article I will describe the entire procedure, however, on the notebook available in my repository you will find Part II already.
-> GO TO PART II