Entrega Final - Grupo 12


Introducción a la Minería de Datos - Otoño 2018

Pedro Belmonte,
Jorge Fabry,
Víctor Garrido,
Pablo Ilabaca

GitHub


Introducción


Motivación

La industria de los videojuegos es una de las más grandes industrias de entretenimiento a nivel mundial. Las mayores publicaciones se enfrentan codo a codo para conseguir el mayor éxito y con esto mayores ventas.

En esta industria, como en muchas otras, los críticos juegan un rol vital a la hora de definir la recepción que tendrá un juego. Casi siempre, los críticos reciben copias de juegos antes de que estos sean lanzados al público, por lo que tienen la primera palabra a la hora de publicitar si un juego es de calidad o no.

Dado esto, se da origen a un fenómeno en el que los críticos dan muy buena crítica a un juego, tal vez motivados por dinero o por quedar bien con los publicadores para seguir recibiendo acceso exclusivo a los juegos, y luego los usuarios dan un puntaje mucho menor, dejando una sensación de engaño y desencanto. A estos juegos con una gran diferencia de puntaje los llamaremos fiascos.

Ejemplos de Fiascos

Interesa entonces utilizar las herramientas que provee este curso para estudiar los distintos patrones que pueden surgir a la hora de puntuar la calidad de un juego. En específico, lograr crear un predictor para saber, ojalá con bastante seguridad, si un juego será un fiasco o no.

Hipótesis

Por la información manejada por los miembros del equipo, y lo que se ha observado de los últimos grandes fiascos en la industria de los videojuegos, se intuye que un factor importante a la hora de determinar si un juego será un fiasco o no será el publicador del juego. Esperamos que esta variable tenga alta correlación con los fiascos.

Data Set


Origen

Para explorar el fenómeno antes descrito, se utiliza el dataset extraido de https://www.kaggle.com/silver1996/videogames/data.

Este se construye de datos de Metacritic.com, el cual incluye 16719 entradas con los datos que se presentan a continuación.

In [18]:
import pandas as pd
import numpy as np
original_data = pd.read_csv('data/Video_Games_Sales_as_at_22_Dec_2016.csv',encoding='latin1')
print("(Filas x Columnas) = ",original_data.shape)
original_data.head()
(Filas x Columnas) =  (16719, 16)
Out[18]:
Name Platform Year_of_Release Genre Publisher NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales Critic_Score Critic_Count User_Score User_Count Developer Rating
0 Wii Sports Wii 2006.0 Sports Nintendo 41.36 28.96 3.77 8.45 82.53 76.0 51.0 8 322.0 Nintendo E
1 Super Mario Bros. NES 1985.0 Platform Nintendo 29.08 3.58 6.81 0.77 40.24 NaN NaN NaN NaN NaN NaN
2 Mario Kart Wii Wii 2008.0 Racing Nintendo 15.68 12.76 3.79 3.29 35.52 82.0 73.0 8.3 709.0 Nintendo E
3 Wii Sports Resort Wii 2009.0 Sports Nintendo 15.61 10.93 3.28 2.95 32.77 80.0 73.0 8 192.0 Nintendo E
4 Pokemon Red/Pokemon Blue GB 1996.0 Role-Playing Nintendo 11.27 8.89 10.22 1.00 31.37 NaN NaN NaN NaN NaN NaN

Los gráficos presentados a continuación se contruyen con este data set, con la intención de extraer información útil de este.

Publicadores controversiales Puntajes por género

Se observa que el data set incluye muchas columnas con distintas variables, y también algunas filas que le faltan valores. Para comenzar a usar este data set se debe hacer una limpieza que solo deje datos que sean útiles.

Limpieza

El siguiente script en R busca limpiar y ordenar el data set para ser utilizado posteriormente por los clasificadores. Se borran varias columnas que no estarían disponibles cuando sale un juego, como ventas. También se borran columnas que no nos aportarán información al explorar a futuro, como el año de lanzamiento, o el nombre del juego.

Se busca tambien limitar la cantidad de valores distintos de varias columnas, para mantener el problema con baja dimensionalidad.

########### 1 - creacion de la tabla y tipos ################
## cambiar esta linea al path del .csv
## si se mantiene la estructura de carpetas del repositorio, basta con 
## dejar el working directory en la ubicacion de este archivo
library(readr)
videogames <- read_csv("../data/Video_Games_Sales_as_at_22_Dec_2016.csv")

# se eliminan las filas que tengan NA (quedan aprox 7000 resultados)
filtered_vid = na.omit(videogames)

# se arreglan los tipos: user-score a numeric, los otros char a factor
filtered_vid$User_Score <- as.numeric(filtered_vid$User_Score)
character_vars <- lapply(filtered_vid, class) == "character"
filtered_vid[, character_vars] <- lapply(filtered_vid[, character_vars], as.factor)

# se define la diferencia necesaria para considerar que un juego fue un fiasco
# (al comparar user_score con critic_score)
dif_for_fail = 2

# se crea y pobla la columna que define si un juego es un fiasco o no
filtered_vid["Is_Fiasco"] <- NA
filtered_vid$Is_Fiasco <- ((filtered_vid$Critic_Score / 10) - filtered_vid$User_Score) > dif_for_fail

########## 2 - reduccion de dimensionalidad para Machine Learning ##########
## eliminar categorias
reduced <- filtered_vid
reduced["User_Count"] <- NULL
reduced["User_Score"] <- NULL
reduced["Critic_Count"] <- NULL
reduced["Other_Sales"] <- NULL
reduced["EU_Sales"] <- NULL
reduced["JP_Sales"] <- NULL
reduced["NA_Sales"] <- NULL
reduced["Year_of_Release"] <- NULL
reduced["Name"] <- NULL
reduced["Developer"] <- NULL
reduced["Developer"] <- NULL
## tomar solo las n consolas mas populares:
n = 5
popular_console <- reduced
popular_console["counter"] <- 1
popular_console = aggregate(counter ~ Platform,popular_console,FUN=sum)
popular_console = popular_console[order(popular_console$counter,decreasing = T),]
reduced <- reduced[reduced$Platform %in% popular_console[1:n,]$Platform,]

## tomar solo los n mayores publicadores:
n = 10
popular_publisher <- reduced
popular_publisher["counter"] <- 1
popular_publisher = aggregate(counter ~ Publisher,popular_publisher,FUN=sum)
popular_publisher = popular_publisher[order(popular_publisher$counter,decreasing = T),]
reduced <- reduced[reduced$Publisher %in% popular_publisher[1:n,]$Publisher,]
#### Descomentar siguiente linea para exportar el dataset
write.csv(reduced, file = "../data/data_para_clasificadores.csv")

Tras esto, se tienen 8 columnas. La primera un número por cada juego. Las siguientes 6 son parámetros, y la última la clase a clasificiar del juego. La clase corresponde a si el juego es un fiasco o no.

