Guía de Aprendizaje: R para Bioinformática

Módulo 2 — Fundamentos, Ecosistema y Estructura de Datos

Enlace web: https://rpubs.com/daniloceschin/Rbioinfo-modulo2

Asignatura: Bioinformática
Carrera: Licenciatura en Genética
Institución: IUCBC – CIMETSA, Córdoba, Argentina
Docente: Dr. Danilo G. Ceschin


MÓDULO 2 — Estructura de Datos: El ADN de R


Principio fundamental: En R, todo objeto tiene un tipo (qué clase de datos contiene) y una estructura (cómo están organizados esos datos). Antes de cargar tu primer archivo FASTA, VCF, BED o tabla de anotación, necesitás entender cómo R representa la información internamente. Las estructuras que veremos acá son la base de absolutamente todo lo que harás en bioinformática con R.


2.1 Tipos de Datos Básicos (Atomic Types)

R tiene 4 tipos atómicos esenciales. Aparecen en cualquier dominio de la bioinformática: genómica, filogenética, proteómica, análisis de variantes, etc.

numeric — Datos cuantitativos

# Valores que aparecen en distintos contextos bioinformáticos
longitud_gen    <- 3426.0        # pares de bases
gc_content      <- 0.523         # fracción GC
e_value         <- 2.4e-87       # E-value de BLAST
distancia_gen   <- 0.142         # distancia evolutiva (Kimura)
cobertura       <- 42.7          # coverage promedio de secuenciación
score_calidad   <- 38.0          # Phred quality score

# Verificación de tipo
is.numeric(gc_content)           # TRUE
class(gc_content)                # "numeric"
typeof(gc_content)               # "double" (doble precisión)

# Integer — eficiente para conteos y posiciones genómicas
posicion_inicio <- 43044295L     # posición en el cromosoma (cromosoma 17)
n_snps          <- 4721L
is.integer(posicion_inicio)      # TRUE

# Valores especiales que aparecen en bioinformática
Inf                              # -log10(0) en p-valores de GWAS
NaN                              # 0/0 al calcular razones
NA_real_                         # dato faltante numérico
log10(0)                         # -Inf  — frecuente en volcano plots

character — Identificadores y secuencias

# Identificadores biológicos — la moneda corriente en bioinformática
gen_id       <- "ENSG00000012048"   # Ensembl gene ID
transcripto  <- "ENST00000357654"   # Ensembl transcript ID
proteina     <- "P38398"            # UniProt accession
snp_id       <- "rs80357382"        # dbSNP rsID
go_term      <- "GO:0006281"        # Gene Ontology term
organismo    <- "Homo sapiens"
cromosoma    <- "chr17"
genbank_id   <- "NM_007294.4"

# Secuencias — character de largo variable
seq_dna      <- "ATGCGATCGATCGGCTAA"
seq_prot     <- "MPIGSKERPTFFEIFKTRCNKADLTHSQISLNSS"
motivo       <- "TATAAA"            # TATA box

# Operaciones frecuentes en bioinformática
nchar(seq_dna)                            # 18 — longitud de secuencia
nchar(seq_prot)                           # 34 — número de aminoácidos
toupper("atgcgatcg")                      # "ATGCGATCG" — normalizar mayúsculas
substr(seq_dna, 1, 3)                     # "ATG" — codón de inicio

# Extraer partes de identificadores compuestos
strsplit("chr17:43044295-43125483", "[:-]")[[1]]
# "chr17"    "43044295" "43125483"

# Expresiones regulares — esenciales para parsear archivos genómicos
ids <- c("ENSG00000012048", "TP53", "ENST00000357654", "P38398")
grepl("^ENSG", ids)           # T F F F — filtrar Ensembl gene IDs
grepl("^ENST", ids)           # F F T F — filtrar Ensembl transcript IDs
gsub("chr", "", c("chr1","chr17","chrX"))  # "1" "17" "X"

# Limpiar versiones de IDs
sub("\\.\\d+$", "", "ENSG00000012048.5")   # "ENSG00000012048"

logical — Booleanos para filtros y selecciones

