2013-03-29 13 views
13

Uproszczona wersja mojego zestawu danych będzie wyglądać następująco:Skumulowana liczba unikalnych wartościach w R

depth value 
    1  a 
    1  b 
    2  a 
    2  b 
    2  b 
    3  c 

Chciałbym, aby ustawić nowy danych, gdzie dla każdej wartości „głębokości”, musiałbym skumulowana liczba unikalnych wartości, zaczynając od góry. na przykład

depth cumsum 
1  2 
2  2 
3  3 

Wszelkie pomysły, jak to zrobić? Jestem stosunkowo nowy w R.

Odpowiedz

1

Dobrym pierwszym krokiem byłoby stworzenie kolumny TRUE lub FALSE, gdzie jest TRUE dla pierwszego z każdej wartości i FALSE dla późniejszych występach tej wartości. Można to łatwo zrobić za pomocą duplicated:

mydata$first.appearance = !duplicated(mydata$value) 

Przekształcanie danych jest najlepiej zrobić za pomocą aggregate. W tym przypadku, to mówi do sumy ponad kolumnę first.appearance ramach każdego podzbioru depth:

newdata = aggregate(first.appearance ~ depth, data=mydata, FUN=sum) 

Wynik będzie wyglądać następująco:

depth first.appearance 
1  1 2 
2  2 0 
3  3 1 

To nadal nie jest skumulowana suma, choć. W tym celu można użyć funkcji cumsum (a następnie pozbyć się starego słupa):

newdata$cumsum = cumsum(newdata$first.appearance) 
newdata$first.appearance = NULL 

Przypomnę więc:

mydata$first.appearance = !duplicated(mydata$value) 
newdata = aggregate(first.appearance ~ depth, data=mydata, FUN=sum) 
newdata$cumsum = cumsum(newdata$first.appearance) 
newdata$first.appearance = NULL 

wyjściowa:

depth cumsum 
1  1  2 
2  2  2 
3  3  3 
0

Oto inne rozwiązanie przy użyciu lapply(). Za pomocą unique(df$depth) zrób wektor unikatowych wartości depth, a następnie dla każdego takiego podzestawu wartości tylko te wartości depth są równe lub mniejsze niż konkretna wartość depth. Następnie obliczyć długość unikalnych wartości value. Ta wartość długości jest przechowywana w cumsum, a następnie depth=x da wartość określonego poziomu głębokości. Z do.call(rbind,...) przekształcić go w jedną ramkę danych.

do.call(rbind,lapply(unique(df$depth), 
       function(x) 
      data.frame(depth=x,cumsum=length(unique(df$value[df$depth<=x]))))) 
    depth cumsum 
1  1  2 
2  2  2 
3  3  3 
12

Uważam to idealny przypadek użycia factor i ustawienie levels ostrożnie. Użyję tutaj data.table z tym pomysłem. Upewnij się, że Twoja kolumna value to character (nie jest to wymaganie bezwzględne).

  • krok 1: Get data.frame konwertowane do data.table biorąc zaledwie unique wiersze.

    require(data.table) 
    dt <- as.data.table(unique(df)) 
    setkey(dt, "depth") # just to be sure before factoring "value" 
    
  • krok 2: Konwersja value do factor i zmuszania do numeric. Upewnij się, że ustaw sobie poziom (jest to ważne).

    dt[, id := as.numeric(factor(value, levels = unique(value)))] 
    
  • krok 3: Ustaw kolumnę klucza do depth dla podzbioru i prostu odebrać ostatnią wartość

    setkey(dt, "depth", "id") 
    dt.out <- dt[J(unique(depth)), mult="last"][, value := NULL] 
    
    # depth id 
    # 1:  1 2 
    # 2:  2 2 
    # 3:  3 3 
    
  • Krok 4: Ponieważ wszystkie wartości w wierszach ze wzrostem głębokości powinien mieć na najmniej wartość poprzedniego rzędu, powinieneś użyć cummax, aby uzyskać ostateczne wyjście.

    dt.out[, id := cummax(id)] 
    

Edit: Powyższy kod był dla celów poglądowych. W rzeczywistości wcale nie potrzebujesz trzeciej kolumny. Tak właśnie napisałbym ostatni kod.

require(data.table) 
dt <- as.data.table(unique(df)) 
setkey(dt, "depth") 
dt[, value := as.numeric(factor(value, levels = unique(value)))] 
setkey(dt, "depth", "value") 
dt.out <- dt[J(unique(depth)), mult="last"] 
dt.out[, value := cummax(value)] 

Oto bardziej skomplikowany przykład i wyjście z kodu:

df <- structure(list(depth = c(1, 1, 2, 2, 3, 3, 3, 4, 5, 5, 6), 
       value = structure(c(1L, 2L, 3L, 4L, 1L, 3L, 4L, 5L, 6L, 1L, 1L), 
       .Label = c("a", "b", "c", "d", "f", "g"), class = "factor")), 
       .Names = c("depth", "value"), row.names = c(NA, -11L), 
       class = "data.frame") 
# depth value 
# 1:  1  2 
# 2:  2  4 
# 3:  3  4 
# 4:  4  5 
# 5:  5  6 
# 6:  6  6 
+1

Oto wersja dplyr' ':' df%>% zorganizować (głębokość)%>% mutować (wartość = cummax (as.numeric (factor (wartość, wyjątkowy poziom = (value)))))%>% aranżacja (depth, desc (value))%>% distinct (depth) '. –

+1

Ta metoda może być ogólnie stosowana, gdy zarówno 'depth' jak i' value' są wartościami łańcuchowymi. Dzięki! – ecoe

+0

@Arun To świetne rozwiązanie! Dzięki! – asterx

5

Oto kolejna próba:

numvals <- cummax(as.numeric(factor(mydf$value))) 
aggregate(numvals, list(depth=mydf$depth), max) 

Co daje:

depth x 
1  1 2 
2  2 2 
3  3 3 

Wydaje do pracy również z przykładem @ Arun:

depth x 
1  1 2 
2  2 4 
3  3 4 
4  4 5 
5  5 6 
6  6 6 
+1

Nie jestem do końca pewien, ale wydaje się, że zarówno 'depth' jak i' value' muszą być posortowane jednocześnie. Na przykład ta metoda nie będzie liczyć unikatowego wystąpienia 'c' bez względu na sposób' setkey() 'this' data.table': 'mydf = data.table (data.frame (depth = c (1,1) , 2,2,6,7), wartość = c ("a", "b", "g", "h", "b", "c"))) ". – ecoe

3

Można to napisać w stosunkowo czysty sposób za pomocą pojedynczej instrukcji SQL za pomocą pakietu sqldf. Zakładamy DF jest oryginalna ramka danych:

library(sqldf) 

sqldf("select b.depth, count(distinct a.value) as cumsum 
    from DF a join DF b 
    on a.depth <= b.depth 
    group by b.depth" 
) 
+0

Jest to bardzo przydatne przy założeniu, że 'depth' jest wartością numeryczną. Jeśli 'depth' jest ciągiem lub ciągiem znaków reprezentującym datę, tak jak było w moim przypadku, może to być bardzo kosztowna operacja. – ecoe

+1

W wielu przypadkach prędkość nie ma znaczenia, a klarowność jest ważniejszą kwestią. Jeśli wydajność jest ważna, naprawdę musisz ją przetestować, a nie przyjmować założenia, a jeśli jest zbyt wolna, dodaj indeks i przetestuj go ponownie. –

Powiązane problemy