In [19]:
data = pd.read_csv('data/data_para_clasificadores.csv',encoding='latin1')
data.head()
Out[19]:
Unnamed: 0 Platform Genre Publisher Global_Sales Critic_Score Rating Is_Fiasco
0 1 X360 Misc Microsoft Game Studios 21.81 61 E False
1 2 PS3 Action Take-Two Interactive 21.04 97 M False
2 3 PS2 Action Take-Two Interactive 20.81 95 M False
3 4 X360 Action Take-Two Interactive 16.27 97 M False
4 5 PS2 Action Take-Two Interactive 16.15 95 M False

Cabe notar que las clases no están balanceadas.

In [20]:
print("Cantidad de Fiascos")
data['Is_Fiasco'].value_counts()
Cantidad de Fiascos
Out[20]:
False    2169
True      179
Name: Is_Fiasco, dtype: int64

Adaptando el Data Set para clasificadores

Los clasificadores no pueden trabajar con Strings directamente. Vemos que Platform, Genre, Publisher y Rating son categorías que utilizan Strings, y hay que aplicar algún tipo de transformación para poder alimentarlas al clasificador.

Para esto, se utiliza un LabelBinarizer, que permite covertir las distintas categorías de una columna en columnas independientes. El resultado final es una matriz de dimensiones: 2348 rows x 34 columns.

In [21]:
from sklearn import preprocessing

## Se aplica LabelBinarizer columna por columna, y finalmente se unen los resultados
## En header se van guardando los nombres de cada columna para luego agregarlas al nuevo Data Set
lb = preprocessing.LabelBinarizer()

lb.fit(data["Platform"])
platform = lb.transform(data["Platform"])
header = lb.classes_

lb.fit(data["Genre"])
genre = lb.transform(data["Genre"])
header = np.append(header,lb.classes_)

lb.fit(data["Publisher"])
publisher = lb.transform(data["Publisher"])
header = np.append(header,lb.classes_)

##sales = np.transpose(np.matrix(data["Global_Sales"].values))
##header = np.append(header,"Global_Sales")

critic_score = np.transpose(np.matrix(data["Critic_Score"].values))
header = np.append(header,"Critic_Score")

lb.fit(data["Rating"])
rating = lb.transform(data["Rating"])
header = np.append(header,lb.classes_)

fiasco = np.transpose(np.matrix(data["Is_Fiasco"].values))
header = np.append(header,"Is_Fiasco")

new_matrix = np.hstack((platform,genre,publisher,critic_score,rating,fiasco))
new_data = pd.DataFrame(new_matrix)
new_data.columns = header

## Se separa los datos de los resultados a predecir.
X = new_data[new_data.columns[:-1]]
y = new_data[new_data.columns[-1]]

Para observar la nueva data:

In [22]:
new_data.head()
Out[22]:
PC PS2 PS3 X360 XB Action Adventure Fighting Misc Platform ... THQ Take-Two Interactive Ubisoft Critic_Score AO E E10+ M T Is_Fiasco
0 0 0 0 1 0 0 0 0 1 0 ... 0 0 0 61 0 1 0 0 0 0
1 0 0 1 0 0 1 0 0 0 0 ... 0 1 0 97 0 0 0 1 0 0
2 0 1 0 0 0 1 0 0 0 0 ... 0 1 0 95 0 0 0 1 0 0
3 0 0 0 1 0 1 0 0 0 0 ... 0 1 0 97 0 0 0 1 0 0
4 0 1 0 0 0 1 0 0 0 0 ... 0 1 0 95 0 0 0 1 0 0

5 rows × 34 columns

Encontrando nuestro Predictor


Experimentos básicos para elegir predictor

Como predictor de fiascos, se busca tener un clasificador que tenga un porcentaje alto de predicción de fiascos. Mediante varios experimentos, se muestra a continuación como se comparan varios clasificadores ante nuestro data set.

Utilizando código del laboratorio 2.2 del curso, se comparan distintos clasificadores mediante el contraste de las métricas promedio obtenidas tras un buen número de pruebas.

In [23]:
import graphviz
import io
import pydotplus
import imageio

from sklearn import tree
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.model_selection import train_test_split, cross_val_score
from matplotlib import pyplot as plt
from sklearn.metrics import f1_score, recall_score, precision_score
from sklearn.dummy import DummyClassifier
from sklearn.naive_bayes import GaussianNB  # naive bayes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC  # support vector machine classifier
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
In [24]:
def run_clf_with_cross_val(clf, X, y, num_tests=100, k=5):
    metrics = {'f1-score': [], 'precision': [], 'recall': [], 'score': []}
    
    for _ in range(num_tests):
        
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.30, stratify=y)
        clf.fit(X_train, y_train)
        predictions = clf.predict(X_test)
        scores = cross_val_score(clf, X, y, cv=k, scoring='f1')
        
        metrics['f1-score'].append(f1_score(y_test,predictions))
        metrics['recall'].append(recall_score(y_test,predictions))
        metrics['precision'].append(precision_score(y_test,predictions))
        metrics['score'].append(scores.mean())
    
    return metrics
In [25]:
import warnings
warnings.filterwarnings('ignore')
def run_many_classifiers(X, y, num_test):
    c0 = ("Base Dummy", DummyClassifier(strategy='stratified'))
    c1 = ("Decision Tree", DecisionTreeClassifier(min_samples_split=100))
    c2 = ("Gaussian Naive Bayes", GaussianNB())
    c3 = ("KNN-3", KNeighborsClassifier(n_neighbors=3))
    c4 = ("KNN-5", KNeighborsClassifier(n_neighbors=5))
    c5 = ("Random Forest",RandomForestClassifier(max_features="auto", max_depth=15, n_estimators=40))


    classifiers = [c0, c1, c2, c3, c4, c5]
    print("Corriendo "+ str(num_test) + " tests por clasificador\n")

    for name, clf in classifiers:
        metrics = run_clf_with_cross_val(clf, X, y, num_test)
        print("----------------")
        print("Resultados para clasificador: ",name) 
        print("Precision promedio:",np.array(metrics['precision']).mean())
        print("Recall promedio:",np.array(metrics['recall']).mean())
        print("F1-score promedio:",np.array(metrics['f1-score']).mean())
        print("Cross Validation F1-score promedio:", np.array(metrics['score']).mean())
        
run_many_classifiers(X, y, 20)
warnings.filterwarnings('once')
Corriendo 20 tests por clasificador

----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.08639287015418194
Recall promedio: 0.08518518518518518
F1-score promedio: 0.08534556795480351
Cross Validation F1-score promedio: 0.07216939061186936
----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.2516799866799867
Recall promedio: 0.04444444444444444
F1-score promedio: 0.07466339131642134
Cross Validation F1-score promedio: 0.04978111319574733
----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.09453782684549078
Recall promedio: 0.877777777777778
F1-score promedio: 0.1704499937947472
Cross Validation F1-score promedio: 0.1787406621828928
----------------
Resultados para clasificador:  KNN-3
Precision promedio: 0.21359837698042558
Recall promedio: 0.07592592592592592
F1-score promedio: 0.11018184057851395
Cross Validation F1-score promedio: 0.08917704173518129
----------------
Resultados para clasificador:  KNN-5
Precision promedio: 0.3476922505598976
Recall promedio: 0.04351851851851852
F1-score promedio: 0.07546195206712222
Cross Validation F1-score promedio: 0.04525641025641025
----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.3230014100181687
Recall promedio: 0.137037037037037
F1-score promedio: 0.1909051157577269
Cross Validation F1-score promedio: 0.14792094336720985