tiene_dominio_pfam <- TRUE
es_pseudogen       <- FALSE
anotado_en_omim    <- NA        # Not Available — distinto de FALSE

# Operadores
TRUE & FALSE    # AND → FALSE
TRUE | FALSE    # OR  → TRUE
!TRUE           # NOT → FALSE

# Casos de uso típicos en bioinformática
e_values  <- c(2.4e-87, 0.003, 1.2e-45, 0.18, 8.9e-12)
identidad <- c(98.2, 45.1, 87.6, 32.0, 72.3)   # % identidad BLAST

# Hits significativos de BLAST: E-value < 1e-10 Y identidad > 70%
hit_significativo <- e_values < 1e-10 & identidad > 70
hit_significativo    # TRUE FALSE TRUE FALSE TRUE

which(hit_significativo)   # 1 3 5   ← posiciones
sum(hit_significativo)     # 3       ← cuántos hits

# NA se propaga — importante al trabajar con datos faltantes
NA & TRUE     # NA
NA | TRUE     # TRUE  ← excepción: TRUE prevalece
is.na(NA)     # TRUE  ← forma correcta de detectar NA

factor — Variables categóricas con niveles

El tipo más crítico para diseños experimentales. En bioinformática aparece en prácticamente todo análisis comparativo.

# Diseño experimental típico en genética
condicion <- factor(
  c("WT","KO","WT","KO","WT","KO"),
  levels = c("WT","KO")     # WT = referencia
)
condicion
# [1] WT KO WT KO WT KO
# Levels: WT KO

# Otros usos frecuentes en bioinformática
# Tipo de variante
tipo_var <- factor(
  c("SNP","INDEL","SNP","SNV","INDEL","SNP"),
  levels = c("SNP","SNV","INDEL")
)

# Clasificación taxonómica
reino <- factor(
  c("Bacteria","Eukaryota","Bacteria","Archaea","Eukaryota"),
  levels = c("Bacteria","Archaea","Eukaryota")
)

# Calidad de secuencia (factor ordenado)
calidad <- factor(
  c("Alta","Media","Baja","Alta","Media"),
  levels  = c("Baja","Media","Alta"),
  ordered = TRUE
)
calidad > "Media"    # FALSE FALSE TRUE FALSE FALSE

# Inspección
levels(condicion)    # "WT"  "KO"
nlevels(condicion)   # 2
table(condicion)     # WT KO → 3 3

⚠️ Regla práctica: Siempre definí los niveles de un factor explícitamente. El orden alfabético por default puede poner el grupo equivocado como referencia en cualquier análisis estadístico comparativo — no solo en RNA-seq, sino en GWAS, análisis de variantes, modelos de selección, etc.


2.2 Coerción de Tipos

Jerarquía implícita

Cuando R mezcla tipos distintos en un mismo contenedor, convierte al tipo más general:

logical  →  integer  →  numeric  →  complex  →  character
(más restrictivo)                           (más general)
# Coerción implícita
c(TRUE, 1L, 2L)         # integer:   1  1  2
c(TRUE, 1.5, 2.0)       # numeric:   1.0  1.5  2.0
c(1, 2, "BRCA1")        # character: "1"  "2"  "BRCA1"

# Coerción explícita — la forma controlada
as.numeric("0.523")      # 0.523  ✓  (GC content leído como texto)
as.integer("4721")       # 4721   ✓  (n_SNPs leído como texto)
as.character(43044295L)  # "43044295"
as.logical(0)            # FALSE
as.logical(1)            # TRUE

# ⚠️ Conversión fallida → NA silencioso
as.numeric("n/a")        # NA  + Warning
as.numeric("1,5")        # NA  ← coma decimal (frecuente en planillas europeas)
as.integer("chr17")      # NA  + Warning

# Detectar el problema
anotacion_raw <- c("0.89", "0.72", "N/D", "0.91", "<0.01")
valores       <- as.numeric(anotacion_raw)
# Warning: NAs introduced
sum(is.na(valores))      # 2  ← cuántos no pudieron convertirse
anotacion_raw[is.na(valores)]   # "N/D"  "<0.01"  ← cuáles son

