Saltar al contenido

lección 4

Duplicados: el mismo cliente tres veces

Las dos clases de duplicado —el exacto y el semántico— y el cruce que multiplica filas sin dar ningún error mientras te infla el total.

⏱ 55 min

Tu informe dice 1.400.000 euros de facturación del trimestre. Contabilidad dice 1.000.000. Las dos cifras salen de la misma base de datos, el mismo día, sobre las mismas ventas. Tu consulta no ha dado ningún error, la tabla no tiene huecos, los tipos son correctos y el código es el mismo de la semana pasada. Y sin embargo hay 400.000 euros que no existen.

Cuando aparece un desvío así, la reacción natural es revisar la aritmética. No está ahí: la suma está bien hecha, lo que está mal es lo que suma. Hay filas que aparecen dos veces, y una suma no tiene forma de saber que dos filas idénticas son el mismo hecho contado dos veces. Le das 1.400 filas y te devuelve el total de 1.400 filas, sin más opinión.

Un hueco se ve. Está ahí, vacío, y cualquier recuento te dice cuántos hay. Un duplicado no se ve: tiene todas sus columnas rellenas, pasa cualquier validación de tipos y no dispara ninguna alarma. Ya sabes que drop_duplicates existe y que hay que quitarlos; esta lección es sobre lo que esa función no resuelve, que es casi todo lo caro.

## Dos clases de duplicado, y solo una se arregla con una función

El duplicado exacto es la fila repetida carácter a carácter. Todas sus columnas coinciden con las de otra fila. Es un accidente de fontanería: un fichero que se cargó dos veces, un proceso nocturno que falló a mitad y se relanzó desde el principio, dos exportaciones con los rangos de fechas solapados, un añadido que se ejecutó dos veces porque el primer intento pareció colgarse.

El duplicado semántico es el que cuesta dinero. «Ana López», «ana lopez» y «ANA LOPEZ» son tres cadenas distintas para el ordenador y una sola persona para la empresa. Ninguna función descubre eso por su cuenta, porque no es una propiedad de los datos: es una afirmación sobre el mundo que tú tienes que escribir. Y hasta que la escribes, esa clienta recibe tres correos, cuenta como tres clientas y su gasto sale repartido en tres personas que gastan poco.

La marca roja de la izquierda es todo lo que encuentra duplicated(). Los corchetes de la derecha son lo que ve cualquier persona que mire la lista: cuatro nombres, no ocho.

Ocho filas. La función encuentra una repetida; una persona encuentra que solo hay cuatro clientes. La diferencia no es que la función esté rota: está contestando otra pregunta. duplicated compara cadenas y tú quieres comparar personas, y entre esas dos cosas hay un trabajo que alguien tiene que hacer a mano. Ese trabajo es la mitad de esta lección.

## El exacto: el que se ve y se quita

Con el exacto la técnica es corta, así que lo interesante es el orden en que se hacen las cosas. Primero se cuenta, luego se mira, y solo al final se borra. duplicated devuelve una serie de verdadero y falso marcando, de cada grupo de filas iguales, todas menos la primera; sumarla da cuántas filas sobran.

1import pandas as pd
2
3ventas = pd.DataFrame({
4 "venta_id": [101, 102, 103, 102, 104],
5 "importe": [40, 25, 60, 25, 15],
6})
7
8print("Filas:", len(ventas))
9print("Duplicadas:", int(ventas.duplicated().sum()))
10
11# keep=False marca TODAS las implicadas, no solo la copia sobrante.
12# Es lo que se mira antes de borrar nada.
13print(ventas[ventas.duplicated(keep=False)])
14
15limpio = ventas.drop_duplicates()
16print("Quedan:", len(limpio), "filas, total", int(limpio["importe"].sum()))

Contar, mirar, borrar. En ese orden.

Lo importante del duplicado exacto no es la función: es que casi siempre es síntoma de un proceso que se ejecuta dos veces, y eso se arregla en el proceso, no en la tabla. Si cada mañana limpias los mismos duplicados, no tienes un problema de datos: tienes una carga que no sabe lo que escribió ayer. Limpiarlo a mano todos los días es pagar el mismo peaje para siempre y quedarse sin la pista del incendio.

Cuenta las filas antes y después, siempre, y compara con lo que esperabas. Si creías que sobraban 400 y sobran 12.000, el problema no está en esta tabla y borrar es exactamente lo peor que puedes hacer: estarías tapando la señal. Un número de filas eliminadas que no cuadra con tu hipótesis es un motivo para parar, no para seguir.

## El semántico: la misma cosa escrita de otra forma

