R有效地计算三位数的组合的频率

时间:2018-11-27 16:19:13

标签: r combinations

我有一个data.frame,其中每个ID都有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 :(得分:2)

您也可以使用基本r:

table(apply(data[,2:4], 1, function(x) paste0(sort(x), collapse = ",")))

答案 1 :(得分:2)

您将要遇到的问题是处理大量组合。即使您尝试应用对每行进行排序的简单解决方案,这也会花费大量时间处理要处理的行数。

使用@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表示)。

在处理具有重复的组合时,我们可以轻松地将它们表示为索引频率向量。

模板向量为:

 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`

这是我们哈希中使用的密钥。

答案 2 :(得分:1)

如果我正确理解了属性的顺序,则无所谓,因此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())

答案 3 :(得分:0)

您可以使用dplyr有效地执行此操作。首先使用group_by对变量a1a2a3进行分组,然后使用summarizen()对频率进行计数:

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