R Подсчет частоты комбинаций трех цифр эффективно - PullRequest
0 голосов
/ 27 ноября 2018

У меня есть data.frame, где каждый идентификатор имеет ровно 3 атрибута.Для упрощения я поместил только 100 строк, хотя в моем реальном наборе данных он составляет около 1.000.000.Есть около 50 различных возможных атрибутов.Атрибуты представляют собой смесь чисел и символов.

data <- data.frame(id = 1:100,
               a1 = sample(letters,100,replace = T),
               a2 = sample(letters,100,replace = T),
               a3 = sample(letters,100,replace = T),
               stringsAsFactors=FALSE) %>% 
               as_tibble()

Я хочу знать, какие наиболее частые комбинации (порядок не имеет значения)

Таким образом, результат должен бытькак-то так

pattern | frequency
a,a,a   |  10
A,b,c   |  5
a,e,c   |  4
...     |  ....

Сначала я начал создавать вектор, который содержит все возможные комбинации:

possible_combinations <- combn(c(letters,LETTERS),3) %>% 
   t() %>% 
   as_tibble() %>%
   unite("combination",sep="") %>% 
   pull()

Затем я написал этот вложенный цикл для подсчета частот:

 counter = 0
 inner_counter = 0
 combination_counter = vector(mode = "numeric",length = length (possible_combinations))

  for (j in 1:length(possible_combinations)){
    for (i in 1:nrow(data)){

        # inner Counter Counts when Attribute of one ID is in one combination
        inner_counter = inner_counter + str_count(possible_combinations[j] , data[[i,2]] )
        inner_counter = inner_counter + str_count(possible_combinations[j] , data[[i,3]] )
        inner_counter = inner_counter + str_count(possible_combinations[j] , data[[i,4]] )

      # if all three attributes are in a combination, then the Counter increases by one 
    if(inner_counter == 3) {
       counter = counter + 1 }
       inner_counter = 0
                            }

  # combination_counter is a vector which saves the frequency with 
  # which a combination ocurred in all different ids

  combination_counter[[j]] = inner_counter
  inner_counter = 0 
 }

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

Ответы [ 4 ]

0 голосов
/ 28 ноября 2018

Проблема, с которой вы столкнетесь, связана с огромным количеством комбинаций.Даже если вы попытаетесь применить простое решение для сортировки каждой строки, это будет стоить много времени для количества строк, с которыми вы имеете дело.

Возьмем следующий пример с простым подходом, предложенным @Lennyy:

set.seed(123)
n <- 1e7

data <- data.frame(id = 1:n,
                   a1 = sample(letters, n, replace = T),
                   a2 = sample(letters, n, replace = T),
                   a3 = sample(letters, n, replace = T),
                   stringsAsFactors = FALSE)

system.time(t2 <- table(apply(data[,2:4], 1, function(x) paste0(sort(x), collapse = ","))))
   user  system elapsed 
373.281   1.695 375.445

Это долго ...

Вот вывод для справки:

head(t2)

a,a,a a,a,b a,a,c a,a,d a,a,e a,a,f 
  603  1657  1620  1682  1759  1734

Нам нужно как-то быстро кодировать каждую строку, не беспокоясь о том, какиестолбец конкретный элемент пришел из.Кроме того, мы должны сделать это таким образом, чтобы гарантировать уникальность.

А как насчет хеш-таблицы?Мы легко можем сделать это с помощью Rcpp.

#include <Rcpp.h>
#include <unordered_map>
using namespace Rcpp;

// [[Rcpp::plugins(cpp11)]]

// [[Rcpp::export]]
IntegerVector countCombos(IntegerMatrix myMat, int numAttr, CharacterVector myAttr) {

    unsigned long int numRows = myMat.nrow();
    unsigned long int numCols = myMat.ncol();
    std::unordered_map<std::string, int> mapOfVecs;

    for (std::size_t i = 0; i < numRows; ++i) {
        std::vector<int> testVec(numAttr, 0);

        for (std::size_t j = 0; j < numCols; ++j) {
            ++testVec[myMat(i, j) - 1];
        }

        std::string myKey(testVec.begin(), testVec.end());

        auto it = mapOfVecs.find(myKey);

        if (it == mapOfVecs.end()) {
            mapOfVecs.insert({myKey, 1});
        } else {
            ++(it->second);
        }
    }

    std::size_t count = 0;
    IntegerVector out(mapOfVecs.size());
    CharacterVector myNames(mapOfVecs.size());

    for (const auto& elem: mapOfVecs) {
        std::size_t i = 0;
        for (auto myChar: elem.first) {
            while (myChar) {
                myNames[count] += myAttr[i];
                --myChar;
            }
            ++i;
        }
        out[count++] = elem.second;
    }

    out.attr("names") = myNames;

    return out;
}

