I ran into this problem and wanted a more robust solution. Specifically, I wanted a function that worked identically to apply
except when a zoo
object (or object that inherits from zoo
, such as xts
) was passed in the argument X
. In this case, the function should return a zoo
(or xts
etc.) object whenever possible. The result should also retain any additional attributes that the user may have attached to the original zoo
object.
There are several challenges. First, depending on what's passed to apply
via the arguments MARGIN
and FUN
, apply
might return a list
, a vector
, or an array
with dimensions greater than 2. See the apply
documentation for more details.
So, if you think about it, the only time result from apply
can be returned as a zoo
object is if the result is either a matrix
with nrow(result) == nrow(originalZooObject)
or a vector with length(result) == nrow(originalZooObject)
. Otherwise, you can't be sure how to add the index
values of the original zoo
object to the result from apply
.
I came up with the following code which retains all of the applicable attributes of the original zoo
object in the result. Therefore, it will also work with most S3 classes built atop zoo
, for instance, xts
.
The only thing I am aware of that won't work well is that the user may have attached an attribute that applies to the columns of the original zoo
object. If FUN
consolidates columns, then the user attribute might not be appropriate for the columns of the result.
Although I used the naming convention for dispatch from a generic function, apply
is not a generic, so apply.zoo
is designed to be called directly. If one were to override the apply
function with generic version of apply
, then apply.zoo
would run automatically any time a zoo
object were passed to apply
.
apply.zoo = function(X, MARGIN, FUN, ...) {
result = apply(as.matrix(X), MARGIN, FUN, ...)
# if result is not a list and X is a zoo object, we can try to convert result
# to zoo.
if(!is.list(result) | is.zoo(X)) {
# if FUN was applied across rows and has the right length but simplfied to a
# vector, convert result back to a matrix
if(is.null(dim(result)) & MARGIN[1] == 1 & length(result) == nrow(X)) {
result = matrix(result, ncol = 1, dimnames = list(NULL, "result"))
}
# if we don't have a matrix at this point, we can't convert back to a zoo object.
if(is.matrix(result)) {
# the matrix returned by apply sometimes has dates as columns and series as
# rows. Check for this and transpose.
if(identical(dimnames(result)[[1]], dimnames(X)[[2]])) result = t(result)
# if result has maintained the same number of rows, it can be restored to a
# zoo object.
if(identical(nrow(X), nrow(result))){
# first ensure the dimname of rows is NULL
dimnames(result)[1] = list(NULL)
# then restore all attributes, other than dim and dimnames
result =
do.call(
structure,
c(
list(result),
attributes(X)[-which(names(attributes(X)) %in% c("dim", "dimnames"))]
)
)
}
}
}
result
}