library(tidyverse) # stringr package is part of the tidyverse collection.

Illustrate S3 System – U of I Women’s Basketball Team example

  • Make sure to read the following section before referring to this lab help document.
  • In the R ecosystem, S3 is the most popular object-oriented programming (OOP) system.
  • The main components of S3: classes, generics, and methods
  • Generics, methods, and dispatch

The generic is a middleman: its job is to define the interface (i.e. the arguments) then find the right implement for the job. The implementation is called method, and the process is called method dispatch
  • To create a new S3 class, three functions are used in most classes:
    • constructor – Create a raw S3 object (new_myclass())
    • validator – Ensure the values are valid (validate_myclass())
    • helper – A user-friendly function that combines both (myclass())

U of I Women’s Basketball Team

Introducing our amazing women’s basketball team! Find more information about the team on this page.

https://fightingillini.com/sports/womens-basketball/roster

Objective: Display the profile information according to roles

  • For player, print the profile information like
    • Adalia Mckenzie is a player on the U of I Women’s Basketball Team. Her jersey number is 24. Adalia is a student in her Senior year. She is 5-10 tall (177.8 cm). Her position is Guard.
  • For staff, print the profile information like
    • Shauna Green is the Head Coach of the U of I Women’s Basketball Team.
    • Maya Solomon is a staff member of the U of I Women’s Basketball Team.
  • For all other people, like the spectator, print the information like
    • Weijia Jia is neither a player nor a staff member.

Create Player Class

Constructor Function for Player Class

new_player <- function(firstname, lastname, jersey, position,
                       studentclass, height_inches, height_cm) {
  stopifnot(is.character(firstname))
  stopifnot(is.character(lastname))
  
  structure(
   list(
      firstname = firstname,
      lastname = lastname,
      jersey = jersey,
      position = position,
      studentclass = studentclass,
      height_inches = height_inches,
      height_cm = height_cm
    ),
    class = "player"
  )

}

Validator Function for Player Class

  • If any information is missing, print ‘… is missing’.
  • If the pattern of height doesn’t follow the “feet-inches” pattern, print a warning message.
  • If the position is not one of the three categories (Guard, Forward, Center), print a warning message.
  • If the class is not one of the five categories (Freshman, Sophomore, Junior, Senior, Fifth), print a warning message.
validate_player <- function(x){
  if(is.na(x$firstname)) print("The player's first name is missing.")
  if(is.na(x$lastname)) print("The player's last name is missing.")
  if(is.na(x$jersey)) print("The player's jersey information is missing.")
  if(is.na(x$position)) print("The player's position information is missing.")
  if(is.na(x$studentclass)) print("The player's class information is missing.")
  if(is.na(x$height_inches)) print("The player's height information in inches is missing.")
  
  if(!str_to_title(x$position) %in% c("Guard", "Forward", "Center")) 
    print("The player's position information is invalid.")
  if(!str_to_title(x$studentclass) %in% c("Freshman", "Sophomore", "Junior", "Senior", "Fifth")) 
    print("The player's class information is invalid.")
  if(!grepl("^\\d+-\\d+$", x$height_inches)) # grepl() is searching for matches
    print("The player's height does not follow the 'feet-inches' pattern.")
  
  x
}

Helper Function for Player Class

First, define a function convertHeightToCm() to convert the height from the “feet-inches” pattern to centimeters.

  • Define the function convertHeightToCm()
convertHeightToCm <- function(height) {
    # Split the input string by the dash
    parts <- str_split(height, "-")[[1]] # str_split() is from stringr package
    
    # Extract feet and inches as numeric values
    feet <- as.numeric(parts[1])
    inches <- as.numeric(parts[2])
    
    # Convert feet to inches and then total inches to centimeters
    height_inches <- (feet * 12) + inches
    height_cm <- height_inches * 2.54
    
    return(height_cm)
}
  • Define the Helper Function
player <- function(firstname, lastname, jersey, position, 
                   studentclass, height) {
  firstname = str_to_title(firstname) # str_to_title() is from stringr
  lastname = str_to_title(lastname)
  jersey = as.character(jersey)
  position = str_to_title(position)
  studentclass = str_to_title(studentclass)
  height_inches = height
  height_cm = convertHeightToCm(height)   
  
  validate_player(new_player(firstname, lastname, jersey, position,
                             studentclass, height_inches, height_cm))
}

Create Staff Class

Constructor Function for Staff Class


