Использование пакета fastmatch в R

Мне нужно найти индексы для числовых значений 1MM в векторе с примерно 10MM значениями. Я нашел пакет fastmatch, но когда я использую функцию fmatch(), я возвращаю только индекс первого совпадения.

Может кто-нибудь помочь мне использовать эту функцию, чтобы найти все значения, а не только первые? Я понимаю, что это основной вопрос, но онлайн-документация довольно скудная, а fmatch значительно сократило вычислительное время.

Большое спасибо!


Вот некоторые примеры данных - для целей этого упражнения позвоните в этот фрейм данных A:

              DateTime     Address       Type     ID
1  2014-03-04 20:21:03   982076970          1  2752394
2  2014-03-04 20:21:07 98174238211          1  2752394
3  2014-03-04 20:21:08 76126162197          1  2752394
4  2014-03-04 20:21:16  6718053253          1  2752394
5  2014-03-04 20:21:17 98210219176          1  2752510
6  2014-03-04 20:21:20  7622877100          1  2752510
7  2014-03-04 20:21:23  2425126157          1  2752510
8  2014-03-04 20:21:23  2425126157          1  2752510
9  2014-03-04 20:21:25   701838650          1  2752394
10 2014-03-04 20:21:27 98210219176          1  2752394

Что я хочу сделать, так это найти количество уникальных значений Type для каждого Address. Есть несколько миллионов строк данных с примерно 1MM уникальными значениями адреса... в среднем каждый адрес появляется примерно в 6 раз в наборе данных. И, хотя значения Type, перечисленные выше, равны 1, они могут принимать любое значение от 0: 5. Я также понимаю, что значения Address довольно длинные, что добавляет времени, необходимого для сопоставления.

Я пробовал следующее:

uvals <- unique(A$Address)
utypes <- matrix(0,length(uvals),2)
utypes[,1] <- uvals

for (i in 1:length(unique(Address))) {
    b <- which(uvals[i] %in% A$Address)
    c <- length(unique(A$Type[b]))
    utypes[i,2] <- c
}

Однако приведенный выше код не очень эффективен - если я перебираю значения 1MM, я считаю, что это займет 10-15 часов.

Я тоже пробовал это в цикле... но это не намного быстрее.

b <- which(A$Address == uvals[i])  

Я знаю, что есть более элегантный/более быстрый способ, я довольно новичок в R и буду признателен за любую помощь.

Ответ 1

Это можно сделать с помощью функции unique в data.table, за которой следует агрегация. Я проиллюстрирую это, используя более или менее выборочные данные, сгенерированные @Chinmay:

Создание выборочных данных:

set.seed(100L)
dat = data.frame(
         address = sample(1e6L, 1e7L, TRUE), 
           value = sample(1:5, 1e7L, TRUE, prob=c(0.5, 0.3, 0.1, 0.07, 0.03))
      )

data.table решение:

require(data.table) ## >= 1.9.2
dat.u = unique(setDT(dat), by=c("address", "value"))
ans   = dat.u[, .N, by=address]

Объяснение:

  • Функция setDT преобразует a data.frame в data.table по ссылке (что очень быстро).Функция
  • unique, работающая на data.table, вызывает метод unique.data.table, который невероятно быстрый по сравнению с base:::unique. Теперь у нас есть только уникальные значения type для каждого address.
  • Все, что осталось сделать, это объединить или сгруппировать по address и получить количество наблюдений, которые есть в каждой группе. Группы частей by=address by address и .N являются встроенной переменной data.table, которая предоставляет количество наблюдений для этой группы.

Ориентиры:

Я создам функции для генерации данных как data.table и data.frame для сравнения data.table с ответом на dplyr решение (a), предложенное @beginneR, хотя я не вижу необходимости в arrange(.) там и, следовательно, пропустит эту часть.

## function to create data
foo <- function(type = "df") {
    set.seed(100L)
    dat = data.frame(
             address = sample(1e6L, 1e7L, TRUE), 
               value = sample(1:5, 1e7L, TRUE, prob=c(0.5, 0.3, 0.1, 0.07, 0.03))
          )
    if (type == "dt") setDT(dat)
    dat
} 

