Calidad de los datos¶
Ejercicio data-quality-audit-annex.pdf · PDF
Le haces a la tabla games una pregunta sencilla: ¿cuál es el juego más antiguo? Ordenas por fecha de lanzamiento y la respuesta llega al instante. Un juego de octubre de 2014. Justo detrás, uno de abril de 2004.
2014 antes que 2004. La consulta no falló. No hubo ningún mensaje en rojo. La tabla simplemente te dio una respuesta equivocada con la cara muy seria. Eso es lo que hacen los datos sucios: no se cuelgan, mienten en silencio. Antes de limpiar nada, necesitas saber exactamente dónde está la suciedad. Esta página te enseña a mirar.
Qué aprenderás
- Nombrar los cuatro tipos de datos sucios: valores ausentes, duplicados, tipos o formatos incorrectos y valores atípicos
- Contar las celdas vacías de cualquier columna con
COUNT(*) - COUNT(column) - Detectar un número o una fecha que en realidad está guardado como texto, y decir qué se rompe por ello
- Distinguir una celda vacía que significa "no lo sabemos" de otra que significa algo
- Redactar un inventario de suciedad: columna, tipo de suciedad, evidencia, decisión
- Evitar la trampa: por qué "mismo nombre" no es un duplicado y "muy grande" no es un error
Abre el cofre antes de fundir¶
La calidad de los datos es hasta qué punto puedes fiarte de una tabla para responder a tu pregunta: sus valores están presentes, son correctos, están escritos de una forma coherente y no están repetidos.
En el videojuego de crafteo que usamos todo el año, la Unidad 1 era farmear y saquear: traías los materiales a casa. La Unidad 2 es el horno: prepararlos. Pero nadie funde a ciegas. Primero abres el cofre del botín y miras: huecos vacíos, el mismo objeto dos veces, objetos rotos, una espada con estadísticas absurdas.
Una auditoría de datos es esa mirada. Inspeccionas una tabla, cuentas lo que tiene mal y lo apuntas, sin cambiar nada.
Nuestro cofre es games: 26,688 juegos de Steam, uno por fila, recogidos del sitio de estadísticas Steam Spy y dejados sucios a propósito. Puedes descargar el archivo desde la página de Recursos.
flowchart LR
A["<b>Auditar</b><br>abrir el cofre, contar la suciedad"] --> B["<b>Decidir</b><br>corregir, marcar o dejar"]
B --> C["<b>Limpiar</b><br>el horno: las próximas lecciones"]
C --> D["<b>Informar</b><br>decir qué hiciste y qué queda"]
Cuidado: hoy nadie arregla nada
La tentación es empezar a reparar el primer problema que veas. Resístete. Un arreglo hecho antes de la auditoría es una conjetura, y no sabrás qué más has roto. El único producto de una auditoría es una lista.
Cuatro tipos de suciedad¶
Casi todo lo que puede estar mal en una tabla cae en cuatro grupos. Cada uno tiene su propia forma de encontrarse.
| Tipo | En el cofre | En una tabla | Cómo encontrarlo |
|---|---|---|---|
| Valor ausente | Un hueco vacío | Un NULL: ningún valor |
COUNT(*) - COUNT(column) |
| Duplicado | El mismo objeto dos veces | La misma cosa real en dos filas | GROUP BY + HAVING COUNT(*) > 1 |
| Tipo o formato incorrecto | Una espada guardada como foto de una espada | Una fecha o un número guardado como texto; una misma cosa escrita de dos maneras | Mirar el tipo de la columna; ordenarla; LIKE |
| Valor atípico | Un objeto con 9,999 de daño | Un valor muy alejado de todos los demás. Quizá un error, quizá real | MIN, MAX, ORDER BY ... DESC |
Las próximas cuatro secciones los tratan uno a uno, sobre games. Cada número de abajo sale de ejecutar la consulta sobre la tabla de clase.
Valores ausentes: los huecos vacíos¶
Un valor ausente es una celda sin nada dentro. En SQL se escribe
NULL, que significa "ningún valor", no cero ni una palabra vacía.
Ya conoces la herramienta de la Unidad 1. COUNT(*) cuenta filas. COUNT(column) cuenta solo las filas donde esa columna no está vacía. La diferencia es el número de huecos vacíos.
SELECT COUNT(*) AS total,
COUNT(price) AS with_price,
COUNT(*) - COUNT(price) AS no_price
FROM games;
| total | with_price | no_price |
|---|---|---|
| 26688 | 23593 | 3095 |
Haz lo mismo con todas las columnas, en un único SELECT, y tendrás la imagen completa:
| Columna | Celdas vacías | Porcentaje de las filas |
|---|---|---|
metascore |
23,838 | 89.3% |
price |
3,095 | 11.6% |
developer |
152 | 0.6% |
publisher |
120 | 0.4% |
median_playtime |
12 | menos del 0.1% |
average_playtime |
9 | menos del 0.1% |
game |
3 | 0.01% |
number, release_date, owners |
0 | 0% |
Escribe siempre el porcentaje junto a la cuenta. "23,838 puntuaciones vacías" suena a mucho. "El 89.3% de la columna está vacío" te dice que la columna casi no existe.
Cuidado: una celda vacía no siempre es un error
Dota 2 no tiene precio. ¿Es un error? No: Dota 2 es gratis para jugar. Ese NULL lleva información. Ahora mira metascore, la puntuación de reseñas del sitio Metacritic: 23,838 juegos no tienen ninguna, simplemente porque ningún crítico los ha reseñado nunca. Ese NULL significa "desconocido".
El mismo hueco vacío, dos historias distintas y, más adelante, dos tratamientos distintos. Un auditor apunta de cuál de las dos historias se trata.
Valores ausentes disfrazados¶
Algunos huecos vacíos no lo parecen. Prueba estos dos:
SELECT COUNT(*) FROM games WHERE average_playtime = 0; -- 25757
SELECT COUNT(*) FROM games WHERE price = 0; -- 0
average_playtime vale 0 en 25,757 filas: el 96.5% de la tabla. Nadie cree que el 96.5% de los juegos de Steam se hayan jugado cero minutos. Ese cero significa "no tenemos datos". Es un valor ausente con disfraz.
Y price = 0 no encuentra nada. Ni un solo juego cuesta exactamente cero. Entonces, ¿dónde están los juegos gratuitos? Son los 3,095 que tienen NULL.
| Lo que ves | Qué es | En games |
|---|---|---|
NULL |
Ningún valor | price IS NULL encuentra 3,095 filas |
0 |
Un valor: el número cero | price = 0 encuentra 0 filas |
0 usado como "sin datos" |
Un valor ausente disfrazado | average_playtime = 0 en 25,757 filas |
Tipos y formatos incorrectos: texto disfrazado¶
Un tipo de dato es la clase de valor que una columna puede contener: número entero, número decimal, texto, fecha. El tipo decide qué puedes hacer con la columna.
Pregúntale a la base de datos de qué está hecha cada columna:
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'games'
ORDER BY ordinal_position;
| column_name | data_type |
|---|---|
| number | bigint |
| game | text |
| release_date | text |
| price | double precision |
| owners | text |
| developer | text |
| publisher | text |
| average_playtime | double precision |
| median_playtime | double precision |
| metascore | double precision |
Dos columnas son text y no deberían: release_date (una fecha) y owners (una cantidad). Esto es lo que rompió la consulta del principio de la página:
SELECT game, release_date FROM games ORDER BY release_date LIMIT 3;
| game | release_date |
|---|---|
| Hazard Ops | 8 Oct, 2014 |
| The Dark Legions | Apr 1, 2004 |
| Geneforge 3 | Apr 1, 2005 |
El texto se ordena como un diccionario, carácter a carácter. 8 va antes que cualquier letra, y Apr va antes que Feb porque la A va antes que la F. Una fecha guardada como texto no se puede ordenar, ni filtrar por año, ni poner en un eje de tiempo.
Y empeora. Un formato es la manera en que se escribe un valor. La mayoría de las fechas de aquí tienen el aspecto Nov 16, 2004. ¿Son todas así?
SELECT release_date, COUNT(*) AS n
FROM games
WHERE release_date NOT LIKE '___ %, ____' -- 3 letters, space, day, comma, 4 digits
GROUP BY release_date
ORDER BY n DESC;
42 filas tienen otra forma: 41 dan solo el mes y el año (Oct 2010 tres veces, Jun 2009 dos...) y una está escrita al revés, 8 Oct, 2014. Una columna, tres maneras de escribir una fecha.
owners tiene la misma enfermedad. Solo contiene 13 valores distintos, y todos son rangos escritos como texto, como 10,000,000 .. 20,000,000. El más común es 0 .. 20,000, con 17,923 juegos. Ordénalo para encontrar el juego con más propietarios y obtendrás un disparate:
SELECT game, owners FROM games ORDER BY owners DESC LIMIT 3;
| game | owners |
|---|---|
| DOOM 3 | 500,000 .. 1,000,000 |
| Unreal Tournament 2004: Editor's Choice Edition | 500,000 .. 1,000,000 |
| Beyond Divinity | 500,000 .. 1,000,000 |
Dota 2, con 100,000,000 .. 200,000,000, pierde. Como texto, 5 gana a 1, así que "500 mil" le gana a "100 millones".
Duplicados: el mismo objeto dos veces¶
Un duplicado es la misma cosa real registrada en más de una fila.
¿Hay algún juego dos veces en la tabla? Agrupa por nombre y quédate con los nombres que aparecen más de una vez:
SELECT game, COUNT(*) AS n
FROM games
WHERE game IS NOT NULL
GROUP BY game
HAVING COUNT(*) > 1
ORDER BY n DESC, game;
Salen 74 nombres, que cubren 149 filas. Arriba del todo, Dark Matter, tres veces. Antes de llamarlo duplicado, míralo:
SELECT number, game, release_date, developer
FROM games WHERE game = 'Dark Matter';
| number | game | release_date | developer |
|---|---|---|---|
| 317 | Dark Matter | Oct 17, 2013 | InterWave Studios |
| 777 | Dark Matter | Feb 27, 2015 | Meridian4 |
| 770 | Dark Matter | May 4, 2018 | Barty Games |
Tres desarrolladores, tres fechas. Son tres juegos distintos que casualmente comparten nombre. Si borras dos de ellos, has destruido datos reales.
Cuidado: number no es un identificador
Es tentador pensar que la columna number distingue las filas. Compruébalo: SELECT COUNT(*), COUNT(DISTINCT number) FROM games; devuelve 26,688 filas pero solo 8,846 números distintos. El mismo number lo reutilizan muchos juegos. Una auditoría también te dice en qué no puedes confiar.
Valores atípicos: el objeto con estadísticas absurdas¶
Un valor atípico es un valor muy alejado de todos los demás. Puede ser un error de escritura. También puede ser perfectamente real.
Los precios están en dólares estadounidenses. Mira los extremos de la columna:
SELECT MIN(price), MAX(price), ROUND(AVG(price)::numeric, 2) AS mean FROM games;
| min | max | mean |
|---|---|---|
| 0.49 | 595.99 | 8.95 |
El juego medio cuesta 8.95 y el más caro cuesta 595.99. Más de sesenta veces la media. ¿Una errata? Mira antes de decidir:
SELECT game, price, developer
FROM games ORDER BY price DESC NULLS LAST LIMIT 4;
| game | price | developer |
|---|---|---|
| ADR-Labelling Game | 595.99 | Suomen Kuljetusturva Oy |
| GameMaker Studio 2 UWP | 399.99 | YoYo Games Ltd. |
| VEGAS Pro 16 Edit Steam Edition | 399 | MAGIX Software GmbH |
| Marvelous Designer 8 for Steam | 320 | CLO Virtual Fashion |
Una herramienta para hacer juegos, un editor de vídeo, un programa de diseño de moda en 3D. Steam también vende software profesional, y el software es caro. 27 filas cuestan más de 100 dólares, y son sobre todo productos como estos, no errores de escritura.
Cuidado: un valor atípico no es un error
Borra solo lo que puedas demostrar que está mal. 595.99 es un precio real de un producto real. Un valor atípico es una pregunta ("¿por qué este es tan distinto?"), y la respuesta se encuentra mirando la fila, nunca suponiendo.
El inventario de suciedad¶
Un inventario de suciedad es el resultado escrito de una auditoría: una línea por problema, cada una con su evidencia y una decisión.
Cada línea tiene cuatro partes. La regla para la tercera es estricta: sin número, no hay evidencia. "Hay NULLs" no es una auditoría. "3,095 NULL en price, el 11.6% de las filas" sí lo es.
Para la decisión tienes tres verbos:
| Decisión | Significado | Cuándo |
|---|---|---|
| Corregir | Reparar el valor | Sabes cuál es el valor correcto |
| Marcar | Dejarlo, y señalarlo para que nadie lo olvide | No sabes el valor y no vas a inventarlo |
| Dejar | Mantenerlo tal cual y anotarlo como una limitación | El valor es real, o no se puede hacer nada |
Este es el inventario de games. También es el mapa del resto de la unidad:
| Columna | Qué encontramos | Decisión | Lección |
|---|---|---|---|
game |
3 filas sin nombre | Eliminar las filas: un juego sin nombre no se puede identificar | Tipos y formatos |
release_date |
Texto, en tres formas (42 filas difieren) | Corregir: convertir a una fecha real | Tipos y formatos |
owners |
Texto. Un rango, no un número. 13 valores | Corregir: extraer el límite inferior como número | Unidades y duplicados |
game |
74 nombres aparecen más de una vez | Investigar: ¿el mismo juego o el mismo nombre? | Unidades y duplicados |
price |
Hasta 595.99. Real: software vendido en Steam | Dejar los valores; agruparlos en tramos | Categorización |
price |
3,095 NULL (11.6%): gratis para jugar |
Corregir con lo que sabemos: gratis significa 0 | Imputación |
metascore |
23,838 NULL (89.3%) |
Marcar, nunca inventar una puntuación | Imputación |
| tiempo de juego | 0 en 25,757 filas (96.5%) | Dejar y anotarlo como una limitación | El informe descriptivo |
¿Por qué debería importarme?
Imagina que tu jefe te pide "la puntuación media de los juegos de Steam". Puedes calcularla en una línea. Pero el inventario te dice que la media se construye sobre el 11% de la tabla, y no un 11% al azar. Limpiar no es dejar la tabla bonita. Es saber exactamente qué pueden decir y qué no pueden decir tus números.
Pruébalo: audita games tú mismo¶
Abre SQL → SQL Lab, elige la base de datos Dades classe y el esquema public. Ejecuta una sentencia cada vez: selecciónala y pulsa Run. Si ejecutas un bloque entero, SQL Lab solo muestra el último resultado.
Paso 1 — Tamaño y tipos. Ejecuta SELECT COUNT(*) FROM games; y después la consulta a information_schema.columns de esta página. Deberías ver 26,688 filas y dos columnas text que no deberían ser texto.
Paso 2 — Valores ausentes. Escribe un solo SELECT con un COUNT(*) - COUNT(column) para cada una de las diez columnas. Tus números deberían coincidir con la tabla de "Valores ausentes". Después ejecuta las dos comprobaciones de disfraz (average_playtime = 0 y price = 0).
Paso 3 — Formatos incorrectos. Ejecuta la consulta con NOT LIKE y las dos consultas sobre owners. Deberías poder decir cuántas fechas tienen una forma distinta (42) y por qué DOOM 3 no es realmente el juego con más propietarios.
Paso 4 — Duplicados. Ejecuta la consulta con HAVING COUNT(*) > 1 y después mira las filas de Dark Matter. Escribe una frase: ¿son el mismo juego?
Paso 5 — Valores atípicos. Ejecuta la consulta de MIN/MAX y los precios más altos. Decide, con una razón, si 595.99 es un error de escritura.
SQL Lab solo muestra 1,000 filas. ¿Está rota mi tabla?
No. SQL Lab limita lo que muestra, no lo que contiene la tabla. COUNT(*) siempre cuenta todas las filas. Si COUNT(*) devuelve un total distinto, probablemente el archivo se subió dos veces: un problema real de duplicados, y uno muy bueno para apuntar en tu inventario.
Ideas clave¶
- Una auditoría de datos significa mirar y contar sin cambiar nada. Su único producto es una lista.
- Hay cuatro tipos de suciedad: valores ausentes, duplicados, tipos o formatos incorrectos y valores atípicos.
COUNT(*) - COUNT(column)da las celdas vacías de una columna. Añade siempre el porcentaje.NULLy0son cosas distintas. UnNULLpuede significar "desconocido" (metascore) o llevar significado (un juego gratuito), y un0puede ser un valor ausente disfrazado (average_playtime).- Una fecha o un número guardado como texto se ordena como un diccionario y no se puede calcular con él.
- Mismo nombre no es un duplicado, y un valor atípico no es un error: mira la fila antes de decidir.
- Un inventario de suciedad tiene cuatro partes por línea: columna, tipo de suciedad, evidencia con un número y una decisión (corregir, marcar o dejar).
Comprobación rápida¶
1. ¿Qué consulta cuenta las celdas vacías de la columna price?
-
SELECT COUNT(price) FROM games -
SELECT COUNT(*) - COUNT(price) FROM games -
SELECT COUNT(*) FROM games WHERE price = 0 -
SELECT MIN(price) FROM games
COUNT(*) cuenta todas las filas y COUNT(price) se salta las filas donde price es NULL, así que la diferencia es el número de celdas vacías: 3,095. Buscar price = 0 no encuentra nada, porque cero es un valor y NULL es la ausencia de valor.
2. Al ordenar games por release_date, Apr 1, 2004 queda antes que cualquier fecha de febrero. ¿Por qué?
- Porque abril tiene menos días que febrero ese año
- Porque la tabla se subió en el orden equivocado
- Porque la columna está guardada como texto, y el texto se ordena alfabéticamente
- Porque
ORDER BYno funciona con fechas
release_date tiene el tipo de dato text, así que la base de datos lo compara letra a letra: la A va antes que la F. Una columna de fecha real se ordenaría por tiempo. El tipo decide lo que puedes hacer con una columna, no el aspecto que tiene.
3. Dota 2 tiene un precio NULL y la mayoría de los juegos tienen un metascore NULL. ¿Cuál es la diferencia?
- Ninguna: los dos son errores que hay que borrar
- El precio es un valor atípico y el metascore es un duplicado
- El precio vacío significa que el juego es gratuito; el metascore vacío significa que nadie reseñó el juego
- El precio vacío es desconocido; el metascore vacío significa que la puntuación es cero
La misma celda vacía puede contar dos historias. El NULL del precio lleva información (gratis para jugar), así que más adelante se puede corregir con lo que sabemos. El NULL del metascore significa desconocido, así que se marca, nunca se inventa.
4. Dark Matter aparece tres veces en games, con tres desarrolladores y tres fechas de lanzamiento. ¿Qué debería decir la auditoría?
- Es un duplicado: quedarse con la primera fila y borrar las otras dos
- Son tres juegos distintos que comparten nombre: conservar los tres
- Es un valor atípico, porque ningún otro nombre aparece tres veces
- La columna
numberdemuestra que son el mismo juego
Un duplicado es la misma cosa real en más de una fila. Distinto desarrollador y distinta fecha significan un juego distinto. Y number no sirve de ayuda: tiene solo 8,846 valores distintos para 26,688 filas, así que no es un identificador.
5. La fila más cara de games cuesta 595.99, y el precio medio es 8.95. ¿Cuál es la reacción correcta?
- Borrar la fila: obviamente es un error de escritura
- Sustituirla por el precio medio
- Mirar primero la fila: es software real vendido en Steam, así que se queda
- Ignorarla: los valores atípicos nunca afectan a nada
Un valor atípico es una pregunta, no un veredicto. Los precios más altos pertenecen a herramientas profesionales (un motor de juegos, un editor de vídeo), que de verdad cuestan eso. Borras solo lo que puedes demostrar que está mal.
6. ¿Cuál de estas es una línea de evidencia válida en un inventario de suciedad?
- "La columna price tiene algunos NULL"
- "Las fechas se ven raras"
- "
COUNT(*) - COUNT(metascore)devuelve 23,838: el 89.3% de las filas no tiene puntuación" - "He corregido la columna owners"
Sin número, no hay evidencia. Una línea válida nombra la consulta y la cifra que devolvió, con un porcentaje cuando ayuda. Y la última opción rompe la primera regla de una auditoría: todavía no se arregla nada.
7. Recibes una tabla con los socios de un gimnasio. La columna visits_last_month vale 0 para el 95% de los socios, y el gimnasio está lleno cada tarde. Elige las dos afirmaciones que escribiría un auditor.
- Los ceros probablemente son valores ausentes disfrazados: "sin datos", no "cero visitas"
- Los ceros son valores atípicos y hay que borrarlos
- El hallazgo va al inventario con su recuento y su porcentaje antes de cambiar nada
- La columna está limpia, porque no contiene ningún
NULL
Es otra vez el caso de average_playtime: un 0 que significa "nadie lo midió". Una columna sin NULL puede estar casi vacía igualmente. El auditor escribe primero la evidencia y decide después si corregir, marcar o dejar.
Ahora ya sabes qué está sucio, y tienes un plan. Es hora de encender el horno: a continuación, Limpieza: tipos y formatos, donde release_date por fin se convierte en una fecha de verdad.