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.
Writing functions
Section titled “Writing functions”A function in R has three parts: a name, a set of inputs, and a body of code that runs when you call it.
Basic function
Section titled “Basic function”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.5The 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] 0This returns zero because there are no G or C bases in the input.
Default arguments
Section titled “Default arguments”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.
Returning multiple values
Section titled “Returning multiple values”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] 488summary$zero_fraction[1] 0.375You 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] FALSEInside 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.05The global threshold is still 0.05. The function’s local variable did not overwrite it. This behavior protects your workspace from unintended side effects.
The apply family
Section titled “The apply family”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.
apply on matrices
Section titled “apply on matrices”The apply function works on matrices and data frames. You choose whether to apply a function across rows or columns.
1means apply across rows.2means 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 EGFR133.3333 165.0000 143.3333Each 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 185Each value is the total expression across all genes for that sample. This is a quick way to check library sizes.
sapply
Section titled “sapply”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 3This counts the number of characters in each gene name. The result is a named vector.
lapply vs sapply
Section titled “lapply vs sapply”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] 1lapply returns a list with one element per sequence.
sapply(sequences, calculate_gc_content)ATGCGA AAATTT GCGCGC 0.5 0.0 1.0sapply collapses the result into a named numeric vector. This is often easier to work with.
Anonymous functions
Section titled “Anonymous functions”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 3This 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.
Quick reference
Section titled “Quick reference”| 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 |
Next steps
Section titled “Next steps”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.