Unidad 4 - Fuentes, Calidad de Datos y EDA

Introducción al Business Analytics · Semana 6 · 06278-ECO

Autor/a

PhD. Eduard F. Martínez-González

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() y table()
  • 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
Advertencia

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).
  • skimrnuevo: 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:

  1. Categorías inconsistentes: “Norte”, “norte”, “NORTE” en la misma columna
  2. Tipo de dato incorrecto: precio llega como texto (character) con signos de dólar (“$1200”)
  3. Valores faltantes: NA en precio y cantidad
  4. Valor imposible: una transacción con cantidad = -5
  5. Outlier de captura: un Mouse con precio = 12.000 (probablemente debería ser 120)
  6. Filas duplicadas: 3 transacciones aparecen dos veces
  7. 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:

  1. Primero, una visión general de todo el dataset con skim()
  2. 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:

Advertencia

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 exactas
  • df[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 argument

Para 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.

Advertencia

¿Eliminar o imputar?

Cuando encuentras un valor imposible, tienes tres opciones:

  1. Eliminar la fila: si no tienes información para corregirla.
  2. Convertir a NA: si quieres conservar el resto de la fila para otros análisis.
  3. 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?

  1. Distribuciones: ¿cómo se comportan las variables individualmente? ¿Simétricas, sesgadas, con múltiples picos?
  2. Resúmenes por grupo: ¿hay diferencias relevantes entre regiones, categorías, trimestres?
  3. Relaciones entre variables: ¿precio y cantidad tienen alguna relación?
  4. 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:

  • precio ahora aparece como numeric, no como character
  • n_missing en precio y cantidad es el mismo (los NAs que venían del origen, no los que generamos)
  • n_unique en region y trimestre ahora coincide con lo esperado (3 regiones, 3 trimestres)
  • El máximo de cantidad ya 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_transacciones confirma 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:

  1. ¿Los productos de Hardware tienden a tener precios más altos o más bajos que Software?
  2. ¿Hay una relación clara entre precio y cantidad? ¿Positiva, negativa o ninguna?
  3. ¿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:

  1. Por qué R genera 5 grupos
  2. 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: