import streamlit as st import pandas as pd import folium from folium.plugins import MarkerCluster, Search from streamlit_folium import st_folium import html import io import numpy as np import matplotlib.pyplot as plt import seaborn as sns # --- Configuración --- st.set_page_config(page_title="Puntajes SIMCE 2024 - Centros Escolares", page_icon=":school:", layout="wide") st.title(":school: Puntajes SIMCE 2024 - 2do Medio") st.markdown("Explora los puntajes SIMCE de los establecimientos educacionales en Chile (Datos del MINEDUC)") # --- Diccionarios para mapear valores a descripciones --- dependencia_map = { 1: 'Municipal Corporación', 2: 'Municipal DAEM', 3: 'Particular subvencionado', 4: 'Particular pagado', 5: 'Corporación de administración delegada', 6: 'Servicio Local de Educación' } socioecon_map = { 1: 'Bajo', 2: 'Medio Bajo', 3: 'Medio', 4: 'Medio Alto', 5: 'Alto' } rural_map = { 1: 'Urbano', 2: 'Rural' } # --- Cargar datos --- @st.cache_data def load_data(): df = pd.read_csv( "src/Data/simce.csv", sep=";", decimal=",", encoding="ISO-8859-1", header=0, on_bad_lines="skip" ) # Crear columnas con descripciones df['dependencia_desc'] = df['dependencia'].map(dependencia_map) df['grupo_socioecon_desc'] = df['grupo_socioecon'].map(socioecon_map) df['es_rural_desc'] = df['es_rural'].map(rural_map) return df # Cargar datos df = load_data() # --- Filtros --- st.subheader("Filtros de Búsqueda") col1, col2, col3, col4 = st.columns(4) regiones = ["(Todas)"] + sorted(df["nombre_region"].dropna().unique().tolist()) ruralidades = ["(Todas)"] + sorted(df["es_rural_desc"].dropna().unique().tolist()) dependencias = ["(Todas)"] + sorted(df["dependencia_desc"].dropna().unique().tolist()) socioecons = ["(Todas)"] + sorted(df["grupo_socioecon_desc"].dropna().unique().tolist()) with col1: sel_region = st.selectbox("Región", regiones, index=0, help="Selecciona una región para filtrar los establecimientos.") with col2: sel_ruralidad = st.selectbox("Ruralidad", ruralidades, index=0, help="Filtra por tipo de área (urbana o rural).") with col3: sel_dependencia = st.selectbox("Dependencia", dependencias, index=0, help="Filtra por tipo de establecimiento.") with col4: sel_socioecon = st.selectbox("Grupo Socioeconómico", socioecons, index=0, help="Filtra por nivel socioeconómico.") # Filtrar datos df_f = df.copy() if sel_region != "(Todas)": df_f = df_f[df_f["nombre_region"] == sel_region] if sel_ruralidad != "(Todas)": df_f = df_f[df_f["es_rural_desc"] == sel_ruralidad] if sel_dependencia != "(Todas)": df_f = df_f[df_f["dependencia_desc"] == sel_dependencia] if sel_socioecon != "(Todas)": df_f = df_f[df_f["grupo_socioecon_desc"] == sel_socioecon] # --- Nueva sección: Comparación de Colegios por Percentiles --- st.subheader("Comparación de Colegio por Percentiles") st.markdown("Selecciona un colegio para comparar sus puntajes en Matemáticas y Lenguaje con respecto a los demás colegios filtrados.") # Función para calcular percentiles def calcular_percentiles(colegio, df_filtrado): # Filtrar datos válidos (sin NaN) df_math = df_filtrado[df_filtrado['promedio_matematica'].notna()] df_lectura = df_filtrado[df_filtrado['promedio_lectura'].notna()] # Obtener puntajes del colegio seleccionado math_score = colegio['promedio_matematica'].values[0] lectura_score = colegio['promedio_lectura'].values[0] # Calcular percentiles if not df_math.empty and not pd.isna(math_score): percentil_math = (sum(df_math['promedio_matematica'] <= math_score) / len(df_math)) * 100 else: percentil_math = None if not df_lectura.empty and not pd.isna(lectura_score): percentil_lectura = (sum(df_lectura['promedio_lectura'] <= lectura_score) / len(df_lectura)) * 100 else: percentil_lectura = None return percentil_math, percentil_lectura # Función para crear boxplot individual def crear_boxplot_individual(datos, colegio_puntaje, titulo, color, nombre_colegio): if datos.empty: return None # Crear figura fig, ax = plt.subplots(figsize=(10, 6)) # Crear boxplot boxplot = ax.boxplot(datos, patch_artist=True, widths=0.6) # Colorear el boxplot boxplot['boxes'][0].set_facecolor(color) # Marcar la posición del colegio seleccionado ax.plot(1, colegio_puntaje, 'ro', markersize=10, label=f'{nombre_colegio}: {colegio_puntaje:.1f}') # Personalizar ax.set_title(titulo, fontsize=16, fontweight='bold') ax.set_ylabel('Puntaje', fontsize=12) ax.set_xticklabels(['']) # Ocultar label del eje x ax.legend(loc='upper right') # Añadir líneas de referencia para percentiles q1, median, q3 = np.percentile(datos, [25, 50, 75]) ax.axhline(y=median, color='green', linestyle='--', alpha=0.7, label=f'Mediana: {median:.1f}') # Añadir estadísticas descriptivas stats_text = f""" N: {len(datos)} Mín: {datos.min():.1f} Q1: {q1:.1f} Mediana: {median:.1f} Q3: {q3:.1f} Máx: {datos.max():.1f} """ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5), fontsize=10) plt.tight_layout() return fig # Función para generar texto comparativo def generar_texto_comparativo(colegio_data, df_filtrados, df_lista_manual, materia): nombre_colegio = colegio_data['nombre_colegio'].values[0] puntaje_colegio = colegio_data[materia].values[0] texto = f"### Comparación de {materia.replace('promedio_', '').capitalize()} para {nombre_colegio}\n\n" # Comparación con todos los colegios filtrados datos_filtrados = df_filtrados[df_filtrados[materia].notna()][materia] if not datos_filtrados.empty: percentil_filtrados = (sum(datos_filtrados <= puntaje_colegio) / len(datos_filtrados)) * 100 mediana_filtrados = np.median(datos_filtrados) diferencia_filtrados = puntaje_colegio - mediana_filtrados texto += f"**Comparación con todos los colegios filtrados ({len(datos_filtrados)} colegios):**\n" texto += f"- Puntaje: {puntaje_colegio:.1f}\n" texto += f"- Percentil: {percentil_filtrados:.1f}% (supera al {percentil_filtrados:.1f}% de los colegios)\n" texto += f"- Diferencia con la mediana: {diferencia_filtrados:+.1f} puntos\n" texto += f"- Mediana de todos los colegios: {mediana_filtrados:.1f}\n\n" # Comparación con la lista manual si existe if not df_lista_manual.empty: datos_lista = df_lista_manual[df_lista_manual[materia].notna()][materia] if not datos_lista.empty: percentil_lista = (sum(datos_lista <= puntaje_colegio) / len(datos_lista)) * 100 mediana_lista = np.median(datos_lista) diferencia_lista = puntaje_colegio - mediana_lista texto += f"**Comparación con la lista de colegios seleccionados ({len(datos_lista)} colegios):**\n" texto += f"- Puntaje: {puntaje_colegio:.1f}\n" texto += f"- Percentil: {percentil_lista:.1f}% (supera al {percentil_lista:.1f}% de los colegios de la lista)\n" texto += f"- Diferencia con la mediana: {diferencia_lista:+.1f} puntos\n" texto += f"- Mediana de la lista: {mediana_lista:.1f}\n\n" return texto # Seleccionar un colegio para comparar colegios_filtrados = sorted(df_f['nombre_colegio'].unique().tolist()) colegio_seleccionado = st.selectbox( "Selecciona un colegio para analizar", ["(Selecciona un colegio)"] + colegios_filtrados, index=0, help="Selecciona un colegio para ver su posición percentil en Matemáticas y Lenguaje." ) if colegio_seleccionado != "(Selecciona un colegio)": # Obtener datos del colegio seleccionado colegio_data = df_f[df_f['nombre_colegio'] == colegio_seleccionado] if not colegio_data.empty: # Calcular percentiles percentil_math, percentil_lectura = calcular_percentiles(colegio_data, df_f) # Mostrar resultados col1, col2 = st.columns(2) with col1: if percentil_math is not None: st.metric( label=f"Percentil en Matemáticas", value=f"{percentil_math:.1f}%", help=f"Este colegio supera al {percentil_math:.1f}% de los colegios filtrados en Matemáticas." ) st.progress(percentil_math/100) else: st.warning("No hay datos de Matemáticas para este colegio.") with col2: if percentil_lectura is not None: st.metric( label=f"Percentil en Lenguaje", value=f"{percentil_lectura:.1f}%", help=f"Este colegio supera al {percentil_lectura:.1f}% de los colegios filtrados en Lenguaje." ) st.progress(percentil_lectura/100) else: st.warning("No hay datos de Lenguaje para este colegio.") # Mostrar información detallada del colegio st.subheader(f"Información detallada de {colegio_seleccionado}") colegio_info = colegio_data.iloc[0] info_cols = st.columns(2) with info_cols[0]: st.write(f"**Región:** {colegio_info['nombre_region']}") st.write(f"**Comuna:** {colegio_info['nombre_comuna']}") st.write(f"**Dependencia:** {colegio_info['dependencia_desc']}") with info_cols[1]: st.write(f"**Grupo Socioeconómico:** {colegio_info['grupo_socioecon_desc']}") st.write(f"**Ruralidad:** {colegio_info['es_rural_desc']}") if percentil_math is not None and percentil_lectura is not None: st.write(f"**Puntaje promedio:** Matemáticas={colegio_info['promedio_matematica']:.1f}, Lenguaje={colegio_info['promedio_lectura']:.1f}") # --- Nueva sección: Crear lista de colegios --- st.subheader("Crear Lista de Colegios") st.markdown("Busca colegios por palabra clave en el nombre y selecciona para crear tu lista personalizada.") # Inicializar estado de sesión para los colegios seleccionados if 'selected_colegios' not in st.session_state: st.session_state.selected_colegios = [] # Input para la palabra clave keyword = st.text_input("Palabra clave en el nombre del colegio", help="Ingresa una palabra para filtrar los nombres de colegios que la contengan (insensible a mayúsculas/minúsculas).") # Filtrar colegios basados en la palabra clave if keyword: df_search = df_f[df_f['nombre_colegio'].str.contains(keyword, case=False, na=False)] else: df_search = df_f # Obtener lista única de nombres de colegios filtrados colegios_disponibles = sorted(df_search['nombre_colegio'].unique().tolist()) # Combinar colegios seleccionados previamente con los disponibles en el filtro actual # Solo incluir colegios que aún estén en df_f (después de aplicar filtros de región, ruralidad, etc.) combined_colegios = sorted(set(colegios_disponibles + [c for c in st.session_state.selected_colegios if c in df_f['nombre_colegio'].values])) # Multiselect para seleccionar colegios, con los previamente seleccionados como predeterminados selected_colegios = st.multiselect( "Selecciona colegios para tu lista", combined_colegios, default=[c for c in st.session_state.selected_colegios if c in combined_colegios], help="Selecciona múltiples colegios de la lista filtrada. Los colegios seleccionados previamente se mantienen." ) # Actualizar el estado de sesión con las nuevas selecciones st.session_state.selected_colegios = selected_colegios # Crear dataframe de colegios seleccionados df_selected = df_f[df_f['nombre_colegio'].isin(st.session_state.selected_colegios)] # --- Mostrar comparación detallada y boxplots separados --- if colegio_seleccionado != "(Selecciona un colegio)": st.subheader("Análisis Comparativo Detallado") # Generar y mostrar texto comparativo para Matemáticas texto_math = generar_texto_comparativo(colegio_data, df_f, df_selected, 'promedio_matematica') st.markdown(texto_math) # Crear y mostrar boxplots individuales para Matemáticas math_filtrados = df_f[df_f['promedio_matematica'].notna()]['promedio_matematica'] math_lista = df_selected[df_selected['promedio_matematica'].notna()]['promedio_matematica'] math_puntaje = colegio_data['promedio_matematica'].values[0] col_math1, col_math2 = st.columns(2) with col_math1: if not math_filtrados.empty: math_boxplot_filtrados = crear_boxplot_individual( math_filtrados, math_puntaje, 'Distribución de Matemáticas - Todos los Colegios Filtrados', 'lightblue', colegio_seleccionado ) st.pyplot(math_boxplot_filtrados) with col_math2: if not df_selected.empty and not math_lista.empty: math_boxplot_lista = crear_boxplot_individual( math_lista, math_puntaje, 'Distribución de Matemáticas - Lista de Colegios Seleccionados', 'lightgreen', colegio_seleccionado ) st.pyplot(math_boxplot_lista) # Generar y mostrar texto comparativo para Lenguaje st.markdown("---") texto_lectura = generar_texto_comparativo(colegio_data, df_f, df_selected, 'promedio_lectura') st.markdown(texto_lectura) # Crear y mostrar boxplots individuales para Lenguaje lectura_filtrados = df_f[df_f['promedio_lectura'].notna()]['promedio_lectura'] lectura_lista = df_selected[df_selected['promedio_lectura'].notna()]['promedio_lectura'] lectura_puntaje = colegio_data['promedio_lectura'].values[0] col_lectura1, col_lectura2 = st.columns(2) with col_lectura1: if not lectura_filtrados.empty: lectura_boxplot_filtrados = crear_boxplot_individual( lectura_filtrados, lectura_puntaje, 'Distribución de Lenguaje - Todos los Colegios Filtrados', 'lightblue', colegio_seleccionado ) st.pyplot(lectura_boxplot_filtrados) with col_lectura2: if not df_selected.empty and not lectura_lista.empty: lectura_boxplot_lista = crear_boxplot_individual( lectura_lista, lectura_puntaje, 'Distribución de Lenguaje - Lista de Colegios Seleccionados', 'lightgreen', colegio_seleccionado ) st.pyplot(lectura_boxplot_lista) # Usar df_selected si hay colegios seleccionados, de lo contrario usar df_f df_map = df_selected if not df_selected.empty else df_f # Filtrar filas con coordenadas válidas df_map = df_map.dropna(subset=["latitud", "longitud"]) # --- Estadísticas resumidas --- st.subheader("Estadísticas Resumidas") if not df_map.empty: col_stats1, col_stats2 = st.columns(2) with col_stats1: st.metric("Número de Establecimientos", len(df_map)) st.metric("Promedio Lenguaje", f"{df_map['promedio_lectura'].mean():.1f}") with col_stats2: st.metric("Promedio Matemáticas", f"{df_map['promedio_matematica'].mean():.1f}") st.metric("Regiones Cubiertas", df_map["nombre_region"].nunique()) else: st.warning("No hay datos disponibles con los filtros seleccionados o no se han seleccionado colegios.") # --- Mapeo de colores por tipo --- color_map = { 'Municipal Corporación': 'blue', 'Municipal DAEM': 'green', 'Particular subvencionado': 'orange', 'Particular pagado': 'purple', 'Corporación de administración delegada': 'red', 'Servicio Local de Educación': 'cadetblue' } # Función para asignar color según tipo def tipo_color(tipo: str) -> str: return color_map.get(tipo, "gray") # --- Crear mapa centrado en Chile --- m = folium.Map(location=[-33.45, -70.65], zoom_start=6, tiles="CartoDB positron") # Cluster cluster = MarkerCluster().add_to(m) # --- Agregar búsqueda por nombre de colegio --- school_search = Search( layer=cluster, search_label="nombre_colegio", placeholder="Buscar por nombre del colegio...", collapsed=False, ).add_to(m) # --- Agregar marcadores --- for _, r in df_map.iterrows(): lat, lon = float(r["latitud"]), float(r["longitud"]) nombre = html.escape(str(r.get("nombre_colegio", ""))) comuna = html.escape(str(r.get("nombre_comuna", ""))) tipo = html.escape(str(r.get("dependencia_desc", ""))) lenguaje = html.escape(str(r.get("promedio_lectura", ""))) matematica = html.escape(str(r.get("promedio_matematica", ""))) rural = html.escape(str(r.get("es_rural_desc", ""))) grupoeconomico = html.escape(str(r.get("grupo_socioecon_desc", ""))) popup_html = f""" {nombre}
Tipo: {tipo}
Comuna: {comuna}
Promedio Lenguaje: {lenguaje}
Promedio Matemáticas: {matematica}
Es Rural: {rural}
Grupo Socioeconómico: {grupoeconomico} """ marker = folium.Marker( location=[lat, lon], popup=folium.Popup(popup_html, max_width=350), icon=folium.Icon(color=tipo_color(r.get("dependencia_desc")), icon="plus", prefix="fa"), ) marker.add_to(cluster) marker.properties = {"nombre_colegio": nombre} # Para búsqueda # --- Leyenda mejorada --- legend_html = """
Leyenda
Municipal Corporación
Municipal DAEM
Particular subvencionado
Particular pagado
Corporación de administración delegada
Servicio Local de Educación
Otros
""" m.get_root().html.add_child(folium.Element(legend_html)) # --- Botón para reiniciar mapa --- if st.button("Reiniciar Mapa"): m = folium.Map(location=[-33.45, -70.65], zoom_start=6, tiles="CartoDB positron") st.experimental_rerun() # --- Mostrar el mapa --- st_folium(m, width=1200, height=650) # --- Vista de tabla y exportación --- with st.expander("Ver tabla filtrada"): columns_to_display = ['nombre_region', 'nombre_comuna', 'nombre_colegio', 'dependencia_desc', 'grupo_socioecon_desc', 'es_rural_desc', 'promedio_lectura', 'promedio_matematica'] st.dataframe(df_map[columns_to_display]) # Exportar datos filtrados csv = df_map[columns_to_display].to_csv(index=False) st.download_button( label="Descargar datos filtrados como CSV", data=csv, file_name="simce_filtrado.csv", mime="text/csv", ) # Mostrar la lista seleccionada if st.session_state.selected_colegios: st.dataframe(df_selected[columns_to_display]) # Exportar lista seleccionada csv_selected = df_selected[columns_to_display].to_csv(index=False) st.download_button( label="Descargar lista seleccionada como CSV", data=csv_selected, file_name="lista_colegios_seleccionados.csv", mime="text/csv", ) else: st.info("No hay colegios seleccionados aún.")