Kmeans centers in a group_by and summarize pipe

Viewed 92

Trying to find the coordinates of K-means cluster centers on groups in a large data frame. One option is to brute-force loop through. But it'd be nice to figure out some tidy way of making it work. This previous Q&A wasn't clear. Any ideas? Here are some things I've tried, and a MCVE I made via reprex. Many thanks,

library(magrittr)
library(dplyr)
labels <- c(rep(1,4),rep(2,4))
x <- sample.int(100, length(labels))
y <- sample.int(100, length(labels))

df <- as.data.frame(list(labels=labels,x=x,y=y))
df
#>   labels  x  y
#> 1      1 75 32
#> 2      1 71 10
#> 3      1 41 68
#> 4      1 38 69
#> 5      2 99 95
#> 6      2 15 56
#> 7      2 73 96
#> 8      2 67 92

# Error Idea 1
df %>% group_by(labels) %>% summarize(center=kmeans(c(x,y), centers=2))
#> Error: Problem with `summarise()` input `center`.
#> x Input `center` must be a vector, not a `kmeans` object.
#> i Input `center` is `kmeans(c(x, y), centers = 2)`.
#> i The error occurred in group 1: labels = 1.

# Error Idea 2
df %>% group_by(labels) %>% summarize(x=list(x), y=list(y)) %>% select(x,y) %>% lapply(kmeans, centers=2)
#> Error in storage.mode(x) <- "double": (list) object cannot be coerced to type 'double'

# Brute force loop - works but cumbersome
ulabs <- unique(df$labels)
ctr <- vector("list", length(ulabs))
for (i in 1:length(ulabs)){
    tmp <- df[df$labels==ulabs[i],]
    ctr[[i]] <- (kmeans(tmp[, c('x','y')], centers=2))$centers
    
}

ctr
#> [[1]]
#>      x  y
#> 1 20.5 42
#> 2 78.0 36
#> 
#> [[2]]
#>          x        y
#> 1  9.00000 20.00000
#> 2 65.33333 34.33333

1 Answers

Here is a slight modification of your Idea 1, with the help from the purrr package. The results are in the Center column as a list.

library(magrittr)
library(dplyr)
library(purrr)

set.seed(1)

labels <- c(rep(1,4),rep(2,4))
x <- sample.int(100, length(labels))
y <- sample.int(100, length(labels))

df <- as.data.frame(list(labels=labels,x=x,y=y))

df2 <- df %>% 
  group_by(labels) %>%
  nest() %>%
  summarize(Kmeans = map(data, ~kmeans(.x[, c("x", "y")], 
                                centers = 2))) %>%
  mutate(Center = map(Kmeans, "centers"))

df2$Center
# [[1]]
#      x  y
# 1 53.5 55
# 2 17.5 91
# 
# [[2]]
#      x  y
# 1 28.5 64
# 2 84.5 14
Related