Enfoque sencillo para asignar clústeres para nuevos datos después de la agrupación en clústeres de k-means


Estoy ejecutando k-means clustering en un marco de datos df1, y estoy buscando un enfoque simple para calcular el centro de clúster más cercano para cada observación en un nuevo marco de datos df2 (con los mismos nombres de variable). Piense en df1 como el conjunto de entrenamiento y df2 en el conjunto de pruebas; quiero agrupar en el conjunto de entrenamiento y asignar cada punto de prueba al clúster correcto.

Sé cómo hacer esto con la función apply y algunas funciones simples definidas por el usuario (publicaciones anteriores sobre el tema tienen por lo general propuso algo similar):

df1 <- data.frame(x=runif(100), y=runif(100))
df2 <- data.frame(x=runif(100), y=runif(100))
km <- kmeans(df1, centers=3)
closest.cluster <- function(x) {
  cluster.dist <- apply(km$centers, 1, function(y) sqrt(sum((x-y)^2)))
  return(which.min(cluster.dist)[1])
}
clusters2 <- apply(df2, 1, closest.cluster)

Sin embargo, estoy preparando este ejemplo de clústeres para un curso en el que los estudiantes no estarán familiarizados con la función apply, por lo que preferiría mucho si pudiera asignar los clústeres a df2 con una función incorporada. ¿Hay alguna función incorporada conveniente para encontrar el clúster más cercano?

 30
Author: josliber, 2013-12-17

2 answers

Podría usar el paquete flexclust , que tiene un método predict implementado para k-means:

library("flexclust")
data("Nclus")

set.seed(1)
dat <- as.data.frame(Nclus)
ind <- sample(nrow(dat), 50)

dat[["train"]] <- TRUE
dat[["train"]][ind] <- FALSE

cl1 = kcca(dat[dat[["train"]]==TRUE, 1:2], k=4, kccaFamily("kmeans"))
cl1    
#
# call:
# kcca(x = dat[dat[["train"]] == TRUE, 1:2], k = 4)
#
# cluster sizes:
#
#  1   2   3   4 
#130 181  98  91 

pred_train <- predict(cl1)
pred_test <- predict(cl1, newdata=dat[dat[["train"]]==FALSE, 1:2])

image(cl1)
points(dat[dat[["train"]]==TRUE, 1:2], col=pred_train, pch=19, cex=0.3)
points(dat[dat[["train"]]==FALSE, 1:2], col=pred_test, pch=22, bg="orange")

diagrama de flexclust

También hay métodos de conversión para convertir los resultados de funciones de clúster como stats::kmeans o cluster::pam a objetos de clase kcca y viceversa:

as.kcca(cl, data=x)
# kcca object of family ‘kmeans’ 
#
# call:
# as.kcca(object = cl, data = x)
#
# cluster sizes:
#
#  1  2 
#  50 50 
 33
Author: rcs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-16 22:04:34

Algo que noté sobre el enfoque en la pregunta y los enfoques flexclust es que son bastante lentos (benchmarked aquí para un conjunto de entrenamiento y pruebas con 1 millón de observaciones con 2 características cada uno).

El ajuste del modelo original es razonablemente rápido:

set.seed(144)
df1 <- data.frame(x=runif(1e6), y=runif(1e6))
df2 <- data.frame(x=runif(1e6), y=runif(1e6))
system.time(km <- kmeans(df1, centers=3))
#    user  system elapsed 
#   1.204   0.077   1.295 

La solución que publiqué en la pregunta es lenta en el cálculo de las asignaciones de clúster de conjunto de prueba, ya que llama por separado closest.cluster para cada punto de conjunto de prueba:

system.time(pred.test <- apply(df2, 1, closest.cluster))
#    user  system elapsed 
#  42.064   0.251  42.586 

Mientras tanto, el flexclust el paquete parece agregar una gran cantidad de sobrecarga independientemente de si convertimos el modelo ajustado con as.kcca o ajustamos uno nuevo nosotros mismos con kcca (aunque la predicción al final es mucho más rápida)

# APPROACH #1: Convert from the kmeans() output
system.time(km.flexclust <- as.kcca(km, data=df1))
#    user  system elapsed 
#  87.562   1.216  89.495 
system.time(pred.flexclust <- predict(km.flexclust, newdata=df2))
#    user  system elapsed 
#   0.182   0.065   0.250 

# Approach #2: Fit the k-means clustering model in the flexclust package
system.time(km.flexclust2 <- kcca(df1, k=3, kccaFamily("kmeans")))
#    user  system elapsed 
# 125.193   7.182 133.519 
system.time(pred.flexclust2 <- predict(km.flexclust2, newdata=df2))
#    user  system elapsed 
#   0.198   0.084   0.302 

Parece que hay otro enfoque sensato aquí: usar una solución rápida de k-vecinos más cercanos como un árbol k-d para encontrar el vecino más cercano de cada observación de conjunto de pruebas dentro del conjunto de centroides del clúster. Esto se puede escribir de forma compacta y es relativamente rápido:

library(FNN)
system.time(pred.knn <- get.knnx(km$center, df2, 1)$nn.index[,1])
#    user  system elapsed 
#   0.315   0.013   0.345 
all(pred.test == pred.knn)
# [1] TRUE
 10
Author: josliber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-25 14:43:15