🔬 Situación frecuente: Al parsear archivos de anotación (GFF, GTF, VCF, resultados de BLAST), columnas numéricas como e-values, scores o coordenadas a veces vienen como character. Siempre verificá con str() al cargar datos y convertí explícitamente.


2.3 Vectores: La Unidad Atómica de R

Un vector es una secuencia ordenada de elementos del mismo tipo. Es la estructura más básica de R — incluso un valor único es un vector de longitud 1. Toda la arquitectura del lenguaje está construida sobre vectores.

Creación

# Identificadores
genes       <- c("BRCA1","TP53","EGFR","MYC","PTEN")
cromosomas  <- c("chr17","chr17","chr7","chr8","chr10")
rsids       <- c("rs80357382","rs28934578","rs121913254")

# Coordenadas genómicas
posiciones  <- c(43044295L, 43045802L, 43047643L, 43049120L)
longitudes  <- c(1200L, 850L, 2340L, 560L)

# Valores numéricos
gc_content  <- c(0.52, 0.61, 0.48, 0.55, 0.49)
e_values    <- c(2.4e-87, 8.1e-45, 1.2e-92, 3.3e-61, 5.7e-38)
identidades <- c(98.2, 87.6, 99.1, 92.4, 85.3)  # % identidad

# Secuencias cortas
codones <- c("ATG","GCC","TAC","AGT","TGA")   # codón de inicio, AAs, stop

# Secuencias numéricas — útiles en bioinformática
seq(1, 1000, by = 10)            # posiciones en ventanas deslizantes
seq(0, 1, length.out = 11)       # escala de grises para heatmap
rep(c("Caso","Control"), each=5) # replicar grupos

# Nombrar elementos — muy útil para no depender de posiciones
names(gc_content) <- genes
gc_content["BRCA1"]    # 0.52

Operaciones vectorizadas

Sin bucles for — R opera sobre todos los elementos simultáneamente:

gc_content  <- c(0.52, 0.61, 0.48, 0.55, 0.49)
e_values    <- c(2.4e-87, 8.1e-45, 1.2e-92, 3.3e-61, 5.7e-38)
identidades <- c(98.2, 87.6, 99.1, 92.4, 85.3)

# Transformaciones elemento a elemento
-log10(e_values)                 # transformación estándar para visualización
round(gc_content * 100, 1)       # GC% redondeado: 52.0 61.0 ...
longitudes_kb <- longitudes / 1000   # convertir pb a kb

# Estadísticos
mean(gc_content)        # 0.53
median(identidades)     # 92.4
sd(gc_content)          # 0.049
range(e_values)         # 1.2e-92  8.1e-45

# Comparaciones → vector lógico
e_values < 1e-50            # T F T F F
identidades >= 90           # T F T T F

# hits buenos: E-value < 1e-50 Y identidad >= 90%
buenos <- e_values < 1e-50 & identidades >= 90
which(buenos)               # posiciones
sum(buenos)                 # cantidad

# Ordenar — frecuente al rankear resultados de BLAST, GWAS, etc.
order(e_values)                          # índices de menor a mayor
e_values[order(e_values)]               # e-values ordenados
genes[order(e_values)]                  # genes ordenados por e-value
sort(-log10(e_values), decreasing=TRUE) # ranking por significancia

Subsetting

genes      <- c("BRCA1","TP53","EGFR","MYC","PTEN")
e_values   <- c(2.4e-87, 8.1e-45, 1.2e-92, 3.3e-61, 5.7e-38)
identidades <- c(98.2, 87.6, 99.1, 92.4, 85.3)
names(e_values) <- genes

# Por posición (índices desde 1, no desde 0)
e_values[1]              # BRCA1: 2.4e-87
e_values[c(1,3)]         # BRCA1 y EGFR
e_values[-2]             # todos excepto TP53
e_values[1:3]

# Por nombre
e_values["BRCA1"]
e_values[c("TP53","MYC")]

# Por condición lógica (el más usado)
e_values[e_values < 1e-50]
genes[identidades >= 90]         # genes con buena identidad
genes[e_values < 1e-50 & identidades >= 90]   # hits de calidad

