Soru Karmaşık özet fonksiyonu - R data.table paketi ile çözmek mümkün mü?


Büyük miktarda veriyi (~ 17 milyon satır) analiz eden bazı R betiklerini yeniden yazdım ve bellek verimliliğini data.table paket (ki sadece öğreniyorum!).

Kodun bir kısmı beni biraz şaşırttı. Orijinal çözümümü gönderemiyorum çünkü (1) saçmalık (yavaş!), Ve (2) verilerle ilgili çok nüanslı ve bu soruyu sadece karmaşıklaştıracak.

Bunun yerine, bu oyuncak örneğini yaptım (ve gerçekten bir oyuncak örneği):

ds <- data.table(ID=c(1,1,1,1,2,2,2,3,3,3),
Obs=c(1.5,2.5,0.0,1.25,1.45,1.5,2.5,0.0,1.25,1.45), 
Pos=c(1,3,5,6,2,3,5,2,3,4))

Buna benzer:

    ID  Obs Pos
 1:  1 1.50   1
 2:  1 2.50   3
 3:  1 0.00   5
 4:  1 1.25   6
 5:  2 1.45   2
 6:  2 1.50   3
 7:  2 2.50   5
 8:  3 0.00   2
 9:  3 1.25   3
10:  3 1.45   4

Açıklama kolaylığı için, trenleri gözlemlediğimizi iddia edeceğim (her trenin kendine ait olduğu İD), lineer tek yönlü pistte hareket ederek, belirlenen pozisyonlarda yapılan tren ile ilgili gözlemler (soruya alınmayan bir değer).posburadaki parça boyunca 1-6). Bir trenin, pistin tüm uzunluğunu (belki de 6 no'lu pozisyona gelmeden önce patladı) ve gözlemcinin gözlemlediği bir gözlemi kaçırması beklenmez. Bu pozisyonlar ardışıktır. 4 numaralı trende, fakat 5 numaralı pozisyonda gözlemledik, 4 numaralı pozisyondan geçtiğini biliyoruz.

Yukarıdaki data.table'dan şöyle bir tablo oluşturmam gerekiyor:

   Pos Count
1:   1     3
2:   2     3
3:   3     3
4:   4     3
5:   5     2
6:   6     1

Her benzersiz için nerede Sıra data.table adreslerimde, gözlemin yolda o pozisyonda yapılıp yapılmadığına bakılmaksızın pistte (veya daha ileride) bu pozisyona getirilen trenlerin sayımım var.

Eğer bununla nasıl başa çıkılacağı konusunda herhangi bir fikir veya önerisi varsa, bu çok takdir edilecektir. Ne yazık ki, bu yapılabilir mi bilmek için data.table ile yeterince tanıdık değilim! Ya da çözmek için inanılmaz basit bir sorun olabilir ve ben sadece yavaş :)


18
2018-01-08 06:35


Menşei




Cevaplar:


Büyük soru! Örnek veriler özellikle iyi yapılandırılmış ve iyi açıklanmıştır.

İlk önce bu cevabı göstereceğim, sonra adım adım açıklayacağım.

> ids = 1:3   # or from the data: unique(ds$ID)
> pos = 1:6   # or from the data: unique(ds$Pos)
> setkey(ds,ID,Pos)

> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos]
   Pos N
1:   1 3
2:   2 3
3:   3 3
4:   4 3
5:   5 2
6:   6 1
> 

Bu ayrıca büyük verilerinizde çok verimli olmalı.

Adım adım

İlk önce bir Çapraz Katılmayı (CJ) denedim; Yani her bir tren için her konum için.

> ds[CJ(ids,pos)]
    ID Pos  Obs
 1:  1   1 1.50
 2:  1   2   NA
 3:  1   3 2.50
 4:  1   4   NA
 5:  1   5 0.00
 6:  1   6 1.25
 7:  2   1   NA
 8:  2   2 1.45
 9:  2   3 1.50
10:  2   4   NA
11:  2   5 2.50
12:  2   6   NA
13:  3   1   NA
14:  3   2 0.00
15:  3   3 1.25
16:  3   4 1.45
17:  3   5   NA
18:  3   6   NA

Tren başına 6 satır görüyorum. 3 tren görüyorum. Beklediğim gibi 18 satırım var. anlıyorum NA Bu trenin gözlenmediği yer. İyi. Kontrol. Çapraz katılma çalışıyor gibi görünüyor. Şimdi sorguyu oluşturalım.

