Spaces:
Sleeping
Sleeping
| import os | |
| import pandas as pd | |
| import csv | |
| import plotly.express as px | |
| import gradio as gr | |
| import folium | |
| from folium.plugins import MarkerCluster | |
| # Función para cargar CSV de oficinas | |
| def load_offices_csv(path): | |
| with open(path, encoding='latin-1') as f: | |
| sample = ''.join([next(f) for _ in range(10)]) | |
| dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|']) | |
| sep = dialect.delimiter | |
| print(f"Delimitador detectado: '{sep}'".replace('{sep}', sep)) | |
| df = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip') | |
| df.columns = [c.strip() for c in df.columns] | |
| return df | |
| # Carga y preparación de datos | |
| DATA_XLSX = "Base de Datos Prueba.xlsx" | |
| OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv" | |
| df = pd.read_excel(DATA_XLSX, parse_dates=["FECHA_APERTURA"]) | |
| df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp() | |
| off_meta = load_offices_csv(OFFICES_CSV) | |
| # Listas estáticas iniciales (convertir a str para evitar mezclas int/str) | |
| departamentos = sorted(off_meta['DEPARTAMENTO'].dropna().astype(str).unique().tolist()) | |
| municipios = sorted(off_meta['CIUDAD'].dropna().astype(str).unique().tolist()) | |
| zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist()) | |
| productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist()) | |
| colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist()) | |
| segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist()) | |
| min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max()) | |
| min_plazo, max_plazo= int(df['PLAZO'].min()), int(df['PLAZO'].max()) | |
| # Coordenadas de oficinas | |
| office_coords = { | |
| "Abrego": (8.080040, -73.219050), | |
| "Aguachica": (8.310229, -73.599837), | |
| "Aguazul": (5.171710, -72.547300), | |
| "Agustin Codazzi": (10.033471, -73.291284), | |
| "Andes": (5.655900, -75.879512), | |
| "Apartado": (7.885610, -76.634790), | |
| "Arauca": (7.086173, -70.757347), | |
| "Arjona": (10.297838, -75.308821), | |
| "Armenia": (6.167542, -75.764031), | |
| "Av. Caracas": (4.582934, -74.092877), | |
| "Ayapel": (0.000000, 0.000000), | |
| "Barrancabermeja": (7.059381, -73.862874), | |
| "Barrancas": (10.956670, -72.788870), | |
| "Barranquilla": (10.961041, -74.800959), | |
| "Belen": (5.989720, -72.913860), | |
| "Bello": (6.334930, -75.558280), | |
| "Bienestar Social Empleados": (0.000000, 0.000000), | |
| "Bosa": (5.930960, -73.616320), | |
| "Bosconia": (9.972841, -73.885721), | |
| "Bucaramanga Centro": (7.092868, -73.126498), | |
| "Buenaventura": (3.877616, -77.007365), | |
| "Buenos Aires": (3.013970, -76.646120), | |
| "Buga": (6.254484, -75.563634), | |
| "Cachingos": (0.000000, 0.000000), | |
| "Caldas": (6.089999, -75.636627), | |
| "Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226), | |
| "Cali Norte": (6.300640, -70.205430), | |
| "Cali Sur": (0.000000, 0.000000), | |
| "Canal C": (0.000000, 0.000000), | |
| "Candelaria": (3.408325, -76.349086), | |
| "Carepa": (7.757548, -76.656274), | |
| "Carmen de Viboral": (6.082360, -75.335090), | |
| "Cartagena": (0.000000, 0.000000), | |
| "Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000), | |
| "Cartago": (4.746743, -75.913598), | |
| "Castilla": (3.827220, -73.688310), | |
| "Caucasia": (7.987347, -75.196855), | |
| "Centro Bogota (NO VIGENTE)": (0.000000, 0.000000), | |
| "Centro MedelliAn": (6.254484, -75.563634), | |
| "Centro Suba": (5.828915, -73.035056), | |
| "Cerrito": (6.842993, -72.694730), | |
| "Chaparral": (3.750151, -75.340480), | |
| "Chia": (4.859712, -74.059663), | |
| "Chinacota": (7.607990, -72.600380), | |
| "Chinchina": (4.984375, -75.604801), | |
| "Chiquinquira": (5.618910, -73.819970), | |
| "Cienaga de Oro": (8.874380, -75.621750), | |
| "Cimitarra": (6.316110, -73.950540), | |
| "Copacabana": (6.348513, -75.507062), | |
| "Corozal": (9.317780, -75.295830), | |
| "Cucuta Atalaya": (7.907352, -72.524705), | |
| "Cucuta Centro": (7.884556, -72.504855), | |
| "Dabeiba": (7.033533, -76.167771), | |
| "Direccion General": (0.000000, 0.000000), | |
| "Duitama": (5.807690, -73.070165), | |
| "El Bagre": (7.596620, -74.804880), | |
| "El Banco": (8.998900, -73.970580), | |
| "El Cable (NO VIGENTE)": (0.000000, 0.000000), | |
| "El Tejar (NO VIGENTE)": (0.000000, 0.000000), | |
| "Engativa": (6.254484, -75.563634), | |
| "Envigado": (6.173196, -75.592097), | |
| "Espinal (NO VIGENTE)": (0.000000, 0.000000), | |
| "Facatativa": (4.811561, -74.384369), | |
| "Florencia": (1.682220, -77.072610), | |
| "Florida": (3.324420, -76.235460), | |
| "Floridablanca": (7.079171, -73.108311), | |
| "Fonseca": (10.888700, -72.851530), | |
| "Fontibon": (6.777790, -76.128580), | |
| "Fundacion": (10.521380, -74.186640), | |
| "Fundadores (NO VIGENTE)": (0.000000, 0.000000), | |
| "Funza": (4.714865, -74.212997), | |
| "Fusagasuga": (4.311530, -74.355406), | |
| "Galan": (6.637810, -73.288780), | |
| "Garagoa": (5.083373, -73.363727), | |
| "Garzon": (2.195783, -75.629006), | |
| "Gerencia Territorial - Sur": (0.000000, 0.000000), | |
| "Girardot": (4.303965, -74.804788), | |
| "Giron": (7.074196, -73.167534), | |
| "Granada": (4.519657, -74.353677), | |
| "Guaduas": (5.067815, -74.598816), | |
| "Ibague": (4.325569, -75.072920), | |
| "Ibague Centro (NO VIGENTE)": (0.000000, 0.000000), | |
| "Ipiales": (0.836103, -77.679298), | |
| "Itagui": (6.170260, -75.616540), | |
| "Jamundi": (3.111418, -76.606186), | |
| "Kennedy": (4.622480, -74.150010), | |
| "La America": (5.400098, -75.546666), | |
| "La Calera": (4.721216, -73.968126), | |
| "La Ceja": (6.032140, -75.431942), | |
| "La Dorada": (5.464481, -74.704063), | |
| "La Libertad": (2.445261, -76.632240), | |
| "La Plata": (2.391670, -75.891670), | |
| "La Union": (8.857282, -75.277048), | |
| "La Union - Valle": (4.537120, -76.104421), | |
| "La Victoria": (-0.111490, -71.110860), | |
| "Leticia": (-4.215596, -69.939065), | |
| "Libano": (4.922540, -75.063700), | |
| "Lorica (NO VIGENTE)": (0.000000, 0.000000), | |
| "Magangue": (9.186535, -74.788838), | |
| "Maicao": (11.350383, -72.352333), | |
| "Malaga": (6.702446, -72.731766), | |
| "Manizales": (5.050927, -75.519500), | |
| "Manrique": (6.484975, -75.019537), | |
| "Marinilla": (6.173840, -75.334800), | |
| "Mariquita": (5.198660, -74.896950), | |
| "Mesitas": (3.383561, -74.044270), | |
| "Minorista": (0.000000, 0.000000), | |
| "Mitu": (1.255250, -70.233390), | |
| "MoAitos": (0.000000, 0.000000), | |
| "Mocoa": (1.148930, -76.647750), | |
| "Mod Empoderados Costa Norte": (0.000000, 0.000000), | |
| "Mod Empoderados Plus-Bta": (0.000000, 0.000000), | |
| "Mod Empoderados Plus-M/llin": (0.000000, 0.000000), | |
| "Mod Empoderados Plus-Sur": (0.000000, 0.000000), | |
| "Molinos": (10.701780, -74.716750), | |
| "Mompox": (0.000000, 0.000000), | |
| "Moniquira": (5.877280, -73.570440), | |
| "MonteliAbano": (7.983010, -75.417260), | |
| "MonteriAa Centro": (8.754728, -75.881810), | |
| "Monteria": (8.773391, -75.817808), | |
| "Neiva (NO VIGENTE)": (0.000000, 0.000000), | |
| "Niquia": (2.649380, -75.636650), | |
| "OcaAa": (8.233420, -73.353310), | |
| "Orito": (0.665371, -76.872392), | |
| "PE Soledad Malambo": (10.861604, -74.773950), | |
| "PE Acacias": (3.987212, -73.765837), | |
| "PE Anserma": (5.230240, -75.787920), | |
| "PE Baranoa": (10.796676, -74.914419), | |
| "PE Belen": (5.989720, -72.913860), | |
| "PE Cali La Casona": (0.000000, 0.000000), | |
| "PE Cartagena del ChairA": (1.334860, -74.843460), | |
| "PE Dosquebradas": (4.839160, -75.667270), | |
| "PE Istmina": (0.000000, 0.000000), | |
| "PE La Pintada 2": (5.749960, -75.616299), | |
| "PE Majagual": (8.534730, -74.657180), | |
| "PE Pasto (NO VIGENTE)": (0.000000, 0.000000), | |
| "PE Puerto Wilches": (7.348801, -73.898273), | |
| "PE PurificaciA3n": (3.856779, -74.932103), | |
| "PE Santa Rosa del Sur": (7.946502, -74.026033), | |
| "PE TAoquerres": (0.000000, 0.000000), | |
| "PE Tame": (6.470290, -71.716970), | |
| "PE Valle del Guamuez": (0.452500, -76.919170), | |
| "PE Zarzal": (4.395820, -76.069830), | |
| "Pailitas": (8.957360, -73.623460), | |
| "Palmira": (3.538908, -76.298466), | |
| "Pamplona": (7.377155, -72.648957), | |
| "Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000), | |
| "Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000), | |
| "Pasto": (0.000000, 0.000000), | |
| "Patio Bonito": (6.205447, -75.575411), | |
| "Paz de Ariporo": (5.881428, -71.891972), | |
| "Pedregal": (6.254484, -75.563634), | |
| "Perdomo": (2.888170, -75.433810), | |
| "Pereira": (4.812216, -75.692047), | |
| "Piedecuesta": (6.988034, -73.050030), | |
| "PiendamA3 (NO VIGENTE)": (0.000000, 0.000000), | |
| "Pitalito": (1.790464, -76.055636), | |
| "Pivijay": (10.447166, -74.408568), | |
| "Planeta Rica": (8.408920, -75.586800), | |
| "Plato": (9.791910, -74.782970), | |
| "Popayan Norte": (2.462388, -76.535919), | |
| "Popayan Sur": (2.462388, -76.535919), | |
| "Primero de Mayo": (5.821771, -73.043034), | |
| "Principal": (13.375420, -81.369090), | |
| "Puerto Asis": (0.497650, -76.497680), | |
| "Puerto Berrio": (6.490949, -74.402668), | |
| "Puerto Boyaca": (5.971557, -74.571408), | |
| "Puerto CarreAo": (6.190854, -67.484779), | |
| "Puerto IniArida": (-2.148770, -71.754990), | |
| "Puerto Libertador": (7.889560, -75.672370), | |
| "Punto Express Neiva 2": (0.000000, 0.000000), | |
| "Quibdo": (0.000000, 0.000000), | |
| "Quirigua": (2.649380, -75.636650), | |
| "Restrepo": (3.826566, -76.521106), | |
| "Riohacha": (11.381478, -72.905309), | |
| "Rionegro": (7.264871, -73.147840), | |
| "Riosucio": (5.421350, -75.703230), | |
| "Robledo (NO VIGENTE)": (0.000000, 0.000000), | |
| "Sahagun": (8.941133, -75.495272), | |
| "San AndrAs Isla": (6.809803, -72.849736), | |
| "San Andres de Sotavento": (9.144750, -75.508770), | |
| "San Bernardo": (4.178771, -74.421565), | |
| "San Fernando": (9.218060, -74.330294), | |
| "San Francisco (NO VIGENTE)": (0.000000, 0.000000), | |
| "San Gil": (6.557700, -73.133180), | |
| "San Jose del Guaviare": (2.562393, -72.640344), | |
| "San Juan": (4.461030, -73.680480), | |
| "San Marcos": (8.658430, -75.131200), | |
| "San Onofre": (9.737530, -75.525580), | |
| "San Pelayo (NO VIGENTE)": (0.000000, 0.000000), | |
| "San Vicente del Caguan": (2.113170, -74.769180), | |
| "San Vicente del Chucuri": (6.881050, -73.411570), | |
| "Santa Helenita": (10.325410, -74.961830), | |
| "Santa MariAa (NO VIGENTE)": (0.000000, 0.000000), | |
| "Santa Marta Av. Libertador": (11.231008, -74.175841), | |
| "Santa Rosa de Cabal": (4.890245, -75.626971), | |
| "Santafe de Antioquia": (6.556870, -75.828060), | |
| "Santander de Quilichao": (3.008790, -76.485900), | |
| "Santo Domingo": (6.472363, -75.164506), | |
| "Sincelejo": (9.300213, -75.395603), | |
| "Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834), | |
| "Soacha": (4.582123, -74.211534), | |
| "Sogamoso": (5.718314, -72.930984), | |
| "Soledad (NO VIGENTE)": (0.000000, 0.000000), | |
| "Suba": (5.451280, -73.814140), | |
| "Suba Rincon": (4.728370, -74.088350), | |
| "Tesoreria": (0.000000, 0.000000), | |
| "Tierralta": (8.173570, -76.059210), | |
| "ToberiAn": (0.000000, 0.000000), | |
| "Tulua": (4.084320, -76.196650), | |
| "Tumaco": (0.000000, 0.000000), | |
| "Tunja": (5.538590, -73.366380), | |
| "Turbo": (8.098040, -76.731690), | |
| "Ubate": (4.482420, -73.934840), | |
| "Urrao": (6.340116, -76.097593), | |
| "Valledupar": (10.469026, -73.257035), | |
| "Valledupar Centro": (10.476217, -73.245945), | |
| "Velez": (6.012930, -73.673140), | |
| "Venecia": (4.088080, -74.477460), | |
| "Villanueva": (4.609834, -72.927380), | |
| "Villavicencio": (4.144229, -73.634525), | |
| "Villeta": (5.011370, -74.471560), | |
| "Yarumal": (6.962765, -75.416779), | |
| "Yomasa": (4.519873, -74.092186), | |
| "Yopal": (5.340170, -72.394240), | |
| "Yumbo": (3.581378, -76.494648), | |
| "Zipaquira": (5.025810, -73.991283), | |
| } | |
| # Callbacks | |
| def update_municipios(dept): | |
| return sorted(off_meta.dropna().astype(str).unique().tolist()) | |
| def update_zonas(dept, muni): | |
| df2 = off_meta | |
| return sorted(df2['ZONA'].dropna().astype(str).unique().tolist()) | |
| def update_oficinas(dept, muni, zona): | |
| if zona: | |
| df2 = df2[df2['ZONA']==zona] | |
| return sorted(df2['NOMBRE OFICINA'].dropna().astype(str).unique().tolist()) | |
| # Dashboard | |
| def dashboard(f_inicio, f_fin, zona, tipos, | |
| colaborador_sel, segmento_sel): | |
| d = df.copy() | |
| if f_inicio: | |
| d = d[d['FECHA_APERTURA'] >= pd.to_datetime(f_inicio)] | |
| if f_fin: | |
| d = d[d['FECHA_APERTURA'] <= pd.to_datetime(f_fin)] | |
| if tipos: | |
| d = d[d['TIPO PRODUCTO'].astype(str).isin(tipos)] | |
| if colaborador_sel: | |
| d = d[d['SK_COLABORADOR'].astype(str).isin(colaborador_sel)] | |
| if segmento_sel: | |
| d = d[d['SEGMENTO_CLIENTE'].astype(str).isin(segmento_sel)] | |
| fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I', | |
| labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto desembolsado por mes') | |
| df2 = d['TIPO PRODUCTO'].value_counts().reset_index() | |
| df2.columns = ['TIPO PRODUCTO','CANT'] | |
| fig2 = px.pie(df2, names='TIPO PRODUCTO', values='CANT', title='Distribución por producto') | |
| fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas') | |
| df_col = d['SK_COLABORADOR'].value_counts().reset_index() | |
| df_col.columns = ['Colaborador','CANT'] | |
| fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores') | |
| fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)') | |
| df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index() | |
| df_seg.columns = ['Segmento','CANT'] | |
| fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento') | |
| m = folium.Map(location=[4.6, -74.1], zoom_start=6) | |
| mc = MarkerCluster().add_to(m) | |
| for ofi, coord in office_coords.items(): | |
| sub = d[d['OFICINA'].astype(str)==ofi] | |
| if sub.empty: | |
| continue | |
| total = sub['MONTO_I'].sum() | |
| folium.CircleMarker(location=coord, radius=6, fill=True, | |
| popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc) | |
| return fig1, fig2, fig3, fig4, fig5, fig6, m._repr_html_() | |
| # Interfaz Gradio | |
| with gr.Blocks() as demo: | |
| gr.Markdown("## Dashboard Bancamía – Análisis Exploratorio") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| f_inicio = gr.Textbox(label="Fecha inicio (YYYY-MM-DD)", value="2025-01-01") | |
| f_fin = gr.Textbox(label="Fecha fin (YYYY-MM-DD)", value="2025-03-31") | |
| zona = gr.Dropdown(zonas, label="Zona") | |
| tipos = gr.CheckboxGroup(choices=productos, label="Tipo de producto") | |
| colabor = gr.Dropdown(colaboradores, label="Colaborador", multiselect=True) | |
| segmento = gr.Dropdown(segmentos, label="Segmento", multiselect=True) | |
| btn = gr.Button("Actualizar") | |
| with gr.Column(scale=3): | |
| out1 = gr.Plot(); out2 = gr.Plot(); out3 = gr.Plot() | |
| out4 = gr.Plot(); out5 = gr.Plot(); out6 = gr.Plot() | |
| out7 = gr.HTML() | |
| btn.click(dashboard, [f_inicio, f_fin, zona, tipos, colabor, segmento], | |
| [out1, out2, out3, out4, out5, out6, out7]) | |
| if __name__ == "__main__": | |
| demo.launch() | |