İlk satırı gruba göre seçin


87

Bunun gibi bir veri çerçevesinden

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

Her id / string çiftinin ilk satırıyla yeni bir tane oluşturmak istiyorum. Sqldf, içindeki R kodunu kabul ettiyse, sorgu şu şekilde görünebilir:

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

Gibi yeni bir sütun oluşturmanın kısa bir çözümü var mı?

test$row <- rownames(test)

ve aynı sqldf sorgusunu min (satır) ile çalıştırıyor mu?



1
@Matthew, sorum daha eski.
dmvianna

2
Sorunuz 1 yaşında ve diğer soru 4 yaşında değil mi? Bu sorunun pek çok kopyası var
Matthew

@Matthew Üzgünüm, tarihleri ​​yanlış okumuş olmalıyım.
dmvianna

Yanıtlar:


120

Bunu duplicatedçok hızlı yapmak için kullanabilirsiniz .

test[!duplicated(test$id),]

Hız tutkunları için kıyaslamalar:

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

Bunu tekrar deneyelim, ancak ilk etapta sadece yarışmacılar ve daha fazla veri ve daha fazla çoğaltma ile.

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

Kazanan: system.time (dat3 [! Duplicated (dat3 $ id),]) kullanıcı sistemi geçti 0,07 0,00 0,07
dmvianna

2
@dmvianna: Yüklemedim ve onunla uğraşmak istemedim. :)
Joshua Ulrich

Data.table kodumun olabildiğince verimli olduğundan emin miyiz? Bu araçtan en iyi performansı elde etme yeteneğime güvenmiyorum.
joran

2
Ayrıca, data.table'ı kıyaslayacaksanız, keying'e göre sıralamayı baz çağrılarına dahil etmelisiniz.
mnel

1
@JoshuaUlrich Bir soru daha: neden ilk cümle gerekli, yani verilerin zaten sıralandığını varsayalım. !duplicated(x)sıralanmamış olsa bile her grubun ilkini bulur, iiuc.
Matt Dowle

38

Ben dplyr yaklaşımını tercih ediyorum.

group_by(id) ardından biri

  • filter(row_number()==1) veya
  • slice(1) veya
  • slice_head(1) # (dplyr => 1.0)
  • top_n(n = -1)
    • top_n()dahili olarak rank işlevini kullanır. Negatif, sıranın altından seçer.

Bazı durumlarda, group_by'den sonra kimliklerin düzenlenmesi gerekli olabilir.

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)

Her üç yöntem de aynı sonucu döndürür

# 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
Bağırmaya değer slice. slice(x)için bir kısayol filter(row_number() %in% x).
Gregor Thomas

Çok zarif. Eğer kızkardeşimi dönüştürmek için neden biliyor musunuz data.tablebir karşı data.frameişe bunun için?
James Hirschorn

@JamesHirschorn Ben tüm farklılıklar konusunda uzman değilim. Ama data.tabledevralır data.frameböylece birçok durumda bir komutlar dplyr kullanabilirsiniz data.table. Yukarıdaki örnek, örneğin testbir data.table. Daha derin bir açıklama için örneğin stackoverflow.com/questions/13618488/…
Kresten

Bu, bunu yapmanın tam tersi bir yoludur ve gördüğünüz gibi data.frame aslında burada bir tibble. Ben şahsen size her zaman tibbles ile çalışmanızı tavsiye ederim çünkü ggplot2 benzer bir şekilde inşa edilmiştir.
Garini

17

Ne dersin

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

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

Düzenle

data.tablesİlk satırı anahtarla döndüren benzersiz bir yöntem de vardır.

jdtu <- function() unique(DT)

