I'm wondering why a custom smooth()
function for a custom class is not taking precedence over stats::smooth()
. Here is an example:
create_my_class = function(mydata){
class(mydata) = c("my_class", "data.frame")
return(mydata)
}
df = data.frame(x = rnorm(100), y = rnorm(100))
df = create_my_class(df)
Now, I know I can define a plot()
function for this class, and it will supersede other plot()
methods when I pass a myclass
object:
library(ggplot2)
plot.my_class = function(data){
ggplot(data, aes(x,y))
geom_point()
}
plot(df)
But why doesn't this approach work with a function called smooth.my_class()
? For instance, this simple smoothing function:
smooth.my_class = function(data){
lm(y~x, data)
}
when run as smooth(df)
throws the error "Error in smooth(df) : attempt to smooth non-numeric values", which is the same if I run stats::smooth(df)
.
Is there a bigger problem here? Can only certain function names be re-used to take a custom class?
CodePudding user response:
Yes, there is a bigger problem here. If you want to define a specific method to handle a particular class, the function in question needs to be a generic function.
For example, look at the function definition for print
:
print
#> function (x, ...)
#> UseMethod("print")
That's all there is to the function print
. When you type print(object)
into the console, this function executes, and all it does is look up the class of object
, append the class name to the word print
and look up that function. For example, if object
is of class "data.frame", then UseMethod("print")
looks for print.data.frame
, and if it exists, will call it with the arguments you supplied to print
. If no specific method exists for a class, a generic function has (or should have) a fallback default method.
The reason why your custom smooth
function doesn't work is that smooth
is not a generic function. When you run smooth
, no lookup of a class-specific method occurs, and you just get plain old stats::smooth
You could make smooth
a generic function though. The following should dispatch correctly for your custom class and in all other cases default to stats::smooth
:
smooth <- function(x, ...) UseMethod("smooth")
smooth.my_class = function(data, ...){
lm(y~x, data)
}
smooth.default <- function(x, ...) stats::smooth(x, ...)
For example if we try it on your custom class we get an lm
smooth(df)
#>
#> Call:
#> lm(formula = y ~ x, data = data)
#>
#> Coefficients:
#> (Intercept) x
#> 0.07497 0.09133
But calling it on a numeric vector invokes stats::smooth
smooth(df$x)
#> 3RS3R Tukey smoother resulting from stats::smooth(x = x)
#> used 6 iterations
#> [1] 0.08976935 0.08976935 0.08976935 -0.19641797 -0.19641797 -0.19641797
#> [7] -0.19641797 0.02547707 0.08861531 0.08861531 -0.03327923 -0.09422650
#> [13] -0.09422650 -0.15654766 -0.15654766 -0.37095531 -0.49807061 -0.87394069
#> [19] -1.18237852 -1.18237852 -0.05479660 -0.05479660 -0.05479660 0.26811991
#> [25] 0.37611915 0.37611915 0.70314430 0.70314430 0.70314430 0.70314430
#> [31] 0.70314430 0.24278460 0.24278460 0.24278460 0.51725720 0.51725720
#> [37] 0.51725720 0.51725720 0.67223056 0.67223056 0.67223056 0.67223056
#> [43] 0.67223056 0.09257700 0.08426558 0.08426558 0.08426558 0.08426558
#> [49] 0.08426558 0.51762272 0.51762272 0.51762272 0.51762272 0.51762272
#> [55] 0.51762272 0.02758035 0.02758035 0.10725655 0.13239083 0.13239083
#> [61] 0.18802296 0.18802296 0.55088073 0.55088073 0.55088073 0.55088073
#> [67] 0.55088073 0.76919007 0.76919007 0.76919007 0.48469683 0.48469683
#> [73] -0.32896249 -0.32896249 -0.31640679 -0.28686752 -0.21660688 -0.21660688
#> [79] -0.21660688 -0.21660688 -0.87848779 -0.87848779 -0.87848779 -1.69139208
#> [85] -1.77982364 -1.77982364 -1.22898324 0.33751308 0.44113212 0.44113212
#> [91] 0.85571511 0.85571511 0.85571511 0.34594270 0.29631435 0.19705764
#> [97] -0.02076665 -0.02076665 -0.02076665 -0.02076665
Created on 2022-04-24 by the reprex package (v2.0.1)