Volver a la lista

Herramienta para eliminar duplicados en bibliotecas de cebadores: combina tres conceptos en una sola herramienta.

Se crea una herramienta en Python que, paso a paso, elimina los duplicados de grandes bibliotecas de cebadores/oligonucleótidos en tiempo O(n), combinando conjuntos hash, diccionarios y expresiones regulares.

Intermedio
|
75min
|
Verificado (2026-07)
cebadoroligonucleótidoEliminar duplicadosConjunto hash.Complemento inverso.Normalización de la secuencia.
Progreso0/8 (0%)

Eliminador de duplicados en bibliotecas de cebadores — Uniendo 3 conceptos para crear una herramienta

Al finalizar este tema

Podrás construir una herramienta práctica que encuentra casi instantáneamente las duplicidades en bibliotecas de cebadores de decenas de miles de entradas, uniendo los tres conceptos aprendidos por separado en el libro de texto (DryBench): tablas hash, diccionarios y expresiones regulares. No se trata simplemente de "eliminar duplicados", sino de comprender por qué el método ingenuo es lento y entender cómo cada concepto constituye una pieza clave de la herramienta, adquiriendo esta comprensión a través de la práctica.

Este texto es un ejemplo educativo general. No utiliza la biblioteca real de un laboratorio específico, sino que toma como tema el problema recurrente en biología molecular: "los problemas que surgen cuando aumenta el stock de oligonucleótidos".


"¿Eh, no es esto lo que pedí la última vez?" — El problema que enfrentamos

Al realizar PCR, clonación, qPCR y secuenciación en el laboratorio, terminamos pidiendo cebadores (oligonucleótidos cortos de ADN, normalmente de 18 a 25 nt) una y otra vez. Al principio, una sola hoja de cálculo era suficiente: nombre, secuencia, uso, fecha de pedido y temperatura de fusión (TmT_m).

Sin embargo, al pasar uno o dos años, esta hoja se expande a miles o decenas de miles de filas. Cuando varias personas utilizan la misma hoja, ocurren cosas como las siguientes:

  • El profesor Kim registra una secuencia bajo hGAPDH_F, y más tarde el profesor Lee la registra exactamente igual pero con un nombre diferente GAPDH_forward.
  • Alguien introduce ATGCGT... en mayúsculas, mientras que otro lo introduce como atgcgt... en minúsculas.
  • Al copiar y pegar, se adjuntan espacios o tabulaciones invisibles al principio o al final de la secuencia.
  • Algunas secuencias se introducen invertidas como complemento inverso, pero en realidad ocupan la misma posición que un cebador ya existente.
  • Debido a errores tipográficos, se mezclan caracteres que no son ACGT, como ATGXGT.

¿Cuál es el resultado? Se terminan pidiendo los mismos oligonucleótidos dos o tres veces, se desperdicia dinero y en el laboratorio surge la confusión "¿es este cebador realmente ese cebador?", lo que provoca errores.

Por eso, decidimos "ordenar las duplicidades" y escribimos un código ingenuo: comparando cada secuencia con todas las demás una por una.

python
# Método ingenuo: compara todos los pares (versión deliberadamente lenta)
def find_duplicates_naive(sequences):
duplicates = []
for i in range(len(sequences)):
for j in range(i + 1, len(sequences)):
if sequences[i] == sequences[j]:
duplicates.append((i, j))
return duplicates

Si hay 100 muestras, es instantáneo. Pero, ¿qué pasa si hay 10,000? El número de comparaciones es aproximadamente 10000 × 10000 / 2 = 50 millones de veces. En un portátil, toma desde unas pocas decenas de segundos hasta unos minutos. Si son 100,000, es 100 veces eso. No terminará ni siquiera después de tomar una taza de café.

No nos detengamos aquí y planteemos una pregunta. ¿Por qué es lento? ¿Y qué herramientas se necesitan para convertir esto en algo instantáneo? Este artículo desarrollará la respuesta a través de código.


Primero veamos el producto terminado (ejecutando la caja negra primero)

Si empezamos desde un editor vacío, nos perderemos. Por lo tanto, primero ejecutaremos la herramienta que crearemos finalmente y, después, abriremos la tapa para analizar su interior. (Este es el método de aprendizaje "top-down" que se utiliza en lugares como fast.ai: primero juegas una partida de un juego terminado y luego aprendes las reglas).

