文字列ベクトル入力を使用して、dplyrの複数の列でグループ化


157

私はplyrの理解をdplyrに変換しようとしていますが、複数の列でグループ化する方法がわかりません。

# make data with weird column names that can't be hard coded
data = data.frame(
  asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

# plyr - works
ddply(data, columns, summarize, value=mean(value))

# dplyr - raises error
data %.%
  group_by(columns) %.%
  summarise(Value = mean(value))
#> Error in eval(expr, envir, enclos) : index out of bounds

plyrの例をdplyr-esque構文に変換するために何が欠けていますか?

Edit 2017:Dplyrが更新されたため、よりシンプルなソリューションを利用できます。現在選択されている回答をご覧ください。


3
トップグーグルだったので、ここに来ました。あなたは使用することができgroup_by_、今で説明vignette("nse")
ジェームズOwers

3
@kungfujam:列のペアではなく、最初の列のみでグループ化されているように見える
sharoz

1
を使用する必要があります.dots。:ここでは解決策は@hadleyさんから以下の回答適応していますdf %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
ジェームズOwers

1
以下の回答に完全なコードを入力してください
James Owers、2015年

1
コメントの回答で誰かが指摘したように、目的はハードコードされた列名を必要としないことです。
sharoz

回答:


52

この質問が投稿されたため、dplyrはgroup_byドキュメントはこちら)のスコープバージョンを追加しました。これにより、で使用するのと同じ関数を次のように使用できますselect

