1 Introduction

Chimeric Antigen Receptor T-cell (CAR-T) therapy represents a revolutionary approach to cancer treatment, where a patient’s T-cells are genetically modified to recognize and attack cancer cells. The success of CAR-T therapy critically depends on identifying suitable target antigens—proteins expressed on cancer cells that can serve as targets for the engineered T-cells.

This vignette exemplifies a comprehensive computational workflow for CAR-T target assessment at the isoform level. We demonstrate this workflow using ERBB2 (HER2/neu) as a case study, systematically evaluating isoforms based on subcellular localization, membrane topology, epitope retention, and expression patterns. ERBB” is a receptor tyrosine kinase overexpressed in approximately 25-30% of breast cancers Slamon, D.J. et al. and an established target for monoclonal antibody therapy (trastuzumab) Yoon, J. et al..

1.1 Required Packages

library(biomaRt)
library(Biostrings)
library(ggnewscale)
library(patchwork)
library(tidyverse) 

options(timeout = 300)
theme_set(theme_light())

2 Phase 1: Isoform Discovery

2.1 Step 1: Retrieve Isoform Sequences

Download protein sequences for all HER2 isoforms from Ensembl (GRCh38, release 115). We retrieve sequences for all protein-coding transcripts to ensure comprehensive coverage.

# Connect to Ensembl
ensembl <- useEnsembl(biomart = "genes", dataset = "hsapiens_gene_ensembl")

# Get protein-coding ERBB2 transcripts
get_gene_transcripts <- function(gene_symbol, mart) {
  getBM(
    attributes = c("ensembl_gene_id", "ensembl_transcript_id", 
                   "transcript_biotype", "transcript_length"),
    filters = "hgnc_symbol",
    values = gene_symbol,
    mart = mart
  ) %>%
    filter(transcript_biotype == "protein_coding") %>%
    arrange(desc(transcript_length))
}

her2_transcripts <- get_gene_transcripts("ERBB2", ensembl)

# Retrieve protein sequences
get_protein_sequences <- function(transcript_ids, mart) {
  seqs <- getBM(
    attributes = c("ensembl_transcript_id", "peptide"),
    filters = "ensembl_transcript_id",
    values = transcript_ids,
    mart = mart
  )
  
  sequences <- AAStringSet(seqs$peptide)
  names(sequences) <- seqs$ensembl_transcript_id
  sequences[width(sequences) > 0]  # Remove empty sequences
}

her2_sequences_all <- get_protein_sequences(her2_transcripts$ensembl_transcript_id, ensembl)
print(her2_sequences_all)
## AAStringSet object of length 24:
##      width seq                                              names               
##  [1]   102 MKLRLPASPETHLDMLRHLYQGC...NYALAVLDNGDPLNNTTPVTGA ENST00000578709
##  [2]  1226 MKLRLPASPETHLDMLRHLYQGC...STFKGTPTAENPEYLGLDVPV* ENST00000584601
##  [3]   177 XPCHPECQPQNGSVTCFGPEADQ...KETELRKVKGIWIPDGENVKIP ENST00000582818
##  [4]  1056 MELAALCRWGLLLALLPPGAAST...PAPGAGGMVHHRHRSSSTRNM* ENST00000584450
##  [5]   604 MKLRLPASPETHLDMLRHLYQGC...IWKFPDEEGACQPCPINCTHS* ENST00000584014
##  ...   ... ...
## [20]  1267 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000959774
## [21]  1194 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938923
## [22]  1137 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938925
## [23]  1290 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938924
## [24]  1140 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000959775

2.2 Step 2: Download Expression Data

We use pre-processed TCGA/GTEx expression data from Xena Browser. This dataset provides transcript-level expression across cancer and normal tissues. Note: Ensembl’s September 2025 update added ~120,000 new transcripts not yet reflected in available expression databases, so some isoforms are absent from the expression data.

