Unidad 4 - Fuentes, Calidad de Datos y EDA
Introducción al Business Analytics · Semana 6 · 06278-ECO
1 Objetivo de la semana
Esta semana NO hablaremos de modelos ni de gráficos sofisticados. El foco está en algo más fundamental: saber qué tan confiables son tus datos antes de usarlos.
La pregunta central de esta semana
Antes de analizar, siempre debes poder responder:
- ¿De dónde vienen estos datos y cómo fueron recolectados?
- ¿Hay valores faltantes, duplicados o imposibles?
- ¿Las categorías están escritas de forma consistente?
- ¿Hay valores atípicos que distorsionarán cualquier cálculo?
Un modelo entrenado con datos sucios produce resultados sucios. La limpieza no es opcional — es el paso que protege todas las decisiones que vienen después.
1.1 Lo que aprenderemos esta semana
1. Entender el origen y tipo de los datos
- Fuentes primarias vs. secundarias
- Datos estructurados vs. no estructurados
- Por qué el origen importa para la interpretación
2. Diagnosticar problemas de calidad
- Visión general con
skim(): faltantes, tipos, distribuciones - Revisión de categorías con
unique()ytable() - Duplicados con
duplicated()
3. Limpiar con dplyr
- Estandarizar categorías, corregir tipos, filtrar imposibles
- Del dataset “sucio” al dataset “listo para analizar”
4. EDA: explorar antes de modelar
- Resumen descriptivo con
skim() - Visualizaciones exploratorias con
ggplot2 - El propósito del EDA no es “hacer gráficos bonitos” — es encontrar señales y anomalías antes de comprometer decisiones
2 Fuentes de datos
Antes de limpiar, necesitamos entender de dónde vienen los datos. El origen determina qué errores son probables.
Fuente primaria vs. secundaria
- Primaria: los datos los recolectaste tú (o tu organización) directamente para este análisis. Ejemplos: encuesta de satisfacción, registro de ventas propio, experimento A/B.
- Ventaja: sabes exactamente qué pregunta se hizo y cómo.
- Riesgo típico: errores de captura, formularios mal diseñados.
- Secundaria: alguien más los recolectó para otro propósito. Ejemplos: datos del DANE, Nielsen, Kaggle, API de redes sociales.
- Ventaja: mayor volumen, menor costo.
- Riesgo típico: no conoces el proceso de recolección; las definiciones pueden no coincidir con las tuyas.
En este curso trabajamos principalmente con datos estructurados: tablas con filas (observaciones) y columnas (variables).
| Tipo | Descripción | Ejemplos |
|---|---|---|
| Estructurado | Tabla con filas y columnas | Excel, CSV, base de datos SQL |
| Semi-estructurado | Tiene estructura pero flexible | JSON, XML |
| No estructurado | Sin formato predefinido | Texto libre, imágenes, audio |
Por qué el origen importa para interpretar
Si tus datos de ventas vienen del sistema ERP pero tienen una columna “región” que el equipo llenaba manualmente en Excel… es probable que encuentres “Norte”, “norte”, “NORTE” y “Nortte” representando la misma región. No es un error del sistema — es un error humano de captura. Saber el origen te permite anticipar qué tipo de suciedad buscar.
3 Instalación y carga de paquetes
Esta semana usaremos tres paquetes:
dplyr— para manipular y limpiar datos (ya lo conocemos de Semana 04).ggplot2— para hacer gráficos exploratorios (ya lo conocemos de Semana 05).skimr— nuevo: genera un resumen rápido y completo de cada variable del dataset.
Recordatorio
install.packages() se corre una sola vez en la Consola (nunca en el script). library() se corre cada vez que abres RStudio. Si trabajas en este documento WebR, los paquetes ya están disponibles.
4 El dataset de esta semana: ventas_raw
Usaremos una versión realista y sucia del mismo dataset de ventas de las semanas anteriores. Como si lo hubiéramos recibido “tal cual” del sistema antes de cualquier limpieza.
¿Qué tiene de diferente este dataset?
Es el mismo origen (ventas de tecnología, 3 trimestres, 3 regiones), pero con problemas típicos de datos reales:
- Categorías inconsistentes: “Norte”, “norte”, “NORTE” en la misma columna
- Tipo de dato incorrecto:
preciollega como texto (character) con signos de dólar (“$1200”) - Valores faltantes:
NAen precio y cantidad - Valor imposible: una transacción con
cantidad = -5 - Outlier de captura: un Mouse con precio = 12.000 (probablemente debería ser 120)
- Filas duplicadas: 3 transacciones aparecen dos veces
- Columna basura:
observaciones, casi siempre vacía
Nuestro trabajo esta semana es detectar, documentar y corregir cada uno de estos problemas.
5 Paso 1 — Diagnóstico: ¿qué tan sucios están los datos?
Antes de corregir, necesitamos un diagnóstico completo. La secuencia es:
- Primero, una visión general de todo el dataset con
skim() - Luego, si algo llama la atención, profundizamos con herramientas específicas
5.1 Visión general: skim()
skim() es nuestra primera herramienta de diagnóstico. En una sola llamada nos dice: cuántas filas tiene el dataset, el tipo de cada variable, cuántos valores faltantes hay, y un resumen estadístico por columna.
Cómo leer la salida de skim()
La salida se divide en dos bloques según el tipo de variable:
Variables de texto (character):
| Columna | Lo que nos dice |
|---|---|
n_missing |
Cuántos valores faltantes tiene esa columna |
n_unique |
Cuántos valores distintos hay |
Mira la columna region: si n_unique dice 5 en vez de 3, hay inconsistencias de mayúsculas/minúsculas.
Mira precio: si aparece como character en vez de numeric, hay un problema de tipo de dato.
Variables numéricas (numeric):
| Columna | Lo que nos dice |
|---|---|
n_missing |
Cuántos valores faltantes tiene esa columna |
mean, sd |
Promedio y desviación estándar |
p0, p100 |
Mínimo y máximo — aquí aparecen los valores imposibles y outliers |
Mira cantidad: si p0 (el mínimo) es negativo, hay una cantidad imposible.
Regla práctica: lee skim() de arriba a abajo, columna por columna. Cualquier cosa que no tenga sentido es un problema que documentar.
5.2 Inconsistencias en categorías: unique() y table()
skim() nos dijo cuántos valores únicos hay en cada columna de texto. Ahora vamos a ver cuáles son esos valores:
Error clásico: “Norte” ≠ “norte” ≠ “NORTE”
Para R, estas son tres categorías diferentes. Si calculas el ingreso por región con group_by(region), obtendrás 5 grupos en vez de 3. Los totales nunca cuadrarán y el gerente tomará decisiones con datos incorrectos — sin que R emita ningún error.
Este es uno de los errores más frecuentes y silenciosos en análisis de datos.
5.3 Filas duplicadas: duplicated()
Qué hace duplicated()
duplicated(df) compara cada fila con todas las anteriores y devuelve TRUE si ya apareció antes.
sum(duplicated(df))→ cuántas filas son copias exactasdf[duplicated(df), ]→ ver esas filas
¿Por qué aparecen duplicados? En la práctica ocurren por exportaciones dobles del sistema, joins mal hechos, o actualizaciones que acumulan en vez de reemplazar. Siempre pregunta la causa antes de eliminar.
5.4 Tipo de dato incorrecto: el problema de precio
skim() ya nos alertó que precio es de tipo character. Veamos exactamente por qué:
¿Por qué es un problema que precio sea character?
Cuando una columna numérica llega como texto, R no puede hacer aritmética con ella:
## Esto da error
sum(ventas_raw$precio, na.rm = TRUE)
## Error: invalid 'type' (character) of argumentPara convertirla necesitamos: 1. Eliminar el signo $ con gsub() 2. Convertir el texto limpio a número con as.numeric()
Lo haremos en la sección de limpieza.
5.5 Valores imposibles y outliers
Con skim() ya vimos el mínimo de cantidad. Confirmemos el problema y exploremos el precio visualmente:
El histograma como detector de outliers
Cuando el eje X se extiende muy a la derecha con una barra solitaria, es señal de outlier. La gran mayoría de precios están entre 20 y 1.250, pero hay un punto en ~12.000.
Ese valor corresponde a un Mouse con precio = 12.000. Un mouse no cuesta $12.000 — es casi con certeza un error de captura. Antes de corregirlo, siempre documenta la decisión.
6 Paso 2 — Limpieza: corregir cada problema
Ya sabemos qué tiene mal el dataset. Ahora lo corregimos. La lógica es simple: partimos de ventas_raw (la base original, que no tocamos) y construimos ventas_clean, que iremos actualizando en cada paso hasta tener un dataset confiable.
El orden importa: primero eliminamos filas problemáticas (duplicados, imposibles), luego corregimos columnas (texto, tipos, outliers), y al final ajustamos la estructura (columnas innecesarias, variables nuevas).
6.1 Eliminar filas duplicadas
El primer paso es eliminar los registros que aparecen más de una vez. Si los dejamos, cualquier suma o promedio quedará inflado, porque R contará la misma transacción dos veces.
La diferencia entre ambas dimensiones confirma cuántos duplicados se removieron. Si el resultado es 3, el proceso funcionó como esperábamos.
Qué hace distinct()
distinct(df) compara todas las columnas de cada fila. Si dos filas son idénticas en todo, elimina la segunda y conserva la primera.
La línea nrow(ventas_raw) - nrow(ventas_clean) nos dice cuántas filas duplicadas se removieron — es una forma rápida de confirmar que la operación hizo lo que esperábamos.
Si quisieras eliminar duplicados solo en algunas columnas (por ejemplo, mismo producto + trimestre + region), puedes especificarlas:
distinct(df, producto, trimestre, region, .keep_all = TRUE)6.2 Estandarizar categorías
El diagnóstico mostró que region, trimestre y producto tienen variantes por mayúsculas y minúsculas. Si no las corregimos, group_by() producirá más grupos de los reales y los totales no cuadrarán.
La solución es llevar todo al mismo formato. Aquí usamos toupper() para convertir todo a mayúsculas, lo que elimina cualquier inconsistencia de capitalización.
Después de toupper(), table() debe mostrar exactamente 3 regiones y 3 trimestres, cada uno con el conteo correcto.
Tres funciones para estandarizar texto
| Función | Resultado | Cuándo usarla |
|---|---|---|
toupper(x) |
"NORTE" |
Códigos, IDs, siglas — la usamos aquí |
tolower(x) |
"norte" |
Paso intermedio antes de capitalizar |
tools::toTitleCase(x) |
"Norte" |
Nombres propios, categorías |
La elección depende del estilo que quieras en la base final. Para códigos como Q1, Q2, Q3 y nombres de región, las mayúsculas son la opción más limpia.
6.3 Corregir el tipo de dato de precio
precio llega como texto (character) y tiene un signo $ en algunos valores. Eso impide hacer cualquier cálculo aritmético. Necesitamos primero eliminar el símbolo $ y luego convertir el texto a número.
Si class() devuelve "numeric", la conversión fue exitosa. Cualquier valor que no se pueda convertir (por ejemplo, si tuviera letras) quedaría como NA — lo cual también sería una señal importante.
Qué hace gsub("\\$", "", precio)
gsub(patron, reemplazo, texto) busca el patrón en el texto y lo reemplaza.
"\\$"→ el patrón es el símbolo$(se escribe\\$porque$tiene significado especial en expresiones regulares)""→ reemplaza por nada, es decir, elimina el símbolo- Resultado:
"$1200"→"1200"→as.numeric()→1200
6.4 Eliminar valores imposibles
Una cantidad negativa es un valor que no tiene sentido en este contexto de negocio: no se puede vender −5 unidades. Lo más seguro es eliminar esa fila, dejando una nota de la decisión.
El resultado de min() confirma que ya no hay cantidades negativas en el dataset.
¿Eliminar o imputar?
Cuando encuentras un valor imposible, tienes tres opciones:
- Eliminar la fila: si no tienes información para corregirla.
- Convertir a NA: si quieres conservar el resto de la fila para otros análisis.
- Corregir el valor: si tienes evidencia suficiente (una devolución documentada, por ejemplo, puede tener cantidad negativa de forma legítima).
La opción correcta depende del contexto de negocio, no de una regla estadística. Lo más importante es documentar la decisión.
6.5 Corregir el outlier de precio
El histograma del diagnóstico mostró un precio de ~12.000 para un Mouse. Ese valor es claramente un error de captura: un mouse no cuesta $12.000. Antes de corregir, conviene ver la fila completa para confirmar el error.
La fila confirma que es un Mouse. Decisión documentada: reemplazar 12.000 por 120, que es el precio típico de Mouse en el dataset.
El máximo de precio ahora debe ser consistente con el rango de Laptops (~1.200).
ifelse() para correcciones puntuales
ifelse(condicion, valor_si_TRUE, valor_si_FALSE) aplica una lógica fila por fila:
- Si la condición se cumple → usa el valor nuevo
- Si no → conserva el valor original
Siempre usa una condición específica (producto + umbral de precio) para no corregir por error otras filas.
6.6 Eliminar la columna basura y calcular ingreso
La columna observaciones tiene casi todo en NA y no aporta información analítica. La eliminamos para no cargar el dataset con ruido. Aprovechamos este último paso para calcular ingreso = precio × cantidad, que usaremos en el EDA.
El “acta de limpieza”
En un análisis profesional, cada decisión de limpieza debe quedar documentada. Al final del proceso deberías poder responder:
- ¿Cuántas filas eliminé y por qué?
- ¿Qué valores imputé o corregí y con qué criterio?
- ¿Qué columnas eliminé y por qué no aportaban?
Esto permite que otro analista (o tú mismo en tres meses) pueda reproducir exactamente el mismo dataset limpio.
7 Paso 3 — EDA: explorar el dataset limpio
Con los datos ya limpios, comenzamos el Análisis Exploratorio de Datos (EDA). El objetivo del EDA no es responder la pregunta de negocio final — es entender la estructura, distribución y relaciones antes de comprometerse con un análisis o modelo.
¿Qué busca el EDA?
- Distribuciones: ¿cómo se comportan las variables individualmente? ¿Simétricas, sesgadas, con múltiples picos?
- Resúmenes por grupo: ¿hay diferencias relevantes entre regiones, categorías, trimestres?
- Relaciones entre variables: ¿precio y cantidad tienen alguna relación?
- Valores anómalos residuales: ¿quedó algo raro que no detectamos antes?
El EDA combina tablas (dplyr) y gráficos (ggplot2). Ninguno es suficiente solo.
7.1 Resumen estadístico del dataset limpio
Ahora que los datos están limpios, skim() nos da un panorama confiable:
Compara con el skim() del dataset crudo
Vuelve a la sección 1.1 y compara las dos salidas. Deberías notar:
precioahora aparece comonumeric, no comocharactern_missingen precio y cantidad es el mismo (los NAs que venían del origen, no los que generamos)n_uniqueenregionytrimestreahora coincide con lo esperado (3 regiones, 3 trimestres)- El máximo de
cantidadya no es negativo
Esta comparación es parte del EDA: confirmar que el dataset limpio tiene la forma que esperábamos.
7.2 Distribuciones de variables numéricas
Distribución bimodal: una señal importante
Si el histograma muestra dos “jorobas” claramente separadas, es porque tenemos dos segmentos de productos muy distintos: productos baratos (Mouse, Licencia, Soporte) y productos caros (Laptop).
Esto ya es un hallazgo de negocio: no se puede calcular un “precio promedio representativo” sin separar los segmentos.
7.3 Comparar grupos: ingreso por región
¿Qué nos dice esta tabla?
- El ingreso total es la métrica de negocio más importante.
n_transaccionesconfirma cuántos registros sobrevivieron la limpieza.- Si una región tiene muchas transacciones pero bajo ingreso total, puede indicar que vende productos de bajo precio — no necesariamente que tenga bajo desempeño.
7.4 Evolución temporal: ¿qué está cambiando?
7.5 Relación entre variables: scatter plot exploratorio
Interpretación del scatter plot
Observa el gráfico y responde:
- ¿Los productos de Hardware tienden a tener precios más altos o más bajos que Software?
- ¿Hay una relación clara entre precio y cantidad? ¿Positiva, negativa o ninguna?
- ¿Ves algún punto que se aleje del patrón general?
El scatter plot no te da la respuesta — te da la pregunta correcta para profundizar.
8 Resumen del flujo completo
Conexión con el resto del curso
| Lo que hicimos hoy | Cómo lo usaremos después |
|---|---|
Dataset limpio (ventas_clean) |
Base para todos los modelos de Semanas 09-12 |
| EDA de distribuciones | En ML: verificar que los datos de train y test tengan distribuciones similares |
| Detección de outliers | En regresión (Semana 12): los outliers inflan el RMSE y sesgan los coeficientes |
| Categorías estandarizadas | En clasificación (Semana 11): las etiquetas deben ser consistentes o el modelo las trata como clases distintas |
| Documentar decisiones de limpieza | Exigencia del proyecto final: toda transformación debe estar justificada |
Semana 07 (Parcial): El parcial incluye preguntas sobre diagnóstico de calidad de datos e interpretación de EDA. Revisa las secciones de skim(), unique(), table() y los histogramas.
9 Checklist — ¿Qué debes poder explicar?
Antes de pasar a la siguiente semana, verifica que puedes:
Sobre fuentes y calidad:
Sobre diagnóstico en R:
Sobre limpieza:
Sobre EDA:
10 Preguntas de comprensión
Pregunta 1 — Diagnóstico
Recibes un dataset de 500 transacciones. Al correr skim() ves que la columna precio tiene tipo character y n_unique = 47. Luego corres unique(df$precio) y ves valores como "25.000", "1.200.000" y "$ 450".
¿Cuáles son los dos problemas de calidad que debes resolver antes de calcular el ingreso promedio? ¿Qué función de R usarías para cada uno?
Pregunta 2 — Consecuencia de no limpiar
Un analista recibe el dataset ventas_raw y calcula el ingreso total por región sin limpiar primero. Obtiene 5 grupos en vez de 3. Explica en dos oraciones:
- Por qué R genera 5 grupos
- Cómo afecta esto a la toma de decisiones si el gerente usa esa tabla para asignar presupuesto regional
Pregunta 3 — EDA como decisión
Después de limpiar los datos, corres skim(ventas_clean) y luego un histograma del precio. Observas una distribución bimodal (dos picos: uno en ~80 y otro en ~1.200).
¿Qué significa esa distribución bimodal en términos de negocio? ¿Cambiaría tu análisis si el gerente te pide “el precio promedio de ventas”? ¿Por qué?
10.1 Material adicional (opcional)
Si quieres profundizar en los temas de esta semana:
- R for Data Science — Cap. 18 (Missing values): cobertura detallada de estrategias para NAs → https://r4ds.hadley.nz/missing-values
- Tidy Data (Hadley Wickham): artículo original que define qué significa un dataset “ordenado” → https://vita.had.co.nz/papers/tidy-data.pdf
- dplyr cheat sheet: referencia rápida de todas las funciones de manipulación → https://rstudio.github.io/cheatsheets/data-transformation.pdf