カスタム関数を使用してpipe mutateステートメントを使用しようとしています。私はこれをやや似た SO post に見ましたが、無駄です。次のようなデータフレームがあるとします(ここで、blob
は特定のタスクに関連しない変数ですが、データ全体の一部です):
df <-
data.frame(exclude=c('B','B','D'),
B=c(1,0,0),
C=c(3,4,9),
D=c(1,1,0),
blob=c('fd', 'fs', 'sa'),
stringsAsFactors = F)
変数名を使用する関数があるので、exclude
列の値に基づいていくつかを選択します。 exclude
で指定されていない変数(常に1文字)の合計を計算します。
FUN <- function(df){
sum(df[c('B', 'C', 'D')] [!names(df[c('B', 'C', 'D')]) %in% df['exclude']] )
}
FUN
に単一の行(行1)を与えると、C
とD
(exclude
で言及されていないもの)の予想される合計が得られます。つまり、4:
FUN(df[1,])
Mutateを使用してパイプで同様にするには(結果を変数s
に追加します)。次の2つの試行は機能しません。
df %>% mutate(s=FUN(.))
df %>% group_by(1:n()) %>% mutate(s=FUN(.))
[〜#〜] update [〜#〜]これも意図したとおりに機能しません:
df %>% rowwise(.) %>% mutate(s=FUN(.))
これは機能しますが、dplyrのmutate(およびパイプ)内にはありません。
df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))
dplyr
を使用する場合は、rowwise
と関数FUN
を使用できます。
_df %>%
rowwise %>%
do({
result = as_data_frame(.)
result$s = FUN(result)
result
})
_
同じことは、rowwise
の代わりに_group_by
_を使用して(すでに試したように)達成できますが、do
の代わりにmutate
を使用しても実現できます。
_df %>%
group_by(1:n()) %>%
do({
result = as_data_frame(.)
result$s = FUN(result)
result
})
_
この場合mutate
が機能しないのは、ティブル全体をそれに渡すため、FUN(df)
を呼び出すようなものです。
同じことを行うより効率的な方法は、列の行列を含めてrowSums
を使用することです。
_cols <- c('B', 'C', 'D')
include_mat <- outer(function(x, y) x != y, X = df$exclude, Y = cols)
# or outer(`!=`, X = df$exclude, Y = cols) if it's more readable to you
df$s <- rowSums(df[cols] * include_mat)
_
purrr
アプローチこれには、nest
と_map_dbl
_の組み合わせを使用できます。
_library(tidyverse)
df %>%
rowwise %>%
nest(-blob) %>%
mutate(s = map_dbl(data, FUN)) %>%
unnest
_
それを少し分解してみましょう。まず、rowwise
を使用すると、後続の各関数を適用して、各行に適用する必要がある任意の複雑な操作をサポートできます。
次に、nest
は、FUN
に供給されるデータのリストである新しい列を作成します(tibblesとdata.framesの美しさ!)。このrowwise
を適用しているため、各行には_exclude:D
_の1行のティブルが含まれています。
最後に、_map_dbl
_を使用して、FUN
をこれらの各ティブルにマップします。 _map_dbl
_は、意図した出力が数値(つまりdouble)であるため、他の_map_*
_関数のファミリーに使用されます。
unnest
は、ティブルをより標準的な構造に戻します。
purrrlyr
アプローチpurrrlyr
は、親のdplyr
およびpurrr
ほど「人気」ではないかもしれませんが、その_by_row
_関数には、ここでいくつかのユーティリティがあります。
上記の例では、データフレームdf
とユーザー定義関数FUN
を次のように使用します。
_df %>%
by_row(..f = FUN, .to = "s", .collate = "cols")
_
それでおしまい!あなたに与える:
_# tibble [3 x 6]
exclude B C D blob s
<chr> <dbl> <dbl> <dbl> <chr> <dbl>
1 B 1 3 1 fd 4
2 B 0 4 1 fs 5
3 D 0 9 0 sa 9
_
確かに、構文は少し奇妙ですが、以下がその分解方法です。
..f
_ =各行に適用する関数.to
_ =出力列の名前、この場合はs
.collate
_ =リスト、行、または列ごとに結果を照合する方法。 FUN
には出力が1つしかないため、_"cols"
_または_"rows"
_のどちらを使用しても問題ありません。purrrlyr
の使用の詳細については、 ここ を参照してください...
事前に警告しますが、_by_row
_の機能は気に入っていますが、パフォーマンスを向上させるための最良の方法とは限りません。 purrr
の方が直感的ですが、速度がかなり低下します。次のmicrobenchmark
テストを参照してください。
_library(microbenchmark)
mbm <- microbenchmark(
purrr.test = df %>% rowwise %>% nest(-blob) %>%
mutate(s = map_dbl(data, FUN)) %>% unnest,
purrrlyr.test = df %>% by_row(..f = FUN, .to = "s", .collate = "cols"),
rowwise.test = df %>%
rowwise %>%
do({
result = as_tibble(.)
result$s = FUN(result)
result
}),
group_by.test = df %>%
group_by(1:n()) %>%
do({
result = as_tibble(.)
result$s = FUN(result)
result
}),
sapply.test = {df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))},
times = 1000
)
autoplot(mbm)
_
purrrlyr
アプローチは、do
とrowwise
またはgroup_by(1:n())
を組み合わせて使用するアプローチよりも高速であることがわかります(@konvasの回答を参照)。sapply
アプローチと同等です。ただし、パッケージは確かに最も直感的ではありません。標準のpurrr
のアプローチは最も遅いようですが、おそらく操作が簡単です。異なるユーザー定義関数が速度の順序を変更する場合があります。