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.
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