new_staff = function(name, role, role_with_article = NULL){
  stopifnot(is.character(name))
  stopifnot(is.character(role))
  
  structure(
    list(name = name,
         role = role,
         role_with_article = role_with_article
         ),
    class = "staff"
  )
}

Validator Function for Staff Class

  • if name or role is missing or not character type, print warning message.
validate_staff <- function(x){
  if(is.na(x$name) || !is.character(x$name)) 
    print("The staff's name is not valid")
  if(is.na(x$role)|| !is.character(x$role)) 
    print("The staff's role information is not valid")
  x
}

Helper Function for Staff Class

First, define a function article_selector() to help choosing appropriate article for the role information.

article_selector <- function(role) {
 title <- switch(
   role,
   "head coach" = "the Head Coach",
   "associate head coach" = "the Associate Head Coach",
   "assistant coach" = "an Assistant Coach",
   "a staff member"
  )
 return(title)
}

The helper function for staff

staff <- function(name, role){
  name = str_to_title(name)
  role = role
  role_with_article = article_selector(role)
  
  validate_staff(new_staff(name, role, role_with_article))       
         
}

Create Spectator Class


new_spectator = function(name){
  structure(
    list(name = name),
    class = "other"
  )
}

Generic and Methods

  • Define the generic function

Typically, we define a generic function by writing code that includes a call to UseMethod().

my_generic <- function(x, ...) {
  UseMethod("my_generic")
}

But in this case, print() is a defined generic existing in R already. We can directly use it and go ahead to define methods for the custom class.

  • Define the methods
print.player = function(x){
  cat(paste0(
    x$firstname, " ", 
    x$lastname, " is a player on the U of I Women's Basketball Team. ", 
    "Her jersey number is ", x$jersey, ". ",  
    x$firstname, " is a student in her ", x$studentclass, " year. ",
    "She is ", x$height_inches, " tall (", x$height_cm," cm). ", 
    "Her position is ", x$position, "." 
               )
        )
}

print.staff = function(x){
  cat(paste0(x$name, " is ", x$role_with_article, 
               " of the U of I Women's Basketball Team." ))
}

print.other = function(x){
  cat(paste0(x$name, " is neither a player nor a staff member."))
}

Test

Define Objects

# Player

Mckenzie <- player(
  firstname = "Adalia",
  lastname = "Mckenzie",
  jersey = "24",
  position = "guard",
  studentclass = "senior",
  height = "5-10"
)

### Incorrect Usage
Bryant <- player(
  firstname = "Genesis",
  lastname = "Bryant",
  jersey = "1", 
  position = "guard",
  studentclass = "fifth",
  height = "5'6''"
  )
#> Warning in convertHeightToCm(height): NAs introduced by coercion
#> [1] "The player's height does not follow the 'feet-inches' pattern."
# Corrected
Bryant <- player(
  firstname = "Genesis",
  lastname = "Bryant",
  jersey = "1",
  position = "guard",
  studentclass = "fifth",
  height = "5-6")


# Staff

Green <- staff(
  name = "shauna green", 
  role = "head coach"
)

Solomon <- staff(
  name = "Maya Solomon", 
  role = "director of basketball operations"
)

# Spectator

Jia <- new_spectator(
  name = "Weijia Jia"
)

Display Information

print(Mckenzie)
#> Adalia Mckenzie is a player on the U of I Women's Basketball Team. Her jersey number is 24. Adalia is a student in her Senior year. She is 5-10 tall (177.8 cm). Her position is Guard.
print(Bryant)
#> Genesis Bryant is a player on the U of I Women's Basketball Team. Her jersey number is 1. Genesis is a student in her Fifth year. She is 5-6 tall (167.64 cm). Her position is Guard.
print(Green)
#> Shauna Green is the Head Coach of the U of I Women's Basketball Team.
print(Solomon)
#> Maya Solomon is a staff member of the U of I Women's Basketball Team.
print(Jia)
#> Weijia Jia is neither a player nor a staff member.
Mckenzie
#> Adalia Mckenzie is a player on the U of I Women's Basketball Team. Her jersey number is 24. Adalia is a student in her Senior year. She is 5-10 tall (177.8 cm). Her position is Guard.
Bryant
#> Genesis Bryant is a player on the U of I Women's Basketball Team. Her jersey number is 1. Genesis is a student in her Fifth year. She is 5-6 tall (167.64 cm). Her position is Guard.
Green
#> Shauna Green is the Head Coach of the U of I Women's Basketball Team.
Solomon
#> Maya Solomon is a staff member of the U of I Women's Basketball Team.
Jia
#> Weijia Jia is neither a player nor a staff member.