首页 > 解决方案 > 在 R 中聚类用户;监控集群结构的变化以检测“消失”或“移动”集群的用户

问题描述

我正在使用纵向用户事件生成的数据集,并且我正在尝试使用 k-means 在月-年级别对数据中的用户 ID 进行聚类。这个想法是我想看看用户如何在不同的时间点从不同的集群原型中消失或移动到不同的集群原型中。

这是我到目前为止的代码,其中包含一个模拟数据框和聚类过程。

library(Pareto)
library(uuid)
library(ggplot2)
library(data.table)
library(zoo)
#generating the user ID variable
set.seed(1)
n_users <- 1300
n_rows <- 365000

relative_probs <- rPareto(n = n_users, t = 1, alpha = 0.3, truncation = 500) 
unique_ids <- UUIDgenerate(n = n_users)

id_sample <- sample(unique_ids, size = n_rows, prob = relative_probs, replace = TRUE)

id_sample


df<-data.frame(id_sample)


#creating the date variable
df$Date<-sample(seq(as.Date("2015-01-01"), as.Date("2017-12-31"), by = "1 day"), 
                size = n_rows,replace = T)

#creating a numeric value called Duration; this will be a feature in the clustering
df$Duration<-sample(0:3000, nrow(df), replace = T)
df<-df%>%arrange(Date)
#getting Month-Year
df$MonthYear<-as.Date(as.yearmon(df$Date, "%m/%Y"))
MonthYear<-unique(df$MonthYear)

#empty results df
resultsdf<-data.frame()

for (i in MonthYear) {
  #getting variables for clustering. I need to cluster based on the number of  times the User appears in the data i.e. "Count"
  #the second variable is the mean duration for each User ID i.e. "MeanDur" 
  #the third and final variable is the standard deviation of duration i.e. "SDDur"
  
df_filtered<-df%>%
filter(MonthYear<=i)

  callerData<-df_filtered%>%
    group_by(id_sample)%>%
    summarise(Count=n(),MeanDur=mean(Duration),SDDur=sd(Duration))
 #convert NA to zero's
  callerData$SDDur[is.na(callerData$SDDur)]<-0.0 
  #scale data
  scaledData<-scale(callerData[,2:4])
  
  set.seed(20)
  clust<-kmeans(scaledData, centers= 5,nstart = 15)
  #pinning cluster number back onto callerData
  callerData$Cluster<-clust$cluster
  #getting cluster means and creating a rank order based on "Count" 
  callerData_centers<-callerData%>%
    group_by(Cluster)%>%
    summarise(Count=mean(Count),MeanDur=mean(MeanDur),SDDur=mean(SDDur))%>%
    arrange(Count)
    
callerDate_centers$Rank<-c(1:5)
  #Once the new ranking variable is created, I then use the code below to consistently name the clusters based on their rank
  setDT(callerData_centers)[Rank==1,ClusName:="Cluster 1"]
  callerData_centers[Rank==2,ClusName:="Cluster 2"]
  callerData_centers[Rank==3,ClusName:="Cluster 3"]
  callerData_centers[Rank==4,ClusName:="Cluster 4"]
  callerData_centers[Rank==5,ClusName:="Cluster 5"]
#get the ClusName variable and the Cluster; this is then used to merge the new name back onto callerData
  callerData_vars<-callerData_centers%>%select(Cluster,ClusName)
  callerData<-merge(callerData,callerData_vars, by="Cluster")
 
    newVars<-callerData%>%
    select(CallerId,ClusterName)%>%
    mutate(MonthYear=i)
 
resultsdf<-rbind(resultsdf,newVars)
}


head(resultsdf)


所以代码向后过滤MonthYear<=i并聚集它。但是我想知道有没有办法让我检测某些用户是否从一个集群移动到另一个集群或消失?例如,id_sample=abcdef在集群 1 中待了 7 个月,但在 7 个月后移至集群 4。我怎么能检测到这个?

标签: rfor-loopcluster-analysisk-meansprediction

解决方案


我不得不稍微更改您的代码以使其运行。CallerId并且ClusterName不属于callerData. 所以首先运行这个:

library(Pareto)
library(uuid)
library(ggplot2)
library(data.table)
library(zoo)
library(dplyr)
#generating the user ID variable
#generating the user ID variable
set.seed(1)
n_users <- 1300
n_rows <- 365000

relative_probs <-
  rPareto(
    n = n_users,
    t = 1,
    alpha = 0.3,
    truncation = 500
  )
unique_ids <- UUIDgenerate(n = n_users)

id_sample <-
  sample(unique_ids,
         size = n_rows,
         prob = relative_probs,
         replace = TRUE)

id_sample


df <- data.frame(id_sample)


