Spaces:
Build error
Build error
| # app.py – Explorador geoespacial Vasculitis ANCA (Bogotá) | |
| # ───────────────────────────────────────────────────────────── | |
| # • Carga el Excel “Vasculitis…2025‑04‑16_1949 (1).xlsx”. | |
| # • Normaliza los nombres de columna a snake_case ASCII. | |
| # • Renombra dinámicamente latitud / longitud. | |
| # • Deriva: | |
| # – edad_cat (quinquenios) | |
| # – flags de antecedentes | |
| # – patrón de biopsia resumido | |
| # • Filtros completos por género, edad, localidad, ANCA, MPO, PR3, | |
| # antecedentes, patrón de biopsia y compromiso renal. | |
| # • Mapa Folium con: | |
| # – coroplético pacientes / localidad | |
| # – capas ambientales (PM10, PM2.5, Ozono, Temp, Precip, Viento, WQI) | |
| # – heatmap opcional | |
| # – una sola capa de clústeres 1 km con pop‑ups resumidos | |
| # • Gráficos Univariado y Bivariado que aceptan TODAS las variables | |
| # (numéricas → histograma / dispersión; categóricas → barras / box‑plot). | |
| # • Toda etiqueta de biopsia u antecedente usa la forma corta | |
| # (p.ej. “Crescéntica”, “Vasculitis + glom.”, “Hipertensión”, “EPOC”…). | |
| import re, unicodedata, warnings, branca, folium, gradio as gr | |
| import pandas as pd, geopandas as gpd, numpy as np | |
| from shapely.geometry import Point | |
| from folium.plugins import HeatMap | |
| from sklearn.cluster import DBSCAN | |
| import plotly.express as px, plotly.graph_objects as go | |
| import pandas.api.types as ptypes | |
| import math | |
| warnings.filterwarnings("ignore") | |
| def snake(cols): | |
| out = [] | |
| for col in cols: | |
| txt = unicodedata.normalize("NFKD", col) | |
| txt = txt.encode("ascii", "ignore").decode("utf-8") | |
| txt = re.sub(r"[^\w]+", "_", txt.strip().lower()) | |
| out.append(txt.strip("_")) | |
| return out | |
| DATA_XLSX = "VasculitisAsociadasA-Bdd3_DATA_LABELS_2025-04-16_1949 (1).xlsx" | |
| LOCALIDADES = "loca.json" | |
| GEO_AMBIENTALES = { | |
| "PM10": "pm10_prom_anual.geojson", | |
| "PM2.5": "pm25_prom_anual_2023 (2).geojson", | |
| "Ozono": "ozono_prom_anual_2022 (2).geojson", | |
| "Temperatura": "temp_anualprom_2023 (2).geojson", | |
| "Precipitación": "precip_anualacum_2023 (2).geojson", | |
| "Viento": "vel_viento_0_23h_anual_2023.geojson", | |
| "WQI": "tramo_wqi.geojson", | |
| "Heatmap pacientes": None | |
| } | |
| META_CAPAS = { | |
| "PM10": ("conc_pm10", "µg/m³", branca.colormap.linear.OrRd_09, "id", "Zona"), | |
| "PM2.5": ("conc_pm25", "µg/m³", branca.colormap.linear.Reds_09, "id", "Zona"), | |
| "Ozono": ("conc_ozono", "ppb", branca.colormap.linear.PuBuGn_09, "id", "Zona"), | |
| "Temperatura": ("temperatur", "°C", branca.colormap.linear.YlOrBr_09, "id", "Zona"), | |
| "Precipitación": ("precip_per", "mm", branca.colormap.linear.Blues_09, "id", "Zona"), | |
| "Viento": ("velocidad", "m/s", branca.colormap.linear.GnBu_09, "id", "Zona"), | |
| "WQI": ("wqi", "", branca.colormap.linear.Greens_09, "tramo", "Tramo") | |
| } | |
| # ─── 1. Pacientes ──────────────────────────────────────── | |
| df = pd.read_excel(DATA_XLSX, dtype=str) | |
| df.columns = snake(df.columns) | |
| col_lat = next(c for c in df.columns if "residencia" in c and "latitud" in c) | |
| col_lon = next(c for c in df.columns if "residencia" in c and "longitud" in c) | |
| df = df.rename(columns={col_lat:"latitud", col_lon:"longitud"}) | |
| df["latitud"] = pd.to_numeric(df["latitud"].str.replace(",", "."), errors="coerce") | |
| df["longitud"] = pd.to_numeric(df["longitud"].str.replace(",", "."), errors="coerce") | |
| df = df.dropna(subset=["latitud","longitud"]) | |
| df["geometry"] = df.apply(lambda r: Point(r["longitud"], r["latitud"]), axis=1) | |
| df = gpd.GeoDataFrame(df, geometry="geometry", crs="EPSG:4326") | |
| # ─── 2. Localidades ───────────────────────────────────── | |
| geo_loc = gpd.read_file(LOCALIDADES).to_crs("EPSG:4326") | |
| geo_loc.columns = snake(geo_loc.columns) | |
| loc_col = next(c for c in geo_loc.columns if "localidad" in c or "locnombre" in c) | |
| geo_loc = geo_loc.rename(columns={loc_col:"localidad"}) | |
| geo_loc["localidad"] = geo_loc["localidad"].str.upper() | |
| df = gpd.sjoin(df, geo_loc[["localidad","geometry"]], how="left", predicate="within") \ | |
| .drop(columns="index_right") | |
| # ─── 3. Capas ambientales ─────────────────────────────── | |
| def load_gjson(path): | |
| g = gpd.read_file(path).to_crs("EPSG:4326") | |
| g.columns = snake(g.columns) | |
| for c in g.columns: | |
| if ptypes.is_datetime64_any_dtype(g[c].dtype): | |
| g[c] = g[c].astype(str) | |
| elif g[c].dtype == object: | |
| txt = g[c].str.strip() | |
| if txt.str.match(r"^-?\d+(\.\d+)?$").all(): | |
| g[c] = txt.astype(float) | |
| else: | |
| g[c] = txt | |
| return g | |
| caps_amb = {k: load_gjson(v) for k,v in GEO_AMBIENTALES.items() if v} | |
| wqi_bins = [0, 20, 35, 50, 70, 100] | |
| wqi_labels = ["Pobre", "Marginal", "Regular", "Buena", "Excelente"] | |
| wqi_colors = ["red", "olive", "purple", "green", "blue"] | |
| # 2) Extrae el GeoDataFrame de WQI y conviértelo a numérico | |
| g_wqi = caps_amb["WQI"].copy() | |
| g_wqi["wqi_val"] = pd.to_numeric(g_wqi["wqi"], errors="coerce") | |
| # 3) Crea la categoría | |
| g_wqi["wqi_cat"] = pd.cut( | |
| g_wqi["wqi_val"], | |
| bins=wqi_bins, | |
| labels=wqi_labels, | |
| include_lowest=True | |
| ) | |
| # 4) Construye el colormap por pasos | |
| WQI_COLORMAP = branca.colormap.StepColormap( | |
| colors=wqi_colors, | |
| index=wqi_bins, | |
| vmin=wqi_bins[0], | |
| vmax=wqi_bins[-1], | |
| caption="WQI" | |
| ) | |
| # 5) Guarda de nuevo en caps_amb | |
| caps_amb["WQI"] = g_wqi | |
| # ─── 4. Derivadas y flags ─────────────────────────────── | |
| df["genero_cat"] = df.get("genero","").str.capitalize() | |
| df["estrato_cat"] = df.get("estrato_socioeconomico","").str.capitalize() | |
| df["edad"] = pd.to_numeric(df.get("edad_en_anos_del_paciente","").str.replace(",", "."), errors="coerce") | |
| bins = list(range(0,105,5)) | |
| labels = [f"{b}-{b+4}" for b in bins[:-1]] | |
| df["edad_cat"] = pd.cut(df["edad"], bins=bins, labels=labels, right=False) | |
| df["anca_cat"] = df.get("ancas") | |
| df["mpo_cat"] = df.get("mpo") | |
| df["pr3_cat"] = df.get("pr3") | |
| df["sindrome_renal"] = df.get("sindrome_renal_al_ingreso","").str.capitalize() | |
| df["manifestaciones_extrarenales"] = df.get("manifestaciones_extrarenales","").str.capitalize() | |
| df["proteinuria"] = df.get("proteinuria","").str.capitalize() | |
| df["creatinina"] = pd.to_numeric(df.get("creatinina","").str.replace(",", "."), errors="coerce") | |
| ante_cols = { | |
| "diabetes":"antecedente_personal_de_diabetes", | |
| "falla_cardiaca":"antecedente_personal_de_falla_cardiaca", | |
| "epoc":"antecedente_personal_de_epoc", | |
| "hipertension":"antecedente_personal_de_hipertension_arterial", | |
| "vih":"antecedente_personal_de_vih", | |
| "autoinmune":"antecedente_personal_de_otra_enfermedad_autoinmune", | |
| "cancer":"antecedente_personal_de_cancer" | |
| } | |
| resumen_ante = { | |
| "diabetes":"Diabetes", | |
| "falla_cardiaca":"Falla cardíaca", | |
| "epoc":"EPOC", | |
| "hipertension":"Hipertensión", | |
| "vih":"VIH", | |
| "autoinmune":"Enf. autoinmune", | |
| "cancer":"Cáncer" | |
| } | |
| for key,col in ante_cols.items(): | |
| df[key] = (df.get(col,"0").astype(str).str.lower() | |
| .map({"si":1,"sí":1,"checked":1,"1":1}) | |
| .fillna(0).astype(int) | |
| ) | |
| bio_raw = [c for c in df.columns if c.startswith("hallazgos_histologicos_en_biopsia")] | |
| ren_bio = {c:f"bio_{i}" for i,c in enumerate(bio_raw,1)} | |
| df = df.rename(columns=ren_bio) | |
| bio_cols = list(ren_bio.values()) | |
| BIO_REGEX = [ | |
| (r"sin_alteraciones$", "Sin alteraciones"), | |
| (r"sin_proliferacion_extracapilar", "Necrosis sin PC"), | |
| (r"menos_del_50.*focal", "Focal"), | |
| (r"clase_mixta", "Mixta"), | |
| (r"mas_del_50.*cresc", "Crescéntica"), | |
| (r"sin_compromiso_glomerular$", "Vasculitis sin glom."), | |
| (r"con_compromiso_glomerular$", "Vasculitis + glom."), | |
| (r"sin_dato$", "Sin dato") | |
| ] | |
| # crear un dict raw_col → short | |
| raw2short = {} | |
| for patt, short in BIO_REGEX: | |
| raw = next(c for c in bio_raw if re.search(patt, c)) | |
| raw2short[raw] = short | |
| # después de raw2short = { … } | |
| resumen_bio_map = raw2short.copy() | |
| def patron_bio(row): | |
| for raw, flag in ren_bio.items(): | |
| if str(row[flag]).strip().lower() in ("si","sí","checked","1"): | |
| return raw2short.get(raw, "Sin dato") | |
| return "Sin dato" | |
| df["biopsia_patron"] = df.apply(patron_bio, axis=1) | |
| df["biopsia_positiva"] = np.where(df["biopsia_patron"]=="Sin dato","No","Si") | |
| # ─── 5. Filtrado ──────────────────────────────────────── | |
| def filtrar(d, gen, edades, locs, renal, ants, bios, anca, mpo, pr3): | |
| d2 = d.copy() | |
| if gen!="Todos": d2 = d2[d2["genero_cat"]==gen] | |
| if edades: d2 = d2[d2["edad_cat"].isin(edades)] | |
| if locs: d2 = d2[d2["localidad"].isin(locs)] | |
| if renal!="Todos": d2 = d2[d2["biopsia_positiva"]==renal] | |
| if bios and bios!=["Todos"]: | |
| d2 = d2[d2["biopsia_patron"].isin(bios)] | |
| if anca!="Todos": d2 = d2[d2["anca_cat"]==anca] | |
| if mpo!="Todos": d2 = d2[d2["mpo_cat"]==mpo] | |
| if pr3!="Todos": d2 = d2[d2["pr3_cat"]==pr3] | |
| for ant in ants: | |
| if ant=="Todos": continue | |
| key = next(k for k,v in resumen_ante.items() if v==ant) | |
| d2 = d2[d2[key]==1] | |
| return d2 | |
| # ─── 6. Mapas ─────────────────────────────────────────── | |
| # ─── 6. Mapas ─────────────────────────────────────────── | |
| def choropleth(m, g, val, title, cmap, zfield, zalias): | |
| g = g.copy() | |
| g[val] = pd.to_numeric(g[val], errors="coerce") | |
| vmin, vmax = g[val].min(), g[val].max() | |
| cm = cmap.scale(vmin, vmax) | |
| cm.caption = title | |
| cm.add_to(m) | |
| is_line = g.geometry.iloc[0].geom_type.startswith("Line") | |
| style = ( | |
| lambda f,vc=val: {"color":cm(f["properties"][vc]),"weight":4,"opacity":0.9} | |
| ) if is_line else ( | |
| lambda f,vc=val: {"fillColor":cm(f["properties"][vc]),"fillOpacity":0.8, | |
| "color":"black","weight":0.3} | |
| ) | |
| fields = [zfield, val] | |
| aliases = [zalias, title] | |
| for extra in ("nombre","rio"): | |
| if extra in g.columns: | |
| fields.append(extra); aliases.append("Río"); break | |
| folium.GeoJson( | |
| g, name=title, | |
| style_function=style, | |
| highlight_function=lambda f: {"weight":2,"color":"#444","fillOpacity":0.95}, | |
| tooltip=folium.GeoJsonTooltip(fields=fields, aliases=aliases, sticky=True) | |
| ).add_to(m) | |
| def capa_clusters(m, d): | |
| """ | |
| Añade al mapa m una capa de clústeres de pacientes (DBSCAN 1 km), | |
| con popups que muestran género, edad (si existe), patrón biopsia y antecedentes. | |
| """ | |
| if d.empty: | |
| return | |
| coords = np.radians(d[["latitud", "longitud"]].astype(float)) | |
| if len(coords) < 3: | |
| return | |
| labels = DBSCAN(eps=1/6371, min_samples=3, metric="haversine").fit_predict(coords) | |
| d = d.copy() | |
| d["cluster"] = labels | |
| pal = branca.colormap.linear.Set1_09 | |
| fg = folium.FeatureGroup(name="Clústeres (1 km)", overlay=True) | |
| for cl in sorted([c for c in d["cluster"].unique() if c != -1]): | |
| color = pal(cl / max(1, d["cluster"].nunique() - 1)) | |
| for _, r in d[d["cluster"] == cl].iterrows(): | |
| # Edad segura | |
| if pd.notna(r["edad"]) and not math.isnan(r["edad"]): | |
| edad_txt = f"{int(r['edad'])} años" | |
| else: | |
| edad_txt = "Sin dato edad" | |
| # Antecedentes resumidos | |
| ant = [v for k, v in resumen_ante.items() if r.get(k) == 1] | |
| ants_txt = "; ".join(ant) if ant else "Ninguno" | |
| popup = ( | |
| f"Clúster #{cl}<br>" | |
| f"Género: {r['genero_cat']}<br>" | |
| f"Edad: {edad_txt}<br>" | |
| f"Biopsia: {r['biopsia_patron']}<br>" | |
| f"Antecedentes: {ants_txt}" | |
| ) | |
| folium.CircleMarker( | |
| location=(r["latitud"], r["longitud"]), | |
| radius=6, | |
| color=color, | |
| fill=True, fill_color=color, fill_opacity=0.9, | |
| weight=1, | |
| popup=popup | |
| ).add_to(fg) | |
| fg.add_to(m) | |
| def crear_mapa(d_filt, capas, ver_cluster): | |
| """ | |
| Construye el mapa completo: | |
| - coroplético de pacientes por localidad | |
| - capas ambientales | |
| - heatmap de puntos | |
| - marcadores individuales con popups seguros | |
| - clústeres si ver_cluster=True | |
| """ | |
| # 1) Coroplético por localidad | |
| g = d_filt.groupby("localidad").size().reset_index(name="pacientes") | |
| geo = geo_loc.merge(g, on="localidad", how="left").fillna({"pacientes": 0}) | |
| m = folium.Map(location=[4.65, -74.1], zoom_start=11, tiles="CartoDB positron") | |
| choropleth( | |
| m, geo, "pacientes", "Pacientes por localidad (N)", | |
| branca.colormap.linear.Reds_09, "localidad", "Localidad" | |
| ) | |
| # 2) Capas ambientales | |
| for capa in capas: | |
| # 1) Saltar el heatmap aquí | |
| if capa == "Heatmap pacientes": | |
| continue | |
| # 2) WQI: paso discreto + leyenda | |
| if capa == "WQI": | |
| # Añadir la leyenda de WQI (continua o en pasos, como prefieras) | |
| WQI_COLORMAP.add_to(m) | |
| folium.GeoJson( | |
| caps_amb["WQI"], | |
| name="WQI (valor y categoría)", | |
| style_function=lambda f: { | |
| "color": WQI_COLORMAP(f["properties"]["wqi_val"]), | |
| "fillColor": WQI_COLORMAP(f["properties"]["wqi_val"]), | |
| "weight": 3, | |
| "fillOpacity": 0.7 | |
| }, | |
| tooltip=folium.GeoJsonTooltip( | |
| fields=["nombre", # nombre del río | |
| "tramo", # identificador de tramo | |
| "wqi_val"], # valor numérico de WQI | |
| aliases=["Río", # alias para nombre | |
| "Tramo", # alias para tramo | |
| "WQI (valor)"], # alias para wqi_val | |
| sticky=True | |
| ) | |
| ).add_to(m) | |
| continue # no volver a procesar esta capa | |
| # 3) Resto de capas: color continuo con tu choropleth genérico | |
| gdf = caps_amb.get(capa) | |
| val, uni, cmap, zfield, zalias = META_CAPAS[capa] | |
| if gdf is not None and val in gdf.columns: | |
| choropleth( | |
| m, | |
| gdf, | |
| val, | |
| f"{capa}{' ('+uni+')' if uni else ''}", | |
| cmap, | |
| zfield, | |
| zalias | |
| ) | |
| # 3) Heatmap de puntos | |
| if "Heatmap pacientes" in capas and not d_filt.empty: | |
| HeatMap( | |
| d_filt[["latitud", "longitud"]].astype(float).values, | |
| radius=18, name="Heatmap pacientes" | |
| ).add_to(m) | |
| # 4) Marcadores individuales | |
| fg_pts = folium.FeatureGroup(name="Puntos pacientes", overlay=True) | |
| for _, r in d_filt.iterrows(): | |
| # Edad segura | |
| if pd.notna(r["edad"]) and not math.isnan(r["edad"]): | |
| edad_txt = f"{int(r['edad'])} años" | |
| else: | |
| edad_txt = "Sin dato edad" | |
| # Antecedentes resumidos | |
| ant = [v for k, v in resumen_ante.items() if r.get(k) == 1] | |
| ants_txt = "<br>".join(ant) if ant else "Ninguno" | |
| popup_html = ( | |
| f"Localidad: {r['localidad']}<br>" | |
| f"Edad: {edad_txt}<br>" | |
| f"Género: {r['genero_cat']}<br>" | |
| f"Biopsia: {r['biopsia_patron']}<br>" | |
| f"Antecedentes:<br>{ants_txt}" | |
| ) | |
| folium.CircleMarker( | |
| location=(r["latitud"], r["longitud"]), | |
| radius=5, | |
| color="#c00", | |
| fill=True, fill_color="white", | |
| fill_opacity=0.85, weight=1, | |
| popup=popup_html | |
| ).add_to(fg_pts) | |
| fg_pts.add_to(m) | |
| # 5) Capa de clústeres opcional | |
| if ver_cluster: | |
| capa_clusters(m, d_filt) | |
| folium.LayerControl(collapsed=False).add_to(m) | |
| return m._repr_html_() | |
| # ─── 7. Gráficos ───────────────────────────────────────── | |
| def col_of(v): | |
| """Mapea nombre legible a columna interna.""" | |
| if v in resumen_ante.values(): | |
| return next(k for k,val in resumen_ante.items() if val==v) | |
| if v in raw2short.values() or v=="Patrón biopsia": | |
| return "biopsia_patron" | |
| return v | |
| def g_uni(var, d): | |
| if d.empty: | |
| return go.Figure() | |
| col = col_of(var) | |
| # 1) Flags de antecedentes (0/1) → barras de conteo "No"/"Si" | |
| if var in resumen_ante.values(): | |
| s = d[col].map({0:"No",1:"Si"}) | |
| fig = px.histogram(s, x=s, | |
| category_orders={col:["No","Si"]}, | |
| text_auto=True, | |
| title=var) | |
| # 2) Patrón biopsia → barras de conteo de cada categoría | |
| elif var=="Patrón biopsia" or var in raw2short.values(): | |
| fig = px.histogram(d, x="biopsia_patron", | |
| category_orders={"biopsia_patron": list(raw2short.values())}, | |
| text_auto=True, | |
| title="Patrón biopsia") | |
| # 3) Variables numéricas → histograma | |
| elif d[col].dtype.kind in "if": | |
| fig = px.histogram(d, x=col, nbins=20, title=var) | |
| # 4) Resto categóricas → barras de conteo con color | |
| else: | |
| fig = px.histogram(d, x=col, color=col, text_auto=True, title=var) | |
| fig.update_layout(bargap=0.1) | |
| return fig | |
| def g_bi(x, y, d): | |
| """ | |
| Gráfico bivariado: | |
| - num vs num → scatter con trendline | |
| - num vs cat → boxplot | |
| - cat vs cat → barras agrupadas | |
| Reconoce correctamente: | |
| • Patrones de biopsia (incluida la etiqueta "Patrón biopsia") | |
| • Etiquetas de antecedentes. | |
| """ | |
| if d.empty: | |
| return go.Figure() | |
| # Mapeo de la variable de UI al nombre real de columna en df | |
| def map_var(v): | |
| # Dropdown de patrón de biopsia (UI) → columna biop_patron | |
| if v == "Patrón biopsia": | |
| return "biopsia_patron" | |
| # Cualquier etiqueta corta de biopsia | |
| if v in resumen_bio_map.values(): | |
| return "biopsia_patron" | |
| # Etiqueta de antecedente → nombre de flag en df | |
| for key, lab in resumen_ante.items(): | |
| if v == lab: | |
| return key | |
| # Variables numéricas o de texto sin transformar | |
| return v | |
| cx = map_var(x) | |
| cy = map_var(y) | |
| # Determinar si cada una es categórica (flags, biopsia o texto) | |
| is_cat = {} | |
| for var in (cx, cy): | |
| is_cat[var] = ( | |
| var == "biopsia_patron" | |
| or var in resumen_ante.keys() | |
| or d[var].dtype == object | |
| ) | |
| # 1) cat vs cat → histograma agrupado | |
| if is_cat[cx] and is_cat[cy]: | |
| fig = px.histogram( | |
| d, | |
| x=cx, | |
| color=cy, | |
| barmode="group", | |
| category_orders={ | |
| cx: list(resumen_bio_map.values()) if cx=="biopsia_patron" else list(resumen_ante.values()), | |
| cy: list(resumen_bio_map.values()) if cy=="biopsia_patron" else list(resumen_ante.values()), | |
| }, | |
| labels={cx: x, cy: y}, | |
| title=f"{x} vs {y}" | |
| ) | |
| # 2) num vs cat → boxplot | |
| elif is_cat[cx] ^ is_cat[cy]: | |
| # uno es categórico, otro numérico | |
| if is_cat[cx]: | |
| fig = px.box( | |
| d, | |
| x=cx, | |
| y=cy, | |
| points="all", | |
| category_orders={cx: list(resumen_bio_map.values()) if cx=="biopsia_patron" else list(resumen_ante.values())}, | |
| labels={cx: x, cy: y}, | |
| title=f"{x} vs {y}" | |
| ) | |
| else: | |
| fig = px.box( | |
| d, | |
| x=cy, | |
| y=cx, | |
| points="all", | |
| category_orders={cy: list(resumen_bio_map.values()) if cy=="biopsia_patron" else list(resumen_ante.values())}, | |
| labels={cx: x, cy: y}, | |
| title=f"{x} vs {y}" | |
| ) | |
| # 3) num vs num → scatter + trendline | |
| else: | |
| fig = px.scatter( | |
| d, | |
| x=cx, | |
| y=cy, | |
| trendline="ols", | |
| labels={cx: x, cy: y}, | |
| title=f"{x} vs {y}" | |
| ) | |
| fig.update_layout(bargap=0.1) | |
| return fig | |
| # ─── 8. Interfaz Gradio ─────────────────────────────────── | |
| def interfaz(): | |
| gen = ["Todos"] + sorted(df["genero_cat"].dropna().unique()) | |
| ages = sorted(df["edad_cat"].dropna().unique()) | |
| locs = sorted(df["localidad"].dropna().unique()) | |
| ancas = ["Todos"] + sorted(df["anca_cat"].dropna().unique()) | |
| mpos = ["Todos"] + sorted(df["mpo_cat"].dropna().unique()) | |
| pr3s = ["Todos"] + sorted(df["pr3_cat"].dropna().unique()) | |
| vars_cat = [ | |
| "genero_cat","estrato_cat","edad_cat","sindrome_renal", | |
| "manifestaciones_extrarenales","proteinuria", | |
| "anca_cat","mpo_cat","pr3_cat" | |
| ] + ["Patrón biopsia"] + list(resumen_ante.values()) | |
| vars_num = ["edad","creatinina"] | |
| vars_all = vars_cat + vars_num | |
| with gr.Blocks(title="Vasculitis ANCA Bogotá") as demo: | |
| gr.Markdown("## Explorador geoespacial – Vasculitis ANCA (Bogotá)") | |
| with gr.Row(): | |
| ui_gen = gr.Dropdown(gen, label="Género", value="Todos") | |
| ui_age = gr.CheckboxGroup(ages, label="Edad (quinquenios)") | |
| ui_loc = gr.Dropdown(locs, multiselect=True, label="Localidades") | |
| ui_renal = gr.Dropdown(["Todos","Si","No"], value="Todos", label="Compromiso renal") | |
| ui_ant = gr.CheckboxGroup(["Todos"]+list(resumen_ante.values()), label="Antecedentes") | |
| ui_bio = gr.CheckboxGroup(["Todos"]+list(raw2short.values()), label="Patrón biopsia") | |
| with gr.Row(): | |
| ui_anca = gr.Dropdown(ancas, label="ANCA", value="Todos") | |
| ui_mpo = gr.Dropdown(mpos, label="MPO", value="Todos") | |
| ui_pr3 = gr.Dropdown(pr3s, label="PR3", value="Todos") | |
| ui_capas = gr.CheckboxGroup(list(GEO_AMBIENTALES.keys()), label="Capas mapa") | |
| ui_clu = gr.Checkbox(label="Mostrar clústeres (1 km)") | |
| with gr.Tab("Mapa"): | |
| btn_map = gr.Button("Generar mapa") | |
| out_map = gr.HTML() | |
| btn_map.click( | |
| lambda *i: crear_mapa(filtrar(df,*i[:-2]), i[-2], i[-1]), | |
| inputs=[ui_gen,ui_age,ui_loc,ui_renal, | |
| ui_ant,ui_bio,ui_anca,ui_mpo,ui_pr3, | |
| ui_capas,ui_clu], | |
| outputs=out_map | |
| ) | |
| with gr.Tab("Univariado"): | |
| ui_var = gr.Dropdown(vars_all, label="Variable") | |
| btn_uni = gr.Button("Graficar") | |
| out_uni = gr.Plot() | |
| btn_uni.click( | |
| lambda v,*i: g_uni(v, filtrar(df,*i)), | |
| inputs=[ui_var,ui_gen,ui_age,ui_loc,ui_renal, | |
| ui_ant,ui_bio,ui_anca,ui_mpo,ui_pr3], | |
| outputs=out_uni | |
| ) | |
| with gr.Tab("Bivariado"): | |
| ui_x = gr.Dropdown(vars_all, label="Variable X") | |
| ui_y = gr.Dropdown(vars_all, label="Variable Y") | |
| btn_bi = gr.Button("Graficar") | |
| out_bi = gr.Plot() | |
| btn_bi.click( | |
| lambda x,y,*i: g_bi(x,y, filtrar(df,*i)), | |
| inputs=[ui_x,ui_y,ui_gen,ui_age,ui_loc,ui_renal, | |
| ui_ant,ui_bio,ui_anca,ui_mpo,ui_pr3], | |
| outputs=out_bi | |
| ) | |
| demo.launch() | |
| if __name__ == "__main__": | |
| interfaz() | |