Se puede observar que los resultados obtenidos no son considerablemente buenos. Tomando en cuenta el F1-score, que describe en general la eficacia de un clasificador, los puntajes son bien bajos, aunque aún así mejores que el base dummy.

De todos los clasificadores explorados, Gaussan Naive Bayes y Random Forest son los que se ven más prometedores a predictor de fiascos.

Utilizando Subsampling y Oversampling

En un intento de encontrar mejores resultados que los anteriores, se aplicarán estas estrategias sobre el dataset, buscando que los clasificadores aprendan mejor teniendo clases balanceadas. Nuevamente nos apoyamos en código trabajado en el laboratorio 2.2.

In [26]:
# oversampling sobre la clase True
idx = np.random.choice(new_data.loc[data.Is_Fiasco == True].index, size=1990)
data_oversampled = pd.concat([new_data, new_data.iloc[idx]])

print("Data oversampled on class 'True'")
print(data_oversampled['Is_Fiasco'].value_counts())
print()

# subsampling sobre la clase False
idx = np.random.choice(new_data.loc[new_data.Is_Fiasco == False].index, size=1990, replace=False)
data_subsampled = new_data.drop(new_data.iloc[idx].index)

print("Data subsampled on class 'False'")
print(data_subsampled['Is_Fiasco'].value_counts())
Data oversampled on class 'True'
1    2169
0    2169
Name: Is_Fiasco, dtype: int64

Data subsampled on class 'False'
1    179
0    179
Name: Is_Fiasco, dtype: int64
In [27]:
warnings.filterwarnings('ignore')
# datos "oversampleados" 
X_over = data_oversampled[new_data.columns[:-1]]
y_over = data_oversampled[new_data.columns[-1]]

# datos "subsampleados"
X_subs = data_subsampled[new_data.columns[:-1]]
y_subs = data_subsampled[new_data.columns[-1]]

print("----------Prueba Oversampling------------")
run_many_classifiers(X_over, y_over, 20)

print("\n\n----------Prueba Subsampling------------")
run_many_classifiers(X_subs, y_subs, 20)
----------Prueba Oversampling------------
Corriendo 20 tests por clasificador

----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.5039098401046036
Recall promedio: 0.5049923195084485
F1-score promedio: 0.504396468912438
Cross Validation F1-score promedio: 0.5017355488878898
----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.7780625335592861
Recall promedio: 0.8319508448540706
F1-score promedio: 0.8035882519157607
Cross Validation F1-score promedio: 0.8053475297748977
----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.5672590330572691
Recall promedio: 0.9359447004608296
F1-score promedio: 0.7061688705505398
Cross Validation F1-score promedio: 0.6991221391668334
----------------
Resultados para clasificador:  KNN-3
Precision promedio: 0.8318863261311359
Recall promedio: 0.9894777265745007
F1-score promedio: 0.9038370509092353
Cross Validation F1-score promedio: 0.9000595875342261
----------------
Resultados para clasificador:  KNN-5
Precision promedio: 0.790455272812608
Recall promedio: 0.9927035330261138
F1-score promedio: 0.8800468163041308
Cross Validation F1-score promedio: 0.879381575429894
----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.8975587105762111
Recall promedio: 0.9960829493087558
F1-score promedio: 0.9441978074590572
Cross Validation F1-score promedio: 0.9282979745820384


----------Prueba Subsampling------------
Corriendo 20 tests por clasificador

----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.49393886037532847
Recall promedio: 0.4953703703703704
F1-score promedio: 0.49375666682561964
Cross Validation F1-score promedio: 0.4911618520570743
----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.6504945358482197
Recall promedio: 0.6777777777777778
F1-score promedio: 0.6583004028697915
Cross Validation F1-score promedio: 0.6133204829954582
----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.5840915067393669
Recall promedio: 0.836111111111111
F1-score promedio: 0.6833652214531264
Cross Validation F1-score promedio: 0.6881190722270022
----------------
Resultados para clasificador:  KNN-3
Precision promedio: 0.571714001331877
Recall promedio: 0.6481481481481481
F1-score promedio: 0.6060496593745379
Cross Validation F1-score promedio: 0.6046189067294558
----------------
Resultados para clasificador:  KNN-5
Precision promedio: 0.5787112322112168
Recall promedio: 0.6481481481481483
F1-score promedio: 0.6099569004724595
Cross Validation F1-score promedio: 0.601387696350414
----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.6921772715506428
Recall promedio: 0.6990740740740741
F1-score promedio: 0.6931361751910214
Cross Validation F1-score promedio: 0.6697863289897583

También, como experimento, se buscó hacer subsampling y oversampling al mismo tiempo para no repetir tantos datos, pero tampoco quedarnos con tan pocos. Esto se muestra a continuación.

In [28]:
idx = np.random.choice(new_data.loc[data.Is_Fiasco == True].index, size=71)
data_master = pd.concat([new_data, new_data.iloc[idx]])
idx = np.random.choice(new_data.loc[new_data.Is_Fiasco == False].index, size=1669, replace=False)
data_master = data_master.drop(new_data.iloc[idx].index)
print("Data subsampled on class 'False' and oversampled on class 'True'")
print(data_master['Is_Fiasco'].value_counts())
X_mast = data_master[new_data.columns[:-1]]
y_mast = data_master[new_data.columns[-1]]
run_many_classifiers(X_mast, y_mast, 10)
Data subsampled on class 'False' and oversampled on class 'True'
0    500
1    250
Name: Is_Fiasco, dtype: int64
Corriendo 10 tests por clasificador

----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.3311626194733918
Recall promedio: 0.3493333333333334
F1-score promedio: 0.33954293163939997
Cross Validation F1-score promedio: 0.325600474540678
----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.5898924059130131
Recall promedio: 0.48533333333333334
F1-score promedio: 0.5171136326718581
Cross Validation F1-score promedio: 0.45440157199683257
----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.41798565938820104
Recall promedio: 0.8826666666666668
F1-score promedio: 0.5671468661221597
Cross Validation F1-score promedio: 0.5659838589358948
----------------
Resultados para clasificador:  KNN-3
Precision promedio: 0.5316630300406916
Recall promedio: 0.5266666666666666
F1-score promedio: 0.528070365307337
Cross Validation F1-score promedio: 0.5273948515808902
----------------
Resultados para clasificador:  KNN-5
Precision promedio: 0.5017846774376254
Recall promedio: 0.4773333333333333
F1-score promedio: 0.48830950461472045
Cross Validation F1-score promedio: 0.46698340874811467
----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.710274605571011
Recall promedio: 0.6586666666666667
F1-score promedio: 0.682457979962363
Cross Validation F1-score promedio: 0.6496337439076678

