Функция / инструкция для подсчета количества раз, когда значение уже было просмотрено

Я пытаюсь определить, есть ли в MATLAB или R функция, похожая на следующую.

Скажем, у меня есть входной вектор v.

v = [1, 3, 1, 2, 4, 2, 1, 3]

Я хочу создать вектор w длины, эквивалентной v. Каждый элемент w[i] должен сообщать мне следующее: для соответствующего значения v[i], сколько раз это значение встречалось до сих пор в v, то есть во всех элементах v до позиции i, но не включая ее. В этом примере

w = [0, 0, 1, 0, 0, 1, 2, 1]

Я действительно ищу, есть ли у каких-либо статистических или предметно-ориентированных языков подобная функция / инструкция и как она может называться.


person hayesti    schedule 20.08.2014    source источник
comment
Роланд, если ты не против, я сохраню оригинальные бирки. Моя цель состоит не в том, чтобы использовать саму функцию, а в том, чтобы определить некоторые языки, на которых она может быть найдена. R и Matlab казались лучшей отправной точкой.   -  person hayesti    schedule 20.08.2014
comment
Эффективное решение (O (n)) должно включать в себя массив аккумуляторов.   -  person Mendi Barel    schedule 20.08.2014
comment
Пожалуйста, изучите вики-страницы тегов. тег инструкций не подходит, и цель, указанная в вашем комментарии, сделает ваш вопрос не по теме (вы просите рекомендации по инструменту).   -  person Roland    schedule 20.08.2014
comment
В R вы также можете использовать dplyr следующим образом: library(dplyr); data.frame(v) %>% group_by(v) %>% mutate(count = row_number()-1) (результатом будет data.frame, но вы можете легко извлечь столбец count, если он вам нужен отдельно).   -  person talat    schedule 20.08.2014


Ответы (4)


В R вы можете попробовать следующее:

 v <- c(1,3,1,2,4,2,1,3)
 ave(v, v, FUN=seq_along)-1
 #[1] 0 0 1 0 0 1 2 1

Объяснение

 ave(seq_along(v), v, FUN=seq_along)  #It may be better to use `seq_along(v)` considering different classes i.e. `factor` also.
 #[1] 1 1 2 1 1 2 3 2

Здесь мы группируем последовательность элементов по v. Для элементов, соответствующих одной группе, функция seq_along создаст 1,2,3 etc. В случае v элементы той же группы 1 находятся в позициях 1,3,7, поэтому эти соответствующие позиции будут 1,2,3. Вычитая с помощью 1, мы сможем начать с 0.

Чтобы лучше понять это,

 lst1 <- split(v,v)
 lst2 <- lapply(lst1, seq_along)
 unsplit(lst2, v)
 #[1] 1 1 2 1 1 2 3 2

Использование data.table

  library(data.table)
  DT <- data.table(v, ind=seq_along(v))
  DT[, n:=(1:.N)-1, by=v][,n[ind]]
  #[1] 0 0 1 0 0 1 2 1
person akrun    schedule 20.08.2014
comment
И, конечно же, это можно сделать быстрее с помощью data.table или dplyr. - person Roland; 20.08.2014
comment
@Roland Спасибо, я обновил решение data.table. Вы знаете, есть ли в data.table лучший способ сохранить первоначальный порядок? - person akrun; 20.08.2014
comment
@Roland, я сомневаюсь, что преобразование числового вектора в объект data.table и последующая работа с ним сделает это быстрее. Думаю, будет медленнее. Но мог ошибаться - person David Arenburg; 20.08.2014
comment
@DavidArenburg С v <- sample(1:1e4, 1e6, TRUE) решение data.table data.table(v)[, w:=seq_len(.N) - 1, by=v][["w"]] быстрее примерно в 5 раз в моем тесте. - person Roland; 20.08.2014
comment
@Roland, не тестировал, но я тебе верю. Вы ведь сравниваете с ave? - person David Arenburg; 20.08.2014
comment
Ага, ave - ценный инструмент с неудачным названием. Его следует называть do_anything :-) - person Carl Witthoft; 20.08.2014
comment
@akrun Если вы используете :=, вам не нужно беспокоиться о порядке. - person Roland; 20.08.2014

В Matlab нет функции для этого (насколько я знаю), но вы можете добиться этого следующим образом:

w = sum(triu(bsxfun(@eq, v, v.'), 1));

Объяснение: bsxfun(...) сравнивает каждый элемент друг с другом. Тогда triu(..., 1) сохраняет только совпадения элемента с предыдущими элементами (т.е. значениями выше диагонали). Наконец sum(...) добавляет все совпадения с предыдущими элементами.


Более явная, но более медленная альтернатива (не рекомендуется):

w = arrayfun(@(n) sum(v(1:n-1)==v(n)), 1:numel(v));

Объяснение: для каждого индекса n (где n изменяется как 1:numel(v)) сравните все предыдущие элементы v(1:n-1) с текущим элементом v(n) и получите количество совпадений (sum(...)).

person Luis Mendo    schedule 20.08.2014
comment
Спасибо за ваше решение. Жалко, что в Matlab нет чего-то более примитивного. - person hayesti; 20.08.2014
comment
Я согласен. Но вы можете инкапсулировать строку bsxfun в функцию, так что она будет для вас примитивной. Как вы видите в ответе Миньона, это довольно быстро - person Luis Mendo; 20.08.2014

В R есть функция make.unique, которую можно использовать для получения требуемого результата. Сначала используйте его, чтобы сделать все элементы уникальными:

(v.u <- make.unique(as.character(v))) # it only works on character vectors so you must convert first
[1] "1"   "3"   "1.1" "2"   "4"   "2.1" "1.2" "3.1"

Затем вы можете взять этот вектор, удалить исходные данные, преобразовать пробелы в 0 и преобразовать обратно в целое число, чтобы получить счетчики:

as.integer(sub("^$","0",sub("[0-9]+\\.?","",v.u)))
[1] 0 0 1 0 0 1 2 1
person James    schedule 20.08.2014
comment
Это довольно интересно. Жаль, что вам нужно преобразовывать в строки и обратно. Я думаю, что могут быть сценарии, в которых такие вещи были бы полезны в целочисленной форме. - person hayesti; 20.08.2014
comment
Интересный подход +1 - person Tyler Rinker; 20.08.2014

Если вы хотите использовать цикл for в Matlab, вы можете получить результат с помощью:

res=v;
res(:)=0;
for c=1:length(v)
    helper=find(v==v(c));
    res(c)=find(helper==c);
end

не уверен в времени выполнения по сравнению с решением Луиса Мендо. Сейчас это проверю.

Изменить

Выполнение кода 10.000 раз приводит к:

My Solution: Elapsed time is 0.303828 seconds 
Luis Mendo's Solution (bsxfun): Elapsed time is 0.180215 seconds.
Luis Mendo's Solution (arrayfun): Elapsed time is 3.868467 seconds.

Таким образом, решение bsxfun является самым быстрым, за ним следует цикл for, за которым следует решение arrayfun. Теперь собираюсь сгенерировать более длинные v-массивы и посмотреть, изменится ли что-то.

Изменить 2. Изменение v на

v = ceil(rand(100,1)*8);

привело к более очевидному ранжированию во время выполнения:

My Solution: Elapsed time is 4.020916 seconds.
Luis Mendo's Solution (bsxfun):Elapsed time is 0.808152 seconds.
Luis Mendo's Solution (arrayfun): Elapsed time is 22.126661 seconds.
person The Minion    schedule 20.08.2014