In [ ]:
import pandas as pd
df = pd.read_csv("../data/online_gaming_behavior_dataset.csv")
print(df.shape)
print(df.columns)
df.head()
(40034, 13)
Index(['PlayerID', 'Age', 'Gender', 'Location', 'GameGenre', 'PlayTimeHours',
'InGamePurchases', 'GameDifficulty', 'SessionsPerWeek',
'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked',
'EngagementLevel'],
dtype='str')
Out[ ]:
| PlayerID | Age | Gender | Location | GameGenre | PlayTimeHours | InGamePurchases | GameDifficulty | SessionsPerWeek | AvgSessionDurationMinutes | PlayerLevel | AchievementsUnlocked | EngagementLevel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 9000 | 43 | Male | Other | Strategy | 16.271119 | 0 | Medium | 6 | 108 | 79 | 25 | Medium |
| 1 | 9001 | 29 | Female | USA | Strategy | 5.525961 | 0 | Medium | 5 | 144 | 11 | 10 | Medium |
| 2 | 9002 | 22 | Female | USA | Sports | 8.223755 | 0 | Easy | 16 | 142 | 35 | 41 | High |
| 3 | 9003 | 35 | Male | USA | Action | 5.265351 | 1 | Easy | 9 | 85 | 57 | 47 | Medium |
| 4 | 9004 | 33 | Male | Europe | Action | 15.531945 | 0 | Medium | 2 | 131 | 95 | 37 | Medium |
In [ ]:
import os
print(os.listdir("../data"))
['online_gaming_behavior_dataset.csv']
In [ ]:
# Import required libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
In [ ]:
df = pd.read_csv("../data/online_gaming_behavior_dataset.csv")
df.head()
Out[ ]:
| PlayerID | Age | Gender | Location | GameGenre | PlayTimeHours | InGamePurchases | GameDifficulty | SessionsPerWeek | AvgSessionDurationMinutes | PlayerLevel | AchievementsUnlocked | EngagementLevel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 9000 | 43 | Male | Other | Strategy | 16.271119 | 0 | Medium | 6 | 108 | 79 | 25 | Medium |
| 1 | 9001 | 29 | Female | USA | Strategy | 5.525961 | 0 | Medium | 5 | 144 | 11 | 10 | Medium |
| 2 | 9002 | 22 | Female | USA | Sports | 8.223755 | 0 | Easy | 16 | 142 | 35 | 41 | High |
| 3 | 9003 | 35 | Male | USA | Action | 5.265351 | 1 | Easy | 9 | 85 | 57 | 47 | Medium |
| 4 | 9004 | 33 | Male | Europe | Action | 15.531945 | 0 | Medium | 2 | 131 | 95 | 37 | Medium |
In [ ]:
print(df.shape)
print(df.columns)
(40034, 13)
Index(['PlayerID', 'Age', 'Gender', 'Location', 'GameGenre', 'PlayTimeHours',
'InGamePurchases', 'GameDifficulty', 'SessionsPerWeek',
'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked',
'EngagementLevel'],
dtype='str')
In [ ]:
df.info()
<class 'pandas.DataFrame'> RangeIndex: 40034 entries, 0 to 40033 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PlayerID 40034 non-null int64 1 Age 40034 non-null int64 2 Gender 40034 non-null str 3 Location 40034 non-null str 4 GameGenre 40034 non-null str 5 PlayTimeHours 40034 non-null float64 6 InGamePurchases 40034 non-null int64 7 GameDifficulty 40034 non-null str 8 SessionsPerWeek 40034 non-null int64 9 AvgSessionDurationMinutes 40034 non-null int64 10 PlayerLevel 40034 non-null int64 11 AchievementsUnlocked 40034 non-null int64 12 EngagementLevel 40034 non-null str dtypes: float64(1), int64(7), str(5) memory usage: 4.0 MB
In [ ]:
df.isnull().sum()
Out[ ]:
PlayerID 0 Age 0 Gender 0 Location 0 GameGenre 0 PlayTimeHours 0 InGamePurchases 0 GameDifficulty 0 SessionsPerWeek 0 AvgSessionDurationMinutes 0 PlayerLevel 0 AchievementsUnlocked 0 EngagementLevel 0 dtype: int64
In [ ]:
sns.countplot(data=df, x="EngagementLevel")
plt.show()
In [ ]:
# Separate input features and target variable
X = df.drop("EngagementLevel", axis=1)
y = df["EngagementLevel"]
print("Feature shape:", X.shape)
print("Target shape:", y.shape)
print("\nTarget classes:")
print(y.value_counts())
Feature shape: (40034, 12) Target shape: (40034,) Target classes: EngagementLevel Medium 19374 High 10336 Low 10324 Name: count, dtype: int64
In [ ]:
# Remove PlayerID because it is just an identifier
if "PlayerID" in X.columns:
X = X.drop("PlayerID", axis=1)
print(X.columns)
Index(['Age', 'Gender', 'Location', 'GameGenre', 'PlayTimeHours',
'InGamePurchases', 'GameDifficulty', 'SessionsPerWeek',
'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked'],
dtype='str')
In [ ]:
# Separate numeric and categorical columns
numeric_features = X.select_dtypes(include=["int64", "float64"]).columns.tolist()
categorical_features = X.select_dtypes(include=["object"]).columns.tolist()
print("Numeric features:", numeric_features)
print("Categorical features:", categorical_features)
Numeric features: ['Age', 'PlayTimeHours', 'InGamePurchases', 'SessionsPerWeek', 'AvgSessionDurationMinutes', 'PlayerLevel', 'AchievementsUnlocked'] Categorical features: ['Gender', 'Location', 'GameGenre', 'GameDifficulty']
C:\Users\Sophie\AppData\Local\Temp\ipykernel_19744\1700668079.py:3: Pandas4Warning: For backward compatibility, 'str' dtypes are included by select_dtypes when 'object' dtype is specified. This behavior is deprecated and will be removed in a future version. Explicitly pass 'str' to `include` to select them, or to `exclude` to remove them and silence this warning. See https://pandas.pydata.org/docs/user_guide/migration-3-strings.html#string-migration-select-dtypes for details on how to write code that works with pandas 2 and 3. categorical_features = X.select_dtypes(include=["object"]).columns.tolist()
In [ ]:
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import OneHotEncoder, StandardScaler, label_binarize
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
f1_score,
classification_report,
confusion_matrix,
roc_auc_score
)
In [ ]:
# Numeric data: fill missing values and scale
numeric_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
# Categorical data: fill missing values
categorical_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
# Combine both preprocessing steps
preprocessor = ColumnTransformer(transformers=[
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features)
])
In [ ]:
# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y
)
print("X_train:", X_train.shape)
print("X_test:", X_test.shape)
print("y_train:", y_train.shape)
print("y_test:", y_test.shape)
X_train: (32027, 11) X_test: (8007, 11) y_train: (32027,) y_test: (8007,)
In [ ]:
# Decision tree
dt_model = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", DecisionTreeClassifier(random_state=42))
])
dt_model.fit(X_train, y_train)
y_pred_dt = dt_model.predict(X_test)
In [ ]:
# Evaluate decision tree
print("Decision Tree Accuracy:", accuracy_score(y_test, y_pred_dt))
print("Decision Tree Precision:", precision_score(y_test, y_pred_dt, average="weighted"))
print("Decision Tree Recall:", recall_score(y_test, y_pred_dt, average="weighted"))
print("Decision Tree F1 Score:", f1_score(y_test, y_pred_dt, average="weighted"))
print("\nClassification Report:\n")
print(classification_report(y_test, y_pred_dt))
Decision Tree Accuracy: 0.833395778693643
Decision Tree Precision: 0.8338977898654687
Decision Tree Recall: 0.833395778693643
Decision Tree F1 Score: 0.8336055742387114
Classification Report:
precision recall f1-score support
High 0.80 0.82 0.81 2067
Low 0.79 0.80 0.80 2065
Medium 0.87 0.86 0.86 3875
accuracy 0.83 8007
macro avg 0.82 0.83 0.82 8007
weighted avg 0.83 0.83 0.83 8007
In [ ]:
cm_dt = confusion_matrix(y_test, y_pred_dt)
sns.heatmap(cm_dt, annot=True, fmt="d", cmap="Blues",
xticklabels=dt_model.classes_,
yticklabels=dt_model.classes_)
plt.title("Decision Tree Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()
In [ ]:
# Convert target labels into binary format for multiclass AUC
y_test_bin = label_binarize(y_test, classes=sorted(y.unique()))
y_prob_dt = dt_model.predict_proba(X_test)
auc_dt = roc_auc_score(y_test_bin, y_prob_dt, multi_class="ovr", average="weighted")
print("Decision Tree AUC-ROC:", auc_dt)
Decision Tree AUC-ROC: 0.8696013996767114
In [ ]:
# SVM
svm_model = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", SVC(probability=True, random_state=42))
])
svm_model.fit(X_train, y_train)
y_pred_svm = svm_model.predict(X_test)
y_prob_svm = svm_model.predict_proba(X_test)
print("SVM Accuracy:", accuracy_score(y_test, y_pred_svm))
print("SVM Precision:", precision_score(y_test, y_pred_svm, average="weighted"))
print("SVM Recall:", recall_score(y_test, y_pred_svm, average="weighted"))
print("SVM F1 Score:", f1_score(y_test, y_pred_svm, average="weighted"))
auc_svm = roc_auc_score(y_test_bin, y_prob_svm, multi_class="ovr", average="weighted")
print("SVM AUC-ROC:", auc_svm)
SVM Accuracy: 0.8982140626951417 SVM Precision: 0.8989143315495862 SVM Recall: 0.8982140626951417 SVM F1 Score: 0.8977512234275661 SVM AUC-ROC: 0.9429657473099876
In [ ]:
cm_svm = confusion_matrix(y_test, y_pred_svm)
sns.heatmap(cm_svm, annot=True, fmt="d", cmap="Greens",
xticklabels=svm_model.classes_,
yticklabels=svm_model.classes_)
plt.title("SVM Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()
In [ ]:
# Model results
In [ ]:
# Tuning
dt_param_grid = {
"classifier__criterion": ["gini", "entropy"],
"classifier__max_depth": [5, 10, 15, None],
"classifier__min_samples_split": [2, 5, 10],
"classifier__min_samples_leaf": [1, 2, 4]
}
dt_grid = GridSearchCV(
estimator=dt_model,
param_grid=dt_param_grid,
cv=5,
scoring="f1_weighted",
n_jobs=-1
)
dt_grid.fit(X_train, y_train)
print("Best Decision Tree Parameters:")
print(dt_grid.best_params_)
print("\nBest CV Score:")
print(dt_grid.best_score_)
Best Decision Tree Parameters:
{'classifier__criterion': 'entropy', 'classifier__max_depth': 10, 'classifier__min_samples_leaf': 4, 'classifier__min_samples_split': 10}
Best CV Score:
0.9016214299081533
In [ ]:
best_dt = dt_grid.best_estimator_
y_pred_best_dt = best_dt.predict(X_test)
y_prob_best_dt = best_dt.predict_proba(X_test)
print("Tuned Decision Tree Accuracy:", accuracy_score(y_test, y_pred_best_dt))
print("Tuned Decision Tree Precision:", precision_score(y_test, y_pred_best_dt, average="weighted"))
print("Tuned Decision Tree Recall:", recall_score(y_test, y_pred_best_dt, average="weighted"))
print("Tuned Decision Tree F1 Score:", f1_score(y_test, y_pred_best_dt, average="weighted"))
auc_best_dt = roc_auc_score(y_test_bin, y_prob_best_dt, multi_class="ovr", average="weighted")
print("Tuned Decision Tree AUC-ROC:", auc_best_dt)
Tuned Decision Tree Accuracy: 0.9009616585487699 Tuned Decision Tree Precision: 0.9008195082895362 Tuned Decision Tree Recall: 0.9009616585487699 Tuned Decision Tree F1 Score: 0.9007897417504346 Tuned Decision Tree AUC-ROC: 0.9332336524836699
In [ ]:
cm_best_dt = confusion_matrix(y_test, y_pred_best_dt)
sns.heatmap(cm_best_dt, annot=True, fmt="d", cmap="Blues",
xticklabels=best_dt.classes_,
yticklabels=best_dt.classes_)
plt.title("Tuned Decision Tree Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()
In [ ]:
svm_param_grid = {
"classifier__C": [0.1, 1, 10],
"classifier__kernel": ["linear", "rbf"],
"classifier__gamma": ["scale", "auto"]
}
svm_grid = GridSearchCV(
estimator=svm_model,
param_grid=svm_param_grid,
cv=5,
scoring="f1_weighted",
n_jobs=-1
)
svm_grid.fit(X_train, y_train)
print("Best SVM Parameters:")
print(svm_grid.best_params_)
print("\nBest CV Score:")
print(svm_grid.best_score_)
Best SVM Parameters:
{'classifier__C': 10, 'classifier__gamma': 'auto', 'classifier__kernel': 'rbf'}
Best CV Score:
0.8973343973819523
In [ ]:
best_svm = svm_grid.best_estimator_
y_pred_best_svm = best_svm.predict(X_test)
y_prob_best_svm = best_svm.predict_proba(X_test)
print("Tuned SVM Accuracy:", accuracy_score(y_test, y_pred_best_svm))
print("Tuned SVM Precision:", precision_score(y_test, y_pred_best_svm, average="weighted"))
print("Tuned SVM Recall:", recall_score(y_test, y_pred_best_svm, average="weighted"))
print("Tuned SVM F1 Score:", f1_score(y_test, y_pred_best_svm, average="weighted"))
auc_best_svm = roc_auc_score(y_test_bin, y_prob_best_svm, multi_class="ovr", average="weighted")
print("Tuned SVM AUC-ROC:", auc_best_svm)
Tuned SVM Accuracy: 0.8989634070188585 Tuned SVM Precision: 0.8994374772757093 Tuned SVM Recall: 0.8989634070188585 Tuned SVM F1 Score: 0.8985511811195575 Tuned SVM AUC-ROC: 0.9432520461545656
In [ ]:
cm_best_svm = confusion_matrix(y_test, y_pred_best_svm)
sns.heatmap(cm_best_svm, annot=True, fmt="d", cmap="Greens",
xticklabels=best_svm.classes_,
yticklabels=best_svm.classes_)
plt.title("Tuned SVM Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()
In [ ]:
results = pd.DataFrame({
"Model": [
"Decision Tree",
"Tuned Decision Tree",
"SVM",
"Tuned SVM"
],
"Accuracy": [
accuracy_score(y_test, y_pred_dt),
accuracy_score(y_test, y_pred_best_dt),
accuracy_score(y_test, y_pred_svm),
accuracy_score(y_test, y_pred_best_svm)
],
"Precision": [
precision_score(y_test, y_pred_dt, average="weighted"),
precision_score(y_test, y_pred_best_dt, average="weighted"),
precision_score(y_test, y_pred_svm, average="weighted"),
precision_score(y_test, y_pred_best_svm, average="weighted")
],
"Recall": [
recall_score(y_test, y_pred_dt, average="weighted"),
recall_score(y_test, y_pred_best_dt, average="weighted"),
recall_score(y_test, y_pred_svm, average="weighted"),
recall_score(y_test, y_pred_best_svm, average="weighted")
],
"F1 Score": [
f1_score(y_test, y_pred_dt, average="weighted"),
f1_score(y_test, y_pred_best_dt, average="weighted"),
f1_score(y_test, y_pred_svm, average="weighted"),
f1_score(y_test, y_pred_best_svm, average="weighted")
],
"AUC-ROC": [
auc_dt,
auc_best_dt,
auc_svm,
auc_best_svm
]
})
results
Out[ ]:
| Model | Accuracy | Precision | Recall | F1 Score | AUC-ROC | |
|---|---|---|---|---|---|---|
| 0 | Decision Tree | 0.833396 | 0.833898 | 0.833396 | 0.833606 | 0.869601 |
| 1 | Tuned Decision Tree | 0.900962 | 0.900820 | 0.900962 | 0.900790 | 0.933234 |
| 2 | SVM | 0.898214 | 0.898914 | 0.898214 | 0.897751 | 0.942966 |
| 3 | Tuned SVM | 0.898963 | 0.899437 | 0.898963 | 0.898551 | 0.943252 |
In [ ]:
# Remove target variable
X_cluster = df.drop("EngagementLevel", axis=1)
# Remove PlayerID if it exists
if "PlayerID" in X_cluster.columns:
X_cluster = X_cluster.drop("PlayerID", axis=1)
print(X_cluster.head())
Age Gender Location GameGenre PlayTimeHours InGamePurchases \ 0 43 Male Other Strategy 16.271119 0 1 29 Female USA Strategy 5.525961 0 2 22 Female USA Sports 8.223755 0 3 35 Male USA Action 5.265351 1 4 33 Male Europe Action 15.531945 0 GameDifficulty SessionsPerWeek AvgSessionDurationMinutes PlayerLevel \ 0 Medium 6 108 79 1 Medium 5 144 11 2 Easy 16 142 35 3 Easy 9 85 57 4 Medium 2 131 95 AchievementsUnlocked 0 25 1 10 2 41 3 47 4 37
In [ ]:
X_cluster_processed = preprocessor.fit_transform(X_cluster)
# Convert to array if needed
if hasattr(X_cluster_processed, "toarray"):
X_cluster_processed = X_cluster_processed.toarray()
In [ ]:
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
inertias = []
sil_scores = []
k_values = range(2, 9)
for k in k_values:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_cluster_processed)
inertias.append(kmeans.inertia_)
sil_scores.append(silhouette_score(X_cluster_processed, labels))
In [ ]:
plt.plot(k_values, inertias, marker='o')
plt.title("Elbow Method")
plt.xlabel("Number of Clusters")
plt.ylabel("Inertia")
plt.show()
In [ ]:
plt.plot(k_values, sil_scores, marker='o')
plt.title("Silhouette Scores")
plt.xlabel("Number of Clusters")
plt.ylabel("Score")
plt.show()
In [ ]:
best_k = 3
kmeans_final = KMeans(n_clusters=best_k, random_state=42, n_init=10)
df["Cluster"] = kmeans_final.fit_predict(X_cluster_processed)
print(df["Cluster"].value_counts())
Cluster 1 16188 0 15805 2 8041 Name: count, dtype: int64
In [ ]:
print("Silhouette Score:", silhouette_score(X_cluster_processed, df["Cluster"]))
print("Inertia:", kmeans_final.inertia_)
Silhouette Score: 0.09250132787771276 Inertia: 320152.55243620666
In [ ]:
cluster_summary = df.groupby("Cluster").mean(numeric_only=True)
print(cluster_summary)
PlayerID Age PlayTimeHours InGamePurchases \
Cluster
0 28917.836128 31.996077 12.013948 0.0
1 29086.247714 31.990919 12.076099 0.0
2 29070.014053 31.988807 11.940694 1.0
SessionsPerWeek AvgSessionDurationMinutes PlayerLevel \
Cluster
0 14.524391 94.747548 49.641316
1 4.509390 94.984433 49.484680
2 9.530780 94.493222 50.027609
AchievementsUnlocked
Cluster
0 24.538121
1 24.513714
2 24.529287
In [ ]:
cluster_summary_clean = df.drop(columns=["PlayerID"], errors="ignore").groupby("Cluster").mean(numeric_only=True)
print(cluster_summary_clean)
Age PlayTimeHours InGamePurchases SessionsPerWeek \
Cluster
0 31.996077 12.013948 0.0 14.524391
1 31.990919 12.076099 0.0 4.509390
2 31.988807 11.940694 1.0 9.530780
AvgSessionDurationMinutes PlayerLevel AchievementsUnlocked
Cluster
0 94.747548 49.641316 24.538121
1 94.984433 49.484680 24.513714
2 94.493222 50.027609 24.529287
In [ ]:
pd.crosstab(df["Cluster"], df["EngagementLevel"], normalize="index")
Out[ ]:
| EngagementLevel | High | Low | Medium |
|---|---|---|---|
| Cluster | |||
| 0 | 0.467953 | 0.086175 | 0.445872 |
| 1 | 0.049790 | 0.427786 | 0.522424 |
| 2 | 0.265390 | 0.253327 | 0.481283 |
In [ ]:
cluster_summary_clean = df.drop(columns=["PlayerID"], errors="ignore").groupby("Cluster").mean(numeric_only=True)
print(cluster_summary_clean)
Age PlayTimeHours InGamePurchases SessionsPerWeek \
Cluster
0 31.996077 12.013948 0.0 14.524391
1 31.990919 12.076099 0.0 4.509390
2 31.988807 11.940694 1.0 9.530780
AvgSessionDurationMinutes PlayerLevel AchievementsUnlocked
Cluster
0 94.747548 49.641316 24.538121
1 94.984433 49.484680 24.513714
2 94.493222 50.027609 24.529287
In [ ]: