Нахождение индекса первых изменений в элементах вектора

У меня есть вектор v и я хотел бы найти индекс первых изменений в элементах вектора в R. Как я могу это сделать? Например:

v = c(1, 1, 1, 1, 1, 1, 1, 1.5, 1.5, 2, 2, 2, 2, 2)

Ответ 1

rle - хорошая идея, но если вы хотите только индексы точек изменения, которые вы можете просто сделать:

c(1,1+which(diff(v)!=0))
## 1 8 10

Ответ 2

Вы ищете rle:

rle(v)
## Run Length Encoding
##   lengths: int [1:3] 7 2 5
##   values : num [1:3] 1 1.5 2

Это говорит о том, что значение изменяется в местах 7 + 1, 7 + 2 + 1 (и 7 + 2 + 5 + 1 будет индексом элемента "один от конца" )

Ответ 3

Внутренний пакет data.table (что еще не экспортируется) использует функцию uniqlist (в dev 1.8.11) или альтернативно duplist (в текущем 1.8.10 @CRAN), которая делает именно то, что вы после, Это должно быть довольно быстро. Здесь контрольный показатель:

require(data.table)
set.seed(45)
# prepare a huge vector (sorted)
x <- sort(as.numeric(sample(1e5, 1e7, TRUE)))

require(microbenchmark)
ben <- function(v) c(1,1+which(diff(v)!=0))
matthew <- function(v) rle(v)
matteo <- function(v) firstDiff(v)
exegetic <- function(v) first.changes(v)
# if you use 1.8.10, replace uniqlist with duplist
dt <- function(v) data.table:::uniqlist(list(v))

microbenchmark( ans1 <- ben(x), matthew(x), matteo(x), 
                exegetic(x), ans2 <- dt(x), times=10)

# Unit: milliseconds
#            expr       min         lq     median         uq        max neval
#  ans1 <- ben(x)  1181.808  1229.8197  1313.2646  1357.5026  1553.9296    10
#      matthew(x)  1456.918  1496.0300  1581.0062  1660.4067  2148.1691    10
#       matteo(x) 28609.890 29305.1117 30698.5843 32706.3147 34290.9864    10
#     exegetic(x)  1365.243  1546.0652  1576.8699  1659.5488  1886.6058    10
#   ans2 <- dt(x)   113.712   114.7794   143.9938   180.3743   221.8386    10

identical(as.integer(ans1), ans2) # [1] TRUE

Я не знаком с Rcpp, но похоже, что решение может быть улучшено совсем немного.

Изменить: Обратитесь к обновленному ответу Matteo для таймингов Rcpp.

Ответ 4

Если вам нужна операция быстро, вы можете использовать пакет Rcpp для вызова С++ из R:

library(Rcpp)
library(data.table)
library(microbenchmark)

# Rcpp solution
cppFunction('
NumericVector firstDiff(NumericVector & vett)
{
  const int N = vett.size();

  std::list<double> changes;
  changes.push_back(1.0);

  NumericVector::iterator iterH = vett.begin() + 1;
  NumericVector::iterator iterB = vett.begin();

  int count = 2;
  for(iterH = vett.begin() + 1; iterH != vett.end(); iterH++, iterB++)
  {
    if(*iterH != *iterB) changes.push_back(count);
    count++;
  }

  return wrap(changes);
 }
')

# Data table
dt <- function(input) data.table:::uniqlist(list(input))

# Comparison
set.seed(543)
x <- sort(as.numeric(sample(1e5, 1e7, TRUE)))
microbenchmark(ans1 <- firstDiff(x), which(diff(x) != 0)[1], rle(x),
               ans2 <- dt(x), times = 10) 

Unit: milliseconds
                   expr       min        lq    median        uq       max neval
    ans1 <- firstDiff(x)  50.10679  50.12327  50.14164  50.16343  50.28475    10
  which(diff(x) != 0)[1] 545.66478 547.58388 556.15550 561.78275 617.40281    10
                  rle(x) 664.53262 687.04316 709.84949 714.91528 721.37204    10
                   dt(x)  60.60317  82.30181  82.70207  86.13330  94.07739    10

identical(as.integer(ans1), ans2)
#[1] TRUE

Rcpp немного быстрее, чем data.table и намного быстрее, чем другие альтернативы в этом примере.

Ответ 5

> v <- c(1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 5, 5, 6, 6, 6, 6)

first.changes <- function(d) {
  p <- cumsum(rle(d)$lengths) + 1
  p[-length(p)]
}

> first.changes(v)
[1]  4  6 11 12 14

Или, используя ваши данные,

> v = c(1, 1, 1, 1, 1, 1, 1, 1.5, 1.5, 2, 2, 2, 2, 2)

> first.changes(v)
[1]  8 10