Generic S3 function for multiple classes - r

I have a generic function like this:
convert <- function(x) UseMethod("simplifyResultConvert")
convert.default <- function(x) {
x
}
convert.POSIXct <- function(x) {
as.character(x)
}
convert.factor <- function(x) {
as.character(x)
}
convert.Date <- function(x) {
as.character(x)
}
Is there any way to simplify it by making one generic for type: POSIXct, Date and factor?
To make it clear: I need something like:
convert.(POSIXct || factor || date) <- funciton(x) {as.character(x)}

Write it like this:
convert.default <- function(x) x
convert.Date <-
convert.factor <-
convert.POSIXct <- function(x) as.character(x)
A further simplification would be:
convert.default <- identity
convert.Date <-
convert.factor <-
convert.POSIXct <- as.character

You can put a test of the class in the default method, for example:
convert.default <- function(x) {
if (inherits(x, "POSIXct") ||
inherits(x, "factor") ||
inherits(x, "Date"))
as.character(x)
else
x
}
This is not quite the same as what you want, because an object could have class c("POSIXct", "other") and if there was a convert.other() method set, it would be called instead of the default.
If you really want the behaviour you asked for, you need the three functions, but you can save a bit of typing by using
convert.Date <- convert.factor <- convert.POSIXct <- function(x) {
as.character(x)
}

Since there are no super-classes in S3, this is not possible with S3. However, you can easily do this with S4 which allows defining super-classes:
setGeneric("convert", function(object) {
standardGeneric("convert")
})
setClassUnion("fooClasses", members = c("factor", "Date", "POSIXt")) #POSIXt is a super-class
setMethod("convert", signature(object = "fooClasses"), function(object) {
as.character(object)
})
class(convert(as.Date("2010-10-10")))
#[1] "character"
class(convert(as.POSIXct("2010-10-10")))
#[1] "character"

Related

How can I change the behavior of the $ operator in environments?

I want to override the behavior of the dollar operator, so that if I have
x <- new.env()
x$foo <- 3
will e.g. call something. I tried to look for possible functions such as $, but my knowledge of the internals is not good enough.
I tried this:
`$` <- function(a, b) {
res <- .Primitive("$")(a, b);
print(res);
if(is.null(res)) { print("null!") };
return(res)
}
It kind of seem to work, but:
> x$foobar
NULL
[1] "null!"
NULL
> x$foobar <- 3
> x$foobar
NULL
[1] "null!"
NULL
>
So it seems to stay null despite the override.
Normal behavior of R's environments:
myenv <- new.env(parent = emptyenv())
myenv$foo <- 3
class(myenv)
# [1] "environment"
myenv$foo
# [1] 3
myenv$foobar
# NULL
Let's define a super-class (I'll name it environment2, feel free to be creative here) and override $ for that class:
class(myenv) <- c("environment2", "environment")
`$.environment2` <- function(x, name) {
stopifnot(name %in% names(x))
NextMethod()
}
myenv$foo
# [1] 3
myenv$foobar
# Error in `$.environment2`(myenv, foobar) : name %in% names(x) is not TRUE
You can easily clean up that error if you'd like, either using an if statement with stop, or (in R-4 or newer) naming the conditions in stopifnot.
`$.environment2` <- function(x, name) {
if (!name %in% names(x)) stop("something meaningful", call. = FALSE)
NextMethod()
}
`$.environment2` <- function(x, name) {
stopifnot(
"something meaningful" = name %in% names(x)
)
NextMethod()
}
### both render
myenv$foobar
# Error in `$.environment2`(myenv, foobar) : something meaningful
They are relatively equivalent, but with if/stop, you can reduce the error context:
`$.environment2` <- function(x, name) {
if (!name %in% names(x)) stop("something meaningful", call. = FALSE)
NextMethod()
}
myenv$foobar
# Error: something meaningful

How to rename a column in a ts object

I am working with objects of class ts in R.
Is there code I can use to change column names in this kind of an object?
For a data frame, I would use something like this:
Shipper_City <- rename(Shipper_City,"ShipCity_Old" = "ShipCity")
Use the 'colnames()' function
You want to use colnames(), because you're dealing with a matrix in case of a ts object:
colnames(data) <- c("ColName1", "ColName2")
Hope this helps.
Here is a function that I created that might be useful for you.
rename.ts <- function(ts, ...){
if (inherits(ts, "mts")) {
x <- list(...)
old_names <- names(x)
if (all(old_names %in% colnames(ts))) {
id_old_names <- which(old_names %in% colnames(ts))
colnames(ts)[id_old_names] <- unname(unlist(x))
} else {
stop("You must provide valid column names")
}
} else {
stop("You must provide a mts object as argument")
}
ts
}
# Example
mts <- ts(data = mtcars, start = 2013, frequency = 4)
rename.ts(mts, "mpg" = "mpg2", "cyl" = "cyl2")

Giving arguments from "..." argument to right function in R [duplicate]