# File paths
expr_file <- "data/TcgaTargetGtex_rsem_isoform_tpm.gz"
filtered_file <- "results/her2_isoforms_filtered.txt"
output_rds <- "results/her2_expr_filtered.rds"

# HER2 transcripts to extract
her2_transcripts <- names(her2_sequences_all)

# Filter expression file line-by-line (efficient for large files)
con <- gzfile(expr_file, "r")
header <- readLines(con, n = 1)
writeLines(header, filtered_file)

line_count <- 0
matched_count <- 0

while (TRUE) {
  lines <- readLines(con, n = 1000)
  if (length(lines) == 0) break
  
  line_count <- line_count + length(lines)
  
  # Extract HER2 transcript lines
  pattern <- paste0("^(", paste(her2_transcripts, collapse = "|"), ")(\\.|\\t)")
  matching_lines <- grep(pattern, lines, value = TRUE)
  
  if (length(matching_lines) > 0) {
    write(matching_lines, file = filtered_file, append = TRUE)
    matched_count <- matched_count + length(matching_lines)
  }
}

close(con)

# Read filtered data and save as RDS
her2_expr <- read_tsv(filtered_file, show_col_types = FALSE)
saveRDS(her2_expr, output_rds)

2.3 Step 3: Identify Available Transcripts

Identify which HER2 isoforms have expression data and filter sequences accordingly.

# Load pre-filtered expression data
her2_expr <- readRDS("../../results/HER2_analysis/her2_expr_filtered.rds")

# Extract transcript IDs (remove version numbers)
her2_available_transcripts <- her2_expr %>% 
  mutate(transcript_clean = str_remove(sample, "\\..*")) %>%
  pull(transcript_clean) %>%
  unique()

# Filter sequences to those with expression data
sequence_names_clean <- str_remove(names(her2_sequences_all), "\\..*")
keep_indices <- sequence_names_clean %in% her2_available_transcripts
her2_sequences_filtered <- her2_sequences_all[keep_indices]

cat("Sequences with expression data:", length(her2_sequences_filtered), "\n")
## Sequences with expression data: 10

2.4 Step 4: Export for External Tools

Save sequences in FASTA format for external prediction tools (DeepLoc2 for subcellular localization, DeepTMHMM for membrane topology).

fasta_file <- "../../data/her2_isoforms_filtered.fasta"
writeXStringSet(her2_sequences_filtered, filepath = fasta_file)

cat("✓ FASTA saved:", fasta_file, "\n\n")
cat("Upload to:\n")
cat("  • DeepLoc2: https://services.healthtech.dtu.dk/services/DeepLoc-2.1/\n")
cat("  • DeepTMHMM: https://dtu.biolib.com/DeepTMHMM\n")

3 Phase 2: Structural Characterization

3.1 Step 5: Align Isoforms with MAFFT

Align sequences using MAFFT with the --localpair algorithm, which is optimized for sequences with large insertions/deletions from alternative splicing. This ensures proper alignment of conserved functional domains.

temp_input <- tempfile(fileext = ".fasta")
temp_output <- tempfile(fileext = ".fasta")

writeXStringSet(her2_sequences_filtered, temp_input)

# MAFFT alignment optimized for isoforms
system2("mafft", 
        args = c("--localpair", "--maxiterate", "1000", "--quiet", temp_input),
        stdout = temp_output)

her2_aligned <- readAAStringSet(temp_output)
unlink(c(temp_input, temp_output))

cat("Alignment width:", unique(width(her2_aligned)), "positions\n")
## Alignment width: 1294 positions
# writeXStringSet(her2_aligned, "../../results/HER2_analysis/her2_aligned_mafft.fasta")

3.2 Step 6: Import and Process Predictions

Import external prediction results and perform integrated analysis: classify isoforms by subcellular localization, map membrane topology to aligned sequences, and identify therapeutic epitope retention across all isoforms.

# ===== Import DeepLoc2 Results =====
deeploc_results <- read_csv("../../results/HER2_analysis/HER2_deeploc21_filtered.csv", 
                             show_col_types = FALSE)

