Допустим, я хочу создать столбец в таблице data.table, в которой значение в каждой строке равно стандартному отклонению значений в трех других ячейках в той же строке. Например, если я сделаю
DT <- data.table(a = 1:4, b = c(5, 7, 9, 11), c = c(13, 16, 19, 22), d = c(25, 29, 33, 37))
DT
a b c d
1: 1 5 13 25
2: 2 7 16 29
3: 3 9 19 33
4: 4 11 22 37
и я хотел бы добавить столбец, содержащий стандартное отклонение a, b и d для каждой строки, например:
a b c d abdSD
1: 1 5 13 23 12.86
2: 2 7 16 27 14.36
3: 3 9 19 31 15.87
4: 4 11 22 35 17.39
Конечно, я мог бы написать цикл for или использовать функцию apply для вычисления этого. К сожалению, то, что я действительно хочу сделать, нужно применить к миллионам строк, это не такая простая функция, как вычисление стандартного отклонения, и ее нужно завершить за доли секунды, поэтому мне действительно нужно векторизованное решение. Я хочу написать что-то вроде
DT[, abdSD := sd(c(a, b, d))]
но, к сожалению, это не дает правильного ответа. Есть ли какой-либо синтаксис data.table, который может создать вектор из разных значений в одной строке и сделать этот вектор доступным для функции, заполняющей новую ячейку в этой строке? Любая помощь будет принята с благодарностью. @Arun
Я думаю, вам стоит попробовать пакет matrixStats
library(matrixStats)
#sample data
dt <- data.table(a = 1:4, b = c(5, 7, 9, 11), c = c(13, 16, 19, 22), d = c(25, 29, 33, 37))
dt[, `:=`(abdSD = rowSds(as.matrix(.SD), na.rm=T)), .SDcols=c('a','b','d')]
dt
Выход:
a b c d abdSD
1: 1 5 13 25 12.85820
2: 2 7 16 29 14.36431
3: 3 9 19 33 15.87451
4: 4 11 22 37 17.38774
Не ответ, но просто пытаюсь показать разницу между использованием apply
и решением, предоставленным Prem выше:
Я увеличил выборку данных до 40000 строк, чтобы показать значительную разницу во времени:
library(matrixStats)
#sample data
dt <- data.table(a = 1:40000, b = rep(c(5, 7, 9, 11),10000), c = rep(c(13, 16, 19, 22),10000), d = rep(c(25, 29, 33, 37),10000))
df <- data.frame(a = 1:40000, b = rep(c(5, 7, 9, 11),10000), c = rep(c(13, 16, 19, 22),10000), d = rep(c(25, 29, 33, 37),10000))
t0 = Sys.time()
dt[, `:=`(abdSD = rowSds(as.matrix(.SD), na.rm=T)), .SDcols=c('a','b','d')]
print(paste("Time taken for data table operation = ",Sys.time() - t0))
# [1] "Time taken for data table operation = 0.117115020751953"
t0 = Sys.time()
df$abdSD <- apply(df[,c("a","b","d")],1, function(x){sd(x)})
print(paste("Time taken for apply opertaion = ",Sys.time() - t0))
# [1] "Time taken for apply opertaion = 2.93488311767578"
Использование DT
и matrixStats
явно выигрывает гонку
Как упоминал @Frank, я мог бы избежать добавления столбца, выполнив by=1:nrow(DT)
DT[, abdSD:=sd(c(a,b,d)),by=1:nrow(DT)]
выход:
a b c d abdSD
1: 1 5 13 25 12.85820
2: 2 7 16 29 14.36431
3: 3 9 19 33 15.87451
4: 4 11 22 37 17.38774
если вы добавите столбец row_name, это будет очень просто
DT$row_id<-row.names(DT)
Просто по = row_id, вы получите желаемый результат
DT[, abdSD:=sd(c(a,b,d)),by=row_id]
Результат будет:
a b c d row_id abdSD
1: 1 5 13 25 1 12.85820
2: 2 7 16 29 2 14.36431
3: 3 9 19 33 3 15.87451
4: 4 11 22 37 4 17.38774
Если вы хотите удалить row_id, просто добавьте [,row_id:=NULL]
DT[, abdSD:=sd(c(a,b,d)),by=row_id][,row_id:=NULL]
Эта линия получит все, что вы хотите
a b c d abdSD
1: 1 5 13 25 12.85820
2: 2 7 16 29 14.36431
3: 3 9 19 33 15.87451
4: 4 11 22 37 17.38774
Вы просто должны делать это по очереди.
data.frame по умолчанию делает это по строкам, data.table по умолчанию, я думаю. Это немного сложно
Надеюсь это поможет
К вашему сведению, вы также можете выполнить by=1:nrow(DT)
, не создавая новую колонку.
Спасибо, Фрэнк! Мне было интересно, как это сделать. Я сделал by=nrow(DT)
, и он не работал. Оказывается, 1:nrow(DT)
В зависимости от размера ваших данных вы можете захотеть преобразовать данные в длинный формат, а затем рассчитать результат следующим образом:
complexFunc <- function(x) sd(x)
cols <- c("a", "b", "d")
rowres <- melt(DT[, rn:=.I], id.vars = "rn", variable.factor=FALSE)[,
list(abdRes=complexFunc(value[variable %chin% cols])), by=.(rn)]
DT[rowres, on=.(rn)]
или если ваша сложная функция имеет 3 аргумента, вы можете сделать что-то вроде
DT[, abdSD := mapply(complexFunc, a, b, d)]
В этом случае векторизовать sd
несложно:
vecSD = function(x) {
n = ncol(x)
sqrt((n/(n-1)) * (Reduce(`+`, x*x)/n - (Reduce(`+`, x)/n)^2))
}
DT[, vecSD(.SD), .SDcols = c('a', 'b', 'd')]
#[1] 12.85820 14.36431 15.87451 17.38774
Он подан здесь github.com/Rdatatable/data.table/issues/1063