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



我有一个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,但我不知道如何用不同的方式来做。运行时对我的小玩具示例来说甚至很糟糕,对我的真实数据来说几乎是不可行的。

你也可以用基本的r:

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

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

以@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`

这是我们散列中使用的密钥。

如果我理解正确,属性的顺序无关紧要,所以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())

您可以使用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

相关内容

  • 没有找到相关文章

最新更新