Abrufen der letzten n Elemente eines Vektors. Gibt es einen besseren Weg als die Funktion length ()?

84

Wenn ich aus Gründen der Argumentation die letzten fünf Elemente eines Vektors mit 10 Längen in Python haben möchte, kann ich den Operator "-" im Bereichsindex verwenden, also:

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

Was ist der beste Weg, dies in R zu tun? Gibt es einen saubereren Weg als meine derzeitige Technik, die Funktion length () zu verwenden?

> 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
> 

Die Frage bezieht sich übrigens auf die Zeitreihenanalyse, bei der es oft nützlich ist, nur an aktuellen Daten zu arbeiten.

Thomas Browne
quelle

Antworten:

120

siehe ?tailund ?headfür einige praktische Funktionen:

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

Für das Argument: Alles außer den letzten fünf Elementen wäre:

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

Wie @Martin Morgan in den Kommentaren sagt, gibt es zwei andere Möglichkeiten, die schneller sind als die Tail-Lösung, falls Sie dies millionenfach mit einem Vektor von 100 Millionen Werten ausführen müssen. Zur besseren Lesbarkeit würde ich mit Schwanz gehen.

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     

Benchmarking-Code:

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
quelle
Aber nicht schneller als das Schneiden - Tests bestätigen dies.
Nick Bastin
1
Danke Nick interessant. Ja, Python Slicing ist eine nette Funktion der Sprache.
Thomas Browne
5
@ Nick: In der Tat. Bei einem Vektor mit einer Länge von 1e6 und 1000 Replikationen ist er etwa 0,3 Sekunden langsamer. Stellen Sie sich vor, was Sie mit den 0,3 Sekunden tun können, die Sie gespeichert haben ...
Joris Meys
6
Die Implementierung von utils ::: tail.default x[seq.int(to=length(x), length.out=5)]scheint ungefähr 10x schneller zu sein als, tail()aber ohne die Sanity Checks; x[length(x)-(4:0)]ist noch schneller.
Martin Morgan
1
@Joris: Ich kann mir vorstellen, was ich mit ihnen machen würde, nachdem ich diese bestimmte Operation milliardenfach in einer inneren Schleife ausgeführt habe .. :-) Der Punkt ist, dass das Schneiden nicht weniger klar, sondern optimaler ist, also im Allgemeinen ich Ich würde diesen Weg gehen.
Nick Bastin
6

Mit zwei weiteren Zeichen können Sie in R genau dasselbe tun:

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

oder

x[-(1:5)]
Sacha Epskamp
quelle
Was ist, wenn ich die Länge des Vektors nicht kenne, aber immer noch die letzten 5 Elemente möchte? Die Python-Version funktioniert immer noch, aber Ihr R-Beispiel gibt die letzten 15 Elemente zurück und würde daher immer noch einen Aufruf von length () erfordern.
Thomas Browne
10
Sacha, ich glaube nicht, dass deine Antwort verallgemeinert. In Ihrem Codebeispiel werden die ersten fünf Ergebnisse gelöscht, anstatt die letzten fünf beizubehalten. In diesem Beispiel ist es dasselbe, aber Folgendes funktioniert nicht: x <- 0:20; x[-5:-1]- Dies gibt die letzten fünfzehn Elemente zurück.
Andrie
Ich kenne Python nicht, aber in den OPs x[-5:]: Bedeutet das, die ersten 5 Elemente zu überspringen oder die letzten 5 zu behalten? Wenn es der erste ist, verwendet er indirekt Ihre Länge, wie Sie hier sind (ansonsten, woher wissen Sie, welche Elemente zu überspringen sind?)
Nick Sabbe
1
Der Operator "-" in Python bedeutet rückwärts zählen. In diesem Fall werden also immer die letzten 5 Elemente zurückgegeben.
Thomas Browne
2
Ah richtig, ich kenne Python nicht und nahm an, dass es bedeutete, die ersten 5 zu überspringen, tail was Sie dann wollen.
Sacha Epskamp
6

Die Ablehnung von tailhier allein aufgrund der Geschwindigkeit scheint nicht wirklich zu betonen, dass ein Teil der langsameren Geschwindigkeit von der Tatsache herrührt, dass das Arbeiten mit dem Schwanz sicherer ist, wenn Sie nicht sicher sind, dass die Länge von x ndie Zahl überschreitet von Elementen, die Sie unterteilen möchten:

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 gibt einfach die maximale Anzahl von Elementen zurück, anstatt einen Fehler zu generieren, sodass Sie keine Fehlerprüfung selbst durchführen müssen. Ein guter Grund, es zu benutzen. Sicherer sauberer Code, wenn zusätzliche Mikrosekunden / Millisekunden für Sie bei der Verwendung keine große Rolle spielen.


quelle
3

Wie wäre es 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
quelle
Später Kommentar. Die Verarbeitungszeit, die zum Umkehren des Vektors benötigt wird, ist für lange Vektoren zu groß. Versuchen Sie es zu planen, wennx <- 1:10e6
Chris Njuguna
Guter Punkt @ChrisNjuguna. Funktioniert hervorragend mit einem Vektor der Länge 10 :)
Brian Davis
2

Hier ist eine Funktion, um es zu tun und scheint ziemlich schnell.

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)]
}
}

VERWENDUNG:

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

BENCHMARK:

                                                    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
quelle
2

Ich füge hier nur etwas Ähnliches hinzu. Ich wollte auf einen Vektor mit Backend-Indizes zugreifen, dh so etwas wie schreiben, tail(x, i)aber zurückkehren x[length(x) - i + 1]und nicht den ganzen Schwanz.

Nach folgenden Kommentaren habe ich zwei Lösungen verglichen:

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

In diesem Fall scheint es also, dass selbst für kleine Vektoren im tailVergleich zum direkten Zugriff sehr langsam ist

ClementWalter
quelle