12

我的 R 项目变得越来越复杂,我开始寻找一些与 Java/C# 中的类或 python 中的模块等效的构造,这样我的全局命名空间就不会被那些从未在外部使用过的函数弄得乱七八糟一个特定的 .r 文件。

所以,我想我的问题是:在多大程度上可以将函数的范围限制在特定的 .r 文件或类似文件中?

我想我可以将整个 .r 文件变成一个巨大的函数,然后将函数放入其中,但这会与回显相混淆:

我的文件.r:

myfile <- function() {
    somefunction <- function(a,b,c){}
    anotherfunction <- function(a,b,c){}

    # do some stuff here...
    123
    456
    # ...
}
myfile()

输出:

> source("myfile.r",echo=T)

> myfile <- function() {
+     somefunction <- function(a,b,c){}
+     anotherfunction <- function(a,b,c){}
+ 
+     # do some stuff here...
+     # . .... [TRUNCATED] 

> myfile()
> 

您可以看到“123”没有打印出来,即使我们echo=Tsource命令中使用了。

我想知道是否还有其他更标准的构造,因为将所有内容放在一个函数中听起来不像是真正标准的东西?但也许是?此外,如果这意味着echo=T有效,那么这对我来说是一个明确的奖励。

4

3 回答 3

21

首先,正如@Spacedman 所说,你会得到一个包裹的最佳服务,但还有其他选择。

S3 方法

R 的原始“面向对象”被称为 S3。R 的大部分代码库都使用这种特定的范例。它plot()适用于各种对象。plot()是一个通用函数,R 核心团队和包开发人员可以并且已经为plot(). 严格来说,这些方法可能具有类似plot.foo()wherefoo是函数为其定义plot()方法的对象类的名称。S3 的美妙之处在于,您(几乎)不需要知道或调用plot.foo()您只是使用plot(bar),Rplot()会根据 object 的类计算出要分派到哪个方法bar

在您对问题的评论中,您提到您有一个函数,该函数populate()具有类的方法(有效),"crossvalidate"并且"prod"您将其保存在单独的.r文件中。设置它的 S3 方法是:

populate <- function(x, ...) { ## add whatever args you want/need
    UseMethod("populate")
}

populate.crossvalidate <-
    function(x, y, z, ...) { ## add args but must those of generic
    ## function code here
}

populate.prod <-
    function(x, y, z, ...) { ## add args but must have those of generic
    ## function code here
}

给定一些bar带有类的对象"prod",调用

populate(bar)

将导致 R 调用populate()(泛型),然后它会查找具有名称的函数,populate.prod因为那是bar. 它找到我们的populate.prod(),然后调度该函数,将我们最初指定的参数传递给它。

所以你看到你只使用泛型名称引用方法,而不是完整的函数名称。R 为您计算出需要调用的方法。

这两种populate()方法可以具有非常不同的参数,但严格来说它们应该具有与泛型函数相同的参数。所以在上面的例子中,所有方法都应该有参数x.... (使用公式对象的方法有一个例外,但我们在这里不必担心。)

包命名空间

从 R 2.14.0 开始,所有 R 包都有自己的命名空间,即使包作者没有提供命名空间,尽管命名空间在 R 中的存在时间比这要长得多。

在您的示例中,我们希望注册populate()泛型,它是 S3 系统的两种方法。我们还希望导出通用函数。通常我们不希望或不需要导出单个方法。因此,将您的函数弹出到包源.R文件R夹中的文件中,然后在包源的顶层创建一个名为NAMESPACE并添加以下语句的文件:

export(populate) ## export generic

S3method(populate, crossvalidate) ## register methods
S3method(populate, prod)

然后,一旦你安装了你的包,你会注意到你可以调用populate(),但是如果你尝试populate.prod()从提示符或另一个函数中直接按名称调用 etc,R 会抱怨。这是因为作为单个方法的函数尚未从命名空间中导出,因此在命名空间之外是不可见的。包中调用的任何函数populate()都可以访问您定义的方法,但包外的任何函数或代码根本看不到这些方法。如果需要,可以使用:::运算符调用非导出函数,即

mypkg:::populate.crossvalidate(foo, bar)

将起作用,mypkg您的包的名称在哪里。

老实说,您甚至不需要NAMESPACE文件,因为 R 会在您安装软件包时自动生成一个文件,该文件会自动导出所有功能。这样,您的两种方法将显示为populate.xxx()xxx特定方法在哪里)并将作为 S3 方法运行。