Aquí no hay función que valga, porque lo que hay que decidir es qué cuenta como «lo mismo». La estrategia es construir una forma canónica —una única versión de referencia del texto— y comparar por ella en vez de por el original: todo lo que colapse a la misma forma canónica es la misma cosa. Las variantes que hay que colapsar son un catálogo corto, y casi nunca viene una sola: suelen venir tres a la vez.

  • Mayúsculas y minúsculas. «MADRID», «Madrid» y «madrid». La más fácil y la que más veces aparece.
  • Espacios invisibles. Uno al final, uno al principio, dos en medio. No se ven en pantalla ni al imprimir la tabla, y hacen que dos textos idénticos a la vista sean distintos para el ordenador.
  • Tildes y ñ. «López» y «Lopez», «Peña» y «Pena». Unos sistemas admiten acentos y otros no, y la misma persona teclea su nombre de las dos maneras según el formulario.
  • Abreviaturas y formas legales. «Talleres Gómez S.L.», «Talleres Gomez SL», «TALLERES GOMEZ, S.L.». Aquí ya no basta con normalizar: hay que decidir a mano qué formas son equivalentes.
  • Puntuación y separadores. Guiones y puntos en códigos, teléfonos y matrículas. Un identificador con guiones y el mismo sin guiones son dos claves distintas para cualquier cruce.
Cada escritura se junta con las demás en una etapa distinta: la de mayúsculas en la primera, la del espacio sobrante en la segunda, la de la tilde en la tercera. Quita una etapa y esa rama nunca llega.

Ese dibujo es el argumento para no quedarse a medias. Una normalización incompleta parece hecha, y es peor que ninguna: junta la mitad de las variantes, baja el recuento de duplicados y te deja convencido de que el problema está resuelto. Si te dejas la etapa de las tildes, «Ana López» sigue siendo una clienta distinta de «ana lopez» y nadie te lo va a decir.

## La trampa del orden: normalizar antes de deduplicar

Este es el error de secuencia que hay que tener grabado, porque el código corre igual en los dos órdenes. Si deduplicas y luego normalizas, drop_duplicates trabaja sobre los textos originales, que son todos distintos, y no encuentra nada: quita cero filas. Después normalizas y te quedas con una tabla que tiene las tres «Ana López» tan repetidas como al principio, pero ya con aspecto de limpia.

1def clave(serie):
2 """La forma canonica de un texto: lo que se compara, no lo que se muestra."""
3 return (serie
4 .str.lower()
5 .str.strip()
6 .str.normalize("NFKD")
7 .str.encode("ascii", errors="ignore")
8 .str.decode("ascii"))
9
10# PRIMERO la clave, en una columna NUEVA.
11df["cliente_clave"] = clave(df["cliente"])
12
13# DESPUES se deduplica por la clave, no por el nombre original.
14unicos = df.drop_duplicates(subset=["cliente_clave"])

La clave vive en su propia columna. El nombre original no se toca.

Y hay un punto donde la normalización deja de ser segura: normalizar de más fusiona cosas que no son la misma. Si quitas espacios y puntuación de los nombres de empresa, «Gomez SL» y «Gomez, S.L.U.» se juntan, y son dos sociedades distintas. Cuanto más agresiva es la clave, más duplicados encuentras y más riesgo tienes de juntar a dos clientes reales. Cuando exista un identificador de verdad —un correo, un NIF, un número de cliente—, la clave es ese identificador y no el nombre. El nombre es el último recurso.

## Qué columnas definen «la misma cosa»: subset

drop_duplicates sin argumentos compara todas las columnas, y eso solo sirve para el duplicado exacto. En cuanto quieres deduplicar por criterio, hay que decir por qué columnas, y esa lista es el argumento subset. Elegirla es la decisión más importante de este tema y no es una decisión técnica: es una afirmación sobre el negocio metida en una línea de código.

Las dos líneas son correctas y dan resultados distintos. La de la izquierda se lleva una compra que existió: 25 euros que ya no están en ningún sitio.

