---
id: compute_text_duplicates_py_datascience
name: compute_text_duplicates
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def compute_text_duplicates(texts, near_threshold=0.85, sample_max=2000) -> dict"
description: "Detecta documentos duplicados en un corpus de texto. Los duplicados EXACTOS se calculan siempre con la stdlib: cada documento se normaliza (colapsa espacios, strip, lower) y se hashea con SHA-1; n_exact_dup es cuántos docs repiten uno ya visto y exact_dup_pct su porcentaje. Los CASI-duplicados (near-dup) usan la dependencia OPCIONAL datasketch (MinHash + LSH sobre 3-shingles de palabras); si no está instalada, esa parte degrada a available:False sin afectar al resto. Estilo dict-no-throw del grupo eda — nunca lanza."
tags: [eda, datascience, text, nlp, duplicates, minhash, pure, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [hashlib, re]
example: |
  from datascience.compute_text_duplicates import compute_text_duplicates
  texts = ["El gato come pescado", "El gato come pescado", "Un perro ladra"]
  result = compute_text_duplicates(texts)
  # {"n_docs": 3, "n_exact_dup": 1, "exact_dup_pct": 33.33, "n_unique": 2,
  #  "near_dup": {"available": False, "n_near_dup_docs": 0}}
tested: true
tests:
  - "test_duplicados_exactos"
  - "test_sin_duplicados"
  - "test_vacio"
  - "test_near_dup_degrada"
test_file_path: "python/functions/datascience/compute_text_duplicates_test.py"
file_path: "python/functions/datascience/compute_text_duplicates.py"
params:
  - name: texts
    desc: "Lista de documentos de texto. Los elementos None o que no sean str se descartan silenciosamente; n_docs cuenta solo los documentos válidos. None como argumento se trata como lista vacía."
  - name: near_threshold
    desc: "Umbral de similitud Jaccard (0–1) para considerar dos documentos casi-duplicados en el cálculo near-dup vía MinHashLSH. Solo aplica si datasketch está instalada. Default 0.85."
  - name: sample_max
    desc: "Número máximo de documentos muestreados (los primeros) para el cálculo near-dup, que es O(n) en memoria de MinHashes. No afecta al conteo de duplicados exactos, que siempre recorre todo el corpus. Default 2000."
output: "Dict con exactamente 5 claves, siempre presentes: n_docs (int, docs válidos), n_exact_dup (int, docs que repiten un texto normalizado ya visto = n_docs - n_unique), exact_dup_pct (float a 2 decimales = n_exact_dup/n_docs*100, o None si el corpus está vacío), n_unique (int, nº de textos normalizados distintos), y near_dup (sub-dict con available:bool y n_near_dup_docs:int; cuando available es True incluye además threshold con el near_threshold usado). La función nunca lanza: captura toda excepción y degrada."
---

## Ejemplo

```python
from datascience.compute_text_duplicates import compute_text_duplicates

# Tres copias del mismo texto (con espacios/casing distintos) + dos únicos.
texts = [
    "El gato come pescado",
    "El gato come pescado",
    "el  GATO   come pescado",   # mismo tras normalizar
    "Un perro ladra",
    "La luna brilla",
]

compute_text_duplicates(texts)
# {
#   "n_docs": 5,
#   "n_exact_dup": 2,          # 3 copias del primer texto => 2 repeticiones
#   "exact_dup_pct": 40.0,     # 2 / 5 * 100
#   "n_unique": 3,             # 3 textos normalizados distintos
#   "near_dup": {"available": False, "n_near_dup_docs": 0},  # datasketch ausente
# }

# Corpus vacío: contrato estable, exact_dup_pct None, sin excepción.
compute_text_duplicates([])
# {"n_docs": 0, "n_exact_dup": 0, "exact_dup_pct": None, "n_unique": 0,
#  "near_dup": {"available": False, "n_near_dup_docs": 0}}
```

## Cuando usarla

Úsala en la fase de calidad de un EDA de texto, cuando quieras saber cuánto de
tu corpus es ruido duplicado antes de entrenar, vectorizar o muestrear: te da
el porcentaje de duplicados exactos (`exact_dup_pct`), el número de documentos
únicos (`n_unique`) y, si tienes `datasketch` instalada, una estimación de
casi-duplicados (paráfrasis, copias con pequeñas ediciones) vía MinHash + LSH.
Pásale directamente la columna/lista de textos crudos; la función filtra None y
no-str por ti y nunca lanza, así que es segura para encadenar en pipelines de
perfilado.

## Gotchas

- **Near-dup requiere `datasketch` (opcional).** Si la librería no está
  instalada, `near_dup` degrada a `{"available": False, "n_near_dup_docs": 0}`
  (sin clave `threshold`) y el resto del resultado se calcula igual. Los
  duplicados **exactos** funcionan siempre porque solo usan la stdlib (hash).
- **Normalización de exactos.** Dos textos cuentan como el mismo duplicado
  exacto si coinciden tras `" ".join(doc.split()).strip().lower()`: se colapsan
  espacios/tabuladores/saltos, se recortan extremos y se ignora el caso. Cambios
  de puntuación o acentos SÍ los distinguen (no se eliminan).
- **`n_exact_dup` cuenta repeticiones, no grupos.** Con 3 copias de un mismo
  texto, `n_exact_dup` es 2 (las dos copias extra), no 1. Equivale a
  `n_docs - n_unique`.
- **`exact_dup_pct` es `None` con corpus vacío** (no `ZeroDivisionError`); en
  cualquier otro caso es un float redondeado a 2 decimales.
- **`sample_max` solo limita el near-dup.** El conteo de duplicados exactos
  recorre todo el corpus; el near-dup muestrea los primeros `sample_max`
  documentos para acotar memoria. Si el corpus está ordenado, considera barajar
  antes para que la muestra sea representativa.
- **Elementos no-str se descartan.** `True`/`False` no cuentan como str y se
  ignoran igual que `None`; `n_docs` refleja solo los documentos válidos.