deeploc_filtered <- deeploc_results %>%
  mutate(transcript_clean = str_remove(Protein_ID, "\\..*")) %>%
  filter(transcript_clean %in% her2_available_transcripts)

# ===== Import DeepTMHMM Results =====
parse_deeptmhmm <- function(file_path) {
  lines <- readLines(file_path)
  results <- list()
  i <- 1
  
  while (i <= length(lines)) {
    if (startsWith(lines[i], ">")) {
      transcript_id <- str_extract(lines[i], "(?<=>)[^ ]+")
      sequence <- lines[i + 1]
      topology <- lines[i + 2]
      
      results[[transcript_id]] <- list(
        transcript_id = transcript_id,
        sequence = sequence,
        topology = topology
      )
      i <- i + 3
    } else {
      i <- i + 1
    }
  }
  return(results)
}

deeptmhmm_results <- parse_deeptmhmm(
  "../../results/HER2_analysis/HER2_deeptmhmm_predicted_topologies_filtered.3line"
)

# ===== Classify Isoforms by Localization =====
transcript_classification <- deeploc_filtered %>%
  dplyr::select(Protein_ID, `Cell membrane`, Extracellular) %>%
  mutate(
    transcript_clean = str_remove(Protein_ID, "\\..*"),
    classification = case_when(
      `Cell membrane` > Extracellular ~ "Membrane-bound",
      Extracellular > `Cell membrane` ~ "Secreted",
      TRUE ~ "Ambiguous"
    )
  )

classification_colors <- c(
  "Membrane-bound" = "#E74C3C",
  "Secreted" = "#3498DB",
  "Ambiguous" = "#95A5A6"
)

# ===== Map Topology to Alignment =====
map_topology_to_alignment <- function(deeptmhmm_results, aligned_sequences) {
  map_dfr(names(deeptmhmm_results), function(tid) {
    topology <- deeptmhmm_results[[tid]]$topology
    aligned_seq <- as.character(aligned_sequences[tid])
    
    topology_chars <- strsplit(topology, "")[[1]]
    aligned_chars <- strsplit(aligned_seq, "")[[1]]
    aligned_topology <- character(length(aligned_chars))
    
    # Transfer topology to aligned positions
    original_pos <- 1
    for (aligned_pos in 1:length(aligned_chars)) {
      if (aligned_chars[aligned_pos] == "-") {
        aligned_topology[aligned_pos] <- "-"
      } else {
        if (original_pos <= length(topology_chars)) {
          aligned_topology[aligned_pos] <- topology_chars[original_pos]
        }
        original_pos <- original_pos + 1
      }
    }
    
    data.frame(
      position = 1:length(aligned_chars),
      aa = aligned_chars,
      topology = aligned_topology,
      transcript_id = tid
    ) %>%
      mutate(
        topology_type = case_when(
          topology == "S" ~ "Signal peptide",
          topology == "O" ~ "Extracellular",
          topology == "M" ~ "Transmembrane",
          topology == "I" ~ "Intracellular",
          topology == "-" ~ "Alignment gap",
          TRUE ~ "Unknown"
        )
      )
  })
}

aligned_segments <- map_topology_to_alignment(deeptmhmm_results, her2_aligned)

# ===== Map Epitopes Across Isoforms =====
# Therapeutic antibody epitope positions in canonical HER2
epitope_positions <- list(
  P = list(c(257, 267), c(308, 318)),  # Pertuzumab
  T = list(c(580, 582), c(593, 595), c(610, 625))  # Trastuzumab
)

