Obteniendo los últimos n elementos de un vector. ¿Existe una forma mejor que usar la función length ()?

84

Si, por el bien del argumento, quiero los últimos cinco elementos de un vector de 10 longitudes en Python, puedo usar el operador "-" en el índice de rango así:

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

¿Cuál es la mejor forma de hacer esto en R? ¿Existe una forma más limpia que mi técnica actual que es usar la función 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
> 

La pregunta está relacionada con el análisis de series de tiempo por cierto, donde a menudo es útil trabajar solo con datos recientes.

Thomas Browne
fuente

Respuestas:

120

vea ?taily ?headpara algunas funciones convenientes:

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

Por el bien del argumento: todo menos los últimos cinco elementos serían:

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

Como dice @Martin Morgan en los comentarios, hay otras dos posibilidades que son más rápidas que la solución de cola, en caso de que tengas que realizar esto un millón de veces en un vector de 100 millones de valores. Para mayor legibilidad, iría con cola.

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     

código de evaluación comparativa:

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
fuente
Pero no más rápido que cortar: las pruebas lo confirman.
Nick Bastin
1
Gracias Nick interesante. Sí, el corte de Python es una buena característica del lenguaje.
Thomas Browne
5
@Nick: De hecho. En un vector de longitud 1e6 y 1000 repeticiones, es aproximadamente 0,3 segundos más lento. Imagina lo que puedes hacer con los 0.3 segundos que ahorraste ...
Joris Meys
6
La implementación de utils ::: tail.default x[seq.int(to=length(x), length.out=5)]parece ser 10 veces más rápida que, tail()pero sin los controles de cordura; x[length(x)-(4:0)]es aún más rápido.
Martin Morgan
1
@Joris: Puedo imaginar lo que haría con ellos después de ejecutar esa operación en particular en un bucle interno mil millones de veces ... :-) El punto es que el corte no es menos claro, sino más óptimo, así que en general Iría por esa ruta.
Nick Bastin
6

Puede hacer exactamente lo mismo en R con dos caracteres más:

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

o

x[-(1:5)]
Sacha Epskamp
fuente
¿Qué pasa si no conozco la longitud del Vector, pero siempre quiero los últimos 5 elementos? La versión de Python todavía funciona, pero su ejemplo de R devuelve los últimos 15 elementos y, por lo tanto, ¿aún requeriría una llamada a length ()?
Thomas Browne
10
Sacha, no creo que tu respuesta se generalice. Lo que hace su ejemplo de código es eliminar los primeros 5 resultados, en lugar de mantener los últimos cinco. En este ejemplo es lo mismo, pero lo siguiente no funciona: x <- 0:20; x[-5:-1]- esto devuelve los últimos quince elementos.
Andrie
No sé Python, pero en los OP x[-5:]: ¿esto significa omitir los primeros 5 elementos o mantener los últimos 5? Si es el primero, indirectamente está usando tu longitud, como tú, aquí (de lo contrario, ¿cómo sabes qué elementos omitir?)
Nick Sabbe
1
el operador "-" en Python significa contar hacia atrás. Entonces siempre devolverá los últimos 5 elementos en este caso.
Thomas Browne
2
Ah, claro, no sé Python y asumí que significaba omitir los primeros 5. tail es lo que quieres entonces.
Sacha Epskamp
6

La desaprobación de tailaquí basada solo en la velocidad realmente no parece enfatizar que parte de la velocidad más lenta proviene del hecho de que es más seguro trabajar con la cola, si no está seguro de que la longitud de x excederá nel número de elementos que desea subconjuntos:

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 simplemente devolverá el número máximo de elementos en lugar de generar un error, por lo que no es necesario que realice ninguna comprobación de errores usted mismo. Una gran razón para usarlo. Código más seguro y más limpio, si los microsegundos / milisegundos adicionales no le importan mucho en su uso.


fuente
3

¿Qué tal 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
fuente
Comentario tardío. El tiempo de procesamiento necesario para invertir el vector es demasiado grande para vectores largos. Intente cronometrarlo cuandox <- 1:10e6
Chris Njuguna
Buen punto @ChrisNjuguna. Sin embargo, funciona muy bien con un vector de longitud 10 :)
Brian Davis
2

Aquí hay una función para hacerlo y parece razonablemente rápida.

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

USO:

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

PUNTO DE REFERENCIA:

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

Solo agrego aquí algo relacionado. Quería acceder a un vector con índices de backend, es decir, escribir algo como tail(x, i)pero para volver x[length(x) - i + 1]y no toda la cola.

Después de los comentarios, comparé dos soluciones:

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

Entonces, en este caso parece que incluso para vectores pequeños, tailes muy lento en comparación con el acceso directo

ClementeWalter
fuente