Control Flow
Control flow lets your scripts make decisions and repeat actions. These are essential tools for processing biological data. You will use conditionals to filter results and loops to iterate over genes, samples, or files.
Conditionals
Section titled “Conditionals”Conditionals let your code take different actions based on a test. In bioinformatics, you often check whether a p-value is significant or whether a file exists before reading it.
if/else
Section titled “if/else”The if/else statement runs one block of code when a condition is TRUE and another when it is FALSE.
pvalue <- 0.003
if (pvalue < 0.05) { cat("Result is statistically significant\n")} else { cat("Result is not significant\n")}Result is statistically significantThe condition inside if() must evaluate to a single TRUE or FALSE. R will warn you if you pass a vector with more than one element.
if/else if/else
Section titled “if/else if/else”You can chain multiple conditions together. This is useful for classifying genes by their fold change direction.
log2fc <- 2.5
if (log2fc > 1) { cat("Gene is upregulated\n")} else if (log2fc < -1) { cat("Gene is downregulated\n")} else { cat("No significant change\n")}Gene is upregulatedR evaluates conditions from top to bottom. It runs the first block whose condition is TRUE and skips the rest.
Checking file existence
Section titled “Checking file existence”Before reading a data file, check that it actually exists. This prevents your script from crashing with a confusing error.
input_file <- "counts.csv"
if (file.exists(input_file)) { cat("Reading:", input_file, "\n")} else { cat("File not found:", input_file, "\n")}File not found: counts.csvThis pattern is especially helpful in pipelines where upstream steps may or may not have produced output files.
Vectorized ifelse()
Section titled “Vectorized ifelse()”The if/else statement works on a single value. When you need to apply a condition across an entire vector, use ifelse(). This is the R way to classify every element at once without writing a loop.
pvalues <- c(0.001, 0.23, 0.04, 0.87, 0.005)significance <- ifelse(pvalues < 0.05, "significant", "not significant")significance[1] "significant" "not significant" "significant" "not significant"[5] "significant"You can nest ifelse() calls to create three or more categories. Here we classify genes as upregulated, downregulated, or not significant based on log2 fold change.
log2fc_values <- c(2.5, -0.3, -1.8, 0.1, 3.2)direction <- ifelse(log2fc_values > 1, "up", ifelse(log2fc_values < -1, "down", "ns"))direction[1] "up" "ns" "down" "ns" "up"Nested ifelse() can become hard to read beyond two or three levels. For more complex cases, consider dplyr::case_when().
For loops
Section titled “For loops”A for loop repeats a block of code once for each element in a vector. This is useful when you need to process a list of genes, samples, or files one at a time.
genes <- c("TP53", "BRCA1", "EGFR", "KRAS")
for (gene in genes) { cat("Processing gene:", gene, "\n")}Processing gene: TP53Processing gene: BRCA1Processing gene: EGFRProcessing gene: KRASLooping with an index
Section titled “Looping with an index”Sometimes you need the position of each element, not just its value. Use seq_along() to generate index numbers safely. It handles empty vectors correctly, unlike 1:length(x).
samples <- c("S1", "S2", "S3")
for (i in seq_along(samples)) { cat("Sample", i, ":", samples[i], "\n")}Sample 1 : S1Sample 2 : S2Sample 3 : S3Collecting results in a loop
Section titled “Collecting results in a loop”When you need to store results from each iteration, pre-allocate a vector first. This is much faster than growing a vector inside the loop.
counts <- c(100, 250, 50, 300, 75)log_counts <- numeric(length(counts))
for (i in seq_along(counts)) { log_counts[i] <- log2(counts[i] + 1)}log_counts[1] 6.658211 7.971544 5.672425 8.233620 6.247928Vectorized alternative
Section titled “Vectorized alternative”R is designed for vectorized operations. They run faster than loops because R processes the entire vector in optimized C code under the hood. Whenever possible, prefer vectorized operations over explicit loops.
The loop above can be replaced with a single line:
log_counts_vec <- log2(counts + 1)log_counts_vec[1] 6.658211 7.971544 5.672425 8.233620 6.247928The results are identical. The vectorized version is shorter, easier to read, and faster on large datasets.
While loops
Section titled “While loops”A while loop keeps running as long as its condition is TRUE. This is useful when you do not know in advance how many iterations you need.
Here we simulate how many times a cell population must double to exceed a threshold:
threshold <- 100current <- 10doublings <- 0
while (current < threshold) { current <- current * 2 doublings <- doublings + 1}cat("Reached", current, "after", doublings, "doublings\n")Reached 160 after 4 doublingsBe careful with while loops. If the condition never becomes FALSE, your script will run forever. Always make sure something inside the loop moves toward ending the condition.
next and break
Section titled “next and break”Inside a loop, next skips to the next iteration and break exits the loop entirely. These are useful for handling missing data or stopping early when a condition is met.
pvalues <- c(0.001, NA, 0.04, 0.5, 0.003)
for (i in seq_along(pvalues)) { if (is.na(pvalues[i])) { cat("Skipping NA at position", i, "\n") next } if (pvalues[i] < 0.01) { cat("Found highly significant result at position", i, "\n") }}Found highly significant result at position 1Skipping NA at position 2Found highly significant result at position 5Use next to skip problematic entries like NA values. Use break when you only need the first match and want to stop searching.
Why vectorized operations are preferred
Section titled “Why vectorized operations are preferred”R was built for working with vectors and matrices. Vectorized functions operate on entire vectors in a single call. They are almost always faster and more concise than writing an explicit loop.
Here are key reasons to prefer vectorized code:
- Speed. Vectorized functions call optimized C or Fortran code internally. Loops in R are interpreted and carry overhead on each iteration.
- Readability. A single line like
log2(counts + 1)communicates intent more clearly than a five-line loop. - Less room for bugs. Fewer lines of code means fewer places for mistakes. You avoid common loop errors like off-by-one indexing or forgetting to pre-allocate.
That said, loops are not always wrong. Some tasks are genuinely sequential. Reading multiple files or calling an API for each sample requires iteration. Use the right tool for the job.
For applying functions across vectors and lists without writing explicit loops, R provides the apply family: apply(), sapply(), lapply(), and others. These are covered in the Functions page.
Quick reference
Section titled “Quick reference”| Construct | Purpose | Example use case |
|---|---|---|
if/else |
Branch on a single condition | Check if a p-value is significant |
if/else if/else |
Branch on multiple conditions | Classify a gene as up, down, or unchanged |
ifelse() |
Vectorized conditional | Label an entire column of p-values |
for |
Iterate over a vector | Process each sample file |
while |
Loop until a condition is met | Simulate growth until a threshold |
next |
Skip current iteration | Skip NA values in a loop |
break |
Exit a loop early | Stop after finding the first match |
Next steps
Section titled “Next steps”You now know how to make decisions and repeat actions in R. The next page covers how to organize your code into reusable functions. Head to Functions to continue.