Gráficos de resultados obtenidos

A continuación se grafican los resultados obtenidos para Base Dummy, Decision Tree y Random Forest. Como se han corrido varias veces los clasificadores puede que los gráficos no correspondan perfectamente a los valores, pero sí con la cercanía suficiente para ser precisos. a

Conclusión Clasificadores

Haciendo un análisis de los resultados obtenidos, podemos considerar que Random Forest es clasificador que tiene mejor desempeño en cuanto a resultados, en especial al hacer oversampling.

Si bien no es siempre certero, tiene un puntaje suficiente de exactitud, lo que consideramos un logro aceptable con respecto a lo que esperabamos obtener. Si necesitaramos crear un predictor efectivo, utilizariamos ese clasificador.

Graficando Decision Trees

En un ejercicio para explorar la importancia de las variables, y para observar la lógica de los decision trees, se grafican los árboles de decisión, donde los colores indican afinidad con una clase.

In [29]:
c12= DecisionTreeClassifier(min_samples_split=100)
c13= DecisionTreeClassifier(min_samples_split=100)
c14= DecisionTreeClassifier(min_samples_split=100)
c15= DecisionTreeClassifier(min_samples_split=100)
features=new_data.columns[:-1]
train, test= train_test_split(new_data,test_size=.30, stratify=y)

        
X_train= train[features]
y_train=train["Is_Fiasco"]

X_test=test[features]
y_test=test["Is_Fiasco"]

dt12=c12.fit(X_train,y_train)
dt13=c13.fit(X_over,y_over)
dt14=c14.fit(X_subs,y_subs)
dt15=c15.fit(X_mast,y_mast)

def show_tree(tree, features, path):
    path = "images/" + path
    f= io.StringIO()
    export_graphviz(tree, out_file=f, feature_names=features,filled=True,rounded=True)
    pydotplus.graph_from_dot_data(f.getvalue()).write_png(path)
    img= imageio.imread(path)
    plt.rcParams["figure.figsize"]=(20,20)
    plt.imshow(img)

Árbol Normal:

In [30]:
show_tree(dt12, features, 'arbol_normal.png')

Árbol Oversampled:

In [31]:
show_tree(dt13, features, 'arbol_oversampled.png')

Árbol Subsampled:

In [32]:
show_tree(dt14, features, 'arbol_subsampled.png')

Árbol con Oversampling y Subsampling:

In [33]:
show_tree(dt15, features, 'arbol_master.png')

Importancia de Atributos

Aplicando Random Forest, se pueden obtener los atributos que más seguido se utilizan para discriminar la clase a clasificar. De esta forma se puede evidenciar cuales son las columnas más importantes para decidir si un juego es un fiasco o no.

In [34]:
import operator
clf6 = RandomForestClassifier(n_estimators= 1000,max_depth=100, random_state=0)
train, test= train_test_split(new_data,test_size=.30, stratify=y)
        
X_train= train[features]
y_train=train["Is_Fiasco"]

X_test=test[features]
y_test=test["Is_Fiasco"]
clf6.fit(X_train, y_train)
mi_lista_de_tuplas = []
for i in range(33):
    tupla = (header[i],clf6.feature_importances_[i])
    mi_lista_de_tuplas.append(tupla)
mi_lista_de_tuplas.sort(key=operator.itemgetter(1), reverse=True)
for i in range(33):
    print(mi_lista_de_tuplas[i])
('Critic_Score', 0.49078201018960455)
('PC', 0.039170555944524385)
('Activision', 0.03610005623252404)
('Sports', 0.02980405907050479)
('Electronic Arts', 0.02928461058057324)
('Action', 0.028832442667322055)
('E', 0.026244652829040895)
('PS3', 0.026189248905637487)
('X360', 0.022456646523667113)
('PS2', 0.0216533895162774)
('T', 0.021615515182657134)
('Racing', 0.02114163999028283)
('XB', 0.01972920074662465)
('M', 0.019506173980658802)
('E10+', 0.017821175206524615)
('Take-Two Interactive', 0.017314528620391706)
('Shooter', 0.01694242823023238)
('Simulation', 0.013084170703486013)
('Ubisoft', 0.01128650333082829)
('Konami Digital Entertainment', 0.01117915692635813)
('Role-Playing', 0.010258194749869696)
('THQ', 0.00953026365195995)
('Misc', 0.00915266989278169)
('Sony Computer Entertainment', 0.009121167244447342)
('Sega', 0.009003614577840945)
('Microsoft Game Studios', 0.008024055815691072)
('Strategy', 0.007667704329205631)
('Platform', 0.006919341378085991)
('Namco Bandai Games', 0.00541606998042984)
('Fighting', 0.0026607760176953392)
('Adventure', 0.001797950666092383)
('Puzzle', 0.0002048379473792084)
('AO', 0.00010518837080061251)

Vemos con esto que la variable más importante por lejos es Critic_Score, lo cual tiene mucho sentido pues con esta en parte se define la clase Is_Fiasco. Luego, dentro de las más importantes tenemos una plataforma (PC), dos publicadores (Activision y EA) y dos géneros (Action y Sports).

Clustering


Como una nueva estrategia para hacer análisis de los datos,se trabajó con clustering usando K-means. Se utilizaron los atributos “Global_Sales”, “Critic_Score” y “User_Score”, que son los únicos datos numéricos que se manejaban en el data set. El siguiente script de R detalla el trabajo hecho de clustering.

########### 1 - creacion de la tabla y tipos ################
## cambiar esta linea al path del .csv
## si se mantiene la estructura de carpetas del repositorio, basta con
## dejar el working directory en la ubicacion de este archivo
library(readr)
videogames <-
  read_csv("../data/Video_Games_Sales_as_at_22_Dec_2016.csv")

# se eliminan las filas que tengan NA (quedan aprox 7000 resultados)
filtered_vid = na.omit(videogames)

# se arreglan los tipos: user-score a numeric, los otros char a factor
filtered_vid$User_Score <- as.numeric(filtered_vid$User_Score)
character_vars <- lapply(filtered_vid, class) == "character"
filtered_vid[, character_vars] <-
  lapply(filtered_vid[, character_vars], as.factor)

# se define la diferencia necesaria para considerar que un juego fue un fiasco
# (al comparar user_score con critic_score)
dif_for_fail = 2

#se crea y pobla la columna que define si un juego es un fiasco o no
filtered_vid["Is_Fiasco"] <- NA
filtered_vid$Is_Fiasco <-
  ((filtered_vid$Critic_Score / 10) - filtered_vid$User_Score) > dif_for_fail

