Calidad de los datos y descriptiva con propósito
Dpto. de Fundamentos del Análisis Económico. Universidad de Alicante
Del fichero recibido a la decisión informada, con dos objetivos:
Conocer los datos y su calidad: qué mide cada variable, qué falta, qué está mal, qué no es comparable
Describir con propósito: los patrones que responden a una pregunta económica concreta
No hay receta: depende de los datos y del objetivo, y es iterativo.
Es un problema económico, más que informático: una decisión se toma sobre un número (un margen, una tasa de paro, un ticket medio), y ese número hereda los defectos de los datos
Cada decisión de limpieza es un supuesto sobre cómo se generaron los datos: se escribe, se justifica y se puede deshacer
El negocio
Plataforma que conecta pequeños comercios con compradores en el Levante y Madrid
Dirección quiere responder a tres preguntas:
¿Cuánto gasta un cliente típico y en qué canal?
¿Qué vendedores son más productivos?
¿Crecemos o solo es la campaña de Navidad?
Tablas disponibles
pedidos_bruto: una fila por pedido, tal y como sale del sistema
vendedores: una fila por comercio, con datos de un formulario que rellenan ellos mismos
serie_ventas: ventas mensuales agregadas, 2023-2025
ventas_prov y devoluciones: resúmenes ya agregados (ventas por provincia; devoluciones por canal y categoría)
marketplace.RData, en la página Datos. Al final del tema, un caso con datos del INEImportante
El ejercicio del tema se hace durante estas clases: Tema 04 - Ejercicios del tema. Sus cuatro entregas corresponden a las tres PRÁCTICAS más una con datos del INE.
| variable | qué debería ser | qué llega |
|---|---|---|
fecha |
fecha | texto en DOS formatos distintos |
importe |
número (euros) | texto con coma decimal y punto de miles |
canal |
factor de 3 niveles | texto con mayúsculas y espacios |
cp_envio |
texto de 5 dígitos | número (ha perdido el cero inicial) |
valoracion |
1 a 10, o ausente | incluye el valor centinela -99 |
descuento |
0 a 1, o “sin promoción” | NA con DOS significados distintos |
Antes de tocar nada, cuatro preguntas siempre iguales:
Tamaño y unidad de observación: ¿cuántas filas y qué es una fila? ¿coincide con lo que esperábamos?
Tipos: ¿cada variable tiene el tipo que corresponde a lo que mide?
Rango y categorías: ¿los valores son posibles? ¿las categorías son las del diccionario?
Ausencias: ¿dónde falta información y por qué?
El diccionario de variables (qué mide, en qué unidades, qué valores admite) es parte de los datos: si no viene, hay que pedirlo o reconstruirlo
Hay informes completos automáticos (DataExplorer::create_report()): mucho ruido, no conocen el contexto y no distinguen un error de un dato extremo legítimo. La decisión sigue siendo vuestra. Más herramientas, en el anexo
El tipo lo decide lo que mide la variable, no cómo se ha leído:
Cantidades → número. importe llega como texto con formato español ("1.234,56"): no da error, pero no se puede sumar
Códigos e identificadores → texto. cp_envio llega como número y pierde el cero (3003 por “03003”); su media no tiene sentido
Categorías → factor, vengan como texto (canal, sector) o como números (en la EPA, SEXO1 vale 1 o 6). Se convierten después de corregir sus erratas (Problema 3)
Fechas → fecha (Problema 2)
Una fecha en texto se ordena alfabéticamente: "04/03/2025" va antes que "12/01/2024"
El riesgo real es la ambigüedad: 03/04/2025 es 3 de abril en España y 4 de marzo en EE.UU.
parse_date_time() (del paquete lubridate, que viene con el tidyverse) prueba los órdenes que le digamos: día-mes-año y año-mes-díagroup_by() del Tema 03 obedece al texto, no a la intenciónNA, es que había una categoría que no estaba en nuestra lista. Ya limpias, las demás categorías pasan a factor, varias a la vez con across() (Tema 03)vendedores <- vendedores |>
mutate(
# regla: una facturación por empleado por debajo de 5.000 euros al año
# es imposible en este sector; esa fila estaba declarada en miles
fact_empleado = facturacion_anual / empleados,
facturacion_eur = if_else(fact_empleado < 5000, # if_else(condición,
facturacion_anual * 1000, # si se cumple,
facturacion_anual) # si no)
)
vendedores |> summarize(min = min(facturacion_eur / empleados),
max = max(facturacion_eur / empleados))Hay dos tipos y no significan lo mismo
Filas idénticas: casi siempre un fallo de exportación o de carga
Clave repetida con contenido distinto: dos versiones del mismo hecho
summary() dice cuántos ausentes hay en cada variable numérica (NA's)Y deja ver los ausentes disfrazados: códigos centinela que R lee como números válidos. El mínimo de valoracion es -99, en una escala de 1 a 10
Un -99 no declarado contamina toda la descriptiva: entra en la media, en la desviación típica y en el gráfico
Trabajad con pedidos_bruto y vendedores. En 25 minutos, y sin corregir nada todavía, elaborad un informe de una página con:
Número de filas y comprobación de si id_pedido identifica de forma única cada pedido
Lista de variables cuyo tipo NO corresponde a lo que miden, con el problema concreto de cada una
Recuento de las categorías realmente presentes en canal y en provincia, señalando cuáles son la misma cosa
Número de ausentes por variable, distinguiendo los NA de los códigos centinela
Para CADA problema detectado, una frase: qué cifra del informe final quedaría mal si no lo corregimos
Nota
Ahora el ejercicio 1, los cinco apartados: es esta misma auditoría, apartado por apartado.
Contarlos es el primer paso. Lo que decide qué hacer con ellos es por qué faltan. Tres mecanismos:
Por azar: un fallo del servidor un martes. Quedarse con los casos completos solo reduce la muestra
Según algo que observamos: los clientes de más edad contestan menos las encuestas por la app. Se corrige condicionando en esa variable (comparando dentro de cada grupo de edad)
Según el propio valor que falta: el cliente enfadado no contesta la encuesta. La valoración media observada mide la satisfacción de los que contestan
descuento = NA significa “sin promoción”. El valor correcto es 0valoracion), tres opciones:drop_na(): válido si la ausencia es por azar. Si depende del valor, se elimina justo al cliente que interesa (el enfadado)Sustituir el NA por un valor estimado. Mantiene el tamaño muestral, pero inventa variación: la dispersión sale menor que la real
Con la media (o mediana) de todos: a quien calla se le asigna la nota de los que hablan. Si los que callan son distintos en todo lo demás, es la peor opción
Con la media condicional: la de los clientes parecidos (mismo canal, misma categoría). Es la idea de la regresión (Tema 05)
Un valor atípico es el que queda lejos del resto. Atípico no significa malo ni erróneo
No hay una regla aceptada para detectarlos. El diagrama de caja aplica una convención: más de 1,5 veces el rango intercuartílico por debajo de Q1 o por encima de Q3
Otras convenciones son igual de defendibles: el 5% (o el 1%) de cada cola, o más de tres desviaciones típicas de la media
Aquí la del diagrama de caja marca 77 pedidos, más del 6% de la muestra. Con asimetría (renta, ventas, tamaño de empresa) esa convención señala como atípica una cola derecha normal
Dos tipos:
Errores en los datos (una coma mal puesta): detectarlos, entender por qué, y corregirlos o eliminarlos documentándolo
Valores legítimos: de la misma población (un mayorista), eliminarlos destruye información; de otra población (un cliente B2B entre minoristas), se analizan aparte
¿Cuándo es uno y cuándo otro? No hay regla mecánica: hay que entender los datos y saber de qué van. La IA tampoco lo resuelve, porque no sabe cómo se generaron
4.729 euros con 3 unidades: coma mal puesta. 4.974 euros con 90 unidades: mayorista
Si se conservan: analizar en logaritmos, usar la mediana, o winsorizar (sustituir los valores que pasan el umbral usado para detectarlos por el propio umbral), declarándolo
Sobre la auditoría de la práctica anterior, en 30 minutos:
Escribid un único bloque de limpieza que corrija tipos, categorías y ausentes centinela, y elimine los duplicados exactos
Comprobad el resultado: filas antes y después, tipos, recuento de canal y rango de fecha
Calculad los ingresos totales y el ticket medio antes y después de eliminar los duplicados exactos. ¿En cuántos euros y en qué porcentaje cambia la cifra que iría al informe?
Decidid qué hacer con los 8 pedidos de clave repetida y justificad la decisión en dos frases
Elegid un pedido atípico y argumentad si es error, cliente mayorista u otra población, usando unidades como apoyo
Nota
Ahora el ejercicio 2. Los apartados a) a e) son esta práctica; f) y g) van un paso más allá, con la columna que viene dada y con la identidad que tiene que cumplirse.
pedidos_limpio <- pedidos_bruto |>
# 1. tipos
mutate(importe = parse_number(importe, locale = locale(decimal_mark = ",")),
cp_envio = str_pad(cp_envio, 5, pad = "0"),
fecha = as_date(parse_date_time(fecha, c("dmy", "ymd")))) |>
# 2. categorías
mutate(canal = parse_factor(str_trim(str_to_lower(canal)),
levels = c("web", "app", "tienda"))) |>
# 3. ausentes: centinela y estructural
mutate(valoracion = na_if(valoracion, -99),
descuento = replace_na(descuento, 0)) |>
# 4. duplicados exactos
distinct()El orden es siempre el mismo:
pregunta económica → magnitud que la responde → estadístico o gráfico → lectura
Ejemplos con nuestro caso:
| Pregunta de dirección | Magnitud que la responde |
|---|---|
| ¿Cuánto gasta un cliente típico? | centro de una distribución asimétrica |
| ¿La app canibaliza la web o amplía el mercado? | gasto por cliente y solapamiento |
| ¿Qué vendedores son más productivos? | ventas por empleado |
| ¿Crecemos? | variación interanual |
La variación de una variable es su distribución. Lo que aparezca aquí puede obligar a volver a limpiar. Qué buscar:
Inconsistencias: categorías imposibles (“desconocido”, “otros” que lo son todo), valores fuera de rango
Valores frecuentes: ceros, números redondos, repeticiones. ¿Por qué? Muchas empresas con 49 empleados: el umbral de pequeña empresa es 50
Categorías: ¿tienen sentido? ¿Conviene agrupar las que tienen pocas observaciones o se comportan igual?
Variables continuas: asimetría (logaritmos) y si conviene discretizarlas en tramos (grupos de edad, tramos de renta)
| Variable | Tabla | Gráfico | Resumen |
|---|---|---|---|
Cualitativa (canal) |
frecuencias y proporciones (count()) |
barras (geom_bar()) |
moda, proporciones |
Cuantitativa (importe) |
estadísticos de centro, dispersión y posición | histograma, densidad, caja | media, mediana, desviación típica, cuartiles |
pedidos |> count(canal) |> mutate(prop = n / sum(n))
# agrupar categorías pequeñas
vendedores |>
mutate(provincia = fct_lump_min(provincia, min = 10,
other_level = "Otras")) |>
count(provincia)
pedidos |> summarize(media = mean(importe), mediana = median(importe),
de = sd(importe), p25 = quantile(importe, 0.25),
p75 = quantile(importe, 0.75))Ahora la pregunta es si hay evidencia en los datos de que dos variables están relacionadas: si saber el valor de una cambia lo que esperamos de la otra
La herramienta es la distribución condicional de \(Y\) dado \(X\), \(\Pr(Y \mid X = x)\):
si es la misma para cualquier valor \(x\), no hay relación
si cambia con \(x\), la hay
¿Se devuelve igual en todos los canales?
| canal | pedidos | devueltos | % devueltos |
|---|---|---|---|
| web | 557 | 41 | 7,4% |
| app | 468 | 57 | 12,2% |
| tienda | 183 | 5 | 2,7% |
La proporción de devoluciones cambia con el canal: es evidencia de relación
Se comparan proporciones dentro de cada canal: cada canal tiene un número distinto de pedidos
Si la distribución de \(Y\) cambia con \(X\), conocer \(X\) ayuda a predecir \(Y\): su media condicional \(E[Y \mid X]\)
| Variables | Qué se compara | Estadísticos | Gráfico |
|---|---|---|---|
| Dos cualitativas ( devuelto, canal) |
las proporciones de \(Y\) en cada categoría de \(X\) | tabla de contingencia, proporciones por fila | barras al 100% |
| Cuantitativa y cualitativa ( importe, canal) |
la distribución de \(Y\) en cada grupo | media, mediana y dispersión por grupo | cajas por grupo, densidades superpuestas |
| Dos cuantitativas ( importe, unidades) |
cómo cambia \(Y\) al cambiar \(X\) | correlación | dispersión con su curva |
La regresión sirve en los tres casos: estima la media condicional \(E[Y \mid X]\), sea \(X\) cualitativa o cuantitativa (Tema 05). La correlación solo existe entre dos cuantitativas
Antes de comparar grupos: cuántas observaciones hay en cada uno. Una media sobre 7 pedidos no se compara con una sobre 700
La correlación mide la asociación lineal entre dos variables cuantitativas, entre -1 y 1
Con varias variables se mira la matriz de correlaciones, mejor en un gráfico
unidades e importe: 0,58. En logaritmos baja a 0,15: la arrastran unos pocos pedidos enormes. Es sensible a los atípicos
La curva de geom_smooth() es la media condicional \(E[Y \mid X]\); con method = "lm", la recta de la regresión
Importante
Hasta ahora geom_bar() contaba él solo las observaciones de cada categoría. Aquí la altura ya la hemos calculado con count(), así que hay que decirle que no vuelva a contar: geom_bar(stat = "identity"), y geom_col() es su atajo.
pedidos |> group_by(canal) |>
summarize(n = n(), media = mean(importe), mediana = median(importe))
ggplot(pedidos, aes(x = canal, y = importe)) +
geom_boxplot() + scale_y_log10()
ggplot(pedidos, aes(x = importe, colour = canal)) +
geom_density() + scale_x_log10()
lm(importe ~ canal, data = pedidos) # las mismas medias por canalUn KPI es un número que resume el estado del negocio y sobre el que alguien decide
Para que sirva necesita cuatro cosas, y las cuatro se olvidan a menudo:
Definición escrita: ¿“cliente activo” es quien compró alguna vez, en el último año o en el último mes?
Denominador explícito: por pedido, por cliente, por empleado, por euro invertido
Frecuencia y periodo de comparación: mensual frente al mes anterior no es lo mismo que frente al mismo mes del año pasado
Responsable: quién puede actuar sobre él
| provincia | ventas 2024 | ventas 2025 | variación absoluta | variación % |
|---|---|---|---|---|
| Teruel | 4.000 | 10.000 | +6.000 | +150,0% |
| Alicante | 412.000 | 445.000 | +33.000 | +8,0% |
| Valencia | 380.000 | 399.000 | +19.000 | +5,0% |
| Madrid | 915.000 | 951.000 | +36.000 | +3,9% |
Teruel encabeza el crecimiento porcentual y aporta el 6% del aumento total. Madrid es el último en porcentaje y el primero en euros
Un porcentaje sobre una base pequeña es ruido: mostrad siempre el nivel al lado
La media de medias da a un vendedor con 3 pedidos el mismo peso que a otro con 90
Para agregar tasas hay que ponderar por el denominador, o mejor: sumar numeradores y denominadores
| categoría | canal | pedidos | devueltos | tasa |
|---|---|---|---|---|
| Electrónica | web | 200 | 20 | 10,0% |
| Electrónica | app | 800 | 96 | 12,0% |
| Textil | web | 800 | 240 | 30,0% |
| Textil | app | 200 | 64 | 32,0% |
| Total | web | 1.000 | 260 | 26,0% |
| Total | app | 1.000 | 160 | 16,0% |
Paradoja de Simpson: la app devuelve más en cada categoría y menos en el total. La causa es la composición: la app vende sobre todo electrónica, que se devuelve poco
Qué hacer: comparar dentro de grupos homogéneos. Es lo que hace una regresión que incluye la categoría (Tema 05)
Formas correctas: la variación interanual (mismo mes del año anterior, la más habitual), la media móvil de 12 meses o la serie desestacionalizada que publican INE y Eurostat
Elección del periodo: “las ventas han crecido un 65% en 2025” mide de enero a diciembre, del mes más flojo a la Navidad. Casi cualquier tendencia se fabrica eligiendo el punto de partida: mostrad la serie completa. Cálculo y efecto base en el anexo
Dirección pide cinco indicadores para la reunión mensual. En 30 minutos, sobre pedidos_limpio y serie_ventas:
Elegid los cinco KPI y escribid la definición exacta de cada uno (numerador, denominador, periodo)
Calculadlos con summarize() y presentadlos en una tabla
Para el gasto por cliente, decidid si dais media o mediana y justificadlo con la forma de la distribución
Añadid la variación interanual de ventas del último mes, no la mensual, y explicad en una frase por qué
Escribid al pie del cuadro una advertencia sobre la trampa que más amenaza a vuestros indicadores (composición, base pequeña, estacionalidad o selección en las valoraciones)
Nota
Ahora el ejercicio 3. Los apartados a), b), c), g) y h) son esta práctica; d), e) y f) son las tres trampas de estas transparencias: los promedios de promedios, la agregación que invierte el resultado y la base pequeña.
Muchas preguntas solo se responden con estadística oficial: INE, Eurostat, Banco de España, OCDE, Banco Mundial… Todas publican sus tablas para descargar en CSV
La nuestra: ¿ha subido la cesta de la compra más que el resto de los precios? Hace falta la variación anual del IPC general y la de alimentos, en un periodo largo
Cada tabla de INEbase se descarga en CSV desde su página, y esa dirección se lee directamente desde R:
read_csv2() es read_csv() para el formato español: separador ; y coma decimal. Los nombres con espacios van entre comillas invertidas: `Tipo de dato`
50902 identifica la tabla (IPC nacional, general y grupos): comprobad en la web qué contiene. Viene ordenada, una fila por grupo, tipo de dato y periodo. La API del INE, en el anexo
ipc <- ipc |>
filter(`Tipo de dato` == "Variación anual",
`Grupos ECOICOP` %in% c("Índice general",
"01 Alimentos y bebidas no alcohólicas")) |>
mutate(fecha = ym(Periodo)) # "2025M12" -> fecha (lubridate)
ggplot(ipc, aes(x = fecha, y = Total, color = `Grupos ECOICOP`)) +
geom_line() + geom_hline(yintercept = 0)Un dato oficial también exige AED:
Cuántas series han llegado, y si son las pedidas
Huecos y valores provisionales
Base del índice: se comparan variaciones, no niveles de bases distintas
Ponderaciones: la cesta del IPC cambia cada año
La lectura económica: si alimentación sube más que el general, el encarecimiento golpea más a los hogares de renta baja, que dedican a comer una parte mayor de su gasto. Un índice medio lo esconde
Nota
Ahora el ejercicio 4: la EPA, con la tabla 65335 del INE.
Las tablas del INE son un menú cerrado: paro por sexo y comunidad, sí; paro según la situación de la pareja, no
Todas se calculan con el mismo fichero: una fila por persona entrevistada, los microdatos. Con él se puede:
Reproducir cualquier tabla publicada
Construir el cruce que nadie publicó
Estimar modelos a nivel individual
El precio: los conceptos (ocupado, parado) no vienen calculados. Los construís vosotros
Los microdatos de la EPA están en el ftp del INE: datos_2t26.zip (segundo trimestre de 2026, 21 MB) y el diseño de registro, dr_EPA_2026.xlsx, que dice qué significa cada columna y cada código
El código, en la carpeta del proyecto; lo descargado, en una subcarpeta datos:
epa/
├── analisis-epa.qmd
└── datos/ (datos_2t26.zip, dr_EPA_2026.xlsx, CSV/EPA_2026T2.tab)
url <- "https://www.ine.es/ftp/microdatos/epa/datos_2t26.zip"
download.file(url, "datos/datos_2t26.zip") # basta una vez
unzip("datos/datos_2t26.zip", files = "CSV/EPA_2026T2.tab", exdir = "datos")
epa <- read_delim("datos/CSV/EPA_2026T2.tab", delim = "\t",
col_types = cols(.default = col_character()))download.file() guarda en disco lo que hay en una dirección web. unzip() extrae del zip solo el fichero indicado (files) en la carpeta indicada (exdir)
read_delim() lee texto con el separador que se le diga ("\t" es el tabulador). Con col_types se lee todo como texto, porque los códigos del INE son texto: "03" es un código. Otros formatos del zip, en el anexo
| Variable | Qué es |
|---|---|
NVIVI, NPERS |
vivienda y orden de la persona dentro de ella (47.914 hogares) |
RELPP1, NCONY |
relación con la persona de referencia y orden de su cónyuge |
AOI |
situación laboral codificada (la variable clave) |
FACTOREL |
factor de elevación: a cuántas personas de la población representa la fila |
group_by(NVIVI) del Tema 03AOI está vacía para los menores de 16 años: al leer el texto llega como NAEsos NA son estructurales, como el descuento de la Parte I: la pregunta no se les hace. Se excluyen de la población de 16 y más años, sin imputar nada
Y filter() descarta los NA sin avisar: cualquier condición sobre AOI deja fuera a los menores aunque no se haya pedido. Mejor decirlo explícitamente
AOI no es “0 = parado, 1 = ocupado”. Hay que leer el diseño de registro:| Código | Significado |
|---|---|
| 03, 04 | ocupados (03 son ocupados subempleados) |
| 05, 06 | parados (05 busca su primer empleo) |
| 07, 08, 09 | inactivos |
06 como paro deja fuera a quien busca su primer empleo, que son justo los jóvenes: la tasa nacional baja de 9,873% a 8,757%. Un punto entero de paro por no leer una tabla de códigos de siete líneas| Fichero completo | Sin ponderar | Ponderando |
|---|---|---|
| Tasa de paro | 9,499% | 9,873% |
Para una cifra representativa de la población hay que ponderar con FACTOREL. El INE publica 9,87% para ese trimestre (tabla 65335): coincide, y es la prueba de que hemos entendido el fichero
Regla: primero se replica una cifra publicada, y solo después se calcula lo que no está publicado
Ya podemos contestar lo que no está en ninguna tabla: ¿cómo es el paro de una persona según la situación de su pareja?
Cada fila trae en NCONY el orden de su cónyuge dentro del hogar, así que hay que unir el fichero consigo mismo
| Situación de la pareja | Tasa de paro de la persona | n |
|---|---|---|
| pareja ocupada | 6,00% | 26.046 |
| pareja parada | 21,04% | 1.842 |
\(\hat\beta_0 = 0{,}0600\) es la tasa con la pareja ocupada (6,00%) y \(\hat\beta_1 = 0{,}1504\) la diferencia: 6,00 + 15,04 = 21,04%. Como parado vale 1 o 0, \(\beta_1\) es una diferencia de probabilidades
¿Sigue ahí la diferencia una vez descontados sexo, edad y estudios? Se añaden al modelo:
\[ \text{parado}_i = \beta_0 + \beta_1\, \text{pareja parada}_i + \beta_2\, \text{mujer}_i + \beta_3\, \text{superior}_i + \gamma\, \text{edad}_i + u_i \]