我有一个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
对变量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