luisbv commited on
Commit
d2725da
·
1 Parent(s): a8d1eb6
Files changed (2) hide show
  1. main.py +396 -0
  2. requirements.txt +11 -0
main.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from plotly.subplots import make_subplots
7
+ from prophet import Prophet
8
+ from prophet.plot import plot_plotly, plot_components_plotly
9
+ from datetime import datetime
10
+ import statsmodels.api as sm
11
+ from datetime import datetime
12
+ import io
13
+ # Configuración de la página
14
+ st.set_page_config(page_title="Analytics Dashboard", layout="wide", page_icon="📈")
15
+
16
+
17
+ if 'df' not in st.session_state:
18
+ st.session_state.df = pd.DataFrame()
19
+
20
+ # Función para cargar datos
21
+ @st.cache_data
22
+ def load_data(uploaded_file):
23
+ try:
24
+ if uploaded_file.name.endswith('.csv'):
25
+ # Para CSV, detectar automáticamente el separador
26
+ content = uploaded_file.read().decode('utf-8')
27
+ dialect = io.StringIO(content[:1024]).read()
28
+ sniffer = pd.io.parsing.Sniffer()
29
+ delimiter = sniffer.sniff(dialect).delimiter
30
+
31
+ uploaded_file.seek(0) # Resetear el puntero del archivo
32
+ df = pd.read_csv(uploaded_file, delimiter=delimiter)
33
+ else:
34
+ # Para Excel
35
+ df = pd.read_excel(uploaded_file)
36
+
37
+ # Convertir automáticamente columnas de fecha
38
+ df = df.apply(lambda col: pd.to_datetime(col, errors='ignore')
39
+ if col.dtypes == object else col)
40
+ return df
41
+ except Exception as e:
42
+ st.error(f"Error al cargar el archivo: {e}")
43
+ return None
44
+
45
+ # Función para combinar columnas de fecha
46
+ def create_date_column(df):
47
+ date_cols = st.columns(3)
48
+ with date_cols[0]:
49
+ year_col = st.selectbox("Selecciona columna de AÑO", df.columns)
50
+ with date_cols[1]:
51
+ month_col = st.selectbox("Selecciona columna de MES", df.columns[df.columns != year_col])
52
+ with date_cols[2]:
53
+ condition = [(column != year_col and column != month_col) for column in df.columns]
54
+ day_col = st.selectbox("Selecciona columna de DÍA", df.columns[condition])
55
+
56
+ if st.button("Crear fecha combinada"):
57
+ try:
58
+ # Convertir a enteros primero para mayor seguridad
59
+ df['year'] = df[year_col].astype(int)
60
+ df['month'] = df[month_col].astype(int)
61
+ df['day'] = df[day_col].astype(int)
62
+
63
+ # Crear la columna de fecha
64
+ df['ds'] = pd.to_datetime(df[['year', 'month', 'day']])
65
+
66
+ # Mantener solo la columna resultante
67
+ df = df.drop(['year', 'month', 'day'], axis=1)
68
+
69
+ st.session_state.df = df
70
+ st.success("Fecha creada exitosamente! Muestra de fechas:")
71
+ st.dataframe(st.session_state.df[['ds']].head())
72
+ except Exception as e:
73
+ st.error(f"Error al crear fecha: {str(e)}")
74
+ return df
75
+
76
+ # Función para descomposición de series temporales
77
+ def plot_decomposition(df, date_col, value_col):
78
+ st.header("📈 Descomposición de Series Temporales")
79
+
80
+ col1, col2 = st.columns(2)
81
+ with col1:
82
+ decomposition_model = st.selectbox("Modelo de descomposición", ["aditiva", "multiplicativa"])
83
+ with col2:
84
+ period = st.number_input("Periodo estacional", min_value=2, max_value=365, value=12)
85
+
86
+ if st.button("Analizar componentes"):
87
+ try:
88
+ ts = df.set_index(date_col)[value_col].sort_index()
89
+ decomposition = sm.tsa.seasonal_decompose(ts.dropna(),
90
+ model=decomposition_model,
91
+ period=period)
92
+
93
+ fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.1,
94
+ subplot_titles=("Serie Original", "Tendencia",
95
+ "Estacionalidad", "Residuales"))
96
+
97
+ # Serie Original
98
+ fig.add_trace(go.Scatter(x=ts.index, y=ts, mode='lines', name="Original",
99
+ line=dict(color='#1f77b4')), row=1, col=1)
100
+
101
+ # Tendencia
102
+ fig.add_trace(go.Scatter(x=decomposition.trend.index, y=decomposition.trend,
103
+ mode='lines', name="Tendencia", line=dict(color='#ff7f0e')),
104
+ row=2, col=1)
105
+
106
+ # Estacionalidad
107
+ fig.add_trace(go.Scatter(x=decomposition.seasonal.index, y=decomposition.seasonal,
108
+ mode='lines', name="Estacionalidad", line=dict(color='#2ca02c')),
109
+ row=3, col=1)
110
+
111
+ # Residuales
112
+ fig.add_trace(go.Scatter(x=decomposition.resid.index, y=decomposition.resid,
113
+ mode='lines', name="Residuales", line=dict(color='#d62728')),
114
+ row=4, col=1)
115
+
116
+ fig.update_layout(title_text=f"Descomposición de {value_col}",
117
+ height=800, showlegend=False,
118
+ hovermode="x unified")
119
+ st.plotly_chart(fig, use_container_width=True)
120
+
121
+ except Exception as e:
122
+ st.error(f"Error en descomposición: {str(e)}")
123
+
124
+ # Análisis de datos
125
+ def perform_analysis(df):
126
+ with st.expander("🔍 Análisis Rápido", expanded=True):
127
+ c1, c2, c3 = st.columns(3)
128
+ with c1:
129
+ st.metric("Registros", df.shape[0])
130
+ with c2:
131
+ st.metric("Variables", df.shape[1])
132
+ with c3:
133
+ missing = df.isna().sum().sum()
134
+ st.metric("Valores Faltantes", missing)
135
+ # Análisis estadístico
136
+ with st.expander("📊 Estadísticas Descriptivas"):
137
+ st.dataframe(df.describe().T.style.background_gradient(cmap='Blues'))
138
+
139
+ with st.expander("📊 Vista General", expanded=True):
140
+
141
+ date_col = 'ds'
142
+ values = [column for column in df.select_dtypes(include=np.number).columns if column not in ['ds', "Año", "Mes", "Día", "Dia"]]
143
+ value_col = st.selectbox("Seleccionar variable para análisis",
144
+ values)
145
+ plot_decomposition(df, date_col, value_col)
146
+
147
+ # Análisis de correlación
148
+ with st.expander("🧮 Matriz de Correlación"):
149
+ numeric_df = df.select_dtypes(include=np.number)
150
+ if len(numeric_df.columns) > 1:
151
+ corr = numeric_df.corr()
152
+ fig = go.Figure(data=go.Heatmap(
153
+ z=corr.values,
154
+ x=corr.columns,
155
+ y=corr.index,
156
+ colorscale='RdBu',
157
+ zmin=-1,
158
+ zmax=1,
159
+ hoverongaps=True,
160
+ text=np.round(corr.values, 2)
161
+ ))
162
+ fig.update_layout(title='Matriz de Correlación',
163
+ width=800,
164
+ height=600)
165
+ st.plotly_chart(fig, use_container_width=True)
166
+ else:
167
+ st.warning("No hay suficientes columnas numéricas para calcular correlaciones")
168
+
169
+ # Visualización temporal interactiva
170
+ def time_analysis(df, date_col):
171
+ with st.expander("⏳ Análisis Temporal", expanded=True):
172
+ col1, col2 = st.columns([1, 3])
173
+ with col1:
174
+ values = [column for column in df.select_dtypes(include=np.number).columns if column not in ['ds', "Año", "Mes", "Día", "Dia"]]
175
+ value_col = st.selectbox("Seleccionar variable para análisis temporal",
176
+ values)
177
+ resample_freq = st.selectbox("Frecuencia de muestreo",
178
+ ['D', 'W', 'M', 'Q', 'Y'])
179
+ with col2:
180
+ try:
181
+ ts_df = df.set_index(date_col)[value_col]
182
+ resampled = ts_df.resample(resample_freq).mean()
183
+
184
+ fig = px.line(resampled,
185
+ title=f"Evolución Temporal - {value_col}",
186
+ labels={'value': value_col, 'index': 'Fecha'},
187
+ markers=True)
188
+ fig.update_traces(line_width=2,
189
+ hovertemplate="Fecha: %{x}<br>Valor: %{y:.2f}")
190
+ fig.update_layout(hovermode="x unified")
191
+ st.plotly_chart(fig, use_container_width=True)
192
+ except Exception as e:
193
+ st.error(f"Error en análisis temporal: {e}")
194
+
195
+ # Pronósticos con Prophet
196
+ def forecast_section(df, date_col):
197
+ st.header("🔮 Modelo Predictivo")
198
+
199
+ # Verificar y convertir a datetime si es necesario
200
+ if not np.issubdtype(df[date_col].dtype, np.datetime64):
201
+ df[date_col] = pd.to_datetime(df[date_col], errors='coerce')
202
+
203
+ col1, col2 = st.columns(2)
204
+ with col1:
205
+ # Selección de agrupación temporal
206
+ freq_agrupacion = st.selectbox(
207
+ "Frecuencia de agrupación",
208
+ options=['Diaria', 'Semanal', 'Mensual'],
209
+ help="Agrupa los datos en intervalos temporales antes de modelar"
210
+ )
211
+
212
+ # Mapeo de frecuencias de pandas
213
+ freq_map = {
214
+ 'Diaria': 'D',
215
+ 'Semanal': 'W-MON',
216
+ 'Mensual': 'MS'
217
+ }
218
+
219
+ # Selección de método de agregación
220
+ metodo_agregacion = st.selectbox(
221
+ "Método de agregación",
222
+ options=['Suma', 'Promedio', 'Maximo', 'Minimo'],
223
+ index=1
224
+ )
225
+
226
+ with col2:
227
+ values = [column for column in df.select_dtypes(include=np.number).columns
228
+ if column not in ['ds', "Año", "Mes", "Día", "Dia"]]
229
+ target_col = st.selectbox("Variable a predecir", values)
230
+ min_value = 1
231
+ max_value = 14
232
+ value = 7
233
+ if freq_agrupacion == "Mensual":
234
+ max_value = 2
235
+ value = 1
236
+ elif freq_agrupacion == "Semanal":
237
+ max_value = 4
238
+ value = 2
239
+ periods = st.number_input(
240
+ "Periodos a predecir",
241
+ min_value=min_value,
242
+ max_value=max_value,
243
+ value=value,
244
+ help="Número de unidades temporales a pronosticar según la frecuencia seleccionada"
245
+ )
246
+
247
+ # Agrupación temporal de los datos
248
+ try:
249
+ df_temp = df.set_index(date_col)[target_col]
250
+
251
+ if metodo_agregacion == 'Suma':
252
+ df_grouped = df_temp.resample(freq_map[freq_agrupacion]).sum()
253
+ elif metodo_agregacion == 'Promedio':
254
+ df_grouped = df_temp.resample(freq_map[freq_agrupacion]).mean()
255
+ elif metodo_agregacion == 'Maximo':
256
+ df_grouped = df_temp.resample(freq_map[freq_agrupacion]).max()
257
+ elif metodo_agregacion == 'Minimo':
258
+ df_grouped = df_temp.resample(freq_map[freq_agrupacion]).min()
259
+
260
+ df_grouped = df_grouped.reset_index()
261
+ df_grouped.columns = [date_col, target_col]
262
+ df = df_grouped.dropna()
263
+
264
+ # Mostrar vista previa de los datos agrupados
265
+ with st.expander("📊 Vista de datos agrupados", expanded=False):
266
+ st.write(f"Formato actual: {freq_agrupacion} ({len(df)} registros)")
267
+ fig = px.line(df, x=date_col, y=target_col, title=f"Datos Agrupados - {target_col}")
268
+ fig.update_traces(mode='lines+markers')
269
+ st.plotly_chart(fig, use_container_width=True)
270
+
271
+ except Exception as e:
272
+ st.error(f"Error al agrupar datos: {str(e)}")
273
+ return
274
+
275
+ # Resto de la configuración del modelo
276
+
277
+ if st.button("Ejecutar Pronóstico", type="primary"):
278
+ with st.spinner("Creando modelo..."):
279
+ try:
280
+ prophet_df = df[[date_col, target_col]].rename(columns={
281
+ date_col: 'ds',
282
+ target_col: 'y'
283
+ }).dropna()
284
+
285
+ model = Prophet()
286
+
287
+
288
+ model.fit(prophet_df)
289
+
290
+ # Generar futuro considerando la frecuencia
291
+ future = model.make_future_dataframe(
292
+ periods=periods,
293
+ freq=freq_map[freq_agrupacion][0] # Tomar primer carácter (D, W, M)
294
+ )
295
+
296
+ forecast = model.predict(future)
297
+
298
+ # Resultados
299
+ st.subheader("Predicción y Componentes")
300
+ fig1 = plot_plotly(model, forecast)
301
+ fig1.update_layout(
302
+ height=600,
303
+ hovermode="x unified",
304
+ xaxis_title="Fecha",
305
+ yaxis_title=target_col,
306
+ annotations=[
307
+ dict(
308
+ text=f"Frecuencia: {freq_agrupacion} | Agregación: {metodo_agregacion}",
309
+ xref="paper",
310
+ yref="paper",
311
+ x=0.5,
312
+ y=-0.15,
313
+ showarrow=False
314
+ )
315
+ ]
316
+ )
317
+ st.plotly_chart(fig1, use_container_width=True)
318
+
319
+ st.subheader("Descomposición de Componentes")
320
+ fig2 = plot_components_plotly(model, forecast)
321
+ st.plotly_chart(fig2, use_container_width=True)
322
+
323
+ # Mostrar métricas de rendimiento
324
+ with st.expander("📈 Métricas de Rendimiento", expanded=True):
325
+ from sklearn.metrics import mean_absolute_error, mean_squared_error
326
+
327
+ merged = forecast.set_index('ds')[['yhat']].join(
328
+ prophet_df.set_index('ds')['y']
329
+ ).dropna()
330
+
331
+ mae = mean_absolute_error(merged['y'], merged['yhat'])
332
+ rmse = np.sqrt(mean_squared_error(merged['y'], merged['yhat']))
333
+
334
+ col_met1, col_met2 = st.columns(2)
335
+ with col_met1:
336
+ st.metric("MAE (Error Absoluto Medio)", f"{mae:.2f}")
337
+ with col_met2:
338
+ st.metric("RMSE (Raíz del Error Cuadrático Medio)", f"{rmse:.2f}")
339
+
340
+ except Exception as e:
341
+ st.error(f"Error en el modelo: {str(e)}")
342
+
343
+ def main():
344
+ st.title("📈 Dashboard Analítico Interactivo")
345
+
346
+ # Crear pestañas
347
+ tab1, tab2, tab3 = st.tabs(["Carga de Datos", "Análisis Exploratorio", "Pronósticos"])
348
+
349
+ with tab1:
350
+ st.header("📤 Carga de Datos")
351
+ uploaded_file = st.file_uploader("Sube tu archivo (Excel o CSV)", type=["xlsx", "xls", "csv"])
352
+
353
+ if uploaded_file:
354
+ df = load_data(uploaded_file)
355
+ if df is not None:
356
+ st.success("Datos cargados exitosamente!")
357
+
358
+ # Mostrar información básica del archivo
359
+ file_details = {
360
+ "Nombre": uploaded_file.name,
361
+ "Tipo": uploaded_file.type,
362
+ "Tamaño": f"{uploaded_file.size / 1024:.2f} KB"
363
+ }
364
+
365
+ st.json(file_details)
366
+ # Nueva sección de análisis de fecha
367
+ if 'ds' not in st.session_state.df.columns:
368
+ st.warning("⚠️ No se detectó columna de fecha. Se creará una nueva columna 'ds'")
369
+ create_date_column(df)
370
+
371
+ st.dataframe(df.head(), use_container_width=True)
372
+
373
+ if 'df' in st.session_state:
374
+
375
+ with tab2:
376
+ st.header("🔍 Análisis Exploratorio")
377
+ perform_analysis(st.session_state.df)
378
+
379
+ # Selección de columna de fecha para análisis temporal
380
+ date_cols = st.session_state.df.select_dtypes(include=['datetime', 'datetimetz']).columns
381
+ if len(date_cols) > 0:
382
+ selected_date = st.selectbox("Seleccionar columna de fecha", date_cols)
383
+ time_analysis(st.session_state.df, selected_date)
384
+ else:
385
+ st.warning("No se detectaron columnas de fecha en los datos")
386
+
387
+ with tab3:
388
+ st.header("🔮 Pronósticos con Prophet")
389
+ if len(date_cols) > 0:
390
+ selected_date = st.selectbox("Seleccionar fecha para pronóstico", date_cols)
391
+ forecast_section(st.session_state.df, selected_date)
392
+ else:
393
+ st.error("Se requiere al menos una columna de fecha para pronósticos")
394
+
395
+ if __name__ == "__main__":
396
+ main()
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # requirements.txt
2
+ streamlit>=1.22.0
3
+ pandas>=1.5.0
4
+ numpy>=1.24.0
5
+ plotly>=5.13.0
6
+ prophet>=1.1.3
7
+ xlrd>=2.0.1
8
+ openpyxl>=3.0.10
9
+ scikit-learn>=1.2.0
10
+ matplotlib>=3.7.0
11
+ tqdm>=4.65.0