【问题标题】:Find top deciles from dataframe by group按组从数据帧中查找前十分位数
【发布时间】:2015-07-10 22:48:22
【问题描述】:

我正在尝试使用函数和lapply 创建新变量,而不是使用循环在数据中正确工作。我曾经使用过Stata,并且可以使用类似于here 讨论的方法来解决这个问题。

由于在 R 中以编程方式命名变量非常困难或至少很尴尬(而且您似乎不能对 assign 使用索引),所以我将命名过程留到 lapply 之后。然后我使用for 循环在合并之前进行重命名,然后再次进行合并。有没有更有效的方法来做到这一点?我将如何更换循环?我应该进行某种重塑吗?

#Reproducible data
data <- data.frame("custID" = c(1:10, 1:20),
    "v1" = rep(c("A", "B"), c(10,20)), 
    "v2" = c(30:21, 20:19, 1:3, 20:6), stringsAsFactors = TRUE)

#Function to analyze customer distribution for each category (v1)
pf <- function(cat, df) {

        df <- df[df$v1 == cat,]
        df <- df[order(-df$v2),]

    #Divide the customers into top percents
    nr <- nrow(df)
    p10 <- round(nr * .10, 0)
    cat("Number of people in the Top 10% :", p10, "\n")
    p20 <- round(nr * .20, 0)
    p11_20 <- p20-p10
    cat("Number of people in the 11-20% :", p11_20, "\n")

    #Keep only those customers in the top groups
    df <- df[1:p20,]

    #Create a variable to identify the percent group the customer is in
    top_pct <- integer(length = p10 + p11_20)

    #Identify those in each group
    top_pct[1:p10] <- 10
    top_pct[(p10+1):p20] <- 20

    #Add this variable to the data frame
    df$top_pct <- top_pct

    #Keep only custID and the new variable
    df <- subset(df, select = c(custID, top_pct))

    return(df)

}


##Run the customer distribution function
v1Levels <- levels(data$v1)
res <- lapply(v1Levels, pf, df = data)

#Explore the results
summary(res)

    #      Length Class      Mode
    # [1,] 2      data.frame list
    # [2,] 2      data.frame list

print(res)

    # [[1]]
    #   custID top_pct
    # 1      1      10
    # 2      2      20
    # 
    # [[2]]
    #    custID top_pct
    # 11      1      10
    # 16      6      10
    # 12      2      20
    # 17      7      20



##Merge the two data frames but with top_pct as a different variable for each category

#Change the new variable name
for(i in 1:length(res)) {
    names(res[[i]])[2] <- paste0(v1Levels[i], "_top_pct")
}

#Merge the results
res_m <- res[[1]]
for(i in 2:length(res)) {
    res_m <- merge(res_m, res[[i]], by = "custID", all = TRUE)
}

print(res_m)

    #   custID A_top_pct B_top_pct
    # 1      1        10        10
    # 2      2        20        20
    # 3      6        NA        10
    # 4      7        NA        20

【问题讨论】:

  • 您可以使用quantile()hist(breaks = define_your_nonuniform_breaks...) 摆脱大部分问题
  • 不要调用你的 df data,因为这会影响内置函数。
  • 当您为每个分类级别执行df &lt;- df[df$v1 == cat,] 时,您只是在执行一个笨拙的拆分-应用-组合。请参阅有关 plyr/dplyr group_by 的文档。尝试阅读 R 范例以执行常见操作。
  • 我重命名了这个“从数据帧中查找前 10% 和 10-20% 的十分位条目,按 v1 分组”,因为这是代码的意图。您可能想重新表述这个问题。
  • @smci 看起来“dataframes”不是 R“data.frame”s 的正确标签

标签: r dataframe rank quantile split-apply-combine


【解决方案1】:

坚持您的 Stata 直觉并使用单一数据集:

require(data.table)
DT <- data.table(data)

DT[,r:=rank(v2)/.N,by=v1]

你可以通过输入DT来查看结果。


如果您愿意,您可以在此处对v1 等级内r 进行分组。遵循Stata成语...

DT[,g:={
  x = rep(0,.N)
  x[r>.8] = 20
  x[r>.9] = 10
  x
}]

这就像gen,然后是两个replace ... if 语句。同样,您可以使用DT 查看结果。


最后,您可以使用子集

DT[g>0]

给了

   custID v1 v2     r  g
1:      1  A 30 1.000 10
2:      2  A 29 0.900 20
3:      1  B 20 0.975 10
4:      2  B 19 0.875 20
5:      6  B 20 0.975 10
6:      7  B 19 0.875 20

这些步骤也可以链接在一起:

DT[,r:=rank(v2)/.N,by=v1][,g:={x = rep(0,.N);x[r>.8] = 20;x[r>.9] = 10;x}][g>0]

(感谢@ExperimenteR:)

要在 OP 中重新排列所需的输出,列中的值为 v1,请使用 dcast

dcast(
  DT[,r:=rank(v2)/.N,by=v1][,g:={x = rep(0,.N);x[r>.8] = 20;x[r>.9] = 10;x}][g>0], 
  custID~v1)

目前,dcast 需要最新版本的data.table,可以从 Github 获得(我认为)。

【讨论】:

  • dcast(DT[,r:=rank(v2)/.N,by=v1][,g:={x = rep(0,.N);x[r&gt;.8] = 20;x[r&gt;.9] = 10;x}][g&gt;0], custID~v1) 将列并排放置。
  • @ExperimenteR 酷,谢谢!我还没有学习/安装dcast
【解决方案2】:

你不需要函数pf 来实现你想要的。试试dplyr/tidyr组合

library(dplyr)
library(tidyr)
data %>% 
    group_by(v1) %>% 
    arrange(desc(v2))%>%
    mutate(n=n()) %>% 
    filter(row_number() <= round(n * .2)) %>% 
    mutate(top_pct= ifelse(row_number()<=round(n* .1), 10, 20)) %>%
    select(custID, top_pct) %>% 
    spread(v1,  top_pct)
#  custID  A  B
#1      1 10 10
#2      2 20 20
#3      6 NA 10
#4      7 NA 20

【讨论】:

  • 这太棒了,你打败了我。 @KevinM,阅读这些 dplyr 命令的范例。 (data.table 也是一个可能的包,但对 R 初学者不太友好。所以用 dplyr 学习。)
  • 抱歉措辞强硬。按照建议调低。
【解决方案3】:

在 R 中做这种事情的惯用方法是使用 splitlapply 的组合。使用lapply 已经成功了一半;你也只需要使用split

lapply(split(data, data$v1), function(df) {
    cutoff <- quantile(df$v2, c(0.8, 0.9))
    top_pct <- ifelse(df$v2 > cutoff[2], 10, ifelse(df$v2 > cutoff[1], 20, NA))
    na.omit(data.frame(id=df$custID, top_pct))
})

使用quantile 查找分位数。

【讨论】:

  • 请参阅我的new post,了解如何组合这些结果。有什么要补充的吗?
猜你喜欢
  • 1970-01-01
  • 2016-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-13
相关资源
最近更新 更多