Skip to Content
Course content

41: Practice Exercise: Building a Custom Summary Statistics Function

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

When you start writing your own functions in R, it's easy to assume that because mean() or sd() work perfectly on a clean vector, they'll work perfectly inside your custom wrapper. But real-world data is messy. I've seen countless scripts crash or return useless results because of one tiny oversight in how R handles missing values.

# A typical first attempt at a summary function
get_stats <- function(data_vec) {
  avg <- mean(data_vec)
  med <- median(data_vec)
  stdev <- sd(data_vec)
  
  return(c(mean = avg, median = med, sd = stdev))
}

# Let's test it with some actual data
my_data <- c(12, 15, 14, NA, 18, 21)
get_stats(my_data)
# Output: 
#    mean   median       sd 
#      NA       NA       NA 

The NA Propagation Trap

If you ran the code above, you probably noticed it didn't crash, but it gave you a result that's functionally useless: a vector of NAs. In R, NA (Not Available) is contagious. If a single value in your vector is missing, most mathematical functions will return NA by default to warn you that the calculation is incomplete. This is actually a safety feature, but when you're building a summary function, it's a nuisance.

The fix is simple, but essential: you have to explicitly tell the internal functions to ignore the missing values using the na.rm = TRUE argument. I usually prefer to do this inside the function so the person calling the function doesn't have to worry about it.

# The fixed version
get_stats <- function(data_vec) {
  avg <- mean(data_vec, na.rm = TRUE)
  med <- median(data_vec, na.rm = TRUE)
  stdev <- sd(data_vec, na.rm = TRUE)
  
  return(c(mean = avg, median = med, sd = stdev))
}

get_stats(my_data)
# Output:
#    mean   median       sd 
# 16.0000 15.0000 3.807887 

Making the Function Robust and Flexible

Now, while the version above works, it's a bit "brittle." What happens if someone accidentally passes a character vector into get_stats? R will throw a confusing error. Also, some users might actually want to know if there are NAs present rather than just hiding them.

To make this professional-grade, we should add a type check and a parameter to control na.rm. I like to set na.rm = TRUE as the default, but leave the door open for the user to change it. I'll also wrap the logic in an if statement to ensure we're only dealing with numbers.

get_stats_pro <- function(data_vec, remove_na = TRUE) {
  # First, make sure we are actually dealing with numbers
  if (!is.numeric(data_vec)) {
    stop("The input must be a numeric vector. You passed: ", class(data_vec))
  }
  
  # We use the remove_na variable directly inside the functions
  avg <- mean(data_vec, na.rm = remove_na)
  med <- median(data_vec, na.rm = remove_na)
  stdev <- sd(data_vec, na.rm = remove_na)
  
  # I'm returning a named list here instead of a vector. 
  # Lists are generally more flexible in R for returning mixed data.
  list(
    mean = avg,
    median = med,
    standard_deviation = stdev,
    n_obs = length(data_vec),
    n_missing = sum(is.na(data_vec))
  )
}

# Now it's a tool, not just a snippet
get_stats_pro(my_data) 

By adding the n_obs and n_missing counts, we've turned a basic calculation into a legitimate summary tool. You're no longer just blindly ignoring NAs; you're reporting exactly how many were ignored. That's the difference between a script that "just works" and a professional piece of software.




📋 Practical Task

Exercise: Building a Robust Environmental Sensor Summary Tool

Imagine you are analyzing data from a remote weather station. The station records temperatures, but occasionally the sensor glitches and records non-numeric strings (like "ERROR") or leaves gaps (NAs).

Your task is to create a function called summarize_sensor_data that does the following:

  • Accepts a vector (which might contain numbers, NAs, or strings).
  • First, coerces the input to numeric using as.numeric(). (Note: R will turn non-numeric strings into NAs during this process).
  • Calculates the mean, minimum, and maximum values, ignoring NAs.
  • Calculates the range (Max minus Min).
  • Returns a named list containing all four metrics, plus a count of how many NA values were present after the numeric coercion.

Test your function with this vector:
sensor_readings <- c(22.5, 23.1, "ERROR", 21.8, NA, 24.2, "MALFUNCTION", 22.0)

Rating
0 0

There are no comments for now.

to be the first to leave a comment.