阅读编写 R 扩展手册中的第 1 节创建 R 包以了解所涉及的详细信息,但如果你不想这样做,你不需要做一半,特别是如果包是供你自己使用的。只需创建适当的包文件夹(即R和),将文件man粘贴到. 在您添加的位置写入单个文件.RR.Rdman

\name{Misc Functions}
\alias{populate}
\alias{populate.crossvalidate}
\alias{populate.prod}

在文件的顶部。添加\alias{}您拥有的任何其他功能。然后你需要构建和安装包。

替代使用sys.source()

虽然我不(不能!)真的推荐我在下面提到的作为长期可行的选择,但有一种替代方法可以让您.r按照最初的要求将功能与单个文件隔离开来。这是通过使用环境而不是名称空间来实现的,并且不涉及创建包。

sys.source()函数可用于从.R文件中获取 R 代码/函数并在环境中对其进行评估。当您.R的文件正在创建/定义函数时,如果您另一个环境中获取它,那么这些函数将在该环境中定义。默认情况下,它们在标准搜索路径上不可见,因此populate()定义的函数crossvalidate.R不会与populate()定义的冲突prod.R只要您使用两个不同的环境。当您需要使用一组功能时,您可以将环境分配给搜索路径,然后它将奇迹般地对所有内容可见,完成后您可以将其分离。附加其他环境,使用它,分离等。或者您可以安排在特定环境中使用诸如eval().

就像我说的那样,这不是推荐的解决方案,但它会按照您描述的方式工作。例如

## two source files that both define the same function
writeLines("populate <- function(x) 1:10", con = "crossvalidate.R")
writeLines("populate <- function(x) letters[1:10]", con = "prod.R")

## create two environments
crossvalidate <- new.env()
prod <- new.env()

## source the .R files into their respective environments
sys.source("crossvalidate.R", envir = crossvalidate)
sys.source("prod.R", envir = prod)

## show that there are no populates find-able on the search path

> ls()
[1] "crossvalidate" "prod" 
> find("populate")
character(0)

现在,附加其中一个环境并调用populate()

> attach(crossvalidate)
> populate()
 [1]  1  2  3  4  5  6  7  8  9 10
> detach(crossvalidate)

现在在另一个环境中调用该函数

> attach(prod)
> populate()
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
> detach(prod)

显然,每次你想使用一个特定的函数时,你都需要在attach()它的环境中调用它,然后再调用它detach()。这是一种痛苦。

我确实说过您可以安排在规定的环境中评估 R 代码(实际上是表达式)。例如,您可以使用eval()of with()

> with(crossvalidate, populate())
[1]  1  2  3  4  5  6  7  8  9 10

至少现在您只需要一次调用即可运行populate()您选择的版本。但是,如果用它们的全名来调用这些函数,例如populate.crossvalidate()太费力(根据您的评论),那么我敢说即使是这个with()想法也会太麻烦?无论如何,当您可以很容易地拥有自己的 R 包时,为什么还要使用它。

于 2012-10-26T18:56:04.197 回答
16

不要担心“制作包装”的复杂性。别再这样想了。你要做的是:

  1. 在您正在处理项目的文件夹中,创建一个名为“R”的文件夹
  2. 把你的 R 代码放在那里,每个文件一个函数
  3. 在您的项目目录中创建一个说明文件。查看现有示例以了解确切格式,但您只需要几个字段。
  4. 获取开发工具。install.packages("devtools")
  5. 使用开发工具。library(devtools)

现在,将函数写入 R 文件夹中的 R 文件。要将它们加载到 R 中,请不要获取它们。做load_all()。您的函数将被加载,但不会被加载到全局环境中。

编辑您的 R 文件之一,然后再做load_all()一次。这将加载 R 文件夹中的所有修改文件,从而更新您的函数。

就是这样。编辑,load_all冲洗并重复。您创建了一个包,但它非常轻量级,您不必处理 R 包构建工具的束缚和纪律。

我已经看到、使用甚至编写过尝试实现用于加载对象的轻量级打包机制的代码,但没有一个比 devtools 做得更好。

万岁哈德利!

于 2012-10-26T07:33:33.250 回答
3

您可能需要考虑制作一个包裹。作为替代方案,您可以查看环境。最后,RStudio 的项目可能更适合您。

于 2012-10-26T06:24:58.853 回答