Search code examples
rr-s3r-s4

R Turning several functions to one object using Object Oriented Programming (S3 and S4)


I'm trying to understand how to turn functions into object oriented programming in R. So for example, how could the data and 2 functions below be turned into one object using S3 (and then S4)? (Perhaps some other simple data and functions would serve better as examples?)

data <- c(1, 2, 3)

# Function 1
adding_1 <- function(x){
  x <- x+1
}

# Function 2
subtracting_1 <- function(x){
  x <- x-1
}

And how would below functions be executed using the OOP.

data1 <- adding_1(data)
data1
data2 <- subtracting_1(data)
data2

Solution

  • In R, object-oriented programming is implemented in several very different ways.
    The S3 type of OO is the most used because it's very simple and yet does a good job at having what seems to be the same functions behave differently with different types of objects. A good reference is Advanced R by Hadley Wickham.

    In R objects have attributes. One of these attributes is the special class attribute. You can see this with

    x <- 1:3
    y <- c(1, 2, 3)
    class(x)    # "integer"
    class(y)    # "numeric"
    

    The S3 system is a function overloading system. A special function is defined, the generic. Then other functions, the methods, are defined to process objects depending or their classes. A method that must be defined is the default method.

    Here I use your example to define first a generic and then the default method.

    # Function 1
    adding_1 <- function(x, ...) UseMethod("adding_1")
    adding_1.default <- function(x, ...){
      x <- x + 1
      x
    }
    

    Now the methods for objects of class "list" and "data.frame".

    adding_1.list <- function(x, ...){
      num <- sapply(x, is.numeric)
      x[num] <- lapply(x[num], adding_1)
      x
    }
    adding_1.data.frame <- function(x, ...){
      num <- sapply(x, is.numeric)
      x[num] <- lapply(x[num], adding_1)
      x
    }
    

    And the same for subtracting_1.

    # Function 2
    subtracting_1 <- function(x, ...) UseMethod("subtracting_1")
    subtracting_1.default <- function(x){
      x <- x - 1
      x
    }
    subtracting_1.list <- function(x, ...){
      num <- sapply(x, is.numeric)
      x[num] <- lapply(x[num], subtracting_1)
      x
    }
    subtracting_1.data.frame <- function(x, ...){
      num <- sapply(x, is.numeric)
      x[num] <- lapply(x[num], subtracting_1)
      x
    }
    

    Test cases.

    When called with x as an argument (or y above) it's the default method that is called since there is no adding_1.integer nor adding_1.numeric.

    And the same goes for mat.

    But when called with a data frame, a special processing is needed in order not to have the function try to add 1 to character strings or other types of non-numeric column vectors that might be in the data frame.

    mat <- matrix(1:6, 3)
    df1 <- data.frame(x = letters[1:5], y = rnorm(5), z = 101:105)
    
    adding_1(x)
    adding_1(mat)
    adding_1(df1)
    
    subtracting_1(x)
    subtracting_1(mat)
    subtracting_1(df1)