Как быстро формировать группы (квартили, децили и т.д.) Путем упорядочения столбцов (ов) в кадре данных
Я вижу много вопросов и ответов re 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
Есть ли лучший (более чистый/быстрый/однострочный) подход? Спасибо!
Ответы
Ответ 1
Метод, который я использую, является одним из этих или 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
Там есть удобная функция 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
Я добавлю версию data.table
для всех, кто data.table
в data.table
(т.е. Решение @BondedDust переведено в data.table
и немного data.table
):
library(data.table)
setDT(temp)
temp[ , quartile := cut(value,
breaks = quantile(value, probs = 0:4/4),
labels = 1:4, right = FALSE)]
Что гораздо лучше (чище, быстрее), чем то, что я делал:
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
Вы можете использовать функцию 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
Адаптация dplyr::ntile
для использования оптимизации data.table
обеспечивает более быстрое решение.
library(data.table)
setDT(temp)
temp[order(value) , quartile := floor( 1 + 4 * (.I-1) / .N)]
Вероятно, он не считается чистым, но быстрее и однострочным.
Сроки по большему набору данных
Сравнивая это решение с ntile
и cut
для data.table
как предложено @docendo_discimus и @MichaelChirico.
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
Извините, что немного опоздал на вечеринку. Я хотел добавить мой один лайнер, используя cut2
, поскольку я не знал max/min для своих данных и хотел, чтобы группы были одинаково большими. Я прочитал о 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
Ответ 7
temp$quartile <- ceiling(sapply(temp$value,function(x) sum(x-temp$value>=0))/(length(temp$value)/4))
Ответ 8
Я хотел бы предложить версию, которая кажется более надежной, так как я столкнулся с множеством проблем, используя quantile()
в опции break 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
Возможно, есть более быстрый способ, но я бы сделал:
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))