データテーブルの行ごとの演算子に関する他の投稿があります。 単純すぎる または 特定のシナリオ を解決する
ここでの私の質問はより一般的です。 dplyrを使用するソリューションがあります。私は遊んでみましたが、data.table構文を使用して同等のソリューションを見つけることができませんでした。 dplyrバージョンと同じ結果を再現するエレガントなdata.tableソリューションを提案できますか?
EDIT 1:実際のデータセットで推奨されるソリューションのベンチマークの概要(10MB、73000行、24の数値列で行われた統計)。ベンチマークの結果は主観的です。ただし、経過時間は一貫して再現可能です。
| Solution By | Speed compared to dplyr |
|-------------|-----------------------------|
| Metrics v1 | 4.3 times SLOWER (use .SD) |
| Metrics v2 | 5.6 times FASTER |
| ExperimenteR| 15 times FASTER |
| Arun v1 | 3 times FASTER (Map func)|
| Arun v2 | 3 times FASTER (foo func)|
| Ista | 4.5 times FASTER |
EDIT 2:翌日にNACount列を追加しました。これが、このコラムがさまざまな寄稿者によって提案されたソリューションに含まれていない理由です。
データ設定
library(data.table)
dt <- data.table(ProductName = c("Lettuce", "Beetroot", "Spinach", "Kale", "Carrot"),
Country = c("CA", "FR", "FR", "CA", "CA"),
Q1 = c(NA, 61, 40, 54, NA), Q2 = c(22, 8, NA, 5, NA),
Q3 = c(51, NA, NA, 16, NA), Q4 = c(79, 10, 49, NA, NA))
# ProductName Country Q1 Q2 Q3 Q4
# 1: Lettuce CA NA 22 51 79
# 2: Beetroot FR 61 8 NA 10
# 3: Spinach FR 40 NA NA 49
# 4: Kale CA 54 5 16 NA
# 5: Carrot CA NA NA NA NA
dplyr + rowwise()を使用した解決策
library(dplyr) ; library(magrittr)
dt %>% rowwise() %>%
transmute(ProductName, Country, Q1, Q2, Q3, Q4,
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
NAcnt= sum(is.na(c(Q1, Q2, Q3, Q4))))
# ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM NAcnt
# 1 Lettuce CA NA 22 51 79 50.66667 22 79 152 1
# 2 Beetroot FR 61 8 NA 10 26.33333 8 61 79 1
# 3 Spinach FR 40 NA NA 49 44.50000 40 49 89 2
# 4 Kale CA 54 5 16 NA 25.00000 5 54 75 1
# 5 Carrot CA NA NA NA NA NaN Inf -Inf 0 4
error with data.table(行ごとではなく列全体を計算します)
dt[, .(ProductName, Country, Q1, Q2, Q3, Q4,
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
NAcnt= sum(is.na(c(Q1, Q2, Q3, Q4))))]
# ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM NAcnt
# 1: Lettuce CA NA 22 51 79 35.90909 5 79 395 9
# 2: Beetroot FR 61 8 NA 10 35.90909 5 79 395 9
# 3: Spinach FR 40 NA NA 49 35.90909 5 79 395 9
# 4: Kale CA 54 5 16 NA 35.90909 5 79 395 9
# 5: Carrot CA NA NA NA NA 35.90909 5 79 395 9
ALMOSTソリューションですが、より複雑で欠落しているQ1、Q2、Q3、Q4出力列
dtmelt <- reshape2::melt(dt, id=c("ProductName", "Country"),
variable.name="Quarter", value.name="Qty")
dtmelt[, .(AVG = mean(Qty, na.rm=TRUE),
MIN = min (Qty, na.rm=TRUE),
MAX = max (Qty, na.rm=TRUE),
SUM = sum (Qty, na.rm=TRUE),
NAcnt= sum(is.na(Qty))), by = list(ProductName, Country)]
# ProductName Country AVG MIN MAX SUM NAcnt
# 1: Lettuce CA 50.66667 22 79 152 1
# 2: Beetroot FR 26.33333 8 61 79 1
# 3: Spinach FR 44.50000 40 49 89 2
# 4: Kale CA 25.00000 5 54 75 1
# 5: Carrot CA NaN Inf -Inf 0 4
matrixStats
パッケージの効率的な行ごとの関数を使用できます。
_library(matrixStats)
dt[, `:=`(MIN = rowMins(as.matrix(.SD), na.rm=T),
MAX = rowMaxs(as.matrix(.SD), na.rm=T),
AVG = rowMeans(.SD, na.rm=T),
SUM = rowSums(.SD, na.rm=T)), .SDcols=c(Q1, Q2,Q3,Q4)]
dt
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX AVG SUM
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79
# 3: Spinach FR 40 NA 79 49 40 79 56.00000 168
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0
_
500000行のデータセットの場合(CRANの_data.table
_を使用)
_dt <- rbindlist(lapply(1:100000, function(i)dt))
system.time(dt[, `:=`(MIN = rowMins(as.matrix(.SD), na.rm=T),
MAX = rowMaxs(as.matrix(.SD), na.rm=T),
AVG = rowMeans(.SD, na.rm=T),
SUM = rowSums(.SD, na.rm=T)), .SDcols=c("Q1", "Q2","Q3","Q4")])
# user system elapsed
# 0.089 0.004 0.093
_
rowwise
(またはby=1:nrow(dt)
)は、_for loop
_の「ユーフェミズム」です。
_library(dplyr) ; library(magrittr)
system.time(dt %>% rowwise() %>%
transmute(ProductName, Country, Q1, Q2, Q3, Q4,
MIN = min (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
MAX = max (c(Q1, Q2, Q3, Q4), na.rm=TRUE),
AVG = mean(c(Q1, Q2, Q3, Q4), na.rm=TRUE),
SUM = sum (c(Q1, Q2, Q3, Q4), na.rm=TRUE)))
# user system elapsed
# 80.832 0.111 80.974
system.time(dt[, `:=`(AVG= mean(as.numeric(.SD),na.rm=TRUE),MIN = min(.SD, na.rm=TRUE),MAX = max(.SD, na.rm=TRUE),SUM = sum(.SD, na.rm=TRUE)),.SDcols=c("Q1", "Q2","Q3","Q4"),by=1:nrow(dt)] )
# user system elapsed
# 141.492 0.196 141.757
_
by=1:nrow(dt)
を使用して、data.table
で行ごとの演算を実行します
library(data.table)
dt[, `:=`(AVG= mean(as.numeric(.SD),na.rm=TRUE),MIN = min(.SD, na.rm=TRUE),MAX = max(.SD, na.rm=TRUE),SUM = sum(.SD, na.rm=TRUE)),.SDcols=c(Q1, Q2,Q3,Q4),by=1:nrow(dt)]
ProductName Country Q1 Q2 Q3 Q4 AVG MIN MAX SUM
1: Lettuce CA NA 22 51 79 50.66667 22 79 152
2: Beetroot FR 61 8 NA 10 26.33333 8 61 79
3: Spinach FR 40 NA 79 49 56.00000 40 79 168
4: Kale CA 54 5 16 NA 25.00000 5 54 75
5: Carrot CA NA NA NA NA NaN Inf -Inf 0
Warning messages:
1: In min(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) :
no non-missing arguments to min; returning Inf
2: In max(c(NA_real_, NA_real_, NA_real_, NA_real_), na.rm = TRUE) :
no non-missing arguments to max; returning -Inf
5行目では、max、sum、min、maxの計算を行っていないため、警告メッセージが表示されました。たとえば、以下を参照してください。
min(c(NA,NA,NA,NA),na.rm=TRUE)
[1] Inf
Warning message:
In min(c(NA, NA, NA, NA), na.rm = TRUE) :
no non-missing arguments to min; returning Inf
もう1つの方法(ただし、毎回na.omit()
が呼び出され、多くのメモリ割り当ても行われるため、それほど効率的ではありません):
_require(data.table)
new_cols = c("MIN", "MAX", "SUM", "AVG")
dt[, (new_cols) := Map(function(x, f) f(x),
list(na.omit(c(Q1,Q2,Q3,Q4))),
list(min, max, sum, mean)),
by = 1:nrow(dt)]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX SUM AVG
# 1: Lettuce CA NA 22 51 79 22 79 152 50.66667
# 2: Beetroot FR 61 8 NA 10 8 61 79 26.33333
# 3: Spinach FR 40 NA 79 49 40 79 168 56.00000
# 4: Kale CA 54 5 16 NA 5 54 75 25.00000
# 5: Carrot CA NA NA NA NA Inf -Inf 0 NaN
_
しかし、前述したように、colwise()
とrowwise()
が実装されると、これははるかに単純になります。この場合の構文は次のようになります。
_dt[, rowwise(.SD, list(MIN=min, MAX=max, SUM=sum, AVG=mean), na.rm=TRUE), by = 1:nrow(dt)]
# `by = ` is really not necessary in this case.
_
この場合はさらに簡単です:
_rowwise(dt, list(...), na.rm=TRUE)
_
編集:
別のバリエーション:
_myNACount <- function(x, ...) length(attributes(x)$na.action)
foo <- function(x, ...) {
funs = c(min, max, mean, sum, myNACount)
lapply(funs, function(f) f(x, ...))
}
dt[, (new_cols) := foo(na.omit(c(Q1, Q2, Q3, Q4)), na.rm=TRUE), by=1:nrow(dt)]
# ProductName Country Q1 Q2 Q3 Q4 MIN MAX SUM AVG NAs
# 1: Lettuce CA NA 22 51 79 22 79 50.66667 152 1
# 2: Beetroot FR 61 8 NA 10 8 61 26.33333 79 1
# 3: Spinach FR 40 NA NA 49 40 49 44.50000 89 2
# 4: Kale CA 54 5 16 NA 5 54 25.00000 75 1
# 5: Carrot CA NA NA NA NA Inf -Inf NaN 0 4
_
apply
関数を使用して、行単位の計算を実行できます。関数を個別に定義することで、物事をよりクリーンに保つことができます。
dstats <- function(x){
c(mean(x,na.rm=TRUE),
min(x, na.rm=TRUE),
max(x, na.rm=TRUE),
sum(x, na.rm=TRUE))
}
これで、関数をdata.tableの行に適用できます。
(dt[,
c("AVG", "MIN", "MAX", "SUM") := data.frame(t(apply(.SD, 1, dstats))),
.SDcols=c("Q1", "Q2","Q3","Q4"),
with = FALSE])
[.data.table
を使用してこれを行う唯一の利点は、:=
を使用して参照により高速に追加できることです。
これはmatrixStats
ソリューションよりも低速ですが柔軟性が高く、@ ExperimenteRによるdplyr
ソリューションよりも高速で、36秒で計時します(他のメソッドのタイミングは@ExperimenteRのタイミングと同様でした)回答)。