data = data.frame(
    asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

library(dplyr)
df1 <- data %>%
  group_by_at(vars(one_of(columns))) %>%
  summarize(Value = mean(value))

#compare plyr for reference
df2 <- plyr::ddply(data, columns, plyr::summarize, value=mean(value))
table(df1 == df2, useNA = 'ifany')
## TRUE 
##  27 

あなたの例の質問からの出力は期待通りです(上記のplyrとの比較と以下の出力を参照):

# A tibble: 9 x 3
# Groups:   asihckhdoydkhxiydfgfTgdsx [?]
  asihckhdoydkhxiydfgfTgdsx a30mvxigxkghc5cdsvxvyv0ja       Value
                     <fctr>                    <fctr>       <dbl>
1                         A                         A  0.04095002
2                         A                         B  0.24943935
3                         A                         C -0.25783892
4                         B                         A  0.15161805
5                         B                         B  0.27189974
6                         B                         C  0.20858897
7                         C                         A  0.19502221
8                         C                         B  0.56837548
9                         C                         C -0.22682998

dplyr::summarize一度にグループ化されるのは1つの層だけなので、結果として生成されるティブルでいくつかのグループ化がまだ行われていることに注意してください(後でサプライズが発生する可能性があります)。予期しないグループ化動作から完全に安全にしたい場合は、%>% ungroup要約した後、いつでもパイプラインに追加できます。


0.7.0いくつかの列で見積り/見積り解除システムも利用できるように更新しますか?
JelenaČuklina

4
.dots引数を次のgroup_by()ように使用することもできますdata %>% group_by(.dots = columns) %>% summarize(value = mean(value))
ポールルージュ

one_of()ここで何かするようにという呼びかけはありますか?式はへの呼び出しでラップされるため、このコンテキストでは冗長であると思いますvars()
ノワ

@Khashirはい、この答えはまだ機能します@knowahそうですone_of()、このコンテキストでは呼び出しは冗長です
エンピロマンサー

1
@Sos select構文を使用して複数の列に関数を適用するには、新しいacross関数を参照してください:dplyr.tidyverse.org/reference/across.htmlこの場合、次のようになりますsummarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer

102

コードを完全に記述するために、新しい構文を使用したHadleyの回答の更新を次に示します。

library(dplyr)

df <-  data.frame(
    asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# Columns you want to group by
grp_cols <- names(df)[-3]

# Convert character vector to list of symbols
dots <- lapply(grp_cols, as.symbol)

# Perform frequency counts
df %>%
    group_by_(.dots=dots) %>%
    summarise(n = n())

出力:

Source: local data frame [9 x 3]
Groups: asihckhdoydk

  asihckhdoydk a30mvxigxkgh  n
1            A            A 10
2            A            B 10
3            A            C 13
4            B            A 14
5            B            B 10
6            B            C 12
7            C            A  9
8            C            B 12
9            C            C 10

1
これはまだ列名をハードコーディングしているようですが、代わりに数式で使用しています。質問のポイントはとても入力する必要がないために、文字列を使用する方法ですasihckhdoydk...
グレゴール・トーマス

1
を使用dots <- lapply(names(df)[-3], function(x) as.symbol(x))して.dots引数を作成するために更新されたソリューションを持っている
James Owers、2015年

4
これらの答えを整理すること.dots=は、重要なステップでした。誰かがそれがgroup_by通話で必要とされる理由について適切なハンドルを持っている場合、この回答を編集できますか?現在、それは少し不可解です。
Andrew

12
vignette("nse")許容される引用の3つの方法があることを示します:数式、引用、文字。どの環境から取得するかを心配しているのでない限り、おそらくAgroup_by_(.dots=grp_cols)
Bフリードマンの

58

dplyrでのこれのサポートは現在かなり弱いですが、最終的には構文は次のようになると思います。

df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja"))

しかし、それはおそらくしばらくはないでしょう(私はすべての結果を検討する必要があるためです)。

regroup()それまでは、シンボルのリストを受け取るを使用できます。

library(dplyr)

df <-  data.frame(
  asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

df %.%
  regroup(list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %.%
  summarise(n = n())

列名の文字ベクトルがある場合は、lapply()and を使用してそれらを正しい構造に変換できますas.symbol()

vars <- setdiff(names(df), "value")
vars2 <- lapply(vars, as.symbol)

df %.% regroup(vars2) %.% summarise(n = n())

6
as.symbolそれを解決します。ありがとう!開発に役立つ場合:このシナリオは私にとって非常に一般的なものです。他の変数のすべての組み合わせで数値結果を集計します。
sharoz 14年

明らかにこれはこの特定の例でのみ機能し、他の例では機能しません。
Paulo E. Cardoso 14

3
私はもともとこれを答えとしてマークしましたが、dplyrを更新するとkungfujamの答えが機能します。
sharoz

regroupも非推奨です(少なくともバージョン0.4.3以降)。
Berk U.16年

27

の列の文字列指定は、アンダースコアで終わる名前dplyrdplyr関数のバリアントを通じてサポートされるようになりました。たとえば、group_by関数に対応して、group_by_文字列引数を取る関数があります。このビネットは、これらの関数の構文を詳細に説明しています。

次のスニペットは、@ sharozが最初に提起した問題を完全に解決します(.dots引数を書き出す必要があることに注意してください)。

# Given data and columns from the OP

data %>%
    group_by_(.dots = columns) %>%
    summarise(Value = mean(value))

(現在、dplyrは%>%演算子を使用しており、%.%非推奨であることに注意してください)。


17

dplyrが文字列引数を完全にサポートするまでは、おそらくこの要旨が役に立ちます。

https://gist.github.com/skranz/9681509

これには、文字列引数を使用するs_group_by、s_mutate、s_filterなどのラッパー関数が多数含まれています。それらを通常のdplyr関数と混在させることができます。例えば

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)

11

これは、文字ベクトルとしてではなく、オブジェクト(そうではありませんが...)を渡す場合に機能します。

df %.%
    group_by(asdfgfTgdsx, asdfk30v0ja) %.%
    summarise(Value = mean(value))

> df %.%
+   group_by(asdfgfTgdsx, asdfk30v0ja) %.%
+   summarise(Value = mean(value))
Source: local data frame [9 x 3]
Groups: asdfgfTgdsx

  asdfgfTgdsx asdfk30v0ja        Value
1           A           C  0.046538002
2           C           B -0.286359899
3           B           A -0.305159419
4           C           A -0.004741504
5           B           B  0.520126476
6           C           C  0.086805492
7           B           C -0.052613078
8           A           A  0.368410146
9           A           B  0.088462212

どこdfにいdataたの?

?group_by 言う:

 ...: variables to group by. All tbls accept variable names, some
      will also accept functons of variables. Duplicated groups
      will be silently dropped.

これは、名前の文字バージョンではなく、でそれらをどのように参照するかを意味すると解釈しfoo$barます。barここでは引用されていません。それとも、式中の変数を参照したいですか:foo ~ bar

@Arunはまた、次のことができると述べています。

df %.%
    group_by("asdfgfTgdsx", "asdfk30v0ja") %.%
    summarise(Value = mean(value))

ただし、評価されていないものをデータオブジェクトの変数の名前として渡すことはできません。

これは、Hadleyが...引数を介して渡したものを検索するために使用している内部メソッドが原因であると思います。


1
@アルンありがとうございます。私はそれに気づかなかったが、それも理にかなっている。この点について、あなたとあなたのコメントを引用してメモを追加しました。
Gavin Simpson

4
残念ながら、列名のハードコーディングに頼ることはできません。私はそれらを指定せずにこれをやろうとしています。
sharoz 14年

4
data = data.frame(
  my.a = sample(LETTERS[1:3], 100, replace=TRUE),
  my.b = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

group_by(data,newcol=paste(my.a,my.b,sep="_")) %>% summarise(Value=mean(value))

4

ここで回答から欠落している(小さな)ケースの1つは、明示的にしたかったのですが、グループ化する変数がパイプラインの途中で動的に生成される場合です。

library(wakefield)
df_foo = r_series(rnorm, 10, 1000)
df_foo %>% 
  # 1. create quantized versions of base variables
  mutate_each(
    funs(Quantized = . > 0)
  ) %>% 
  # 2. group_by the indicator variables
  group_by_(
    .dots = grep("Quantized", names(.), value = TRUE)
    ) %>% 
  # 3. summarize the base variables
  summarize_each(
    funs(sum(., na.rm = TRUE)), contains("X_")
  )

これは基本的にgrep、と組み合わせて使用してgroup_by_(.dots = ...)これを実現する方法を示しています。


3

.dots引数をdplyr::group_by関数への文字ベクトル入力として使用する一般的な例:

iris %>% 
    group_by(.dots ="Species") %>% 
    summarise(meanpetallength = mean(Petal.Length))

または、グループ化変数のハードコーディングされた名前なし(OPからの質問):

iris %>% 
    group_by(.dots = names(iris)[5]) %>% 
    summarise_at("Petal.Length", mean)

OPの例では:

data %>% 
    group_by(.dots =names(data)[-3]) %>% 
    summarise_at("value", mean)

代名詞、準引用、定理、およびtidyevalを説明するプログラミングに関するdplyrビネットも参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.