Saltar a contenido

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.
  • NULL y 0 son cosas distintas. Un NULL puede significar "desconocido" (metascore) o llevar significado (un juego gratuito), y un 0 puede 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 BY no 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 number demuestra 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.