Así es como se utiliza el dedup_primers.py terminado.

python
report = dedup_primer_library("primers.csv")
print(report.summary())

Entonces, se genera el siguiente informe.

text
=== Informe de duplicados de la biblioteca de primers ===
Elementos totales          : 12.480
Elementos válidos          : 12.451   (se excluyen 29 errores de formato)
Secuencias únicas          : 9.832
Duplicados exactos         : 2.619    (misma secuencia, nombre distinto)
Duplicados reverso-complem.: 174      (misma secuencia al invertir)
Tiempo de procesamiento    : 0,31 segundos
─────────────────────────────────
Las tres secuencias más duplicadas
  1) ATGGCACCACAGTCCATGCC  ×7   (GAPDH_F, hGAPDH_forward, ...)
  2) GTCGACCTGCAGGCATGCAA  ×5
  3) CCTGCAGGTCGACTCTAGAG  ×4

La clave reside en dos aspectos: primero, 0.31 segundos, que permite completar en un instante una tarea que la versión anterior tardaba varios minutos; segundo, no se limita a indicar si hay "duplicados" o no, sino que también informa cuántas veces y con qué nombres se ha duplicado cada secuencia. Este artículo se centra en cómo lograr estos dos puntos.


¿De qué componentes se compone esta herramienta (despiece)?

El "motor de eliminación de duplicados" que vamos a ensamblar es, en realidad, una máquina en la que tres conceptos fundamentales están interconectados. Vamos a indicar el origen de cada componente, como si fuera un modelo de plástico.

text
Motor de deduplicación de primers
   ┌─────────────────────────────────────────────┐
   │                                               │
   │ [Entrada] Leer CSV ───── pieza: entrada/salida  │ ← Se proporciona completa
   │              │                                │
   │              ▼                                │
   │ [Limpieza] Normalizar y validar ─ regex         │ ← La construyes tú ★
   │              │                                │
   │              ▼                                │
   │ [Decisión] Detectar duplicados ─ conjunto hash  │ ← La construyes tú ★
   │              │                                │
   │              ▼                                │
   │ [Agregación] Agrupar nombres ─ diccionario      │ ← La construyes tú ★
   │              │                                │
   │              ▼                                │
   │ [Salida] Informe                               │
   └─────────────────────────────────────────────┘
ComponentWhere you learned it (DryBench)What this tool does
File I/Ostring-and-file-ioRead names and sequences from CSV
Regular expressionsregex-pattern-matchingValidate and refine sequences to ensure they contain only ACGT
Hash sethash-tableDetermine if a sequence has been seen before in O(1) time
Dictionarylist-and-dictionaryGroup names associated with each sequence

📌 If you're new to these concepts (entry links at the top) The following three are core components of this tutorial. If they're unfamiliar, spend 30 seconds reviewing them in the side drawer (mini cards on the right) before proceeding. We'll revisit them here when needed.

Now let's build them one by one, from top to bottom. We'll provide the file reading component (tool) already completed, so you only need to focus on the three components: cleaning, validation, and aggregation. Just three, right? We deliberately split it this way because humans can typically grasp only three new concepts at a time.


Step 0 — See what the data looks like

Before writing code, let's look at the ingredients. If you export the primer sheet as CSV, it will roughly look like this:

csv
name,sequence,purpose,date
GAPDH_F,ATGGCACCACAGTCCATGCC,qPCR,2025-01-12
GAPDH_R,GGCATGGACTGTGGTCATGAG,qPCR,2025-01-12
hGAPDH_forward, atggcaccacagtccatgcc ,qPCR,2025-06-03
cloning_F,GTCGACCTGCAGGCATGCAA,cloning,2025-02-20
bad_entry,ATGXGTNN,cloning,2025-03-01

A simple vista se aprecian los problemas.

  • GAPDH_F y hGAPDH_forward tienen nombres diferentes, pero secuencias idénticas. Además, la segunda incluye letras minúsculas y espacios en blanco al principio y al final.
  • La secuencia de bad_entry, ATGXGTNN, contiene X, por lo que no es una secuencia de ADN válida (es un error tipográfico o una abreviatura).