# Modificar
e_values["PTEN"] <- 3.1e-42
e_values[is.na(e_values)] <- 1  # reemplazar NA con valor por default

2.4 Data Frames: La Estructura Reina

El data frame es la estructura más usada en bioinformática aplicada. Es equivalente a una tabla 2D donde cada fila es una observación (gen, variante, secuencia, muestra, especie) y cada columna es un atributo.

Características clave: - Cada columna puede ser de diferente tipo - Todas las columnas tienen el mismo largo - Es el formato nativo de tablas de anotación, resultados de BLAST, tablas de variantes, metadatos de muestras, etc.

Creación y carga

# Tabla de anotación de genes — data.frame típico en bioinformática
anotacion <- data.frame(
  gene_id    = c("ENSG00000012048","ENSG00000141510","ENSG00000146648",
                 "ENSG00000136997","ENSG00000171862"),
  symbol     = c("BRCA1","TP53","EGFR","MYC","PTEN"),
  chr        = c("chr17","chr17","chr7","chr8","chr10"),
  inicio     = c(43044295L, 7668402L, 55019017L, 127735434L, 89692905L),
  fin        = c(43125483L, 7687550L, 55211628L, 127741434L, 89728532L),
  hebra      = c("-","-","+","+","+"),
  biotype    = factor(c("protein_coding","protein_coding","protein_coding",
                        "protein_coding","protein_coding")),
  stringsAsFactors = FALSE
)

# Calcular longitud del gen como columna derivada
anotacion$longitud_pb <- anotacion$fin - anotacion$inicio

# Cargar desde archivo (los formatos más comunes en bioinformática)
# CSV / TSV genérico
anotacion  <- read.csv("annotation.csv", stringsAsFactors = FALSE)
blast_hits <- read.table("blast_results.txt", header=TRUE, sep="\t",
                         stringsAsFactors = FALSE)

# Archivos con comentarios (como VCF o GFF simplificados)
variantes  <- read.table("variantes.tsv", header=TRUE, sep="\t",
                         comment.char="#", stringsAsFactors=FALSE)

# Con readr (más robusto)
library(readr)
blast_hits <- read_tsv("blast_results.txt",
                       col_types = cols(evalue = col_double(),
                                        pident = col_double()))

Inspección inicial — siempre lo primero

dim(anotacion)          # c(filas, columnas)
nrow(anotacion)         # número de genes/variantes/secuencias
ncol(anotacion)         # número de atributos
head(anotacion)         # primeras 6 filas
View(anotacion)         # visor interactivo en RStudio

# *** Fundamental: verificar que los tipos son correctos ***
str(anotacion)
# 'data.frame': 5 obs. of 8 variables:
# $ gene_id   : chr "ENSG00000012048" ...
# $ symbol    : chr "BRCA1" ...
# $ chr       : chr "chr17" ...
# $ inicio    : int 43044295 7668402 ...
# $ fin       : int 43125483 7687550 ...
# $ hebra     : chr "-" "-" ...
# $ biotype   : Factor w/ 1 level "protein_coding": 1 1 ...
# $ longitud_pb: int 81188 19148 ...

summary(anotacion)

Acceso y manipulación

# Columna → vector
anotacion$symbol
anotacion[["longitud_pb"]]    # equivalente

# Fila → data.frame de 1 fila
anotacion[1, ]
anotacion[anotacion$symbol == "BRCA1", ]

# Filtros típicos en bioinformática
# Genes en un cromosoma específico
chr17_genes <- anotacion[anotacion$chr == "chr17", ]

# Genes largos (> 50 kb)
genes_largos <- subset(anotacion, longitud_pb > 50000,
                       select = c(symbol, chr, longitud_pb))

# Con dplyr (sintaxis más legible)
library(dplyr)

anotacion |>
  filter(chr == "chr17", longitud_pb > 10000) |>
  select(symbol, inicio, fin, longitud_pb) |>
  arrange(desc(longitud_pb))

# Resumen por cromosoma
anotacion |>
  group_by(chr) |>
  summarise(
    n_genes         = n(),
    longitud_media  = mean(longitud_pb),
    longitud_max    = max(longitud_pb)
  )

