Final_Project / app.py
GMARTINEZMILLA's picture
feat: Updated colours in 3D cluster
07a6071
raw
history blame
41.2 kB
import streamlit as st
import time
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import matplotlib.pyplot as plt
import numpy as np
import lightgbm as lgb
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import mean_absolute_error, mean_squared_error
from joblib import dump, load
from utils import recomienda_tf
# Page configuration
st.set_page_config(page_title="DeepInsightz", page_icon=":bar_chart:", layout="wide")
# Custom CSS for dynamic theme styling
# Streamlit detects light and dark mode automatically via the user's settings in Hugging Face Spaces
if st.get_option("theme.base") == "dark":
background_color = "#282828"
text_color = "white"
metric_box_color = "#4f4f4f"
sidebar_color = "#282828"
plot_bgcolor = "rgba(0, 0, 0, 0)"
primary_color = '#00FF00' # for positive delta
negative_color = '#FF0000' # for negative delta
else:
background_color = "#f4f4f4"
text_color = "#black"
metric_box_color = "#dee2e8"
sidebar_color = "#dee2e8"
plot_bgcolor = "#f4f4f4"
primary_color = '#228B22' # for positive delta in light mode
negative_color = '#8B0000' # for negative delta in light mode
st.markdown(f"""
<style>
body {{
background-color: {background_color};
color: {text_color};
}}
[data-testid="stMetric"] {{
background-color: {metric_box_color};
border-radius: 10px;
text-align: center;
padding: 15px 0;
margin-bottom: 20px;
}}
[data-testid="stMetricLabel"] {{
display: flex;
justify-content: center;
align-items: center;
color: {text_color};
}}
[data-testid="stSidebar"] {{
background-color: {sidebar_color};
}}
</style>
""", unsafe_allow_html=True)
# Load CSV files at the top
df = pd.read_csv("df_clean.csv")
nombres_proveedores = pd.read_csv("nombres_proveedores.csv", sep=';')
euros_proveedor = pd.read_csv("euros_proveedor.csv", sep=',')
ventas_clientes = pd.read_csv("ventas_clientes.csv", sep=',')
customer_clusters = pd.read_csv('predicts/customer_clusters.csv') # Load the customer clusters here
df_agg_2024 = pd.read_csv('predicts/df_agg_2024.csv')
pca_data_5 = pd.read_csv('pca_data.csv')
historical_data = pd.read_csv('historical_data.csv')
with st.sidebar:
st.sidebar.title("DeepInsightz")
page = st.sidebar.selectbox("Selecciona la herramienta que quieres utilizar...", ["📃 Resumen", "🕵️ Análisis de Cliente", "💡 Recomendación de Artículos"])
# Generamos la columna total_sales
ventas_clientes['total_sales'] = ventas_clientes[['VENTA_2021', 'VENTA_2022', 'VENTA_2023']].sum(axis=1)
# Ordenar los clientes de mayor a menor según sus ventas totales
ventas_top_100 = ventas_clientes.sort_values(by='total_sales', ascending=False).head(100)
ventas_top_100['total_sales'] = ventas_top_100['total_sales'] / 3
# Ensure customer codes are strings
df['CLIENTE'] = df['CLIENTE'].astype(str)
nombres_proveedores['codigo'] = nombres_proveedores['codigo'].astype(str)
euros_proveedor['CLIENTE'] = euros_proveedor['CLIENTE'].astype(str)
customer_clusters['cliente_id'] = customer_clusters['cliente_id'].astype(str) # Ensure customer IDs are strings
fieles_df = pd.read_csv("clientes_relevantes.csv")
cestas = pd.read_csv("cestas.csv")
productos = pd.read_csv("productos.csv")
df_agg_2024['cliente_id'] = df_agg_2024['cliente_id'].astype(str)
marca_id_mapping = load('marca_id_mapping.joblib')
# Convert all columns except 'CLIENTE' to float in euros_proveedor
for col in euros_proveedor.columns:
if col != 'CLIENTE':
euros_proveedor[col] = pd.to_numeric(euros_proveedor[col], errors='coerce')
# Check for NaN values after conversion
if euros_proveedor.isna().any().any():
st.warning("Some values in euros_proveedor couldn't be converted to numbers. Please review the input data.")
# Ignore the last two columns of df
df = df.iloc[:, :-2]
# Function to get supplier name
def get_supplier_name(code):
code = str(code) # Ensure code is a string
name = nombres_proveedores[nombres_proveedores['codigo'] == code]['nombre'].values
return name[0] if len(name) > 0 else code
def get_supplier_name_encoded(encoded_code):
try:
# Ensure the encoded code is an integer
encoded_code = int(encoded_code)
print(f"Encoded Code: {encoded_code}")
# Use the label encoder to map the encoded code back to the original manufacturer code
if encoded_code < len(marca_id_mapping.classes_):
real_code = marca_id_mapping.inverse_transform([encoded_code])[0]
print(f"Real Manufacturer Code: {real_code}")
else:
print(f"Encoded code not found in the label encoder: {encoded_code}")
return f"Unknown code: {encoded_code}" # Handle case where encoded code is not found
# Now, use the real_code to find the manufacturer name in nombres_proveedores
name = nombres_proveedores[nombres_proveedores['codigo'] == str(real_code)]['nombre'].values
print(f"Manufacturer Name Found: {name}") # Check what name is returned
# Return the manufacturer name if found, otherwise return the real_code
return name[0] if len(name) > 0 else real_code
except Exception as e:
print(f"Error encountered: {e}")
return f"Error for code: {encoded_code}"
# Custom Donut Chart with Plotly for Inbound/Outbound Percentage
def create_donut_chart(values, labels, color_scheme, title):
fig = px.pie(
values=values,
names=labels,
hole=0.7,
color_discrete_sequence=color_scheme
)
fig.update_traces(textinfo='percent+label', hoverinfo='label+percent', textposition='inside', showlegend=False)
fig.update_layout(
annotations=[dict(text=f"{int(values[1])}%", x=0.5, y=0.5, font_size=40, showarrow=False)],
title=title,
height=300,
margin=dict(t=30, b=10, l=10, r=10),
paper_bgcolor=plot_bgcolor, # Use theme-dependent background color
plot_bgcolor=plot_bgcolor
)
return fig
# Donut chart with color scheme based on theme
if st.get_option("theme.base") == "dark":
donut_color_scheme = ['#155F7A', '#29b5e8'] # Dark mode colors
else:
donut_color_scheme = ['#007BFF', '#66b5ff'] # Light mode colors
# Function to create radar chart with square root transformation
def radar_chart(categories, values, amounts, title):
N = len(categories)
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(12, 12), subplot_kw=dict(projection='polar'))
# Apply square root transformation
sqrt_values = np.sqrt(values)
sqrt_amounts = np.sqrt(amounts)
max_sqrt_value = max(sqrt_values)
normalized_values = [v / max_sqrt_value for v in sqrt_values]
# Adjust scaling for spend values
max_sqrt_amount = max(sqrt_amounts)
scaling_factor = 0.7 # Adjust this value to control how much the spend values are scaled up
normalized_amounts = [min((a / max_sqrt_amount) * scaling_factor, 1.0) for a in sqrt_amounts]
normalized_values += normalized_values[:1]
ax.plot(angles, normalized_values, 'o-', linewidth=2, color='#FF69B4', label='% Units (sqrt)')
ax.fill(angles, normalized_values, alpha=0.25, color='#FF69B4')
normalized_amounts += normalized_amounts[:1]
ax.plot(angles, normalized_amounts, 'o-', linewidth=2, color='#4B0082', label='% Spend (sqrt)')
ax.fill(angles, normalized_amounts, alpha=0.25, color='#4B0082')
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=8, wrap=True)
ax.set_ylim(0, 1)
circles = np.linspace(0, 1, 5)
for circle in circles:
ax.plot(angles, [circle]*len(angles), '--', color='gray', alpha=0.3, linewidth=0.5)
ax.set_yticklabels([])
ax.spines['polar'].set_visible(False)
plt.title(title, size=16, y=1.1)
plt.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
return fig
if page == "📃 Resumen":
# st.title("Welcome to DeepInsightz")
# st.markdown("""
# ### Data-driven Customer Clustering
# We analyzed thousands of customers and suppliers to help businesses make smarter sales decisions.
# """)
# Create layout with three columns
col1, col2, col3 = st.columns((1.5, 4, 2.5), gap='medium')
# Left Column (Red): Metrics and Donut Charts
with col1:
st.markdown('#### Información General')
st.metric(label="Rango de fechas", value="2021-2023")
st.metric(label="Clientes analizados", value="3.000")
st.metric(label="Productos únicos vendidos", value="10.702")
st.metric(label="Líneas de venta totales", value="764.396")
# Middle Column (White): 3D Cluster Model and Bar Chart
with col2:
st.markdown('#### Cluster de Clientes 3D')
# Create 3D PCA plot using actual data from pca_data_5
fig_cluster = px.scatter_3d(
pca_data_5,
x='PC1',
y='PC2',
z='PC3',
color='cluster_id',
hover_name='CustomerID',
color_continuous_scale='Turbo'
)
fig_cluster.update_layout(
scene=dict(aspectratio=dict(x=1, y=1, z=0.8)), # Adjusted aspect ratio for better balance
margin=dict(t=10, b=10, l=10, r=10), # Tighten margins further
height=600, # Slightly increased height for better visibility
)
st.plotly_chart(fig_cluster, use_container_width=True)
# Right Column (Blue): Key Metrics Overview and Data Preparation Summary
with col3:
# Mostrar la tabla con los 100 mejores clientes
st.markdown('#### Top 100 Clientes')
# Configurar columnas para mostrar los clientes y las ventas totales
st.dataframe(ventas_top_100[['codigo_cliente', 'total_sales']],
column_order=("codigo_cliente", "total_sales"),
hide_index=True,
width=450, # Ajustar el ancho de la tabla
height=600, # Ajustar la altura de la tabla
column_config={
"codigo_cliente": st.column_config.TextColumn(
"Código de Cliente",
),
"total_sales": st.column_config.ProgressColumn(
"Venta Total (€)",
format="%d",
min_value=0,
max_value=ventas_top_100['total_sales'].max()
)}
)
# Customer Analysis Page
elif page == "🕵️ Análisis de Cliente":
st.markdown("""
<h2 style='text-align: center; font-size: 2.5rem;'>Análisis de Cliente</h2>
<p style='text-align: center; font-size: 1.2rem; color: gray;'>
Introduce el código del cliente para explorar información detallada del mismo, incluyendo ventas anteriores, predicciones para el año actual e información específica por fabricante.
</p>
""", unsafe_allow_html=True)
# Combine text input and dropdown into a single searchable selectbox
customer_code = st.selectbox(
"Escribe o selecciona el código de tu cliente",
df['CLIENTE'].unique(), # All customer codes
format_func=lambda x: str(x), # Ensures the values are displayed as strings
help="Start typing to search for a specific customer code"
)
if st.button("Calcular"):
if customer_code:
with st.spinner("Estamos identificando el grupo del cliente..."):
# Find Customer's Cluster
customer_match = customer_clusters[customer_clusters['cliente_id'] == customer_code]
time.sleep(1)
if not customer_match.empty:
cluster = customer_match['cluster_id'].values[0]
with st.spinner(f"Seleccionando el modelo predictivo..."):
# Load the Corresponding Model
model_path = f'models/modelo_cluster_{cluster}.txt'
gbm = lgb.Booster(model_file=model_path)
with st.spinner("Preparando los datos..."):
# Load predict data for that cluster
predict_data = pd.read_csv(f'predicts/predict_cluster_{cluster}.csv')
# Convert cliente_id to string
predict_data['cliente_id'] = predict_data['cliente_id'].astype(str)
with st.spinner("Filtrando data..."):
# Filter for the specific customer
customer_code_str = str(customer_code)
customer_data = predict_data[predict_data['cliente_id'] == customer_code_str]
with st.spinner("Geneerando predicciones de venta..."):
if not customer_data.empty:
# Define features consistently with the training process
lag_features = [f'precio_total_lag_{lag}' for lag in range(1, 25)]
features = lag_features + ['mes', 'marca_id_encoded', 'año', 'cluster_id']
# Prepare data for prediction
X_predict = customer_data[features]
# Convert categorical features to 'category' dtype
categorical_features = ['mes', 'marca_id_encoded', 'cluster_id']
for feature in categorical_features:
X_predict[feature] = X_predict[feature].astype('category')
# Make Prediction for the selected customer
y_pred = gbm.predict(X_predict, num_iteration=gbm.best_iteration)
# Reassemble the results
results = customer_data[['cliente_id', 'marca_id_encoded', 'fecha_mes']].copy()
results['ventas_predichas'] = y_pred
# Load actual data from df_agg_2024
actual_sales = df_agg_2024[df_agg_2024['cliente_id'] == customer_code_str]
if not actual_sales.empty:
# Merge predictions with actual sales
results = results.merge(actual_sales[['cliente_id', 'marca_id_encoded', 'fecha_mes', 'precio_total']],
on=['cliente_id', 'marca_id_encoded', 'fecha_mes'],
how='left')
results.rename(columns={'precio_total': 'ventas_reales'}, inplace=True)
else:
# If no actual sales data for 2024, fill 'ventas_reales' with 0
results['ventas_reales'] = 0
# Ensure any missing sales data is filled with 0
results['ventas_reales'].fillna(0, inplace=True)
# Define the cutoff date for the last 12 months
fecha_inicio = pd.to_datetime("2023-01-01")
fecha_corte = pd.to_datetime("2024-09-01")
# Convertir fecha_mes a datetime en el DataFrame historical_data
historical_data['fecha_mes'] = pd.to_datetime(historical_data['fecha_mes'], errors='coerce')
# Ensure cliente_id is of type string and strip any leading/trailing whitespace
historical_data['cliente_id'] = historical_data['cliente_id'].astype(str).str.strip()
customer_code_str = str(customer_code).strip() # Ensure the customer code is also properly formatted
filtered_historical_data = historical_data[historical_data['cliente_id'] == customer_code_str]
# Filtrar los datos históricos por cliente y por el rango de fechas (2023)
fecha_inicio_2023 = pd.to_datetime("2023-01-01")
fecha_fin_2023 = pd.to_datetime("2023-12-31")
datos_historicos = historical_data[
(historical_data['cliente_id'] == customer_code_str) &
(historical_data['fecha_mes'] >= fecha_inicio_2023) &
(historical_data['fecha_mes'] <= fecha_fin_2023)
].groupby('fecha_mes')['precio_total'].sum().reset_index()
# Renombrar la columna 'precio_total' a 'ventas_historicas' si no está vacía
if not datos_historicos.empty:
datos_historicos.rename(columns={'precio_total': 'ventas_historicas'}, inplace=True)
else:
# Si los datos históricos están vacíos, generar fechas de 2023 con ventas_historicas = 0
fechas_2023 = pd.date_range(start='2023-01-01', end='2023-12-31', freq='M')
datos_historicos = pd.DataFrame({'fecha_mes': fechas_2023, 'ventas_historicas': [0] * len(fechas_2023)})
# Filtrar los datos de predicciones y ventas reales para 2024
datos_cliente_total = results.groupby('fecha_mes').agg({
'ventas_reales': 'sum',
'ventas_predichas': 'sum'
}).reset_index()
# Asegurarnos de que fecha_mes en datos_cliente_total es datetime
datos_cliente_total['fecha_mes'] = pd.to_datetime(datos_cliente_total['fecha_mes'], errors='coerce')
# Generar un rango de fechas para 2024 si no hay predicciones
fechas_2024 = pd.date_range(start='2024-01-01', end='2024-12-31', freq='M')
fechas_df_2024 = pd.DataFrame({'fecha_mes': fechas_2024})
# Asegurarnos de que fecha_mes en fechas_df_2024 es datetime
fechas_df_2024['fecha_mes'] = pd.to_datetime(fechas_df_2024['fecha_mes'], errors='coerce')
# Combinar datos históricos con predicciones y ventas reales usando un merge
# Usamos how='outer' para asegurarnos de incluir todas las fechas de 2023 y 2024
datos_combinados = pd.merge(datos_historicos, datos_cliente_total, on='fecha_mes', how='outer').sort_values('fecha_mes')
# Rellenar los NaN: 0 en ventas_historicas donde faltan predicciones, y viceversa
datos_combinados['ventas_historicas'].fillna(0, inplace=True)
datos_combinados['ventas_predichas'].fillna(0, inplace=True)
datos_combinados['ventas_reales'].fillna(0, inplace=True)
# Crear la gráfica con Plotly
fig = go.Figure()
# Graficar ventas históricas
fig.add_trace(go.Scatter(
x=datos_combinados['fecha_mes'],
y=datos_combinados['ventas_historicas'],
mode='lines+markers',
name='Ventas Históricas',
line=dict(color='blue')
))
# Graficar ventas predichas
fig.add_trace(go.Scatter(
x=datos_combinados['fecha_mes'],
y=datos_combinados['ventas_predichas'],
mode='lines+markers',
name='Ventas Predichas',
line=dict(color='orange')
))
# Graficar ventas reales
fig.add_trace(go.Scatter(
x=datos_combinados['fecha_mes'],
y=datos_combinados['ventas_reales'],
mode='lines+markers',
name='Ventas Reales',
line=dict(color='green')
))
# Personalizar el layout para enfocarse en 2023 y 2024
fig.update_layout(
title=f"Ventas Históricas, Predichas y Reales para Cliente {customer_code}",
xaxis_title="Fecha",
yaxis_title="Ventas (€)",
height=600,
xaxis_range=[fecha_inicio_2023, pd.to_datetime("2024-09-30")], # Ajustar el rango del eje x a 2023-2024
legend_title="Tipo de Ventas",
hovermode="x unified"
)
# Mostrar la gráfica en Streamlit
st.plotly_chart(fig)
# Calculate metrics for 2024 data
datos_2024 = datos_combinados[datos_combinados['fecha_mes'].dt.year == 2024]
actual = datos_2024['ventas_reales']
predicted = datos_2024['ventas_predichas']
def calculate_mape(y_true, y_pred):
mask = y_true != 0
return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100
mae = mean_absolute_error(actual, predicted)
mse = mean_squared_error(actual, predicted)
rmse = np.sqrt(mse)
mape = calculate_mape(actual, predicted)
smape = np.mean(2 * np.abs(actual - predicted) / (np.abs(actual) + np.abs(predicted))) * 100
# Display metrics
st.subheader("Métricas de Predicción (2024)")
col1, col2, col3, col4 = st.columns(4)
col1.metric("MAE", f"{mae:.2f} €",help="Promedio de la diferencia absoluta entre las predicciones y los valores reales.")
col2.metric("MAPE", f"{mape:.2f}%",help="Porcentaje promedio de error en las predicciones.")
col3.metric("RMSE", f"{rmse:.2f} €",help="Medida de la desviación estándar de los residuos de predicción.")
col4.metric("SMAPE", f"{smape:.2f}%",help="Alternativa al MAPE que maneja mejor los valores cercanos a cero.")
# Split space into two columns
col1, col2 = st.columns(2)
# Column 1: Radar chart for top manufacturers
with col1:
st.subheader("¡Esto tiene buena pinta!")
st.info("Su cliente ha superado las ventas predichas de las siguientes marcas:")
# Group results by manufacturer to calculate the total predicted and actual sales
grouped_results = results.groupby('marca_id_encoded').agg({
'ventas_reales': 'sum',
'ventas_predichas': 'sum'
}).reset_index()
# Identify manufacturers that exceeded predicted sales
overperforming_manufacturers = grouped_results[grouped_results['ventas_reales'] > grouped_results['ventas_predichas']].copy()
if not overperforming_manufacturers.empty:
# Calculate the extra amount (difference between actual and predicted sales)
overperforming_manufacturers['extra_amount'] = overperforming_manufacturers['ventas_reales'] - overperforming_manufacturers['ventas_predichas']
# Sort by the highest extra amount
overperforming_manufacturers = overperforming_manufacturers.sort_values(by='extra_amount', ascending=False)
# Limit to top 10 overperforming manufacturers
top_overperformers = overperforming_manufacturers.head(10)
# Display two cards per row
for i in range(0, len(top_overperformers), 2):
cols = st.columns(2) # Create two columns for two cards in a row
for j, col in enumerate(cols):
if i + j < len(top_overperformers):
row = top_overperformers.iloc[i + j]
manufacturer_name = get_supplier_name_encoded(row['marca_id_encoded'])
predicted = row['ventas_predichas']
actual = row['ventas_reales']
extra = row['extra_amount']
# Use st.metric for compact display in each column
with col:
st.metric(
label=f"{manufacturer_name}",
value=f"{actual:.2f}€",
delta=f"Exceeded by {extra:.2f}€",
delta_color="normal"
)
# Radar chart logic remains the same
customer_df = df[df["CLIENTE"] == str(customer_code)]
all_manufacturers = customer_df.iloc[:, 1:].T
all_manufacturers.index = all_manufacturers.index.astype(str)
customer_euros = euros_proveedor[euros_proveedor["CLIENTE"] == str(customer_code)]
sales_data = customer_euros.iloc[:, 1:].T
sales_data.index = sales_data.index.astype(str)
sales_data_filtered = sales_data.drop(index='CLIENTE', errors='ignore')
sales_data_filtered = sales_data_filtered.apply(pd.to_numeric, errors='coerce')
all_manufacturers = all_manufacturers.apply(pd.to_numeric, errors='coerce')
top_units = all_manufacturers.sort_values(by=all_manufacturers.columns[0], ascending=False).head(10)
top_sales = sales_data_filtered.sort_values(by=sales_data_filtered.columns[0], ascending=False).head(10)
combined_top = pd.concat([top_units, top_sales]).index.unique()[:20]
combined_top = [m for m in combined_top if m in all_manufacturers.index and m in sales_data_filtered.index]
if combined_top:
combined_data = pd.DataFrame({
'units': all_manufacturers.loc[combined_top, all_manufacturers.columns[0]],
'sales': sales_data_filtered.loc[combined_top, sales_data_filtered.columns[0]]
}).fillna(0)
combined_data_sorted = combined_data.sort_values(by=['units', 'sales'], ascending=False)
non_zero_manufacturers = combined_data_sorted[combined_data_sorted['units'] > 0]
if len(non_zero_manufacturers) < 3:
zero_manufacturers = combined_data_sorted[combined_data_sorted['units'] == 0].head(3 - len(non_zero_manufacturers))
manufacturers_to_show = pd.concat([non_zero_manufacturers, zero_manufacturers])
else:
manufacturers_to_show = non_zero_manufacturers
values = manufacturers_to_show['units'].tolist()
amounts = manufacturers_to_show['sales'].tolist()
manufacturers = [get_supplier_name(m) for m in manufacturers_to_show.index]
if manufacturers:
fig = radar_chart(manufacturers, values, amounts, f'Gráfico de radar para los {len(manufacturers)} principales fabricantes del cliente {customer_code}')
st.pyplot(fig)
# Column 2: Alerts and additional analysis
with col2:
st.subheader("¡Puede que tengas que revisar esto!")
st.warning("Se esperaba que tu cliente comprara más productos de las siguientes marcas:")
# Group results by manufacturer to calculate the total predicted and actual sales
grouped_results = results.groupby('marca_id_encoded').agg({
'ventas_reales': 'sum',
'ventas_predichas': 'sum'
}).reset_index()
# Identify manufacturers that didn't meet predicted sales
underperforming_manufacturers = grouped_results[grouped_results['ventas_reales'] < grouped_results['ventas_predichas']].copy()
if not underperforming_manufacturers.empty:
# Calculate the missed amount
underperforming_manufacturers['missed_amount'] = underperforming_manufacturers['ventas_predichas'] - underperforming_manufacturers['ventas_reales']
# Sort by the highest missed amount
underperforming_manufacturers = underperforming_manufacturers.sort_values(by='missed_amount', ascending=False)
# Limit to top 10 missed amounts
top_misses = underperforming_manufacturers.head(10)
# Display two cards per row
for i in range(0, len(top_misses), 2):
cols = st.columns(2) # Create two columns for two cards in a row
for j, col in enumerate(cols):
if i + j < len(top_misses):
row = top_misses.iloc[i + j]
manufacturer_name = get_supplier_name_encoded(row['marca_id_encoded'])
predicted = row['ventas_predichas']
actual = row['ventas_reales']
missed = row['missed_amount']
# Use st.metric for compact display in each column
with col:
st.metric(
label=f"{manufacturer_name}",
value=f"{actual:.2f}€",
delta=f"Missed by {missed:.2f}€",
delta_color="inverse"
)
else:
st.success("All manufacturers have met or exceeded predicted sales.")
# # Gráfico adicional: Comparar las ventas predichas y reales para los principales fabricantes
# st.markdown("### Predicted vs Actual Sales for Top Manufacturers")
# top_manufacturers = results.groupby('marca_id_encoded').agg({'ventas_reales': 'sum', 'ventas_predichas': 'sum'}).sort_values(by='ventas_reales', ascending=False).head(10)
# fig_comparison = go.Figure()
# fig_comparison.add_trace(go.Bar(x=top_manufacturers.index, y=top_manufacturers['ventas_reales'], name="Actual Sales", marker_color='blue'))
# fig_comparison.add_trace(go.Bar(x=top_manufacturers.index, y=top_manufacturers['ventas_predichas'], name="Predicted Sales", marker_color='orange'))
# fig_comparison.update_layout(
# title="Actual vs Predicted Sales by Top Manufacturers",
# xaxis_title="Manufacturer",
# yaxis_title="Sales (€)",
# barmode='group',
# height=400,
# hovermode="x unified"
# )
# st.plotly_chart(fig_comparison, use_container_width=True)
# Gráfico de ventas anuales
ventas_clientes['codigo_cliente'] = ventas_clientes['codigo_cliente'].astype(str).str.strip()
sales_columns = ['VENTA_2021', 'VENTA_2022', 'VENTA_2023']
if all(col in ventas_clientes.columns for col in sales_columns):
customer_sales_data = ventas_clientes[ventas_clientes['codigo_cliente'] == customer_code]
if not customer_sales_data.empty:
customer_sales = customer_sales_data[sales_columns].values[0]
years = ['2021', '2022', '2023']
# Convert 'fecha_mes' to datetime format if it's not already
if not pd.api.types.is_datetime64_any_dtype(results['fecha_mes']):
results['fecha_mes'] = pd.to_datetime(results['fecha_mes'], errors='coerce')
# Add the 2024 actual and predicted data
if 'ventas_predichas' in results.columns and 'ventas_reales' in results.columns:
actual_sales_2024 = results[results['fecha_mes'].dt.year == 2024]['ventas_reales'].sum()
predicted_sales_2024 = results[results['fecha_mes'].dt.year == 2024]['ventas_predichas'].sum()
# Assuming only 9 months of actual data are available, annualize the sales
months_available = 9
actual_sales_2024_annual = (actual_sales_2024 / months_available) * 12
# Prepare data for the bar chart
sales_values = list(customer_sales) + [actual_sales_2024_annual]
predicted_values = list(customer_sales) + [predicted_sales_2024]
years.append('2024')
# Create the bar chart for historical and 2024 data
fig_sales_bar = go.Figure()
fig_sales_bar.add_trace(go.Bar(
x=years[:3],
y=sales_values[:3],
name="Historical Sales",
marker_color='blue'
))
fig_sales_bar.add_trace(go.Bar(
x=[years[3]],
y=[sales_values[3]],
name="2024 Actual Sales (Annualized)",
marker_color='green'
))
fig_sales_bar.add_trace(go.Bar(
x=[years[3]],
y=[predicted_values[3]],
name="2024 Predicted Sales",
marker_color='orange'
))
# Customize layout
fig_sales_bar.update_layout(
title=f"Ventas anuales de tu cliente",
xaxis_title="Year",
yaxis_title="Sales (€)",
barmode='group',
height=600,
legend_title_text="Sales Type",
hovermode="x unified"
)
# Display the chart
st.plotly_chart(fig_sales_bar, use_container_width=True)
else:
st.warning(f"No predicted or actual data found for customer {customer_code} for 2024.")
# Customer Recommendations Page
elif page == "💡 Recomendación de Artículos":
# Carga de CSV necesarios cestas y productos
cestas = pd.read_csv('cestas.csv')
productos = pd.read_csv('productos.csv')
# Estilo principal de la página
st.markdown(
"<h1 style='text-align: center;'>Recomendación de Artículos</h1>",
unsafe_allow_html=True
)
st.markdown("""
<p style='text-align: center; color: #5D6D7E;'>Obtén recomendaciones personalizadas para tus clientes basadas en su cesta de compra.</p>
""", unsafe_allow_html=True)
st.write("### Selecciona los artículos y asigna las cantidades para la cesta:")
# Añadir separador para mejorar la segmentación visual
st.divider()
# Mostrar lista de artículos disponibles (ahora se usa el código asociado a cada descripción)
available_articles = productos[['ARTICULO', 'DESCRIPCION']].drop_duplicates()
# Crear diccionario para asignar las descripciones a los códigos
article_dict = dict(zip(available_articles['DESCRIPCION'], available_articles['ARTICULO']))
# Permitir seleccionar las descripciones, pero trabajar con los códigos
selected_descriptions = st.multiselect("Select Articles", available_articles['DESCRIPCION'].unique())
quantities = {}
if selected_descriptions:
st.write("### Selecciona los artículos y las unidades:")
for description in selected_descriptions:
code = article_dict[description] # Usar el código del artículo
col1, col2 = st.columns([1, 3]) # Ajustar proporciones para que las cantidades vayan a la izquierda
with col1:
# Caja de número para la cantidad, asociada al código
quantities[code] = st.number_input(f"Quantity {code}", min_value=0, step=1, key=code)
with col2:
# Mostrar la descripción del artículo
st.write(description)
# Añadir un botón estilizado "Calcular" con icono
if st.button("🛒 Obtener Recomendaciones"):
# Crear una lista de artículos basada en los códigos y cantidades
new_basket = []
for code in quantities:
quantity = quantities[code]
if quantity > 0:
new_basket.extend([code] * quantity) # Añadir el código tantas veces como 'quantity'
if new_basket:
# Procesar la lista para recomendar
recommendations_df = recomienda_tf(new_basket, cestas, productos)
if not recommendations_df.empty:
st.success("### Según tu cesta, te recomendamos que consideres añadir uno de estos artículos:")
st.dataframe(recommendations_df, height=300, width=800) # Ajustar el tamaño del DataFrame
else:
st.warning("⚠️ No recommendations found for the provided basket.")
else:
st.warning("⚠️ Please select at least one article and set its quantity.")
# # Customer Recommendations Page
# elif page == "Articles Recommendations":
# st.title("Articles Recommendations")
# st.markdown("""
# Get tailored recommendations for your customers based on their basket.
# """)
# st.write("Select items and assign quantities for the basket:")
# # Mostrar lista de artículos disponibles
# available_articles = productos['ARTICULO'].unique()
# selected_articles = st.multiselect("Select Articles", available_articles)
# # Crear inputs para ingresar las cantidades de cada artículo seleccionado
# quantities = {}
# for article in selected_articles:
# quantities[article] = st.number_input(f"Quantity for {article}", min_value=0, step=1)
# if st.button("Calcular"): # Añadimos el botón "Calcular"
# # Crear una lista de artículos basada en la selección de códigos y cantidades
# new_basket = []
# for article in selected_articles:
# quantity = quantities[article]
# if quantity > 0:
# new_basket.extend([article] * quantity) # Añadir el código 'article' tantas veces como 'quantity'
# if new_basket:
# # Procesar la lista para recomendar
# recommendations_df = recomienda_tfid(new_basket)
# if not recommendations_df.empty:
# st.write("### Recommendations based on the current basket:")
# st.dataframe(recommendations_df)
# else:
# st.warning("No recommendations found for the provided basket.")
# else:
# st.warning("Please select at least one article and set its quantity.")