#creating the date variable
df$Date <-
  sample(seq(as.Date("2015-01-01"), as.Date("2017-12-31"), by = "1 day"),
         size = n_rows,
         replace = T)

#creating a numeric value called Duration; this will be a feature in the clustering
df$Duration <- sample(0:3000, nrow(df), replace = T)
df <- df %>% arrange(Date)
#getting Month-Year
df$MonthYear <- as.Date(as.yearmon(df$Date, "%m/%Y"))
MonthYear <- unique(df$MonthYear)

#empty results df
resultsdf <- data.frame()

for (i in MonthYear) {
  #getting variables for clustering. I need to cluster based on the number of  times the User appears in the data i.e. "Count"
  #the second variable is the mean duration for each User ID i.e. "MeanDur"
  #the third and final variable is the standard deviation of duration i.e. "SDDur"
  
  df_filtered <- df %>%
    filter(MonthYear <= i)
  
  callerData <- df_filtered %>%
    group_by(id_sample) %>%
    summarise(
      Count = n(),
      MeanDur = mean(Duration),
      SDDur = sd(Duration)
    )
  #convert NA to zero's
  callerData$SDDur[is.na(callerData$SDDur)] <- 0.0
  #scale data
  scaledData <- scale(callerData[, 2:4])
  
  set.seed(20)
  clust <- kmeans(scaledData, centers = 5, nstart = 15)
  #pinning cluster number back onto callerData
  callerData$Cluster <- clust$cluster
  #getting cluster means and creating a rank order based on "Count"
  callerData_centers <- callerData %>%
    group_by(Cluster) %>%
    summarise(
      Count = mean(Count),
      MeanDur = mean(MeanDur),
      SDDur = mean(SDDur)
    ) %>%
    arrange(Count)
  
  callerData_centers$Rank <- c(1:5)
  #Once the new ranking variable is created, I then use the code below to consistently name the clusters based on their rank
  setDT(callerData_centers)[Rank == 1, ClusName := "Cluster 1"]
  callerData_centers[Rank == 2, ClusName := "Cluster 2"]
  callerData_centers[Rank == 3, ClusName := "Cluster 3"]
  callerData_centers[Rank == 4, ClusName := "Cluster 4"]
  callerData_centers[Rank == 5, ClusName := "Cluster 5"]
  #get the ClusName variable and the Cluster; this is then used to merge the new name back onto callerData
  callerData_vars <-
    callerData_centers %>% select(Cluster, ClusName)
  callerData <- merge(callerData, callerData_vars, by = "Cluster")
  
  newVars <- callerData %>%
    select(id_sample, ClusName) %>%
    mutate(MonthYear = i)
  
  resultsdf <- rbind(resultsdf, newVars)
}

然后,您可以将数据框重塑为宽格式,并看到一些 id 会从一个集群跳到另一个集群(例如,在第 5 行从第 4 列到第 5 列)

# long to wide
dfwide <- data.table::dcast(resultsdf, formula = id_sample ~ MonthYear, value.var = 'ClusName')
colnames(dfwide)[2:37] <- paste0('Date_', MonthYear)

> dfwide[1:5, 1:5]
                             id_sample Date_2015-01-01 Date_2015-02-01 Date_2015-03-01 Date_2015-04-01
1 0025a4ba-d620-4ffc-a82d-660354c2b21d            <NA>       Cluster 3       Cluster 3       Cluster 3
2 00403759-46d8-4c60-b298-a57e6299b2ca       Cluster 3       Cluster 3       Cluster 3       Cluster 3
3 005e6e19-8e02-4326-993d-1fffa0b70c67       Cluster 4       Cluster 4       Cluster 4       Cluster 4
4 007c99ef-7e37-42c0-8883-90c275c03eab       Cluster 3       Cluster 3       Cluster 3       Cluster 3
5 007e70f9-d960-4679-8088-a1065ea9835c       Cluster 3       Cluster 3       Cluster 3       Cluster 2

然后,您可以检查哪些 ID 没有留在一个集群中:

# check if they stay in their cluster:
dfwide_subset <- na.omit(dfwide) # drop rows with NAs 

res <- dfwide_subset[, c(2:5)] == dfwide_subset[, 2]
res <- data.frame('id_sample' = dfwide_subset$id_sample,
                  'switches_cluster' = rowSums(!res))

> head(res)
                             id_sample switches_cluster
2 00403759-46d8-4c60-b298-a57e6299b2ca                0
3 005e6e19-8e02-4326-993d-1fffa0b70c67                0
4 007c99ef-7e37-42c0-8883-90c275c03eab                0
5 007e70f9-d960-4679-8088-a1065ea9835c                1
6 00b90528-5ee1-40f2-a2ba-5b6fc4b3707f                0
8 00d5cf0c-e2b4-4ed6-a69c-776b83ff8697                0

推荐阅读