########## 2 - reduccion de dimensionalidad para Machine Learning ##########
## eliminar categorias
reduced <- filtered_vid
reduced["User_Count"] <- NULL
reduced["User_Score"] <- NULL
reduced["Critic_Count"] <- NULL
reduced["Other_Sales"] <- NULL
reduced["EU_Sales"] <- NULL
reduced["JP_Sales"] <- NULL
reduced["NA_Sales"] <- NULL
reduced["Year_of_Release"] <- NULL
reduced["Name"] <- NULL
reduced["Developer"] <- NULL
reduced["Developer"] <- NULL
## tomar solo las n consolas mas populares:
n = 5
popular_console <- reduced
popular_console["counter"] <- 1
popular_console = aggregate(counter ~ Platform, popular_console, FUN = sum)
popular_console = popular_console[order(popular_console$counter, decreasing = T), ]
reduced <-
  reduced[reduced$Platform %in% popular_console[1:n, ]$Platform, ]

## tomar solo los n mayores publicadores:
n = 10
popular_publisher <- reduced
popular_publisher["counter"] <- 1
popular_publisher = aggregate(counter ~ Publisher, popular_publisher, FUN =
                                sum)
popular_publisher = popular_publisher[order(popular_publisher$counter, decreasing = T), ]
reduced <-
  reduced[reduced$Publisher %in% popular_publisher[1:n, ]$Publisher, ]
#### Descomentar siguiente linea para exportar el dataset
#write.csv(reduced, file = "../data/data_para_clasificadores.csv")



################    CLUSTERING     ##############


datacluster <-
  data.frame(reduced["Global_Sales"], reduced["Critic_Score"])#se hace un vector de 2 variables para hacer un cluster
plot(datacluster) #lo miro, el me mira

# vemos el codo para ver cual k tomar ---------------
set.seed(2)
wss <- 0
clust = 15 # graficaremos hasta 15 clusters
for (i in 1:clust) {
  wss[i] <-
    sum(kmeans(datacluster, centers = i)$withinss)
}
plot(1:clust,
     wss,
     type = "b",
     xlab = "Numero de clusters",
     ylab = "wss")

# hacemos kmeans k=3 ------------------------------------
set.seed(2)
km.out <- kmeans(datacluster, 3, nstart = 20)
# vemos los resultados
plot(
  datacluster,
  col = (km.out$cluster),
  main = "Resultados usando k = 3 (plot con colores)",
  xlab = "Global_Sales",
  ylab = "Critic_Scores",
  pch = 20,
  cex = 2
)

library(ggplot2) # instalar si es necesario
# install.packages("GGally")
library(GGally)
#otra forma de visualizar
datacluster$cluster <- factor(km.out$cluster)
ggpairs(datacluster, aes(colour = cluster), alpha = 0.4)
#m?s a?n
library("cluster") # instalar si es necesario
clusplot(
  datacluster[, c(1, 2)],
  km.out$cluster,
  color = TRUE,
  shade = TRUE,
  labels = 2,
  lines = 0,
  main = "IPCAP (k=3)"
)


hc.complete <- hclust(dist(datacluster), method = "complete")
hc.single <- hclust(dist(datacluster), method = "single")
hc.average <- hclust(dist(datacluster), method = "average")
par(mfrow = c(1, 3))
plot(
  hc.complete,
  main = "Complete",
  xlab = "Global_Sales",
  ylab = "Critic_Scores",
  cex = .9
)
plot(
  hc.single,
  main = "Single",
  xlab = "Global_Sales",
  ylab = "Critic_Scores",
  cex = .9
)
plot(
  hc.average,
  main = "Average",
  xlab = "Global_Sales",
  ylab = "Critic_Scores",
  cex = .9
)

library("dbscan")
db <- dbscan(datacluster[, c(1, 2)], eps = 1, minPts = 20)

Ventas Globales / Puntación de la crítica

Primero se consideran las ventas globales y la puntuación de la crítica. Calculando el valor del parámetro WSS para distintas cantidades de clusters y utilizando el método del codo, se distinguió que 3 es un número de clusters que podría dar buenos resultados.

Con estas variables, se ve que el data set no se adapta directamente a una estrategia de clustering. Sin embargo, aún nos permite caracterizar más los datos con los que se trabajan. Se evidencia que los juegos valorados más positivamente tienden a tener mejores ventas que los que no, y en general los juegos que acumulan ventas masivas pertenecen al cluster de juegos de valoración alta por los críticos.

Ventas Globales / Puntuación de usuarios

Luego se consideran las ventas globales y puntuación de usuarios. Usando el siguiente gráfico también se decide usar K = 3

Con este clustering ocurre algo más interesante. Vemos que independiente de la puntuación de los usuarios, se registra un cluster correspondiente a juegos con altas ventas, donde básicamente todos los juegos con más de 5 millones de ventas se encuentra en esta categoría. Este cluster de altas ventas tiene un puntaje de usuarios promedio bastante alto, aunque de todas formas se pueden identificar varios outliers que tienen un puntaje considerablemente más bajo.

Puntuaciones de usuarios / Puntuaciones de críticos

Luego se consideran las puntuaciones de usuarios y críticos. Usando el siguiente gráfico también se decide usar K = 3

Este gráfico, a pesar de que es interesante ver graficadas las puntuaciones de los usuarios v/s los críticos, no entrega información que se considere relevante o nueva.

Puntuaciones de usuarios / Puntuaciones de críticos / Ventas Globales

En este gráfico se consideran las tres variables antes descritas. Al igual que el gráfico anterior, no entrega información que se considere de gran relevancia.

Reglas de Asociación


Se aplicaron técnicas de reglas de asociación. Para esto primero se hace un preprocesamiento de los datos, en donde, por ejemplo, se definen las clases respecto a la cantidad de ventas (Muchisimas, Muchas, Bastante, Intermedio, Pocas, Muypocas).

library(readr)  #cargamos readr para poder leer csv's
library(arules)  # cargamos arules
videogames <- read_csv("../data/Video_Games_Sales_as_at_22_Dec_2016.csv")
auxiliar <- videogames #usamos un auxiliar para realizar cambios sin modificar el original

# se eliminan las filas que tengan NA (quedan aprox 7000 resultados)
sinna = na.omit(videogames)
sinnauxiliar = na.omit(videogames)
#se definen los intervalos para calificar los atributos num?ricos y asignarles una etiqueta
#En caso de haber un n?mero mucho m?s alto que los dem?s, se considera outlier y se le asigna
#la etiqueta "much?simo" mientras que el segundo n?mero m?s alto se divide en 5 para crear
#los intervalos si es que no es un outlier como el anterior

#ventas en Norte Am?rica
Muchisimas = 40
Muchas = 3.136+3.136+3.136+3.136+3.136
Bastante = 3.136+3.136+3.136+3.136
Intermedio = 3.136+3.136+3.136
Pocas= 3.136+3.136
Muypocas = 3.136
ventasnulas_o_sin_informacion = 0

#ventas en Europa
Muchisimas2 = 28.96
Muchas2 = 2.552+2.552+2.552+2.552+2.552
Bastante2 = 2.552+2.552+2.552+2.552
Intermedio2 = 2.552+2.552+2.552
Pocas2= 2.552+2.552
Muypocas2 = 2.552
ventasnulas_o_sin_informacion2 = 0

