2016-08-19 11 views
6

Chciałbym łączyć wiersze ramki danych tak, że zakresy opisane przez kolumnę "początek" i "koniec" obejmują wszystkie wartości z oryginalnego zestawu danych. Mogą występować nakładki, powtórzenia i zakresy zagnieżdżone. Może brakować niektórych zakresów.Skonsoliduj wiersze na podstawie zakresów dat

Oto przykład tego rodzaju danych Chciałbym zwinąć:

data = data.frame(rbind(
    c("Roger", 1, 10), 
    c("Roger", 10, 15), 
    c("Roger", 16, 17), 
    c("Roger", 3, 6), 
    c("Roger", 20, 25), 
    c("Roger", NA, NA), 
    c("Susan", 2, 8))) 
names(data) = c("name", "start", "end") 
data$start = as.numeric(as.character(data$start)) 
data$end = as.numeric(as.character(data$end)) 

pożądany wynik byłby:

name start end 
Roger 1  17 
Roger 20 25 
Susan 2  8 

Moja próba było rozwinąć każdy element w zakres dla każdego rzędu. To działa, ale nie jestem pewien, jak zmniejszyć to z powrotem. Dodatkowo pełny zestaw danych, z którym pracuję, ma ~ 30 milionów wierszy i bardzo duże zakresy, więc ta metoda jest BARDZO powolna.

pb <- txtProgressBar(min = 0, max = length(data$name), style = 3) 
mylist = list() 
for(i in 1:length(data$name)){ 
    subdata = data[i,] 
    if(is.na(subdata$start)){ 
    mylist[[i]] = subdata 
    mylist[[i]]$each = NA 
    } 
    if(!is.na(subdata$start)){ 
    sequence = seq(subdata$start, subdata$end) 
    mylist[[i]] = subdata[rep(1, each = length(sequence)),] 
    mylist[[i]]$daily = sequence 
    } 
    setTxtProgressBar(pb, i) 
} 

rbindlist(mylist) 
+0

Może to oczywiste, ale dlaczego Roger pojawia się dwa razy? a nie w jednym wierszu z początkiem = 1 i końcem = 25? – snoram

+0

@snoram Dobre pytanie. Ponieważ Roger nie miał 18 lub 19, więc te dwie zapisy odzwierciedlają lukę w jego zakresach. – Nancy

+2

Powiązane: [collapse przecinające się regiony w R] (http://stackoverflow.com/questions/16957293/collapse-intersecting-regions-in-r) oraz [Merge Overlapping Ranges na unikalne grupy] (http://stackoverflow.com/questions/15235821/merge-overlapping-ranges-into-unique-groups) – Henrik

Odpowiedz

10

Zgaduję IRanges jest dużo bardziej wydajne, ale ...

library(data.table) 

# remove missing values 
DT = na.omit(setDT(data)) 

# sort 
setorder(DT, name, start) 

# mark threshold for a new group 
DT[, high_so_far := shift(cummax(end), fill=end[1L]), by=name] 

# group and summarise 
DT[, .(start[1L], end[.N]), by=.(name, g = cumsum(start > high_so_far + 1L))] 

#  name g V1 V2 
# 1: Roger 0 1 17 
# 2: Roger 1 20 25 
# 3: Susan 1 2 8 

Jak to działa:

  • cummax jest skumulowana maksymalna, więc najwyższa jak dotąd wartość, łącznie z bieżącym rzędem.
  • Aby przyjąć wartość z wyłączeniem bieżącego wiersza, należy użyć shift (która pochodzi z poprzedniego wiersza).
  • cumsum(some_condition) to standardowy sposób tworzenia zmiennej grupującej.
  • .N to ostatni wiersz grupy określony przez by=.

W razie potrzeby kolumny można nazwać w ostatnim kroku, np. .(s = start[1L], e = end[.N]).


z datą odstępach. Jeśli pracuję z datami, proponuję klasę IDate; po prostu użyj as.IDate, aby przekonwertować Date.

może +1 w terminach, ale niestety nie cummax mogę, więc ...

cummax_idate = function(x) (setattr(cummax(unclass(x)), "class", c("Date", "IDate"))) 

set.seed(1) 
d = sample(as.IDate("2011-11-11") + 1:10) 
cummax_idate(d) 
# [1] "2011-11-14" "2011-11-15" "2011-11-16" "2011-11-18" "2011-11-18" 
# [6] "2011-11-19" "2011-11-20" "2011-11-20" "2011-11-21" "2011-11-21" 

Myślę, że ta funkcja może być używana zamiast cummax Mamy.

Dodatkowe () w funkcji są dostępne, ponieważ setattr nie wydrukuje swoich danych wyjściowych.

+0

Chciałbym przenieść 'na.omit' do zastosowania po' setDT', aby użyć ostatecznie szybszą metodę 'na.omit.data.table'. – jangorecki

+0

OK, gotowe.Dzięki, @jangorecki – Frank

+0

@To świetnie. Tak naprawdę używam tego dla zakresów dat, ale konwertowanie dat na numeryczne, a następnie z powrotem na daty działa z tą metodą i zachowuje daty. – Nancy

Powiązane problemy