This question already has answers here:
Split up `...` arguments and distribute to multiple functions
(4 answers)
Closed 6 years ago.
I have a function to compute the correlation of matrix of both categorical and continuous variables:
correlation <- function(matrix, ...) {
xx <- do.call(rbind, lapply(colnames(mtrc), function(ex_i) {
ty_i <- wtype(matrix, ex_i)
yy <- sapply(colnames(mtrc), function(ex_j) {
ty_j <- wtype(matrix, ex_j)
if(ty_i == "numeric" & ty_j == "numeric") {
cor(mtrc[ , c(ex_i, ex_j)], ...)[1, 2]
} else if(ty_i == "factor" & ty_j == "factor") {
cramersV(table(mtrc[ , c(ex_i, ex_j)]), ...)
} else {
fm <- paste(ex_i, "~", ex_j)
if(ty_i == "factor") {
fm <- paste(ex_j, "~", ex_i)
}
fm <- lm(fm, data = mtrc[ , c(ex_i, ex_j)], ...)
lm.beta(fm)
}
})
names(yy) <- colnames(mtrc)
yy
}))
rownames(xx) <- colnames(mtrc)
xx
}
My question is how to pass, properly, the argument ... to cor, cramerV and lm. Since the argument's names of these three functions do not match if the user gives an argument for cor and there is a categorical variable in the matrix, the cramerV or lm raises an error (unused argument...).
So... I'm open to any solution or idea you can have.
I did not realize that there was an excellent question by Richard Scriven at 2014: Split up `...` arguments and distribute to multiple functions, when I made my answer below. So yes, this is a duplicated question. But I will keep my answer here, as it represents what I thought (and what I think).
Original answer
I think this is better, by giving your correlation function a finer control:
correlation <- function(matrix, cor.opt = list(), cramersV.opt = list(), lm.opt = list()) {
xx <- do.call(rbind, lapply(colnames(mtrc), function(ex_i) {
ty_i <- wtype(matrix, ex_i)
yy <- sapply(colnames(mtrc), function(ex_j) {
ty_j <- wtype(matrix, ex_j)
if(ty_i == "numeric" & ty_j == "numeric") {
do.call("cor", c(list(x = mtrc[ , c(ex_i, ex_j)]), cor.opt))[1, 2]
} else if(ty_i == "factor" & ty_j == "factor") {
do.call("cramersV", c(list(x = table(mtrc[ , c(ex_i, ex_j)])), cramersV.opt))
} else {
fm <- paste(ex_i, "~", ex_j)
if(ty_i == "factor") {
fm <- paste(ex_j, "~", ex_i)
}
fm <- do.call("lm", c(list(formula = fm, data = mtrc[ , c(ex_i, ex_j)]), lm.opt))
lm.beta(fm)
}
})
names(yy) <- colnames(mtrc)
yy
}))
rownames(xx) <- colnames(mtrc)
xx
}
You can pass different arguments intended for different functions via arguments cor.opt, cramersV.opt and lm.opt. Then, inside your function correlation, use do.call() for all relevant function call.
Comment
I like #Roland's idea. He chooses to use ..., while splitting list(...) according to formal arguments of different functions. On the other hand, I have asked you to manually specify those arguments into different lists. In the end, both of us ask you to use do.call() for function call.
Roland's idea is broadly applicable, as it is easier to extend to more functions requiring ....

call a function from a vector of given functions in R

have the following function:
setTypes <- function(df2, ...) {
fns <- as.list(substitute(list(...)))
for(i in 1:length(df2)) {
if(fns[i] == '') {
next
}
df2[i,] <- fns[i](df2[i,])
}
return(df2)
}
want to do this:
test<-setTypes(sls,c('','as.Date','','','as.numeric','as.numeric'))
idea is to change the types of the fields in a data frame without having to do sls$field <- as.numeric(sls$field) for every field.
I had written a function like this that worked:
fn <- function(t) {
return(t("55.55000"))
}
and the output is this:
> fn(as.numeric)
[1] 55.55
however, i can't figure out why either doing variable length argument as a list and calling it as list[index](input) doesn't work. or even passing a vector of functions like c(as.Date, as.numeric, as.character) and doing c[1]('2015-10-10') # as.Date('2015-10-10')
I am receiving the error 'attempt to apply non-function'.. I've also tried using call but to no avail. Help?
The problem is that class(c[1]) is a list use c[[1]] instead
Example code
v <- c(as.numeric,as.character)
v[[1]]("1")
v[[2]](1)
EDIT
Your example should be:
setTypes <- function(df2, ...) {
fns <- list(...)
for(i in 1:NCOL(df2)) {
if(is.function(fns[[i]])) {
df2[,i] <- fns[[i]](df2[,i])
}
}
return(df2)
}
df <- data.frame(v1 = c(1,2), v2 = c("1","2"))
setTypes(df,as.character,'',as.numeric)

Add rownames and dim to S3 class

Based on this question Link data.frame and matrix (Accepted answer), I tried to add dim to S3 class with this command:
dim.JoinedUp <- function(x)
{
print(paste(dim(x$data_frame), dim(x$matrix)))
}
This function worked but there is a problem with rownames:
rownames.JoinedUp <- function(x)
{
print(rownames(x$data_frame))
}
I get the dim results when I type rownames(new_obj).
That is because rownames isn't an S3 method. See the definition of rownames:
function (x, do.NULL = TRUE, prefix = "row")
{
dn <- dimnames(x)
if (!is.null(dn[[1L]]))
dn[[1L]]
else {
nr <- NROW(x)
if (do.NULL)
NULL
else if (nr > 0L)
paste0(prefix, seq_len(nr))
else character()
}
}
<bytecode: 0x3d0f2b0>
<environment: namespace:base>
It calls dimnames, so you will have to create an method dimnames for your class. Something like:
dimnames.JoinedUp <- function(x) {
dimnames(x$data_frame)
}

Resources