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
7: Writing Functions in R
When I first started writing R, I had a nagging feeling that functions were just "shortcuts" for lines of code I was too lazy to type twice. This led me to a mistake that almost every R learner makes: I thought that if I changed a variable inside a function, it would automatically update that variable in my main workspace. I treated functions like macros rather than isolated environments.
"Why didn't my variable change?"
Look at this snippet. Imagine you're trying to "clean" a number by rounding it using a function:
my_number <- 10.567
round_it <- function(x) {
x <- round(x, 2)
}
round_it(my_number)
print(my_number)
# You might expect 10.57, but it's still 10.567!
It feels broken, right? You passed the variable in, the function did the work, but the original my_number didn't budge. This happens because R uses lexical scoping. When you pass my_number into the function, R creates a local copy of that value for the variable x. The function plays around with x in its own little bubble, and then, the moment the function finishes, that bubble pops and everything inside it vanishes.
Thinking in Returns and Scopes
To actually get something out of a function, you have to explicitly tell R to "hand it back" to you using the return() statement, or by making the last line of the function the value you want. I generally prefer being explicit with return() when I'm mentoring juniors—it makes the intent crystal clear.
Let's build something more useful than a rounding tool. Say you're analyzing sales data and you frequently need to calculate the percentage growth between two periods, but your data is messy and full of NA values. Instead of writing the same ifelse logic ten times, we wrap it in a function:
calculate_growth <- function(current, previous) {
# Guard clause: handle cases where previous is 0 to avoid Inf
if (previous == 0) {
return(NA)
}
growth <- (current - previous) / previous
return(growth * 100)
}
# Now we assign the result back to a variable in the global environment
jan_sales <- 100
feb_sales <- 120
feb_growth <- calculate_growth(feb_sales, jan_sales)
print(feb_growth) # 20
Notice what happened here. I didn't try to change jan_sales; I captured the output of the function into a new variable, feb_growth. That is the fundamental mental shift you need: functions are like vending machines. You put inputs in, and it spits a result out. It doesn't change the money in your pocket; it just gives you a soda.
Adding Flexibility with Default Arguments
One of the most powerful parts of writing your own functions is the ability to set defaults. I use this constantly for settings that "usually" stay the same but occasionally need to be tweaked. If you don't want to force the user to provide every single argument every time, you can assign a value in the function definition.
Let's expand our growth function to allow for a "rounding" preference:
calculate_growth_fancy <- function(current, previous, digits = 2) {
if (previous == 0) return(NA)
growth <- ((current - previous) / previous) * 100
return(round(growth, digits))
}
# Use the default (2 digits)
calculate_growth_fancy(120, 100) # 20
# Override the default for a more precise report
calculate_growth_fancy(120.456, 100.123, digits = 4) # 20.3134
By setting digits = 2, the function becomes easier to use for 90% of your tasks, but remains powerful enough for the 10% where you need precision. It's a small touch, but it's what separates "scripts that work" from "professional software."
📋 Practical Task
Build a Custom Weighted-Average Price Calculator
In financial analysis, you often can't just take a simple average of prices because you bought different amounts of an asset at different prices. You need a Weighted Average Price.
Your Task: Write a function called calc_weighted_avg that takes two vectors: prices and volumes.
- The function should multiply each price by its corresponding volume, sum those products, and then divide by the total volume.
- Constraint: Add a check to ensure that the
pricesandvolumesvectors are the same length. If they aren't, the function should return a helpful error message usingstop("Your error message here"). - Constraint: Ensure the final result is rounded to 2 decimal places.
Test your function with this data:
buy_prices <- c(150.20, 155.10, 148.00)
buy_volumes <- c(10, 20, 15)
# Expected Result: 151.83
There are no comments for now.