Skip to content

Data Structures

R provides several data structures for organizing your data. Each one serves a different purpose. In bioinformatics, you will use all of them regularly. This page walks through the most important ones with practical examples.

A vector is the most basic data structure in R. It holds a sequence of values that are all the same type. You can think of it as a single column of data.

Use the c() function to create a vector. Here we store the lengths of five genes in base pairs.

gene_lengths <- c(1542, 987, 2341, 456, 1823)
gene_lengths
[1] 1542 987 2341 456 1823

The length() function tells you how many elements are in the vector.

length(gene_lengths)
[1] 5

Vectors can hold text values too. Character vectors are useful for storing gene names, sample IDs, or any other labels.

gene_names <- c("TP53", "BRCA1", "EGFR", "KRAS", "MYC")
gene_names
[1] "TP53" "BRCA1" "EGFR" "KRAS" "MYC"

Logical vectors hold TRUE or FALSE values. These are handy for tracking quality control status.

passed_qc <- c(TRUE, TRUE, FALSE, TRUE, FALSE)
passed_qc
[1] TRUE TRUE FALSE TRUE FALSE

You can extract specific elements from a vector using square brackets. R uses 1-based indexing, so the first element is at position 1.

gene_names[1]
[1] "TP53"

Pass a vector of positions to grab multiple elements at once.

gene_names[c(1, 3, 5)]
[1] "TP53" "EGFR" "MYC"

The colon operator creates a range of positions. This is a quick way to select consecutive elements.

gene_names[2:4]
[1] "BRCA1" "EGFR" "KRAS"

You can filter a vector using a logical condition. This returns only the gene lengths greater than 1000.

gene_lengths[gene_lengths > 1000]
[1] 1542 2341 1823

A logical vector can also serve as a filter. Here we use the passed_qc vector to select only the genes that passed quality control.

gene_names[passed_qc]
[1] "TP53" "BRCA1" "KRAS"

You can assign names to each element of a vector. This lets you look up values by name instead of by position. Named vectors are common for storing gene expression values.

expression <- c(TP53 = 23.5, BRCA1 = 45.2, EGFR = 12.8, KRAS = 67.3, MYC = 34.1)
expression
TP53 BRCA1 EGFR KRAS MYC
23.5 45.2 12.8 67.3 34.1

Access a single value by its name using quotes inside square brackets.

expression["BRCA1"]
BRCA1
45.2

You can also select multiple named elements at once.

expression[c("TP53", "MYC")]
TP53 MYC
23.5 34.1

The names() function returns all the names as a character vector.

names(expression)
[1] "TP53" "BRCA1" "EGFR" "KRAS" "MYC"

R applies mathematical functions to every element of a vector at once. This is called vectorization. It makes R fast and concise. For example, you can log-transform all expression values in one step.

log2_expr <- log2(expression)
log2_expr
TP53 BRCA1 EGFR KRAS MYC
4.554589 5.498251 3.678072 6.072535 5.091700

Summary functions collapse a vector into a single value.

sum(gene_lengths)
[1] 7149
mean(gene_lengths)
[1] 1429.8

The sort() function reorders the values. Set decreasing = TRUE to sort from highest to lowest.

sort(expression, decreasing = TRUE)
KRAS BRCA1 MYC TP53 EGFR
67.3 45.2 34.1 23.5 12.8

Factors represent categorical data. They are essential when your data has a fixed set of possible values, such as treatment groups or batch labels. R stores the unique categories as “levels.”

condition <- c("treated", "control", "treated", "control", "treated")
condition_factor <- factor(condition)
condition_factor
[1] treated control treated control treated
Levels: control treated

The levels() function shows the unique categories.

levels(condition_factor)
[1] "control" "treated"

The table() function counts how many times each level appears. This is useful for checking sample balance across experimental groups.

table(condition_factor)
condition_factor
control treated
2 3

Sometimes categories have a natural order. Ordered factors let you specify this ranking. Here we create a severity factor with a defined ranking from mild to severe.