# Unir tablas — fundamental al integrar resultados con anotación
# Equivalente a JOIN (SQL) o VLOOKUP (Excel)
blast_hits <- data.frame(
  query_id = c("seq_001","seq_002","seq_003"),
  subject  = c("ENSG00000012048","ENSG00000141510","ENSG00000146648"),
  pident   = c(98.2, 87.6, 99.1),
  evalue   = c(2.4e-87, 8.1e-45, 1.2e-92)
)

# Agregar anotación a los hits de BLAST
blast_anotado <- merge(blast_hits, anotacion,
                       by.x = "subject", by.y = "gene_id",
                       all.x = TRUE)

# Con dplyr:
blast_anotado <- left_join(blast_hits, anotacion,
                           by = c("subject" = "gene_id"))

📋 tibble vs data.frame: Los paquetes del tidyverse y muchos paquetes modernos devuelven tibble — una versión mejorada con mejor impresión. Son intercambiables en la mayoría de los casos; convertir con as.data.frame() si un paquete antiguo lo requiere.


2.5 Matrices: Datos Homogéneos y Análisis Multivariados

La matriz es la estructura central de los análisis cuantitativos en bioinformática: alineamientos múltiples, matrices de distancia, perfiles de binding, datos de microarray, frecuencias alélicas, etc.

Diferencia clave con data.frame: todos los elementos deben ser del mismo tipo, casi siempre numeric.

Creación y usos en distintos dominios

# ── Caso 1: Matriz de distancias evolutivas ──────────────────────────────────
# (output típico de dist.dna() de ape, dist() o muscle + análisis filogenético)
especies  <- c("H.sapiens","P.troglodytes","G.gorilla","P.pygmaeus","M.mulatta")
distancias <- matrix(
  c(0.000, 0.012, 0.015, 0.028, 0.071,
    0.012, 0.000, 0.014, 0.027, 0.070,
    0.015, 0.014, 0.000, 0.029, 0.072,
    0.028, 0.027, 0.029, 0.000, 0.074,
    0.071, 0.070, 0.072, 0.074, 0.000),
  nrow = 5, byrow = TRUE,
  dimnames = list(especies, especies)
)
distancias["H.sapiens", "P.troglodytes"]   # 0.012

# ── Caso 2: Matriz de frecuencias alélicas (datos poblacionales) ─────────────
poblaciones <- c("AFR","EUR","EAS","AMR","SAS")
snps        <- paste0("rs", c(334, 1799945, 2230021, 4988235))

freq_mat <- matrix(
  c(0.19, 0.48, 0.04, 0.31, 0.26,   # rs334   (HBB — malaria resistance)
    0.00, 0.01, 0.00, 0.00, 0.00,   # rs1799945 (HFE)
    0.75, 0.65, 0.89, 0.72, 0.70,   # rs2230021
    0.45, 0.52, 0.38, 0.47, 0.50),  # rs4988235 (lactasa)
  nrow = 4, byrow = TRUE,
  dimnames = list(snps, poblaciones)
)

# ── Caso 3: Matriz de scores de alineamiento ─────────────────────────────────
secuencias <- c("seq_A","seq_B","seq_C","seq_D")
scores_ali <- matrix(
  c(100,  85,  62,  41,
    85, 100,  58,  39,
    62,  58, 100,  71,
    41,  39,  71, 100),
  nrow = 4,
  dimnames = list(secuencias, secuencias)
)

Operaciones matriciales

# Acceso
freq_mat["rs334", ]                    # frecuencias de rs334 en todas las pops
freq_mat[, "EUR"]                      # todos los SNPs en europeos
freq_mat["rs334", "AFR"]              # valor puntual
freq_mat[1:2, c("AFR","EUR")]          # submatriz

# Operaciones por filas/columnas
rowMeans(freq_mat)         # frecuencia media de cada SNP
colMeans(freq_mat)         # frecuencia media en cada población
apply(freq_mat, 1, var)    # variabilidad de cada SNP entre poblaciones
apply(freq_mat, 2, sd)     # variabilidad entre SNPs en cada población