Bence, testkarşılaştırmalı değerlendirme dışında sipariş veriyorsanız , karşılaştırmalı değerlendirmeden setkeyve data.tabledönüşümü de kaldırabilirsiniz (setkey temelde kimliğe göre sıralandığı gibi, aynı şekilde 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         

ve daha fazla veriyle

** Benzersiz yöntemle düzenleyin **

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        

Eşsiz yöntem burada en hızlıdır.


4
Anahtarı ayarlamanıza bile gerek yok. unique(DT,by="id")doğrudan çalışır
Matthew

Bilginize itibariyle data.tablesürümü> = 1.9.8, varsayılan byargümanı uniqueolan by = seq_along(x)yerine önceki varsayılan (tüm sütunlar)by = key(x)
IceCreamToucan

12

Basit bir ddplyseçenek:

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

Hız bir sorunsa, benzer bir yaklaşım şu şekilde alınabilir data.table:

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

veya bu çok daha hızlı olabilir:

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

Şaşırtıcı bir şekilde, sqldf bunu daha hızlı yapıyor: 1.77 0.13 1.92 vs 10.53 0.00 10.79 data.table ile
dmvianna

3
@dmvianna Data.table'ı mutlaka saymam. Bu araç konusunda uzman değilim, bu yüzden data.table kodum bu konuda en verimli yol olmayabilir.
joran

Ben buna erken oy verdim. Onu büyük bir data.table üzerinde çalıştırdığımda gülünç derecede yavaştı ve işe yaramadı: satır sayısı sonradan aynıydı.
James Hirschorn

@JamesHirachorn Bunu uzun zaman önce yazdım, paket çok değişti ve data.table'ı neredeyse hiç kullanmıyorum. Bu paketle bunu yapmanın doğru yolunu bulursanız, daha iyi hale getirmek için bir düzenleme önermekten çekinmeyin.
joran

8

şimdi, dplyrfarklı bir sayaç eklemek için.

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

Gruplar yaratırsınız, gruplar içinde özetler.

Veriler sayısal ise,
first(value)bunun last(value)yerine [vardır ] kullanabilirsinizhead(value, 1)

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

Tam:

> 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

Bu cevap oldukça dplyreskidir - dahil edilecek her sütun için bir ifade yazmayı gerektirmeyen bunu yapmanın daha iyi yolları vardır (örneğin aşağıdaki atomman'ın cevabına bakın) . Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would use ilk (değer) 'vs head(value)(veya sadece value[1])
Gregor Thomas

7

(1) SQLite yerleşik bir rowidsözde sütuna sahiptir, bu nedenle bu çalışır:

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

veren:

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

(2) sqldfKendisinin derow.names= argümanı vardır:

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

veren:

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

(3) Yukarıdaki ikisinin unsurlarını karıştıran üçüncü bir alternatif daha da iyi olabilir:

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

veren:

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

Bunların üçünün de , aynı satırdan diğer sütunların seçilmesiyle sonuçlanmasının garantili olduğu minveya maxkullanımının garanti edildiği SQLite uzantısına dayandığına dikkat edin. (Garanti edilemeyen diğer SQL tabanlı veritabanlarında.)


Teşekkürler! Bu, kabul edilen cevap IMO'sundan çok daha iyidir çünkü birden fazla toplama işlevi kullanarak bir toplama adımında ilk / son öğeyi almak genelleştirilebilir (yani bu değişkenin ilkini alın, o değişkeni toplayın, vb.).
Bridgeburners

4

Temel R seçeneği split()- lapply()- do.call()deyimdir:

> 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

Bir daha doğrudan bir seçenek olan fonksiyonu: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

Çağrının 1, )sonundaki virgül boşluğu , ilk satırı ve tüm sütunları seçmek için çağrı yapmaya eşdeğer lapply()olduğu için önemlidir[1, ] .


Bu çok yavaştı Gavin: kullanıcı sistemi 91,84 6,02 101,10'u geçti
dmvianna

Veri çerçevelerini içeren her şey olacaktır. Hizmetlerinin bir bedeli var. Dolayısıyla örneğin data.table.
Gavin Simpson

benim savunmamda ve R's, soruda verimlilik hakkında hiçbir şey söylemediniz. Genellikle kullanım kolaylığı olan bir özelliktir. En azından data.table desteği olan bir sonraki sürüme kadar "yavaş" olan ply'nin popülerliğine tanık olun.
Gavin Simpson

1
Katılıyorum. Sana hakaret etmek istemedim. Yine de, @ Joshua-Ulrich'in yönteminin hem hızlı hem de kolay olduğunu buldum . : 7)
dmvianna

Özür dilemene gerek yok ve bunu bir hakaret olarak görmedim. Sadece herhangi bir verimlilik iddiası olmadan sunulduğuna işaret ediyordu. Bu Yığın Taşması Soru-Cevap sorusunun yalnızca sizin yararınıza değil, benzer bir sorunu olduğu için sorunuzla karşılaşan diğer kullanıcılar için de olduğunu unutmayın.
Gavin Simpson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.