Tenía curiosidad sobre esto también. No pude encontrar una respuesta obvia, así que hice un intento. Por ejemplo, tomo prestado la respuesta ilustrativa de William Chen a ¿Cuáles son las desventajas de usar un árbol de decisión para la clasificación? [1] Estoy bastante seguro de que hay mejores maneras de hacer partes de esto (incluso puede haber un paquete, aunque no encontré uno), pero a continuación se describe una posible solución. Dado que los límites del árbol de decisión son paralelos a los ejes, vi los límites como una colección de rectángulos no superpuestos. Para los modelos basados en kernel como SVM, puede haber algo fuera de la caja para hacer esto para dimensiones más bajas [2].
Para comenzar, reutilicemos su código para construir un árbol de decisión con iris; luego podemos usar los datos perturbados como una comprobación de la cordura de que nuestra asignación de límites funciona.
library(rpart)
library(rpart.plot)
model1 <- rpart(Species ~ Sepal.Length + Sepal.Width, iris)
prp(model1, digits = 3)
- ¿La analítica de datos es un campo profesional interesante?
- Estoy en datawarehouse y perfil ETL. Quiero convertirme en científico de datos. ¿Cómo puedo cambiar el perfil?
- ¿Cómo te prepararías para el rol de científico de datos (Analytics) en Facebook?
- ¿Qué tipo de análisis hacen los científicos de datos?
- ¿Un INFJ disfrutaría de una carrera como científico de datos? ¿Por qué?
También recrearemos el modelo perturbado
set.seed(1)
tmp <- function() rbinom(nrow(iris), size = 1, prob = 0.5)
perturb <- function() (tmp() - tmp()) / 10
iris$Sepal.Length.Perturbed <- iris$Sepal.Length + perturb()
iris$Sepal.Width.Perturbed <- iris$Sepal.Width + perturb()
model2 <- rpart(Species ~ Sepal.Length.Perturbed +
Sepal.Width.Perturbed, iris)
prp(model2, digits = 3)
Ahora que sabemos cómo se ve el árbol, podemos graficarlo. Para empezar, veremos los puntos de división para cada eje. En este punto, quería ver los puntos donde se cruzaban las líneas para recoger las coordenadas de los rectángulos. También seguí adelante y agregué coordenadas a las esquinas, y construí los puntos de borde del gráfico. Esta parte del código se parece a:
# use the split data to build x, y lines to define boundaries
splits <- function(model) { cbind(data.frame("variable"=row.names(model$split)), data.frame(model$split)) }
verticals <- function(split, varname) { unique(sort(split[which(split$variable==varname),]$index)) }
horizontals <- function(split, varname) { unique(sort(split[which(split$variable==varname),]$index, decreasing=TRUE)) }
esquinas <- rbind (c (x = -Inf, y = -Inf),
c (x = -Inf, y = Inf),
c (x = Inf, y = -Inf),
c (x = Inf, y = Inf))
boundary_points <- función (vertical, horizontal, esquinas) {
x <- rbind (expand.grid (x = vertical, y = horizontal),
esquinas,
expand.grid (x = vertical, y = esquinas [, 1]),
expand.grid (x = esquinas [, 2], y = horizontal))
return (único (x))
}
split1 <- divisiones (modelo1)
vertical1 <- verticales (split1, "Sepal.Length")
horizontal1 <- horizontales (split1, "Ancho de sepal")
bound_id1 <- boundary_points (vertical1, horizontal1, esquinas)
Al trazar nuestro conjunto de datos y agregar las líneas de límite, podemos tener una idea aproximada de nuestros límites (muy generales).
p1 <- ggplot(iris1, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(aes(colour=Species))
p1_bound_lines <- p1 +
do.call (“geom_vline”, list (x = vertical1)) +
do.call (“geom_hline”, list (y = horizontal1)) +
geom_point (data = bound_id1, aes (x = x, y = y), shape = 1, size = 4) +
ggtitle (“Modelo 1: Líneas de árbol de decisión”) +
theme (plot.title = element_text (face = “bold”))
ggsave (“./ assets / model1_line_boundaries.png”, plot = p1_bound_lines, dpi = 300)
Bien. Bueno. Tenemos más rectángulos que divisiones de límites. Esto podría ser un problema si hay una cantidad loca de divisiones. Solo tenemos unos 30 aquí, así que probablemente estemos bien.
Como probablemente queremos usar algún tipo de parámetro alfa para la superposición, queremos limitar nuestra superposición a cero para que nuestro sombreado sea uniforme. Para hacer esto, creé una pequeña función que encuentra los rectángulos donde busca el punto más cercano a lo largo de cada eje. Comienzo en la esquina superior izquierda, me muevo a la derecha a lo largo del eje x, luego hacia abajo en el eje y, eliminando cada “punto de partida” en sucesión.
# using boundary points, identify the non-overlapping bounding rectangles
find_rects <- function(points) {
output_frame <- data.frame(xmin=numeric(0), xmax=numeric(0), ymin=numeric(0), ymax=numeric(0))
tmp_data <- points[order(-points$y, points$x),] # order so we can start at the top left and pop the point off as needed
rownames(tmp_data) <- NULL
sub_find_rects <- función (tmp = tmp_data, out_frame = output_frame) {
if (nrow (tmp) <= 1) {
xmin <- -Inf
xmax <- tmp $ x [1]
ymin <- tmp $ y [1]
ymax <- Inf
to_bind <- data.frame (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax)
out_frame <- rbind (out_frame, to_bind)
return (out_frame)
} else if ((tmp $ y [1] == -Inf & nrow (tmp> 1)) |
tmp $ x [1] == Inf & nrow (tmp> 1)) {# omita la fila inferior y la fila derecha; no pueden ser esquinas superiores izquierdas
tmp <- tmp [2: nrow (tmp),]
return (sub_find_rects (tmp, out_frame))
} más {
xmin <- tmp $ x [1]
ymax <- tmp $ y [1]
ymin <- max (tmp [tmp $ x == tmp $ x [1] & tmp $ y! = tmp $ y [1],] $ y)
xmax tmp $ x [1],] $ x)
to_bind <- data.frame (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax)
out_frame <- rbind (out_frame, to_bind)
tmp <- tmp [2: nrow (tmp),]
return (sub_find_rects (tmp = tmp, out_frame = out_frame))
}
}
salida <- sub_find_rects (tmp_data)
return (salida [1: nrow (salida) -1,]) # omitir la última fila (esquina inferior derecha)
}
Definiremos nuestras áreas delimitadas utilizando los rectángulos.
bounded_areas1 <- data.frame("id"=seq((length(vertical1) + 1) * (length(horizontal1) + 1)))
bounded_areas1 <- cbind(bounded_areas1, find_rects(bound_id1))
Ahora queremos agregar color. Ya tenemos un modelo, así que reutilicémoslo. Según nuestros puntos de límite para cada rectángulo, simplemente tomaremos el punto central de cada uno.
find_center <- function(x1, x2, y1, y2) {
x_center <- (x1 + x2) / 2
y_center <- (y1 + y2) / 2
return(c(x_center, y_center))
}
est1 <- t (mapply (find_center, bounded_areas1 $ xmin, bounded_areas1 $ xmax, bounded_areas1 $ ymin, bounded_areas1 $ ymax))
est1 <- rename (data.frame (est1), c ("X1" = "Sepal.Length", "X2" = "Sepal.Width"))
est1 $ classify <- predic (model1, est1, type = "class")
est1 <- cbind (est1, bounded_areas1)
Ahora podemos trazar.
p1 +
geom_rect(data=est1, aes(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, fill=classify), alpha=0.2) +
ggtitle("Model 1: Decision Tree Boundaries") +
theme(plot.title=element_text(face="bold"))
Increíble. Probemos los datos perturbados ahora
p2 <- ggplot(iris2, aes(x=Sepal.Length.Perturb, y=Sepal.Width.Perturb)) +
geom_point(aes(colour=Species))
split2 <- divisiones (modelo2)
vertical2 <- verticales (split2, "Sepal.Length.Perturb")
horizontal2 <- horizontales (split2, "Sepal.Width.Perturb")
bound_id2 <- boundary_points (vertical2, horizontal2, esquinas)
bounded_areas2 <- data.frame ("id" = seq ((length (vertical2) + 1) * (length (horizontal2) + 1)))
bounded_areas2 <- cbind (bounded_areas2, find_rects (bound_id2))
est2 <- t (mapply (find_center, bounded_areas2 $ xmin, bounded_areas2 $ xmax, bounded_areas2 $ ymin, bounded_areas2 $ ymax))
est2 <- rename (data.frame (est2), c ("X1" = "Sepal.Length.Perturb", "X2" = "Sepal.Width.Perturb"))
est2 $ classify <- predic (model2, est2, type = "class")
est2 <- cbind (est2, bounded_areas2)
p2 +
geom_rect (datos = est2, aes (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = clasificar), alfa = 0.2) +
ggtitle (“Modelo 2: Límites del árbol de decisión”) +
theme (plot.title = element_text (face = “bold”))
Ahora tenemos una implementación ingenua para construir límites de árbol de decisión con ggplot. El código fuente completo se pega a continuación.
[1] http://www.quora.com/What-are-the-disadvantages-of-using-a-decision-tree-for-classification
[2] Ver , por ejemplo , ¿Cómo trazo un gráfico de clasificación de una SVM en R?
setwd("~/Dropbox/Quora/1")
datos (iris)
biblioteca (ggplot2)
biblioteca (rpart)
biblioteca (rpart.plot)
model1 <- rpart (Species ~ Sepal.Length + Sepal.Width, iris)
prp (modelo1, dígitos = 3)
png (“./ assets / iris_decision_tree.png”, ancho = 2000, altura = 1600, unidades = “px”, res = 300)
prp (modelo1, dígitos = 3)
dev.off ()
# perturbar los datos
set.seed (1)
tmp <- function () {rbinom (nrow (iris), size = 1, prob = 0.5)}
perturbar <- función () {(tmp () – tmp ()) / 10}
iris $ Sepal.Length.Perturb <- iris $ Sepal.Length + perturb ()
iris $ Sepal.Width.Perturb <- iris $ Sepal.Width + perturb ()
model2 <- rpart (Species ~ Sepal.Length.Perturb + Sepal.Width.Perturb, iris)
prp (modelo2, dígitos = 3)
png (“./ assets / iris_decision_tree_perturbed.png”, ancho = 2000, altura = 1600, unidades = “px”, res = 300)
prp (modelo2, dígitos = 3)
dev.off ()
iris1 <- iris [c ("Sepal.Length", "Sepal.Width", "Species")]
iris2 <- iris [c ("Sepal.Length.Perturb", "Sepal.Width.Perturb", "Species")]
p1 <- ggplot (iris1, aes (x = Sepal.Length, y = Sepal.Width)) +
geom_point (aes (color = Species))
p2 <- ggplot (iris2, aes (x = Sepal.Length.Perturb, y = Sepal.Width.Perturb)) +
geom_point (aes (color = Species))
# usa los datos divididos para construir líneas x, y para definir límites
divide <- function (model) {cbind (data.frame ("variable" = row.names (model $ split)), data.frame (model $ split))}
verticales <- función (split, varname) {unique (sort (split [which (split $ variable == varname),] $ index))}
horizontales <- función (split, varname) {unique (sort (split [which (split $ variable == varname),] $ index, decreing = TRUE))}
esquinas <- rbind (c (x = -Inf, y = -Inf),
c (x = -Inf, y = Inf),
c (x = Inf, y = -Inf),
c (x = Inf, y = Inf))
boundary_points <- función (vertical, horizontal, esquinas) {
x <- rbind (expand.grid (x = vertical, y = horizontal),
esquinas,
expand.grid (x = vertical, y = esquinas [, 1]),
expand.grid (x = esquinas [, 2], y = horizontal))
return (único (x))
}
split1 <- divisiones (modelo1)
vertical1 <- verticales (split1, "Sepal.Length")
horizontal1 <- horizontales (split1, "Ancho de sepal")
bound_id1 <- boundary_points (vertical1, horizontal1, esquinas)
p1_bound_lines <- p1 +
do.call (“geom_vline”, list (x = vertical1)) +
do.call (“geom_hline”, list (y = horizontal1)) +
geom_point (data = bound_id1, aes (x = x, y = y), shape = 1, size = 4) +
ggtitle (“Modelo 1: Líneas de árbol de decisión”) +
theme (plot.title = element_text (face = “bold”))
ggsave (“./ assets / model1_line_boundaries.png”, plot = p1_bound_lines, dpi = 300)
# utilizando puntos de límite, identifique los rectángulos delimitadores no superpuestos
find_rects <- función (puntos) {
output_frame <- data.frame (xmin = numeric (0), xmax = numeric (0), ymin = numeric (0), ymax = numeric (0))
tmp_data <- puntos [orden (-puntos $ y, puntos $ x),] # orden para que podamos comenzar en la parte superior izquierda y resaltar el punto según sea necesario
nombres de fila (tmp_data) <- NULL
tmp_data <- tmp_data
sub_find_rects <- función (tmp = tmp_data, out_frame = output_frame) {
if (nrow (tmp) <= 1) {
xmin <- -Inf
xmax <- tmp $ x [1]
ymin <- tmp $ y [1]
ymax <- Inf
to_bind <- data.frame (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax)
out_frame <- rbind (out_frame, to_bind)
return (out_frame)
} else if ((tmp $ y [1] == -Inf & nrow (tmp> 1)) |
tmp $ x [1] == Inf & nrow (tmp> 1)) {# omita la fila inferior y la fila derecha; no pueden ser esquinas superiores izquierdas
tmp <- tmp [2: nrow (tmp),]
return (sub_find_rects (tmp, out_frame))
} más {
xmin <- tmp $ x [1]
ymax <- tmp $ y [1]
ymin <- max (tmp [tmp $ x == tmp $ x [1] & tmp $ y! = tmp $ y [1],] $ y)
xmax tmp $ x [1],] $ x)
to_bind <- data.frame (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax)
out_frame <- rbind (out_frame, to_bind)
tmp <- tmp [2: nrow (tmp),]
return (sub_find_rects (tmp = tmp, out_frame = out_frame))
}
}
salida <- sub_find_rects (tmp_data)
return (salida [1: nrow (salida) -1,]) # omitir la última fila (esquina inferior derecha)
}
bounded_areas1 <- data.frame ("id" = seq ((length (vertical1) + 1) * (length (horizontal1) + 1)))
bounded_areas1 <- cbind (bounded_areas1, find_rects (bound_id1))
# ahora necesitamos colorearlos. Ya tenemos un modelo, así que reutilicémoslo.
# encuentra el punto central de todas las referencias. usando todos los puntos, comenzando con el
# arriba a la izquierda, encontremos los cuatro puntos y asócielos con el color
# modelado en el centro del rectángulo.
find_center <- función (x1, x2, y1, y2) {
x_center <- (x1 + x2) / 2
y_center <- (y1 + y2) / 2
return (c (x_center, y_center))
}
est1 <- t (mapply (find_center, bounded_areas1 $ xmin, bounded_areas1 $ xmax, bounded_areas1 $ ymin, bounded_areas1 $ ymax))
est1 <- rename (data.frame (est1), c ("X1" = "Sepal.Length", "X2" = "Sepal.Width"))
est1 $ classify <- predic (model1, est1, type = "class")
est1 <- cbind (est1, bounded_areas1)
p1 +
geom_rect (datos = est1, aes (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = clasificar), alfa = 0.2) +
ggtitle (“Modelo 1: Límites del árbol de decisión”) +
theme (plot.title = element_text (face = “bold”))
ggsave (“./ assets / model1_boundaries.png”, ppp = 300)
# increíble. Probemos los datos perturbados ahora
split2 <- divisiones (modelo2)
vertical2 <- verticales (split2, "Sepal.Length.Perturb")
horizontal2 <- horizontales (split2, "Sepal.Width.Perturb")
bound_id2 <- boundary_points (vertical2, horizontal2, esquinas)
bounded_areas2 <- data.frame ("id" = seq ((length (vertical2) + 1) * (length (horizontal2) + 1)))
bounded_areas2 <- cbind (bounded_areas2, find_rects (bound_id2))
est2 <- t (mapply (find_center, bounded_areas2 $ xmin, bounded_areas2 $ xmax, bounded_areas2 $ ymin, bounded_areas2 $ ymax))
est2 <- rename (data.frame (est2), c ("X1" = "Sepal.Length.Perturb", "X2" = "Sepal.Width.Perturb"))
est2 $ classify <- predic (model2, est2, type = "class")
est2 <- cbind (est2, bounded_areas2)
p2 +
geom_rect (datos = est2, aes (xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = clasificar), alfa = 0.2) +
ggtitle (“Modelo 2: Límites del árbol de decisión”) +
theme (plot.title = element_text (face = “bold”))
ggsave (“./ assets / model2_boundaries.png”, ppp = 300)