# Transponer — muy usado
t(freq_mat)                # ahora: poblaciones × SNPs

# Álgebra lineal
cor(t(freq_mat))           # correlación entre poblaciones
dist(freq_mat)             # distancias euclídeas entre SNPs

Heatmap — visualización estándar para matrices

library(pheatmap)

# Heatmap de frecuencias alélicas por población
pheatmap(
  mat              = freq_mat,
  color            = colorRampPalette(c("white","#0A7EA4","navy"))(50),
  display_numbers  = TRUE,        # mostrar valores en las celdas
  number_format    = "%.2f",
  cluster_rows     = TRUE,        # agrupar SNPs por perfil similar
  cluster_cols     = TRUE,        # agrupar poblaciones por perfil similar
  main             = "Frecuencias alélicas por población"
)

# Heatmap de matriz de distancias evolutivas
pheatmap(
  mat          = distancias,
  color        = colorRampPalette(c("white","#F5A623","#0D1F3C"))(50),
  display_numbers = TRUE,
  main         = "Distancias evolutivas entre primates (Kimura 2P)"
)

🧬 En Bioconductor: Muchas clases S4 tienen matrices como componente central. DNAStringSet de Biostrings almacena múltiples secuencias. GRanges de GenomicRanges maneja coordenadas. SummarizedExperiment envuelve una matriz de datos junto a metadatos de filas y columnas en un objeto validado.


2.6 Listas: Contenedores de Objetos Complejos

La lista es el contenedor más flexible de R: puede almacenar objetos de cualquier tipo y tamaño. Es la estructura que usan internamente casi todos los resultados estadísticos y los objetos de Bioconductor.

Creación

# Lista de metadatos de una secuencia / experimento de secuenciación
registro_sec <- list(
  accession    = "NM_007294.4",
  organismo    = "Homo sapiens",
  gen          = "BRCA1",
  longitud_pb  = 7088L,
  cromosoma    = "chr17",
  coordenadas  = c(43044295L, 43125483L),
  hebra        = "-",
  exones       = 23L,
  secuencia    = "ATGCGATCGATCG...",  # secuencia completa
  dominios     = data.frame(          # data.frame anidado
    nombre = c("RING","BRCT_1","BRCT_2"),
    inicio = c(1L, 1646L, 1755L),
    fin    = c(109L, 1736L, 1855L)
  ),
  referencias  = list(
    pmid_original = "7545954",
    pmid_estructura = "8493562"
  )
)

str(registro_sec)    # ver estructura completa
length(registro_sec)
names(registro_sec)

Acceso a elementos

# $ — acceso por nombre (más legible, más usado)
registro_sec$accession          # "NM_007294.4"
registro_sec$coordenadas        # c(43044295, 43125483)
registro_sec$dominios           # el data.frame completo

# [[ ]] — por nombre o posición
registro_sec[["longitud_pb"]]   # 7088
registro_sec[[4]]               # 7088 (por posición)

# Acceso con nombre guardado en variable ($ no puede hacer esto)
campo <- "gen"
registro_sec[[campo]]           # "BRCA1"

# [ ] — extrae sublista (mantiene tipo lista)
registro_sec["accession"]       # lista de 1 elemento — sigue siendo lista
class(registro_sec["accession"])     # "list"
class(registro_sec[["accession"]])   # "character"  ← diferencia crítica

# Acceso anidado
registro_sec$dominios$nombre          # c("RING","BRCT_1","BRCT_2")
registro_sec$referencias$pmid_original # "7545954"

Resultados estadísticos — todos son listas

# Ejemplo: comparación de GC content entre regiones codificantes y no codificantes
gc_coding    <- c(0.54, 0.58, 0.52, 0.56, 0.51, 0.55)
gc_noncoding <- c(0.41, 0.38, 0.44, 0.40, 0.42, 0.39)

res <- t.test(gc_coding, gc_noncoding)

# Ver la estructura completa del resultado
str(res)          # es una lista S3 con 9 elementos
names(res)        # "statistic" "parameter" "p.value" "conf.int" etc.

# Extraer elementos clave
res$statistic     # t = 8.27
res$p.value       # 1.2e-05
res$conf.int      # [0.10, 0.17]
res$estimate      # mean_coding=0.543, mean_noncoding=0.407