map_epitopes_from_alignment <- function(aligned_seqs, canonical_name, epitope_pos) {
  canonical_seq <- as.character(aligned_seqs[[canonical_name]])
  epitope_map_list <- list()
  
  for(seq_name in names(aligned_seqs)) {
    isoform_seq <- as.character(aligned_seqs[[seq_name]])
    canonical_ungapped_pos <- 0
    epitope_positions_aligned <- list()
    
    for(i in 1:nchar(canonical_seq)) {
      canon_char <- substr(canonical_seq, i, i)
      iso_char <- substr(isoform_seq, i, i)
      
      if(canon_char != "-") canonical_ungapped_pos <- canonical_ungapped_pos + 1
      
      # Check if position is in epitope and isoform has residue
      for(epitope_name in names(epitope_pos)) {
        for(region in epitope_pos[[epitope_name]]) {
          if(canonical_ungapped_pos >= region[1] && 
             canonical_ungapped_pos <= region[2] &&
             iso_char != "-") {
            epitope_positions_aligned[[length(epitope_positions_aligned) + 1]] <- data.frame(
              epitope = epitope_name,
              position = i,
              canonical_pos = canonical_ungapped_pos
            )
          }
        }
      }
    }
    
    if(length(epitope_positions_aligned) > 0) {
      epitope_map_list[[seq_name]] <- bind_rows(epitope_positions_aligned)
    }
  }
  
  bind_rows(epitope_map_list, .id = "transcript_id")
}

epitope_mapping <- map_epitopes_from_alignment(
  aligned_seqs = her2_aligned,
  canonical_name = "ENST00000269571",
  epitope_pos = epitope_positions
)

# Summarize epitope retention
epitope_summary <- epitope_mapping %>%
  group_by(transcript_id, epitope) %>%
  summarise(n_positions = n(), .groups = "drop") %>%
  pivot_wider(names_from = epitope, values_from = n_positions, values_fill = 0)

print(epitope_summary)
## # A tibble: 9 × 3
##   transcript_id       P     T
##   <chr>           <int> <int>
## 1 ENST00000269571    22    22
## 2 ENST00000445658    11    22
## 3 ENST00000578199    22    22
## 4 ENST00000578502    22    22
## 5 ENST00000580074     0    10
## 6 ENST00000582818     0    22
## 7 ENST00000584014    22    22
## 8 ENST00000584450    22    22
## 9 ENST00000584601    22    22

3.3 Step 7: Visualize Integrated Results

Create comprehensive visualization combining subcellular localization (DeepLoc2), membrane topology (DeepTMHMM), and epitope locations in a single integrated figure.

# Define isoform display order
transcript_order <- c(
  "ENST00000580074",
  "ENST00000578709",
  "ENST00000584601",
  "ENST00000584450",
  "ENST00000584014",
  "ENST00000582818",
  "ENST00000578502",  # Longest
  "ENST00000578199",  # Secreted
  "ENST00000445658",  # Truncated
  "ENST00000269571"   # Canonical
)

# ===== DeepLoc2 Heatmap =====
localization_cols <- c("Cell membrane", "Cytoplasm", "Endoplasmic reticulum",
                       "Extracellular", "Golgi apparatus", "Lysosome/Vacuole",
                       "Mitochondrion", "Nucleus", "Peroxisome", "Plastid")

plot_data_combined <- deeploc_filtered %>%
  mutate(Protein_ID = factor(Protein_ID, levels = transcript_order)) %>%
  dplyr::select(Protein_ID, all_of(localization_cols)) %>%
  pivot_longer(cols = -Protein_ID, names_to = "localization", values_to = "probability")

deeploc_heatmap <- ggplot(plot_data_combined, 
                          aes(x = localization, y = Protein_ID, fill = probability)) +
  geom_tile(color = "white", linewidth = 0.5) +
  scale_fill_gradientn(
    colors = c("#4A90B8", "#5E9DC3", "#FFFACD", "#F26D5E", "#D73027"),
    values = c(0, 0.2, 0.5, 0.8, 1),
    limits = c(0, 1)
  ) +
  labs(title = "HER2 Isoforms", x = NULL, y = NULL, fill = "Probability") +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 16),
    axis.text.y = element_text(size = 16, family = "mono"),
    plot.title = element_text(size = 18, face = "bold"),
    legend.text = element_text(size = 10),
    legend.title = element_text(size = 16),
    legend.position = "top",
    panel.grid = element_blank()
  )