Por lo tanto, para contar correctamente los "duplicados", debe normalizar las secuencias (hacerlas uniformes) antes de compararlas y filtrar los elementos que no son secuencias (validación). De lo contrario, tratará erróneamente ATGGCACCACAGTCCATGCC y atggcaccacagtccatgcc como secuencias diferentes, lo que hará que omita los duplicados.

Esta es la idea clave. La mitad de la eliminación de duplicados no es "comparar bien", sino "normalizar antes de comparar".


Paso 1: leer el archivo (esta parte se proporciona completa)

Leer archivos no es el objetivo de aprendizaje de este tutorial. Dado que su espacio de trabajo mental es valioso, le proporcionaré este código "genérico" como un producto terminado y pasaré a la siguiente etapa. (Sigue exactamente lo que aprendió en la tarjeta string-and-file-io. Si le resulta desconocido, consulte el glosario).

python
import csv
def load_primers(filepath):
"""Lee del CSV una lista de tuplas (nombre, secuencia original)."""
records = []
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"].strip()
seq = row["sequence"] # Se deja sin limpiar para procesarla en el siguiente paso
records.append((name, seq))
return records
# En el ejercicio se simula con una lista para poder ejecutarlo directamente en el navegador.
raw_records = [
("GAPDH_F", "ATGGCACCACAGTCCATGCC"),
("GAPDH_R", "GGCATGGACTGTGGTCATGAG"),
("hGAPDH_forward", " atggcaccacagtccatgcc "),
("cloning_F", "GTCGACCTGCAGGCATGCAA"),
("bad_entry", "ATGXGTNN"),
]
assert len(raw_records) == 5
assert raw_records[2][1] == " atggcaccacagtccatgcc " # Conserva espacios y minúsculas

raw_records todavía está en un estado inicial y sin procesar. Contiene minúsculas, espacios en blanco y secuencias incorrectas. Esto se corregirá en la siguiente etapa.


Paso 2: creación — Normalización y validación de secuencias ★ (comience aquí)

✍️ Esta es la sección que deben completar. Componente = expresión regular. Objetivo: transformar la secuencia a un "formato estándar" y filtrar cualquier elemento que no sea ADN.

Primero, definamos el "formato estándar". Sigamos estas reglas:

  1. Eliminar los espacios en blanco al principio y al final.
  2. Convertir todo a mayúsculas.
  3. Verificar que la secuencia esté compuesta únicamente por A, C, G y T. De lo contrario, descartarla.

Aquí es donde las expresiones regulares demuestran su utilidad. La pregunta "¿la cadena completa es una repetición de ACGT?" se puede resolver con un único patrón de expresión regular: ^[ACGT]+$.

🔎 ¿Por qué usar una expresión regular? (Diagrama: coincidencia de patrones de expresiones regulares) ^ representa el inicio de la cadena, $ el final, [ACGT] significa "uno de estos cuatro caracteres" y + significa "una o más repeticiones". Al combinarlos, se obtiene el significado "solo hay ACGT desde el principio hasta el final". Aunque también se podría verificar cada carácter con un bucle if, la expresión regular expresa esta intención en una sola línea.

python
import re
# Patrón que comprueba que toda la secuencia contiene solo A/C/G/T; se compila para reutilizarlo
DNA_PATTERN = re.compile(r"^[ACGT]+$")
def normalize(seq):
"""Elimina espacios y convierte a mayúsculas; todavía sin validar."""
return seq.strip().upper()
def is_valid_dna(seq):
"""Comprueba que la secuencia normalizada sea ADN válido."""
return DNA_PATTERN.match(seq) is not None

Ahora, vamos a verificarlo.

python
assert normalize(" atggcaccacagtccatgcc ") == "ATGGCACCACAGTCCATGCC"
assert is_valid_dna("ATGGCACCACAGTCCATGCC") is True
assert is_valid_dna("ATGXGTNN") is False # Contiene X y N → se descarta
assert is_valid_dna("") is False # La cadena vacía también se descarta

Observe carefully the last assert. The empty string is filtered because the pattern uses + (one or more). If it had used * (zero or more), the empty string would have passed as "valid DNA". A single regular expression symbol can make or break data quality.

🤔 Self-explanation prompt Why is it correct to perform normalize first and is_valid_dna later? What would happen if validation were done first, followed by normalization? Explain this to yourself using atgc (lowercase) as an example. (Hint: Would the lowercase atgc pass the ^[ACGT]+$ check?)

