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

时间:2018-02-15 09:50:52

标签: r for-loop data-structures k-means

这是我的第二篇文章,让我们说它早于第一篇文章,我将在此处链接:

creating a matrix/dataframe with two for loops in R

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

 > 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")))

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

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

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

dfList是一个包含25个数据帧的列表,通过12个月的滚动窗口从原始数据帧中进行子集化。

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

但是这里有悲伤,我希望质心能够成为前一次运行的质心(如果它们从第一次运行中得到修复,那么无论如何都会很棒。)

我没有问题&#34;手工&#34;:

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循环只返回一个只有第一次迭代的矩阵:

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),但它是一样的。

我会感谢任何帮助,评论或建议:我可能会像我之前的问题那样犯一些非常愚蠢的错误,或者我选择了一条使我的工作变得复杂的艰难道路。

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

感谢大家的时间。

1 个答案:

答案 0 :(得分:1)

这是一种方法,但我无法使用您的小数据集和k。也许它可以更好地处理您的实际数据。如果您不想知道为什么/如何运作,请跳至 TL; DR

使用Reduce

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

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

这相当于2*1+2,然后是2*(2*1+2)+3等等。也许它目前的形式没有吸引力。让我们进行一些打印,然后“累积”数据:

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并调用函数。然后它返回值(2*1+24)和向量3的第三个元素,并发挥其魔力。等等。

处理Reduce时通常会做出一个“假设”,即两个值必须与对象的“类型”相同。这不是必须的,所以我会稍微处理一下。

另外需要注意的是,它是从列表的前两个元素开始,这也不是一个严格的要求。如果我们设置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编制索引,并且它们会返回我认为我们需要的内容。

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

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

  3. 我更喜欢选项1,因为它在原始k调用中放置了“默认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
    

    你可能会对此犹豫不决,但这就是我们告诉它的事情:“通过将list(centers=4)添加到开头来初始化向量,然后累积结果”,所以我们不应该感到惊讶的是它比我们开始的时间长一点。

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

    这证实了这一点。用

    清理它
    clusters <- clusters[-1]
    

    现在clusters中的每一个都是使用前一个

    kmeans(...)返回的
    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个数据集。