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 pxEl 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 pxCarga 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.
calendar = pd.read_parquet('data/calendar.parquet')
train = pd.read_parquet('data/sales_train.parquet')
prices = pd.read_parquet('data/sell_prices.parquet')
sample_submit = pd.read_parquet('data/sample_submission.parquet')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:
train.head(10)| 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:
prices.head(10)| 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
pd.options.display.float_format = '{:.2f}'.format
prices.describe(include='all')| 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:
calendar.head(10)| 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
calendar.describe(include='all')| 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:
train.isna().sum()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
df = train.loc[:, ~train.columns.str.contains('id')]
df = df.replace(0, np.nan)
df_na = df.isna().copy()
df_na['sum'] = df_na.sum(axis=1)
df_na['mean'] = df_na['sum'] / df_na.shape[1]
bar = df_na[['sum', 'mean']]
plt.figure(figsize=(8, 6))
sns.kdeplot(bar['mean'], fill=True, color='blue')
plt.gca().xaxis.set_major_formatter(PercentFormatter(1)) # eje x como %
plt.xlim(0, 1)
plt.gca().set_yticklabels([])
plt.title("Densidad del porcentaje de valores cero - todas las series temporales")
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
MIN_DATE = datetime(2011, 1, 29)
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 = df.copy()
# conservar id y columnas de días
df = df[['id'] + cols_d(df)]
# pivot largo
ts = df.melt(id_vars=['id'], var_name='day', value_name='sales')
# convertir índice de día en entero
ts['day'] = ts['day'].str.removeprefix('d_').astype(int)
# calcular fecha real
ts['dates'] = ts['day'].apply(lambda x: MIN_DATE + timedelta(days=x - 1))
ts.drop(columns='day', inplace=True)
# limpiar sufijo
ts['id'] = ts['id'].astype(str).str.replace('_validation', '')
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 = agg.rename(columns={group_cols[-1]: 'id'})
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.
"""
ts = extract_ts(df)
ts['month'] = ts['dates'].dt.month
ts['year'] = ts['dates'].dt.year
monthly = (
ts
.groupby(['year', 'month', 'id'], as_index=False)
.agg(
sales=('sales', 'sum'),
dates=('dates', 'min')
)
)
monthly = monthly[monthly['dates'].dt.day == 1]
last = monthly['dates'].max()
monthly = monthly[monthly['dates'] != last]
return monthlyEn primer lugar, se presenta la serie temporal agregada de todos los artículos, tiendas, categorías, departamentos y ventas.
# ventas totales agregadas
agg = train.sum().to_frame('sales').T
agg['id'] = 'total'
ts_agg = extract_ts(agg)
fig = px.line(
ts_agg,
x='dates',
y='sales',
title='Ventas agregadas',
labels={'dates': 'Fecha', 'sales': 'Ventas'}
)
fig.update_layout(template='plotly_white')
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
stt = agg_wide(train, ['state_id'])
ts_stt = set_monthly(stt)
fig = px.line(
ts_stt,
x='dates',
y='sales',
color='id',
title='Ventas mensuales por estado',
labels={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Estado'}
)
fig.update_layout(template='plotly_white')
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
cat = agg_wide(train, ['cat_id'])
cat_monthly = set_monthly(cat)
fig = px.line(
cat_monthly,
x='dates',
y='sales',
color='id',
title='Ventas por categoría',
labels={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Categoría'}
)
fig.update_layout(template='plotly_white')
fig.show()# conteo de ventas por categoría
counts = train['cat_id'].value_counts().reset_index()
counts.columns = ['id','n']
fig = px.bar(
counts,
x='id',
y='n',
color='id',
title='Ventas por categoría',
labels={'id': 'Categoría', 'n': 'Conteo'}
)
fig.update_layout(template='plotly_white')
fig.update_xaxes(tickfont=dict(size=7))
fig.show()sto = agg_wide(train, ['store_id'])
store_monthly = set_monthly(sto)
# extraer estado de la id de tienda
store_monthly['state_id'] = store_monthly['id'].str.slice(0, 2)
fig = px.line(
store_monthly,
x='dates',
y='sales',
color='id',
facet_col='state_id',
facet_col_wrap=3,
title='Ventas por tienda',
labels={'dates': 'Fecha', 'sales': 'Ventas', 'id': 'Tienda', 'state_id': 'Estado'}
)
fig.update_layout(
template='plotly_white',
legend_title_text='Tienda',
legend_orientation='h',
legend_y=-0.2
)
fig.update_xaxes(title=None)
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
.groupby(['dept_id', 'state_id'])[cols_d(train)]
.sum()
.reset_index()
)
df_dept = dept[['dept_id', 'state_id'] + cols_d(dept)]
ts_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))
# agregar mes/año
ts_dept['month'] = ts_dept['dates'].dt.month
ts_dept['year'] = ts_dept['dates'].dt.year
dept_monthly = (
ts_dept
.groupby(['year', 'month', 'dept_id', 'state_id'], as_index=False)
.agg(
sales=('sales', 'sum'),
dates=('dates', 'min')
)
)
# filtrar primer día y quitar mes incompleto
dept_monthly = dept_monthly[dept_monthly['dates'].dt.day == 1]
last = dept_monthly['dates'].max()
dept_monthly = dept_monthly[dept_monthly['dates'] != last]
# gráfica por depto y estado
fig = px.line(
dept_monthly,
x='dates',
y='sales',
color='dept_id',
facet_row='state_id',
facet_col='dept_id',
title='Ventas por departamento y estado',
labels={
'dates': 'Fecha',
'sales': 'Ventas',
'dept_id': 'Depto',
'state_id': 'Estado'
}
)
fig.update_layout(template='plotly_white', showlegend=False)
fig.update_annotations(font_size=8)
fig.update_xaxes(title=None)
fig.update_yaxes(title=None)
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
.assign(event=lambda df: ~df['event_type_1'].isna())
.groupby('event')
.size()
.reset_index(name='count')
)
events['total'] = events['count'].sum()
events['perc'] = events['count'] / events['total']
fig = px.bar(
events,
x='event',
y='perc',
title='Días con eventos',
labels={'event': 'Evento', 'perc': 'Porcentaje'},
color='event'
)
fig.update_layout(template='plotly_white', showlegend=False)
fig.update_xaxes(title=None)
fig.update_yaxes(title=None, tickformat='.0%')
fig.show()# tipos de eventos
tps = (
calendar
.dropna(subset=['event_type_1'])
.groupby('event_type_1')
.size()
.reset_index(name='count')
)
tps['total'] = tps['count'].sum()
tps['perc'] = tps['count'] / tps['total']
label_map = {
'Religious': 'Religioso',
'National': 'Nacional',
'Cultural': 'Cultural',
'Sporting': 'Deportivo'
}
tps['event_type_1'] = tps['event_type_1'].map(label_map)
fig = px.pie(
tps,
names='event_type_1',
values='perc',
title='Tipos de eventos',
hole=0,
labels={'event_type_1':'Evento','perc':'Porcentaje'}
)
fig.update_traces(
texttemplate='%{label}: %{percent:.0%}',
hovertemplate='%{label}: %{percent:.0%}'
)
fig.show()# días con SNAP por estado
snap_cols = [col for col in calendar.columns if col.startswith('snap_')]
snp = (
calendar
.melt(id_vars=['date'], value_vars=snap_cols, var_name='state', value_name='snap')
.assign(state=lambda df: df['state'].str[-2:])
.assign(snap=lambda df: df['snap'].astype(bool))
.groupby(['state', 'snap'])
.size()
.reset_index(name='count')
)
snp['total'] = snp.groupby('state')['count'].transform('sum')
snp['perc'] = snp['count'] / snp['total']
fig = px.bar(
snp,
x='snap',
y='perc',
facet_col='state',
facet_col_wrap=3,
color='snap',
title='Días con compras SNAP por estado',
labels={'snap': 'SNAP', 'perc': 'Porcentaje', 'state': 'Estado'}
)
fig.update_yaxes(title=None, tickformat='.0%')
fig.update_xaxes(title=None)
fig.update_traces(
hovertemplate='SNAP: %{x}<br>' + 'Porcentaje: %{y:.0%}<extra></extra>'
)
fig.update_layout(template='plotly_white', showlegend=False)
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:
item_info = train[['item_id', 'cat_id', 'dept_id']].drop_duplicates()
df = prices.merge(calendar[['wm_yr_wk', 'year']], on='wm_yr_wk', how='left')
df = df.merge(item_info, on='item_id', how='left')
df_group = (
df.groupby(['year', 'cat_id', 'dept_id'])['sell_price']
.mean()
.reset_index()
)foods = df_group[df_group['cat_id'] == 'FOODS']
fig = px.line(
foods,
x='year',
y='sell_price',
color='dept_id',
markers=True,
title='Evolución del precio promedio',
labels={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
template='plotly_white'
)
fig.show()hobbies = df_group[df_group['cat_id'] == 'HOBBIES']
fig = px.line(
hobbies,
x='year',
y='sell_price',
color='dept_id',
markers=True,
title='Evolución del precio promedio',
labels={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
template='plotly_white'
)
fig.show()household = df_group[df_group['cat_id'] == 'HOUSEHOLD']
fig = px.line(
household,
x='year',
y='sell_price',
color='dept_id',
markers=True,
title='Evolución del precio promedio',
labels={'sell_price': 'Precio promedio', 'year': 'Año', 'dept_id': 'Departamento'},
template='plotly_white'
)
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.