Now, complete the function that transforms raw records into a list of "clean sequence + name". Invalid items will be collected separately and reported in the summary as "X items excluded".

python
def clean_records(raw_records):
cleaned = [] # (nombre, secuencia normalizada)
rejected = [] # (nombre, original): error de formato
for name, seq in raw_records:
norm = normalize(seq)
if is_valid_dna(norm):
cleaned.append((name, norm))
else:
rejected.append((name, seq))
return cleaned, rejected
cleaned, rejected = clean_records(raw_records)
assert len(cleaned) == 4 # Solo se excluye bad_entry
assert len(rejected) == 1
assert rejected[0][0] == "bad_entry"
# Clave: aunque los nombres difieran, las secuencias ahora son idénticas
assert cleaned[0][1] == cleaned[2][1] == "ATGGCACCACAGTCCATGCC"

La última línea marca el éxito de esta etapa. GAPDH_F y hGAPDH_forward ahora tienen exactamente la misma secuencia. Antes de la normalización, el ordenador las reconocía como cadenas diferentes. Ahora, podemos contar los "duplicados".


Paso 3: Creación — Identificación instantánea de duplicados ★ (conjunto hash)

✍️ Sección para completar directamente. Componente = conjunto hash. Objetivo: responder en O(1) a la pregunta: "¿Ya he visto esta secuencia?".

Recordemos por qué la versión inicial era tan lenta. Para verificar una secuencia, la comparaba una por una con todas las secuencias anteriores. Si procesamos n elementos, el número de comparaciones crece proporcionalmente a n².

🔎 O(n²) vs. O(n) (Diagrama — notación Big O) El operador in aplicado a una lista la recorre desde el principio: O(n) por elemento, lo que da un total de O(n²). En cambio, el operador in en un conjunto calcula directamente la posición mediante una función hash y responde en O(1): O(n) en total. Si los datos se multiplican por 10, el método de lista se vuelve 100 veces más lento, mientras que el método del conjunto solo se vuelve 10 veces más lento.

La clave del conjunto hash es que, al aplicar una función hash a una secuencia, se obtiene un "número de celda (dirección)", y basta con consultar directamente esa celda. Por lo tanto, el tiempo de verificación es prácticamente constante, independientemente de la cantidad de datos. Es como encontrar un libro directamente por su número de clasificación en una biblioteca. En Python, set es precisamente esta tabla hash.

El principio que aprendimos en la tarjeta hash-table se aplica aquí en solo tres líneas.

python
def find_exact_duplicates(cleaned):
"""Encuentra nombres con secuencias exactamente duplicadas en O(n)."""
seen = set() # Secuencias vistas hasta ahora (conjunto hash)
unique = [] # Primera aparición de (nombre, secuencia)
duplicates = [] # (nombre, secuencia) duplicados
for name, seq in cleaned:
if seq in seen: # ← Decisión O(1): el núcleo del algoritmo
duplicates.append((name, seq))
else:
seen.add(seq)
unique.append((name, seq))
return unique, duplicates
unique, dups = find_exact_duplicates(cleaned)
assert len(unique) == 3 # GAPDH_F, GAPDH_R, cloning_F
assert len(dups) == 1 # hGAPDH_forward tiene la misma secuencia que GAPDH_F
assert dups[0][0] == "hGAPDH_forward"
assert dups[0][1] == "ATGGCACCACAGTCCATGCC"

if seq in seen: — Este es el protagonista que redujo el tiempo de procesamiento de varios minutos a 0,3 segundos. Si se hubiera utilizado una lista, la prueba se habría vuelto progresivamente más lenta a medida que seen aumentara, pero al utilizar un conjunto, la velocidad de la prueba se mantiene constante incluso con 100.000 elementos.

🤔 Indicación para la autoexplicación Si se reemplaza seen por [] (lista) en lugar de set(), el código seguirá "funcionando" y los resultados serán idénticos. Sin embargo, ¿por qué no se debería hacer esto? Explica qué sucede con 10.000 o 100.000 elementos, relacionándolo con la complejidad O(n²).


Paso 4: Construcción — Agrupar las repeticiones por cantidad y nombre ★ (diccionario)

