|
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 |
|
|
|
|
|
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)") |
|
|
|
|
|
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' |
|
} |
|
|
|
|
|
@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" |
|
) |
|
|
|
|
|
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 |
|
|
|
|
|
df = load_data() |
|
|
|
|
|
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.") |
|
|
|
|
|
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] |
|
|
|
|
|
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.") |
|
|
|
|
|
def calcular_percentiles(colegio, df_filtrado): |
|
|
|
df_math = df_filtrado[df_filtrado['promedio_matematica'].notna()] |
|
df_lectura = df_filtrado[df_filtrado['promedio_lectura'].notna()] |
|
|
|
|
|
math_score = colegio['promedio_matematica'].values[0] |
|
lectura_score = colegio['promedio_lectura'].values[0] |
|
|
|
|
|
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 |
|
|
|
|
|
def crear_boxplot_individual(datos, colegio_puntaje, titulo, color, nombre_colegio): |
|
if datos.empty: |
|
return None |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 6)) |
|
|
|
|
|
boxplot = ax.boxplot(datos, patch_artist=True, widths=0.6) |
|
|
|
|
|
boxplot['boxes'][0].set_facecolor(color) |
|
|
|
|
|
ax.plot(1, colegio_puntaje, 'ro', markersize=10, label=f'{nombre_colegio}: {colegio_puntaje:.1f}') |
|
|
|
|
|
ax.set_title(titulo, fontsize=16, fontweight='bold') |
|
ax.set_ylabel('Puntaje', fontsize=12) |
|
ax.set_xticklabels(['']) |
|
ax.legend(loc='upper right') |
|
|
|
|
|
q1, median, q3 = np.percentile(datos, [25, 50, 75]) |
|
ax.axhline(y=median, color='green', linestyle='--', alpha=0.7, label=f'Mediana: {median:.1f}') |
|
|
|
|
|
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 |
|
|
|
|
|
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" |
|
|
|
|
|
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" |
|
|
|
|
|
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 |
|
|
|
|
|
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)": |
|
|
|
colegio_data = df_f[df_f['nombre_colegio'] == colegio_seleccionado] |
|
|
|
if not colegio_data.empty: |
|
|
|
percentil_math, percentil_lectura = calcular_percentiles(colegio_data, df_f) |
|
|
|
|
|
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.") |
|
|
|
|
|
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}") |
|
|
|
|
|
st.subheader("Crear Lista de Colegios") |
|
st.markdown("Busca colegios por palabra clave en el nombre y selecciona para crear tu lista personalizada.") |
|
|
|
|
|
if 'selected_colegios' not in st.session_state: |
|
st.session_state.selected_colegios = [] |
|
|
|
|
|
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).") |
|
|
|
|
|
if keyword: |
|
df_search = df_f[df_f['nombre_colegio'].str.contains(keyword, case=False, na=False)] |
|
else: |
|
df_search = df_f |
|
|
|
|
|
colegios_disponibles = sorted(df_search['nombre_colegio'].unique().tolist()) |
|
|
|
|
|
|
|
combined_colegios = sorted(set(colegios_disponibles + [c for c in st.session_state.selected_colegios if c in df_f['nombre_colegio'].values])) |
|
|
|
|
|
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." |
|
) |
|
|
|
|
|
st.session_state.selected_colegios = selected_colegios |
|
|
|
|
|
df_selected = df_f[df_f['nombre_colegio'].isin(st.session_state.selected_colegios)] |
|
|
|
|
|
if colegio_seleccionado != "(Selecciona un colegio)": |
|
st.subheader("An谩lisis Comparativo Detallado") |
|
|
|
|
|
texto_math = generar_texto_comparativo(colegio_data, df_f, df_selected, 'promedio_matematica') |
|
st.markdown(texto_math) |
|
|
|
|
|
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) |
|
|
|
|
|
st.markdown("---") |
|
texto_lectura = generar_texto_comparativo(colegio_data, df_f, df_selected, 'promedio_lectura') |
|
st.markdown(texto_lectura) |
|
|
|
|
|
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) |
|
|
|
|
|
df_map = df_selected if not df_selected.empty else df_f |
|
|
|
df_map = df_map.dropna(subset=["latitud", "longitud"]) |
|
|
|
|
|
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.") |
|
|
|
|
|
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' |
|
} |
|
|
|
|
|
def tipo_color(tipo: str) -> str: |
|
return color_map.get(tipo, "gray") |
|
|
|
|
|
m = folium.Map(location=[-33.45, -70.65], zoom_start=6, tiles="CartoDB positron") |
|
|
|
|
|
cluster = MarkerCluster().add_to(m) |
|
|
|
|
|
school_search = Search( |
|
layer=cluster, |
|
search_label="nombre_colegio", |
|
placeholder="Buscar por nombre del colegio...", |
|
collapsed=False, |
|
).add_to(m) |
|
|
|
|
|
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""" |
|
<b>{nombre}</b><br> |
|
<b>Tipo:</b> {tipo}<br> |
|
<b>Comuna:</b> {comuna}<br> |
|
<b>Promedio Lenguaje:</b> {lenguaje}<br> |
|
<b>Promedio Matem谩ticas:</b> {matematica}<br> |
|
<b>Es Rural:</b> {rural}<br> |
|
<b>Grupo Socioecon贸mico:</b> {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} |
|
|
|
|
|
legend_html = """ |
|
<div style=" |
|
position: fixed; |
|
bottom: 30px; left: 30px; z-index: 9999; |
|
background: white; padding: 12px; border: 2px solid #ccc; border-radius: 10px; |
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1); font-family: Arial, sans-serif; font-size: 14px;"> |
|
<b style="color:black; font-size: 16px;">Leyenda</b><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:blue;margin-right:8px;"></span><span style="color:black;">Municipal Corporaci贸n</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:green;margin-right:8px;"></span><span style="color:black;">Municipal DAEM</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:orange;margin-right:8px;"></span><span style="color:black;">Particular subvencionado</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:purple;margin-right:8px;"></span><span style="color:black;">Particular pagado</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:red;margin-right:8px;"></span><span style="color:black;">Corporaci贸n de administraci贸n delegada</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:cadetblue;margin-right:8px;"></span><span style="color:black;">Servicio Local de Educaci贸n</span><br> |
|
<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:gray;margin-right:8px;"></span><span style="color:black;">Otros</span> |
|
</div> |
|
""" |
|
m.get_root().html.add_child(folium.Element(legend_html)) |
|
|
|
|
|
if st.button("Reiniciar Mapa"): |
|
m = folium.Map(location=[-33.45, -70.65], zoom_start=6, tiles="CartoDB positron") |
|
st.experimental_rerun() |
|
|
|
|
|
st_folium(m, width=1200, height=650) |
|
|
|
|
|
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]) |
|
|
|
|
|
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", |
|
) |
|
|
|
|
|
if st.session_state.selected_colegios: |
|
st.dataframe(df_selected[columns_to_display]) |
|
|
|
|
|
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.") |