Con subset a [cliente] el criterio dice «un cliente, una fila», y la segunda compra desaparece porque esa clienta compró dos veces. Con [cliente, fecha] el criterio pasa a ser «un cliente por día» y sobrevive. Ninguna es la correcta en abstracto: depende de lo que la tabla represente. Y la ambigüedad no se acaba ahí: las dos filas del 12 de marzo por 40 euros, ¿son un error de carga o compró dos veces lo mismo, como cuando alguien pide dos cafés? Eso no lo sabe pandas ni lo sabes tú. Lo sabe quien conoce el sistema que generó la tabla, y por eso este es uno de los pocos momentos del oficio en los que la respuesta correcta es levantarse a preguntar.

  1. 01.¿Qué representa una fila? Un pedido, una línea de pedido, un cliente, una lectura de sensor, un cliente y un mes. Sin esta respuesta, cualquier subset es una apuesta.
  2. 02.¿Qué columnas identifican una fila de forma única? Si existe un identificador de verdad, el subset es ese identificador y se acabó la discusión.
  3. 03.¿Puede repetirse legítimamente? Un cliente con dos pedidos: sí. Un pedido con dos filas idénticas: no. Esta respuesta es lo que separa limpiar de destruir.

Escribe el grano de la tabla en una línea antes de tocar nada: «una fila es una línea de factura». Si en esa frase aparece la palabra «y» dos veces, probablemente tengas dos tablas mezcladas en una, y ahí los duplicados no son el problema principal sino la consecuencia.

## keep: mirar a todos los implicados antes de borrar

El argumento keep decide cuál de las filas repetidas sobrevive, y por defecto es «la primera»: la que apareció antes. Es un criterio casual, porque depende del orden en que se cargaron los datos y no de cuál de las dos es mejor. El valor que importa mientras investigas es keep=False, que no conserva ninguna: marca todas las filas implicadas en cualquier repetición. Es la diferencia entre ver la copia sobrante y ver el grupo entero, y solo con el grupo entero delante se puede decidir algo.

1# Ver a todos los implicados, agrupados, antes de tocar nada.
2sospechosos = df[df.duplicated(subset=["cliente_clave"], keep=False)]
3print(sospechosos.sort_values("cliente_clave"))
4
5# Y si hay que elegir cual sobrevive, se ordena ANTES. drop_duplicates
6# se queda con la PRIMERA que encuentra, no con la mejor.
7df = df.sort_values("actualizado_en", ascending=False)
8unicos = df.drop_duplicates(subset=["cliente_clave"], keep="first")

Dos usos distintos: keep=False es para investigar, keep=first es para decidir.

## Cuándo una fila repetida no es un duplicado

Hasta aquí hemos hablado de cómo encontrar duplicados. Toca la otra mitad, que se olvida más: hay filas que se parecen muchísimo y son hechos distintos. Borrarlas no es limpiar, es perder datos reales, y tiene una propiedad especialmente fea: no deja rastro. Un dato que falta se puede contar; un dato que borraste ya no está ahí para que nadie lo cuente.

  • El mismo cliente compró dos veces. Con dos identificadores de pedido distintos, es lo que se espera de un buen cliente. Deduplicar por cliente convierte a los que más compran en los que compran una vez.
  • El mismo sensor midió lo mismo. Dieciocho lecturas de 21,4 grados seguidas significan que la temperatura estaba estable. Cada lectura es una fila y ninguna es copia: lo que las distingue es la hora.
  • La tabla es de líneas y tú piensas en cabeceras. La factura 7 aparece en cuatro filas porque llevaba cuatro productos. Nada está repetido: el grano es la línea.
  • Dos personas distintas con el mismo nombre. Dos «Marta Ruiz» de Sevilla, de 32 años, con correos distintos. Si tu clave es el nombre, acabas de fusionar a dos clientas, y el error es invisible porque el resultado queda más limpio.
  • Una tabla con historia. Si guarda cómo era cada cliente en cada momento, un cliente aparece varias veces a propósito. Ahí no se deduplica: se filtra por la versión vigente.

## El cruce que multiplica filas

Y llegamos al que explica los 400.000 euros del principio, que es el más peligroso de todos porque el duplicado no estaba en tus datos: lo creaste tú al cruzarlos. No hay una fila repetida en la tabla de pedidos, no hay ningún error, ninguna advertencia, y el total sube un 40%.

La causa es un malentendido muy común sobre lo que hace un cruce. Cruzar dos tablas no es «añadirle columnas a la de la izquierda». Es «por cada coincidencia encontrada, una fila». Si la tabla de la derecha tiene dos filas con el mismo cliente, cada pedido de ese cliente encuentra dos coincidencias, y sale dos veces.

Dos pedidos se abren en abanico porque su cliente está dos veces en la tabla de la derecha. Nadie ha tocado la tabla de pedidos y su total ha subido 400.

Mira las dos flechas dobles: salen de los pedidos del único cliente que está repetido en la tabla de clientes. El error no está donde aparece. La tabla de pedidos está perfecta, la que tiene el duplicado es la de clientes, y el síntoma se ve en el total de la de pedidos. Por eso el reflejo útil no es «reviso los pedidos» sino «reviso la tabla de la derecha».

