Получение последних n элементов вектора. Есть ли способ лучше, чем использование функции length ()?


86

Если в качестве аргумента мне нужны последние пять элементов вектора длиной 10 в Python, я могу использовать оператор «-» в индексе диапазона, чтобы:

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

Как лучше всего это сделать в R? Есть ли более чистый способ, чем моя нынешняя техника, - использовать функцию 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
> 

Вопрос связан с анализом временных рядов, кстати, где часто бывает полезно работать только с недавними данными.

Ответы:


123

см. ?tailи ?headнекоторые удобные функции:

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

Ради аргумента: все, кроме последних пяти элементов, будут:

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

Как говорит @Martin Morgan в комментариях, есть две другие возможности, которые быстрее, чем хвостовое решение, на случай, если вам придется выполнить это миллион раз на векторе из 100 миллионов значений. Для читабельности я бы пошел с хвостиком.

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     

код тестирования:

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

Но не быстрее, чем нарезка - тестирование это подтверждает.
Ник Бастин

1
Спасибо Ник, интересно. Да, нарезка Python - приятная особенность языка.
Томас Браун

5
@Nick: Конечно. В векторе длиной 1e6 и 1000 повторений он примерно на 0,3 секунды медленнее. Представьте, что вы можете сделать с сэкономленными 0,3 секунды ...
Джорис Мейс,

6
Реализация utils ::: tail.default x[seq.int(to=length(x), length.out=5)]кажется примерно в 10 раз быстрее, чем tail()без проверок работоспособности ; x[length(x)-(4:0)]еще быстрее.
Мартин Морган

1
@Joris: Я могу представить, что я буду делать с ними после того, как я запустил эту конкретную операцию во внутреннем цикле миллиард раз .. :-) Дело в том, что нарезка не менее ясна, но более оптимальна, поэтому в целом я Я бы пошел по этому маршруту.
Ник Бастин

6

Вы можете сделать то же самое в R с двумя другими персонажами:

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

или же

x[-(1:5)]

Что делать, если я не знаю длину вектора, но мне всегда нужны последние 5 элементов? Версия python все еще работает, но ваш пример R возвращает последние 15 элементов, и поэтому все равно потребуется вызов length ()?
Thomas Browne

10
Саша, я не думаю, что твой ответ обобщает. Что делает ваш пример кода, так это отбрасывает первые 5 результатов, а не сохраняет последние пять. В этом примере это то же самое, но следующее не работает: x <- 0:20; x[-5:-1]- возвращает последние пятнадцать элементов.
Андри

Я не знаю python, но в OP x[-5:]: означает ли это пропустить первые 5 элементов или оставить последние 5? Если это первый, он косвенно использует вашу длину, как и вы здесь (иначе, как узнать, какие элементы пропустить?)
Ник Саббе

1
оператор «-» в Python означает обратный отсчет. Таким образом, в этом случае он всегда будет возвращать последние 5 элементов.
Thomas Browne

2
Ах да, я не знаю python и предположил, что это означает пропустить первые 5. tail - это то, что вам тогда нужно.
Саша Эпскэмп

6

Неудобство tailздесь, основанное только на скорости, на самом деле, похоже, не подчеркивает, что часть более низкой скорости происходит из того факта, что с хвостом безопаснее работать, если вы не уверены, что длина x превысит n, число элементов, которые вы хотите выделить:

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 просто вернет максимальное количество элементов вместо того, чтобы генерировать ошибку, поэтому вам не нужно самостоятельно проверять ошибки. Отличный повод его использовать. Более безопасный чистый код, если дополнительные микросекунды / миллисекунды не имеют для вас большого значения при его использовании.


3

Как насчет 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

Поздний комментарий. Время обработки обратного вектора слишком велико для длинных векторов. Попробуйте рассчитать время, когдаx <- 1:10e6
Крис Нджугуна

Хорошее замечание @ChrisNjuguna. Хотя отлично работает с вектором длиной 10 :)
Брайан Дэвис

2

Вот функция для этого, и она кажется достаточно быстрой.

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

ПРИМЕНЕНИЕ:

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

ЭТАЛОН:

                                                    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

2

Я просто добавляю сюда кое-что связанное с этим. Мне нужно было получить доступ к вектору с индексами бэкэнда, т.е. написать что-то вроде, tail(x, i)но чтобы вернутьx[length(x) - i + 1] а не весь хвост.

Следуя комментариям, я протестировал два решения:

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

Таким образом, в этом случае оказывается, что даже для небольших векторов tailэто очень медленно по сравнению с прямым доступом

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.