web-dev-qa-db-ja.com

R:データフレームの行を取得して整数を返す関数を使用した行ごとのdplyr :: mutate

カスタム関数を使用して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)を与えると、CDexcludeで言及されていないもの)の予想される合計が得られます。つまり、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,]))
8
user3375672

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)
_
8
konvas

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)
_

enter image description here

purrrlyrアプローチは、dorowwiseまたはgroup_by(1:n())を組み合わせて使用​​するアプローチよりも高速であることがわかります(@konvasの回答を参照)。sapplyアプローチと同等です。ただし、パッケージは確かに最も直感的ではありません。標準のpurrrのアプローチは最も遅いようですが、おそらく操作が簡単です。異なるユーザー定義関数が速度の順序を変更する場合があります。

6
Dave Gruenewald