#ventas en Jap?n
Muchas3 = 1.3+1.3+1.3+1.3+1.3
Bastante3 = 1.3+1.3+1.3+1.3
Intermedio3 = 1.3+1.3+1.3
Pocas3= 1.3+1.3
Muypocas3 = 1.3
ventasnulas_o_sin_informacion3 = 0

#Otras ventas
Muchas4 = 2.114+2.114+2.114+2.114+2.114
Bastante4 = 2.114+2.114+2.114+2.114
Intermedio4 = 2.114+2.114+2.114
Pocas4 = 2.114+2.114
Muypocas4 = 2.114
ventasnulas_o_sin_informacion3 = 0

#Ventas globales
Muchas5 = 82.53
Bastante5 = 2+2+2+2
Intermedio5 = 2+2+2
Pocas5 = 2+2
Muypocas5 = 2
ventasnulas_o_sin_informacion4 = 0

#se crea una columna extra para ventas de norte am?rica
sinna["Ventas NA"] <- NA

# se asignan las etiquetas a las ventas de norte am?rica
for (i in 1:6947){
  if(sinna[i,6]>Muchisimas){
    sinna[i,17]="muchisimas ventas en NA"  }
  else if (sinna[i,6]>Bastante){
    sinna[i,17]="muchas ventas en NA"  }
  else if (sinna[i,6]>Intermedio){
    sinna[i,17]="Bastantes ventas en NA"  }
  else if (sinna[i,6]>Pocas){
    sinna[i,17]="ventas en NA intermedias"  }
  else if (sinna[i,6]>Muypocas){
    sinna[i,17]="pocas ventas en NA"  }
  else if (sinna[i,6]>ventasnulas_o_sin_informacion){
    sinna[i,17]="muy pocas ventas en NA"  }
  else if (sinna[i,6]==ventasnulas_o_sin_informacion){
    sinna[i,17]="nulas ventas en NA o sin informacion"  }
}

#se crea una columna extra para ventas de Europa
sinna["Ventas EU"] <- NA

for (i in 1:6947){
  if(sinna[i,7]==Muchisimas2){
    sinna[i,18]="muchisimas Ventas en EU"  }
  else if (sinna[i,7]>Bastante2){
    sinna[i,18]="muchas Ventas en EU"  }
  else if (sinna[i,7]>Intermedio2){
    sinna[i,18]="Bastantes Ventas en EU "  }
  else if (sinna[i,7]>Pocas2){
    sinna[i,18]=" Ventas en EU intermedias"  }
  else if (sinna[i,7]>Muypocas2){
    sinna[i,18]="pocas Ventas en EU"  }
  else if (sinna[i,7]>ventasnulas_o_sin_informacion){
    sinna[i,18]="muy pocas Ventas en EU"  }
  else if (sinna[i,7]==ventasnulas_o_sin_informacion){
    sinna[i,18]="Ventas en EU nulas o sin informacion"  }
}

#se crea una columna extra para ventas de Japon
sinna["Ventas JP"] <- NA

for (i in 1:6947){
  if (sinna[i,8]>Bastante3){
    sinna[i,19]="muchas Ventas en JP"  }
  else if (sinna[i,8]>Intermedio3){
    sinna[i,19]="Bastantes Ventas en JP "  }
  else if (sinna[i,8]>Pocas3){
    sinna[i,19]="Ventas en JP intermedias"  }
  else if (sinna[i,8]>Muypocas3){
    sinna[i,19]="pocas Ventas en JP"  }
  else if (sinna[i,8]>ventasnulas_o_sin_informacion){
    sinna[i,19]="muy pocas Ventas en JP"  }
  else if (sinna[i,8]==ventasnulas_o_sin_informacion){
    sinna[i,19]="nulas Ventas en JP o sin informacion"  }
}

#se crea una columna extra para "otras" ventas
sinna["Ventas otras"] <- NA

for (i in 1:6947){
  if (sinna[i,9]>Bastante4){
    sinna[i,20]="muchas Ventas otras"  }
  else if (sinna[i,9]>Intermedio4){
    sinna[i,20]="Bastantes Ventas otras "  }
  else if (sinna[i,9]>Pocas4){
    sinna[i,20]="Ventas otras intermedias"  }
  else if (sinna[i,9]>Muypocas4){
    sinna[i,20]="pocas Ventas otras"  }
  else if (sinna[i,9]>ventasnulas_o_sin_informacion){
    sinna[i,20]="muy pocas Ventas otras"  }
  else if (sinna[i,9]==ventasnulas_o_sin_informacion){
    sinna[i,20]="Ventas otras nulas o sin informacion"  }
}

#se crea una columna extra para "otras" ventas
sinna["Ventas globales"] <- NA

for (i in 1:6947){
  if (sinna[i,10]>Bastante5){
    sinna[i,21]="muchas Ventas globales"  }
  else if (sinna[i,10]>Intermedio5){
    sinna[i,21]="Bastantes Ventas globales"  }
  else if (sinna[i,10]>Pocas5){
    sinna[i,21]="Ventas globales intermedias"  }
  else if (sinna[i,10]>Muypocas5){
    sinna[i,21]="pocas Ventas globales"  }
  else if (sinna[i,10]>ventasnulas_o_sin_informacion){
    sinna[i,21]="muy pocas Ventas globales"  }
  else if (sinna[i,10]==ventasnulas_o_sin_informacion){
    sinna[i,21]="Ventas globales nulas o sin informacion"  }
}

# se crean los intervalos para las etiquetas de los puntajes en las cr?ticas de cr?ticos y usuarios y
#el n?mero de cr?ticas de cada uno de estos

#Puntajes cr?ticos
Muchas6 = 100
Bastante6 = 80
Intermedio6 = 60
Pocas6 = 40
Muypocas6 = 20
ventasnulas_o_sin_informacion4 = 0

#Puntajes usuarios
Muchas7 = 10
Bastante7 = 8
Intermedio7 = 6
Pocas7 = 4
Muypocas7 = 2
ventasnulas_o_sin_informacion7 = 0

#N? cr?ticas cr?ticos
Muchas8 = 22.6+22.6+22.6+22.6+22.6
Bastante8 = 22.6+22.6+22.6+22.6
Intermedio8 = 22.6+22.6+22.6
Pocas8 = 22.6+22.6
Muypocas8 = 22.6
ventasnulas_o_sin_informacion8 = 0

#N? cr?ticas usuarios
Muchas9 = 2133+2133+2133+2133+2133
Bastante9 = 2133+2133+2133+2133
Intermedio9 = 2133+2133+2133
Pocas9 = 2133+2133
Muypocas9 = 2133
ventasnulas_o_sin_informacion8 = 0

#se asignan las etiquetas
#se crea una columna extra para puntajes de cr?ticos

sinna["Puntajes Cr?ticos"] <- NA

