#r #data.table #percentile
Вопрос:
У меня есть таблица data.table с более чем десятью тысячами строк, и она выглядит так:
DT1 <- data.table(ID = 1:10,
result_2010 = c("TRUE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE"),
result_2011 = c("FALSE", "TRUE", "FALSE", "FALSE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE"),
years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))
ID result_2010 result_2011 years
1: 1 TRUE FALSE 15.0
2: 2 FALSE TRUE 16.5
3: 3 TRUE FALSE 31.0
4: 4 FALSE FALSE 1.0
5: 5 FALSE FALSE 40.2
6: 6 TRUE FALSE 0.3
7: 7 FALSE TRUE 12.0
8: 8 FALSE FALSE 22.7
9: 9 TRUE FALSE 19.0
10: 10 FALSE TRUE 12.0
Для «result_2010» и «result_2011» я хочу провести процентильный анализ «лет», но только в том случае, если значение для отдельного человека «ВЕРНО». Код, который я попробовал, кажется, работает, но он возвращает те же результаты для «result_2010» и «result_2011», что, безусловно, неверно:
DT1 %>%
group_by(result_2010 == "TRUE") %>%
summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
"25.quantile"= round(quantile(years,c(.25)),digits=1),
"Median"= round(quantile(years,c(.50)),digits=1),
"75.quantile"= round(quantile(years,c(.75)),digits=1),
"90.quantile"= round(quantile(years,c(.90)),digits=1),
"Mean" = round(mean(years),digits=1))
DT1 %>%
group_by(result_2011 == "TRUE") %>%
summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
"25.quantile"= round(quantile(years,c(.25)),digits=1),
"Median"= round(quantile(years,c(.50)),digits=1),
"75.quantile"= round(quantile(years,c(.75)),digits=1),
"90.quantile"= round(quantile(years,c(.90)),digits=1),
"Mean" = round(mean(years),digits=1))
Кто-нибудь может помочь, как исправить мой код?
Комментарии:
1. вы, вероятно, хотели использовать
filter
вместоgroup_by
i.efilter(result_2010 == "TRUE")
2. Есть ли какая-то особая причина, по которой вы используете
"TRUE"
/"FALSE"
вместо более прямогоTRUE
/FALSE
? Я нахожу, что эффективная обработка часто начинается с эффективных данных.3. Отвечают ли какие-либо (все?) ответы на ваш вопрос, Габеш?
4. Мы, кажется, ошеломляем спрашивающего количеством решений и их сложностью. Габеш, должно быть, боится пытаться сделать все это. Не говоря уже о том, чтобы решить, кому дать 15 очков репутации. А сам он получил 30 баллов за свой вопрос 🙁 ! PS. Я снова проверил ваше решение, как при вводе переменных
result_2010
result_2011
DT1
, так и приlogical
character
вводе, и каждый раз, когда я получаю ошибку «Ошибка … объект «значение» не найден».
Ответ №1:
Используя melt
и. aggregate
library(data.table)
melt(DT1, c(1, 4), 2:3) |>
transform(variable=substring(variable, 8)) |>
subset(value == TRUE) |>
with(aggregate(list(q=years), list(year=variable), (x)
c(quantile(x), mean=mean(x))))
# year q.0% q.25% q.50% q.75% q.100% q.mean
# 1 2010 0.300 11.325 17.000 22.000 31.000 16.325
# 2 2011 12.000 12.000 12.000 14.250 16.500 13.500
Примечание: Пожалуйста, используйте R>=4.1> для обозначения |>
труб и (x)
функций (или записи function(x)
).
Комментарии:
1. Мне особенно нравится этот
dplyr
поток, подобный трубе, сансdplyr
. Очень плохо (на мой взгляд), что групповоеtransform
(не используемое здесь, просто в целом) кажется не таким гладким (т. Е. Необходимым для использованияave
).2. @r2evans Вы когда-нибудь проверяли
ave
код, скрывающийсяlapply(split())
внутри?3. Да, в прошлом я смотрел на это, и использование
`split<-`
довольно поучительно. В целом, dplyrgroup_by(grp) %>% mutate(a = ...)
, похоже, не так хорошо переводитсяtransform(a = ave(a, grp, FUN = (x) ...))
и работает еще хуже при одновременном преобразовании нескольких переменных.
Ответ №2:
library(tidyverse)
DT1 <- tibble(ID = 1:10,
result_2010 = c(TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE),
result_2011 = c(FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE),
years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))
fQuantMean = function(x) t(quantile(x)) %>%
as_tibble() %>% bind_cols(mean = mean(x))
tibble(
year = c(2010, 2011),
data = list(DT1$years[DT1$result_2010],
DT1$years[DT1$result_2011])
) %>% group_by(year) %>%
group_modify(~fQuantMean(.x$data[[1]]))
выход
# A tibble: 2 x 7
# Groups: year [2]
year `0%` `25%` `50%` `75%` `100%` mean
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2010 0.3 11.3 17 22 31 16.3
2 2011 12 12 12 14.2 16.5 13.5
Обновление для всех, кто заинтересован!
Здравствуйте, уважаемые коллеги. Как видите, каждая задача может быть решена несколькими различными способами. Поэтому я решил сравнить методы, предложенные здесь. Поскольку @Gabesz упомянул, что у него есть 10000 наблюдений, я решил проверить каждое из решений с точки зрения производительности.
n=10000
set.seed(1234)
DT1 <- tibble(ID = 1:n,
result_2010 = sample(c(TRUE, FALSE), n, replace = TRUE),
result_2011 = sample(c(TRUE, FALSE), n, replace = TRUE),
years = rnorm(n, 20, 5))
Затем я сделал небольшой тест
fQuantMean = function(x) t(quantile(x)) %>%
as_tibble() %>% bind_cols(mean = mean(x))
fFiolka = function(){
tibble(
year = c(2010, 2011),
data = list(DT1$years[DT1$result_2010],
DT1$years[DT1$result_2011])
) %>% group_by(year) %>%
group_modify(~fQuantMean(.x$data[[1]]))
}
fFiolka()
# # A tibble: 2 x 7
# # Groups: year [2]
# year `0%` `25%` `50%` `75%` `100%` mean
# <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 2010 -0.00697 16.4 19.9 23.3 38.6 19.9
# 2 2011 -0.633 16.5 20.0 23.4 38.6 20.0
library(data.table)
fjay_sf = function(){
melt(DT1, c(1, 4), 2:3) |>
transform(variable=substring(variable, 8)) |>
subset(value == TRUE) |>
with(aggregate(list(q=years), list(year=variable), (x)
c(quantile(x), mean=mean(x))))
}
fjay_sf()
# year q.0% q.25% q.50% q.75% q.100% q.mean
# 1 2010 -0.006968224 16.447077579 19.947385976 23.348571278 38.636456902 19.944574420
# 2 2011 -0.633138113 16.530534403 20.043636844 23.424378551 38.636456902 20.013130400
# Warning message:
# In melt(DT1, c(1, 4), 2:3) :
# The melt generic in data.table has been passed a tbl_df and will attempt to redirect
# to the relevant reshape2 method; please note that reshape2 is deprecated, and this
# redirection is now deprecated as well. To continue using melt methods from reshape2
# while both libraries are attached, e.g. melt.list, you can prepend the namespace
# like reshape2::melt(DT1). In the next version, this warning will become an error.
cols <- grep('result_', names(DT1), value = TRUE)
get_stats_fun <- function(DT, col) {
DT %>%
filter(.data[[col]] == "TRUE") %>%
summarise("quantile" = list(round(quantile(years,c(.10,.25,.50,.75,.90)),1)),
"median" = round(median(years), 1),
"Mean" = round(mean(years),1)) %>%
unnest_wider(quantile)
}
fShah = function(){
map_df(cols, ~get_stats_fun(DT1, .x), .id = 'Year') %>%
mutate(Year = cols)
}
fShah()
# # A tibble: 2 x 8
# Year `10%` `25%` `50%` `75%` `90%` median Mean
# <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 result_2010 13.5 16.4 19.9 23.3 26.4 19.9 19.9
# 2 result_2011 13.4 16.5 20 23.4 26.6 20 20
library(microbenchmark)
ggplot2::autoplot(microbenchmark(fFiolka(), fjay_sf(), fShah(), times=100))
Надеюсь, приведенная выше диаграмма все это объясняет.
@r2evans, пожалуйста, не вините меня в том, что я пропустил ваше решение, но это вызвало у меня некоторые ошибки.
Ответ №3:
Вы можете написать функцию и запустить ее в каждом result
столбце.
library(tidyverse)
cols <- grep('result_', names(DT1), value = TRUE)
get_stats_fun <- function(DT, col) {
DT %>%
filter(.data[[col]] == "TRUE") %>%
summarise("quantile" = list(round(quantile(years,c(.10,.25,.50,.75,.90)),1)),
"median" = round(median(years), 1),
"Mean" = round(mean(years),1)) %>%
unnest_wider(quantile)
}
map_df(cols, ~get_stats_fun(DT1, .x), .id = 'Year') %>%
mutate(Year = cols)
# Year `10%` `25%` `50%` `75%` `90%` median Mean
# <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#1 result_2010 4.7 11.3 17 22 27.4 17 16.3
#2 result_2011 12 12 12 14.2 15.6 12 13.5
Ответ №4:
A melt
/ dcast
опция:
library(data.table)
tmp <- melt(DT1, c("ID", "years"), variable.name = "year"
)[ value == "TRUE",
][, .(variable = c(paste0("q", c(10, 25, 50, 75, 90)), "mu"),
value = c(quantile(years, c(0.1, 0.25, 0.5, 0.75, 0.9)),
mean(years)))
, by = .(year)]
tmp
# year variable value
# <fctr> <char> <num>
# 1: result_2010 q10 4.710
# 2: result_2010 q25 11.325
# 3: result_2010 q50 17.000
# 4: result_2010 q75 22.000
# 5: result_2010 q90 27.400
# 6: result_2010 mu 16.325
# 7: result_2011 q10 12.000
# 8: result_2011 q25 12.000
# 9: result_2011 q50 12.000
# 10: result_2011 q75 14.250
# 11: result_2011 q90 15.600
# 12: result_2011 mu 13.500
dcast(tmp, year ~ variable, value.var = "value")
# year mu q10 q25 q50 q75 q90
# <fctr> <num> <num> <num> <num> <num> <num>
# 1: result_2010 16.325 4.71 11.325 17 22.00 27.4
# 2: result_2011 13.500 12.00 12.000 12 14.25 15.6
У вас есть полный контроль над именами, просто назначьте их (по порядку) в "variable"
столбце (вы можете выбрать лучшее название).
Или одинокий melt
:
melt(DT1, c("ID", "years"), variable.name = "year"
)[ value == "TRUE",
][, setNames(as.list(c(quantile(years, c(0.1, 0.25, 0.5, 0.75, 0.9)),
mean(years))),
c(paste0("q", c(10, 25, 50, 75, 90)), "mu"))
, by = .(year)][]
# year q10 q25 q50 q75 q90 mu
# <fctr> <num> <num> <num> <num> <num> <num>
# 1: result_2010 4.71 11.325 17 22.00 27.4 16.325
# 2: result_2011 12.00 12.000 12 14.25 15.6 13.500
Имена снова легко контролируются, теперь во 2-м аргументе setNames
. Предпосылка заключается в том, что возврат именованной list
data.table
обработки преобразует ее в именованные столбцы, поэтому любая функция, которая делает это, легко применима.
Ответ №5:
Это будет мой первый ответ, поэтому, пожалуйста, простите меня, если я сделаю что-то не так. Внимательно прочитав ваш вопрос, вы хотели, чтобы кто-то помог вам улучшить ваш код. Вот оно, пожалуйста.
library(tidyverse)
library(data.table)
DT1 <- data.table(ID = 1:10,
result_2010 = c("TRUE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE"),
result_2011 = c("FALSE", "TRUE", "FALSE", "FALSE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE"),
years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))
DT1 %>%
filter(result_2010 == "TRUE") %>%
summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
"25.quantile"= round(quantile(years,c(.25)),digits=1),
"Median"= round(quantile(years,c(.50)),digits=1),
"75.quantile"= round(quantile(years,c(.75)),digits=1),
"90.quantile"= round(quantile(years,c(.90)),digits=1),
"Mean" = round(mean(years),digits=1))
DT1 %>%
filter(result_2011 == "TRUE") %>%
summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
"25.quantile"= round(quantile(years,c(.25)),digits=1),
"Median"= round(quantile(years,c(.50)),digits=1),
"75.quantile"= round(quantile(years,c(.75)),digits=1),
"90.quantile"= round(quantile(years,c(.90)),digits=1),
"Mean" = round(mean(years),digits=1))
В первом случае он возвращает значения 4.7, 11.3, 17, 22, 27.4, 16.3. Во втором случае он возвращается 12, 12, 12, 14.2, 15.6, 13.5.
Я вижу здесь так много разных ответов. Хотя я честно признаю, что некоторых из них я не понимаю (пока). Мне очень нравится решение с квантилем%>% tibble%>>% bind_cols. Но постучите по тому, что у меня низкая репутация, указывая на это как на полезное.