In [49]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
import seaborn as sns
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
from sklearn.metrics import silhouette_score
from sklearn.cluster import KMeans
#To ignore warnings
warnings.filterwarnings("ignore")
In [50]:
#Find where the dataset is being stored and load it
file_path = r"C:OneDrive/Year 3/AI and Machine Learning/online_gaming_behavior_dataset.csv"
data=pd.read_csv(file_path)
In [51]:
#Print dataset to see if it has loaded correctly
print(data)
PlayerID Age Gender Location GameGenre PlayTimeHours \
0 9000 43 Male Other Strategy 16.271119
1 9001 29 Female USA Strategy 5.525961
2 9002 22 Female USA Sports 8.223755
3 9003 35 Male USA Action 5.265351
4 9004 33 Male Europe Action 15.531945
... ... ... ... ... ... ...
40029 49029 32 Male USA Strategy 20.619662
40030 49030 44 Female Other Simulation 13.539280
40031 49031 15 Female USA RPG 0.240057
40032 49032 34 Male USA Sports 14.017818
40033 49033 19 Male USA Sports 10.083804
InGamePurchases GameDifficulty SessionsPerWeek \
0 0 Medium 6
1 0 Medium 5
2 0 Easy 16
3 1 Easy 9
4 0 Medium 2
... ... ... ...
40029 0 Easy 4
40030 0 Hard 19
40031 1 Easy 10
40032 1 Medium 3
40033 0 Easy 13
AvgSessionDurationMinutes PlayerLevel AchievementsUnlocked \
0 108 79 25
1 144 11 10
2 142 35 41
3 85 57 47
4 131 95 37
... ... ... ...
40029 75 85 14
40030 114 71 27
40031 176 29 1
40032 128 70 10
40033 84 72 39
EngagementLevel
0 Medium
1 Medium
2 High
3 Medium
4 Medium
... ...
40029 Medium
40030 High
40031 High
40032 Medium
40033 Medium
[40034 rows x 13 columns]
In [52]:
#Dropping the unique ID column and the in game purchases comlumn
data=data.drop(['PlayerID', 'InGamePurchases'], axis=1)
In [53]:
# Creating numerical columns
num_cols=['Age', 'PlayTimeHours', 'SessionsPerWeek', 'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked']
# Creating categorical variables
cat_cols= ['Gender', 'Location', 'GameGenre', 'GameDifficulty', 'EngagementLevel']
In [54]:
# Checking summary statistics
data[num_cols].describe().T
Out[54]:
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Age | 40034.0 | 31.992531 | 10.043227 | 15.000000 | 23.000000 | 32.000000 | 41.000000 | 49.000000 |
| PlayTimeHours | 40034.0 | 12.024365 | 6.914638 | 0.000115 | 6.067501 | 12.008002 | 17.963831 | 23.999592 |
| SessionsPerWeek | 40034.0 | 9.471774 | 5.763667 | 0.000000 | 4.000000 | 9.000000 | 14.000000 | 19.000000 |
| AvgSessionDurationMinutes | 40034.0 | 94.792252 | 49.011375 | 10.000000 | 52.000000 | 95.000000 | 137.000000 | 179.000000 |
| PlayerLevel | 40034.0 | 49.655568 | 28.588379 | 1.000000 | 25.000000 | 49.000000 | 74.000000 | 99.000000 |
| AchievementsUnlocked | 40034.0 | 24.526477 | 14.430726 | 0.000000 | 12.000000 | 25.000000 | 37.000000 | 49.000000 |
In [55]:
# Creating histograms
data[num_cols].hist(figsize=(10,10))
plt.show()
In [56]:
# Printing the % sub categories of each category
for i in cat_cols:
print(data[i].value_counts (normalize=True))
print('*'*40)
Gender Male 0.598466 Female 0.401534 Name: proportion, dtype: float64 **************************************** Location USA 0.399660 Europe 0.299845 Asia 0.202203 Other 0.098291 Name: proportion, dtype: float64 **************************************** GameGenre Sports 0.201029 Action 0.200804 Strategy 0.200130 Simulation 0.199406 RPG 0.198631 Name: proportion, dtype: float64 **************************************** GameDifficulty Easy 0.49995 Medium 0.30002 Hard 0.20003 Name: proportion, dtype: float64 **************************************** EngagementLevel Medium 0.483939 High 0.258181 Low 0.257881 Name: proportion, dtype: float64 ****************************************
In [57]:
for i in cat_cols:
if i!='EngagementLevel':
(pd.crosstab(data[i],data['EngagementLevel'],normalize='index')*100).plot(kind='bar',figsize=(8,4),stacked=False)
plt.ylabel('Percentage Engegement Level')
In [58]:
# Mean of numerical variables grouped by Engagement Level
data.groupby(['EngagementLevel'])[num_cols].mean()
Out[58]:
| Age | PlayTimeHours | SessionsPerWeek | AvgSessionDurationMinutes | PlayerLevel | AchievementsUnlocked | |
|---|---|---|---|---|---|---|
| EngagementLevel | ||||||
| High | 31.920085 | 12.069238 | 14.254547 | 131.921827 | 50.823723 | 25.095975 |
| Low | 31.896939 | 12.104915 | 4.530511 | 66.882119 | 46.101414 | 22.661565 |
| Medium | 32.082120 | 11.957503 | 9.553267 | 89.856405 | 50.926293 | 25.216424 |
In [59]:
# Plotting the correlation between numerical variables
plt.figure(figsize=(15,8))
sns.heatmap(data[num_cols].corr(), annot=True, fmt='0.2f', cmap='YlOrRd')
Out[59]:
<Axes: >
In [60]:
#convert text catagories to numerical so the prediction algorithms can read it
data['Gender'] = data['Gender'].map({'M':0, 'F':1})
data['Location'] = data['Location'].astype('category').cat.codes
data['GameGenre'] = data['GameGenre'].astype('category').cat.codes
In [61]:
#Selecting the features for prediction
features=['Age','Location', 'GameGenre', 'PlayTimeHours', 'SessionsPerWeek', 'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked']
In [62]:
#Using the prediction features and selecting the target variable
X=data[features]
Y=data['EngagementLevel']
In [63]:
#Splitting the dataset into testing and traning sets (70/30 split)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3, random_state=109) #Random state ensures repeatability
In [64]:
#Creating and traing the decision tree model
dtree=DecisionTreeClassifier(max_depth=5) #Max depth set to five for easy readability/faster loading times when testing
dtree=dtree.fit(X_train, Y_train)
In [65]:
#Using the trained DT model to make predictions on the testing dataset
dt_pred = dtree.predict(X_test)
#Calculating and printing the DT preformance metrics
print("DT Accuracy:", accuracy_score(Y_test, dt_pred))
print("DT Precision:", precision_score(Y_test, dt_pred, average='weighted'))
print("DT Recall:", recall_score(Y_test, dt_pred, average='weighted'))
print("DT F1 Score:", f1_score(Y_test, dt_pred, average='weighted'))
#Calculating the DT AUC-ROC score
dt_prob = dtree.predict_proba(X_test)
dt_auc = roc_auc_score(Y_test, dt_prob, multi_class='ovr')
print("DT AUC-ROC:", dt_auc)
DT Accuracy: 0.857547248355674 DT Precision: 0.8596886322695013 DT Recall: 0.857547248355674 DT F1 Score: 0.856660642337442 DT AUC-ROC: 0.9225327673135123
In [66]:
#Creating a standard scaler so that all data is on a similar scale
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
In [67]:
#Creating SVM model with a linear kernal (uses straight line boundaries)
svm_model = SVC(kernel='linear', probability=True)
svm_model.fit(X_train_scaled, Y_train)
Out[67]:
SVC(kernel='linear', probability=True)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SVC(kernel='linear', probability=True)
In [68]:
#Predicting engagement level using trained SVM model
svm_pred = svm_model.predict(X_test_scaled)
In [69]:
#Printing SVM preformance metrics
print("SVM Accuracy:", accuracy_score(Y_test, svm_pred))
print("SVM Precision:", precision_score(Y_test, svm_pred, average='weighted'))
print("SVM Recall:", recall_score(Y_test, svm_pred, average='weighted'))
print("SVM F1 Score:", f1_score(Y_test, svm_pred, average='weighted'))
#Calulating the SVM AUC-ROC score
svm_prob = svm_model.predict_proba(X_test_scaled)
svm_auc = roc_auc_score(Y_test, svm_prob, multi_class='ovr')
print("SVM AUC-ROC:", svm_auc)
SVM Accuracy: 0.8308217467321621 SVM Precision: 0.8312654920537748 SVM Recall: 0.8308217467321621 SVM F1 Score: 0.829470880450099 SVM AUC-ROC: 0.9178585491097667
In [70]:
#Plotting DT
plt.figure(figsize=(12, 8))
tree.plot_tree(dtree,feature_names=features, filled=True, rounded=True, max_depth=7)
Out[70]:
[Text(0.5, 0.9166666666666666, 'SessionsPerWeek <= 2.5\ngini = 0.633\nsamples = 28023\nvalue = [7282.0, 7207.0, 13534.0]'), Text(0.25, 0.75, 'SessionsPerWeek <= 1.5\ngini = 0.273\nsamples = 4166\nvalue = [141, 3511, 514]'), Text(0.375, 0.8333333333333333, 'True '), Text(0.125, 0.5833333333333334, 'AchievementsUnlocked <= 46.5\ngini = 0.141\nsamples = 2766\nvalue = [97.0, 2559.0, 110.0]'), Text(0.0625, 0.4166666666666667, 'PlayerLevel <= 90.5\ngini = 0.13\nsamples = 2592\nvalue = [89, 2414, 89]'), Text(0.03125, 0.25, 'PlayerLevel <= 2.5\ngini = 0.119\nsamples = 2381\nvalue = [80, 2232, 69]'), Text(0.015625, 0.08333333333333333, 'gini = 0.304\nsamples = 40\nvalue = [4, 33, 3]'), Text(0.046875, 0.08333333333333333, 'gini = 0.116\nsamples = 2341\nvalue = [76, 2199, 66]'), Text(0.09375, 0.25, 'AvgSessionDurationMinutes <= 137.5\ngini = 0.245\nsamples = 211\nvalue = [9, 182, 20]'), Text(0.078125, 0.08333333333333333, 'gini = 0.167\nsamples = 167\nvalue = [9, 152, 6]'), Text(0.109375, 0.08333333333333333, 'gini = 0.434\nsamples = 44\nvalue = [0.0, 30.0, 14.0]'), Text(0.1875, 0.4166666666666667, 'PlayerLevel <= 68.0\ngini = 0.289\nsamples = 174\nvalue = [8, 145, 21]'), Text(0.15625, 0.25, 'AvgSessionDurationMinutes <= 154.5\ngini = 0.176\nsamples = 127\nvalue = [6, 115, 6]'), Text(0.140625, 0.08333333333333333, 'gini = 0.136\nsamples = 111\nvalue = [6, 103, 2]'), Text(0.171875, 0.08333333333333333, 'gini = 0.375\nsamples = 16\nvalue = [0, 12, 4]'), Text(0.21875, 0.25, 'AvgSessionDurationMinutes <= 118.5\ngini = 0.489\nsamples = 47\nvalue = [2, 30, 15]'), Text(0.203125, 0.08333333333333333, 'gini = 0.291\nsamples = 30\nvalue = [2, 25, 3]'), Text(0.234375, 0.08333333333333333, 'gini = 0.415\nsamples = 17\nvalue = [0, 5, 12]'), Text(0.375, 0.5833333333333334, 'AvgSessionDurationMinutes <= 123.5\ngini = 0.453\nsamples = 1400\nvalue = [44.0, 952.0, 404.0]'), Text(0.3125, 0.4166666666666667, 'AchievementsUnlocked <= 37.5\ngini = 0.264\nsamples = 911\nvalue = [32, 774, 105]'), Text(0.28125, 0.25, 'AvgSessionDurationMinutes <= 118.5\ngini = 0.172\nsamples = 702\nvalue = [25, 637, 40]'), Text(0.265625, 0.08333333333333333, 'gini = 0.152\nsamples = 666\nvalue = [21.0, 612.0, 33.0]'), Text(0.296875, 0.08333333333333333, 'gini = 0.468\nsamples = 36\nvalue = [4, 25, 7]'), Text(0.34375, 0.25, 'PlayerLevel <= 60.0\ngini = 0.472\nsamples = 209\nvalue = [7, 137, 65]'), Text(0.328125, 0.08333333333333333, 'gini = 0.198\nsamples = 120\nvalue = [4, 107, 9]'), Text(0.359375, 0.08333333333333333, 'gini = 0.489\nsamples = 89\nvalue = [3, 30, 56]'), Text(0.4375, 0.4166666666666667, 'AchievementsUnlocked <= 13.5\ngini = 0.493\nsamples = 489\nvalue = [12, 178, 299]'), Text(0.40625, 0.25, 'AvgSessionDurationMinutes <= 168.5\ngini = 0.416\nsamples = 138\nvalue = [5, 100, 33]'), Text(0.390625, 0.08333333333333333, 'gini = 0.246\nsamples = 109\nvalue = [5, 94, 10]'), Text(0.421875, 0.08333333333333333, 'gini = 0.328\nsamples = 29\nvalue = [0, 6, 23]'), Text(0.46875, 0.25, 'PlayerLevel <= 32.5\ngini = 0.376\nsamples = 351\nvalue = [7, 78, 266]'), Text(0.453125, 0.08333333333333333, 'gini = 0.478\nsamples = 96\nvalue = [0, 58, 38]'), Text(0.484375, 0.08333333333333333, 'gini = 0.194\nsamples = 255\nvalue = [7, 20, 228]'), Text(0.75, 0.75, 'AvgSessionDurationMinutes <= 93.5\ngini = 0.589\nsamples = 23857\nvalue = [7141.0, 3696.0, 13020.0]'), Text(0.625, 0.8333333333333333, ' False'), Text(0.625, 0.5833333333333334, 'AvgSessionDurationMinutes <= 23.5\ngini = 0.499\nsamples = 11788\nvalue = [883.0, 3280.0, 7625.0]'), Text(0.5625, 0.4166666666666667, 'SessionsPerWeek <= 15.5\ngini = 0.394\nsamples = 1939\nvalue = [72, 1449, 418]'), Text(0.53125, 0.25, 'AchievementsUnlocked <= 33.5\ngini = 0.277\nsamples = 1485\nvalue = [51, 1248, 186]'), Text(0.515625, 0.08333333333333333, 'gini = 0.172\nsamples = 995\nvalue = [35, 903, 57]'), Text(0.546875, 0.08333333333333333, 'gini = 0.434\nsamples = 490\nvalue = [16, 345, 129]'), Text(0.59375, 0.25, 'AvgSessionDurationMinutes <= 17.5\ngini = 0.541\nsamples = 454\nvalue = [21, 201, 232]'), Text(0.578125, 0.08333333333333333, 'gini = 0.466\nsamples = 259\nvalue = [10, 173, 76]'), Text(0.609375, 0.08333333333333333, 'gini = 0.336\nsamples = 195\nvalue = [11.0, 28.0, 156.0]'), Text(0.6875, 0.4166666666666667, 'SessionsPerWeek <= 5.5\ngini = 0.423\nsamples = 9849\nvalue = [811, 1831, 7207]'), Text(0.65625, 0.25, 'AvgSessionDurationMinutes <= 62.5\ngini = 0.503\nsamples = 1813\nvalue = [61, 1095, 657]'), Text(0.640625, 0.08333333333333333, 'gini = 0.349\nsamples = 1021\nvalue = [38, 803, 180]'), Text(0.671875, 0.08333333333333333, 'gini = 0.5\nsamples = 792\nvalue = [23, 292, 477]'), Text(0.71875, 0.25, 'AvgSessionDurationMinutes <= 76.5\ngini = 0.319\nsamples = 8036\nvalue = [750, 736, 6550]'), Text(0.703125, 0.08333333333333333, 'gini = 0.267\nsamples = 6065\nvalue = [252, 668, 5145]'), Text(0.734375, 0.08333333333333333, 'gini = 0.427\nsamples = 1971\nvalue = [498, 68, 1405]'), Text(0.875, 0.5833333333333334, 'SessionsPerWeek <= 9.5\ngini = 0.53\nsamples = 12069\nvalue = [6258, 416, 5395]'), Text(0.8125, 0.4166666666666667, 'SessionsPerWeek <= 8.5\ngini = 0.23\nsamples = 4985\nvalue = [434, 203, 4348]'), Text(0.78125, 0.25, 'SessionsPerWeek <= 7.5\ngini = 0.173\nsamples = 4290\nvalue = [214, 186, 3890]'), Text(0.765625, 0.08333333333333333, 'gini = 0.148\nsamples = 3606\nvalue = [126.0, 157.0, 3323.0]'), Text(0.796875, 0.08333333333333333, 'gini = 0.294\nsamples = 684\nvalue = [88, 29, 567]'), Text(0.84375, 0.25, 'AvgSessionDurationMinutes <= 150.5\ngini = 0.465\nsamples = 695\nvalue = [220.0, 17.0, 458.0]'), Text(0.828125, 0.08333333333333333, 'gini = 0.202\nsamples = 477\nvalue = [40, 13, 424]'), Text(0.859375, 0.08333333333333333, 'gini = 0.294\nsamples = 218\nvalue = [180, 4, 34]'), Text(0.9375, 0.4166666666666667, 'SessionsPerWeek <= 12.5\ngini = 0.301\nsamples = 7084\nvalue = [5824, 213, 1047]'), Text(0.90625, 0.25, 'AvgSessionDurationMinutes <= 125.5\ngini = 0.494\nsamples = 2145\nvalue = [1323, 64, 758]'), Text(0.890625, 0.08333333333333333, 'gini = 0.348\nsamples = 801\nvalue = [137, 33, 631]'), Text(0.921875, 0.08333333333333333, 'gini = 0.212\nsamples = 1344\nvalue = [1186, 31, 127]'), Text(0.96875, 0.25, 'AvgSessionDurationMinutes <= 101.5\ngini = 0.165\nsamples = 4939\nvalue = [4501, 149, 289]'), Text(0.953125, 0.08333333333333333, 'gini = 0.401\nsamples = 454\nvalue = [337, 20, 97]'), Text(0.984375, 0.08333333333333333, 'gini = 0.135\nsamples = 4485\nvalue = [4164, 129, 192]')]
In [71]:
#Example prediction for a single player (column numbers are interchangable)
single_player = pd.DataFrame([[27, 200, 2, 9, 120, 4, 1, 12]], columns=features)
print(dtree.predict(single_player))
['Low']
In [72]:
#Selecting two data columns to compare when clustering
subset = data[['SessionsPerWeek', 'PlayTimeHours']]
In [73]:
#Standardising chosen features to be on the same scale
KMscaler = StandardScaler()
cluster_scaled = KMscaler.fit_transform(subset)
In [74]:
#Graph limited to 500 plots so avoid overcorwding
player_sample = data.sample(n=500, random_state=42) #Random state ensures the same 500 records are picked
#Jitter added to spread out plots
x = player_sample['SessionsPerWeek'] + np.random.normal(0, 0.5, size=player_sample.shape[0])
y = player_sample['PlayTimeHours'] + np.random.normal(0, 0.2, size=player_sample.shape[0])
In [75]:
#Creating scatter plot comparing the Sessions played x Hours played
plt.figure(figsize=(8,6))
plt.scatter(x, y)
plt.xlabel('Sessions Per Week')
plt.ylabel('Play Time Hours')
plt.title('Player Behaviour Scatter Plot')
plt.show()
In [76]:
#Clustering example
km1 = KMeans(n_clusters=3) #Creating a k means model with three clusters
km1.fit(cluster_scaled)
Out[76]:
KMeans(n_clusters=3)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KMeans(n_clusters=3)
In [77]:
#Getting and adding the cluster labels for each player to the dataset
labels1 = km1.labels_
data['player_segment'] = km1.labels_
In [78]:
#Max sample plots set to 500
player_sample_1 = data.sample(n=500, random_state=42)
#Adding jitter for better visuals
player_sample_1['SessionsPerWeek_jitter'] = player_sample_1['SessionsPerWeek'] + np.random.normal(0, 0.2, size=player_sample_1.shape[0])
player_sample_1['PlayTimeHours_jitter'] = player_sample_1['PlayTimeHours'] + np.random.normal(0, 0.5, size=player_sample_1.shape[0])
In [79]:
#Plotting Scatterplot for first clustering example
plt.figure(figsize=(8,6))
sns.scatterplot(x='SessionsPerWeek_jitter', y='PlayTimeHours_jitter', hue='player_segment', palette='Set1', data=player_sample_1)
plt.xlabel('Sessions per Week')
plt.ylabel('Play Time Hours')
plt.title('KMeans Player Behaviour')
plt.legend(title='Cluster Groups')
plt.show()
In [80]:
#Clustering example 2
subset_2 = data[['PlayerLevel', 'PlayTimeHours']]
In [81]:
#Standardising chosen features
cluster_scaled_2 = StandardScaler().fit_transform(subset_2)
In [82]:
#Creating second k means model with three clusters
km2 = KMeans(n_clusters=3)
km2.fit(cluster_scaled_2)
Out[82]:
KMeans(n_clusters=3)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KMeans(n_clusters=3)
In [83]:
#Store the newcluster labels in the dataset
data['player_segment_2'] = km2.labels_
In [85]:
#Second player sample, limited to 500 plots so the graph is readable
player_sample_2 = data.sample(n=500, random_state=42)
#Adding jitter so simliar plots don't overlap
player_sample_2['PlayerLevel_jitter'] = player_sample_2['PlayerLevel'] + np.random.normal(0, 0.2, size=player_sample_2.shape[0])
player_sample_2['PlayTimeHours_jitter'] = player_sample_2['PlayTimeHours'] + np.random.normal(0, 0.5, size=player_sample_2.shape[0])
In [86]:
#Plotting second scattergraph for clustering exmaple 2
plt.figure(figsize=(8,6))
sns.scatterplot(x='PlayerLevel_jitter', y='PlayTimeHours_jitter', hue='player_segment_2', palette='Set1', data=player_sample_2)
plt.xlabel('Player Level')
plt.ylabel('Play Time Hours')
plt.title('K-Means Player Segments by Level & Hours Played')
plt.legend(title='Cluster')
plt.show()
In [87]:
#Creating an empty list to store values
WCSS = []
silhouette_scores = []
#Setting range of clusters to try
K = range(2, 9) #silhouette score cannot run k=1
#Creating loop that will keep looping through the set range
for i in K:
model = KMeans(n_clusters=i, init='k-means++', max_iter=300, n_init=10, random_state=42)
model.fit(cluster_scaled) #Fitting the k means model to the scaled data
WCSS.append(model.inertia_)
#Calculating silhouette scor
score = silhouette_score(cluster_scaled, model.labels_)
silhouette_scores.append(score)
In [88]:
#Plotting the 'Elbow Method' graph
plt.plot(K, WCSS)
plt.xlabel('Number of clusters (k)')
plt.ylabel('WCSS')
plt.show()
In [89]:
#Plotting the silhouette score graph
plt.figure(figsize=(8,5))
plt.plot(K, silhouette_scores, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score vs Number of Clusters')
plt.show()
In [ ]:
#Printing the column heading of the dataset
print(data.columns)
In [ ]: