グループで最初の行を選択します


87

このようなデータフレームから

test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10

> test
    id string
 1   1      A
 2   1      F
 3   2      B
 4   2      G
 5   3      C
 6   3      H
 7   4      D
 8   4      I
 9   5      E
 10  5      J

各ID /文字列ペアの最初の行で新しいものを作成したいと思います。sqldfがその中のRコードを受け入れた場合、クエリは次のようになります。

res <- sqldf("select id, min(rownames(test)), string 
              from test 
              group by id, string")

> res
    id string
 1   1      A
 3   2      B
 5   3      C
 7   4      D
 9   5      E

次のような新しい列を作成する以外の解決策はありますか

test$row <- rownames(test)

min(row)で同じsqldfクエリを実行しますか?



1
@マシュー、私の質問は古いです。
dmvianna 2014

2
あなたの質問は1歳で、他の質問は4歳ですよね?この質問には非常に多くの重複があります
マシュー

@Matthew申し訳ありませんが、日付を読み間違えたに違いありません。
dmvianna 2014

回答:


120

duplicatedこれを非常に迅速に行うために使用できます。

test[!duplicated(test$id),]

スピードフリークのためのベンチマーク:

ju <- function() test[!duplicated(test$id),]
gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1))
gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
jply <- function() ddply(test,.(id),function(x) head(x,1))
jdt <- function() {
  testd <- as.data.table(test)
  setkey(testd,id)
  # Initial solution (slow)
  # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
  # Faster options :
  testd[!duplicated(id)]               # (1)
  # testd[, .SD[1L], by=key(testd)]    # (2)
  # testd[J(unique(id)),mult="first"]  # (3)
  # testd[ testd[,.I[1L],by=id] ]      # (4) needs v1.8.3. Allows 2nd, 3rd etc
}

library(plyr)
library(data.table)
library(rbenchmark)

# sample data
set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]

benchmark(ju(), gs1(), gs2(), jply(), jdt(),
    replications=5, order="relative")[,1:6]
#     test replications elapsed relative user.self sys.self
# 1   ju()            5    0.03    1.000      0.03     0.00
# 5  jdt()            5    0.03    1.000      0.03     0.00
# 3  gs2()            5    3.49  116.333      2.87     0.58
# 2  gs1()            5    3.58  119.333      3.00     0.58
# 4 jply()            5    3.69  123.000      3.11     0.51

もう一度試してみましょう。ただし、最初のヒートからの候補者だけで、より多くのデータとより多くのレプリケーションを使用します。

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
benchmark(ju(), jdt(), order="relative")[,1:6]
#    test replications elapsed relative user.self sys.self
# 1  ju()          100    5.48    1.000      4.44     1.00
# 2 jdt()          100    6.92    1.263      5.70     1.15

勝者:system.time(dat3 [!duplicated(dat3 $ id)、])ユーザーシステム経過0.07 0.00 0.07
dmvianna 2012年

2
@dmvianna:インストールしていないので、気になりませんでした。:)
Joshua Ulrich

data.tableコードが可能な限り効率的であると確信していますか?そのツールから最高のパフォーマンスを引き出す能力に自信がありません。
joran 2012年

2
また、data.tableのベンチマークを行う場合は、キーイングを基本呼び出しにIDによる順序付けを含める必要があると思います。
mnel 2012年

1
@JoshuaUlrichもう1つの質問:最初の文が必要なのはなぜですか。つまり、データが既に並べ替えられているという仮定です。!duplicated(x)ソートされていなくても、各グループの最初のグループを検索します、iiuc。
Matt Dowle 2012年

38

私はdplyrアプローチを好みます。

group_by(id) 続いていずれか

  • filter(row_number()==1) または
  • slice(1) または
  • slice_head(1) #(dplyr => 1.0)
  • top_n(n = -1)
    • top_n()内部的にランク関数を使用します。ネガティブはランクの一番下から選択します。

場合によっては、group_byの後にIDを配置する必要があります。

library(dplyr)

# using filter(), top_n() or slice()

m1 <-
test %>% 
  group_by(id) %>% 
  filter(row_number()==1)

m2 <-
test %>% 
  group_by(id) %>% 
  slice(1)

m3 <-
test %>% 
  group_by(id) %>% 
  top_n(n = -1)

3つのメソッドすべてが同じ結果を返します

# A tibble: 5 x 2
# Groups:   id [5]
     id string
  <int> <fct> 
1     1 A     
2     2 B     
3     3 C     
4     4 D     
5     5 E

2
slice同様に叫ぶ価値があります。slice(x)のショートカットですfilter(row_number() %in% x)
グレゴールトーマス

とてもエレガント。これが機能data.tableするdata.frameために、なぜ私をに変換しなければならないのか知っていますか?
JamesHirschorn19年

@JamesHirschorn私はすべての違いの専門家ではありません。ただしdata.table、から継承するdata.frameため、多くの場合、でdplyrコマンドを使用できます data.table。上記の例testは、たとえば、がの場合にも機能しdata.tableます。例えば参照stackoverflow.com/questions/13618488/...深いexplanantionのために
クレステン

これはそれを行うための整然とした方法であり、ご覧のとおり、data.frameは実際にはここでは少しずつです。ggplot2も同様の方法で構築されているため、個人的には常にtibblesを使用することをお勧めします。
ガリーニ

17

どうですか

DT <- data.table(test)
setkey(DT, id)

DT[J(unique(id)), mult = "first"]

編集

data.tablesキーで最初の行を返す独自のメソッドもあります

jdtu <- function() unique(DT)

testベンチマーク外で注文する場合は、ベンチマークからsetkeydata.table変換を削除することもできると思います(setkeyは基本的にidでソートされるため、と同じですorder)。

set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]
DT <- data.table(DT, key = 'id')
ju <- function() test[!duplicated(test$id),]

jdt <- function() DT[J(unique(id)),mult = 'first']


 library(rbenchmark)
benchmark(ju(), jdt(), replications = 5)
##    test replications elapsed relative user.self sys.self 
## 2 jdt()            5    0.01        1      0.02        0        
## 1  ju()            5    0.05        5      0.05        0         

そしてより多くのデータで

**独自の方法で編集**

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
DT <- data.table(test, key = 'id')
       test replications elapsed relative user.self sys.self 
2  jdt()            5    0.09     2.25      0.09     0.00    
3 jdtu()            5    0.04     1.00      0.05     0.00      
1   ju()            5    0.22     5.50      0.19     0.03        

ユニークな方法はここで最速です。


4
キーを設定する必要もありません。unique(DT,by="id")直接動作
マシュー

FYIのようにdata.tableバージョン> = 1.9.8、デフォルトbyの引数がuniqueあるby = seq_along(x)代わりに、以前のデフォルトの、(すべての列)by = key(x)
IceCreamToucan

12

簡単なddplyオプション:

ddply(test,.(id),function(x) head(x,1))

速度が問題になる場合は、次の方法で同様のアプローチをとることができますdata.table

testd <- data.table(test)
setkey(testd,id)
testd[,.SD[1],by = key(testd)]

または、これはかなり速いかもしれません:

testd[testd[, .I[1], by = key(testd]$V1]

驚いたことに、sqldfの方が高速です:1.77 0.13 1.92 vs 10.53 0.00 10.79 with data.table
dmvianna

3
@dmvianna必ずしもdata.tableを数えるとは限りません。私はそのツールの専門家ではないので、私のdata.tableコードはそれを実行するための最も効率的な方法ではないかもしれません。
joran 2012年

私はこれを時期尚早に賛成した。大きなdata.tableで実行したとき、それは途方もなく遅く、機能しませんでした。その後、行数は同じでした。
JamesHirschorn19年

@JamesHirachorn私はずっと前にこれを書きました、パッケージは大きく変更されました、そして私はほとんどdata.tableを使用しません。そのパッケージでこれを行う正しい方法を見つけた場合は、それを改善するための編集を提案してください。
joran

8

さて、のためdplyrに、別個のカウンターを追加します。

df %>%
    group_by(aa, bb) %>%
    summarise(first=head(value,1), count=n_distinct(value))

グループを作成し、グループ内に要約します。

データが数値の場合は、次を使用できます:の代わりに
first(value)[もありますlast(value)]head(value, 1)

参照:http//cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html

フル:

> df
Source: local data frame [16 x 3]

   aa bb value
1   1  1   GUT
2   1  1   PER
3   1  2   SUT
4   1  2   GUT
5   1  3   SUT
6   1  3   GUT
7   1  3   PER
8   2  1   221
9   2  1   224
10  2  1   239
11  2  2   217
12  2  2   221
13  2  2   224
14  3  1   GUT
15  3  1   HUL
16  3  1   GUT

> library(dplyr)
> df %>%
>   group_by(aa, bb) %>%
>   summarise(first=head(value,1), count=n_distinct(value))

Source: local data frame [6 x 4]
Groups: aa

  aa bb first count
1  1  1   GUT     2
2  1  2   SUT     2
3  1  3   SUT     3
4  2  1   221     3
5  2  2   217     3
6  3  1   GUT     2

この回答はかなり古いものです-これをdplyr行うには、含まれるすべての列に対してステートメントを記述する必要がない、より良い方法があります(たとえば、以下のアトムマンの回答を参照してください). Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would use first(value) `vs head(value)(または単にvalue[1]
Gregorトーマス

7

(1)SQLiteにはrowid疑似列が組み込まれているため、これは機能します。

sqldf("select min(rowid) rowid, id, string 
               from test 
               group by id")

与える:

  rowid id string
1     1  1      A
2     3  2      B
3     5  3      C
4     7  4      D
5     9  5      E

(2)sqldfそれ自体にもrow.names=引数があります:

sqldf("select min(cast(row_names as real)) row_names, id, string 
              from test 
              group by id", row.names = TRUE)

与える:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

(3)上記の2つの要素を混合する3番目の選択肢はさらに良いかもしれません:

sqldf("select min(rowid) row_names, id, string 
               from test 
               group by id", row.names = TRUE)

与える:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

これらの3つはすべて、SQLのSQLite拡張機能に依存しており、minまたはを使用するとmax、同じ行から他の列が選択されることが保証されていることに注意してください。(他のSQLベースのデータベースでは保証されない場合があります。)


ありがとう!これは、複数の集計関数を使用して集計ステップの最初/最後の要素を取得する(つまり、この変数の最初の要素を取得する、その変数を合計するなど)ことに一般化できるため、受け入れられた回答IMOよりもはるかに優れています。
Bridgeburners 2015年

4

ベースRオプションがあるsplit()- - lapply()do.call()イディオム

> do.call(rbind, lapply(split(test, test$id), head, 1))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

より直接的なオプションはlapply()[関数です。

> do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

呼び出し1, )の最後のコンマスペースは、最初の行とすべての列を選択するための呼び出しと同等であるため、不可欠です。lapply()[1, ]


これは非常に遅かった、ギャビン:ユーザーシステムが経過した91.84 6.02 101.10
dmvianna

データフレームを含むものはすべてになります。それらの有用性には代償が伴います。したがって、たとえばdata.tableです。
Gavin Simpson

私の弁護とRの弁護では、あなたは質問の効率について何も言及しませんでした。多くの場合、使いやすさがある機能。少なくともdata.tableをサポートする次のバージョンまでは、「遅い」プライの人気を目撃してください。
Gavin Simpson

1
同意する。私はあなたを侮辱するつもりはありませんでした。私は、ジョシュア・ウルリッヒの方法@だったこと、しかし、検索をした両方の高速かつ簡単に。:7)
dmvianna 2012年

謝る必要はなく、侮辱とは思わなかった。効率性を主張することなく提供されていることを指摘していました。このStackOverflow Q&Aは、あなたの利益のためだけでなく、同様の問題を抱えているためにあなたの質問に出くわした他のユーザーの利益のためであることを忘れないでください。
Gavin Simpson
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.