VicGerardoPR commited on
Commit
ce5036a
·
verified ·
1 Parent(s): f3dcadd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +169 -100
app.py CHANGED
@@ -31,47 +31,6 @@ def load_data():
31
  else:
32
  data = pd.read_excel(uploaded_file)
33
 
34
- # Procesar la columna de fecha HML_TXDATE (que es categórica)
35
- if 'HML_TXDATE' in data.columns:
36
- # Convertir directamente la columna categórica a valores de día de la semana
37
- # Mapeamos cada valor posible a su día de la semana correspondiente
38
- dia_semana_map = {
39
- '1': 'Lunes',
40
- '2': 'Martes',
41
- '3': 'Miércoles',
42
- '4': 'Jueves',
43
- '5': 'Viernes',
44
- '6': 'Sábado',
45
- '7': 'Domingo'
46
- }
47
-
48
- # Suponiendo que HML_TXDATE contiene valores numéricos del 1-7 o texto que puede convertirse
49
- try:
50
- # Intentar limpiar y convertir a texto simple
51
- data['DIA_NUMERO'] = data['HML_TXDATE'].astype(str).str.extract(r'(\d+)').fillna('1')
52
- # Mapear a nombres de días
53
- data['DIA_SEMANA_ESP'] = data['DIA_NUMERO'].map(dia_semana_map)
54
- # Si no existe el mapeo, usar Lunes como valor predeterminado
55
- data['DIA_SEMANA_ESP'] = data['DIA_SEMANA_ESP'].fillna('Lunes')
56
-
57
- # Crear columna de semana (puede ser arbitraria si no hay info real de semana)
58
- if 'SEMANA' not in data.columns:
59
- data['SEMANA'] = 1 # Valor predeterminado
60
-
61
- # Crear columna de fecha solo (arbitraria si no hay info real)
62
- if 'FECHA_SOLO' not in data.columns:
63
- data['FECHA_SOLO'] = data['DIA_SEMANA_ESP']
64
-
65
- st.success("✅ Columna de fecha 'HML_TXDATE' procesada como día de la semana")
66
-
67
- except Exception as e:
68
- st.warning(f"Error al procesar la columna HML_TXDATE: {e}")
69
- st.info("Procesando la columna como texto directo")
70
- # Usar directamente el valor como nombre del día
71
- data['DIA_SEMANA_ESP'] = data['HML_TXDATE']
72
- data['SEMANA'] = 1 # Valor predeterminado
73
- data['FECHA_SOLO'] = data['HML_TXDATE']
74
-
75
  return data
76
  except Exception as e:
77
  st.error(f"Error al cargar el archivo: {e}")
@@ -79,36 +38,17 @@ def load_data():
79
  return None
80
 
81
  # Función para generar gráficos basados en conteos por persona y día/semana
82
- def create_person_performance_plot(data, person_col, time_unit, metric_col=None, plot_type="Barras"):
83
  if person_col not in data.columns:
84
  st.error(f"La columna de persona '{person_col}' no existe en el dataset")
85
  return None
86
 
87
- fig, ax = plt.subplots(figsize=(12, 6))
88
-
89
- # Determinar la columna de tiempo según la unidad seleccionada
90
- if time_unit == "Día":
91
- if 'FECHA_SOLO' not in data.columns:
92
- st.error("La columna de fecha diaria no está disponible. Asegúrate de que la fecha fue procesada correctamente.")
93
- return None
94
- time_col = 'FECHA_SOLO'
95
- time_label = 'Fecha'
96
- elif time_unit == "Día de semana":
97
- if 'DIA_SEMANA_ESP' not in data.columns:
98
- st.error("La columna de día de semana no está disponible. Asegúrate de que la fecha fue procesada correctamente.")
99
- return None
100
- time_col = 'DIA_SEMANA_ESP'
101
- time_label = 'Día de semana'
102
- elif time_unit == "Semana":
103
- if 'SEMANA' not in data.columns:
104
- st.error("La columna de semana no está disponible. Asegúrate de que la fecha fue procesada correctamente.")
105
- return None
106
- time_col = 'SEMANA'
107
- time_label = 'Semana'
108
- else:
109
- st.error(f"Unidad de tiempo '{time_unit}' no reconocida")
110
  return None
111
 
 
 
112
  # Contar unidades o usar la métrica especificada
113
  if metric_col is None or metric_col == 'Conteo':
