代码之家  ›  专栏  ›  技术社区  ›  Richard Herron

如何通过对数据帧中的列排序来快速形成组(四分位、小数等)

  •  57
  • Richard Herron  · 技术社区  · 14 年前

    我看到很多问题和答案 order sort

    我的尝试是:

    temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12))
    temp
    #    name       value quartile
    # 1     a  2.55118169       NA
    # 2     b  0.79755259       NA
    # 3     c  0.16918905       NA
    # 4     d  1.73359245       NA
    # 5     e  0.41027113       NA
    # 6     f  0.73012966       NA
    # 7     g -1.35901658       NA
    # 8     h -0.80591167       NA
    # 9     i  0.48966739       NA
    # 10    j  0.88856758       NA
    # 11    k  0.05146856       NA
    # 12    l -0.12310229       NA
    temp.sorted <- temp[order(temp$value), ]
    temp.sorted$quartile <- rep(1:4, each=12/4)
    temp <- temp.sorted[order(as.numeric(rownames(temp.sorted))), ]
    temp
    #    name       value quartile
    # 1     a  2.55118169        4
    # 2     b  0.79755259        3
    # 3     c  0.16918905        2
    # 4     d  1.73359245        4
    # 5     e  0.41027113        2
    # 6     f  0.73012966        3
    # 7     g -1.35901658        1
    # 8     h -0.80591167        1
    # 9     i  0.48966739        3
    # 10    j  0.88856758        4
    # 11    k  0.05146856        2
    # 12    l -0.12310229        1
    

    有更好(更干净/更快/单线)的方法吗?谢谢!

    10 回复  |  直到 6 年前
        1
  •  74
  •   IRTFM    9 年前

    我使用的方法是 Hmisc::cut2(value, g=4) :

    temp$quartile <- with(temp, cut(value, 
                                    breaks=quantile(value, probs=seq(0,1, by=0.25), na.rm=TRUE), 
                                    include.lowest=TRUE))
    

    temp$quartile <- with(temp, factor(
                                findInterval( val, c(-Inf,
                                   quantile(val, probs=c(0.25, .5, .75)), Inf) , na.rm=TRUE), 
                                labels=c("Q1","Q2","Q3","Q4")
          ))
    

    第一种方法的副作用是用值标记四分位数,我认为这是一件“好事”,但如果不是“对你有好处”,或者在评论中提出的有效问题是一个问题,你可以使用版本2。你可以用 labels= 在里面 cut

    temp$quartile <- factor(temp$quartile, levels=c("1","2","3","4") )
    

    或者更快,但在工作方式上稍显模糊,尽管它不再是一个因子,而是一个数值向量:

    temp$quartile <- as.numeric(temp$quartile)
    
        2
  •  68
  •   talat    10 年前

    ntile 包中函数 dplyr . 从某种意义上说,它是灵活的,可以很容易地定义要创建的*块或“容器”的数量。

    加载包(如果没有,请先安装)并添加四分位列:

    library(dplyr)
    temp$quartile <- ntile(temp$value, 4)  
    

    或者,如果要使用dplyr语法:

    temp <- temp %>% mutate(quartile = ntile(value, 4))
    

    两种情况下的结果都是:

    temp
    #   name       value quartile
    #1     a -0.56047565        1
    #2     b -0.23017749        2
    #3     c  1.55870831        4
    #4     d  0.07050839        2
    #5     e  0.12928774        3
    #6     f  1.71506499        4
    #7     g  0.46091621        3
    #8     h -1.26506123        1
    #9     i -0.68685285        1
    #10    j -0.44566197        2
    #11    k  1.22408180        4
    #12    l  0.35981383        3
    

    注意,您不需要预先创建“四分位”列并使用 set.seed 要使随机化可重复:

    set.seed(123)
    temp <- data.frame(name=letters[1:12], value=rnorm(12))
    
        3
  •  17
  •   MichaelChirico    6 年前

    我将添加 data.table 为其他人提供的版本(即@bondedust的解决方案翻译为 数据表 减少了一点):

    library(data.table)
    setDT(temp)
    temp[ , quartile := cut(value,
                            breaks = quantile(value, probs = 0:4/4),
                            labels = 1:4, right = FALSE)]
    

    哪个更好(更干净, faster )比我所做的还要多:

    temp[ , quartile := 
            as.factor(ifelse(value < quantile(value, .25), 1,
                             ifelse(value < quantile(value, .5), 2,
                                    ifelse(value < quantile(value, .75), 3, 4))]
    

    但是,请注意,这种方法要求分位数是不同的,例如,它将失败 rep(0:1, c(100, 1)) ;在这种情况下该做什么是开放式的,所以我把它留给你了。

        4
  •  6
  •   Gavin Simpson    14 年前

    quantile() 函数,但使用时需要处理舍入/精度 cut()

    set.seed(123)
    temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12))
    brks <- with(temp, quantile(value, probs = c(0, 0.25, 0.5, 0.75, 1)))
    temp <- within(temp, quartile <- cut(value, breaks = brks, labels = 1:4, 
                                         include.lowest = TRUE))
    

    给:

    > head(temp)
      name       value quartile
    1    a -0.56047565        1
    2    b -0.23017749        2
    3    c  1.55870831        4
    4    d  0.07050839        2
    5    e  0.12928774        3
    6    f  1.71506499        4
    
        5
  •  5
  •   EMuPi    8 年前

    适应 dplyr::ntile 利用 data.table

    library(data.table)
    setDT(temp)
    temp[order(value) , quartile := floor( 1 + 4 * (.I-1) / .N)]
    

    可能不符合清洁剂的要求,但速度更快,而且只有一条生产线。

    在更大的数据集上计时

    将此解决方案与 ntile cut 对于 数据表

    library(microbenchmark)
    library(dplyr)
    
    set.seed(123)
    
    n <- 1e6
    temp <- data.frame(name=sample(letters, size=n, replace=TRUE), value=rnorm(n))
    setDT(temp)
    
    microbenchmark(
        "ntile" = temp[, quartile_ntile := ntile(value, 4)],
        "cut" = temp[, quartile_cut := cut(value,
                                           breaks = quantile(value, probs = seq(0, 1, by=1/4)),
                                           labels = 1:4, right=FALSE)],
        "dt_ntile" = temp[order(value), quartile_ntile_dt := floor( 1 + 4 * (.I-1)/.N)]
    )
    

    给予:

    Unit: milliseconds
         expr      min       lq     mean   median       uq      max neval
        ntile 608.1126 647.4994 670.3160 686.5103 691.4846 712.4267   100
          cut 369.5391 373.3457 375.0913 374.3107 376.5512 385.8142   100
     dt_ntile 117.5736 119.5802 124.5397 120.5043 124.5902 145.7894   100
    
        6
  •  4
  •   Community Reversed Engineer    7 年前

    很抱歉来晚了一点。我想用 cut2 因为我不知道我的数据的最大值/最小值,所以我希望组的大小相同。我在一期被标记为重复的文章中读到了关于第二部分的内容(链接如下)。

    library(Hmisc)   #For cut2
    set.seed(123)    #To keep answers below identical to my random run
    
    temp <- data.frame(name=letters[1:12], value=rnorm(12), quartile=rep(NA, 12))
    
    temp$quartile <- as.numeric(cut2(temp$value, g=4))   #as.numeric to number the factors
    temp$quartileBounds <- cut2(temp$value, g=4)
    
    temp
    

    结果:

    > temp
       name       value quartile  quartileBounds
    1     a -0.56047565        1 [-1.265,-0.446)
    2     b -0.23017749        2 [-0.446, 0.129)
    3     c  1.55870831        4 [ 1.224, 1.715]
    4     d  0.07050839        2 [-0.446, 0.129)
    5     e  0.12928774        3 [ 0.129, 1.224)
    6     f  1.71506499        4 [ 1.224, 1.715]
    7     g  0.46091621        3 [ 0.129, 1.224)
    8     h -1.26506123        1 [-1.265,-0.446)
    9     i -0.68685285        1 [-1.265,-0.446)
    10    j -0.44566197        2 [-0.446, 0.129)
    11    k  1.22408180        4 [ 1.224, 1.715]
    12    l  0.35981383        3 [ 0.129, 1.224)
    

    Similar issue where I read about cut2 in detail

        7
  •  0
  •   James    14 年前
    temp$quartile <- ceiling(sapply(temp$value,function(x) sum(x-temp$value>=0))/(length(temp$value)/4))
    
        8
  •  0
  •   hannes101    7 年前

    我想提出一个更健壮的版本,因为我在使用 quantile() 在中断选项中 cut() 在我的数据集上。 ntile 功能 plyr ,但它也适用于 ecdf 作为输入。

    temp[, `:=`(quartile = .bincode(x = ntile(value, 100), breaks = seq(0,100,25), right = TRUE, include.lowest = TRUE)
                decile = .bincode(x = ntile(value, 100), breaks = seq(0,100,10), right = TRUE, include.lowest = TRUE)
    )]
    
    temp[, `:=`(quartile = .bincode(x = ecdf(value)(value), breaks = seq(0,1,0.25), right = TRUE, include.lowest = TRUE)
                decile = .bincode(x = ecdf(value)(value), breaks = seq(0,1,0.1), right = TRUE, include.lowest = TRUE)
    )]
    

    是这样吗?

        9
  •  0
  •   MaoXilin    5 年前

    试试这个功能

    getQuantileGroupNum <- function(vec, group_num, decreasing=FALSE) {
      if(decreasing) {
        abs(cut(vec, quantile(vec, probs=seq(0, 1, 1 / group_num), type=8, na.rm=TRUE), labels=FALSE, include.lowest=T) - group_num - 1)
      } else {
        cut(vec, quantile(vec, probs=seq(0, 1, 1 / group_num), type=8, na.rm=TRUE), labels=FALSE, include.lowest=T)
      }
    }
    
    > t1 <- runif(7)
    > t1
    [1] 0.4336094 0.2842928 0.5578876 0.2678694 0.6495285 0.3706474 0.5976223
    > getQuantileGroupNum(t1, 4)
    [1] 2 1 3 1 4 2 4
    > getQuantileGroupNum(t1, 4, decreasing=T)
    [1] 3 4 2 4 1 3 1
    
        10
  •  -1
  •   nico    14 年前

    a <- rnorm(100) # Our data
    q <- quantile(a) # You can supply your own breaks, see ?quantile
    
    # Define a simple function that checks in which quantile a number falls
    getQuant <- function(x)
       {
       for (i in 1:(length(q)-1))
           {
           if (x>=q[i] && x<q[i+1])
              break;
           }
       i
       }
    
    # Apply the function to the data
    res <- unlist(lapply(as.matrix(a), getQuant))