5

我正在构建一个包来处理多达 4 种不同类型的数据。这些类型中的每一种都是矩阵、data.frame 或树形式的合法类。根据处理数据的方式和其他实验因素,其中一些数据组件可能会丢失,但能够将此信息存储为特殊类的实例并具有识别不同组件的方法仍然非常有用数据。

方法一:

我已经尝试过一种看起来像嵌套树的增量继承结构,其中每种数据类型组合都有自己明确定义的类。这似乎很难在未来扩展额外的数据类型,并且对于新开发人员来说学习所有类名也是一个挑战,无论这些名称组织得多么好。

方法二:

第二种方法是创建一个包含所有 4 种数据类型的插槽的“主类”。为了让缺失数据的实例的槽为 NULL,似乎有必要首先在NULL类和新数据类型类之间定义一个虚拟类联合,然后使用虚拟类联合作为相关的预期类进入大师班。这是一个示例(假设每个数据类型类都已定义):

################################################################################
# Use setClassUnion to define the unholy NULL-data union as a virtual class.
################################################################################    
setClassUnion("dataClass1OrNULL", c("dataClass1", "NULL"))
setClassUnion("dataClass2OrNULL", c("dataClass2", "NULL"))
setClassUnion("dataClass3OrNULL", c("dataClass3", "NULL"))
setClassUnion("dataClass4OrNULL", c("dataClass4", "NULL"))
################################################################################
# Now define the master class with all 4 slots, and 
# also the possibility of empty (NULL) slots and an explicity prototype for
# slots to be set to NULL if they are not provided at instantiation.
################################################################################
setClass(Class="theMasterClass", 
    representation=representation(
        slot1="dataClass1OrNULL",
        slot2="dataClass2OrNULL",
        slot3="dataClass3OrNULL",
        slot4="dataClass4OrNULL"),
    prototype=prototype(slot1=NULL, slot2=NULL, slot3=NULL, slot4=NULL)
)
################################################################################

所以这个问题可以改写为:

这些方法是否有更有效和/或更灵活的替代方案?

这个例子是从一个关于将 slot 的默认值设置为 NULL的 SO question 的答案修改而来的。这个问题的不同之处在于,我有兴趣了解 R 中创建具有在需要时可以为空的插槽的类的最佳选项,尽管在所有其他非空情况下都需要特定的复杂类。

4

1 回答 1

2

在我看来...

方法二

它有点违背了采用正式类系统的目的,然后创建一个包含定义不明确的槽('A'或NULL)的类。至少我会尝试使 DataClass1 具有类似“NULL”的默认值。举个简单的例子,这里的默认值是一个长度为零的数值向量。

setClass("DataClass1", representation=representation(x="numeric"))
DataClass1 <- function(x=numeric(), ...) {
    new("DataClass1", x=x, ...)
}

然后

setClass("MasterClass1", representation=representation(dataClass1="DataClass1"))
MasterClass1 <- function(dataClass1=DataClass1(), ...) {
    new("MasterClass1", dataClass1=dataClass1, ...)
}

这样做的一个好处是方法不必测试插槽中的实例是 NULL 还是“DataClass1”

setMethod(length, "DataClass1", function(x) length(x@x))
setMethod(length, "MasterClass1", function(x) length(x@dataClass1))

> length(MasterClass1())
[1] 0
> length(MasterClass1(DataClass1(1:5)))
[1] 5

为了回应您关于在用户访问“空”插槽时警告用户的评论,并记住用户通常希望函数做某事而不是告诉他们他们做错了什么,我可能会返回DataClass1()准确反映状态的空对象的对象。也许一种show方法会提供一个概述来加强插槽的状态——DataClass1:无。如果 MasterClass1 代表一种协调几种不同分析的方式,这似乎特别合适,用户可能只做一些不同的分析。

这种方法(或您的方法 2)的一个限制是您没有获得方法分派 - 您不能编写仅适用于DataClass1具有非零长度实例的实例的方法,并且被迫做一些一种手动调度(例如,使用ifor switch)。对于开发人员来说,这似乎是一个限制,但它也适用于用户——用户不知道哪些操作是唯一适用于具有非零长度 DataClass1 实例的 MasterClass1 实例。

方法一

当您说层次结构中的类名称会让您的用户感到困惑时,似乎这可能指向一个更基本的问题——您过于努力地对数据类型进行全面表示;用户将永远无法跟踪 ClassWithMatrixDataFrameAndTree,因为它不代表他们查看数据的方式。这也许是一个降低你的雄心壮志的机会,只真正解决你正在调查的领域中最突出的部分。或者也许是一个重新思考用户可能如何思考并与他们收集的数据交互的机会,并使用界面(用户看到的)与实现(你如何'

撇开类的命名和数量不谈,当您说“将来很难扩展其他数据类型”时,我想知道 S4 类的某些细微差别是否让您感到困惑?简短的解决方案是避免编写自己的initialize方法,并依靠构造函数来完成棘手的工作,类似于

setClass("A", representation(x="numeric"))
setClass("B", representation(y="numeric"), contains="A")

A <- function(x = numeric(), ...) new("A", x=x, ...)
B <- function(a = A(), y = numeric(), ...) new("B", a, y=y, ...)

接着

> B(A(1:5), 10)
An object of class "B"
Slot "y":
[1] 10

Slot "x":
[1] 1 2 3 4 5
于 2011-12-01T01:40:42.837 回答