Skip to content

Functions

Functions let you wrap a block of code into a single reusable command. In bioinformatics, you often repeat the same filtering, normalization, or annotation step across many datasets. Writing a function means you define the logic once and call it whenever you need it. This reduces errors and makes your code easier to read.

A function in R has three parts: a name, a set of inputs, and a body of code that runs when you call it.

Here is a function that calculates the GC content of a DNA sequence. GC content is the fraction of bases that are guanine or cytosine.

calculate_gc_content <- function(sequence) {
bases <- strsplit(toupper(sequence), "")[[1]]
gc_count <- sum(bases %in% c("G", "C"))
gc_fraction <- gc_count / length(bases)
return(gc_fraction)
}
calculate_gc_content("ATGCGATCGA")
[1] 0.5

The function splits the string into individual characters. It counts how many are G or C. Then it divides by the total number of bases. Let’s try a sequence with no GC content.

calculate_gc_content("AAATTTAAATTT")
[1] 0

This returns zero because there are no G or C bases in the input.

You can give function arguments default values. The caller can override them when needed. This is useful when you have a standard threshold but occasionally want a different one.

The function below labels genes from a differential expression analysis. It takes a p-value and a log2 fold change and returns “up”, “down”, or “ns” for not significant.

label_significance <- function(pvalue, log2fc, alpha = 0.05, fc_threshold = 1) {
if (pvalue < alpha & log2fc > fc_threshold) {
return("up")
} else if (pvalue < alpha & log2fc < -fc_threshold) {
return("down")
} else {
return("ns")
}
}
label_significance(0.001, 2.5)
[1] "up"

The p-value is below 0.05 and the fold change exceeds 1. The gene is labeled as upregulated.

label_significance(0.001, -1.8)
[1] "down"

A negative fold change with a significant p-value means the gene is downregulated.

label_significance(0.5, 3.0)
[1] "ns"

Even though the fold change is large, the p-value is above the threshold. The gene is not significant.

You can override defaults by naming the argument you want to change.

label_significance(0.001, 0.5, fc_threshold = 0.25)
[1] "up"

Here we lowered the fold change threshold to 0.25. Now a fold change of 0.5 qualifies as upregulated.

R functions can only return a single object. To return multiple values, pack them into a list. This is common when you want to compute several summary statistics at once.

summarize_counts <- function(counts) {
result <- list(
total = sum(counts),
mean = mean(counts),
median = median(counts),
nonzero = sum(counts > 0),
zero_fraction = mean(counts == 0)
)
return(result)
}
gene_counts <- c(0, 15, 230, 0, 45, 120, 0, 78)
summary <- summarize_counts(gene_counts)
summary$total
[1] 488
summary$zero_fraction
[1] 0.375

You access each value using the $ operator with the name you assigned in the list. In this case, 37.5% of the samples had zero counts for this gene.

Scope controls where a variable is visible. Variables created inside a function are local to that function. They do not affect variables outside the function, even if they share the same name.

threshold <- 0.05
check_significance <- function(pvalue) {
threshold <- 0.01 # This is a local variable
return(pvalue < threshold)
}
check_significance(0.03)
[1] FALSE

Inside the function, threshold is 0.01. A p-value of 0.03 is not below 0.01, so the function returns FALSE.

threshold # Unchanged
[1] 0.05

The global threshold is still 0.05. The function’s local variable did not overwrite it. This behavior protects your workspace from unintended side effects.

Loops are common in programming. R offers the apply family of functions as a concise alternative. These functions apply a function to each element of a data structure without writing an explicit loop.

The apply function works on matrices and data frames. You choose whether to apply a function across rows or columns.

  • 1 means apply across rows.
  • 2 means apply across columns.
count_matrix <- matrix(
c(100, 250, 50, 120, 300, 75, 90, 280, 60),
nrow = 3,
byrow = TRUE,
dimnames = list(
c("TP53", "BRCA1", "EGFR"),
c("Sample1", "Sample2", "Sample3")
)
)
# Row means (per gene)
apply(count_matrix, 1, mean)
TP53 BRCA1 EGFR
133.3333 165.0000 143.3333

Each value is the average expression of that gene across all three samples.

# Column sums (per sample)
apply(count_matrix, 2, sum)
Sample1 Sample2 Sample3
310 830 185

Each value is the total expression across all genes for that sample. This is a quick way to check library sizes.

sapply applies a function to each element of a vector or list. It returns a simplified result, usually a named vector.

genes <- c("TP53", "BRCA1", "EGFR", "KRAS", "MYC")
sapply(genes, nchar)
TP53 BRCA1 EGFR KRAS MYC
4 5 4 4 3

This counts the number of characters in each gene name. The result is a named vector.

lapply always returns a list. sapply tries to simplify the result into a vector or matrix. Use lapply when you want to preserve the list structure. Use sapply when you want a cleaner output.

sequences <- c("ATGCGA", "AAATTT", "GCGCGC")
lapply(sequences, calculate_gc_content)
[[1]]
[1] 0.5
[[2]]
[1] 0
[[3]]
[1] 1

lapply returns a list with one element per sequence.

sapply(sequences, calculate_gc_content)
ATGCGA AAATTT GCGCGC
0.5 0.0 1.0

sapply collapses the result into a named numeric vector. This is often easier to work with.

Sometimes you need a quick function for a single use. You can define it inline without giving it a name. This is called an anonymous function.

counts <- list(
sample1 = c(100, 200, 0, 50),
sample2 = c(300, 0, 150, 80),
sample3 = c(50, 100, 200, 0)
)
sapply(counts, function(x) sum(x > 0))
sample1 sample2 sample3
3 3 3

This counts how many genes have nonzero expression in each sample. The anonymous function function(x) sum(x > 0) is defined right where it is used.

Function Input Output Use case
apply Matrix or data frame Vector or matrix Apply a function across rows or columns
sapply Vector or list Simplified vector or matrix Apply a function to each element with clean output
lapply Vector or list List Apply a function to each element, keep list structure
vapply Vector or list Vector with specified type Like sapply but with a guaranteed return type

You now know how to write reusable functions and apply them across data structures. In the next section, you will learn how to extend R with external packages. See Working with Packages.