Question

In R I want to convert (coerce?) an object returned from stats::spectrum (class 'spec') into a new S4 class. The S3 class 'spec' is essentially a list of various information with mixed formats (I've commented the screen output):

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

Now let's say we define a new, S4 generator for a class named 'specS4', wherein the slot names are the names in the 'spec' object

specS4 <- setClass("specS4",
  representation = representation(freq="numeric", spec="numeric", coh="numeric", phase="numeric", kernel="numeric", df="numeric", bandwidth="numeric", 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)
)

and generate a new object from it:

psd4 <- specS4()

validObject(psd4)
# [1] TRUE

What would be the best way to assign each component of psd3 to its corresponding slot in psd4? A complication is that spectrum may return NULL for a few (known) fields; assigning these values would throw an error in checkSlotAssignment (for the representation given).

A painful solution I have is:

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)
}

Which works, even though as.specS4.spec is intentionally incomplete.

psd4c <- as.specS4(psd3)

validObject(psd4c)
# [1] TRUE

Is there a better way to achieve what as.specS4.spec does? This solution seems precarious.

Was it helpful?

Solution

I just realized how simple this actually is. Doh!

Match slotNames in the 'specS4' object with names in the 'spec' object, then assign with slot (assumes code in the question has been run):

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top