# ===== Topology Plot with Epitopes =====
plot_topology_with_epitopes <- function(aligned_segments, epitope_data, transcript_order) {
  topology_colors <- c(
    "Signal peptide" = "#C41C24",
    "Extracellular" = "#FFB20F",
    "Transmembrane" = "#18848C",
    "Intracellular" = "#96BDC6",
    "Alignment gap" = "#EDE7E3"
  )
  
  epitope_colors <- c("P" = "#55a630", "T" = "#ff5d8f")
  
  aligned_segments <- aligned_segments %>%
    mutate(transcript_id = factor(transcript_id, levels = transcript_order))
  
  # Base topology plot
  p <- ggplot(aligned_segments, aes(x = position, y = transcript_id, fill = topology_type)) +
    geom_tile(height = 0.8, width = 1) +
    scale_fill_manual(values = topology_colors, name = "Topology") +
    scale_x_continuous(expand = c(0, 0), breaks = seq(0, max(aligned_segments$position), 400)) +
    labs(x = "Amino acid position (aligned)", y = NULL) +
    theme_minimal() +
    theme(
      axis.text.y = element_blank(),
      axis.text.x = element_text(size = 16),
      axis.title.x = element_text(size = 18),
      legend.position = "right",
      legend.text = element_text(size = 16),
      legend.title = element_text(size = 18),
      panel.grid = element_blank()
    )
  
  # Add epitope overlay
  if(!is.null(epitope_data) && nrow(epitope_data) > 0) {
    epitope_data <- epitope_data %>%
      mutate(transcript_id = factor(transcript_id, levels = transcript_order))
    
    p <- p +
      new_scale_fill() +
      geom_tile(data = epitope_data,
                aes(x = position, y = transcript_id, fill = epitope),
                height = 0.8, width = 1, alpha = 0.9, inherit.aes = FALSE) +
      scale_fill_manual(
        values = epitope_colors,
        name = "Epitope",
        labels = c("P" = "Pertuzumab", "T" = "Trastuzumab")
      )
  }
  
  return(p)
}

topology_plot <- plot_topology_with_epitopes(aligned_segments, epitope_mapping, transcript_order)

# ===== Combined Plot =====
combined_plot <- deeploc_heatmap + topology_plot + 
  plot_layout(widths = c(1, 1.5))

print(combined_plot)

# ggsave("../../results/HER2_analysis/figures/her2_localization_topology_epitopes.pdf",
#        combined_plot, width = 20, height = 8)

4 Phase 3: Expression Analysis

4.1 Step 8: Prepare Expression Data

Filter to targetable isoforms (membrane-bound with retained epitopes) and merge with tissue annotations from TCGA/GTEx.

# Targetable isoforms based on topology and epitope analysis
targetable_isoforms <- c("ENST00000269571", "ENST00000584601", "ENST00000584450", 
                         "ENST00000584014", "ENST00000578502", "ENST00000578199", 
                         "ENST00000445658", "ENST00000582818")

# Load tissue annotations
phenotype <- read_tsv("../../data/HER2_TCGA_GTEX_category_2026.txt", show_col_types = FALSE) %>%
  separate(TCGA_GTEX_main_category, 
           into = c("dataset", "tissue_or_disease"), 
           sep = " ", extra = "merge") %>%
  mutate(tissue_or_disease = tolower(tissue_or_disease))

# Transform and filter expression data
her2_expr_long <- her2_expr %>%
  pivot_longer(cols = -sample, names_to = "sample_id", values_to = "log2_tpm001") %>%
  dplyr::rename(transcript_id = sample) %>%
  mutate(
    transcript_clean = str_remove(transcript_id, "\\..*"),
    # Convert log2(TPM + 0.001) to log2(TPM + 1)
    tpm = 2^log2_tpm001 - 0.001,
    tpm = pmax(tpm, 0),
    log2_tpm_plus1 = log2(tpm + 1)
  ) %>%
  filter(transcript_clean %in% targetable_isoforms)