## DT function
dt_sol <- function(x) {
    unique(x, by=c("address", "value"))[, .N, by=address]
}

## dplyr function
dplyr_sol <- function(x) {
    distinct(x) %>% group_by(address) %>% summarise(N = n_distinct(value))
}

Тайминги, представленные здесь, представляют собой три последовательных прогона system.time(.) для каждой функции.

## benchmark timings in seconds
##        pkg   run-01   run-02   run-03                                 command
## data.table     2.4       2.3      2.4  system.time(ans1 <- dt_sol(foo("dt")))
##      dplyr    15.3      16.3     15.7   system.time(ans2 <- dplyr_sol(foo()))

По какой-то причине dplyr автоматически упорядочивает результат по переменной группировки. Поэтому, чтобы сравнить результаты, я также закажу их в результате от data.table:

system.time(setkey(ans1, address)) ## 0.102 seconds
identical(as.data.frame(ans1), as.data.frame(ans2)) ## TRUE

Итак, data.table здесь ~ 6x быстрее.

Обратите внимание, что bit64:::integer64 также поддерживается в data.table - поскольку вы указываете, что значения адреса слишком велики, вы также можете сохранить их как integer64.

Ответ 2

Вы можете попытаться создать индекс ваших значений 10MM и отсортировать их. Затем поиск ваших значений 1MM в этом индексированном векторе должен быть быстрее.

Например, используя пакет data.table, вы можете сделать это с помощью функции setkey, которая индексирует данный столбец data.table.

require(data.table)

set.seed(100)

dat <- sample(1:1e+07, size = 1e+07, replace = T)
searchval <- sample(dat, size = 1e+06)

DT <- data.table(dat, index = seq_along(dat))
setkey(DT, dat)
DT
##                dat   index
##        1:        1  169458
##        2:        1 4604823
##        3:        1 7793446
##        4:        2 5372388
##        5:        3 2036622
##       ---                 
##  9999996:  9999996 1271426
##  9999997:  9999998  530029
##  9999998: 10000000  556672
##  9999999: 10000000 6776063
## 10000000: 10000000 6949665


lookup <- data.table(val = searchval)
setkey(lookup, val)
lookup
##              val
##       1:       2
##       2:      16
##       3:      24
##       4:      33
##       5:      36
##      ---        
##  999996: 9999970
##  999997: 9999973
##  999998: 9999988
##  999999: 9999996
## 1000000: 9999998

Теперь вы можете найти все значения из lookup в DT, просто используя

DT[lookup]
##              dat   index
##       1:       2 5372388
##       2:      16  537927
##       3:      16 1721233
##       4:      24 7286522
##       5:      33 7448516
##      ---                
## 2000298: 9999973 8008610
## 2000299: 9999988 3099060
## 2000300: 9999988 7996302
## 2000301: 9999996 1271426
## 2000302: 9999998  530029

Ответ 3

fmatch, похоже, ясно указывает, что он находит только первое совпадение. И учитывая, что он использует базовую стратегию хеширования, я полагаю, что маловероятно, что он хранит несколько элементов на ключ, что является одним из способов, которым он остается таким быстрым (и он работает так же, как работает match).

У вас много повторяющихся значений? Возможно, вы можете сохранить их в отдельном месте/таблице и создать быстрый индекс для списка возможных совпадений. Было бы более полезно, если бы вы предоставили образцы данных, представляющих то, что вы пытаетесь сделать, и код, который вы пытались увидеть, если его было бы легко расширить.

Ответ 4

Если я правильно понял ваш вопрос, вы также можете сделать это с помощью dplyr:

Я буду включать два разных способа, так как я не совсем уверен, каков ваш желаемый результат.

Сначала создайте некоторые данные образца:

Address <- rep(letters, 5)
Type <- sample(1:5, size=5*26, replace=T)
A <- data.frame(Address, Type)

Затем установите и загрузите dplyr

require(dplyr)

a) Чтобы найти количество различных значений Type для каждого значения Address:

A %.% arrange(Address, Type) %.% group_by(Address) %.% summarize(NoOfTypes = length(unique(Type)))

b) Чтобы найти все уникальные комбинации Address и Type:

A %.% arrange(Address, Type) %.% group_by(Address, Type) %.% filter( 1:n() == 1)