✍️ Sección para completar manualmente. Componente = diccionario. Objetivo: agrupar "los nombres utilizados para cada secuencia".

Después de los pasos 1 a 3, se puede determinar si existen duplicados. Sin embargo, en la práctica, lo que realmente interesa es "¿cuántos nombres y cuántas veces se ha registrado esta secuencia?". GAPDH_F, hGAPDH_forward, GAPDH_qF... Es necesario agrupar estos elementos dispersos bajo una única secuencia para obtener una estructura organizada.

Esta es la función principal del diccionario. Clave = secuencia, Valor = lista de nombres utilizados para esa secuencia.

🔎 ¿Por qué un diccionario? (Diagrama — lista y diccionario) Internamente, un diccionario también es una tabla hash (es un tipo de estructura de datos relacionada con el conjunto del paso 3). Mientras que un conjunto solo recuerda si un elemento "existe o no", un diccionario también recuerda "el valor asociado a la clave". Es ideal para relaciones de uno a muchos, como "secuencia → lista de nombres".

python
from collections import defaultdict
def group_by_sequence(cleaned):
"""Agrupa por secuencia como clave y guarda como valor la lista de nombres que la usan."""
groups = defaultdict(list)
for name, seq in cleaned:
groups[seq].append(name) # Si no existe, crea automáticamente una lista vacía
return groups
groups = group_by_sequence(cleaned)
# La secuencia GAPDH debe tener dos nombres asociados
assert groups["ATGGCACCACAGTCCATGCC"] == ["GAPDH_F", "hGAPDH_forward"]
assert len(groups["GGCATGGACTGTGGTCATGAG"]) == 1 # GAPDH_R es única
assert len(groups) == 3 # Tres secuencias únicas

Gracias a defaultdict(list), logré implementar la lógica de "agregar si la clave existe, crear una nueva si no existe" en una sola línea con groups[seq].append(name), evitando la necesidad de una bifurcación con if. Ahora, al extraer solo los valores que tienen dos o más elementos de este groups, se obtiene la "lista de secuencias duplicadas".

python
def duplicated_groups(groups):
"""Selecciona secuencias con dos o más nombres y las ordena por número de duplicados."""
dups = {seq: names for seq, names in groups.items() if len(names) > 1}
# Ordena de mayor a menor número de duplicados
return sorted(dups.items(), key=lambda kv: len(kv[1]), reverse=True)
ranked = duplicated_groups(groups)
assert len(ranked) == 1
assert ranked[0][0] == "ATGGCACCACAGTCCATGCC"
assert ranked[0][1] == ["GAPDH_F", "hGAPDH_forward"]

El “TOP 3 de secuencias más repetidas” del informe proviene exactamente de ranked. Aquí, tres componentes (expresión regular, conjunto hash y diccionario) se integran en un solo proceso.


Paso 5 de creación — Un paso más: duplicados por complementariedad inversa (avanzado)

Hasta ahora, solo hemos detectado duplicados con caracteres idénticos. Sin embargo, existe otra particularidad en la biología: la complementariedad inversa.

El ADN es de doble cadena; la cadena opuesta a 5'-ATGC-3' es 3'-TACG-5', que al leerse en sentido inverso se convierte en 5'-GCAT-3'. Si alguien anota un cebador basándose en la cadena opuesta, aunque los caracteres parezcan completamente diferentes, podría ser el mismo oligonucleótido uniéndose en el mismo sitio.

Para detectar esto, podemos tomar como valor representativo (forma canónica) “la secuencia o su complementariedad inversa que aparezca primero en orden alfabético”. Así, tanto la secuencia original como su complementaria se agrupan bajo el mismo valor representativo. Esta es una extensión de la “normalización” que realizamos en el paso 2. La utilidad de la normalización se manifiesta nuevamente aquí.

python
COMPLEMENT = str.maketrans("ACGT", "TGCA")
def reverse_complement(seq):
return seq.translate(COMPLEMENT)[::-1]
def canonical(seq):
"""Usa como representante el menor lexicográficamente entre la secuencia y su reverso complementario."""
rc = reverse_complement(seq)
return min(seq, rc)
# Verificación: dos secuencias reverso-complementarias deben tener el mismo representante
assert reverse_complement("ATGC") == "GCAT"
assert canonical("ATGC") == canonical("GCAT") # Ambas convergen en la menor lexicográficamente
assert canonical("AAAA") == "AAAA" # Es menor que su reverso complementario TTTT