114
  # Agrupar y contar filas
@@ -117,33 +57,52 @@ def create_person_performance_plot(data, person_col, time_unit, metric_col=None,
117
  # Agrupar y sumar la métrica especificada
118
  performance_data = data.groupby([time_col, person_col])[metric_col].sum().reset_index(name='Unidades')
119
 
120
- # Asegurarse de que el dataframe tiene las columnas necesarias
121
- if set([time_col, person_col, 'Unidades']).issubset(performance_data.columns):
122
- # Crear visualización según el tipo de gráfico
123
- if plot_type == "Barras":
124
- sns.barplot(x=time_col, y='Unidades', hue=person_col, data=performance_data, ax=ax)
125
- plt.title(f"Unidades por {time_label} y Persona")
126
- plt.xlabel(time_label)
127
- plt.ylabel('Unidades')
128
- plt.xticks(rotation=45, ha='right')
129
- plt.legend(title="Persona")
130
- elif plot_type == "Líneas":
131
- sns.lineplot(x=time_col, y='Unidades', hue=person_col, data=performance_data, marker='o', ax=ax)
132
- plt.title(f"Tendencia de Unidades por {time_label} y Persona")
133
- plt.xlabel(time_label)
134
- plt.ylabel('Unidades')
135
- plt.xticks(rotation=45, ha='right')
136
- plt.legend(title="Persona")
137
- else:
138
- plt.text(0.5, 0.5, "No se pudieron generar los datos para la visualización",
139
- ha='center', va='center', transform=ax.transAxes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  plt.tight_layout()
142
  return fig
143
 
144
  # Función para generar gráficos básicos basados en conteos
145
  def create_count_plot(data, x_col, plot_type):
146
- fig, ax = plt.subplots(figsize=(12, 6))
147
 
148
  # Preparar datos de conteo
149
  if x_col in data.columns:
@@ -166,10 +125,20 @@ def create_count_plot(data, x_col, plot_type):
166
  plt.title(f"Frecuencia de valores - {x_col}")
167
 
168
  # Crear gráfico de barras
169
- sns.barplot(x=x_col, y='count', data=count_data, ax=ax)
 
 
 
 
 
 
 
 
 
 
170
  plt.xticks(rotation=45, ha='right')
171
  plt.ylabel('Frecuencia')
172
- plt.tight_layout()
173
 
174
  elif plot_type == "Pastel":
175
  # Contar valores
@@ -184,17 +153,32 @@ def create_count_plot(data, x_col, plot_type):
184
  else:
185
  plt.title(f"Distribución de valores - {x_col}")
186
 
 
 
 
 
 
 
187
  # Crear gráfico de pastel
188
- counts.plot.pie(autopct='%1.1f%%', ax=ax)
 
 
 
 
 
189
  plt.ylabel('')
190
 
191
  elif plot_type == "Histograma":
192
  # Solo aplicable a datos numéricos
193
  if pd.api.types.is_numeric_dtype(valid_data):
 
194
  sns.histplot(valid_data, kde=True, ax=ax)
 
 
 
195
  plt.xlabel(x_col)
196
  plt.ylabel('Frecuencia')
197
- plt.title(f"Distribución de {x_col}")
198
  else:
199
  plt.text(0.5, 0.5, "El histograma solo es aplicable a datos numéricos",
200
  ha='center', va='center', transform=ax.transAxes)
@@ -249,6 +233,90 @@ def main():
249
  col_type_df = pd.DataFrame(list(col_types.items()), columns=['Columna', 'Tipo'])
250
  st.dataframe(col_type_df)
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  # Pestaña para diferentes tipos de visualización
253
  tab1, tab2 = st.tabs(["Visualización por Persona/Tiempo", "Visualización Simple"])
254
 
@@ -256,21 +324,19 @@ def main():
256
  with tab1:
257
  st.subheader("Rendimiento por Persona y Tiempo")
258
 
259
- col1, col2, col3, col4 = st.columns(4)
260
 
261
  with col1:
262
  # Seleccionar columna de persona
263
  person_col = st.selectbox(
264
  "Selecciona la columna de Persona",
265
- [col for col in all_cols if col_types.get(col) == "Texto/Categórico"]
266
  )
267
 
268
  with col2:
269
- # Seleccionar unidad de tiempo
270
- time_unit = st.selectbox(
271
- "Selecciona la unidad de tiempo",
272
- ["Día", "Día de semana", "Semana"]
273
- )
274
 
275
  with col3:
276
  # Seleccionar tipo de gráfico
@@ -279,7 +345,10 @@ def main():
279
  ["Barras", "Líneas"]
280
  )
281
 
282
- with col4:
 
 
 
283
  # Seleccionar columna métrica (opcional)
284
  metric_options = ["Conteo"] + [col for col in all_cols if col_types.get(col) == "Numérico"]
285
  metric_col = st.selectbox("Métrica a medir", metric_options)
@@ -292,7 +361,7 @@ def main():
292
  fig = create_person_performance_plot(
293
  data,
294
  person_col,
295
- time_unit,
296
  metric_col,
297
  plot_type
298
  )
@@ -303,7 +372,7 @@ def main():
303
  st.markdown(
304
  get_image_download_link(
305
  fig,
306
- f"Rendimiento_{person_col}_{time_unit}",
307
  "📥 Descargar imagen"
308
  ),
309
  unsafe_allow_html=True
 
31
  else:
32
  data = pd.read_excel(uploaded_file)
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return data
35
  except Exception as e:
36
  st.error(f"Error al cargar el archivo: {e}")
 
38
  return None
39
 
40
  # Función para generar gráficos basados en conteos por persona y día/semana
41
+ def create_person_performance_plot(data, person_col, time_col, metric_col=None, plot_type="Barras"):
42
  if person_col not in data.columns:
43
  st.error(f"La columna de persona '{person_col}' no existe en el dataset")
44
  return None
45
 
46
+ if time_col not in data.columns:
47
+ st.error(f"La columna de tiempo '{time_col}' no existe en el dataset")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  return None
49
 
50
+ fig, ax = plt.subplots(figsize=(14, 8))
51
+
52
  # Contar unidades o usar la métrica especificada
53
  if metric_col is None or metric_col == 'Conteo':
54
  # Agrupar y contar filas
 
57
  # Agrupar y sumar la métrica especificada
58
  performance_data = data.groupby([time_col, person_col])[metric_col].sum().reset_index(name='Unidades')
59
 
60
+ # Calcular totales por persona para añadir a la leyenda
61
+ totales_persona = performance_data.groupby(person_col)['Unidades'].sum().reset_index()
62
+ totales_persona['Etiqueta'] = totales_persona.apply(lambda x: f"{x[person_col]} (Total: {x['Unidades']})", axis=1)
63
+
64
+ # Fusionar con el dataframe principal
65
+ performance_data = pd.merge(performance_data, totales_persona[[person_col, 'Etiqueta']], on=person_col)
66
+
67
+ # Crear visualización según el tipo de gráfico
68
+ if plot_type == "Barras":
69
+ chart = sns.barplot(x=time_col, y='Unidades', hue='Etiqueta', data=performance_data, ax=ax)
70
+
71
+ # Añadir etiquetas con los valores en cada barra
72
+ for container in chart.containers:
73
+ chart.bar_label(container, fmt='%d')
74
+
75
+ plt.title(f"Unidades por {time_col} y Persona")
76
+ plt.xlabel(time_col)
77
+ plt.ylabel('Unidades')
78
+ plt.xticks(rotation=45, ha='right')
79
+ plt.legend(title="Persona (Total)")
80
+
81
+ elif plot_type == "Líneas":
82
+ chart = sns.lineplot(x=time_col, y='Unidades', hue='Etiqueta', data=performance_data, marker='o', ax=ax)
83
+
84
+ # Añadir etiquetas con los valores en cada punto
85
+ for line in ax.lines:
86
+ if len(line.get_xdata()) > 0: # Verificar que la línea tiene datos
87
+ for x, y in zip(line.get_xdata(), line.get_ydata()):
88
+ ax.text(x, y, f'{int(y)}', ha='center', va='bottom')
89
+
90
+ plt.title(f"Tendencia de Unidades por {time_col} y Persona")
91
+ plt.xlabel(time_col)
92
+ plt.ylabel('Unidades')
93
+ plt.xticks(rotation=45, ha='right')
94
+ plt.legend(title="Persona (Total)")
95
+
96
+ # Añadir el total general en el título
97
+ total_general = performance_data['Unidades'].sum()
98
+ plt.title(f"{plt.gca().get_title()} - Total General: {total_general}")
99
 
100
  plt.tight_layout()
101
  return fig
102
 
103
  # Función para generar gráficos básicos basados en conteos
104
  def create_count_plot(data, x_col, plot_type):
105
+ fig, ax = plt.subplots(figsize=(14, 8))
106
 
107
  # Preparar datos de conteo
108
  if x_col in data.columns:
 
125
  plt.title(f"Frecuencia de valores - {x_col}")
126
 
127
  # Crear gráfico de barras
128
+ chart = sns.barplot(x=x_col, y='count', data=count_data, ax=ax)
129
+
130
+ # Añadir etiquetas con el número exacto y porcentaje
131
+ total = count_data['count'].sum()
132
+ for i, p in enumerate(chart.patches):
133
+ height = p.get_height()
134
+ percentage = (height/total) * 100
135
+ chart.annotate(f'{int(height)} ({percentage:.1f}%)',
136
+ (p.get_x() + p.get_width() / 2., height),
137
+ ha='center', va='bottom')
138
+
139
  plt.xticks(rotation=45, ha='right')
140
  plt.ylabel('Frecuencia')
141
+ plt.title(f"{plt.gca().get_title()} - Total: {total}")
142
 
143
  elif plot_type == "Pastel":
144
  # Contar valores
 
153
  else:
154
  plt.title(f"Distribución de valores - {x_col}")
155
 
156
+ # Calcular porcentajes
157
+ total = counts.sum()
158
+
159
+ # Crear etiquetas con número y porcentaje
160
+ etiquetas = [f'{k}: {v} ({v/total:.1%})' for k, v in counts.items()]
161
+
162
  # Crear gráfico de pastel
163
+ wedges, texts = ax.pie(counts, wedgeprops={'edgecolor': 'w'})
164
+
165
+ # Añadir leyenda con cantidades
166
+ ax.legend(wedges, etiquetas, title=f"Total: {total}",
167
+ loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
168
+
169
  plt.ylabel('')
170
 
171
  elif plot_type == "Histograma":
172
  # Solo aplicable a datos numéricos
173
  if pd.api.types.is_numeric_dtype(valid_data):
174
+ # Crear histograma
175
  sns.histplot(valid_data, kde=True, ax=ax)
176
+
177
+ # Añadir información sobre el total
178
+ total = len(valid_data)
179
  plt.xlabel(x_col)
180
  plt.ylabel('Frecuencia')
181
+ plt.title(f"Distribución de {x_col} - Total: {total}")
182
  else:
183
  plt.text(0.5, 0.5, "El histograma solo es aplicable a datos numéricos",
184
  ha='center', va='center', transform=ax.transAxes)
 
233
  col_type_df = pd.DataFrame(list(col_types.items()), columns=['Columna', 'Tipo'])
234
  st.dataframe(col_type_df)
235
 
236
+ # Sección para procesar columnas de fecha
237
+ st.subheader("Procesamiento de Columnas de Fecha")
238
+
239
+ # Seleccionar columna para procesar como fecha
240
+ date_col = st.selectbox(
241
+ "Selecciona una columna para procesar como fecha",
242
+ ["Ninguna"] + [col for col in all_cols if col_types.get(col) == "Texto/Categórico"]
243
+ )
244
+
245
+ # Si se seleccionó una columna
246
+ if date_col != "Ninguna":
247
+ # Seleccionar tipo de procesamiento
248
+ date_process_type = st.radio(
249
+ "¿Cómo quieres procesar esta columna?",
250
+ ["Como día de semana (1-7)", "Como fecha con formato"]
251
+ )
252
+
253
+ if date_process_type == "Como día de semana (1-7)":
254
+ # Mapeo para días de la semana
255
+ dia_semana_map = {
256
+ '1': 'Lunes',
257
+ '2': 'Martes',
258
+ '3': 'Miércoles',
259
+ '4': 'Jueves',
260
+ '5': 'Viernes',
261
+ '6': 'Sábado',
262
+ '7': 'Domingo'
263
+ }
264
+
265
+ try:
266
+ # Extraer números de la columna
267
+ data['DIA_NUMERO'] = data[date_col].astype(str).str.extract(r'(\d+)').fillna('1')
268
+ # Mapear a nombres de días
269
+ data['DIA_SEMANA'] = data['DIA_NUMERO'].map(dia_semana_map)
270
+ # Si no existe el mapeo, usar Lunes como valor predeterminado
271
+ data['DIA_SEMANA'] = data['DIA_SEMANA'].fillna('Lunes')
272
+
273
+ st.success(f"✅ Columna '{date_col}' procesada como día de semana")
274
+
275
+ except Exception as e:
276
+ st.error(f"Error al procesar la columna como día de semana: {e}")
277
+
278
+ elif date_process_type == "Como fecha con formato":
279
+ # Permitir al usuario especificar el formato
280
+ date_format = st.text_input(
281
+ "Ingresa el formato de fecha",
282
+ value="%Y/%m/%d %H:%M:%S",
283
+ help="Ejemplos: %Y/%m/%d para aaaa/mm/dd, %d/%m/%Y para dd/mm/aaaa, %Y-%m-%d %H:%M:%S para aaaa-mm-dd HH:MM:SS"
284
+ )
285
+
286
+ try:
287
+ # Convertir a datetime con el formato especificado
288
+ data['FECHA_DATETIME'] = pd.to_datetime(data[date_col], format=date_format, errors='coerce')
289
+
290
+ if not data['FECHA_DATETIME'].isna().all():
291
+ # Extraer componentes
292
+ data['DIA_SEMANA'] = data['FECHA_DATETIME'].dt.day_name()
293
+ data['FECHA_SOLO'] = data['FECHA_DATETIME'].dt.date
294
+ data['SEMANA'] = data['FECHA_DATETIME'].dt.isocalendar().week
295
+ data['MES'] = data['FECHA_DATETIME'].dt.month
296
+ data['AÑO'] = data['FECHA_DATETIME'].dt.year
297
+
298
+ # Crear una columna con el formato español
299
+ dias_esp = {
300
+ 'Monday': 'Lunes',
301
+ 'Tuesday': 'Martes',
302
+ 'Wednesday': 'Miércoles',
303
+ 'Thursday': 'Jueves',
304
+ 'Friday': 'Viernes',
305
+ 'Saturday': 'Sábado',
306
+ 'Sunday': 'Domingo'
307
+ }
308
+ data['DIA_SEMANA'] = data['DIA_SEMANA'].map(dias_esp)
309
+
310
+ st.success(f"✅ Columna '{date_col}' procesada como fecha con formato: {date_format}")
311
+ else:
312
+ st.error("No se pudo convertir ningún valor. Verifica el formato.")
313
+
314
+ except Exception as e:
315
+ st.error(f"Error al procesar la columna como fecha: {e}")
316
+
317
+ # Actualizar la lista de columnas disponibles después del procesamiento
318
+ all_cols = data.columns.tolist()
319
+
320
  # Pestaña para diferentes tipos de visualización
321
  tab1, tab2 = st.tabs(["Visualización por Persona/Tiempo", "Visualización Simple"])
322
 
 
324
  with tab1:
325
  st.subheader("Rendimiento por Persona y Tiempo")
326
 
327
+ col1, col2, col3 = st.columns(3)
328
 
329
  with col1:
330
  # Seleccionar columna de persona
331
  person_col = st.selectbox(
332
  "Selecciona la columna de Persona",
333
+ [col for col in all_cols if col_types.get(col) == "Texto/Categórico" or col == "DIA_SEMANA"]
334
  )
335
 
336
  with col2:
337
+ # Seleccionar columna de tiempo
338
+ time_options = ["DIA_SEMANA", "SEMANA", "MES", "AÑO", "FECHA_SOLO"] + all_cols
339
+ time_col = st.selectbox("Selecciona la columna de Tiempo", time_options)
 
 
340
 
341
  with col3:
342
  # Seleccionar tipo de gráfico
 
345
  ["Barras", "Líneas"]
346
  )
347
 
348
+ # Opciones adicionales
349
+ col1, col2 = st.columns(2)
350
+
351
+ with col1:
352
  # Seleccionar columna métrica (opcional)
353
  metric_options = ["Conteo"] + [col for col in all_cols if col_types.get(col) == "Numérico"]
354
  metric_col = st.selectbox("Métrica a medir", metric_options)
 
361
  fig = create_person_performance_plot(
362
  data,
363
  person_col,
364
+ time_col,
365
  metric_col,
366
  plot_type
367
  )
 
372
  st.markdown(
373
  get_image_download_link(
374
  fig,
375
+ f"Rendimiento_{person_col}_{time_col}",
376
  "📥 Descargar imagen"
377
  ),
378
  unsafe_allow_html=True