R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
122: S3 Classes and Methods In Depth
I've always found S3 to be one of the most "R" things about R. It's not a formal object-oriented system like Java or Python; it's essentially a polite agreement between the programmer and the R interpreter. If you give an object a specific class attribute, R will look for functions that match that class. Let's get our hands dirty by trying to build something that feels natural, and see where it breaks.
The List that Thinks it's Something Else
Imagine we're building a tool to track sensor readings. I want my readings to have a value and a unit, but I don't want them to just be anonymous lists. I want them to be "SensorReading" objects.
# I'll start by creating a simple list
my_reading <- list(value = 23.5, unit = "Celsius", timestamp = Sys.time())
# Now, I'll just tell R this is a 'SensorReading'
class(my_reading) <- "SensorReading"
print(my_reading)
If you run that, you'll notice something boring: it still prints exactly like a list. This is my first point of frustration. I've told R it's a SensorReading, but print() doesn't seem to care. Why? Because print is a generic function, and it hasn't been told how to handle my new class yet. It's falling back to the default method for lists.
Teaching R How to Print My Object
In S3, the convention is generic.class. If I want to change how print() behaves for SensorReading, I need to write a function called print.SensorReading.
print.SensorReading <- function(x, ...) {
cat("Sensor Reading [", as.character(x$timestamp), "]\n", sep = "")
cat("Value:", x$value, x$unit, "\n")
invisible(x) # It's a good habit to return the object invisibly
}
# Let's try it again
print(my_reading)
Now it works! When I call print(my_reading), R looks at the class of my_reading, sees "SensorReading", and searches for a function named print.SensorReading. I didn't have to change the call to print(); I just changed the environment. This is the "magic" of S3 dispatch.
Building a Generic Dispatcher
Printing is great, but what if I want a custom action? Let's say I want a function called convert() that changes the units of my sensor reading. I might try this:
convert.SensorReading <- function(x, to_unit) {
if (to_unit == "Fahrenheit" && x$unit == "Celsius") {
x$value <- (x$value * 9/5) + 32
x$unit <- "Fahrenheit"
}
return(x)
}
# I'll try to use it...
convert(my_reading, "Fahrenheit")
# Error in convert(my_reading, "Fahrenheit") : could not find function "convert"
And there it is—the classic S3 gotcha. I wrote the method, but I forgot to create the generic. print() was already a generic provided by R, but convert() doesn't exist. I need to tell R that convert is a function that should dispatch based on the class of its first argument.
convert <- function(x, ...) {
UseMethod("convert")
}
# Now this should actually work
my_reading_f <- convert(my_reading, "Fahrenheit")
print(my_reading_f)
By calling UseMethod("convert"), I'm telling R: "Look at the class of x and find the corresponding convert.class function."
The Safety Net of Class Inheritance
One of the most powerful parts of S3 is that an object can have multiple classes. This creates a hierarchy. Let's say I have a specific type of sensor—a PressureSensor. I want it to use its own specific conversion logic, but if I don't provide one, I'm happy for it to use the general SensorReading logic.
# I'll give this object two classes
pressure_reading <- list(value = 1013, unit = "hPa", timestamp = Sys.time())
class(pressure_reading) <- c("PressureSensor", "SensorReading")
# I haven't defined print.PressureSensor, so what happens?
print(pressure_reading)
R looks for print.PressureSensor. It doesn't find it. Instead of crashing, it looks at the next class in the vector: SensorReading. It finds print.SensorReading and uses that. This is essentially inheritance. I can define a very specific behavior for a subclass, or let it "bubble up" to a more general class. It keeps the code DRY (Don't Repeat Yourself) and allows for flexible defaults.
📋 Practical Task
Building a Custom 'Employee' HR System
You need to build a small S3-based system to manage employee data. Your task is to implement the following:
- Create a constructor function
new_employee(name, salary, role)that returns a list with the class"Employee". - Create a generic function called
calculate_bonus(). - Implement a method
calculate_bonus.Employeethat returns 10% of the employee's salary. - Create a second class
"Manager"that inherits from"Employee"(the class vector should bec("Manager", "Employee")). - Implement a method
calculate_bonus.Managerthat returns 20% of the salary. - Test your system: create one standard employee and one manager, and use
calculate_bonus()on both to ensure the correct percentage is applied based on their class.
There are no comments for now.