Ahora, en los pasos 3 y 4, solo hay que colocar canonical(seq) en la posición de seq, lo que permite detectar y corregir las duplicaciones por retrocomplementariedad en un solo paso. No se ha creado una pieza nueva; simplemente se ha añadido una "lente" llamada "valor representativo" a las piezas de evaluación y agregación existentes. Así es como se amplían las funcionalidades de una buena herramienta.

🤔 Indicación para la autoexplicación Si se utiliza canonical, ¿cómo se puede diferenciar el número exacto de duplicaciones del número de duplicaciones por retrocomplementariedad? (La clave está en determinar si se trata de seq == canonical(seq) o no).


Unificar las piezas: el motor completo

Al ensamblar los cinco pasos, se obtiene la herramienta que vimos al principio.

python
def dedup_primer_library(raw_records, use_canonical=False):
cleaned, rejected = clean_records(raw_records)
key = canonical if use_canonical else (lambda s: s)
seen = set()
groups = defaultdict(list)
exact_dups = 0
for name, seq in cleaned:
k = key(seq)
if k in seen:
exact_dups += 1
else:
seen.add(k)
groups[k].append(name)
ranked = sorted(
((k, names) for k, names in groups.items() if len(names) > 1),
key=lambda kv: len(kv[1]),
reverse=True,
)
return {
"total": len(raw_records),
"valid": len(cleaned),
"rejected": len(rejected),
"unique": len(groups),
"exact_duplicates": exact_dups,
"top": ranked[:3],
}
report = dedup_primer_library(raw_records)
assert report["total"] == 5
assert report["valid"] == 4
assert report["rejected"] == 1
assert report["unique"] == 3
assert report["exact_duplicates"] == 1
assert report["top"][0][1] == ["GAPDH_F", "hGAPDH_forward"]

Si todos estos assert se completan, habrán creado una herramienta funcional al integrar tres conceptos del libro de texto. ¡Felicitaciones!


¿Qué tan rápido es ahora? (Análisis detallado del rendimiento)

No basta con decir que es "rápido". Vamos a medirlo directamente. Generaremos 10 000 secuencias (incluyendo duplicados) y compararemos la versión básica con la versión que utiliza hash.

python
import random, time
random.seed(42)
bases = "ACGT"
pool = ["".join(random.choice(bases) for _ in range(20)) for _ in range(2000)]
# Repite 2.000 tipos cinco veces → 10.000 elementos con muchos duplicados
big = [(f"p{i}", random.choice(pool)) for i in range(10000)]
# Versión ingenua O(n²): incluso los primeros 1.500 ya son suficientemente lentos
sample = [s for _, s in big[:1500]]
t0 = time.time()
naive_pairs = find_duplicates_naive(sample)
naive_time = time.time() - t0
# Versión hash O(n): los 10.000 completos
t0 = time.time()
report = dedup_primer_library(big)
hash_time = time.time() - t0
print(f"Versión ingenua (1.500): {naive_time:.3f} segundos")
print(f"Versión hash (10.000) : {hash_time:.3f} segundos")
# Aunque procesa más de seis veces más datos, la versión hash es mucho más rápida
assert hash_time < naive_time

Los números varían según la máquina, pero la dirección es siempre la misma. Incluso con pocos datos, la versión ingenua es más lenta, y al aumentar los datos, la brecha explota. Este es el momento en que puedes tocar con tus propias manos la diferencia entre O(n²) y O(n). big-o-notation Las dos curvas que antes solo veías en un gráfico, ahora se están separando en el cronómetro de tu pantalla.


Hay otros caminos (Reflexión multipista)