for (i in 1:6947){
  if (sinna[i,11]>Bastante6){
    sinna[i,22]="Puntajes Cr?ticos muy alto"  }
  else if (sinna[i,11]>Intermedio6){
    sinna[i,22]="Puntajes Cr?ticos alto"  }
  else if (sinna[i,11]>Pocas6){
    sinna[i,22]="Puntajes Cr?ticos intermedio"  }
  else if (sinna[i,11]>Muypocas6){
    sinna[i,22]="Puntajes Cr?ticos bajo"  }
  else if (sinna[i,11]>ventasnulas_o_sin_informacion){
    sinna[i,22]="Puntajes Cr?ticos muy bajo"  }
  else if (sinna[i,11]==ventasnulas_o_sin_informacion){
    sinna[i,22]="Puntajes Cr?ticos nulas o sin informacion"  }
}

#se crea una columna extra para cantidad de cr?ticas de cr?ticos
sinna["n? criticas criticos"] <- NA

for (i in 1:6947){
  if (sinna[i,12]>Bastante8){
    sinna[i,23]="n? criticas criticos muy alto"  }
  else if (sinna[i,12]>Intermedio8){
    sinna[i,23]="n? criticas criticos alto"  }
  else if (sinna[i,12]>Pocas8){
    sinna[i,23]="n? criticas criticos intermedio"  }
  else if (sinna[i,12]>Muypocas8){
    sinna[i,23]="n? criticas criticos bajo"  }
  else if (sinna[i,12]>ventasnulas_o_sin_informacion){
    sinna[i,23]="n? criticas criticos muy bajo"  }
  else if (sinna[i,12]==ventasnulas_o_sin_informacion){
    sinna[i,23]="n? criticas criticos nulas o sin informacion"  }
}

#se crea una columna extra para puntajes de usuarios
sinna["Puntajes usuarios"] <- NA

for (i in 1:6947){
  if (sinna[i,13]>Bastante7){
    sinna[i,24]="Puntajes usuarios muy alto"  }
  else if (sinna[i,13]>Intermedio7){
    sinna[i,24]="Puntajes usuarios alto"  }
  else if (sinna[i,13]>Pocas7){
    sinna[i,24]="Puntajes usuarios intermedio"  }
  else if (sinna[i,13]>Muypocas7){
    sinna[i,24]="Puntajes usuarios bajo"  }
  else if (sinna[i,13]>ventasnulas_o_sin_informacion){
    sinna[i,24]="Puntajes usuarios muy bajo"  }
  else if (sinna[i,13]==ventasnulas_o_sin_informacion){
    sinna[i,24]="Puntajes usuarios nulas o sin informacion"  }
}

#se crea una columna extra para n?mero de cr?ticas de usuarios
sinna["n? cr?ticas usuarios"] <- NA

for (i in 1:6947){
  if (sinna[i,14]>Bastante9){
    sinna[i,25]="n? cr?ticas usuarios muy alto"  }
  else if (sinna[i,14]>Intermedio9){
    sinna[i,25]="n? cr?ticas usuarios alto"  }
  else if (sinna[i,14]>Pocas9){
    sinna[i,25]="n? cr?ticas usuarios intermedio"  }
  else if (sinna[i,14]>Muypocas9){
    sinna[i,25]="n? cr?ticas usuarios bajo"  }
  else if (sinna[i,14]>ventasnulas_o_sin_informacion){
    sinna[i,25]="n? cr?ticas usuarios muy bajo"  }
  else if (sinna[i,14]==ventasnulas_o_sin_informacion){
    sinna[i,25]="n? cr?ticas usuarios nulas o sin informacion"  }
}

#habiendo asignado todas las etiquetas, se eliminan las columnas num?ricas que
#contaminar?an la regla de asociaci?n
sinna["NA_Sales"] <- NULL
sinna["EU_Sales"] <- NULL
sinna["JP_Sales"] <- NULL
sinna["Other_Sales"] <- NULL
sinna["Global_Sales"] <- NULL
sinna["Critic_Score"] <- NULL
sinna["Critic_Count"] <- NULL
sinna["User_Score"] <- NULL
sinna["User_Count"] <- NULL

#se crea un dataset con todos los atributos nuevos con etiquetas
write.csv(sinna, file = "../data/conventas.csv")

#se crea otro dataset pero sin atributos que no aportan a las reglas de asociaci?n
#se considera que no aportan pues las reglas encontradas asocian esas las etiquetas de esos atributos
#sin aportar informaci?n porque o bien la asociaci?n es obvia (por ejemplo: muchas ventas en las distintas regiones del mundo
#implican muchas ventas a nivel global)
#el nuevo dataset tiene solo las ventas a nivel global, borrando las a nivel de regiones
# tambi?n se elimina el rating por las mismas razones
sinna["Rating"] <- NULL
sinna["Ventas JP"] <- NULL
sinna["Ventas NA"] <- NULL
sinna["Ventas EU"] <- NULL
sinna["Ventas otras"] <- NULL
write.csv(sinna, file = "../data/soloventasglobales.csv")

#se crea un dataset eliminando el atributo de ventas globales, dejando uno sin ventas
sinna["Ventas globales"] <- NULL

write.csv(sinna, file = "../data/sinventas.csv")

#se eliminan las columnas relacionadas con el g?nero, la plataforma y el a?o de lanzamiento del videojuego
sinna["Genre"] <- NULL
sinna["Platform"] <- NULL
sinna["Year_of_Release"] <- NULL

write.csv(sinna, file = "../data/singeneroplataformaano.csv")

# se eliminan el n?mero de cr?ticas
sinna["n? criticas criticos"] <- NULL
sinna["n? cr?ticas usuarios"] <- NULL
write.csv(sinna, file = "../data/sinnumerodecriticas.csv")
sinna2 <- sinna

#Luego, se prueba qu? pasa si se deja solamente el atributo del desarrollador o del publicador
sinna["Developer"] <- NULL
write.csv(sinna, file = "../data/sindesarrollador.csv")

sinna2["Publisher"] <- NULL
write.csv(sinna, file = "../data/sinpublicador.csv")

Después de generar estos archivos .csv se aplican las reglas de asociación:

