使用R之前,我使用了很多Perl。在Perl中,我经常使用哈希,在Perl中,哈希的查找通常被认为是快速的。
例如,以下代码将用最多10000个键/值对填充哈希,其中键是随机字母,值是随机整数。然后,它在该哈希中进行10000次随机查找。
#!/usr/bin/perl -w
use strict;
my @letters = ('a'..'z');
print @letters . "\n";
my %testHash;
for(my $i = 0; $i < 10000; $i++) {
my $r1 = int(rand(26));
my $r2 = int(rand(26));
my $r3 = int(rand(26));
my $key = $letters[$r1] . $letters[$r2] . $letters[$r3];
my $value = int(rand(1000));
$testHash{$key} = $value;
}
my @keyArray = keys(%testHash);
my $keyLen = scalar @keyArray;
for(my $j = 0; $j < 10000; $j++) {
my $key = $keyArray[int(rand($keyLen))];
my $lookupValue = $testHash{$key};
print "key " . $key . " Lookup $lookupValue \n";
}
现在,我越来越希望在R中具有类似于哈希的数据结构。以下是等效的R代码:
testHash <- list()
for(i in 1:10000) {
key.tmp = paste(letters[floor(26*runif(3))], sep="")
key <- capture.output(cat(key.tmp, sep=""))
value <- floor(1000*runif(1))
testHash[[key]] <- value
}
keyArray <- attributes(testHash)$names
keyLen = length(keyArray);
for(j in 1:10000) {
key <- keyArray[floor(keyLen*runif(1))]
lookupValue = testHash[[key]]
print(paste("key", key, "Lookup", lookupValue))
}
代码似乎在做等效的事情。但是,Perl的速度要快得多:
>time ./perlHashTest.pl
real 0m4.346s
user **0m0.110s**
sys 0m0.100s
与R相比:
time R CMD BATCH RHashTest.R
real 0m8.210s
user **0m7.630s**
sys 0m0.200s
什么解释了差异? R列表中的查找是否不好?
增加到100,000个列表长度和100,000个查询只会夸大差异?与本地list()相比,R中的哈希数据结构有更好的替代方法吗?
正如Dirk所说,请使用哈希包。这样做的一个巨大限制是它使用环境(经过哈希处理)和[
方法的覆盖来模仿哈希表。但是一个环境不能包含另一个环境,因此您不能使用hash函数嵌套嵌套的哈希。
n <- 10000
keys <- matrix( sample(letters, 3*n, replace = TRUE), nrow = 3 )
keys <- apply(keys, 2, paste0, collapse = '')
value <- floor(1000*runif(n))
testHash <- as.list(value)
names(testHash) <- keys
keys <- sample(names(testHash), n, replace = TRUE)
lookupValue = testHash[keys]
print(data.frame('key', keys, 'lookup', unlist(lookupValue)))
在我的机器上,除了打印外几乎可以立即运行。您的代码以与您报告的相同的速度运行。它在做什么吗?您可以将n设置为10,然后查看输出和testHash,看看是否就此。
关于语法的注意:上面的
apply
只是一个循环,R的速度很慢。这些应用族命令的要点是表达性。后面的许多命令可能已与apply
放在一个循环中,如果是for
循环,那将是一个诱惑。在R中,请尽可能多地走出循环。使用apply family命令使之更为自然,因为该命令旨在代表某个函数对某种类型的列表的应用,而不是通用循环(是的,我知道apply
可以用于多个命令)。
write.table
)。正如其他人所说,R中的运算比循环...和w.r.t.速度,大多数apply()家庭语法只是一个漂亮的包装一个循环。
索引的东西比非索引。 (很明显,我知道。)data.table包支持数据帧类型对象的索引。
我从没用过哈希像@Allen这样的环境说明了(据我所知,我从未吸入过哈希...)
您使用的某些语法有效,但是可以加强。我认为这对速度没有任何实质性影响,但是代码更具可读性。我写的代码不是很紧,但是我做了一些编辑,例如将floor(1000*runif(1))
更改为sample(1:1000, n, replace=T)
。我并不是要书呆子,我只是按照从头开始的方式写了它。
认为下面的两种方法做同样的事情。我们都从“散列”中的键中采样了第二组键,因为这可以防止散列遗漏。您将要测试这些示例如何处理哈希重复项,因为我没有考虑太多。
require(data.table)
dtTest <- function(n) {
makeDraw <- function(x) paste(sample(letters, 3, replace=T), collapse="")
key <- sapply(1:n, makeDraw)
value <- sample(1:1000, n, replace=T)
myDataTable <- data.table(key, value, key='key')
newKeys <- sample(as.character(myDataTable$key), n, replace = TRUE)
lookupValues <- myDataTable[newKeys]
strings <- paste("key", lookupValues$key, "Lookup", lookupValues$value )
write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F )
}
#
hashTest <- function(n) {
testHash <- new.env(hash = TRUE, size = n)
for(i in 1:n) {
key <- paste(sample(letters, 3, replace = TRUE), collapse = "")
assign(key, floor(1000*runif(1)), envir = testHash)
}
keyArray <- ls(envir = testHash)
keyLen <- length(keyArray)
keys <- sample(ls(envir = testHash), n, replace = TRUE)
vals <- mget(keys, envir = testHash)
strings <- paste("key", keys, "Lookup", vals )
write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F )
}
如果我使用100,000次抽奖运行每种方法,则会得到类似以下内容:
> system.time( dtTest(1e5))
user system elapsed
2.750 0.030 2.881
> system.time(hashTest(1e5))
user system elapsed
3.670 0.030 3.861
请记住,这仍然比Perl代码慢得多,在我的PC上,Perl代码似乎在一秒钟内就可以运行100K样本。
希望以上示例对您有所帮助。如果您对why
有任何疑问,也许@ allen,@ vince和@dirk可以回答;)输入上述内容后,我意识到我没有测试@john所做的事情。所以,该死的,让我们全部做3。我将@john的代码更改为使用write.table(),这是他的代码:
johnsCode <- function(n){ keys = sapply(character(n), function(x) paste(letters[ceiling(26*runif(3))], collapse='')) value <- floor(1000*runif(n)) testHash <- as.list(value) names(testHash) <- keys keys <- names(testHash)[ceiling(n*runif(n))] lookupValue = testHash[keys] strings <- paste("key", keys, "Lookup", lookupValue ) write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F ) }
和运行时间:
> system.time(johnsCode(1e5))
user system elapsed
2.440 0.040 2.544
然后就可以了。 @john编写严格/快速的R代码!
也许以前是这样的(我不知道),但是此信息似乎不再准确了:
> d <- new.env() > d$x <- new.env() > d$x$y = 20 > d$x$y [1] 20
因此,环境现在可以提供功能强大的地图/字典。也许您会错过'['运算符,请在这种情况下使用哈希包。
此哈希包文档中的注释也可能引起您的兴趣:
R正在缓慢地使用 环境,(请参阅摘录。使用$和[[具有 已有一段时间了,最近对象可以从 环境等。但是许多构成哈希/字典的功能 仍然缺少诸如切片操作之类的[。
#!/usr/bin/Rscript
testHash <- new.env(hash = TRUE, size = 10000L)
for(i in 1:10000) {
key <- paste(sample(letters, 3, replace = TRUE), collapse = "")
assign(key, floor(1000*runif(1)), envir = testHash)
}
keyArray <- ls(envir = testHash)
keyLen <- length(keyArray)
for(j in 1:10000) {
key <- keyArray[sample(keyLen, 1)]
lookupValue <- get(key, envir = testHash)
cat(paste("key", key, "Lookup", lookupValue, "\n"))
}
它在我的机器上运行非常快,主要是设置。 (尝试一下并发布时间。)
但是,正如约翰所说,真正的问题是您必须考虑R中的向量(例如perl中的map),而他的解决方案可能是最好的。如果您确实想使用哈希,请考虑
keys <- sample(ls(envir = testHash), 10000, replace = TRUE) vals <- mget(keys, envir = testHash)
经过与上述相同的设置,这在我的机器上几乎是即时的。要全部打印,请尝试
cat(paste(keys, vals), sep="\n")
希望这会有所帮助。
艾伦
我意识到最初的问题涉及10,000种事物,但几天前Google便将我引导到这里。我尝试使用哈希程序包,并且遇到了很多困难。然后,我发现了这篇博客文章,该文章表明构建哈希可能需要花费数小时才能完成1000万以上的工作,这与我的经验相吻合:https://appsilon.com/fast-data-lookups-in-r-dplyr-vs-data-table/?utm_campaign=News&utm_medium=Community&utm_source=DataCamp.com