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