Pobranie ostatnich n elementów wektora. Czy jest lepszy sposób niż użycie funkcji length ()?

86

Jeśli ze względu na argumenty chcę ostatnich pięciu elementów wektora o długości 10 w Pythonie, mogę użyć operatora „-” w indeksie zakresu, więc:

>>> x = range(10)
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x[-5:]
[5, 6, 7, 8, 9]
>>>

Jaki jest najlepszy sposób na zrobienie tego w R? Czy istnieje bardziej przejrzysty sposób niż moja obecna technika, która polega na użyciu funkcji length ()?

> x <- 0:9
> x
 [1] 0 1 2 3 4 5 6 7 8 9
> x[(length(x) - 4):length(x)]
[1] 5 6 7 8 9
> 

Pytanie dotyczy analizy szeregów czasowych, przy czym często przydaje się praca tylko na najnowszych danych.

Thomas Browne
źródło

Odpowiedzi:

123

zobacz ?taili ?headkilka wygodnych funkcji:

> x <- 1:10
> tail(x,5)
[1]  6  7  8  9 10

Dla dobra argumentacji: wszystko oprócz ostatnich pięciu elementów wyglądałoby tak:

> head(x,n=-5)
[1] 1 2 3 4 5

Jak mówi @Martin Morgan w komentarzach, istnieją dwie inne możliwości, które są szybsze niż rozwiązanie ogonowe, na wypadek, gdybyś musiał wykonać to milion razy na wektorze o 100 milionach wartości. Dla czytelności wybrałbym ogon.

test                                        elapsed    relative 
tail(x, 5)                                    38.70     5.724852     
x[length(x) - (4:0)]                           6.76     1.000000     
x[seq.int(to = length(x), length.out = 5)]     7.53     1.113905     

kod porównawczy:

require(rbenchmark)
x <- 1:1e8
do.call(
  benchmark,
  c(list(
    expression(tail(x,5)),
    expression(x[seq.int(to=length(x), length.out=5)]),
    expression(x[length(x)-(4:0)])
  ),  replications=1e6)
)
Joris Meys
źródło
Ale nie szybciej niż krojenie - testy to potwierdzają.
Nick Bastin
1
Dzięki Nick interesujące. Tak, krojenie Pythona jest fajną cechą języka.
Thomas Browne
5
@Nick: Rzeczywiście. Na wektorze o długości 1e6 i 1000 replikacji jest o około 0,3 sekundy wolniejszy. Wyobraź sobie, co możesz zrobić z zaoszczędzonymi 0,3 sekundy ...
Joris Meys
6
Implementacja narzędzi ::: tail.default x[seq.int(to=length(x), length.out=5)]wydaje się być około 10x szybsza niż, tail()ale bez sprawdzania poprawności ; x[length(x)-(4:0)]jest jeszcze szybszy.
Martin Morgan
1
@Joris: Mogę sobie wyobrazić, co bym z nimi zrobił po wykonaniu tej konkretnej operacji w pętli wewnętrznej miliard razy .. :-) Chodzi o to, że krojenie nie jest mniej wyraźne, ale bardziej optymalne, więc generalnie ja poszedłbym tą trasą.
Nick Bastin
6

Możesz zrobić dokładnie to samo w R z dwoma dodatkowymi postaciami:

x <- 0:9
x[-5:-1]
[1] 5 6 7 8 9

lub

