Hasta este punto del análisis exploratorio ya hemos estudiado la localización, la variabilidad, la forma y la heterogeneidad de las variables principales del conjunto de datos Big Mart Sales. Falta ahora una dimensión especialmente útil cuando se trabaja con datos económicos, comerciales y administrativos: la concentración.
La idea central es simple. No basta con saber cuánto venden, en promedio, los productos o los outlets; también interesa conocer cómo se reparte el total de ventas entre categorías, tipos de tienda o establecimientos concretos. Dos escenarios pueden tener la misma venta total y la misma media, pero diferir radicalmente en su estructura interna: en uno las ventas pueden estar distribuidas de manera relativamente equilibrada, mientras que en otro una pequeña fracción de categorías o sucursales puede concentrar una porción muy grande del ingreso total.
Desde una perspectiva de EDA, las medidas y visualizaciones de concentración ayudan a responder preguntas como las siguientes:
¿Qué proporción del ingreso total proviene de unas cuantas categorías?
¿Los outlets contribuyen de forma equilibrada al volumen de ventas o existe dominancia de unos pocos?
¿Se observa una estructura tipo Pareto, donde una fracción pequeña de elementos explica gran parte del fenómeno?
En este capítulo se desarrollan medidas descriptivas y representaciones gráficas para estudiar la concentración de las ventas en Big Mart Sales, concluyendo con un mini-dashboard de concentración que después se integrará al dashboard global del libro.
Preparación del entorno
Trabajaremos con el siguiente dataset BigMart, para esta sección usaremos Python y las bibliotecas pandas, numpy, plotly y matplotlib.
import numpy as npimport pandas as pdimport plotly.express as pximport plotly.graph_objects as goDATA_PATH ="../../data/bigmart_sales.csv"df = pd.read_csv(DATA_PATH)df.head()
Item_Identifier
Item_Weight
Item_Fat_Content
Item_Visibility
Item_Type
Item_MRP
Outlet_Identifier
Outlet_Establishment_Year
Outlet_Size
Outlet_Location_Type
Outlet_Type
Item_Outlet_Sales
0
FDA15
9.30
Low Fat
0.016047
Dairy
249.8092
OUT049
1999
Medium
Tier 1
Supermarket Type1
3735.1380
1
DRC01
5.92
Regular
0.019278
Soft Drinks
48.2692
OUT018
2009
Medium
Tier 3
Supermarket Type2
443.4228
2
FDN15
17.50
Low Fat
0.016760
Meat
141.6180
OUT049
1999
Medium
Tier 1
Supermarket Type1
2097.2700
3
FDX07
19.20
Regular
0.000000
Fruits and Vegetables
182.0950
OUT010
1998
NaN
Tier 3
Grocery Store
732.3800
4
NCD19
8.93
Low Fat
0.000000
Household
53.8614
OUT013
1987
High
Tier 3
Supermarket Type1
994.7052
Como en capítulos anteriores, conviene revisar que las variables categóricas relevantes estén limpias y listas para agruparse.
# Estandarización mínima de una variable categórica clásica en Big Martif"Item_Fat_Content"in df.columns: df["Item_Fat_Content"] = ( df["Item_Fat_Content"] .replace({"LF": "Low Fat","low fat": "Low Fat","reg": "Regular" }) )# Vista rápida de columnas útiles para concentracióncandidate_cols = ["Item_Identifier","Item_Type","Outlet_Identifier","Outlet_Type","Outlet_Location_Type","Outlet_Size","Item_Outlet_Sales",]existing = [c for c in candidate_cols if c in df.columns]df[existing].head()
Item_Identifier
Item_Type
Outlet_Identifier
Outlet_Type
Outlet_Location_Type
Outlet_Size
Item_Outlet_Sales
0
FDA15
Dairy
OUT049
Supermarket Type1
Tier 1
Medium
3735.1380
1
DRC01
Soft Drinks
OUT018
Supermarket Type2
Tier 3
Medium
443.4228
2
FDN15
Meat
OUT049
Supermarket Type1
Tier 1
Medium
2097.2700
3
FDX07
Fruits and Vegetables
OUT010
Grocery Store
Tier 3
NaN
732.3800
4
NCD19
Household
OUT013
Supermarket Type1
Tier 3
High
994.7052
Fundamento conceptual
Participación relativa
Sea \(x_i\) el total de ventas asociado a la categoría o unidad \(i\). La participación relativa de esa unidad respecto del total se define por
\[
p_i = \frac{x_i}{\sum_{j=1}^{n} x_j}.
\]
Esta cantidad permite medir el peso de cada categoría en el total agregado. En términos porcentuales:
\[
p_i^{(\%)} = 100 \cdot p_i.
\]
Participación acumulada
Si ordenamos las unidades de mayor a menor según su contribución, la participación acumulada hasta la posición \(k\) está dada por
\[
P_k = \sum_{i=1}^{k} p_{(i)},
\]
donde \(p_{(i)}\) representa la participación de la \(i\)-ésima unidad en el orden descendente.
Esta medida es la base del análisis de Pareto, que permite estudiar cuántas categorías explican cierto porcentaje del total.
Curva de concentración
Otra idea útil consiste en comparar la acumulación observada con una situación ideal de reparto uniforme. Si unas pocas unidades concentran gran parte del total, la curva acumulada crecerá rápidamente al inicio y se separará de la diagonal de reparto uniforme.
Aunque en cursos introductorios puede evitarse una formalización excesiva, esta representación es la antesala conceptual de herramientas más avanzadas como la curva de Lorenz y el índice de Gini.
Concentración de ventas por categoría de producto
Comenzamos agregando la venta total por tipo de producto.
Una primera lectura descriptiva consiste en ordenar las categorías por ventas y observar su peso relativo.
fig = px.bar( sales_by_item_type, x="Item_Type", y="share_pct", title="Participación porcentual de ventas por tipo de producto", labels={"share_pct": "Participación (%)", "Item_Type": "Tipo de producto"})fig.update_layout(xaxis_tickangle=-45)fig.show()
Este gráfico responde a una pregunta básica: qué categorías pesan más en la estructura total de ventas. En un contexto de negocio, esto ayuda a distinguir líneas estratégicas, categorías troncales y segmentos con menor incidencia.
Gráfico de Pareto
El gráfico de Pareto combina barras de contribución individual con una curva acumulada. Es una de las formas más didácticas de introducir la idea de concentración.
pareto_df = sales_by_item_type.copy()fig = go.Figure()fig.add_bar( x=pareto_df["Item_Type"], y=pareto_df["share_pct"], name="Participación individual (%)")fig.add_scatter( x=pareto_df["Item_Type"], y=pareto_df["cum_share_pct"], mode="lines+markers", name="Participación acumulada (%)", yaxis="y2")fig.update_layout( title="Gráfico de Pareto de ventas por tipo de producto", xaxis=dict(title="Tipo de producto"), yaxis=dict(title="Participación individual (%)"), yaxis2=dict( title="Participación acumulada (%)", overlaying="y", side="right",range=[0, 105] ), legend=dict(orientation="h"),)fig.update_xaxes(tickangle=-45)fig.show()
Interpretación
Cuando la curva acumulada crece muy rápido al inicio, ello indica que unas pocas categorías concentran una gran parte de las ventas. Si, en cambio, la acumulación es más gradual, la estructura es relativamente más equilibrada.
Una buena práctica pedagógica consiste en cuantificar preguntas como:
¿Cuántas categorías explican el 50% de las ventas?
¿Cuántas categorías explican el 80%?
¿Qué fracción del total se concentra en las tres categorías principales?
def categories_to_reach_threshold(table, threshold=0.8): idx = np.argmax(table["cum_share"].to_numpy() >= threshold)return idx +1n50 = categories_to_reach_threshold(sales_by_item_type, 0.50)n80 = categories_to_reach_threshold(sales_by_item_type, 0.80)top3_share = sales_by_item_type["share"].head(3).sum() *100summary_concentration_item = pd.DataFrame({"Indicador": ["Categorías para alcanzar 50% de ventas","Categorías para alcanzar 80% de ventas","Participación acumulada de las 3 principales (%)" ],"Valor": [n50, n80, round(top3_share, 2)]})summary_concentration_item
Indicador
Valor
0
Categorías para alcanzar 50% de ventas
4.00
1
Categorías para alcanzar 80% de ventas
9.00
2
Participación acumulada de las 3 principales (%)
40.92
Concentración de ventas por outlet
La concentración también puede analizarse desde el punto de vista de las tiendas o outlets. Esta mirada es importante porque distingue entre un portafolio equilibrado de establecimientos y una estructura dominada por unas pocas unidades.
A diferencia del análisis por tipo de producto, aquí suele haber menos unidades agregadas, de modo que la concentración puede hacerse visible con mayor claridad. Si pocas tiendas explican una fracción grande de las ventas, ello puede tener implicaciones operativas, logísticas o estratégicas.
Concentración por tipo de outlet
Además de estudiar outlets individuales, conviene analizar el reparto de ventas según tipo de tienda.
fig = px.bar( sales_by_outlet_type, x="Outlet_Type", y="share_pct", title="Participación de ventas por tipo de outlet", labels={"share_pct": "Participación (%)", "Outlet_Type": "Tipo de outlet"})fig.update_layout(xaxis_tickangle=-20)fig.show()
Curva de concentración simplificada
Para construir una curva de concentración simple, ordenamos las unidades por contribución creciente o decreciente y comparamos la acumulación observada contra una referencia uniforme.
Aquí haremos una versión descriptiva, suficiente para fines pedagógicos.
def concentration_curve(values): values = np.asarray(values, dtype=float) values = np.sort(values) # ascendente cum_values = np.cumsum(values) cum_values = cum_values / cum_values[-1] cum_units = np.arange(1, len(values) +1) /len(values)return cum_units, cum_valuesx_units, y_conc = concentration_curve(sales_by_item_type["Item_Outlet_Sales"])curve_df = pd.DataFrame({"Fraccion_unidades": x_units,"Fraccion_ventas": y_conc})fig = go.Figure()fig.add_scatter( x=curve_df["Fraccion_unidades"], y=curve_df["Fraccion_ventas"], mode="lines", name="Curva de concentración")fig.add_scatter( x=[0, 1], y=[0, 1], mode="lines", name="Reparto uniforme")fig.update_layout( title="Curva de concentración de ventas por tipo de producto", xaxis_title="Fracción acumulada de categorías", yaxis_title="Fracción acumulada de ventas")fig.show()
Lectura de la curva
Cuanto más separada esté la curva respecto de la diagonal de reparto uniforme, mayor será la concentración. En un reparto perfectamente equilibrado, ambas coincidirían. En cambio, una curva muy hundida al inicio y muy empinada al final refleja que pocas categorías explican gran parte del volumen total.
Indicadores resumidos de concentración
A modo de síntesis, conviene construir una tabla con indicadores descriptivos que resuman la estructura observada.
top1_item = sales_by_item_type["share"].iloc[0] *100top5_item = sales_by_item_type["share"].head(5).sum() *100top1_outlet = sales_by_outlet["share"].iloc[0] *100top3_outlet = sales_by_outlet["share"].head(3).sum() *100concentration_summary = pd.DataFrame({"Ámbito": ["Productos","Productos","Productos","Outlets","Outlets" ],"Indicador": ["Participación de la categoría líder (%)","Participación acumulada del top 5 (%)","Categorías necesarias para 80% de ventas","Participación del outlet líder (%)","Participación acumulada de los 3 principales outlets (%)" ],"Valor": [round(top1_item, 2),round(top5_item, 2), n80,round(top1_outlet, 2),round(top3_outlet, 2) ]})concentration_summary
Ámbito
Indicador
Valor
0
Productos
Participación de la categoría líder (%)
15.17
1
Productos
Participación acumulada del top 5 (%)
58.93
2
Productos
Categorías necesarias para 80% de ventas
9.00
3
Outlets
Participación del outlet líder (%)
18.58
4
Outlets
Participación acumulada de los 3 principales o...
42.53
Mini-dashboard de concentración
El objetivo final del capítulo es reunir varias perspectivas en un panel compacto. En un book de Quarto, una estrategia práctica consiste en combinar tablas de KPIs con gráficos interactivos.
La concentración es un componente esencial del EDA porque complementa lo aprendido en capítulos previos. Mientras la localización resume el centro, la variabilidad describe la dispersión, la forma estudia la geometría de la distribución y la heterogeneidad compara subgrupos, la concentración revela cómo se reparte el total entre las unidades que componen el sistema.
En Big Mart Sales, esta dimensión permite identificar si el negocio depende fuertemente de unas pocas categorías o unos pocos outlets, o si el ingreso se distribuye de manera más equilibrada. Esta información es valiosa tanto para fines descriptivos como para una primera interpretación operativa.
Ejercicios sugeridos
Construye el gráfico de Pareto para Outlet_Location_Type y compara su concentración con la observada en Outlet_Type.
Calcula qué porcentaje de ventas acumula el 20% de las categorías con mayores ventas.
Repite el análisis utilizando el número de registros por categoría en lugar del total de ventas, y compara ambos resultados.
Describe, con lenguaje de negocio, si la estructura de ventas parece diversificada o concentrada.