Acabas de resolver el problema usando Hash Set + Diccionario. Excelente. Sin embargo, no hay un solo camino para resolver el mismo problema. La verdadera habilidad proviene de saber "qué otros métodos existen además del mío y cuándo es mejor usar cada uno".

  • Pandas drop_duplicates(): Si los datos ya están en un DataFrame, se resuelve con una sola línea df.drop_duplicates(subset="seq"). Internamente también utiliza hashes. La librería hace por nosotros lo que nosotros hicimos manualmente. ¿Cuándo es mejor nuestro método? Cuando necesitemos extraer de forma personalizada incluso la frecuencia de duplicados y la lista de nombres. drop_duplicates simplemente los elimina, pero no genera un informe de quién se duplicó cuántas veces.
  • Biopython: Ya existen herramientas especializadas en el parsing y la complementariedad reversa de secuencias. Procesa la complementariedad reversa de forma segura con Seq(...).reverse_complement(). En el mundo profesional, lo correcto es usar estas librerías probadas. Sin embargo, si las usas sin entender los principios, no podrás depurar por qué son lentas o por qué los resultados son extraños. Por eso las hemos construido desde cero.
  • Ordenamiento y comparación de vecinos: Existe un método que consiste en ordenar las secuencias (O(n log n)) y luego comparar solo los elementos adyacentes. Puede ahorrar más memoria que un set, lo cual es ventajoso cuando los datos son tan grandes que no caben en la RAM.

Clave: Nuestra herramienta no es la "respuesta definitiva"; la respuesta definitiva es la persona que comprende el trade-off de cada método. El hash es rápido pero consume memoria, el ordenamiento ahorra memoria pero es un poco más lento, y las librerías son convenientes pero su funcionamiento interno es una caja negra.

Siguientes pasos (Enlaces de salida inferiores)

Si deseas expandir esta herramienta, los siguientes conceptos son el paso natural a seguir.

  • Ahora hemos cargado todas las secuencias en la memoria. ¿Qué pasa si el archivo es más grande que la RAM? → Aprende a "buscar rápidamente en el disco" con Búsqueda Binaria e Índices de DB, lo que te llevará a la aplicación avanzada de Indexación de DB de secuencias.
  • ¿Detectar duplicados en tiempo real en la web? → Con la aplicación práctica que integra WebSocket.
  • ¿Por qué el conjunto es O(1)? → Vuelvan a la tarjeta Tabla hash y dominen también la resolución de colisiones.

Pruébenlo ustedes mismos (problema independiente)

Esta vez, sin el esqueleto, apliquen sus herramientas a una situación completamente nueva. La estructura es la misma: normalización → decisión → agregación.

  1. Añadan un filtro de longitud: Los cebadores suelen tener entre 18 y 25 nt. Amplíen clean_records para enviar las secuencias que se salgan de este rango a rejected. (Verifiquen con assert si se filtran correctamente las de 30 nt).
  2. Advertencia de contenido GC: Calculen el contenido GC de cada secuencia única y añadan una advertencia al informe si está fuera del rango del 40-60%. (Esto es un adelanto del próximo capítulo de aplicaciones, Calculadora de contenido GC).
  3. Informe del complemento inverso: Al usar use_canonical=True, cuenten por separado las "duplicaciones exactas" y las "duplicaciones del complemento inverso", mostrándolas en dos líneas distintas en el informe. (Utilicen la pista del prompt de autoexplicación de 5 pasos).
  4. Desafío: ¿Cómo deberían modificar la expresión regular del paso 2 para clasificar por separado los casos que contienen caracteres abreviados IUPAC (N, R, Y, etc.) como "secuencia abreviada" en lugar de "inválida"?

Autoevalúense cada tarea con assert. Si la superan, ahora son los dueños de esta herramienta.


Resumen

Partimos de un problema aparentemente trivial: "limpiemos las duplicaciones", y vimos cómo tres conceptos de libro de texto se combinan para formar una herramienta práctica.

  • Las expresiones regulares ajustaron la forma de la secuencia antes de compararla y filtraron el ruido (purificación).
  • El conjunto hash respondió "¿lo he visto antes?" en O(1), reduciendo minutos a 0,3 segundos (decisión).
  • El diccionario agrupó los nombres por secuencia, indicando quién se superpuso y cuántas veces (agregación).

Y estos tres elementos, al aprenderlos por separado, podían parecer "¿y para qué sirve esto?". Pero cuando se conectan, resuelven problemas reales del laboratorio. Este es el poder de la aplicación: aprendamos los conceptos por separado y ensamblémoslos como herramientas.

Reitero que este texto es un ejemplo educativo general. Sus bibliotecas reales tendrán muchas más columnas, reglas y excepciones. La versión detallada es lo que ustedes deben añadir a este esqueleto. El esqueleto siempre será el mismo: normalización, decisión, agregación.

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...