################################################### # ÁRBOLES PARA CLASIFICACIÓN Y REGRESIÓN ######## ################################################### #Dominio de ejemplo: día apropiado para jugar al tenis # Para construir a'rboles, tanto de clasificacio'n como de regresio'n, usaremos **C50** (C5.0 es la versio'n mejorada del algoritmo que vimos en clase, C4.5) # El dominio (datos) de ejemplo a usar sera' el de **tennis**, aunque con todas las variables discretas # Recordar instalar estos paquetes si no están instalados library(C50) # Para usar árboles library(mlbench) # Contiene conjuntos de datos library(class) # Para usar KNN library(rpart) # Para usar árboles de regresión library(FNN) # KNN para regresión library(Cubist) # Para usar árboles de modelos tennis <- read.csv("tennis.txt") head(tennis) ########################### # Entrenamiento del árbol ########################### # Quitamos la columna day tennis$day <- NULL # Suponemos que la última columna contiene la clase col_clase <- ncol(tennis) # Otra manera: col_clase <- which(names(tennis) == "play") # Nos aseguramos de que la clase es un factor tennis[ , col_clase] <- as.factor(tennis[ , col_clase]) trainX <- tennis[, -col_clase] trainy <- tennis[, col_clase] model <- C5.0(x=trainX, y=trainy) # Otra manera: # model <- C5.0(play ~ ., tennis) ########################### # Visualizando el a'rbol ########################### summary(model) # Tambie'n podemos construir reglas model_reglas <- C5.0(x=trainX, y=trainy, rules = TRUE) summary(model_reglas) ########################### # Haciendo predicciones ########################### # Vamos a utilizar otro conjunto con ma's datos (**Breast Cancer**) y a separarlos en **train** y **test** data(BreastCancer) # Quitamos el identificador (es un atributo irrelevante) BC <- BreastCancer BC$Id <- NULL # Suponemos que la clase es la última columna col_clase <- ncol(BC) # Vemos que algunos atributos tienen NAs summary(BC) # Otra manera de verlo más breve sapply(BC, function(x) sum(is.na(x))) # También es interesante ver la clase de los atributos sapply(BC, class) # Imputación: una posibilidad: quitar los datos con NAs # BC <- BC[complete.cases(BC),] # Imputación: otra posibilidad: imputar con la media. # En este caso, como sólo hay una columna con NAs, no usaremos lapply # Problema: Bare.nuclei es un factor, que sin embargo parece que pudiera ser un entero # convertiremos primero a entero # pero cuidado con la conversión de factores a números!! bare_nuclei <- as.integer(as.character(BC$Bare.nuclei)) BC$Bare.nuclei <- mean(bare_nuclei, na.rm=TRUE) sapply(BC, function(x) sum(is.na(x))) # Nos aseguramos de que la clase es un factor # (en ocasiones, el algoritmo de entrenamiento sabe que es un problema de clasificación si la variable # de respuesta es un factor. Si no, cree que es un problema de regresión.) BC[,col_clase] <- as.factor(BC[,col_clase]) ########################### # Haciendo predicciones ########################### # Separamos en train (2/3) y test (1/3) set.seed(0) # Para que los resultados se puedan replicar, particionamos los datos siempre igual indices_train <- sample(1:nrow(BC), nrow(BC)*2/3, replace=FALSE) trainX <- BC[indices_train, -col_clase] trainy <- BC[indices_train, col_clase] testX <- BC[-indices_train, -col_clase] testy <- BC[-indices_train, col_clase] #Ahora construimos el modelo, hacemos predicciones y calculamos el error sobre los conjuntos de train y test set.seed(0) # Para fijar la semilla aleatoria, en caso de que el algoritmo tenga elementos aleatorios model <- C5.0(x=trainX, y=trainy) predsTrain <- predict(model, trainX) predsTest <- predict(model, testX) # Aqui calculamos el error en clasificación (clase real distinto de clase predicha) errorTrain <- mean(predsTrain != trainy) errorTest <- mean(predsTest != testy) print(paste0("Error train: ", errorTrain, " Error Test: ", errorTest)) # Podemos plantearnos qué hubiera pasado si hubieramos convertido todos los atributos a números trainX_numeric <- as.data.frame(lapply(trainX, function(x) as.numeric(as.character(x)))) testX_numeric <- as.data.frame(lapply(testX, function(x) as.numeric(as.character(x)))) set.seed(0) # Para fijar la semilla aleatoria, en caso de que el algoritmo tenga elementos aleatorios model <- C5.0(x=trainX_numeric, y=trainy) summary(model) predsTrain <- predict(model, trainX_numeric) predsTest <- predict(model, testX_numeric) errorTrain <- mean(predsTrain != trainy) errorTest <- mean(predsTest != testy) print(paste0("Error train: ", errorTrain, " Error Test: ", errorTest)) ##################################### # Influencia de los hiperparámetros # ##################################### # Dos de los hiperpara'metros ma's importantes de C5 son: # * CF (= 0.25), probabilidad de que el nodo se divida. A mayor probabilidad, mayor el a'rbol. # * minCases (= 2), nu'mero mi'nimo de datos necesarios para seguir subdividiendo # Vamos a probar distintos valores de minCases y veremos que da a lugar a árboles con distintos errores en test. # Más adelante aprenderemos la manera correcta de ajustar los hiperparámetros. for(minCases in c(2,10, 50)){ set.seed(0) model <- C5.0(x=trainX, y=trainy, control = C5.0Control(minCases = minCases)) errorTest <- mean(predict(model, testX) != testy) print(paste0("Con minCases=", minCases, " error: ", errorTest)) } ########################### # ¿Que' tal funciona KNN? # ########################### # Vamos a probarlo directamente con distintos números de vecinos. for(k in c(1,3:5)){ set.seed(0) predTest <- knn(train=trainX, test=testX, cl=trainy, k=k) errorTest <- mean(predTest != testy) print(paste0("Con k=", k, " error: ", errorTest)) } ################################################# # Probemos árboles de regresión con rpart # ################################################# data(BostonHousing) # La variable a predecir es medv (la última) BH <- BostonHousing head(BH) # ¿Hay NAs? summary(BH) sapply(BH, function(x) sum(is.na(x))) # ¿Cuál es la clase de los atributos? sapply(BH, class) set.seed(0) # Para fijar la semilla aleatoria # Separamos en train (2/3) y test (1/3) indices_train <- sample(1:nrow(BH), nrow(BH)*2/3, replace=FALSE) # Nótese que para rpart, no tenemos que separar X (entradas) de y (salida) train <- BH[indices_train, ] test <- BH[-indices_train, ] model <- rpart(medv ~ ., data=train) print(model) plot(model) predsTrain <- predict(model, train) predsTest <- predict(model, test) # Ojo, aquí usamos MAE como error mae <- function(preds, truth) {mean(abs(preds - truth))} errorTrain <- mae(predsTrain, train$medv) errorTest <- mae(predsTest, test$medv) print(paste0(" Error Train: ", errorTrain, "Error Test: ", errorTest)) ############### # Ejercicios # ############### # Hacer BostonHousing con **knn** (regresio'n) para varios valores de k. El problema es que el knn de **class** no hace regresio'n, # por lo que será necesario instalar **FNN** y usar **knn.reg**. Comparar con los resultados que salieron en **rpart** # Hacer BostonHousing con **Cubist**, que son reglas de modelos para regresio'n. # Calcular el error en test y comparar con lo que salio' con **knn** y **part**. ####################################### # FNN: knn para regresión # ####################################### # Aquí si que tenemos que separar en X e y, pero como ya hemos separado en train y test, usaremos eso col_clase <- ncol(BH) trainX <- train[,-col_clase] trainy <- train[, col_clase] testX <- test[, -col_clase] testy <- test[, col_clase] trainX$chas <- as.integer(as.character(trainX$chas)) testX$chas <- as.integer(as.character(testX$chas)) for(k in 1:3){ set.seed(0) predTest <- knn.reg(train=trainX, test=testX, y=trainy, k=k) errorTest <- mae(predTest$pred, testy) print(paste0("Con k=", k, " error: ", errorTest)) } ####################################### # Cubist: árboles/reglas de modelos # ####################################### trainX <- train[,-col_clase] trainy <- train[, col_clase] testX <- test[, -col_clase] testy <- test[, col_clase] model <- cubist(x=trainX, y = trainy) summary(model) predsTest <- predict(model, testX) errorTest <- mean(abs(predsTest - testy)) print(paste0(" Error Test: ", errorTest))