# Merge with phenotype and classification
her2_expr_final <- her2_expr_long %>%
  left_join(phenotype, by = c("sample_id" = "sample")) %>%
  left_join(transcript_classification, by = "transcript_clean") %>%
  filter(!is.na(dataset))

4.2 Step 9: Visualize Expression Patterns

Compare expression across normal tissues (GTEx) and cancer types (TCGA) to assess tumor selectivity and on-target/off-tumor toxicity risk.

# Categorize normal tissues by clinical importance
tissue_categories <- list(
  "Critical" = c("brain", "blood", "blood vessel", "heart", "liver", "lung", "nerve"),
  "Important" = c("pancreas", "kidney", "stomach", "colon", "small intestine", "bladder"),
  "Others" = c("spleen", "uterus", "ovary", "testis", "prostate", "breast", "skin")
)

tissue_category_df <- data.frame(
  tissue_or_disease = unlist(tissue_categories),
  category = rep(names(tissue_categories), sapply(tissue_categories, length))
) %>%
  mutate(category = factor(category, levels = c("Critical", "Important", "Others")))

# ===== GTEx Data =====
gtex_data <- her2_expr_final %>%
  filter(dataset == "GTEX") %>%
  left_join(tissue_category_df, by = "tissue_or_disease") %>%
  filter(!is.na(category)) %>%
  arrange(category, transcript_clean) %>%
  group_by(category) %>%
  mutate(position_within_cat = as.numeric(factor(transcript_clean))) %>%
  ungroup() %>%
  mutate(
    gap_offset = case_when(
      category == "Critical" ~ 0,
      category == "Important" ~ 9,
      category == "Others" ~ 18
    ),
    x_position = gap_offset + position_within_cat
  )
gtex_data <- gtex_data %>%
  mutate(
    group_label = paste0(category, "_", transcript_clean),
  )

# ===== TCGA Data =====
tcga_cancers <- c("breast invasive carcinoma", "stomach adenocarcinoma", "glioblastoma multiforme")

tcga_data <- her2_expr_final %>%
  filter(dataset == "TCGA", tissue_or_disease %in% tcga_cancers) %>%
  arrange(tissue_or_disease, transcript_clean) %>%
  group_by(tissue_or_disease) %>%
  mutate(position_within_disease = as.numeric(factor(transcript_clean))) %>%
  ungroup() %>%
  mutate(
    gap_offset = case_when(
      tissue_or_disease == "breast invasive carcinoma" ~ 0,
      tissue_or_disease == "stomach adenocarcinoma" ~ 9,
      tissue_or_disease == "glioblastoma multiforme" ~ 18
    ),
    x_position = gap_offset + position_within_disease + 27 # Add offset to place after GTEx (26 + 1 gap)
  )

tcga_data <- tcga_data %>%
  mutate(
    group_label = paste0(tissue_or_disease, "_", transcript_clean),
  )

# ===== Combined Visualization =====
# Combine datasets
combined_data <- bind_rows(
  gtex_data %>% 
    dplyr::select(transcript_clean, log2_tpm_plus1, classification, 
           dataset, group_label, x_position),
  tcga_data %>% 
    dplyr::select(transcript_clean, log2_tpm_plus1, classification, 
           dataset, group_label, x_position)
)