Eğer n konumunda bir tren gözlemlenirse, önceki pozisyonları geçmiş olmalı diye yazmıştın. Hemen düşünüyorum roll. Hadi deneyelim.

ds[CJ(ids,pos), roll=TRUE]
    ID Pos  Obs
 1:  1   1 1.50
 2:  1   2 1.50
 3:  1   3 2.50
 4:  1   4 2.50
 5:  1   5 0.00
 6:  1   6 1.25
 7:  2   1   NA
 8:  2   2 1.45
 9:  2   3 1.50
10:  2   4 1.50
11:  2   5 2.50
12:  2   6 2.50
13:  3   1   NA
14:  3   2 0.00
15:  3   3 1.25
16:  3   4 1.45
17:  3   5 1.45
18:  3   6 1.45

Hm. Her tren için gözlemleri ileriye doğru yuvarladı. Biraz kaldı NA 2. ve 3. trenler için 1. pozisyonda, ancak 2. pozisyonda bir tren gözlemlendiyse, 1. pozisyondan geçmesi gerektiğini söylemiştiniz. Ayrıca, 2 ve 3 numaralı trenler için son gözlemi 6. sıraya doğru yuvarladıysanız, ancak trenlerin patlayabileceğini söylemiştiniz. Yani geriye doğru dönmek istiyoruz! budur roll=-Inf. Bu karmaşık -Inf çünkü sen de kontrol edebilirsin ne kadar uzak geriye doğru yuvarlamak, ancak bu soru için buna ihtiyacımız yok; biz sadece süresiz geriye doğru dönmek istiyoruz. Hadi deneyelim roll=-Inf ve ne olduğunu görün.

> ds[CJ(ids,pos), roll=-Inf]
    ID Pos  Obs
 1:  1   1 1.50
 2:  1   2 2.50
 3:  1   3 2.50
 4:  1   4 0.00
 5:  1   5 0.00
 6:  1   6 1.25
 7:  2   1 1.45
 8:  2   2 1.45
 9:  2   3 1.50
10:  2   4 2.50
11:  2   5 2.50
12:  2   6   NA
13:  3   1 0.00
14:  3   2 0.00
15:  3   3 1.25
16:  3   4 1.45
17:  3   5   NA
18:  3   6   NA

Bu daha iyi. Neredeyse. Şimdi tek yapmamız gereken saymak. Ama, bu sinir bozucu NA 2 ve 3 numaralı trenler patladıktan sonra oradalar. Onları kaldıralım.

> ds[CJ(ids,pos), roll=-Inf, nomatch=0]
    ID Pos  Obs
 1:  1   1 1.50
 2:  1   2 2.50
 3:  1   3 2.50
 4:  1   4 0.00
 5:  1   5 0.00
 6:  1   6 1.25
 7:  2   1 1.45
 8:  2   2 1.45
 9:  2   3 1.50
10:  2   4 2.50
11:  2   5 2.50
12:  3   1 0.00
13:  3   2 0.00
14:  3   3 1.25
15:  3   4 1.45

btw, data.table bir tek içinde olmak için mümkün olduğunca çok şey DT[...] bu şekilde sorguyu optimize eder. Dahili olarak, NA ve sonra onları kaldırın; o asla yaratmaz NA ilk sırada. Bu konsept verimlilik için önemlidir.

Son olarak, tek yapmamız gereken saymak. Bunu sonunda bir bileşik sorgu olarak ele alabiliriz.

> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos]
   Pos N
1:   1 3
2:   2 3
3:   3 3
4:   4 3
5:   5 2
6:   6 1

14
2018-01-08 10:37



+1 gerçekten güzel bir çözüm ve daha iyi bir açıklama. Bunu nasıl kıyaslayacağın hakkında bir şeyler söyleyebilir misin? ds[ , list( Pos = 1:Pos[.N] ) , by = ID ][ , .N , by = Pos ] veri büyüdükçe - Simon O'Hanlon
@ SimonO'Hanlon Güzel bir alternatif. Pos[.N] yeni bir uzunluk 1 vektör olurdu, : yeni oluşturmak için fonksiyon 1:Pos[.N] vektör. Tüm bu küçük vektörlerin hafızayı tıkamasını ve daha fazla çöp toplamasına neden olmasını beklerdim. Trenlerin sayısı arttıkça, daha fazla (daha çok grup) ısırılan pozisyon sayısı arttıkça, belki de. Eğer test ederseniz, sonuçla ilgileniyorum! - Matt Dowle
Ben gerçekten data.table sözdizimini anlamıyorum, ama CJ pahalı görünüyor (kavramsal olarak değil, aslında?); benim gibi bir data var, data.table ID ile max Pos'i tanımlar, yani nAtMax? Belki de SimonO'Hanlon ne yapıyor? - Martin Morgan
@MartinMorgan Evet, iyi nokta. Olabilir: ds[,max(Pos),by=ID][,rev(cumsum(rev(tabulate(V1))))]. Ayrıca cevabınıza daha uzun bir yorum ekledi. - Matt Dowle
@MattDowle Harika bir örnek ve harika bir açıklama! Data.table çalışmalarını daha iyi anlamam için bana yardımcı oldu. Bunu bir vignette görmek isterim. Teşekkürler. - Uwe


