循环遍历数据帧列表 return R 中具有固定质心的 k 均值簇矩阵

Looping through a list of dataframes to return a matrix of k-means clusters with fixed centroids in R

这是我的第二个 post,假设它早于第一个,我将在此处 link:

我不会重复我在那里犯的新手错误,所以在这里你可以复制一份数据:

 > dput(head(dfn,1))
structure(c(-0.936707666207839, 0.684585833497428, -1.15671769161442, 
-0.325882814790034, 0.334512025995239, 0.335054315282587, 0.0671142954097706, 
-0.544867778136127, -0.958378799317135, 1.26734044843021, -0.483611966400142, 
-0.0781514731365092, -0.671994127070641, 0.332218249471269, 0.942550991112822, 
0.15534532610427, 0.192944412985922, 0.206169118270958, 0.424191119850985, 
-0.193936625653784, -0.574273356856365, -0.176553706556564, 0.696013509222779, 
0.118827262744793, 0.0649996884597108, 0.470171960447926, -0.570575475596488, 
0.336490371668436, 0.475005575251838, 0.010357165551236, 0.284525279467858, 
0.523668394513643, -0.0290958105736766, 0.62018540798656, 1.37452329937098, 
0.456726128895017), .Dim = c(1L, 36L), .Dimnames = list(NULL, 
    c("2015-01-30", "2015-02-27", "2015-03-31", "2015-04-30", 
    "2015-05-29", "2015-06-30", "2015-07-31", "2015-08-31", "2015-09-30", 
    "2015-10-30", "2015-11-30", "2015-12-31", "2016-01-29", "2016-02-29", 
    "2016-03-31", "2016-04-29", "2016-05-31", "2016-06-30", "2016-07-29", 
    "2016-08-31", "2016-09-30", "2016-10-31", "2016-11-30", "2016-12-30", 
    "2017-01-31", "2017-02-28", "2017-03-31", "2017-04-28", "2017-05-31", 
    "2017-06-30", "2017-07-31", "2017-08-31", "2017-09-29", "2017-10-31", 
    "2017-11-30", "2017-12-29")))

这是一个时间序列数据库,包含 417 行,有 36 个时间范围(过去 3 年的每个月)。

这是我用来创建数据帧列表的代码:

ProgrSubset <- function(x,i) { x[,i:sum(i,11)] }
dfList <- lapply(1:25, function(x) ProgrSubset(dfn, x) )

dfList 是一个包含 25 个数据帧的列表,从原始数据帧中滚动 window 12 个月。

现在我想 运行 在列表的每个数据帧上使用 k-means 算法,并将每次迭代的聚类编号存储在名为 it_mat 的矩阵中。

但遗憾的是,我希望质心是之前 运行 的质心(如果它们从第一个 运行 开始就固定了,无论如何都会很棒)。

我没问题"by hand":

it_mat <- cbind(ref_data$sec_id)
k = 18
cl <- kmeans(dfList[[1]], centers = k, nstart = 10)
it_mat <- cbind(it_mat, cl$cluster)
head(it_mat) #first iteration

colnames(cl$centers) <- colnames(dfn[,2:13])
k <- cl$centers
cl <- kmeans(dfList[[2]], centers = k, nstart = 10)
it_mat <- cbind(it_mat, cl$cluster)
head(it_mat) #second iteration

然后通过数据库列表循环它应该很简单,但它没有显示:我设计的 for 循环只 return 一个只有第一次迭代的矩阵:

it_mat <- cbind(ref_data$sec_id)
for(i in 1:25){
    if(i == 1){
        k = 18
        cl <- kmeans(dfList[[i]], centers = k, nstart = 10)
        it_mat <- cbind(it_mat, cl$cluster)
    }else{
        colnames(cl$centers) <- colnames(dfn[,i:i+11])
        k = cl$centers
        cl <- kmeans(dfList[[i]], centers = k, nstart = 10)
        it_mat <- cbind(it_mat, cl$cluster)
    }
}

可能在错误后停止:Error: empty cluster: try a better set of initial centers ?

但我不关心簇是否为空。

我还尝试在第一次迭代之后只循环后续迭代,以使其在没有 ifelse:

的情况下更简单
for(i in 2:25){
    colnames(cl$centers) <- colnames(dfn[,2:13])
    k <- cl$centers
    cl <- kmeans(dfList[[i]], centers = k, nstart = 10)
    it_mat <- cbind(it_mat, cl$cluster)
}

仍然是相同的结果:只有第一次迭代的矩阵。

我也试过用it_mat[ ,i] <- cl$cluster代替it_mat <- cbind(it_mat, cl$cluster),但都是一样的。

我将不胜感激任何形式的帮助、评论或建议:我可能犯了一些非常愚蠢的错误,就像我之前的问题一样,或者我选择了一条非常困难的道路,使我的工作复杂化。

我的主要目标是了解集群组成在特定时间序列中是如何变化的。

