--- 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.