¿Cuál es una manera de trazar los límites del árbol de decisión con R?

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)


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)

More Interesting

¿Cuál es la diferencia entre trabajar en Walmart y Walmart Labs desde la perspectiva de un científico de datos?

¿Cómo es el papel de un científico de datos en Facebook?

¿Cuáles son algunas diferencias entre un nuevo científico de datos y uno con 5 años de experiencia en la industria? ¿Cuáles son las cosas más importantes que habrán aprendido en ese momento?

Habiendo obtenido una certificación de especialización en ciencia de datos en Coursera y sin experiencia relevante en la industria, ¿cómo comienzo mi carrera como científico de datos?

¿Qué opinas sobre las nuevas rutas de aprendizaje de Coursera en aprendizaje automático y ciencia de datos y qué tan efectivo sería?

Cómo usar de manera óptima los próximos seis meses si tengo que ser mejor en Data Science

¿Cuál es una mejor opción para un científico de datos con experiencia: PR Card para Canadá o H-1B para los EE. UU.?

Siempre que busco solicitar un trabajo de científico de datos, todo lo que veo es una solicitud de un mínimo de 2 años de experiencia. ¿Qué pasa con los DS sin experiencia?

Cómo comenzar a aprender a convertirse en un científico de datos en deportes

¿Cuáles son los dominios más populares para las carreras de ciencia de datos?

¿Qué tipo de trabajo / función puede hacer un científico de datos después de su maestría?

La ciencia de datos como profesión ha sido bastante popular durante los últimos 3-5 años. ¿Cuándo alcanzaremos el equilibrio de oferta y demanda para los científicos de datos?

Cómo conseguir un trabajo de ciencia de datos en Python en India

¿Es posible hacer un cambio de carrera a analista de datos?

¿Qué piensan los científicos de datos sobre el uso de la raíz de C ++, en comparación con R o Python? Me preocupa el rendimiento y la flexibilidad.