La señal que lo delata cabe en una frase, y vale para cualquier cruce que hagas el resto de tu vida: un cruce que añade filas es un aviso. Si entran 6 pedidos y salen 8, algo está mal, y no en los pedidos. Cuando el cruce es de uno a muchos a propósito —pedidos con sus líneas— las filas también aumentan, pero eso lo sabes de antemano y lo esperas. El problema es el aumento que no esperabas.

1# Opcion 1: la guarda a mano. Falla en rojo diciendo lo concreto.
2antes = len(pedidos)
3cruce = pedidos.merge(clientes, on="cliente_id", how="left")
4if len(cruce) != antes:
5 raise ValueError(
6 f"el cruce ha pasado de {antes} a {len(cruce)} filas: "
7 "cliente_id esta repetido en la tabla de clientes"
8 )
9
10# Opcion 2: que lo compruebe pandas. m:1 significa "muchos pedidos,
11# un cliente"; si la derecha repite la clave, revienta sola.
12cruce = pedidos.merge(clientes, on="cliente_id", how="left", validate="m:1")

Las dos formas de que el cruce avise. La segunda es una palabra.

Y una advertencia sobre el arreglo: deduplicar la tabla de la derecha no siempre es lo correcto. Si el cliente está dos veces porque hay dos versiones suyas con fechas de vigencia distintas, quedarte con una al azar te da una ciudad que puede ser la vieja. La pregunta previa es la misma de siempre: por qué está repetido. Si es basura, se deduplica; si es historia, se filtra la versión que toca.

## Qué le hace un duplicado a cada métrica

Los duplicados no afectan a todas las cifras por igual, y saber a cuáles afectan es lo que permite detectarlos sin buscarlos. Con el cruce de antes se ven las cuatro respuestas de golpe: el mismo error, medido de cuatro maneras distintas.

Cada panel tiene su propia escala, así que lo que hay que mirar es la forma. La suma salta, el recuento de distintos no se mueve, y la media sube lo justo para no llamar la atención de nadie.

La suma y el recuento de filas se inflan, y eso es lo esperable. El recuento de valores distintos no se mueve ni un punto: los 6 pedidos siguen siendo 6 pedidos aunque haya 8 filas. Ese contraste es más útil de lo que parece, porque convierte el recuento de distintos en un detector: si el número de filas y el número de identificadores distintos no coinciden, tienes duplicados, y lo sabes con una línea.

Y luego está la media, que es el caso sutil y por eso el peligroso. La media se desplaza hacia el valor que está repetido. Aquí los pedidos que salen dos veces son los de 250 y 150, cuyo promedio está por encima de la media general, así que sube de 166,67 a 175. Solo un 5%: no suficiente para que nadie pare la reunión, suficiente para que la cifra esté mal. Si los repetidos hubieran sido los pequeños, habría bajado con la misma discreción.

La comprobación de tres segundos que conviene hacer en cualquier tabla nueva: comparar el número de filas con el número de valores distintos de su identificador. Si la tabla tiene 8 filas y 6 identificadores, ya sabes que hay duplicados antes de mirar nada más. Y si no sabes cuál es el identificador de esa tabla, acabas de encontrar un problema mayor que los duplicados.

## Cómo se cuenta esto cuando ya ha salido en un informe

  • Di la cifra corregida primero y el motivo después. «La facturación del trimestre es 1.000.000, no 1.400.000: el cruce con clientes duplicaba pedidos». En ese orden, porque lo primero que necesita quien te escucha es el número bueno.
  • Di cuántas filas has quitado y por qué criterio. «He dejado una fila por pedido» es una frase que cualquiera puede rebatir si el criterio es malo. «He limpiado los duplicados» no se puede rebatir, y eso es lo malo.
  • Si has deduplicado por nombre, avísalo. Es el criterio que puede fusionar a dos personas, y quien conoce el negocio a lo mejor sabe que hay dos clientes que se llaman igual. Hacerlo sin decirlo es tomar esa decisión en nombre de otro.
  • Si no sabes si una repetición es legítima, no la borres: pregunta. Mientras esperas, da las dos cifras. Dos cifras con su motivo son un dato; una cifra con una suposición dentro es una apuesta que has hecho por todos.

Los duplicados son el segundo problema de una tabla sucia y el primero que te obliga a preguntar en vez de decidir tú. Ya sabes tratar lo que falta y lo que sobra por repetido. Queda el dato que aparece una sola vez, que está donde debe, que nadie ha copiado, y que aun así descoloca la media entero: el valor extremo.

Regístrate para guardar tu progreso.