# Patrón frecuente: múltiples tests sobre una lista de objetos
secuencias <- list(
  exon1 = c(0.54, 0.58, 0.52),
  exon2 = c(0.61, 0.63, 0.59),
  intron = c(0.41, 0.38, 0.44)
)

# Calcular GC medio de cada región
sapply(secuencias, mean)
# exon1  exon2 intron
# 0.547  0.610  0.410

# Test de normalidad en cada región
tests_norm <- lapply(secuencias, shapiro.test)
sapply(tests_norm, function(x) x$p.value)

2.7 Acceso y Subsetting: $, [ ], [[ ]]

Esta distinción es uno de los puntos de mayor confusión al aprender R. La regla es simple pero su consecuencia es importante:

Operador Devuelve Usar cuando…
$ Elemento simplificado Acceso interactivo por nombre fijo
[ ] Mismo tipo de contenedor Querés mantener estructura (sub-data.frame, sublista)
[[ ]] El elemento en sí Extraer un único elemento de lista o data.frame
# Ejemplos con datos bioinformáticos
blast_df <- data.frame(
query   = c("seq_001","seq_002","seq_003"),
subject = c("BRCA1","TP53","EGFR"),
pident  = c(98.2, 87.6, 99.1),
evalue  = c(2.4e-87, 8.1e-45, 1.2e-92)
)

resultados <- list(
blast    = blast_df,
n_hits   = 3L,
programa = "BLAST+ 2.14"
)

# ─── Diferencia crucial: [ ] vs [[ ]] ──────────────────────────────────────
blast_df["pident"]          # data.frame de 1 columna — class: "data.frame"
blast_df[["pident"]]        # el vector numérico       — class: "numeric"
blast_df$pident             # idéntico a [[]] para data.frame

resultados["blast"]         # lista de 1 elemento  — class: "list"
resultados[["blast"]]       # el data.frame en sí  — class: "data.frame"

# ─── Acceso con variable ────────────────────────────────────────────────────
columna <- "evalue"
blast_df[[columna]]         # ✓ funciona
# blast_df$columna          # ✗ busca columna literalmente llamada "columna"

# ─── Subsetting de data.frame ───────────────────────────────────────────────
blast_df[blast_df$evalue < 1e-50, ]              # filas con e-value bajo
blast_df[blast_df$pident > 90, "subject"]        # subjects con alta identidad
blast_df[, c("query","subject","evalue")]        # columnas seleccionadas

Para objetos S4 de Bioconductor: @

# S4 usa @ para slots — pero las funciones de acceso son preferibles
library(Biostrings)

# DNAStringSet — colección de secuencias DNA
seqs <- DNAStringSet(c(
  BRCA1 = "ATGCGATCGATCGGCTAA",
  TP53  = "ATGGAGGAGCCGCAGTCA",
  EGFR  = "ATGCGACCCTCCGGGACG"
))

class(seqs)               # "DNAStringSet" — S4
length(seqs)              # 3
names(seqs)               # "BRCA1" "TP53" "EGFR"
seqs[["BRCA1"]]           # la secuencia de BRCA1
width(seqs)               # longitudes: 18 18 18

# GRanges — coordenadas genómicas
library(GenomicRanges)
gr <- GRanges(
  seqnames = c("chr17","chr17","chr7"),
  ranges   = IRanges(start = c(43044295L, 7668402L, 55019017L),
                     end   = c(43125483L, 7687550L, 55211628L)),
  strand   = c("-","-","+"),
  gene     = c("BRCA1","TP53","EGFR")
)

# Funciones de acceso (preferir sobre @)
seqnames(gr)    # cromosomas
start(gr)       # posiciones de inicio
end(gr)         # posiciones de fin
strand(gr)      # hebra
mcols(gr)$gene  # metadatos (el factor "gene")
width(gr)       # longitudes: 81189 19149 192612

### 2.8 Clases S3 y S4: El Sistema de Objetos de Bioconductor

