"Loop through" data.table для вычисления условных средних значений
Я хочу "прокрутить" строки таблицы данных и вычислить среднее значение для каждой строки. Среднее значение должно рассчитываться на основе следующего механизма:
- Найдите идентификатор идентификатора в строке я (ID (i))
- Посмотрите значение T2 в строке я (T2 (i))
- Вычислить среднее значение по значениям
Data1
во всех строках j
, которые соответствуют этим двум критериям: ID(j) = ID(i)
и T1(j) = T2(i)
-
Введите вычисленное среднее значение в столбце Data2 строки i
DF = data.frame(ID=rep(c("a","b"),each=6),
T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12))
DT = data.table(DF)
DT[ , Data2:=NA_real_]
ID T1 T2 Data1 Data2
[1,] a 1 1 1 NA
[2,] a 1 2 2 NA
[3,] a 1 3 3 NA
[4,] a 2 1 4 NA
[5,] a 2 2 5 NA
[6,] a 2 3 6 NA
[7,] b 1 1 7 NA
[8,] b 1 2 8 NA
[9,] b 1 3 9 NA
[10,] b 2 1 10 NA
[11,] b 2 2 11 NA
[12,] b 2 3 12 NA
Для этого простого примера результат должен выглядеть следующим образом:
ID T1 T2 Data1 Data2
[1,] a 1 1 1 2
[2,] a 1 2 2 5
[3,] a 1 3 3 NA
[4,] a 2 1 4 2
[5,] a 2 2 5 5
[6,] a 2 3 6 NA
[7,] b 1 1 7 8
[8,] b 1 2 8 11
[9,] b 1 3 9 NA
[10,] b 2 1 10 8
[11,] b 2 2 11 11
[12,] b 2 3 12 NA
Я думаю, что одним из способов сделать это будет цикл через строки, но я считаю, что это неэффективно. Я посмотрел на функцию apply()
, но я уверен, что это решит мою проблему. Я мог бы использовать data.frame
вместо data.table
, если бы это сделало его намного более эффективным или намного более простым. Реальный набор данных содержит приблизительно 1 миллион строк.
Ответы
Ответ 1
Правило большого пальца состоит в том, чтобы сначала скомпилировать, а затем присоединиться к этому.
agg = DT[,mean(Data1),by=list(ID,T1)]
setkey(agg,ID,T1)
DT[,Data2:={JT=J(ID,T2);agg[JT,V1][[3]]}]
ID T1 T2 Data1 Data2
[1,] a 1 1 1 2
[2,] a 1 2 2 5
[3,] a 1 3 3 NA
[4,] a 2 1 4 2
[5,] a 2 2 5 5
[6,] a 2 3 6 NA
[7,] b 1 1 7 8
[8,] b 1 2 8 11
[9,] b 1 3 9 NA
[10,] b 2 1 10 8
[11,] b 2 2 11 11
[12,] b 2 3 12 NA
Как вы можете видеть, это немного уродливо в этом случае (но будет быстро). Он планировал добавить drop
, который позволит избежать бит [[3]]
, и, возможно, мы могли бы предоставить способ сообщить [.data.table
оценить i
при вызове области (то есть без самостоятельного присоединения), что позволило бы избежать бит JT=
который необходим здесь, потому что ID
находится как в agg
, так и DT
.
keyby
был добавлен в v1.8.0 в R-Forge, чтобы избежать необходимости в setkey
.
Ответ 2
Несколько более быстрая альтернатива итерации по строкам будет решением, которое использует векторию.
R> d <- data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12))
R> d
ID T1 T2 Data1
1 a 1 1 1
2 a 1 2 2
3 a 1 3 3
4 a 2 1 4
5 a 2 2 5
6 a 2 3 6
7 b 1 1 7
8 b 1 2 8
9 b 1 3 9
10 b 2 1 10
11 b 2 2 11
12 b 2 3 12
R> rowfunction <- function(i) with(d, mean(Data1[which(T1==T2[i] & ID==ID[i])]))
R> d$Data2 <- sapply(1:nrow(d), rowfunction)
R> d
ID T1 T2 Data1 Data2
1 a 1 1 1 2
2 a 1 2 2 5
3 a 1 3 3 NaN
4 a 2 1 4 2
5 a 2 2 5 5
6 a 2 3 6 NaN
7 b 1 1 7 8
8 b 1 2 8 11
9 b 1 3 9 NaN
10 b 2 1 10 8
11 b 2 2 11 11
12 b 2 3 12 NaN
Кроме того, я бы предпочел предварительно обработать данные, прежде чем получить их в R. I.e. если вы извлекаете данные с SQL-сервера, может быть лучшим выбором, чтобы сервер вычислил средние значения, так как это, скорее всего, будет лучше работать в этом.
R на самом деле не очень хорош при хрустах чисел по нескольким причинам. Но это отлично, когда вы делаете статистику по уже предварительно обработанным данным.
Ответ 3
Использование tapply и часть другого недавнего сообщения:
DF = data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12))
EDIT: На самом деле большая часть исходной функции избыточна и предназначена для чего-то другого. Здесь упрощено:
ansMat <- tapply(DF$Data1, DF[, c("ID", "T1")], mean)
i <- cbind(match(DF$ID, rownames(ansMat)), match(DF$T2, colnames(ansMat)))
DF<-cbind(DF,Data2 = ansMat[i])
# ansMat<-tapply(seq_len(nrow(DF)), DF[, c("ID", "T1")], function(x) {
# curSub <- DF[x, ]
# myIndex <- which(DF$T2 == curSub$T1 & DF$ID == curSub$ID)
# meanData1 <- mean(curSub$Data1)
# return(meanData1 = meanData1)
# })
Трюк делал ответ на ID и T1 вместо ID и T2. Что-нибудь более быстрое?