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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +69 -378
app.py CHANGED
@@ -4,420 +4,111 @@ import matplotlib.pyplot as plt
4
  import seaborn as sns
5
  import io
6
  import base64
7
- from PIL import Image
8
- import numpy as np
9
- from datetime import datetime
10
 
11
- # Configuración de la página
12
- st.set_page_config(
13
- page_title="Visualizador de Datos",
14
- page_icon="📊",
15
- layout="wide"
16
- )
17
 
18
  # Título de la aplicación
19
- st.title("📊 Visualizador de Datos")
20
- st.markdown("### Carga tu archivo CSV o Excel y crea visualizaciones personalizadas")
21
 
22
- # Función para cargar el archivo y procesar fechas
23
  def load_data():
24
- uploaded_file = st.file_uploader("Carga tu archivo CSV o Excel", type=["csv", "xlsx", "xls"])
25
 
26
- if uploaded_file is not None:
 
 
 
 
 
27
  try:
28
- # Determinar el tipo de archivo y cargarlo
29
- if uploaded_file.name.endswith('.csv'):
30
- data = pd.read_csv(uploaded_file)
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}")
37
- return None
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
55
- performance_data = data.groupby([time_col, person_col]).size().reset_index(name='Unidades')
56
  else:
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:
109
- # Eliminar valores nulos para el conteo
110
- valid_data = data[x_col].dropna()
111
-
112
- if plot_type == "Barras":
113
- # Contar valores y ordenar por frecuencia
114
- count_data = valid_data.value_counts().reset_index()
115
- count_data.columns = [x_col, 'count']
116
-
117
- # Ordenar por conteo de mayor a menor
118
- count_data = count_data.sort_values('count', ascending=False)
119
-
120
- # Limitar a los 20 valores más frecuentes para mejor visualización
121
- if len(count_data) > 20:
122
- count_data = count_data.head(20)
123
- plt.title(f"Top 20 valores más frecuentes - {x_col}")
124
- else:
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
145
- counts = valid_data.value_counts()
146
-
147
- # Si hay muchos valores únicos, mostrar solo los top 10
148
- if len(counts) > 10:
149
- # Guardar el resto como "Otros"
150
- otros = pd.Series({'Otros': counts[10:].sum()})
151
- counts = pd.concat([counts[:10], otros])
152
- plt.title(f"Top 10 valores más frecuentes - {x_col}")
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)
185
- else:
186
- plt.text(0.5, 0.5, f"Columna '{x_col}' no encontrada en el dataset",
187
- ha='center', va='center', transform=ax.transAxes)
188
 
189
- plt.tight_layout()
190
  return fig
191
 
192
- # Función para descargar imágenes
193
- def get_image_download_link(fig, filename, text):
194
  buf = io.BytesIO()
195
  fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
196
  buf.seek(0)
197
  b64 = base64.b64encode(buf.read()).decode()
198
- href = f'<a href="data:image/png;base64,{b64}" download="{filename}.png">{text}</a>'
199
  return href
200
 
201
  # Función principal
202
  def main():
203
  # Cargar datos
204
- data = load_data()
205
 
206
- if data is not None:
207
- # Mostrar información básica del dataset
208
- st.subheader("Vista previa de los datos")
209
- st.dataframe(data.head())
210
 
211
- st.subheader("Información del dataset")
212
- col1, col2 = st.columns(2)
213
- with col1:
214
- st.info(f"Número de filas: {data.shape[0]}")
215
- with col2:
216
- st.info(f"Número de columnas: {data.shape[1]}")
217
 
218
- # Mostrar lista de columnas disponibles
219
- st.subheader("Columnas disponibles")
220
- all_cols = data.columns.tolist()
221
-
222
- # Identificar tipo de datos para cada columna
223
- col_types = {}
224
- for col in all_cols:
225
- if pd.api.types.is_numeric_dtype(data[col]):
226
- col_types[col] = "Numérico"
227
- elif pd.api.types.is_datetime64_dtype(data[col]):
228
- col_types[col] = "Fecha/Hora"
229
- else:
230
- col_types[col] = "Texto/Categórico"
231
-
232
- # Mostrar tipos de columnas
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
-
323
- # Pestaña 1: Visualización por Persona y Tiempo
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
343
- plot_type = st.selectbox(
344
- "Tipo de gráfico para rendimiento",
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)
355
- if metric_col == "Conteo":
356
- metric_col = None
357
-
358
- # Botón para generar visualización de rendimiento
359
- if st.button("Generar Visualización de Rendimiento"):
360
- try:
361
- fig = create_person_performance_plot(
362
- data,
363
- person_col,
364
- time_col,
365
- metric_col,
366
- plot_type
367
- )
368
- if fig:
369
- st.pyplot(fig)
370
-
371
- # Botón para descargar la imagen
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
379
- )
380
- except Exception as e:
381
- st.error(f"Error al generar el gráfico: {e}")
382
- st.info("Sugerencia: Verifica que las columnas seleccionadas existan y sean compatibles.")
383
-
384
- # Pestaña 2: Visualización Simple
385
- with tab2:
386
- st.subheader("Visualización de Conteos Simples")
387
-
388
- col1, col2 = st.columns(2)
389
-
390
- with col1:
391
- # Visualizaciones basadas en conteo
392
- plot_type = st.selectbox(
393
- "Tipo de gráfico simple",
394
- ["Barras", "Pastel", "Histograma"]
395
- )
396
-
397
- with col2:
398
- # Seleccionar columna para analizar
399
- x_col = st.selectbox("Selecciona la columna para analizar", all_cols)
400
-
401
- # Crear gráfico simple
402
- if st.button("Generar Visualización Simple"):
403
- try:
404
- st.subheader("Visualización")
405
- fig = create_count_plot(data, x_col, plot_type)
406
- st.pyplot(fig)
407
-
408
- # Botón para descargar la imagen
409
- st.markdown(
410
- get_image_download_link(
411
- fig,
412
- f"{plot_type}_{x_col}",
413
- "📥 Descargar imagen"
414
- ),
415
- unsafe_allow_html=True
416
- )
417
-
418
- except Exception as e:
419
- st.error(f"Error al generar el gráfico: {e}")
420
- st.info("Sugerencia: Verifica que la columna seleccionada sea compatible con el tipo de gráfico.")
421
 
