sorting - 함수 - 데이터 프레임에서 열을 정렬하여 그룹(사 분위수, 십진수 등)을 빠르게 형성하는 방법




tbl_df (6)

나는 많은 질의와 응답으로 ordersort 반복한다. 벡터 또는 데이터 프레임을 그룹으로 정렬하는 항목이 있습니까 (예 : 사 분위 또는 십진수)? "수동"솔루션이 있지만 그룹 테스트를 거친 더 나은 솔루션이있을 수 있습니다.

내 시도는 다음과 같습니다.

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

더 나은 (더 깨끗한 / 빠른 / 한 줄) 접근법이 있습니까? 감사!


내 데이터 집합의 중단 옵션 cut() 에서 quantile() 을 사용하여 많은 문제가 발생했기 때문에 더 강력 해 보이는 버전을 제안하고 싶습니다. plyrntile 함수를 사용하지만 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)
)]

그 맞습니까?


내가 사용하는 방법은 다음 중 하나 또는 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= 을 사용할 수 있습니다. 또는 줄에 코드를 추가 할 수 있습니다.

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

또는 더 이상 요소가 아니지만 숫자 벡터 인 경우에도 작동 방식이 더 빠르지 만 약간 더 모호합니다.

temp$quartile <- as.numeric(temp$quartile)

아마도 더 빠른 방법이있을 수 있지만, 나는 할 것입니다 :

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

파티에 늦어서 미안해. 내 데이터에 최대 / 최소값을 알지 못했고 그룹이 동일하게 유지되기를 원했기 때문에 cut2 를 사용하여 하나의 라이너를 추가하려고했습니다. 나는 duplicate (아래 링크)로 표시된 issue에서 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)

내가 cut2에 대해 자세히 읽은 비슷한 문제


data.table 최적화를 활용하기 위해 dplyr::ntile 을 수정하면 더 빠른 솔루션을 제공합니다.

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

아마도 청소기로는 적합하지 않지만 더 빠르고 한 줄입니다.

더 큰 데이터 세트 타이밍

이 솔루션을 ntile 과 비교하고 @docendo_discimus 및 @MichaelChirico가 제안한 data.tablecut 냅니다.

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

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




order