将“规范”S3 类转换为新的 S4 类

Posted

技术标签:

【中文标题】将“规范”S3 类转换为新的 S4 类【英文标题】:Convert 'spec' S3 class to new S4 class 【发布时间】:2013-01-02 21:01:48 【问题描述】:

R 中,我想将从stats::spectrum(类'spec')返回的对象转换(强制?)为新的S4 类。 S3 类“规范”本质上是一个混合格式的各种信息列表(我已经评论了屏幕输出):

psd3 <- spectrum(rnorm(1e3), plot=FALSE)
summary(psd3)
#           Length Class  Mode     
# freq      500    -none- numeric  
# spec      500    -none- numeric  
# coh         0    -none- NULL     
# phase       0    -none- NULL     
# kernel      0    -none- NULL     
# df          1    -none- numeric  
# bandwidth   1    -none- numeric  
# n.used      1    -none- numeric  
# orig.n      1    -none- numeric  
# series      1    -none- character
# snames      0    -none- NULL     
# method      1    -none- character
# taper       1    -none- numeric  
# pad         1    -none- numeric  
# detrend     1    -none- logical  
# demean      1    -none- logical 

class(unclass(psd3))
# [1] "list"

is.object(psd3) & !isS4(psd3)
# [1] TRUE

现在假设我们为名为“specS4”的类定义了一个新的 S4 生成器,其中插槽名称是“spec”对象中的名称

specS4 <- setClass("specS4",
  representation = representation(freq="numeric", spec="numeric", coh="numeric", phase="numeric", kernel="numeric", df="numeric", band, n.used="numeric", orig.n="numeric", series="character", snames="character", method="character", taper="numeric", pad="numeric", detrend="logical", demean="logical"), 
  prototype = prototype(coh=numeric(0), phase=numeric(0), kernel=numeric(0), df=Inf, snames="", detrend=FALSE, demean=FALSE)
)

并从中生成一个新对象:

psd4 <- specS4()

validObject(psd4)
# [1] TRUE

psd3 的每个组件分配到psd4 中对应的插槽的最佳方法是什么? 一个复杂的问题是spectrum 可能会为一些(已知)字段返回NULL;分配这些值会在checkSlotAssignment 中引发错误(对于给定的表示)。

我有一个痛苦的解决方案是:

nonull.spec <- function(psd)
  stopifnot(inherits(psd, 'spec', FALSE))
  # as.numeric(NULL) --> numeric(0)
  # spec.pgram/.ar both may return NULL for these:
  psd$coh <- as.numeric(psd$coh)
  psd$phase <- as.numeric(psd$phase)
  psd$kernel <- as.numeric(psd$kernel)
  psd$snames <- as.character(psd$snames)
  return(psd)


as.specS4 <- function(psd) UseMethod("as.specS4")
as.specS4.spec <- function(psd)
  stopifnot(inherits(psd, 'spec', FALSE))
  ## deal with possible NULLs
  psd <- nonull.spec(psd)
  ## generate specS4 class
  S4spec <- specS4()
  ## (re)assign from 'spec' list
  S4spec@freq <- psd$freq
  S4spec@spec <- psd$spec
  S4spec@coh <- psd$coh
  S4spec@phase <- psd$phase
  S4spec@kernel <- psd$kernel
  S4spec@snames <- psd$snames
  # [more to assign, obviously]
  # 
  # [run a validity check...]
  #
  return(S4spec)

即使as.specS4.spec 故意不完整,这也有效。

psd4c <- as.specS4(psd3)

validObject(psd4c)
# [1] TRUE

有没有更好的方法来实现as.specS4.spec 的功能?这个解决方案似乎不稳定。

【问题讨论】:

因为对我很有帮助,这里是 Hadley 对 S4 系统的描述:github.com/hadley/devtools/wiki/S4 我的理解是你需要定义'new'和'as' S4methods。构建 S4 类涉及的不仅仅是定义结构和创建 as.name 函数。目前我认为您没有 S4 as-方法。您需要 setMethod 来创建 S4 方法。 你是对的,但 assetAs 是针对 S4 课程的。所以这有效:setClass('spec',prototype=specS4()); setAs("spec", "specS4", function(from, to) new(to, freq = from$freq, spec = from$spec, coh = as.numeric(from$coh), phase = as.numeric(from$phase), kernel = as.numeric(from$kernel), snames = as.character(from$snames)) ); as(psd3, 'specS4') 但您可能注意到我仍然需要手动分配插槽。除非我遗漏了什么,否则这并不能真正节省我的精力。 【参考方案1】:

我才意识到这实际上是多么简单。 哇!

将'specS4'对象中的slotNames与'spec'对象中的names匹配,然后用slot进行赋值(假设问题中的代码已运行):

as.specS4.spec <- function(psd)
  stopifnot(inherits(psd, 'spec', FALSE))
  psd <- nonull.spec(psd)
  S4spec <- specS4() 
  spec_slots <- slotNames(S4spec)
  spec_slots <- spec_slots[match(names(psd), spec_slots)]
  for (what in spec_slots)
    slot(S4spec, what) <- as.vector(unlist(psd[what]))
  
  return(S4spec)


psd4c2 <- as.specS4(psd3)

validObject(psd4c2)
# [1] TRUE
> all.equal(psd4c@freq, psd4c2@freq, psd3$freq)
# [1] TRUE

【讨论】:

以上是关于将“规范”S3 类转换为新的 S4 类的主要内容,如果未能解决你的问题,请参考以下文章

pandas:将 int Series 转换为新的 StringDtype

在其他包的 S4 类之间创建转换方法

r 将矢量转换为新的值范围

将字符串转换为新的数据结构并返回字符串

CVXR:as.vector(data)中的错误:没有用于将此S4类强制转换为向量的方法

如何将 1 和 0 的行转换为新的 int 列