VicGerardoPR commited on
Commit
a4f27d8
·
1 Parent(s): ad9e000
Files changed (3) hide show
  1. .DS_Store +0 -0
  2. app.py +340 -0
  3. requirements.txt +6 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
app.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ import io
7
+ import base64
8
+ from pandas.api.types import is_numeric_dtype
9
+
10
+ st.set_page_config(page_title="EDA y Limpieza de Datos", layout="wide")
11
+
12
+ # Función para generar enlace de descarga
13
+ def get_download_link(df, filename, text):
14
+ csv = df.to_csv(index=False)
15
+ b64 = base64.b64encode(csv.encode()).decode()
16
+ href = f'<a href="data:file/csv;base64,{b64}" download="{filename}.csv">{text}</a>'
17
+ return href
18
+
19
+ # Función para crear un resumen detallado de los datos
20
+ def generate_data_summary(df):
21
+ # Información básica
22
+ st.header("📊 Información General del Dataset")
23
+ col1, col2, col3 = st.columns(3)
24
+ with col1:
25
+ st.metric("Filas", df.shape[0])
26
+ with col2:
27
+ st.metric("Columnas", df.shape[1])
28
+ with col3:
29
+ st.metric("Valores nulos totales", df.isna().sum().sum())
30
+
31
+ # Primeras filas
32
+ st.subheader("Vista previa de los datos")
33
+ st.dataframe(df.head())
34
+
35
+ # Tipos de datos
36
+ st.subheader("Tipos de datos")
37
+ dtypes_df = pd.DataFrame(df.dtypes, columns=['Tipo de dato'])
38
+ dtypes_df.index.name = 'Columna'
39
+ dtypes_df = dtypes_df.reset_index()
40
+ st.dataframe(dtypes_df)
41
+
42
+ # Resumen estadístico para columnas numéricas
43
+ st.subheader("Resumen estadístico")
44
+ st.dataframe(df.describe())
45
+
46
+ # Análisis de valores nulos
47
+ st.subheader("Análisis de valores nulos")
48
+ null_counts = df.isnull().sum()
49
+ null_percentages = (null_counts / len(df) * 100).round(2)
50
+ nulls_df = pd.DataFrame({
51
+ 'Valores nulos': null_counts,
52
+ 'Porcentaje (%)': null_percentages
53
+ })
54
+ nulls_df = nulls_df[nulls_df['Valores nulos'] > 0].sort_values('Valores nulos', ascending=False)
55
+
56
+ if not nulls_df.empty:
57
+ st.dataframe(nulls_df)
58
+
59
+ # Visualización de valores nulos
60
+ st.subheader("Visualización de valores nulos")
61
+ fig, ax = plt.subplots(figsize=(10, 6))
62
+ sns.heatmap(df.isnull(), yticklabels=False, cbar=False, cmap='viridis', ax=ax)
63
+ st.pyplot(fig)
64
+ else:
65
+ st.success("¡No hay valores nulos en el dataset!")
66
+
67
+ # Función para visualizar distribuciones
68
+ def visualize_distributions(df):
69
+ st.header("📈 Visualización de Distribuciones")
70
+
71
+ numeric_cols = df.select_dtypes(include='number').columns.tolist()
72
+ categorical_cols = df.select_dtypes(exclude='number').columns.tolist()
73
+
74
+ if numeric_cols:
75
+ st.subheader("Columnas numéricas")
76
+ selected_num_col = st.selectbox("Selecciona una columna numérica", numeric_cols)
77
+
78
+ col1, col2 = st.columns(2)
79
+ with col1:
80
+ fig, ax = plt.subplots(figsize=(10, 6))
81
+ sns.histplot(df[selected_num_col].dropna(), kde=True, ax=ax)
82
+ plt.title(f'Distribución de {selected_num_col}')
83
+ plt.xlabel(selected_num_col)
84
+ plt.ylabel('Frecuencia')
85
+ st.pyplot(fig)
86
+
87
+ with col2:
88
+ fig, ax = plt.subplots(figsize=(10, 6))
89
+ sns.boxplot(y=df[selected_num_col].dropna(), ax=ax)
90
+ plt.title(f'Boxplot de {selected_num_col}')
91
+ st.pyplot(fig)
92
+
93
+ if categorical_cols:
94
+ st.subheader("Columnas categóricas")
95
+ selected_cat_col = st.selectbox("Selecciona una columna categórica", categorical_cols)
96
+
97
+ fig, ax = plt.subplots(figsize=(10, 6))
98
+ value_counts = df[selected_cat_col].value_counts().sort_values(ascending=False)
99
+
100
+ # Limitar el número de categorías mostradas para mayor claridad
101
+ if len(value_counts) > 15:
102
+ other_count = value_counts[15:].sum()
103
+ value_counts = value_counts[:15]
104
+ value_counts['Otros'] = other_count
105
+
106
+ sns.barplot(x=value_counts.index, y=value_counts.values, ax=ax)
107
+ plt.title(f'Distribución de {selected_cat_col}')
108
+ plt.xticks(rotation=45, ha='right')
109
+ plt.tight_layout()
110
+ st.pyplot(fig)
111
+
112
+ # Función para correlaciones
113
+ def visualize_correlations(df):
114
+ st.header("🔄 Análisis de Correlaciones")
115
+
116
+ numeric_cols = df.select_dtypes(include='number').columns.tolist()
117
+
118
+ if len(numeric_cols) >= 2:
119
+ # Matriz de correlación
120
+ st.subheader("Matriz de correlación")
121
+ corr_matrix = df[numeric_cols].corr()
122
+
123
+ fig, ax = plt.subplots(figsize=(10, 8))
124
+ sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt=".2f", linewidths=0.5, ax=ax)
125
+ plt.tight_layout()
126
+ st.pyplot(fig)
127
+
128
+ # Correlación entre dos variables específicas
129
+ st.subheader("Correlación entre dos variables")
130
+ col1, col2 = st.columns(2)
131
+ with col1:
132
+ x_var = st.selectbox("Variable X", numeric_cols)
133
+ with col2:
134
+ y_var = st.selectbox("Variable Y", numeric_cols, index=min(1, len(numeric_cols)-1))
135
+
136
+ fig, ax = plt.subplots(figsize=(10, 6))
137
+ sns.scatterplot(data=df, x=x_var, y=y_var, ax=ax)
138
+ plt.title(f'Correlación entre {x_var} y {y_var}')
139
+ st.pyplot(fig)
140
+ else:
141
+ st.info("Se necesitan al menos dos columnas numéricas para analizar correlaciones.")
142
+
143
+ # Función para limpiar datos
144
+ def clean_data(df):
145
+ st.header("🧹 Limpieza de Datos")
146
+
147
+ cleaned_df = df.copy()
148
+
149
+ # 1. Manejo de valores nulos
150
+ st.subheader("Manejo de valores nulos")
151
+
152
+ null_columns = df.columns[df.isnull().any()].tolist()
153
+ if null_columns:
154
+ for column in null_columns:
155
+ st.markdown(f"**Columna: {column}**")
156
+ col_type = 'numérica' if is_numeric_dtype(df[column]) else 'categórica'
157
+
158
+ method = st.radio(
159
+ f"¿Cómo quieres manejar los valores nulos en '{column}' (columna {col_type})?",
160
+ options=[
161
+ "Eliminar filas con valores nulos",
162
+ f"Reemplazar con la media (para columnas numéricas)" if is_numeric_dtype(df[column]) else "Reemplazar con la moda (para columnas categóricas)",
163
+ "Reemplazar con cero (para columnas numéricas)" if is_numeric_dtype(df[column]) else "Reemplazar con un valor específico",
164
+ "No hacer nada"
165
+ ],
166
+ key=f"null_{column}"
167
+ )
168
+
169
+ if method == "Eliminar filas con valores nulos":
170
+ cleaned_df = cleaned_df.dropna(subset=[column])
171
+ st.info(f"Se eliminarán {df[column].isna().sum()} filas con valores nulos en '{column}'")
172
+
173
+ elif method == "Reemplazar con la media (para columnas numéricas)":
174
+ mean_value = df[column].mean()
175
+ cleaned_df[column] = cleaned_df[column].fillna(mean_value)
176
+ st.info(f"Los valores nulos en '{column}' serán reemplazados con la media: {mean_value:.2f}")
177
+
178
+ elif method == "Reemplazar con la moda (para columnas categóricas)":
179
+ mode_value = df[column].mode()[0]
180
+ cleaned_df[column] = cleaned_df[column].fillna(mode_value)
181
+ st.info(f"Los valores nulos en '{column}' serán reemplazados con la moda: {mode_value}")
182
+
183
+ elif method == "Reemplazar con cero (para columnas numéricas)":
184
+ cleaned_df[column] = cleaned_df[column].fillna(0)
185
+ st.info(f"Los valores nulos en '{column}' serán reemplazados con cero")
186
+
187
+ elif method == "Reemplazar con un valor específico":
188
+ custom_value = st.text_input(f"Valor de reemplazo para '{column}':", key=f"custom_{column}")
189
+ if custom_value:
190
+ cleaned_df[column] = cleaned_df[column].fillna(custom_value)
191
+ else:
192
+ st.success("¡No hay valores nulos que tratar!")
193
+
194
+ # 2. Manejo de duplicados
195
+ st.subheader("Manejo de duplicados")
196
+ duplicates = df.duplicated().sum()
197
+
198
+ if duplicates > 0:
199
+ st.warning(f"Se encontraron {duplicates} filas duplicadas en el dataset.")
200
+ remove_duplicates = st.checkbox("Eliminar filas duplicadas")
201
+ if remove_duplicates:
202
+ cleaned_df = cleaned_df.drop_duplicates()
203
+ st.info(f"Se eliminarán {duplicates} filas duplicadas.")
204
+ else:
205
+ st.success("¡No hay filas duplicadas en el dataset!")
206
+
207
+ # 3. Manejo de valores atípicos (outliers)
208
+ st.subheader("Manejo de valores atípicos (outliers)")
209
+
210
+ numeric_cols = df.select_dtypes(include=['number']).columns.tolist()
211
+ if numeric_cols:
212
+ outlier_handling = st.checkbox("¿Quieres tratar los valores atípicos?")
213
+
214
+ if outlier_handling:
215
+ selected_col = st.selectbox("Selecciona una columna numérica para analizar outliers", numeric_cols)
216
+
217
+ # Visualizar la distribución con posibles outliers
218
+ fig, ax = plt.subplots(figsize=(10, 6))
219
+ sns.boxplot(y=df[selected_col], ax=ax)
220
+ plt.title(f'Boxplot de {selected_col} - Identificación de outliers')
221
+ st.pyplot(fig)
222
+
223
+ # Calcular límites para outliers usando el método IQR
224
+ Q1 = df[selected_col].quantile(0.25)
225
+ Q3 = df[selected_col].quantile(0.75)
226
+ IQR = Q3 - Q1
227
+ lower_bound = Q1 - 1.5 * IQR
228
+ upper_bound = Q3 + 1.5 * IQR
229
+
230
+ outliers = df[(df[selected_col] < lower_bound) | (df[selected_col] > upper_bound)][selected_col]
231
+
232
+ if not outliers.empty:
233
+ st.warning(f"Se encontraron {len(outliers)} valores atípicos en '{selected_col}'.")
234
+ outlier_method = st.radio(
235
+ f"¿Cómo quieres manejar los outliers en '{selected_col}'?",
236
+ options=[
237
+ "Recortar (capping)",
238
+ "Eliminar filas con outliers",
239
+ "No hacer nada"
240
+ ],
241
+ key=f"outlier_{selected_col}"
242
+ )
243
+
244
+ if outlier_method == "Recortar (capping)":
245
+ cleaned_df[selected_col] = cleaned_df[selected_col].clip(lower_bound, upper_bound)
246
+ st.info(f"Los valores atípicos en '{selected_col}' serán recortados a [{lower_bound:.2f}, {upper_bound:.2f}]")
247
+
248
+ elif outlier_method == "Eliminar filas con outliers":
249
+ mask = (cleaned_df[selected_col] >= lower_bound) & (cleaned_df[selected_col] <= upper_bound)
250
+ cleaned_df = cleaned_df[mask]
251
+ st.info(f"Se eliminarán {len(outliers)} filas con valores atípicos en '{selected_col}'")
252
+ else:
253
+ st.success(f"¡No se encontraron valores atípicos en '{selected_col}'!")
254
+
255
+ # 4. Transformación de tipos de datos
256
+ st.subheader("Transformación de tipos de datos")
257
+ type_conversion = st.checkbox("¿Quieres convertir el tipo de alguna columna?")
258
+
259
+ if type_conversion:
260
+ col1, col2 = st.columns(2)
261
+ with col1:
262
+ column_to_convert = st.selectbox("Selecciona una columna", df.columns)
263
+ with col2:
264
+ new_type = st.selectbox("Nuevo tipo de dato", options=['int', 'float', 'string', 'datetime', 'category'])
265
+
266
+ try:
267
+ if new_type == 'int':
268
+ cleaned_df[column_to_convert] = cleaned_df[column_to_convert].astype(int)
269
+ elif new_type == 'float':
270
+ cleaned_df[column_to_convert] = cleaned_df[column_to_convert].astype(float)
271
+ elif new_type == 'string':
272
+ cleaned_df[column_to_convert] = cleaned_df[column_to_convert].astype(str)
273
+ elif new_type == 'datetime':
274
+ cleaned_df[column_to_convert] = pd.to_datetime(cleaned_df[column_to_convert])
275
+ elif new_type == 'category':
276
+ cleaned_df[column_to_convert] = cleaned_df[column_to_convert].astype('category')
277
+ st.success(f"La columna '{column_to_convert}' ha sido convertida a tipo {new_type}")
278
+ except Exception as e:
279
+ st.error(f"Error al convertir el tipo de dato: {str(e)}")
280
+
281
+ return cleaned_df
282
+
283
+ # Aplicación principal
284
+ def main():
285
+ st.title("📊 Análisis Exploratorio de Datos (EDA) y Limpieza")
286
+ st.markdown("""
287
+ Esta aplicación te permite realizar un análisis exploratorio completo de tus datos,
288
+ visualizar su distribución y realizar operaciones de limpieza paso a paso.
289
+ """)
290
+
291
+ # Subir archivo
292
+ st.header("📁 Carga tu archivo")
293
+ uploaded_file = st.file_uploader("Selecciona un archivo CSV o Excel", type=['csv', 'xlsx', 'xls'])
294
+
295
+ if uploaded_file is not None:
296
+ try:
297
+ # Determinar tipo de archivo y leerlo
298
+ if uploaded_file.name.endswith('.csv'):
299
+ df = pd.read_csv(uploaded_file)
300
+ else:
301
+ df = pd.read_excel(uploaded_file)
302
+
303
+ # Crear pestañas para organizar el análisis
304
+ tab1, tab2, tab3, tab4 = st.tabs(["📊 Resumen de datos", "📈 Visualizaciones", "🔄 Correlaciones", "🧹 Limpieza"])
305
+
306
+ with tab1:
307
+ generate_data_summary(df)
308
+
309
+ with tab2:
310
+ visualize_distributions(df)
311
+
312
+ with tab3:
313
+ visualize_correlations(df)
314
+
315
+ with tab4:
316
+ cleaned_df = clean_data(df)
317
+
318
+ if st.button("Aplicar cambios y descargar datos limpios"):
319
+ st.success("¡Limpieza de datos completada!")
320
+
321
+ # Mostrar comparación
322
+ st.subheader("Comparación: Datos originales vs. Datos limpios")
323
+ col1, col2 = st.columns(2)
324
+ with col1:
325
+ st.write("Datos originales")
326
+ st.metric("Filas", df.shape[0])
327
+ st.metric("Valores nulos", df.isna().sum().sum())
328
+ with col2:
329
+ st.write("Datos limpios")
330
+ st.metric("Filas", cleaned_df.shape[0])
331
+ st.metric("Valores nulos", cleaned_df.isna().sum().sum())
332
+
333
+ # Generar enlace de descarga
334
+ st.markdown(get_download_link(cleaned_df, "datos_limpios", "📥 Descargar datos limpios (CSV)"), unsafe_allow_html=True)
335
+
336
+ except Exception as e:
337
+ st.error(f"Error al procesar el archivo: {str(e)}")
338
+
339
+ if __name__ == "__main__":
340
+ main()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ numpy
4
+ matplotlib
5
+ seaborn
6
+ openpyxl