expression_plot <- ggplot(combined_data, 
                          aes(x = x_position, 
                              y = log2_tpm_plus1, 
                              fill = classification, 
                              group = interaction(dataset, group_label))) +
  geom_boxplot(outlier.shape = NA) +
  scale_fill_manual(values = classification_colors, name = "Localization") +
  scale_x_continuous(
    breaks = combined_data %>% 
      distinct(x_position, group_label) %>% 
      pull(x_position),
    labels = combined_data %>% 
      distinct(x_position, group_label) %>% 
      pull(group_label) 
  ) +
  coord_cartesian(ylim = c(0, 7.5)) +
  geom_vline(xintercept = 26.5, linetype = "dashed", color = "gray50", linewidth = 0.5) +
  # Add dataset labels at the top
  annotate("text", x = 13, y = max(combined_data$log2_tpm_plus1) * 1.05, 
           label = "GTEx (Normal Tissues)", fontface = "bold", size = 4) +
  annotate("text", x = 40, y = max(combined_data$log2_tpm_plus1) * 1.05, 
           label = "TCGA (Cancer Types)", fontface = "bold", size = 4) +
  labs(
    title = "HER2 Isoform Expression Across Normal Tissues and Cancer Types",
    subtitle = "GTEx tissues grouped by essentiality, followed by TCGA cancer types",
    x = "Tissue/Cancer Type_Isoform",
    y = "Expression (log2(TPM + 1))"
  ) +
  theme_minimal() +
  theme(
    plot.margin = margin(t = 20, r = 10, b = 10, l = 40),
    axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
    axis.text.y = element_text(size = 10),
    axis.title = element_text(size = 12),
    plot.title = element_text(size = 14, face = "bold"),
    plot.subtitle = element_text(size = 11),
    legend.position = "right",
    legend.text = element_text(size = 10)
  )

print(expression_plot)

# ggsave("../../results/HER2_analysis/figures/her2_expression_gtex_tcga.pdf",
#        expression_plot, width = 16, height = 8)

5 Session Info

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.2
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Europe/Copenhagen
## tzcode source: internal
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] lubridate_1.9.4     forcats_1.0.1       stringr_1.6.0      
##  [4] dplyr_1.1.4         purrr_1.2.0         readr_2.1.6        
##  [7] tidyr_1.3.2         tibble_3.3.0        ggplot2_4.0.1      
## [10] tidyverse_2.0.0     patchwork_1.3.2     ggnewscale_0.5.2   
## [13] Biostrings_2.78.0   Seqinfo_1.0.0       XVector_0.50.0     
## [16] IRanges_2.44.0      S4Vectors_0.48.0    BiocGenerics_0.56.0
## [19] generics_0.1.4      biomaRt_2.66.0     
## 
## loaded via a namespace (and not attached):
##  [1] KEGGREST_1.50.0      gtable_0.3.6         xfun_0.55           
##  [4] bslib_0.9.0          httr2_1.2.2          Biobase_2.70.0      
##  [7] tzdb_0.5.0           vctrs_0.6.5          tools_4.5.1         
## [10] parallel_4.5.1       curl_7.0.0           AnnotationDbi_1.72.0
## [13] RSQLite_2.4.5        blob_1.3.0           pkgconfig_2.0.3     
## [16] dbplyr_2.5.1         RColorBrewer_1.1-3   S7_0.2.1            
## [19] lifecycle_1.0.4      compiler_4.5.1       farver_2.1.2        
## [22] progress_1.2.3       htmltools_0.5.9      sass_0.4.10         
## [25] yaml_2.3.12          pillar_1.11.1        crayon_1.5.3        
## [28] jquerylib_0.1.4      cachem_1.1.0         tidyselect_1.2.1    
## [31] digest_0.6.39        stringi_1.8.7        labeling_0.4.3      
## [34] fastmap_1.2.0        grid_4.5.1           cli_3.6.5           
## [37] magrittr_2.0.4       utf8_1.2.6           withr_3.0.2         
## [40] prettyunits_1.2.0    filelock_1.0.3       scales_1.4.0        
## [43] rappdirs_0.3.3       bit64_4.6.0-1        timechange_0.3.0    
## [46] rmarkdown_2.30       httr_1.4.7           bit_4.6.0           
## [49] otel_0.2.0           png_0.1-8            hms_1.1.4           
## [52] memoise_2.0.1        evaluate_1.0.5       knitr_1.51          
## [55] BiocFileCache_3.0.0  rlang_1.1.6          glue_1.8.0          
## [58] DBI_1.2.3            xml2_1.5.1           vroom_1.6.7         
## [61] rstudioapi_0.17.1    jsonlite_2.0.0       R6_2.6.1