data.table Mükemmel bir çözüm gibi geliyor. Veriler sipariş edildiği sırada, her trenin maksimumunu bulabilir

maxPos = ds$Pos[!duplicated(ds$ID, fromLast=TRUE)]

Ardından o pozisyona ulaşan trenleri çizelgeleyin.

nAtMax = tabulate(maxPos)

ve her konumdaki trenlerin birikimli toplamını hesaplayarak

rev(cumsum(rev(nAtMax)))
## [1] 3 3 3 3 2 1

Tamamen bellek verimli olmasa da, bu büyük veri için oldukça hızlı olacağını düşünüyorum.


8
2018-01-08 13:48



+1 Soru ve unvandan izlenim aldım. data.table Meep'in örnek verilerinin ve görevlerinin çok azaldığını açıklamasından bu yana gösteri talep edildi. rev(cumsum(rev(tabulate()))) tam olarak istenen görevi yerine getiriyor, ama eğer trenler farklı noktalarda başlıyorsa, gözlemin değeri ilgi çekiyor, trenler artık patlamıyor ya da kamyonlar da var (2 sütun ID)? Bunlar, data.table sorgusuna basit değişiklikler (switch'ler) iken, bazda bazı kafa çizikleri olabilir mi? - Matt Dowle
Halen sahip olduğumdan çok daha iyi olan çözüm için teşekkürler! :) Matt, verilerin daha karmaşık olabileceğini düşündürmek için haklıdır, bu yüzden cevabını kabul ettim. Merak ediyorsanız, üzerinde çalıştığım şey aslında DNA dizi izleme verileridir, hiç bir şey trenlerle ilgisi yoktur :) - Meep


Aşağıdaki gibi deneyebilirsiniz. Daha iyi bir anlayış için bunu bir çok adım çözümüne bölüyorum. Muhtemelen zincirleme yaparak hepsini tek bir adımda birleştirebilirsiniz. [].

Buradaki mantık, ilk olarak her bir kimlik için son pozisyonu buluruz. Daha sonra, her bir Son Pozisyon için kimlik sayısını bulmak için verileri topluyoruz. Nihai Pozisyon 6 için tüm kimlikler Nihai pozisyon 5 için de sayıldığından, cumsum tüm daha yüksek ID sayılarını kendi alt kimliklerine eklemek.

ds2 <- ds[, list(FinalPos=max(Pos)), by=ID]

ds2 
##    ID FinalPos
## 1:  1        6
## 2:  2        5
## 3:  3        4

ds3 <- ds2[ , list(Count = length(ID)), by = FinalPos][order(FinalPos, decreasing=TRUE), list(FinalPos, Count = cumsum(Count))]

ds3
##    FinalPos Count
## 1:        4     3
## 2:        5     2
## 3:        6     1

setkey(ds3, FinalPos)

ds3[J(c(1:6)), roll = 'nearest']

##    FinalPos Count
## 1:        1     3
## 2:        2     3
## 3:        3     3
## 4:        4     3
## 5:        5     2
## 6:        6     1

3
2018-01-08 07:01



+1, çok güzel kullanımı roll="nearest". Ben düşünmüyorum ds3 gerekli? - setkey(ds[, list(N=max(Pos)), keyby=ID], N)[J(1:6), roll="nearest"] - Arun
Bunun hakkında biraz düşünmek, roll="nearest" yanlış bir sonuç verecektir, örneğin, eğer "6" hiç veride mevcut değilse ve 1: 6'dan birleştirme yaparsanız, (NA veya 0 yerine) "2" yi verecek mi? - Arun
@Arun Haklısınız! Cevabımı değiştireyim. - Chinmay Patil