422
  # Ejecutar la aplicación
423
  if __name__ == "__main__":
 
4
  import seaborn as sns
5
  import io
6
  import base64
 
 
 
7
 
8
+ # Configuración básica de la página
9
+ st.set_page_config(page_title="Contador Simple", page_icon="📊")
 
 
 
 
10
 
11
  # Título de la aplicación
12
+ st.title("📊 Visualizador de Conteos")
13
+ st.markdown("### Carga tus archivos CSV y genera gráficos de conteo")
14
 
15
+ # Función para cargar archivos múltiples
16
  def load_data():
17
+ uploaded_files = st.file_uploader("Carga uno o más archivos CSV", type=["csv"], accept_multiple_files=True)
18
 
19
+ if not uploaded_files:
20
+ return None
21
+
22
+ all_data = {}
23
+
24
+ for uploaded_file in uploaded_files:
25
  try:
26
+ # Cargar el archivo
27
+ df = pd.read_csv(uploaded_file)
28
+ # Guardar en el diccionario con el nombre del archivo como clave
29
+ all_data[uploaded_file.name] = df
30
+ st.success(f"✅ Archivo '{uploaded_file.name}' cargado correctamente")
 
 
31
  except Exception as e:
32
+ st.error(f"Error al cargar '{uploaded_file.name}': {e}")
 
 
 
 
 
 
 
 
33
 
34
+ return all_data
35
+
36
+ # Función para crear gráfico de conteo
37
+ def create_count_plot(data, column):
38
+ fig, ax = plt.subplots(figsize=(10, 6))
39
 
40
+ # Contar valores
41
+ value_counts = data[column].value_counts().sort_values(ascending=False)
42
 
43
+ # Si hay demasiados valores únicos, mostrar solo los primeros 15
44
+ if len(value_counts) > 15:
45
+ value_counts = value_counts.head(15)
46
+ plt.title(f"Top 15 valores más frecuentes - {column}")
47
  else:
48
+ plt.title(f"Conteo de valores - {column}")
 
49
 
50
+ # Crear DataFrame para seaborn
51
+ count_df = value_counts.reset_index()
52
+ count_df.columns = [column, 'conteo']
53
 
54
+ # Crear gráfico
55
+ bars = sns.barplot(x=column, y='conteo', data=count_df, ax=ax)
56
 
57
+ # Añadir etiquetas con el número exacto
58
+ for i, p in enumerate(bars.patches):
59
+ bars.annotate(f'{int(p.get_height())}',
60
+ (p.get_x() + p.get_width() / 2., p.get_height()),
61
+ ha='center', va='bottom')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ # Ajustar eje X para mejor visualización
64
+ plt.xticks(rotation=45, ha='right')
65
  plt.tight_layout()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
 
67
  return fig
68
 
69
+ # Función para descargar imagen
70
+ def get_download_link(fig, filename):
71
  buf = io.BytesIO()
72
  fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
73
  buf.seek(0)
74
  b64 = base64.b64encode(buf.read()).decode()
75
+ href = f'<a href="data:image/png;base64,{b64}" download="{filename}.png">📥 Descargar imagen</a>'
76
  return href
77
 
78
  # Función principal
79
  def main():
80
  # Cargar datos
81
+ all_data = load_data()
82
 
83
+ if all_data:
84
+ # Seleccionar archivo
85
+ selected_file = st.selectbox("Selecciona un archivo", list(all_data.keys()))
 
86
 
87
+ # Obtener el DataFrame seleccionado
88
+ data = all_data[selected_file]
 
 
 
 
89
 
90
+ # Mostrar información básica
91
+ st.subheader(f"Vista previa: {selected_file}")
92
+ st.dataframe(data.head())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ # Mostrar información de filas y columnas
95
+ st.info(f"Filas: {data.shape[0]} | Columnas: {data.shape[1]}")
96
 
97
+ # Seleccionar columna para visualizar
98
+ column = st.selectbox("Selecciona una columna para contar", data.columns)
 
 
 
99
 
100
+ # Botón para generar gráfico
101
+ if st.button("Generar Gráfico de Conteo"):
102
+ # Verificar que la columna existe y tiene datos
103
+ if column in data.columns and not data[column].empty:
104
+ # Crear y mostrar el gráfico
105
+ fig = create_count_plot(data, column)
106
+ st.pyplot(fig)
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ # Botón para descargar imagen
109
+ st.markdown(get_download_link(fig, f"conteo_{selected_file}_{column}"), unsafe_allow_html=True)
110
+ else:
111
+ st.error(f"La columna '{column}' no existe o está vacía.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  # Ejecutar la aplicación
114
  if __name__ == "__main__":