severity <- factor(c("mild", "severe", "moderate", "mild", "severe"),
levels = c("mild", "moderate", "severe"),
ordered = TRUE)
severity
[1] mild severe moderate mild severe
Levels: mild < moderate < severe

Lists can hold elements of different types and different lengths. This makes them flexible containers. Many R functions return their results as lists. A statistical test might return a gene name, a p-value, a fold change, and a vector of sample IDs all in one object.

test_result <- list(
gene = "TP53",
pvalue = 0.0023,
log2fc = 2.1,
significant = TRUE,
samples = c("S1", "S2", "S3")
)
test_result
$gene
[1] "TP53"
$pvalue
[1] 0.0023
$log2fc
[1] 2.1
$significant
[1] TRUE
$samples
[1] "S1" "S2" "S3"

Use the $ operator to access a named element of a list.

test_result$gene
[1] "TP53"
test_result$pvalue
[1] 0.0023

Double square brackets also work for extracting list elements by name.

test_result[["samples"]]
[1] "S1" "S2" "S3"

A matrix is a two-dimensional structure where every element has the same type. In bioinformatics, matrices are commonly used to store gene expression count data. Rows represent genes. Columns represent samples.

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")
)
)
count_matrix
Sample1 Sample2 Sample3
TP53 100 250 50
BRCA1 120 300 75
EGFR 90 280 60

Select a single row by name. Leave the column index empty to get all columns.

count_matrix["TP53", ]
Sample1 Sample2 Sample3
100 250 50

Select a single column by name. Leave the row index empty to get all rows.

count_matrix[, "Sample2"]
TP53 BRCA1 EGFR
250 300 280

The dim() function returns the number of rows and columns.

dim(count_matrix)
[1] 3 3

Data frames are the most common structure for tabular data in R. Unlike matrices, each column can hold a different type. One column might be character strings, another might be numbers, and another might be logical values. This makes data frames ideal for storing sample metadata.

sample_metadata <- data.frame(
sample_id = c("S1", "S2", "S3", "S4"),
condition = c("control", "control", "treated", "treated"),
read_count = c(25000000, 28000000, 31000000, 27000000),
passed_qc = c(TRUE, TRUE, TRUE, FALSE)
)
sample_metadata
sample_id condition read_count passed_qc
1 S1 control 2.5e+07 TRUE
2 S2 control 2.8e+07 TRUE
3 S3 treated 3.1e+07 TRUE
4 S4 treated 2.7e+07 FALSE

The str() function shows the structure of a data frame. It tells you the type and first few values of each column. This is one of the most useful functions for inspecting your data.

str(sample_metadata)
'data.frame': 4 obs. of 4 variables:
$ sample_id : chr "S1" "S2" "S3" "S4"
$ condition : chr "control" "control" "treated" "treated"
$ read_count: num 2.5e+07 2.8e+07 3.1e+07 2.7e+07
$ passed_qc : logi TRUE TRUE TRUE FALSE

Use $ to extract a single column as a vector.

sample_metadata$condition
[1] "control" "control" "treated" "treated"

Filter rows using a logical condition. Here we keep only the samples that passed quality control.

sample_metadata[sample_metadata$passed_qc, ]
sample_id condition read_count passed_qc
1 S1 control 2.5e+07 TRUE
2 S2 control 2.8e+07 TRUE
3 S3 treated 3.1e+07 TRUE

Several functions help you inspect the size and structure of a data frame.

nrow(sample_metadata)
[1] 4
ncol(sample_metadata)
[1] 4
colnames(sample_metadata)
[1] "sample_id" "condition" "read_count" "passed_qc"
Structure Dimensions Types allowed Typical use in bioinformatics
Vector 1D One type only Gene names, expression values
Factor 1D Categorical Treatment groups, batch labels
List 1D Mixed types Statistical test results
Matrix 2D One type only Expression count matrices
Data frame 2D Mixed per column Sample metadata, annotation tables

Now that you know how R organizes data, you are ready to learn how to control the flow of your programs. Continue to Control Flow.