. Это дает большой выигрыш в эффективности по сравнению с другими опубликованными решениями:

myRows <- 1:nrow(data)
attrCount <- 26

matOfInts <- vapply(2:ncol(data), function(x) {
    match(data[, x], letters)
}, myRows, USE.NAMES = FALSE)
system.time(t <- countCombos(matOfInts, attrCount, letters))
 user  system elapsed 
2.570   0.007   2.579

Это в 100 раз быстрее !!!!

И вот результат:

head(t)
 jkk  ddd  qvv  ttu  aaq  ccd 
1710  563 1672 1663 1731 1775

Проверка равенства (вывод в другом порядке, поэтому мы должны сначала отсортировать):

identical(sort(unname(t)), as.integer(sort(unname(t2))))
[1] TRUE

Пояснение

Функция countCombos принимает матрицу целых чисел.Эта матрица представляет индексы элементов уникальных атрибутов (в нашем примере это будет представлено letters).

Поскольку мы имеем дело с комбинациями с повторениями, мы можем легко представить их как частоту индексацииvector.

Вектор шаблона:

 a   b   c   d   e       y   z
 |   |   |   |   |       |   |
 v   v   v   v   v       v   v
(0,  0,  0,  0,  0, ...  0,  0)

И вот как определенные комбинации отображаются:

aaa -->> (3, rep(0, 25))
zdd -->> dzd -->> ddz -->> (0, 0, 0, 2, rep(0, 21), 1)

Как только мы создали наш вектор, мы конвертируем егов строку, поэтому ddz становится:

ddz --> c((0,0,0,2, rep(0, 21),1) -->> `00020000000000000000000001`

И это ключ, который используется в нашем хеше.

0 голосов
/ 27 ноября 2018

Если я правильно вас понял, порядок атрибутов не имеет значения, поэтому aba - это то же самое, что aab и baa.У вас также есть 50 различных атрибутов, и все другие решения, похоже, полагаются на их ввод вручную.

Следующий код создает столбец, который объединяет все столбцы атрибутов, сортирует его, чтобы игнорировать порядок атрибутов, и вычисляет количество на группу:

library(dplyr)
library(rlang)
cnames <- colnames(data)
cnames <- cnames[2:length(cnames)] #assuming the first column is the only non-attribute column,
#remove any other non-attribute columns as necessary

#!!!syms(cnames) outputs them as the columns rather than text, taken from here
# https://stackoverflow.com/questions/44613279/dplyr-concat-columns-stored-in-variable-mutate-and-non-standard-evaluation?rq=1
data %>% 
  mutate(comb = sort(paste0(!!!syms(cnames)))) %>% 
  group_by(comb) %>% 
  summarise(cnt = n())
0 голосов
/ 27 ноября 2018

Вы можете сделать это с помощью базы r:

table(apply(data[,2:4], 1, function(x) paste0(sort(x), collapse = ",")))
0 голосов
/ 27 ноября 2018

Вы можете использовать dplyr, чтобы сделать это эффективно.Сначала используйте group_by для группировки переменных a1, a2 и a3, затем используйте summarize и n() для подсчета частот:

set.seed(100)
N = 1e5
data <- data.frame(id = 1:N,
                   a1 = sample(letters[1:5],N,replace = T),
                   a2 = sample(letters[1:5],N,replace = T),
                   a3 = sample(letters[1:5],N,replace = T),
                   stringsAsFactors=FALSE)
data %>%
  group_by(a1, a2, a3) %>%
  summarize(count = n()) %>%
  arrange(count)

## A tibble: 125 x 4
## Groups:   a1, a2 [25]
#   a1    a2    a3    count
#   <chr> <chr> <chr> <int>
# 1 b     a     d       735
# 2 c     b     d       741
# 3 a     d     e       747
# 4 d     a     e       754
# 5 d     e     e       754
# 6 d     e     c       756
# 7 e     a     d       756
# 8 d     c     d       757
# 9 c     c     c       758
#10 d     a     b       759
## ... with 115 more rows
...