Skip to Content
Course content

249: Contributing Fixes to Existing R Packages

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Most of your time as an R developer won't be spent writing packages from scratch; it'll be spent using other people's packages and eventually hitting a wall where a function doesn't do what the documentation claims it does. When that happens, you have two choices: write a wrapper to hide the bug, or actually fix the source. I always lean toward the latter. It's cleaner, and it earns you some respect in the community.

Pinpointing the failure

Let's say we're using a hypothetical utility package called textutils. It has a function called trim_and_cap() that is supposed to trim whitespace and capitalize the first letter of a string. It works great for single strings, but I noticed it crashes when I pass it a character vector containing an NA.

# This works fine
textutils::trim_and_cap("  hello") 
# [1] "Hello"

# This throws an error or returns unexpected results
textutils::trim_and_cap(c("  hello", NA))
# Error in trim_and_cap: argument is not a character string

Before I even touch the source code, I need a Minimal Reproducible Example (MRE). If I can't reproduce the bug in three lines of code, I can't be sure I've fixed it later. The snippet above is my MRE.

Setting up the workspace

I don't just edit the package in my site-library—that's a recipe for disaster. Instead, I fork the repository on GitHub and clone it locally. I'll use devtools to load the package in a way that lets me edit the functions in real-time.

# Clone the repo and open the project in RStudio
# Then, load the package for development
devtools::load_all()

By using load_all(), I'm essentially simulating the package being installed, but any changes I make to the R/ directory are immediately available in my global environment.

The "quick fix" mistake

I dig into the source code for trim_and_cap() and see this:

trim_and_cap <- function(x) {
  x <- trimws(x)
  paste0(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)))
}

The issue is that substr() handles NAs okay, but the overall logic isn't explicitly guarding against them in a way that plays nice with the paste0 call. I'll try a quick fix by adding a simple if check to skip NA values.

# My first (wrong) attempt
trim_and_cap <- function(x) {
  if (is.na(x)) return(NA) 
  x <- trimws(x)
  paste0(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)))
}

I run my MRE again, and R screams at me: Warning message: the condition has length > 1 and only the first element will be used. I forgot the golden rule of R: vectorization. if() only checks a single logical value. Since I'm passing a vector c(" hello", NA), is.na(x) returns a logical vector, and the if statement chokes.

Correcting for vectorization

I need to handle the NAs without breaking the vector. I could use a loop, but that's slow and un-idiomatic. Instead, I'll use ifelse() or just let the natural vectorization of substr and toupper do the work, while ensuring I'm not accidentally introducing "NA" (the string) where I want NA (the logical constant).

Actually, the cleanest way here is to just let R's vectorization happen and ensure the substr doesn't create weird artifacts. Let's try this approach:

trim_and_cap <- function(x) {
  x <- trimws(x)
  # We use vapply or a vectorized approach to ensure we keep NA as NA
  res <- paste0(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)))
  # Ensure that original NAs remain NAs
  res[is.na(x)] <- NA
  return(res)
}

Now I run my MRE again. It works perfectly. I've preserved the vector length and handled the missing values without triggering any warnings.

Finalizing the contribution

Before I submit the Pull Request, I run devtools::test() to make sure I haven't accidentally broken something else in the package. This is where most "fix" PRs get rejected—the contributor fixes one bug but breaks three existing features. Once the tests pass, I commit my changes with a clear message: "Fix: handle NA values in trim_and_cap to prevent crash".




📋 Practical Task

Fixing the Vectorization Bug in calculate_growth()

You are contributing to a package called econtools. The function calculate_growth() is designed to calculate the percentage growth between two numeric vectors. However, it currently fails when the input vectors contain NA values because it uses a non-vectorized if check.

Current broken code:

calculate_growth <- function(old, new) {
  if (is.na(old) || is.na(new)) {
    return(NA)
  }
  return((new - old) / old)
}

Your Task:

  1. Identify why calculate_growth(c(10, 20), c(12, NA)) triggers a warning and fails to return a full vector.
  2. Rewrite the function to be properly vectorized. It should return a numeric vector of the same length as the inputs, where any element involving an NA remains NA.
  3. Ensure your solution does not use a for loop.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.