在最近与同学的对话中,我一直主张避免全局,除了存储常量.这是一种典型的应用统计类型程序,每个人编写自己的代码和项目大小都很小,所以人们很难看到由于草率的习惯造成的麻烦.
在谈论避免使用全局变量时,我主要关注全局变量可能导致问题的以下原因,但我想在R和/或Stata中使用一些示例来遵循原则(以及您可能认为重要的任何其他原则) ),我很难想出可信的人.
对这个问题的一个有用的答案是一个可重现的,自包含的代码片段,其中全局变量会导致特定类型的麻烦,理想情况下是另一个代码片段,其中问题得到纠正.如有必要,我可以生成更正的解决方案,因此问题的示例更为重要.
相关链接:
我第一次尝试了Rcpp功能inline
,它解决了我的速度问题(感谢Dirk!):
R:将负值替换为零
初始版本看起来像这样:
library(inline)
cpp_if_src <- '
Rcpp::NumericVector xa(a);
int n_xa = xa.size();
for(int i=0; i < n_xa; i++) {
if(xa[i]<0) xa[i] = 0;
}
return xa;
'
cpp_if <- cxxfunction(signature(a="numeric"), cpp_if_src, plugin="Rcpp")
Run Code Online (Sandbox Code Playgroud)
但是当被调用时cpp_if(p)
,它会p
用输出覆盖,这不是预期的.所以我认为它是通过引用传递的.
所以我用以下版本修复它:
library(inline)
cpp_if_src <- '
Rcpp::NumericVector xa(a);
int n_xa = xa.size();
Rcpp::NumericVector xr(a);
for(int i=0; i < n_xa; i++) {
if(xr[i]<0) xr[i] = 0;
}
return xr;
'
cpp_if <- cxxfunction(signature(a="numeric"), cpp_if_src, plugin="Rcpp")
Run Code Online (Sandbox Code Playgroud)
这似乎有效.但是现在,当我将其重新加载到R中时,原始版本不再覆盖其输入(即,相同的确切代码现在不会覆盖其输入):
> cpp_if_src <- '
+ Rcpp::NumericVector …
Run Code Online (Sandbox Code Playgroud) 我需要计算一个长度我事先不知道的向量的条目.如何有效地做到这一点?
一个简单的解决方案是"增长"它:从一个小的或空的向量开始,并连续添加新的条目,直到达到停止标准.例如:
foo <- numeric(0)
while ( sum(foo) < 100 ) foo <- c(foo,runif(1))
length(foo)
# 195
Run Code Online (Sandbox Code Playgroud)
然而,出于性能原因,R中的"增长"载体是不受欢迎的.
当然,我可以"以块的形式增长":预先分配一个"大小合适"的矢量,填充它,当它满时加倍它的长度,最后将其缩小到大小.但这感觉容易出错,并且会产生不优雅的代码.
有没有更好或规范的方法来做到这一点?(在我的实际应用中,当然,计算和停止标准有点复杂.)
回复一些有用的评论
即使您事先不知道长度,您是否知道它理论上可能具有的最大长度?在这种情况下,我倾向于使用该长度初始化向量,并且在循环切割NA之后或基于最新的索引值移除未使用的条目.
不,事先不知道最大长度.
随着向量的增长,你需要保留所有的值吗?
是的,我愿意.
那么
rand_num <- runif(300); rand_num[cumsum(rand_num) < 100]
你选择一个足够大的向量,你知道条件满足的概率很高吗?你当然可以检查一下,如果不符合则使用更大的数字.我已经测试过,直到runif(10000)
它仍然比"增长"更快.
我的实际用例涉及动态计算,我不能简单地向量化(否则我不会问).
具体来说,为了近似负二项式随机变量的卷积,我需要计算2007年Furman中定理2中整数随机变量$ K $的概率质量,直到高累积概率.这些质量$ pr_k $涉及一些错综复杂的递归总和.
使用top
,我在以下代码块的注释中指定的特定点手动测量了以下内存使用情况:
x <- matrix(rnorm(1e9),nrow=1e4)
#~15gb
gc()
# ~7gb after gc()
y <- as.vector(x)
gc()
#~15gb after gc()
Run Code Online (Sandbox Code Playgroud)
很明显,这rnorm(1e9)
是一个 ~7gb 的向量,然后被复制以创建矩阵。gc()
删除原始向量,因为它没有分配给任何东西。as.vector(x)
然后强制并将数据复制到向量。
我的问题是,为什么这三个对象不能都指向同一个内存块(至少在一个被修改之前)?矩阵真的只是一个带有一些额外元数据的向量吗?
这是 R 版本 3.6.2
编辑:也在 4.0.3 中进行了测试,结果相同。
我有一个经常出现的情况,我在一组R代码的顶部设置了一个值,该代码用于对一个或多个数据帧进行子集化.像这样的东西:
city_code <- "202"
Run Code Online (Sandbox Code Playgroud)
在整个过程结束时,我想将结果保存在一个适当命名的数据框中,比如说,基于将"city_code"附加到公共存根.
city_results <- paste("city_stats", city_code, sep = "")
Run Code Online (Sandbox Code Playgroud)
我的问题是我无法弄清楚如何将结果数据框重命名为'city_results'的值.有很多关于如何重命名数据框列的信息,但没有关于如何重命名数据框本身的信息.基于提出的答案,这里有一个澄清:
谢谢,@迈克明智.有助于学习哈德利的高级R,手头有一个具体的问题.
library(dplyr)
gear_code <- 4
gear_subset <- paste("mtcars_", gear_code, sep = "")
mtcars_subset <- mtcars %>% filter(gear == gear_code)
head(mtcars_subset)
write.csv(mtcars_subset, file = paste(gear_subset, ".csv", sep = ""))
Run Code Online (Sandbox Code Playgroud)
这让我可以将子集写入适当命名的csv文件.但是,您的建议有点可行,但我不能,例如,使用新名称引用data.frame:
assign(gear_subset, mtcars_subset)
head(gear_subset)
Run Code Online (Sandbox Code Playgroud) 我们有一个非常大的数据框架df
,可以按因子分割.在由此拆分创建的数据帧的每个子集上,我们需要执行操作以增加该子集的行数,直到它确定为止length
.之后,我们rbind
将子集获得更大的版本df
.
有没有办法在不使用内部函数的情况下快速完成此操作?
假设我们的子集操作(在单独的.R文件中)是:
foo <- function(df) { magic }
我们想出了几种方法:
1)
df <- split(df, factor)
df <- lapply(df, foo)
rbindlist(df)
Run Code Online (Sandbox Code Playgroud)
2)
assign('list.df', list(), envir=.GlobalEnv)
assign('i', 1, envir=.GlobalEnv)
dplyr::group_by(df, factor)
dplyr::mutate(df, foo.list(df.col))
df <- rbindlist(list.df)
rm('list.df', envir=.GlobalEnv)
rm('i', envir=.GlobalEnv)
(In a separate file)
foo.list <- function(df.cols) {
magic;
list.df[[i]] <<- magic.df
i <<- i + 1
return(dummy)
}
Run Code Online (Sandbox Code Playgroud)
第一种方法的问题是时间问题.lapply只需要太长时间才能真正理想(使用我们的数据集大约一个小时).
第二种方法的问题是篡改用户的全球环境的非常不希望的副作用.它明显更快,但如果可以的话,这是我们宁愿避免的.
我们也试过传递列表并计算变量,然后substitute
用父环境中的变量尝试它们(一种破解R的缺乏传递引用).
我们已经研究了一些可能相关的SO问题(R将函数应用于数据框的子集,计算数据框的子集,R:通过引用传递 …
通过将子集操作从基本data.frame
操作转移到data.table
操作,我已经实现了大幅加速(~6.5倍).但我想知道我是否可以在记忆方面得到任何改善.
我的理解是R本身并没有通过参考传递(例如,见这里).所以,我正在寻找一种方法(没有重写复杂的函数Rcpp
)来做到这一点.data.table
提供了一些改进[ 在编辑我的问题后包括@joshua ulrich在下面捕获的拼写错误 ].但是如果可能的话,我正在寻找更大的改进.
在我的实际使用案例中,我正在通过模拟退火进行多个数据集的并行仿真.由于开发时间的增加和技术债务的增加,我宁愿不在Rcpp中重写模拟退火和损失函数计算.
我主要关心的是从数据集中删除一些观察子集并添加另一个观察子集.这里给出了一个非常简单(荒谬)的例子.有没有办法减少内存使用量?我当前的用法似乎是按值传递,因此内存使用量(RAM)大约翻了一番.
library(data.table)
set.seed(444L)
df1 <- data.frame(matrix(rnorm(1e7), ncol= 10))
df2 <- data.table(matrix(rnorm(1e7), ncol= 10))
prof_func <- function(df) {
s1 <- sample(1:nrow(df), size= 500, replace=F)
s2 <- sample(1:nrow(df), size= 500, replace=F)
return(rbind(df[-s1,], df[s2,]))
}
dt_m <- df_m <- vector("numeric", length= 500L)
for (i in 1:500) {
Rprof("./DF_mem.out", memory.profiling = TRUE)
y <- …
Run Code Online (Sandbox Code Playgroud) Hadley的新pryr包显示变量的地址非常适合分析.我发现无论何时将变量传递给函数,无论该函数做什么,都会创建该变量的副本.此外,如果函数体将变量传递给另一个函数,则会生成另一个副本.这是一个明显的例子
n = 100000
p = 100
bar = function(X) {
print(pryr::address(X))
}
foo = function(X) {
print(pryr::address(X))
bar(X)
}
X = matrix(rnorm(n*p), n, p)
print(pryr::address(X))
foo(X)
Run Code Online (Sandbox Code Playgroud)
哪个生成
> X = matrix(rnorm(n*p), n, p)
> print(pryr::address(X))
[1] "0x7f5f6ce0f010"
> foo(X)
[1] "0x92f6d70"
[1] "0x92f3650"
Run Code Online (Sandbox Code Playgroud)
尽管功能没有做任何事情,地址每次都会改变.我对这种行为感到困惑,因为我听说R描述为写入时的副本 - 因此可以传递变量,但只有当函数想要写入该变量时才会生成副本.这些函数调用发生了什么?
为了获得最佳的R开发,最好不要编写多个小函数,而是将内容保存在一个函数中?我也发现了一些关于Reference Classes的讨论,但我看到很少有R开发人员使用它.是否有另一种有效的方法来传递我错过的变量?
r ×8
dplyr ×2
allocation ×1
data.table ×1
dataframe ×1
performance ×1
rcpp ×1
scope ×1
stata ×1
vector ×1