谢谢大家的时间。

这是一个方法,但我无法让它与您的小数据集和 k 一起使用。也许它会更好地处理您的实际数据。如果你不想知道 why/how 这行得通,请跳到 TL;DR.

使用Reduce

我使用的技巧是Reduce,它的第一个参数是一个有两个参数的函数。它的一个简单演示是:

Reduce(function(a,b) 2*a+b, 1:4)

这相当于 2*1+2,然后是 2*(2*1+2)+3,等等。目前的形式可能没有吸引力。让我们进行一些打印,然后 "accumulate" 数据:

Reduce(function(a,b) {
  cat(paste(c(a,b), collapse=","), "\n")
  return(2*a+b)
}, 1:4, accumulate=TRUE)
# 1,2 
# 4,3 
# 11,4 
# [1]  1  4 11 26

因此,函数的第一次调用采用向量的第一个元素 1 和第二个元素 2 并调用函数。然后它采用 returned 值(2*1+24)和向量的第三个元素 3 并发挥它的魔力。等等。

一个"assumption"通常在处理Reduce时,两个值必须是相同的"type"对象。这不需要,所以我会稍微耍点小把戏。

另外需要注意的是,它是从列表的前两个元素开始的,这也不是一个严格的要求。如果我们设置init,我们可以控制第一次调用时a是什么。

Reduce(function(a,b) {
  cat(paste(c(a,b), collapse=","), "\n")
  return(2*a+b)
}, 1:4, init=99, accumulate=TRUE)
# 99,1 
# 199,2 
# 400,3 
# 803,4 
# [1]   99  199  400  803 1610

注意到列表中的每个元素是如何只在一次函数调用中使用的吗?

添加kmeans

所以我的技术是考虑在函数的第 n 次调用时我们想要什么:我们想要来自 n-1 的前一个簇对象和第 n 次数据。意识到 "previous cluster object" 看起来很像上一个示例中的 199、400 和 803。我们将编写一个函数,假设前面的集群对象是第一个参数,数据是第二个参数。

my_cascade_kmeans <- function(prevclust, dat) {
  kmeans(dat, centers = prevclust$centers, nstart = 10)
}
Reduce(my_cascade_kmeans, dfList, accumulate = TRUE)

(顺便说一句:我正在收集整个集群输出,而不仅仅是中心,因为最终我们希望得到一个集群对象列表。)

您很快就会发现(并回忆起来)的问题是,第一次调用它时,它是用前两个元素调用的。因此,我们要声明初始值。两种处理方法:

  1. Reduce(my_cascade_kmeans, dfList, init=list(centers=5), accumulate=TRUE)

    这是利用 kmeans 的集群对象和静态 list(centers=5) 的集群对象都可以用 $centers 索引的便利,它们 return 我认为我们需要

  2. Reduce(my_cascade_kmeans, dfList, init=NULL, accumulate=TRUE)

    为此,我们需要修改我们的函数以期望 NULL in prevclust 并相应地处理它。有时这可能会更好。

我更喜欢选项 1,因为它把 "default k value" 放在原来的 Reduce 调用中,而不一定埋在函数代码中。但你可能更喜欢那里,而不是你。

对于这个答案,我将初始集群从 18 减少到 4 ...任何更高的集群并且它失败并显示 Error: empty cluster: try a better set of initial centers,我猜这是由于样本数据集被截断。

TL;DR

my_cascade_kmeans <- function(prevclust, dat) {
  kmeans(dat, centers = prevclust$centers, nstart = 10)
}
clusters <- Reduce(my_cascade_kmeans, dfList, init = list(centers=4), accumulate = TRUE)

length(clusters)
# [1] 26

你可能对此犹豫不决,但这是我们告诉它要做的:"initialize the vector by prepending list(centers=4) to the beginning, and then accumulate the results",所以我们不应该对它是 one-longer 比我们开始的要多。

clusters[[1]]
# $centers
# [1] 4

这证实了这一点。用

清理它
clusters <- clusters[-1]

现在每个 clusters 都是 kmeans(...) 中的 return 使用之前的

clusters[[1]]
# K-means clustering with 4 clusters of sizes 2, 4, 3, 3
# Cluster means:
#         [,1]
# 1  0.9759631
# 2  0.1646323
# 3 -0.4514542
# 4 -1.0172681
# Clustering vector:
# 2015-01-30 2015-02-27 2015-03-31 2015-04-30 2015-05-29 2015-06-30 2015-07-31 2015-08-31 2015-09-30 2015-10-30 2015-11-30 
#          4          1          4          3          2          2          2          3          4          1          3 
# 2015-12-31 
#          2 
# Within cluster sum of squares by cluster:
# [1] 0.16980147 0.12635651 0.02552839 0.02940412
#  (between_SS / total_SS =  94.0 %)
# Available components:
# [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"        
# [9] "ifault"      

锦上添花,这也适用于 2 或 2000 个数据集。