x[-(1:5)]
Sacha Epskamp
źródło
Co jeśli nie znam długości wektora, ale zawsze chcę mieć ostatnie 5 elementów? Wersja dla Pythona nadal działa, ale Twój przykład R zwraca ostatnie 15 elementów, więc nadal wymagałoby wywołania length ()?
Thomas Browne
10
Sacha, nie sądzę, żeby twoja odpowiedź była ogólna. Przykładowy kod polega na odrzuceniu pierwszych 5 wyników zamiast zachowywania ostatnich pięciu. W tym przykładzie wygląda to tak samo, ale nie działa: x <- 0:20; x[-5:-1]- zwraca piętnaście ostatnich elementów.
Andrie
Nie znam Pythona, ale w OP x[-5:]: czy to oznacza pominięcie pierwszych 5 elementów, czy zachowanie ostatnich 5? Jeśli to pierwszy, pośrednio używa twojej długości, tak jak ty, tutaj (w przeciwnym razie, skąd wiesz, które elementy pominąć?)
Nick Sabbe
1
operator „-” w Pythonie oznacza liczenie wstecz. W tym przypadku zawsze zwróci ostatnie 5 elementów.
Thomas Browne
2
Ach tak, nie znam Pythona i założyłem, że oznacza to pominięcie pierwszej piątki tail .
Sacha Epskamp
6

Dezaprobata tailtutaj oparta na samej prędkości nie wydaje się tak naprawdę podkreślać, że część wolniejszej prędkości wynika z faktu, że praca z ogonem jest bezpieczniejsza, jeśli nie masz pewności, że długość x przekroczy nliczbę elementów, które chcesz wydzielić:

x <- 1:10
tail(x, 20)
# [1]  1  2  3  4  5  6  7  8  9 10
x[length(x) - (0:19)]
#Error in x[length(x) - (0:19)] : 
#  only 0's may be mixed with negative subscripts

Tail zwróci po prostu maksymalną liczbę elementów zamiast generować błąd, więc nie musisz samodzielnie sprawdzać błędów. Świetny powód, aby go używać. Bezpieczniejszy i czystszy kod, jeśli dodatkowe mikrosekundy / milisekundy nie mają dla ciebie większego znaczenia w jego użyciu.


źródło
3

A co powiesz rev(x)[1:5]?

x<-1:10
system.time(replicate(10e6,tail(x,5)))
 user  system elapsed 
 138.85    0.26  139.28 

system.time(replicate(10e6,rev(x)[1:5]))
 user  system elapsed 
 61.97    0.25   62.23
Brian Davis
źródło
Późny komentarz. Czas przetwarzania odwracania wektora jest zbyt duży dla długich wektorów. Spróbuj x <- 1:10e6
określić
Słuszna uwaga @ChrisNjuguna. Świetnie działa jednak z wektorem o długości 10 :)
Brian Davis
2

Oto funkcja, która to robi i wydaje się dość szybka.

endv<-function(vec,val) 
{
if(val>length(vec))
{
stop("Length of value greater than length of vector")
}else
{
vec[((length(vec)-val)+1):length(vec)]
}
}

STOSOWANIE:

test<-c(0,1,1,0,0,1,1,NA,1,1)
endv(test,5)
endv(LETTERS,5)

REPER:

                                                    test replications elapsed relative
1                                 expression(tail(x, 5))       100000    5.24    6.469
2 expression(x[seq.int(to = length(x), length.out = 5)])       100000    0.98    1.210
3                       expression(x[length(x) - (4:0)])       100000    0.81    1.000
4                                 expression(endv(x, 5))       100000    1.37    1.691
rmf
źródło
2

Dodam tu tylko coś związanego. Chciałem uzyskać dostęp do wektora z indeksami zaplecza, tj. Napisać coś podobnego, tail(x, i)ale zwrócić, x[length(x) - i + 1]a nie cały ogon.

Po komentarzach porównałem dwa rozwiązania:

accessRevTail <- function(x, n) {
    tail(x,n)[1]
}

accessRevLen <- function(x, n) {
  x[length(x) - n + 1]
}

microbenchmark::microbenchmark(accessRevLen(1:100, 87), accessRevTail(1:100, 87))
Unit: microseconds
                     expr    min      lq     mean median      uq     max neval
  accessRevLen(1:100, 87)  1.860  2.3775  2.84976  2.803  3.2740   6.755   100
 accessRevTail(1:100, 87) 22.214 23.5295 28.54027 25.112 28.4705 110.833   100

Wydaje się więc w tym przypadku, że nawet dla małych wektorów tailjest bardzo wolny w porównaniu z dostępem bezpośrednim

ClementWalter
źródło