Código
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
El presente análisis exploratorio de datos (EDA) tiene como objetivo comprender la estructura, la calidad y las principales características de los datos históricos de ventas de Walmart. Se busca describir cómo están organizados los datos, su naturaleza jerárquica, e identificar patrones, valores atípicos y posibles inconsistencias que puedan afectar su análisis.
El conjunto de datos está compuesto por 42,840 series temporales jerárquicas, correspondientes a registros de ventas a lo largo del tiempo organizados en distintos niveles. Los datos provienen de tres estados de Estados Unidos: California (CA), Texas (TX) y Wisconsin (WI). La naturaleza jerárquica de los datos permite su agregación a diferentes niveles, como producto, departamento, categoría o estado. El período cubierto por las ventas abarca desde enero de 2011 hasta abril de 2016, e incluye también información sobre precios, promociones y días festivos. Cabe señalar que un alto porcentaje de las series presenta periodos con valores de ventas iguales a cero.
En total, el conjunto de datos incluye 3,049 productos individuales, distribuidos en 3 categorías, 7 departamentos y ubicadas en los tres estados mencionados.
Los datos se presentan en tres archivos separados:
sales_train.csv
: Son los datos principales. Contienen una columna para cada uno de los 1913 días desde el 29/01/2011 hasta el 25/04/2016. También incluye los ID de artículo, departamento, categoría, tienda y estado.
calendar.csv
: Contiene las fechas en las que se venden los productos junto con características relacionadas como día de la semana, mes, año y 3 indicadores binarios que indican si las tiendas en cada estado permitían compras con cupones de alimentos SNAP en esta fecha (1) o no (0).
sell_prices.csv
: Contiene información sobre los productos vendidos (ID de tienda, artículo, fecha y precio de venta).
Carga de módulos para la manipulación de datos y visualización interactiva.
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
Carga de los datos utilizando pandas
. Los archivos originales en formato .csv
han sido previamente convertidos a .parquet
para optimizar el uso de memoria y espacio en disco, mejorando la eficiencia en el manejo de grandes volúmenes de datos.
= pd.read_parquet('data/calendar.parquet')
calendar = pd.read_parquet('data/sales_train.parquet')
train = pd.read_parquet('data/sell_prices.parquet')
prices = pd.read_parquet('data/sample_submission.parquet') sample_submit
Como primer paso, es recomendable echar un vistazo rápido a los conjuntos de datos.
Aquí están las primeras 10 filas de los datos de ventas:
10) train.head(
id | item_id | dept_id | cat_id | store_id | state_id | d_1 | d_2 | d_3 | d_4 | ... | d_1904 | d_1905 | d_1906 | d_1907 | d_1908 | d_1909 | d_1910 | d_1911 | d_1912 | d_1913 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | HOBBIES_1_001_CA_1_validation | HOBBIES_1_001 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 1 | 3 | 0 | 1 | 1 | 1 | 3 | 0 | 1 | 1 |
1 | HOBBIES_1_002_CA_1_validation | HOBBIES_1_002 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
2 | HOBBIES_1_003_CA_1_validation | HOBBIES_1_003 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 2 | 1 | 2 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
3 | HOBBIES_1_004_CA_1_validation | HOBBIES_1_004 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 1 | 0 | 5 | 4 | 1 | 0 | 1 | 3 | 7 | 2 |
4 | HOBBIES_1_005_CA_1_validation | HOBBIES_1_005 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 2 | 1 | 1 | 0 | 1 | 1 | 2 | 2 | 2 | 4 |
5 | HOBBIES_1_006_CA_1_validation | HOBBIES_1_006 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 2 | 0 | 0 |
6 | HOBBIES_1_007_CA_1_validation | HOBBIES_1_007 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
7 | HOBBIES_1_008_CA_1_validation | HOBBIES_1_008 | HOBBIES_1 | HOBBIES | CA_1 | CA | 12 | 15 | 0 | 0 | ... | 0 | 0 | 1 | 37 | 3 | 4 | 6 | 3 | 2 | 1 |
8 | HOBBIES_1_009_CA_1_validation | HOBBIES_1_009 | HOBBIES_1 | HOBBIES | CA_1 | CA | 2 | 0 | 7 | 3 | ... | 0 | 0 | 1 | 1 | 6 | 0 | 0 | 0 | 0 | 0 |
9 | HOBBIES_1_010_CA_1_validation | HOBBIES_1_010 | HOBBIES_1 | HOBBIES | CA_1 | CA | 0 | 0 | 1 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 2 |
10 rows × 1919 columns
Se infiere que:
Hay una columna para cada ID de artículo, departamento, categoría, tienda y estado; además de un ID general que combina los demás ID y una marca de validación.
Las ventas por fecha se codifican como columnas que comienzan con el prefijo d_. Estas indican el número de unidades vendidas por día (no el total de dólares).
Hay bastantes valores cero.
Este conjunto de datos tiene demasiadas columnas y filas para mostrarlas todas:
train.shape
(30490, 1919)
Este conjunto de datos da los cambios de precio semanales por artículo:
10) prices.head(
store_id | item_id | wm_yr_wk | sell_price | |
---|---|---|---|---|
0 | CA_1 | HOBBIES_1_001 | 11325 | 9.58 |
1 | CA_1 | HOBBIES_1_001 | 11326 | 9.58 |
2 | CA_1 | HOBBIES_1_001 | 11327 | 8.26 |
3 | CA_1 | HOBBIES_1_001 | 11328 | 8.26 |
4 | CA_1 | HOBBIES_1_001 | 11329 | 8.26 |
5 | CA_1 | HOBBIES_1_001 | 11330 | 8.26 |
6 | CA_1 | HOBBIES_1_001 | 11331 | 8.26 |
7 | CA_1 | HOBBIES_1_001 | 11332 | 8.26 |
8 | CA_1 | HOBBIES_1_001 | 11333 | 8.26 |
9 | CA_1 | HOBBIES_1_001 | 11334 | 8.26 |
# desactivar notación científica
= '{:.2f}'.format
pd.options.display.float_format
='all') prices.describe(include
store_id | item_id | wm_yr_wk | sell_price | |
---|---|---|---|---|
count | 6841121 | 6841121 | 6841121.00 | 6841121.00 |
unique | 10 | 3049 | NaN | NaN |
top | TX_2 | HOUSEHOLD_2_142 | NaN | NaN |
freq | 701214 | 2820 | NaN | NaN |
mean | NaN | NaN | 11382.94 | 4.41 |
std | NaN | NaN | 148.61 | 3.41 |
min | NaN | NaN | 11101.00 | 0.01 |
25% | NaN | NaN | 11247.00 | 2.18 |
50% | NaN | NaN | 11411.00 | 3.47 |
75% | NaN | NaN | 11517.00 | 5.84 |
max | NaN | NaN | 11621.00 | 107.32 |
Resultados:
Los datos del calendario brindan características de fecha, como día de la semana, mes o año; junto con 2 características de eventos diferentes y una columna de cupones de alimentos SNAP:
10) calendar.head(
date | wm_yr_wk | weekday | wday | month | year | d | event_name_1 | event_type_1 | event_name_2 | event_type_2 | snap_CA | snap_TX | snap_WI | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2011-01-29 | 11101 | Saturday | 1 | 1 | 2011 | d_1 | None | None | None | None | 0 | 0 | 0 |
1 | 2011-01-30 | 11101 | Sunday | 2 | 1 | 2011 | d_2 | None | None | None | None | 0 | 0 | 0 |
2 | 2011-01-31 | 11101 | Monday | 3 | 1 | 2011 | d_3 | None | None | None | None | 0 | 0 | 0 |
3 | 2011-02-01 | 11101 | Tuesday | 4 | 2 | 2011 | d_4 | None | None | None | None | 1 | 1 | 0 |
4 | 2011-02-02 | 11101 | Wednesday | 5 | 2 | 2011 | d_5 | None | None | None | None | 1 | 0 | 1 |
5 | 2011-02-03 | 11101 | Thursday | 6 | 2 | 2011 | d_6 | None | None | None | None | 1 | 1 | 1 |
6 | 2011-02-04 | 11101 | Friday | 7 | 2 | 2011 | d_7 | None | None | None | None | 1 | 0 | 0 |
7 | 2011-02-05 | 11102 | Saturday | 1 | 2 | 2011 | d_8 | None | None | None | None | 1 | 1 | 1 |
8 | 2011-02-06 | 11102 | Sunday | 2 | 2 | 2011 | d_9 | SuperBowl | Sporting | None | None | 1 | 1 | 1 |
9 | 2011-02-07 | 11102 | Monday | 3 | 2 | 2011 | d_10 | None | None | None | None | 1 | 1 | 0 |
calendar.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1969 entries, 0 to 1968
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 1969 non-null object
1 wm_yr_wk 1969 non-null int64
2 weekday 1969 non-null object
3 wday 1969 non-null int64
4 month 1969 non-null int64
5 year 1969 non-null int64
6 d 1969 non-null object
7 event_name_1 162 non-null object
8 event_type_1 162 non-null object
9 event_name_2 5 non-null object
10 event_type_2 5 non-null object
11 snap_CA 1969 non-null int64
12 snap_TX 1969 non-null int64
13 snap_WI 1969 non-null int64
dtypes: int64(7), object(7)
memory usage: 215.5+ KB
='all') calendar.describe(include
date | wm_yr_wk | weekday | wday | month | year | d | event_name_1 | event_type_1 | event_name_2 | event_type_2 | snap_CA | snap_TX | snap_WI | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1969 | 1969.00 | 1969 | 1969.00 | 1969.00 | 1969.00 | 1969 | 162 | 162 | 5 | 5 | 1969.00 | 1969.00 | 1969.00 |
unique | 1969 | NaN | 7 | NaN | NaN | NaN | 1969 | 30 | 4 | 4 | 2 | NaN | NaN | NaN |
top | 2011-01-29 | NaN | Saturday | NaN | NaN | NaN | d_1 | SuperBowl | Religious | Father's day | Cultural | NaN | NaN | NaN |
freq | 1 | NaN | 282 | NaN | NaN | NaN | 1 | 6 | 55 | 2 | 4 | NaN | NaN | NaN |
mean | NaN | 11347.09 | NaN | 4.00 | 6.33 | 2013.29 | NaN | NaN | NaN | NaN | NaN | 0.33 | 0.33 | 0.33 |
std | NaN | 155.28 | NaN | 2.00 | 3.42 | 1.58 | NaN | NaN | NaN | NaN | NaN | 0.47 | 0.47 | 0.47 |
min | NaN | 11101.00 | NaN | 1.00 | 1.00 | 2011.00 | NaN | NaN | NaN | NaN | NaN | 0.00 | 0.00 | 0.00 |
25% | NaN | 11219.00 | NaN | 2.00 | 3.00 | 2012.00 | NaN | NaN | NaN | NaN | NaN | 0.00 | 0.00 | 0.00 |
50% | NaN | 11337.00 | NaN | 4.00 | 6.00 | 2013.00 | NaN | NaN | NaN | NaN | NaN | 0.00 | 0.00 | 0.00 |
75% | NaN | 11502.00 | NaN | 6.00 | 9.00 | 2015.00 | NaN | NaN | NaN | NaN | NaN | 1.00 | 1.00 | 1.00 |
max | NaN | 11621.00 | NaN | 7.00 | 12.00 | 2016.00 | NaN | NaN | NaN | NaN | NaN | 1.00 | 1.00 | 1.00 |
Se obtiene que:
sum() train.isna().
id 0
item_id 0
dept_id 0
cat_id 0
store_id 0
..
d_1909 0
d_1910 0
d_1911 0
d_1912 0
d_1913 0
Length: 1919, dtype: int64
from matplotlib.ticker import PercentFormatter
= train.loc[:, ~train.columns.str.contains('id')]
df = df.replace(0, np.nan)
df = df.isna().copy()
df_na 'sum'] = df_na.sum(axis=1)
df_na['mean'] = df_na['sum'] / df_na.shape[1]
df_na[= df_na[['sum', 'mean']]
bar
=(8, 6))
plt.figure(figsize'mean'], fill=True, color='blue')
sns.kdeplot(bar[1)) # eje x como %
plt.gca().xaxis.set_major_formatter(PercentFormatter(0, 1)
plt.xlim(
plt.gca().set_yticklabels([])"Densidad del porcentaje de valores cero - todas las series temporales")
plt.title("")
plt.xlabel("")
plt.ylabel(
plt.tight_layout() plt.show()
Esto significa que solo una minoría de las series temporales tienen menos del 50% de valores cero. El pico está bastante cerca del 100%.
Se realizará una exploración visual para analizar varios gráficos de series temporales en diferentes niveles de agregación empleando funciones auxiliares: cols_d
identifica las columnas cuyo nombre comienza con “d_”, extract_ts
transforma el dataframe ancho en formato largo asignando a cada valor de ventas su fecha real a partir de MIN_DATE
y normaliza los identificadores eliminando sufijos, agg_wide
agrupa y suma las columnas de días según las dimensiones indicadas renombrando la última agrupación como id
, y set_monthly
utiliza estas transformaciones para agregar ventas por año y mes, conservar solo el primer día de cada periodo y descartar el último mes incompleto, dejando así listo el dataset para crear gráficos temporales interactivos.
# constante para fecha mínima
= datetime(2011, 1, 29)
MIN_DATE
def cols_d(df):
"""Devuelve las columnas cuya etiqueta empieza con 'd_'"""
return [c for c in df.columns if c.startswith('d_')]
def extract_ts(df):
"""
Convierte un dataframe ancho (columnas d_1, d_2, …) en formato largo con columnas:
- id: identificador de la serie (sin sufijo "_validation")
- dates: fecha real
- sales: valor de ventas
"""
= df.copy()
df # conservar id y columnas de días
= df[['id'] + cols_d(df)]
df # pivot largo
= df.melt(id_vars=['id'], var_name='day', value_name='sales')
ts # convertir índice de día en entero
'day'] = ts['day'].str.removeprefix('d_').astype(int)
ts[# calcular fecha real
'dates'] = ts['day'].apply(lambda x: MIN_DATE + timedelta(days=x - 1))
ts[='day', inplace=True)
ts.drop(columns# limpiar sufijo
'id'] = ts['id'].astype(str).str.replace('_validation', '')
ts[return ts
def agg_wide(df, group_cols):
"""
Agrega un dataframe ancho sumando las columnas d_* según group_cols,
renombrando la última columna de agrupación como 'id'.
"""
= (
agg
df
.groupby(group_cols)[cols_d(df)]sum()
.
.reset_index()
)= agg.rename(columns={group_cols[-1]: 'id'})
agg return agg
def set_monthly(df):
"""
Toma un dataframe ancho con columna 'id', transforma a largo,
agrega por año y mes, filtra sólo primer día y elimina mes incompleto.
"""
= extract_ts(df)
ts 'month'] = ts['dates'].dt.month
ts['year'] = ts['dates'].dt.year
ts[= (
monthly
ts'year', 'month', 'id'], as_index=False)
.groupby([
.agg(=('sales', 'sum'),
sales=('dates', 'min')
dates
)
)= monthly[monthly['dates'].dt.day == 1]
monthly = monthly['dates'].max()
last = monthly[monthly['dates'] != last]
monthly return monthly
En primer lugar, se presenta la serie temporal agregada de todos los artículos, tiendas, categorías, departamentos y ventas.
# ventas totales agregadas
= train.sum().to_frame('sales').T
agg 'id'] = 'total'
agg[= extract_ts(agg)
ts_agg
= px.line(
fig
ts_agg,='dates',
x='sales',
y='Ventas agregadas',
title={'dates': 'Fecha', 'sales': 'Ventas'}
labels
)='plotly_white')
fig.update_layout(template fig.show()
Se observa lo siguiente:
En general, las ventas van en aumento, lo que parece positivo para Walmart. Se nota un patrón anual claro, con una caída en Navidad, el único día en que las tiendas permanecen cerradas.
Las ventas más recientes de 2016 muestran un crecimiento algo más rápido que en los años anteriores.
Ahora, se analizarán las ventas por estado a nivel de agregación mensual.
# ventas mensuales por estado
= agg_wide(train, ['state_id'])
stt = set_monthly(stt)
ts_stt
= px.line(
fig
ts_stt,='dates',
x='sales',
y='id',
color='Ventas mensuales por estado',
title={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Estado'}
labels
)='plotly_white')
fig.update_layout(template fig.show()
Se observa lo siguiente:
California (CA) registra la mayor cantidad de artículos vendidos en general, mientras que Wisconsin (WI) fue acercándose gradualmente a Texas (TX) hasta superarlo en los últimos meses de los datos de entrenamiento.
CA presentó caídas marcadas en 2013 y 2015, las cuales también se perciben en los demás estados, aunque con menor intensidad. Estos descensos y picos no ocurren de manera constante (por ejemplo, no se aprecian en 2012), pero podrían reflejar principalmente el patrón anual previamente identificado.
El conjunto de datos incluye 10 tiendas: 4 en California, 3 en Texas y 3 en Wisconsin, así como 3 categorías: FOODS
(alimentos), HOBBIES
(pasatiempos) y HOUSEHOLD
(hogar). Se utilizarán niveles de agregación mensuales para mantener las gráficas claras.
# ventas mensuales por categoría
= agg_wide(train, ['cat_id'])
cat = set_monthly(cat)
cat_monthly
= px.line(
fig
cat_monthly,='dates',
x='sales',
y='id',
color='Ventas por categoría',
title={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Categoría'}
labels
)='plotly_white')
fig.update_layout(template fig.show()
# conteo de ventas por categoría
= train['cat_id'].value_counts().reset_index()
counts = ['id','n']
counts.columns
= px.bar(
fig
counts,='id',
x='n',
y='id',
color='Ventas por categoría',
title={'id': 'Categoría', 'n': 'Conteo'}
labels
)='plotly_white')
fig.update_layout(template=dict(size=7))
fig.update_xaxes(tickfont fig.show()
= agg_wide(train, ['store_id'])
sto = set_monthly(sto)
store_monthly # extraer estado de la id de tienda
'state_id'] = store_monthly['id'].str.slice(0, 2)
store_monthly[
= px.line(
fig
store_monthly,='dates',
x='sales',
y='id',
color='state_id',
facet_col=3,
facet_col_wrap='Ventas por tienda',
title={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Tienda', 'state_id': 'Estado'}
labels
)
fig.update_layout(='plotly_white',
template='Tienda',
legend_title_text='h',
legend_orientation=-0.2
legend_y
)=None)
fig.update_xaxes(title fig.show()
Se observa lo siguiente:
La categoría FOODS
es la más frecuente, seguida de HOUSEHOLD
, que se encuentra claramente por encima de HOBBIES
. El número de registros de HOUSEHOLD
se aproxima más al de FOODS
que las cifras de ventas correspondientes, lo que sugiere que se venden más unidades de FOODS
que de HOUSEHOLD
.
En cuanto a las tiendas, las ubicadas en Texas muestran ventas bastante similares entre sí; TX_3
pasa de niveles comparables a TX_1
hasta alcanzar los de TX_2
a lo largo del período analizado. Las tiendas de Wisconsin WI_1
y WI_2
presentan un notable aumento en las ventas en 2012, mientras que WI_3
muestra una caída sostenida durante varios años.
Las tiendas de California exhiben un volumen de ventas relativamente uniforme. Destaca CA_2
, que desciende al nivel de CA_4
en 2015 y posteriormente se recupera, alcanzando las ventas de CA_1
hacia finales del año.
Los datos incluyen 7 departamentos: 3 en la categoría FOODS
y 2 en cada una de las categorías HOBBIES
y HOUSEHOLD
. Junto con los 3 estados, estos niveles suman un total de 21 combinaciones.
# ventas mensuales por departamento y estado
= (
dept
train'dept_id', 'state_id'])[cols_d(train)]
.groupby([sum()
.
.reset_index()
)
= dept[['dept_id', 'state_id'] + cols_d(dept)]
df_dept = df_dept.melt(id_vars=['dept_id', 'state_id'], var_name='day', value_name='sales')
ts_dept
'day'] = ts_dept['day'].str.removeprefix('d_').astype(int)
ts_dept['dates'] = ts_dept['day'].apply(lambda d: MIN_DATE + timedelta(days=d - 1))
ts_dept[# agregar mes/año
'month'] = ts_dept['dates'].dt.month
ts_dept['year'] = ts_dept['dates'].dt.year
ts_dept[
= (
dept_monthly
ts_dept'year', 'month', 'dept_id', 'state_id'], as_index=False)
.groupby([
.agg(=('sales', 'sum'),
sales=('dates', 'min')
dates
)
)# filtrar primer día y quitar mes incompleto
= dept_monthly[dept_monthly['dates'].dt.day == 1]
dept_monthly = dept_monthly['dates'].max()
last = dept_monthly[dept_monthly['dates'] != last]
dept_monthly
# gráfica por depto y estado
= px.line(
fig
dept_monthly,='dates',
x='sales',
y='dept_id',
color='state_id',
facet_row='dept_id',
facet_col='Ventas por departamento y estado',
title={
labels'dates': 'Fecha',
'sales': 'Ventas',
'dept_id': 'Depto',
'state_id': 'Estado'
}
)='plotly_white', showlegend=False)
fig.update_layout(template=8)
fig.update_annotations(font_size=None)
fig.update_xaxes(title=None)
fig.update_yaxes(title fig.show()
Se observa lo siguiente:
FOODS_3
concentra claramente la mayor parte de las ventas dentro de la categoría FOODS
en todos los estados. FOODS_2
muestra un ligero incremento hacia el final del período, especialmente en Wisconsin.
De manera similar, HOUSEHOLD_1
supera con claridad a HOUSEHOLD_2
en volumen de ventas. Por su parte, HOBBIES_1
mantiene un nivel promedio de ventas superior al de HOBBIES_2
, aunque en ambos casos no se aprecia un cambio significativo a lo largo del tiempo.
En esta sección se analizan en las dos variables adicionales proporcionadas: los precios de los productos y los eventos del calendario.
En la Sección 3 se aprecia que el dataframe calendar
incluye características básicas como día de la semana (columna weekday
en formato de texto y wday
en formato numérico), mes, año y, por supuesto, fecha. Junto a la fecha aparece también la columna d
, que vincula cada fecha con los nombres de columna en los datos de entrenamiento.
El resto de los atributos están relacionados con eventos y con cupones de asistencia alimentaria:
Al revisar la Sección 3, se observa que las columnas event_name_2
y event_type_2
solo tienen datos en cinco filas (el resto son valores ausentes). Por eso, este análisis se centrará únicamente en las columnas event_name_1
y event_type_1
.
El acrónimo SNAP corresponde a “Supplemental Nutrition Assistance Program” (programa federal de asistencia nutricional). Según su sitio web:
“El programa SNAP es el mayor programa federal de asistencia nutricional. Proporciona beneficios a personas y familias de bajos ingresos mediante una tarjeta de transferencia electrónica de beneficios, que puede usarse como una tarjeta de débito para adquirir alimentos autorizados en establecimientos de venta al por menor.”
# días con eventos vs sin eventos
= (
events
calendar=lambda df: ~df['event_type_1'].isna())
.assign(event'event')
.groupby(
.size()='count')
.reset_index(name
)'total'] = events['count'].sum()
events['perc'] = events['count'] / events['total']
events[
= px.bar(
fig
events,='event',
x='perc',
y='Días con eventos',
title={'event': 'Evento', 'perc': 'Porcentaje'},
labels='event'
color
)='plotly_white', showlegend=False)
fig.update_layout(template=None)
fig.update_xaxes(title=None, tickformat='.0%')
fig.update_yaxes(title fig.show()
# tipos de eventos
= (
tps
calendar=['event_type_1'])
.dropna(subset'event_type_1')
.groupby(
.size()='count')
.reset_index(name
)'total'] = tps['count'].sum()
tps['perc'] = tps['count'] / tps['total']
tps[
= {
label_map 'Religious': 'Religioso',
'National': 'Nacional',
'Cultural': 'Cultural',
'Sporting': 'Deportivo'
}'event_type_1'] = tps['event_type_1'].map(label_map)
tps[
= px.pie(
fig
tps,='event_type_1',
names='perc',
values='Tipos de eventos',
title=0,
hole={'event_type_1':'Evento','perc':'Porcentaje'}
labels
)
fig.update_traces(='%{label}: %{percent:.0%}',
texttemplate='%{label}: %{percent:.0%}'
hovertemplate
) fig.show()
# días con SNAP por estado
= [col for col in calendar.columns if col.startswith('snap_')]
snap_cols
= (
snp
calendar=['date'], value_vars=snap_cols, var_name='state', value_name='snap')
.melt(id_vars=lambda df: df['state'].str[-2:])
.assign(state=lambda df: df['snap'].astype(bool))
.assign(snap'state', 'snap'])
.groupby([
.size()='count')
.reset_index(name
)'total'] = snp.groupby('state')['count'].transform('sum')
snp['perc'] = snp['count'] / snp['total']
snp[
= px.bar(
fig
snp,='snap',
x='perc',
y='state',
facet_col=3,
facet_col_wrap='snap',
color='Días con compras SNAP por estado',
title={'snap': 'SNAP', 'perc': 'Porcentaje', 'state': 'Estado'}
labels
)=None, tickformat='.0%')
fig.update_yaxes(title=None)
fig.update_xaxes(title
fig.update_traces(='SNAP: %{x}<br>' + 'Porcentaje: %{y:.0%}<extra></extra>'
hovertemplate
)='plotly_white', showlegend=False)
fig.update_layout(template fig.show()
Se encuentra lo siguiente:
Se dispone de información detallada sobre los precios de los productos, incluyendo sus ID de categoría, departamento y tienda (que a su vez incluye el ID de estado). Los precios se presentan como promedios semanales, y la variable wm_yr_wk
permite vincular cada semana con su fecha correspondiente a través de la columna de calendario del mismo nombre.
Para analizar los precios promedio de los productos por categoría y departamento entre los años 2011 y 2016, se empleó el siguiente proceso:
= train[['item_id', 'cat_id', 'dept_id']].drop_duplicates()
item_info = prices.merge(calendar[['wm_yr_wk', 'year']], on='wm_yr_wk', how='left')
df = df.merge(item_info, on='item_id', how='left')
df
= (
df_group 'year', 'cat_id', 'dept_id'])['sell_price']
df.groupby([
.mean()
.reset_index() )
= df_group[df_group['cat_id'] == 'FOODS']
foods
= px.line(
fig
foods,='year',
x='sell_price',
y='dept_id',
color=True,
markers='Evolución del precio promedio',
title={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
labels='plotly_white'
template
)
fig.show()
= df_group[df_group['cat_id'] == 'HOBBIES']
hobbies
= px.line(
fig
hobbies,='year',
x='sell_price',
y='dept_id',
color=True,
markers='Evolución del precio promedio',
title={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
labels='plotly_white'
template
)
fig.show()
= df_group[df_group['cat_id'] == 'HOUSEHOLD']
household
= px.line(
fig
household,='year',
x='sell_price',
y='dept_id',
color=True,
markers='Evolución del precio promedio',
title={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
labels='plotly_white'
template
)
fig.show()
Se observa lo siguiente:
En términos generales, los precios promedio se mantienen relativamente estables a lo largo de los años, con incrementos graduales que podrían atribuirse a la inflación.
FOODS
, se identifican las siguientes tendencias:
FOODS_1
, el precio promedio fluctúa entre 3.3 y 3.4, mostrando estabilidad con leves variaciones a lo largo del período.FOODS_2
, se observa un aumento constante desde 3.8 en 2011 hasta 4.2 en 2016, reflejando una tendencia de alza clara.FOODS_3
, el precio permanece estable en 2.8 hasta 2013, con un ligero incremento a 2.9 hacia 2016.HOBBIES
, se observan las siguientes dinámicas:
HOBBIES_1
, el precio crece de manera sostenida desde 5.2 en 2011 hasta 6.6 en 2016, mostrando un incremento notable.HOBBIES_2
, el precio desciende de 2.8 hasta 2.5 a lo largo de todo el período, con una tendencia descendiente.HOUSEHOLD
, se destacan los siguientes patrones:
HOUSEHOLD_1
, el precio se mantiene en torno a 4.9 hasta 2013, con un aumento gradual a 5.1 en 2016.HOUSEHOLD_2
, el precio disminuye de 6.1 en 2011 a 5.7 en 2016, indicando una tendencia descendente leve pero constante.