¿Sobre qué vamos a trabajar?
Sector analizado
El estudio pertenece al sector de comercio electrónico (ventas de productos en línea). Este es uno de los sectores que más datos genera de forma continua y estructurada, lo que lo convierte en un terreno ideal para practicar análisis de datos.
¿Por qué este sector?
El e-commerce genera datos estructurados ideales para el análisis porque cada transacción queda registrada con precisión. Esto nos permite responder preguntas reales de negocio:
¿Qué categoría vende más? · ¿Cómo evolucionan los ingresos? · ¿Qué variables influyen en el rendimiento comercial?
¿Por qué es rico en datos?
Cada pedido registra automáticamente múltiples variables: el producto, su precio, el descuento aplicado, las unidades, la fecha y el ingreso generado. Esto da lugar a datasets grandes, limpios y multivariados — perfectos para aprender análisis.
Registros por pedido · Precios y descuentos · Volumen de ventas · Ingresos generados · Fechas para series temporales · Categorías para comparar
Conjunto de datos — Variables principales
El dataset contiene información de ventas de Amazon con 50,000 registros. Estas son las columnas con las que trabajaremos durante todo el análisis:
| Variable | Tipo | Significado |
|---|---|---|
order_id | int64 | Identificador único del pedido |
order_date | datetime64 | Fecha en que se realizó la compra |
product_id | int64 | Identificador único del producto |
product_category | object | Categoría del producto (Beauty, Books, Electronics, Fashion, Home & Kitchen, Sports) |
price | float64 | Precio original del producto |
discount_percent | int64 | Porcentaje de descuento aplicado |
quantity_sold | int64 | Unidades vendidas en ese pedido |
customer_region | object | Región geográfica del cliente |
payment_method | object | Método de pago utilizado (UPI, Credit Card, etc.) |
rating | float64 | Calificación del producto (1.0 – 5.0) |
review_count | int64 | Número de reseñas del producto (se eliminará en la limpieza) |
discounted_price | float64 | Precio final después de aplicar el descuento |
total_revenue | float64 | Ingreso total del pedido (precio descontado × unidades) |
¿Qué vamos a hacer hoy?
En este taller analizamos un conjunto de datos de ventas de Amazon con 50,000 registros. Seguiremos el ciclo completo de análisis de datos, que se puede resumir en este flujo de trabajo:
📥 Cargar los datos
Importar el CSV y convertirlo en un DataFrame de pandas.
🔍 Explorar los datos
Seleccionar columnas, filtrar registros, entender la estructura.
🧹 Limpiar los datos
Eliminar columnas innecesarias y filas con valores faltantes.
⚙️ Transformar los datos
Crear nuevas columnas derivadas y calcular métricas útiles.
📐 Analizar estadísticamente
Calcular medidas de tendencia central y de dispersión.
📈 Visualizar
Crear gráficas que comuniquen los hallazgos de forma clara.
Las tres librerías clave
La librería principal para manipulación de datos tabulares. Provee el objeto DataFrame, que funciona como una hoja de cálculo en memoria pero con el poder de Python. Permite cargar, filtrar, transformar y agrupar datos con facilidad.
La librería base de visualización en Python. Con ella creamos gráficos de barras, líneas, pastel e histogramas. Es muy personalizable aunque requiere más código que seaborn para lograr resultados refinados.
Construida sobre matplotlib, seaborn facilita la creación de gráficos estadísticos más elaborados con menos código. En este proyecto la usamos para crear el mapa de calor de correlaciones.
Cargando el dataset
El primer paso de cualquier análisis es cargar los datos y hacer una primera inspección para entender con qué estamos trabajando.
import pandas as pd import matplotlib.pyplot as plt import seaborn as sns df = pd.read_csv("amazon_sales_dataset.csv") df["order_date"] = pd.to_datetime(df["order_date"]) print(df.head()) # primeros 5 registros print(df.info()) # estructura y tipos de datos
¿Qué hace cada línea?
Lee el archivo CSV y lo convierte en un DataFrame: una tabla de filas y columnas que vive en memoria RAM. Es equivalente a abrir un Excel dentro de Python.
Los CSV guardan las fechas como texto ("2022-04-13"). Esta función las convierte a tipo datetime64, lo que nos permite hacer cálculos con fechas: comparar, agrupar por mes, etc.
head() muestra los primeros 5 registros para ver cómo lucen los datos. info() lista todas las columnas con sus tipos de datos y cuántos valores no nulos tiene cada una — esencial para detectar datos faltantes.
Resultado obtenido
RangeIndex: 50000 entries, 0 to 49999 Data columns (total 13 columns): order_id 50000 non-null int64 order_date 50000 non-null datetime64[ns] product_id 50000 non-null int64 product_category 50000 non-null object price 50000 non-null float64 discount_percent 50000 non-null int64 quantity_sold 50000 non-null int64 customer_region 50000 non-null object payment_method 50000 non-null object rating 50000 non-null float64 review_count 50000 non-null int64 discounted_price 50000 non-null float64 total_revenue 50000 non-null float64
El dataset tiene 50,000 filas y 13 columnas. Hay columnas numéricas (float64, int64) y de texto (object). Ninguna columna tiene valores nulos en esta etapa, lo que es un buen indicio de calidad del dataset.
Seleccionar solo lo que necesitamos
No siempre necesitamos trabajar con todas las columnas. Podemos crear una vista reducida del DataFrame eligiendo solo las columnas relevantes para una tarea específica.
selected_columns = df[['order_id', 'product_category', 'price']] print(selected_columns)
Cuando usamos df[['col1', 'col2']] (doble corchete), le pasamos una lista de nombres de columnas al DataFrame. El resultado es un nuevo DataFrame con solo esas columnas. No modifica el original.
order_id product_category price 0 1 Books 128.75 1 2 Fashion 302.60 2 3 Sports 495.80 ... 49999 50000 Home & Kitchen 253.44 [50000 rows x 3 columns]
Filtrar registros con condiciones
El filtrado nos permite quedarnos solo con las filas que cumplen una determinada condición. Es equivalente al filtro de Excel, pero expresado como código.
filtered_data = df[df['product_category'] == 'Books'] print(filtered_data)
La expresión df['product_category'] == 'Books' genera una serie de True/False para cada fila. Al pasarla como índice al DataFrame, obtenemos únicamente las filas donde la condición es True. Esto se llama indexación booleana y es el mecanismo de filtrado estándar en pandas.
[8327 rows x 13 columns]
→ De 50,000 registros, 8,327 son de categoría Books (≈ 16.6%)
Muestra del resultado filtrado
| order_id | order_date | product_id | product_category | ... | rating | review_count | discounted_price | total_revenue |
|---|---|---|---|---|---|---|---|---|
| 1 | 2022-04-13 | 2637 | Books | ... | 3.5 | 443 | 115.88 | 463.52 |
| 4 | 2022-04-17 | 2522 | Books | ... | 5.0 | 212 | 316.16 | 1264.64 |
| 7 | 2022-01-21 | 4068 | Books | ... | 1.6 | 415 | 15.78 | 78.90 |
| 9 | 2022-05-02 | 3262 | Books | ... | 2.8 | 497 | 373.62 | 1494.48 |
| 12 | 2022-11-27 | 3637 | Books | ... | 4.4 | 279 | 150.19 | 150.19 |
| … 8,317 registros intermedios … | ||||||||
| 49975 | 2022-12-30 | 1385 | Books | ... | 1.1 | 221 | 338.31 | 338.31 |
| 49978 | 2022-07-26 | 4991 | Books | ... | 1.3 | 273 | 239.82 | 1199.10 |
| 49980 | 2023-05-30 | 2063 | Books | ... | 4.4 | 268 | 337.87 | 1351.48 |
| 49982 | 2023-03-14 | 3712 | Books | ... | 3.6 | 407 | 106.62 | 106.62 |
| 49988 | 2023-01-30 | 4670 | Books | ... | 2.3 | 142 | 141.84 | 709.20 |
[8,327 rows × 13 columns]
Limpiar: eliminar columnas y filas problemáticas
La limpieza de datos es una de las etapas más críticas del análisis. Datos sucios producen resultados erróneos. Los dos pasos que realizamos son: eliminar columnas irrelevantes y eliminar filas con valores faltantes.
Eliminar columna
df = df.drop(columns=["review_count"])
Removemos review_count porque no la usaremos en nuestro análisis. El método drop() recibe la lista de columnas a eliminar y devuelve un nuevo DataFrame sin ellas. Reasignamos a df para guardar el cambio.
Eliminar filas nulas
df = df.dropna()
dropna() elimina toda fila que tenga al menos un valor faltante (NaN o nulo). En este dataset todos los registros eran completos, por lo que el conteo se mantiene en 50,000 filas tras la operación.
Los valores nulos pueden distorsionar promedios, sumas y correlaciones. Una columna irrelevante puede confundir el análisis y ocupar memoria innecesariamente. Limpiar siempre antes de analizar es una buena práctica profesional.
Crear nuevas columnas derivadas
A partir de las columnas existentes podemos calcular métricas nuevas que son más útiles para el análisis. Esto se llama feature engineering o ingeniería de variables.
# Ingreso por unidad = ingresos totales / unidades vendidas df["ingreso_por_unidad"] = df["total_revenue"] / df["quantity_sold"].replace(0, 1)
El ingreso total dividido entre las unidades vendidas nos da el precio efectivo promedio por unidad. Es más informativo que el precio de lista porque ya refleja los descuentos aplicados.
Si quantity_sold fuera 0 en algún registro, dividiríamos por cero y obtendríamos inf. Al reemplazar los ceros con 1 antes de dividir, evitamos ese error. Es una técnica defensiva de programación.
total_revenue quantity_sold ingreso_por_unidad 0 463.52 4 115.88 1 1210.40 5 242.08 2 793.28 2 396.64 3 1264.64 4 316.16 4 806.72 4 201.68
Ordenar los datos
df_ordenado = df.sort_values( by=["product_category", "ingreso_por_unidad"], ascending=[True, False] )
Ordenamos por categoría (ascendente A→Z) y dentro de cada categoría por ingreso por unidad (descendente, de mayor a menor). Esto nos permite ver fácilmente los productos más rentables de cada categoría.
groupby() — El corazón del análisis agregado
Una de las operaciones más poderosas de pandas es groupby(), que nos permite calcular métricas resumidas por grupos. Es equivalente a una tabla dinámica (pivot table) de Excel.
resumen = df.groupby("product_category").agg( ingreso_total=("total_revenue", "sum"), cantidad_total=("quantity_sold", "sum"), ingreso_promedio_unidad=("ingreso_por_unidad", "mean"), numero_ordenes=("order_id", "count") )
groupby() divide el DataFrame en grupos (Split), aplica una función a cada grupo (Apply), y combina los resultados en un nuevo DataFrame (Combine). Le estamos diciendo: "para cada categoría de producto, calcula la suma del ingreso, la suma de unidades, el promedio del ingreso por unidad y el conteo de órdenes".
El método .agg() — Agregaciones múltiples en una sola instrucción
.agg() es la abreviatura de aggregate (agregar/resumir). Su función es aplicar una o varias operaciones de resumen a una o varias columnas del DataFrame simultáneamente. Sin .agg(), tendríamos que hacer cada cálculo en una línea separada.
# Sintaxis general: df.agg( nombre_resultado = ("columna_origen", "función"), ... ) # En nuestro taller: df.groupby("product_category").agg( ingreso_total = ("total_revenue", "sum" ), # suma cantidad_total = ("quantity_sold", "sum" ), # suma ingreso_promedio_unidad = ("ingreso_por_unidad", "mean" ), # promedio numero_ordenes = ("order_id", "count") # conteo )
| Parte de .agg() | ¿Qué es? | Ejemplo |
|---|---|---|
| nombre_resultado | El nombre que tendrá la columna en el DataFrame de salida. Lo elegimos nosotros libremente. | ingreso_total |
| "columna_origen" | La columna del DataFrame original sobre la que se aplica la operación. | "total_revenue" |
| "función" | La operación matemática a aplicar: "sum", "mean", "count", "min", "max", "std", "median", etc. |
"sum" |
Sin .agg() necesitaríamos 4 líneas separadas y luego unir los resultados manualmente. Con .agg() obtenemos un DataFrame completo y bien estructurado en una sola instrucción, con los nombres de columna que nosotros queramos. Es más legible, más eficiente y produce resultados listos para analizar o exportar.
En el Bloque 7 (estadística descriptiva) usamos .agg(["mean", "median"]) directamente sobre el DataFrame, sin agrupar. En ese caso, aplica las funciones a todas las filas de las columnas seleccionadas, calculando la media y mediana globales del dataset completo.
Resultados obtenidos
| Categoría | Ingreso Total | Cantidad Total | Ing. Prom./Unidad | N° Órdenes |
|---|---|---|---|---|
| Beauty | $5,550,624.97 | 25,422 | $218.68 | 8,465 |
| Books | $5,484,863.03 | 25,065 | $218.74 | 8,327 |
| Electronics | $5,470,594.03 | 24,898 | $218.84 | 8,320 |
| Fashion | $5,480,123.34 | 25,089 | $218.64 | 8,365 |
| Home & Kitchen | $5,473,132.55 | 24,743 | $220.47 | 8,258 |
| Sports | $5,407,235.82 | 24,753 | $217.96 | 8,265 |
Los ingresos por categoría son sorprendentemente parejos: todos oscilan entre $5.4M y $5.5M. Esto sugiere que o bien el dataset fue generado sintéticamente, o que Amazon ha logrado un equilibrio comercial notable entre categorías. El ingreso promedio por unidad también es casi idéntico en todas las categorías (~$218–$220).
Medidas de tendencia central y dispersión
La estadística descriptiva nos permite resumir y caracterizar los datos con números clave. Se divide en dos grandes familias de métricas:
Tendencia central
estadisticas = df[[
"ingreso_por_unidad",
"total_revenue",
"quantity_sold"
]].agg(["mean", "median"])
ing/unidad revenue qty mean 218.89 657.33 2.99 median 215.81 505.41 3.00
Dispersión
dispersion = df[[
"ingreso_por_unidad",
"total_revenue",
"quantity_sold"
]].agg(["var", "std"])
ing/unidad revenue qty var 16209.79 276911.6 2.00 std 127.32 526.22 1.42
| Métrica | ¿Qué mide? | ¿Cuándo usarla? |
|---|---|---|
| Media (mean) | El promedio aritmético | Cuando los datos no tienen valores extremos |
| Mediana (median) | El valor del centro cuando ordenamos los datos | Cuando hay valores extremos (outliers) |
| Moda (mode) | El valor más frecuente | En variables categóricas |
| Varianza (var) | Dispersión promedio al cuadrado | Para cálculos matemáticos |
| Desviación estándar (std) | Dispersión en las mismas unidades que los datos | Para interpretar qué tan "esparcidos" están los datos |
La media del ingreso total ($657) es mayor que la mediana ($505). Esto indica una distribución sesgada a la derecha: hay órdenes de alto valor que "jalan" la media hacia arriba. La mediana es un mejor representante del ingreso "típico". La moda de categoría es Beauty, la más frecuente.
Las 5 gráficas del análisis
Las visualizaciones transforman números en historias. Cada tipo de gráfica tiene un propósito específico. Analizamos cada una en detalle.
Gráfica 1 — Barras: Ingreso total por categoría
ingresos_categoria = df.groupby("product_category")["total_revenue"].sum() plt.figure() ax = ingresos_categoria.plot(kind="bar") plt.title("Ingreso total por categoria") plt.xlabel("Categoria") plt.ylabel("Ingreso total") plt.xticks(rotation=45) for i, valor in enumerate(ingresos_categoria): ax.text(i, valor, f"{valor/1_000_000:.2f}M", ha="center", va="bottom")
Figura 1. Ingreso total acumulado por categoría de producto.
Es ideal para comparar valores entre categorías discretas. Cada barra representa una categoría y su altura es proporcional al valor que queremos comparar. El loop for agrega las etiquetas de valor en millones encima de cada barra, facilitando la lectura exacta.
Las barras confirman visualmente la uniformidad del dataset: todas las categorías alcanzan entre 5.41M y 5.55M de ingreso total, con barras de altura casi idéntica. Beauty lidera con $5.55M y Sports queda último con $5.41M — una brecha de apenas $143K (2.6%). Las etiquetas de valor en millones sobre cada barra facilitan la comparación exacta sin necesidad de leer el eje Y. La escala del eje Y arranca cerca de los 5M, lo que exagera visualmente las diferencias: una señal de precaución para interpretar gráficas de barras donde el eje no parte en cero.
Gráfica 2 — Líneas: Ingresos a lo largo del tiempo
df["anio_mes"] = df["order_date"].dt.to_period("M") ingresos_fecha = df.groupby("anio_mes")["total_revenue"].sum() ingresos_fecha.plot(kind="line") plt.title("Ingresos a lo largo del tiempo")
Figura 2. Evolución mensual del ingreso total a lo largo del tiempo.
El acceso .dt.to_period("M") convierte una fecha como 2022-04-13 al período mensual 2022-04. Esto nos permite sumar todos los ingresos de un mismo mes en una sola fila, creando la serie temporal que luego graficamos.
Las líneas son perfectas para mostrar tendencias a lo largo del tiempo (series temporales). El eje X representa el tiempo (meses) y el eje Y los ingresos. La línea conecta los puntos y nos permite ver si los ingresos suben, bajan o fluctúan con el tiempo.
La gráfica muestra los ingresos mensuales de enero 2022 a diciembre 2023. Se observan fluctuaciones irregulares mes a mes, sin una tendencia clara de crecimiento o caída sostenida. Los ingresos oscilan entre ~$1.24M (mínimo en febrero 2023) y ~$1.46M (máximo en enero 2023 y julio 2022). Esta variabilidad sin patrón estacional claro refuerza la hipótesis de datos sintéticos generados aleatoriamente. En datos reales esperaríamos picos en temporadas como Black Friday o Navidad.
Gráfica 3 — Pastel: Distribución de ingresos por categoría
ingresos_categoria.plot(kind="pie", autopct="%1.1f%%") plt.title("Distribucion de ingresos por categoria")
Figura 3. Proporción del ingreso total de cada categoría.
El parámetro autopct agrega el porcentaje dentro de cada porción del pastel. El formato %1.1f%% significa: mostrar el número con 1 decimal y agregar el símbolo %.
El pastel muestra 6 porciones de tamaño casi idéntico: Beauty 16.9%, Books 16.7%, Fashion 16.7%, Home & Kitchen 16.7%, Electronics 16.6% y Sports 16.5%. La diferencia entre la porción mayor y menor es de apenas 0.4 puntos porcentuales. En ventas reales, la ley de Pareto dictaría que el 20% de las categorías genera el 80% de los ingresos — aquí esa regla no aplica, lo que confirma el origen sintético del dataset. Sin embargo, el ejercicio es valioso porque demuestra cómo una gráfica de pastel puede revelar de un vistazo la distribución proporcional entre categorías.
Gráfica 4 — Histograma: Distribución del ingreso por unidad
ax = df["ingreso_por_unidad"].plot(kind="hist", bins=30) media = df["ingreso_por_unidad"].mean() mediana = df["ingreso_por_unidad"].median() plt.axvline(media, linestyle="--", label=f"Media: {media:.2f}") plt.axvline(mediana, linestyle="-", label=f"Mediana: {mediana:.2f}")
Figura 4. Distribución de frecuencias del ingreso por unidad con líneas de media y mediana.
Un histograma divide el rango de valores en intervalos (bins) y muestra cuántos datos caen en cada uno. No es un gráfico de barras: aquí el eje X es continuo y no hay espacios entre las barras. Los 30 bins dividen el rango de ingresos en 30 intervalos iguales.
plt.axvline() dibuja una línea vertical en el valor especificado. Al agregar las líneas de media y mediana, podemos ver visualmente si la distribución es simétrica o sesgada, y qué tan cerca están ambas medidas.
El histograma muestra que la distribución del ingreso por unidad es aproximadamente uniforme entre $0 y $350, con frecuencias de ~1,800–2,050 registros por bin en ese rango. A partir de $350 la frecuencia cae progresivamente hasta llegar a ~250 registros en el bin de $500. Esto genera un sesgo negativo a la derecha (cola derecha): los ingresos muy altos son menos frecuentes. Las líneas de media ($218.89, línea punteada) y mediana ($215.81, línea sólida) aparecen muy juntas y en el centro de la distribución, confirmando que ambas son representativas del ingreso "típico". La etiqueta "Total datos: 50000" valida que ningún dato fue excluido.
Gráfica 5 — Mapa de calor: Correlaciones
correlacion = df[[
"price", "discount_percent", "quantity_sold",
"total_revenue", "ingreso_por_unidad"
]].corr()
sns.heatmap(correlacion, annot=True)
plt.title("Mapa de calor de correlacion")
Figura 5. Matriz de correlación de Pearson entre las variables numéricas del dataset.
La correlación mide la relación lineal entre dos variables. Su valor va de -1 (perfectamente opuestas) a +1 (perfectamente relacionadas). Un valor de 0 indica que no hay relación lineal. El mapa de calor (heatmap) colorea cada celda según su valor: colores cálidos = correlación positiva, fríos = negativa.
La diagonal principal siempre vale 1.0 (cada variable se correlaciona perfectamente consigo misma). Lo interesante es observar si price y ingreso_por_unidad están altamente correlacionados (esperamos que sí, dado que el ingreso por unidad depende del precio), y si el discount_percent afecta negativamente al ingreso por unidad.
El mapa de calor revela cuatro hallazgos importantes: (1) price ↔ ingreso_por_unidad: 0.97 — correlación casi perfecta, lógica porque el ingreso por unidad se calcula a partir del precio. (2) price ↔ total_revenue: 0.71 — correlación fuerte: a mayor precio, mayor ingreso total. (3) quantity_sold ↔ total_revenue: 0.59 — correlación moderada: más unidades vendidas generan más ingresos. (4) discount_percent ↔ ingreso_por_unidad: -0.20 — correlación negativa débil: mayores descuentos reducen ligeramente el ingreso por unidad, como es de esperar. Las correlaciones cercanas a 0 (como discount_percent ↔ price: -0.0047) indican independencia estadística — los descuentos se aplican independientemente del precio del producto.
Conclusiones del análisis
Tras completar el ciclo completo de análisis, estos son los hallazgos y lecciones principales del taller:
💰 Ingresos balanceados
Las 6 categorías de producto generan ingresos casi idénticos (~$5.4–5.5M cada una). No existe una categoría dominante: Beauty lidera y Sports queda último, pero con una brecha mínima del 2.6%.
📦 Volumen uniforme
El número de órdenes por categoría es muy similar (entre 8,258 y 8,465), y el ingreso promedio por unidad oscila alrededor de $218–$220 en todas las categorías sin variación significativa.
📅 Serie temporal
Los datos cubren los años 2022–2023. La gráfica de líneas permite identificar meses de mayor y menor actividad comercial, útil para planear inventario y campañas de marketing.
📐 Distribución sesgada
La diferencia entre media ($657) y mediana ($505) en el ingreso total revela que hay órdenes de alto valor que elevan el promedio. La mediana es el mejor indicador del ingreso "típico".
🧹 Limpieza exitosa
El dataset no presentó valores nulos, lo que facilitó el proceso. La eliminación de review_count simplificó el análisis sin perder información relevante para los objetivos del taller.
🔧 Herramientas utilizadas
Este análisis completo fue posible con solo tres librerías: pandas para manipulación, matplotlib para visualización base, y seaborn para el heatmap estadístico. Una combinación estándar en ciencia de datos profesional.
Habilidades adquiridas en este taller
Al completar este taller, ahora sabes cómo:
✅ Cargar y explorar un CSV con pandas
✅ Convertir tipos de datos (fechas)
✅ Seleccionar, filtrar y ordenar datos
✅ Eliminar columnas y filas problemáticas
✅ Crear columnas derivadas (feature engineering)
✅ Agrupar datos con groupby() y agg()
✅ Calcular estadísticas descriptivas
✅ Crear 5 tipos de visualizaciones