library(arules) #luego de importar la librer?a necesaria para el m?todo de las reglas de asociaci?n #se leen los archivos .csv creados como transacciones conventas <- read.transactions("../data/conventas.csv", sep=",") soloventasglobales<- read.transactions("../data/soloventasglobales.csv", sep=",") sinventas <- read.transactions("../data/sinventas.csv", sep=",") singeneroplataformaano <- read.transactions("../data/singeneroplataformaano.csv", sep=",") sinnumerodecriticas <- read.transactions("../data/sinnumerodecriticas.csv", sep=",") sindesarrollador <- read.transactions("../data/sindesarrollador.csv", sep=",") sinpublicador <- read.transactions("../data/sinpublicador.csv", sep=",") #se aplica la metodolog?a de las reglas de asociaci?n para cada uno de las transacciones rules <- apriori(conventas, parameter=list(support=0.01, confidence=0.5)) rules.sorted <- sort(rules, by="lift") rules.sorted.first10 <- head(rules.sorted, 10) inspect(rules.sorted.first10) rules2 <- apriori(soloventasglobales, parameter=list(support=0.01, confidence=0.5)) rules2.sorted <- sort(rules2, by="lift") rules2.sorted.first10 <- head(rules2.sorted, 10) inspect(rules2.sorted.first10) rules3 <- apriori(sinventas, parameter=list(support=0.01, confidence=0.5)) rules3.sorted <- sort(rules3, by="lift") rules3.sorted.first10 <- head(rules3.sorted, 10) inspect(rules3.sorted.first10) rules4 <- apriori(singeneroplataformaano, parameter=list(support=0.01, confidence=0.5)) rules4.sorted <- sort(rules4, by="lift") rules4.sorted.first10 <- head(rules4.sorted, 10) inspect(rules4.sorted.first10) rules5 <- apriori(sinnumerodecriticas, parameter=list(support=0.01, confidence=0.5)) rules5.sorted <- sort(rules5, by="lift") rules5.sorted.first10 <- head(rules5.sorted, 30) inspect(rules5.sorted.first10) rules6 <- apriori(sinpublicador, parameter=list(support=0.01, confidence=0.5)) rules6.sorted <- sort(rules6, by="lift") rules6.sorted.first10 <- head(rules6.sorted, 10) inspect(rules6.sorted.first10) rules7 <- apriori(sindesarrollador, parameter=list(support=0.01, confidence=0.5)) rules7.sorted <- sort(rules7, by="lift") rules7.sorted.first10 <- head(rules7.sorted, 10) inspect(rules7.sorted.first10) #dandose cuenta de que las etiquetas de los puntajes incurren en un error, dado a que #lass cr?ticas pueden ser muy altas por parte de los cr?ticos y muy altas por parte de los usuarios #pero de todas formas puede haber una diferencia de puntajes de 2 puntos lo que clasificar?a como una decepci?n #Se retoma entonces el atributo Is.fiasco que tiene valor "true" si hay una diferencia de puntaje de m?s #de dos entre el promedio de puntajes de cr?ticos y usuarios # Se toma el dataset que cuenta con el atributo "is.fiasco" data_is_fiasco <- read_csv("../data/data_para_clasificadores.csv") #se borran los atributos que no aportaron a la asociaci?n anterior data_is_fiasco["Genre"] <- NULL data_is_fiasco["Platform"] <- NULL data_is_fiasco["Global_Sales"] <- NULL data_is_fiasco["Rating"] <- NULL data_is_fiasco["Critic_Score"] <- NULL #se crea el dataset para poder ser leido como transacci?n write.csv(data_is_fiasco, file = "../data/data_para_reglas_de_asociacion.csv") #se lee el dataset data_reglas_asociacion <- read.transactions("../data/data_para_reglas_de_asociacion.csv", sep=",") #se aplica el m?todo de reglas de asociaci?n al dataset que cuenta con #el atributo is.fiasco rules9 <- apriori(data_reglas_asociacion, parameter=list(support=0.001, confidence=0.2)) rules9.sorted <- sort(rules9, by="lift") rules9.sorted.first10 <- head(rules9.sorted, 30) inspect(rules9.sorted.first10)

Con esto se obtienen los siguientes resultados:

Usando todos los datos se obtienen resultados que no aportan mucha información, como por ejemplo:

  • {Muchas ventas Regionales} => {Muchas ventas globales}
  • {Konami, muy pocas ventas en EU} => {Konami Digital Entertainment}

Considerando solo las ventas globales y no las regionales suceden cosas parecidas:

  • {muy pocas Ventas globales, Ubisoft Montreal} => {Ubisoft}

Sin considerar las ventas tampoco se obtienen resultados muy significativos:

  • {EA Canada, Puntajes usuarios alto} => {Electronic Arts}

Sin el género, la plataforma ni el año:

  • {Konami, n° críticas usuarios muy bajo} => {Konami Digital Entertainment}

Sin el número de críticas se obtienen resultados un poco más interesantes, como por ejemplo:

  • {Nintendo, Puntajes Críticos muy alto} => {Puntajes usuarios muy alto}

Lo cual a primera vista podría indicar que los juegos de Nintendo tienden a no ser un fiasco, sin embargo, esto es un error pues ambos puntajes podrían ser muy altos según los criterios definidos y aún así tener una diferencia de 2 puntos.

Al eliminar el desarrollador sucede algo parecido al caso anterior, aunque en el caso de Electronic Arts se observa que puntaje de usuarios alto suele estar relacionado con puntaje de críticos aún más alto:

  • {Electronic Arts, Puntajes usuarios alto} => {Puntajes Críticos muy alto}
  • {Nintendo, Puntajes Críticos muy alto} => {Puntajes usuarios muy alto}

Con el desarrollador pero sin publicador no se observan cambios significativos.

Al considerar el atributo Is_Fiasco se obtienen los resultados más interesantes, en los cuales se sugiere que el publicador puede estar muy relacionado con la clasificación del videojuego:

Esto último ayuda a validar las hipótesis iniciales, de que ciertos publicadores están más ligados a que un juego sea o no un fiasco, y consideramos que es valiosa información extraída del dataset.

Palabras Finales


Estrategias

Clasificadores

Después de bastantes pruebas para encontrar un buen predictor de fiascos de videojuegos, nos encontramos con Random Forest, que tiene un puntaje f1 que sobresale por sobre el de base dummy, en especial aplicando oversampling y subsampling. Consideramos que la diferencia de puntaje obtenida lo vuelve un predictor adecuado con los parámetros manejados.

Lo aprendido durante este curso de clasificadores es un acercamiento práctico que abre las puertas a aplicar estas herramientas en próximos proyectos, y a seguir aprendiendo para refinar los resultados que se pueden obtener con más técnicas y estrategias.

Clustering

El clustering aplicado en este proyecto no nos brindó mucha información nueva, pues consideramos que los datos utilizados no se adecuaban a un modelo en el que se pudieran identificar clusters naturalmente. A pesar de esto, nos sirvió para validar los datos utilizados, y para extraer información sobre su comportamiento.

Como trabajo no desarrollado, se podrían haber evaluado más métricas internas sobre clustering para validar o descartar su uso en nuestros datos. También se podrían haber buscado nuevas dimensiones sobre las cuales aplicar clustering que podrían haber aportado información.

Reglas de asociación

De estas estrategia, si bien fue realizada como último intento de extraer datos, destacamos que tomando los datos correctos nos puede proporcionar valiosa información. La forma intuitiva de interpretar las reglas de asociación nos fue útil para validar nuestra hipótesis de la relación entre los publicadores de videojuegos y los fracasos.

Cierre del curso

Las herramientas que se trabajaron en el curso nos permitieron llevar a cabo un proyecto en el que logramos extraer una gran cantidad de información del data set trabajado. La evolución natural de este proyecto llevó a probar distintas formas de analizar los datos que se reflejaron en resultados diversos: por un lado un predictor relativamente eficaz que podría utilizarse a futuro, y por otro lado una caracterización de la información desde ángulos que no se manejaban anteriormente.