Comprender S3 y S4 es esencial para trabajar con Bioconductor: cada vez que uses Biostrings, GenomicRanges, VariantAnnotation, o cualquier paquete de análisis, estarás interactuando con objetos de estas clases.

S3 — Sistema informal (base R y muchos paquetes CRAN)

# S3: lista con atributo "class"
# Ejemplo: resultado de alineamiento (clase inventada para ilustrar)
modelo <- lm(gc_content ~ longitud_pb, data = anotacion)
class(modelo)       # "lm"
is.list(modelo)     # TRUE  ← internamente es una lista

# Acceso con $ (como cualquier lista)
modelo$coefficients
modelo$residuals
modelo$fitted.values

# Los métodos genéricos se adaptan según la clase del objeto
print(modelo)       # salida formateada para "lm"
summary(modelo)     # resumen estadístico para "lm"
plot(modelo)        # gráficos de diagnóstico para "lm"

# ape (filogenética) usa S3
library(ape)
arbol <- nj(distancias)    # Neighbor-Joining a partir de matriz de distancias
class(arbol)               # "phylo"
is.list(arbol)             # TRUE
arbol$tip.label            # nombres de las hojas del árbol
arbol$edge                 # matriz de conexiones
plot(arbol)                # dibuja el árbol

S4 — Sistema formal (Bioconductor)

# S4: estructura validada con slots tipados
library(Biostrings)

# DNAStringSet
seqs <- DNAStringSet(c(BRCA1="ATGCGATCG", TP53="ATGGAGGAG"))
class(seqs)                  # "DNAStringSet"
isVirtualClass("DNAStringSet")  # FALSE
is(seqs, "XStringSet")       # TRUE — herencia de clases

slotNames(seqs)              # ver slots disponibles

# Funciones de acceso (más robustas que @)
length(seqs)                 # número de secuencias
names(seqs)                  # nombres
width(seqs)                  # longitudes
as.character(seqs)           # convertir a character R

# Operaciones nativas de Biostrings sobre secuencias
reverseComplement(seqs)      # complemento reverso
letterFrequency(seqs, letters="GC")  # contenido GC
subseq(seqs, start=1, end=3)         # primeros 3 nucleótidos

# GRanges (GenomicRanges)
library(GenomicRanges)
is(gr, "GRanges")             # TRUE
is(gr, "GenomicRanges")       # TRUE — herencia

# Operaciones sobre coordenadas genómicas
flank(gr, width=500)          # 500 pb upstream
resize(gr, width=1)           # reducir a 1 pb (TSS, por ejemplo)
findOverlaps(gr, gr)          # solapamientos entre rangos

Jerarquía de clases en Bioconductor

# Ejemplo de herencia — cada nivel agrega funcionalidad
Vector
└── List
└── XStringSet
└── DNAStringSet   ← secuencias DNA

RangedSummarizedExperiment
└── SummarizedExperiment       ← datos genómicos + metadatos
└── DESeqDataSet         ← análisis diferencial

GenomicRanges
└── GRanges                    ← coordenadas genómicas
└── GRangesList          ← listas de coordenadas (ej: exones por gen)

### 2.9 Resumen: Cheat Sheet de Estructuras

Estructura Dim Tipo Crear Acceder Uso en bioinformática
vector 1D Homogéneo c(...) v[i], v["name"] IDs de genes, e-values, coordenadas, GC content
matrix 2D Homogéneo matrix(data,nr,nc) m[r,c] Distancias evolutivas, frecuencias alélicas, scores de alineamiento
data.frame 2D Heterogéneo data.frame(...) df$col, df[r,c] Tablas de anotación, resultados BLAST, metadatos, variantes
list nD Heterogéneo list(a=1,b="x") l[["a"]], l$a Resultados de tests estadísticos, objetos S3 (phylo, lm)
S4 object Variable Tipado/validado Constructor del paquete Funciones de acceso, @ DNAStringSet, GRanges, SummarizedExperiment


Recursos Adicionales

Documentación oficial

Libros de acceso libre

Comunidad


Guía elaborada para Bioinformática — Licenciatura en Genética
IUCBC – CIMETSA · Córdoba, Argentina · 2026
Dr. Danilo G. Ceschin