Skip to Content
Course content

152: Contributing to Open Source R Packages

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

Look, the jump from using a package to actually contributing to one feels like a huge leap, but it's mostly just a shift in workflow. You aren't just writing a script anymore; you're working within someone else's ecosystem. The goal isn't just to "make it work," but to make it fit.

How do I actually set up the package on my machine so I can start hacking?

You don't just download a ZIP file. You need the git history. Once you've forked the repository on GitHub, clone it locally. But here is the part that trips people up: don't just use library(package). If you do that, you're loading the version installed in your R library, not the code you're currently editing in your folder.

Instead, use devtools::load_all(). This simulates installing the package and loading it, but it points directly to your local files. I use this constantly. It’s the fastest way to iterate.

# After cloning the repo and opening the project in RStudio
devtools::load_all()

# Now you can test the function you just modified
my_package_function(x = 10)

How do I make sure my "fix" doesn't accidentally break ten other things?

In a script, you just run the code and see if it works. In a package, you use testthat. If you've found a bug, the very first thing you should do—before writing a single line of the fix—is write a test that fails. This proves the bug exists and gives you a clear goal.

If you're contributing to a package like dplyr or ggplot2, the test suites are massive. Don't try to run everything every time. Focus on the specific test file related to the function you're touching.

# Create a new test file in tests/testthat/test-my-fix.R
test_that("edge case with NA values is handled correctly", {
  expect_equal(my_package_function(c(1, NA, 3)), c(1, 0, 3))
})

# Run the tests
devtools::test()

Once the test fails, write your fix. Once the test passes, you've officially moved the needle.

What makes a Pull Request that a maintainer actually wants to merge?

Maintainers are usually exhausted and overworked. The worst thing you can do is send a "mega-PR" that changes five different functions, updates the README, and tweaks the license. That's a nightmare to review.

Keep your PRs atomic. One issue, one fix, one PR. In your description, don't just say "Fixed bug." Tell them why it happened and how you fixed it. Mention the issue number (e.g., "Closes #124"). I've seen great code get rejected simply because the author didn't explain the reasoning, leaving the maintainer to guess if it was a hack or a proper architectural choice.

Wait, do I have to manually edit the .Rd files for documentation?

Please, for the love of R, don't touch the .Rd files in the man/ folder. Those are machine-generated. If you change a function's arguments or add a new one, you update the roxygen2 comments directly above the function definition in the R/ folder.

It looks like this:

#' Add two numbers together
#' @param x A numeric value
#' @param y A numeric value
#' @return The sum of x and y
#' @export
add_numbers <- function(x, y) {
  x + y
}

After updating those tags, run devtools::document(). This updates the documentation files automatically. If you forget this step, the package will fail the build check, and the maintainer will have to ask you to fix it, which just adds friction to the process.




📋 Practical Task

Exercise: Implementing a Bug Fix and Test for the "rstatshelper" Package

Imagine you have cloned a utility package called rstatshelper. You discovered that the function calculate_mean_trimmed() crashes when the input vector is empty, rather than returning NA.

Your Goal: Simulate the contribution workflow by completing the following steps in your local environment (or a mock project):

  1. Create a Failing Test: Create a file tests/testthat/test-mean-trimmed.R. Write a test_that block that passes an empty numeric vector numeric(0) to calculate_mean_trimmed() and expects the result to be is.na(). Run it and confirm it fails.
  2. Implement the Fix: Locate the function in the R/ directory. Add a conditional check at the start of the function to return NA if the length of the input vector is zero.
  3. Verify: Run devtools::load_all() followed by devtools::test() to ensure the test now passes.
  4. Update Documentation: Add a @section Edge Cases: block to the roxygen2 comments of the function, explaining that empty vectors return NA. Run devtools::document() to update the help files.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.