Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
import pandas as pd
|
2 |
-
import matplotlib.pyplot as plt
|
3 |
import numpy as np
|
4 |
-
import
|
5 |
-
|
6 |
-
import seaborn as sns
|
7 |
import gradio as gr
|
8 |
-
from
|
|
|
|
|
9 |
|
10 |
cities_data = {
|
11 |
'Abancay': {
|
@@ -1151,17 +1151,15 @@ cities_data = {
|
|
1151 |
}
|
1152 |
|
1153 |
|
1154 |
-
|
1155 |
-
|
1156 |
-
|
1157 |
-
plt.style.use('seaborn-v0_8-whitegrid')
|
1158 |
|
1159 |
COLORES = {
|
1160 |
'Total': '#2C3E50',
|
1161 |
'Hombres': '#3498DB',
|
1162 |
'Mujeres': '#E74C3C',
|
1163 |
-
'Brecha': '#8E44AD'
|
1164 |
-
'Fondo': '#F8F9F9'
|
1165 |
}
|
1166 |
|
1167 |
def normalizar_nombres_ciudades(nombre):
|
@@ -1176,388 +1174,251 @@ def normalizar_nombres_ciudades(nombre):
|
|
1176 |
'Pura': 'Piura',
|
1177 |
'Posalipa': 'Pucallpa',
|
1178 |
'Tagapito': 'Talara',
|
1179 |
-
'Juliana': 'Juliaca'
|
|
|
|
|
|
|
1180 |
}
|
1181 |
return correcciones.get(nombre, nombre)
|
1182 |
|
1183 |
-
def
|
1184 |
-
|
1185 |
-
|
1186 |
-
|
1187 |
-
|
1188 |
-
if 'Q' in t:
|
1189 |
-
a帽o = t.split('-')[0]
|
1190 |
-
trimestre = t.split('-')[1]
|
1191 |
-
mes = {'Q1': '03', 'Q2': '06', 'Q3': '09', 'Q4': '12'}[trimestre]
|
1192 |
-
fechas.append(f"{a帽o}-{mes}")
|
1193 |
-
else:
|
1194 |
-
fechas.append(f"{t}-01")
|
1195 |
-
df['fecha_orden'] = pd.to_datetime(fechas, format='%Y-%m')
|
1196 |
-
else:
|
1197 |
-
fechas = []
|
1198 |
-
for p in df[col_fecha]:
|
1199 |
-
if '/' in p:
|
1200 |
-
fechas.append(p.split('/')[0])
|
1201 |
-
else:
|
1202 |
-
fechas.append(f"{p}-01")
|
1203 |
-
df['fecha_orden'] = pd.to_datetime(fechas, format='%Y-%m', errors='coerce')
|
1204 |
-
df = df.sort_values('fecha_orden')
|
1205 |
-
return df
|
1206 |
-
|
1207 |
-
def calcular_rango_y(df, categorias, padding=0.15):
|
1208 |
-
valores = df[categorias].values.flatten()
|
1209 |
-
valores = valores[~np.isnan(valores)]
|
1210 |
-
if len(valores) == 0:
|
1211 |
-
return (0, 1)
|
1212 |
-
min_val = np.nanmin(valores)
|
1213 |
-
max_val = np.nanmax(valores)
|
1214 |
-
rango = max_val - min_val
|
1215 |
-
return (max(0, min_val - rango*padding), max_val + rango*padding)
|
1216 |
-
|
1217 |
-
def graficar_datos_mejorados(df, titulo, subtitulo, ylabel, col_fecha='Trimestre', mostrar_valores=True, formato_valores='.1f'):
|
1218 |
-
fig, ax = plt.subplots(figsize=(14, 8))
|
1219 |
-
ax.set_facecolor(COLORES['Fondo'])
|
1220 |
-
fig.patch.set_facecolor(COLORES['Fondo'])
|
1221 |
-
ax.grid(axis='y', linestyle='--', alpha=0.7)
|
1222 |
-
|
1223 |
-
categorias = df.columns[1:4]
|
1224 |
-
x_indices = np.arange(len(df))
|
1225 |
-
ylim = calcular_rango_y(df, categorias)
|
1226 |
-
|
1227 |
-
for categoria in categorias:
|
1228 |
-
if categoria in df.columns and not df[categoria].isna().all():
|
1229 |
-
y_vals = df[categoria].values
|
1230 |
-
valid_mask = ~np.isnan(y_vals)
|
1231 |
-
|
1232 |
-
ax.plot(x_indices[valid_mask], y_vals[valid_mask],
|
1233 |
-
marker='o', linewidth=3, markersize=8,
|
1234 |
-
label=categoria, color=COLORES[categoria])
|
1235 |
-
|
1236 |
-
if mostrar_valores:
|
1237 |
-
for i, valor in zip(x_indices[valid_mask], y_vals[valid_mask]):
|
1238 |
-
offset = 0.2 if categoria == 'Hombres' else -0.8 if categoria == 'Mujeres' else 0
|
1239 |
-
ax.annotate(f'{valor:{formato_valores}}',
|
1240 |
-
xy=(i, valor),
|
1241 |
-
xytext=(0, 5 + offset),
|
1242 |
-
textcoords='offset points',
|
1243 |
-
ha='center', va='bottom',
|
1244 |
-
fontsize=10, fontweight='bold',
|
1245 |
-
color=COLORES[categoria],
|
1246 |
-
bbox=dict(boxstyle='round,pad=0.3', fc='white', alpha=0.7))
|
1247 |
-
|
1248 |
-
ax.set_title(titulo, fontsize=18, fontweight='bold', pad=20)
|
1249 |
-
plt.figtext(0.5, 0.01, subtitulo, ha='center', fontsize=12, fontstyle='italic')
|
1250 |
-
ax.set_ylabel(ylabel, fontsize=14, fontweight='bold')
|
1251 |
-
ax.set_xticks(x_indices)
|
1252 |
-
ax.set_xticklabels(df[col_fecha].astype(str), rotation=45, ha='right')
|
1253 |
-
ax.set_ylim(ylim)
|
1254 |
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1262 |
return fig
|
1263 |
|
1264 |
-
def
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
'
|
1269 |
-
'
|
1270 |
-
'
|
1271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1272 |
|
1273 |
-
|
1274 |
-
|
1275 |
-
|
1276 |
-
for ciudad,
|
1277 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1278 |
-
df =
|
1279 |
-
|
1280 |
if not df.empty:
|
1281 |
-
|
1282 |
-
|
|
|
|
|
|
|
1283 |
|
1284 |
-
|
1285 |
-
fontsize=18, fontweight='bold', pad=20)
|
1286 |
-
ax_desempleo.set_ylabel('Tasa (%)', fontsize=14)
|
1287 |
-
ax_desempleo.grid(True, linestyle='--', alpha=0.5)
|
1288 |
-
ax_desempleo.xaxis.set_major_locator(mdates.YearLocator())
|
1289 |
-
ax_desempleo.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
|
1290 |
|
1291 |
-
|
1292 |
-
|
1293 |
-
|
1294 |
-
|
1295 |
-
|
1296 |
-
|
1297 |
-
|
1298 |
-
|
1299 |
-
|
1300 |
-
|
1301 |
-
|
1302 |
-
|
1303 |
-
|
|
|
|
|
|
|
|
|
1304 |
|
1305 |
-
|
1306 |
-
|
1307 |
-
|
1308 |
-
|
|
|
|
|
|
|
|
|
|
|
1309 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1310 |
-
df =
|
1311 |
-
|
1312 |
-
'Periodo')
|
1313 |
if not df.empty:
|
1314 |
-
|
1315 |
-
|
1316 |
|
1317 |
-
|
1318 |
-
|
1319 |
-
|
1320 |
-
|
1321 |
-
|
1322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1323 |
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
bbox_to_anchor=(0.5, -0.12),
|
1328 |
-
ncol=5,
|
1329 |
-
fontsize=10,
|
1330 |
-
frameon=True,
|
1331 |
-
fancybox=True,
|
1332 |
-
shadow=True,
|
1333 |
-
title='Ciudades',
|
1334 |
-
title_fontsize='12')
|
1335 |
-
fig_ingresos.tight_layout(rect=[0, 0.1, 1, 0.95])
|
1336 |
-
figuras.append(fig_ingresos)
|
1337 |
-
|
1338 |
-
# Gr谩fico de Brecha Salarial Global
|
1339 |
-
fig_brecha = Figure(figsize=(16, 10))
|
1340 |
-
ax_brecha = fig_brecha.add_subplot(111)
|
1341 |
-
for ciudad, datos in cities_data.items():
|
1342 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1343 |
-
df =
|
1344 |
-
|
1345 |
-
|
1346 |
-
|
1347 |
-
df['
|
1348 |
-
|
1349 |
-
label=nombre, **estilo_comun)
|
1350 |
|
1351 |
-
|
1352 |
-
|
1353 |
-
|
1354 |
-
|
1355 |
-
|
1356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1357 |
|
1358 |
-
|
1359 |
-
leg = fig_brecha.legend(handles, labels,
|
1360 |
-
loc='upper center',
|
1361 |
-
bbox_to_anchor=(0.5, -0.12),
|
1362 |
-
ncol=5,
|
1363 |
-
fontsize=10,
|
1364 |
-
frameon=True,
|
1365 |
-
fancybox=True,
|
1366 |
-
shadow=True,
|
1367 |
-
title='Ciudades',
|
1368 |
-
title_fontsize='12')
|
1369 |
-
fig_brecha.tight_layout(rect=[0, 0.1, 1, 0.95])
|
1370 |
-
figuras.append(fig_brecha)
|
1371 |
-
|
1372 |
-
return figuras
|
1373 |
-
|
1374 |
-
def load_data(city):
|
1375 |
-
data = cities_data[city]
|
1376 |
-
return [
|
1377 |
-
data['desempleo_trimestral'],
|
1378 |
-
data['ingresos_periodo'],
|
1379 |
-
data['informal_periodo'],
|
1380 |
-
data['actividad_trimestral'],
|
1381 |
-
data['poblacion_ocupada']
|
1382 |
-
]
|
1383 |
-
|
1384 |
-
def generate_plots(desempleo_df, ingresos_df, informal_df, actividad_df, poblacion_df):
|
1385 |
-
dfs = {
|
1386 |
-
'desempleo': pd.DataFrame(desempleo_df, columns=["Trimestre", "Total", "Hombres", "Mujeres"]),
|
1387 |
-
'ingresos': pd.DataFrame(ingresos_df, columns=["Periodo", "Total", "Hombres", "Mujeres"]),
|
1388 |
-
'informal': pd.DataFrame(informal_df, columns=["Periodo", "Total", "Hombres", "Mujeres"]),
|
1389 |
-
'actividad': pd.DataFrame(actividad_df, columns=["Trimestre", "Total", "Hombres", "Mujeres"]),
|
1390 |
-
'poblacion': pd.DataFrame(poblacion_df, columns=["Trimestre", "Total", "Hombres", "Mujeres"])
|
1391 |
-
}
|
1392 |
-
|
1393 |
-
for key in dfs:
|
1394 |
-
for col in ["Total", "Hombres", "Mujeres"]:
|
1395 |
-
dfs[key][col] = pd.to_numeric(dfs[key][col], errors='coerce')
|
1396 |
-
|
1397 |
-
for key in dfs:
|
1398 |
-
dfs[key] = ordenar_trimestres(dfs[key], 'Trimestre' if key in ['desempleo', 'actividad', 'poblacion'] else 'Periodo')
|
1399 |
-
|
1400 |
-
figs = []
|
1401 |
-
|
1402 |
-
# Radar Plot
|
1403 |
-
problem_metrics = {
|
1404 |
-
'Desempleo': dfs['desempleo']['Total'].iloc[-1] if not dfs['desempleo'].empty else 0,
|
1405 |
-
'Informalidad': dfs['informal']['Total'].iloc[-1] if not dfs['informal'].empty else 0,
|
1406 |
-
'Brecha Salarial': (
|
1407 |
-
(dfs['ingresos']['Hombres'].iloc[-1] - dfs['ingresos']['Mujeres'].iloc[-1]) /
|
1408 |
-
dfs['ingresos']['Hombres'].iloc[-1] * 100
|
1409 |
-
if not dfs['ingresos'].empty and pd.notna(dfs['ingresos']['Hombres'].iloc[-1]) and pd.notna(dfs['ingresos']['Mujeres'].iloc[-1]) else 0
|
1410 |
-
),
|
1411 |
-
'Actividad': dfs['actividad']['Total'].iloc[-1] if not dfs['actividad'].empty else 0
|
1412 |
-
}
|
1413 |
-
|
1414 |
-
categories = list(problem_metrics.keys())
|
1415 |
-
values = list(problem_metrics.values())
|
1416 |
-
angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist()
|
1417 |
-
values += values[:1]
|
1418 |
-
angles += angles[:1]
|
1419 |
-
|
1420 |
-
fig_radar, ax_radar = plt.subplots(figsize=(10, 10), subplot_kw=dict(polar=True))
|
1421 |
-
ax_radar.fill(angles, values, color=COLORES['Brecha'], alpha=0.2)
|
1422 |
-
ax_radar.set_theta_offset(np.pi/2)
|
1423 |
-
ax_radar.set_theta_direction(-1)
|
1424 |
-
ax_radar.set_thetagrids(np.degrees(angles[:-1]), labels=categories)
|
1425 |
-
ax_radar.set_rlabel_position(0)
|
1426 |
-
plt.yticks([20,40,60,80], ["20%","40%","60%","80%"], color="grey", size=10)
|
1427 |
-
plt.ylim(0,100)
|
1428 |
-
ax_radar.set_title(f'RADAR DE PROBLEM脕TICAS LABORALES\n{dfs["desempleo"]["Trimestre"].iloc[-1]}',
|
1429 |
-
pad=20, fontsize=14, fontweight='bold')
|
1430 |
-
figs.append(fig_radar)
|
1431 |
-
|
1432 |
-
# Gr谩ficos principales
|
1433 |
-
figs.append(graficar_datos_mejorados(dfs['desempleo'], 'TASA DE DESEMPLEO', 'Evoluci贸n por g茅nero', 'Tasa (%)'))
|
1434 |
-
figs.append(graficar_datos_mejorados(dfs['ingresos'], 'INGRESOS PROMEDIO', 'Por per铆odo y g茅nero', 'Ingreso (soles)', col_fecha='Periodo', formato_valores='.0f'))
|
1435 |
-
figs.append(graficar_datos_mejorados(dfs['informal'], 'TASA DE INFORMALIDAD', 'Por per铆odo', 'Tasa (%)', col_fecha='Periodo'))
|
1436 |
-
figs.append(graficar_datos_mejorados(dfs['actividad'], 'TASA DE ACTIVIDAD', 'Participaci贸n econ贸mica', 'Tasa (%)'))
|
1437 |
-
figs.append(graficar_datos_mejorados(dfs['poblacion'], 'POBLACI脫N OCUPADA', 'En miles de personas', 'Poblaci贸n (miles)'))
|
1438 |
|
1439 |
-
|
1440 |
-
|
1441 |
-
brecha = []
|
1442 |
-
for h, m in zip(ingresos['Hombres'], ingresos['Mujeres']):
|
1443 |
-
if pd.notna(h) and pd.notna(m) and h != 0:
|
1444 |
-
brecha.append((h-m)/h*100)
|
1445 |
-
else:
|
1446 |
-
brecha.append(np.nan)
|
1447 |
|
1448 |
-
|
1449 |
-
valid_indices = [i for i, b in enumerate(brecha) if pd.notna(b)]
|
1450 |
-
valid_periods = [str(ingresos['Periodo'].iloc[i]) for i in valid_indices]
|
1451 |
-
valid_brecha = [brecha[i] for i in valid_indices]
|
1452 |
|
1453 |
-
if valid_periods:
|
1454 |
-
bars = ax_brecha.bar(valid_periods, valid_brecha, color=COLORES['Brecha'])
|
1455 |
-
for bar in bars:
|
1456 |
-
height = bar.get_height()
|
1457 |
-
ax_brecha.text(bar.get_x() + bar.get_width()/2., height + 0.5,
|
1458 |
-
f'{height:.1f}%', ha='center', va='bottom',
|
1459 |
-
fontsize=10, fontweight='bold')
|
1460 |
-
ax_brecha.set_title('BRECHA SALARIAL DE G脡NERO', fontsize=18, pad=20)
|
1461 |
-
ax_brecha.set_ylabel('Brecha (%)', fontsize=14)
|
1462 |
-
ax_brecha.grid(axis='y', linestyle='--', alpha=0.7)
|
1463 |
-
figs.append(fig_brecha)
|
1464 |
-
|
1465 |
-
return figs
|
1466 |
-
|
1467 |
-
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=".gradio-container {background-color: #F8F9F9}") as app:
|
1468 |
-
gr.Markdown("# 馃搳 Dashboard de Indicadores Laborales por G茅nero")
|
1469 |
-
gr.Markdown("Analiza los principales indicadores del mercado laboral con perspectiva de g茅nero")
|
1470 |
-
|
1471 |
with gr.Row():
|
1472 |
-
|
1473 |
list(cities_data.keys()),
|
1474 |
-
label="
|
1475 |
value="Chimbote",
|
1476 |
-
|
1477 |
)
|
1478 |
-
|
1479 |
-
with gr.Tab("
|
1480 |
-
with gr.Accordion("Tasa de Desempleo", open=True):
|
1481 |
-
desempleo_df = gr.Dataframe(
|
1482 |
-
headers=["Trimestre", "Total", "Hombres", "Mujeres"],
|
1483 |
-
datatype=["str", "number", "number", "number"],
|
1484 |
-
label="Datos de Desempleo"
|
1485 |
-
)
|
1486 |
-
with gr.Accordion("Ingresos", open=False):
|
1487 |
-
ingresos_df = gr.Dataframe(
|
1488 |
-
headers=["Periodo", "Total", "Hombres", "Mujeres"],
|
1489 |
-
datatype=["str", "number", "number", "number"],
|
1490 |
-
label="Datos de Ingresos"
|
1491 |
-
)
|
1492 |
-
with gr.Accordion("Informalidad", open=False):
|
1493 |
-
informal_df = gr.Dataframe(
|
1494 |
-
headers=["Periodo", "Total", "Hombres", "Mujeres"],
|
1495 |
-
datatype=["str", "number", "number", "number"],
|
1496 |
-
label="Datos de Informalidad"
|
1497 |
-
)
|
1498 |
-
with gr.Accordion("Actividad Econ贸mica", open=False):
|
1499 |
-
actividad_df = gr.Dataframe(
|
1500 |
-
headers=["Trimestre", "Total", "Hombres", "Mujeres"],
|
1501 |
-
datatype=["str", "number", "number", "number"],
|
1502 |
-
label="Datos de Actividad"
|
1503 |
-
)
|
1504 |
-
with gr.Accordion("Poblaci贸n Ocupada", open=False):
|
1505 |
-
poblacion_df = gr.Dataframe(
|
1506 |
-
headers=["Trimestre", "Total", "Hombres", "Mujeres"],
|
1507 |
-
datatype=["str", "number", "number", "number"],
|
1508 |
-
label="Datos de Poblaci贸n Ocupada"
|
1509 |
-
)
|
1510 |
-
|
1511 |
-
btn = gr.Button("Generar Visualizaciones", variant="primary")
|
1512 |
-
|
1513 |
-
with gr.Tab("Visualizaciones"):
|
1514 |
-
with gr.Row():
|
1515 |
-
radar_plot = gr.Plot(label="Radar de Problem谩ticas Laborales")
|
1516 |
-
with gr.Row():
|
1517 |
-
desempleo_plot = gr.Plot(label="Tasa de Desempleo")
|
1518 |
-
with gr.Row():
|
1519 |
-
ingresos_plot = gr.Plot(label="Ingresos Promedio")
|
1520 |
-
brecha_salarial_plot = gr.Plot(label="Brecha Salarial de G茅nero")
|
1521 |
with gr.Row():
|
1522 |
-
|
1523 |
-
|
1524 |
with gr.Row():
|
1525 |
-
|
1526 |
-
|
1527 |
-
with gr.Tab("An谩lisis Global"):
|
1528 |
-
gr.Markdown("## An谩lisis Comparativo entre Ciudades")
|
1529 |
-
global_btn = gr.Button("Generar An谩lisis Global", variant="primary")
|
1530 |
with gr.Row():
|
1531 |
-
|
|
|
|
|
|
|
1532 |
with gr.Row():
|
1533 |
-
|
1534 |
with gr.Row():
|
1535 |
-
|
1536 |
-
|
1537 |
-
|
1538 |
-
|
1539 |
-
|
1540 |
-
|
1541 |
-
)
|
1542 |
-
|
1543 |
-
|
1544 |
-
|
1545 |
-
|
1546 |
-
|
1547 |
-
|
1548 |
-
|
1549 |
-
|
1550 |
-
|
1551 |
-
|
1552 |
-
|
1553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1554 |
]
|
1555 |
-
|
1556 |
-
|
1557 |
-
|
1558 |
-
|
1559 |
-
inputs=[],
|
1560 |
-
outputs=[global_desempleo_plot, global_ingresos_plot, global_brecha_plot]
|
1561 |
-
)
|
1562 |
|
1563 |
app.launch(debug=True)
|
|
|
1 |
import pandas as pd
|
|
|
2 |
import numpy as np
|
3 |
+
import plotly.express as px
|
4 |
+
import plotly.graph_objects as go
|
|
|
5 |
import gradio as gr
|
6 |
+
from gradio.components import Plot
|
7 |
+
|
8 |
+
#DATOS
|
9 |
|
10 |
cities_data = {
|
11 |
'Abancay': {
|
|
|
1151 |
}
|
1152 |
|
1153 |
|
1154 |
+
#DATOS
|
1155 |
+
|
1156 |
+
|
|
|
1157 |
|
1158 |
COLORES = {
|
1159 |
'Total': '#2C3E50',
|
1160 |
'Hombres': '#3498DB',
|
1161 |
'Mujeres': '#E74C3C',
|
1162 |
+
'Brecha': '#8E44AD'
|
|
|
1163 |
}
|
1164 |
|
1165 |
def normalizar_nombres_ciudades(nombre):
|
|
|
1174 |
'Pura': 'Piura',
|
1175 |
'Posalipa': 'Pucallpa',
|
1176 |
'Tagapito': 'Talara',
|
1177 |
+
'Juliana': 'Juliaca',
|
1178 |
+
'Chimbote': 'Chimbote',
|
1179 |
+
'Arequipa': 'Arequipa',
|
1180 |
+
'Trujillo': 'Trujillo'
|
1181 |
}
|
1182 |
return correcciones.get(nombre, nombre)
|
1183 |
|
1184 |
+
def procesar_dataframe(df, columnas):
|
1185 |
+
df_procesado = pd.DataFrame(df, columns=columnas)
|
1186 |
+
for col in columnas[1:]:
|
1187 |
+
df_procesado[col] = pd.to_numeric(df_procesado[col], errors='coerce')
|
1188 |
+
return df_procesado.dropna(how='all')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1189 |
|
1190 |
+
def crear_grafico_lineas(df, titulo, eje_y, color=None, formato=None):
|
1191 |
+
fig = go.Figure()
|
1192 |
+
|
1193 |
+
for columna in ['Total', 'Hombres', 'Mujeres']:
|
1194 |
+
if columna in df.columns and not df[columna].isnull().all():
|
1195 |
+
fig.add_trace(go.Scatter(
|
1196 |
+
x=df.iloc[:,0],
|
1197 |
+
y=df[columna],
|
1198 |
+
name=columna,
|
1199 |
+
mode='lines+markers',
|
1200 |
+
line=dict(color=COLORES.get(columna, color),
|
1201 |
+
marker=dict(size=8),
|
1202 |
+
hovertemplate=f'<b>{columna}</b>: %{{y:{formato or ".2f"}}}<extra></extra>'
|
1203 |
+
))
|
1204 |
+
|
1205 |
+
fig.update_layout(
|
1206 |
+
title=dict(text=titulo, x=0.5, font=dict(size=20)),
|
1207 |
+
yaxis_title=eje_y,
|
1208 |
+
xaxis_title='Periodo',
|
1209 |
+
hovermode='x unified',
|
1210 |
+
template='plotly_white',
|
1211 |
+
legend=dict(
|
1212 |
+
orientation="h",
|
1213 |
+
yanchor="bottom",
|
1214 |
+
y=1.02,
|
1215 |
+
xanchor="right",
|
1216 |
+
x=1
|
1217 |
+
),
|
1218 |
+
height=500
|
1219 |
+
)
|
1220 |
+
|
1221 |
+
if formato:
|
1222 |
+
fig.update_layout(yaxis_tickformat=formato)
|
1223 |
+
|
1224 |
return fig
|
1225 |
|
1226 |
+
def crear_radar_plot(dfs):
|
1227 |
+
metricas = {}
|
1228 |
+
|
1229 |
+
try:
|
1230 |
+
metricas['Desempleo'] = dfs['desempleo']['Total'].iloc[-1]
|
1231 |
+
metricas['Informalidad'] = dfs['informal']['Total'].iloc[-1]
|
1232 |
+
metricas['Actividad'] = dfs['actividad']['Total'].iloc[-1]
|
1233 |
+
metricas['Brecha Salarial'] = ((dfs['ingresos']['Hombres'].iloc[-1] -
|
1234 |
+
dfs['ingresos']['Mujeres'].iloc[-1]) / \
|
1235 |
+
dfs['ingresos']['Hombres'].iloc[-1] * 100
|
1236 |
+
except (KeyError, IndexError, ZeroDivisionError):
|
1237 |
+
return go.Figure()
|
1238 |
+
|
1239 |
+
categories = list(metricas.keys())
|
1240 |
+
values = list(metricas.values())
|
1241 |
+
|
1242 |
+
fig = go.Figure()
|
1243 |
+
|
1244 |
+
fig.add_trace(go.Scatterpolar(
|
1245 |
+
r=values + [values[0]],
|
1246 |
+
theta=categories + [categories[0]],
|
1247 |
+
fill='toself',
|
1248 |
+
fillcolor='rgba(142, 68, 173, 0.2)',
|
1249 |
+
line=dict(color=COLORES['Brecha'], width=2),
|
1250 |
+
name='Indicadores'
|
1251 |
+
))
|
1252 |
+
|
1253 |
+
fig.update_layout(
|
1254 |
+
polar=dict(
|
1255 |
+
radialaxis=dict(
|
1256 |
+
visible=True,
|
1257 |
+
range=[0, 100],
|
1258 |
+
tickfont=dict(size=12),
|
1259 |
+
angularaxis=dict(
|
1260 |
+
rotation=90,
|
1261 |
+
direction='clockwise',
|
1262 |
+
tickfont=dict(size=14))
|
1263 |
+
),
|
1264 |
+
title=dict(text='Radar de Indicadores Laborales', x=0.5, font=dict(size=20)),
|
1265 |
+
showlegend=False,
|
1266 |
+
height=500
|
1267 |
+
)
|
1268 |
+
|
1269 |
+
return fig
|
1270 |
|
1271 |
+
def analisis_comparativo_desempleo():
|
1272 |
+
datos = []
|
1273 |
+
|
1274 |
+
for ciudad, data in cities_data.items():
|
1275 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1276 |
+
df = procesar_dataframe(data['desempleo_trimestral'],
|
1277 |
+
["Trimestre", "Total", "Hombres", "Mujeres"])
|
1278 |
if not df.empty:
|
1279 |
+
ultimo_valor = df['Total'].iloc[-1]
|
1280 |
+
datos.append({'Ciudad': nombre, 'Desempleo': ultimo_valor})
|
1281 |
+
|
1282 |
+
if not datos:
|
1283 |
+
return go.Figure()
|
1284 |
|
1285 |
+
df_comparativo = pd.DataFrame(datos).sort_values('Desempleo', ascending=False)
|
|
|
|
|
|
|
|
|
|
|
1286 |
|
1287 |
+
fig = px.bar(df_comparativo,
|
1288 |
+
x='Ciudad',
|
1289 |
+
y='Desempleo',
|
1290 |
+
color='Desempleo',
|
1291 |
+
color_continuous_scale='Bluered',
|
1292 |
+
text_auto='.1f%',
|
1293 |
+
title='Comparaci贸n de Tasa de Desempleo entre Ciudades')
|
1294 |
+
|
1295 |
+
fig.update_layout(
|
1296 |
+
xaxis_title='',
|
1297 |
+
yaxis_title='Tasa de Desempleo (%)',
|
1298 |
+
coloraxis_showscale=False,
|
1299 |
+
xaxis={'categoryorder':'total descending'},
|
1300 |
+
height=600
|
1301 |
+
)
|
1302 |
+
|
1303 |
+
return fig
|
1304 |
|
1305 |
+
def generar_analisis_global():
|
1306 |
+
figs = []
|
1307 |
+
|
1308 |
+
# Gr谩fico comparativo de desempleo
|
1309 |
+
figs.append(analisis_comparativo_desempleo())
|
1310 |
+
|
1311 |
+
# Gr谩fico de tendencia de ingresos
|
1312 |
+
datos_ingresos = []
|
1313 |
+
for ciudad, data in cities_data.items():
|
1314 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1315 |
+
df = procesar_dataframe(data['ingresos_periodo'],
|
1316 |
+
["Periodo", "Total", "Hombres", "Mujeres"])
|
|
|
1317 |
if not df.empty:
|
1318 |
+
df['Ciudad'] = nombre
|
1319 |
+
datos_ingresos.append(df)
|
1320 |
|
1321 |
+
if datos_ingresos:
|
1322 |
+
df_ingresos = pd.concat(datos_ingresos)
|
1323 |
+
fig_ingresos = px.line(df_ingresos,
|
1324 |
+
x='Periodo',
|
1325 |
+
y='Total',
|
1326 |
+
color='Ciudad',
|
1327 |
+
title='Evoluci贸n de Ingresos por Ciudad',
|
1328 |
+
markers=True)
|
1329 |
+
fig_ingresos.update_layout(height=600)
|
1330 |
+
figs.append(fig_ingresos)
|
1331 |
+
else:
|
1332 |
+
figs.append(go.Figure())
|
1333 |
|
1334 |
+
# Gr谩fico de brecha salarial
|
1335 |
+
datos_brecha = []
|
1336 |
+
for ciudad, data in cities_data.items():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1337 |
nombre = normalizar_nombres_ciudades(ciudad)
|
1338 |
+
df = procesar_dataframe(data['ingresos_periodo'],
|
1339 |
+
["Periodo", "Total", "Hombres", "Mujeres"])
|
1340 |
+
if not df.empty:
|
1341 |
+
df['Brecha'] = (df['Hombres'] - df['Mujeres']) / df['Hombres'].replace(0, np.nan) * 100
|
1342 |
+
df['Ciudad'] = nombre
|
1343 |
+
datos_brecha.append(df)
|
|
|
1344 |
|
1345 |
+
if datos_brecha:
|
1346 |
+
df_brecha = pd.concat(datos_brecha)
|
1347 |
+
fig_brecha = px.bar(df_brecha,
|
1348 |
+
x='Periodo',
|
1349 |
+
y='Brecha',
|
1350 |
+
color='Ciudad',
|
1351 |
+
barmode='group',
|
1352 |
+
title='Evoluci贸n de Brecha Salarial por Ciudad',
|
1353 |
+
text_auto='.1f%')
|
1354 |
+
fig_brecha.update_layout(height=600)
|
1355 |
+
figs.append(fig_brecha)
|
1356 |
+
else:
|
1357 |
+
figs.append(go.Figure())
|
1358 |
|
1359 |
+
return figs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1360 |
|
1361 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"),
|
1362 |
+
css=".gradio-container {background-color: white}") as app:
|
|
|
|
|
|
|
|
|
|
|
|
|
1363 |
|
1364 |
+
gr.Markdown("# 馃搳 Dashboard Anal铆tico del Mercado Laboral")
|
|
|
|
|
|
|
1365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1366 |
with gr.Row():
|
1367 |
+
ciudad = gr.Dropdown(
|
1368 |
list(cities_data.keys()),
|
1369 |
+
label="Seleccionar Ciudad",
|
1370 |
value="Chimbote",
|
1371 |
+
interactive=True
|
1372 |
)
|
1373 |
+
|
1374 |
+
with gr.Tab("An谩lisis Ciudad"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1375 |
with gr.Row():
|
1376 |
+
desempleo_plot = Plot(label="Tasa de Desempleo")
|
1377 |
+
ingresos_plot = Plot(label="Ingresos Promedio")
|
1378 |
with gr.Row():
|
1379 |
+
informalidad_plot = Plot(label="Tasa de Informalidad")
|
1380 |
+
actividad_plot = Plot(label="Tasa de Actividad")
|
|
|
|
|
|
|
1381 |
with gr.Row():
|
1382 |
+
radar_plot = Plot(label="Radar de Indicadores")
|
1383 |
+
brecha_plot = Plot(label="Brecha Salarial")
|
1384 |
+
|
1385 |
+
with gr.Tab("An谩lisis Comparativo"):
|
1386 |
with gr.Row():
|
1387 |
+
global_desempleo = Plot(label="Ranking de Desempleo")
|
1388 |
with gr.Row():
|
1389 |
+
global_ingresos = Plot(label="Comparativa de Ingresos")
|
1390 |
+
global_brecha = Plot(label="Evoluci贸n Brecha Salarial")
|
1391 |
+
|
1392 |
+
@app.change(inputs=ciudad, outputs=[desempleo_plot, ingresos_plot,
|
1393 |
+
informalidad_plot, actividad_plot,
|
1394 |
+
radar_plot, brecha_plot])
|
1395 |
+
def actualizar_graficos(ciudad):
|
1396 |
+
data = cities_data[ciudad]
|
1397 |
+
|
1398 |
+
dfs = {
|
1399 |
+
'desempleo': procesar_dataframe(data['desempleo_trimestral'],
|
1400 |
+
["Trimestre", "Total", "Hombres", "Mujeres"]),
|
1401 |
+
'ingresos': procesar_dataframe(data['ingresos_periodo'],
|
1402 |
+
["Periodo", "Total", "Hombres", "Mujeres"]),
|
1403 |
+
'informal': procesar_dataframe(data['informal_periodo'],
|
1404 |
+
["Periodo", "Total", "Hombres", "Mujeres"]),
|
1405 |
+
'actividad': procesar_dataframe(data['actividad_trimestral'],
|
1406 |
+
["Trimestre", "Total", "Hombres", "Mujeres"])
|
1407 |
+
}
|
1408 |
+
|
1409 |
+
return [
|
1410 |
+
crear_grafico_lineas(dfs['desempleo'], "Tasa de Desempleo", "%", ".1f"),
|
1411 |
+
crear_grafico_lineas(dfs['ingresos'], "Ingresos Promedio", "Soles", ".0f"),
|
1412 |
+
crear_grafico_lineas(dfs['informal'], "Tasa de Informalidad", "%", ".1f"),
|
1413 |
+
crear_grafico_lineas(dfs['actividad'], "Tasa de Actividad", "%", ".1f"),
|
1414 |
+
crear_radar_plot(dfs),
|
1415 |
+
crear_grafico_lineas(dfs['ingresos'].assign(
|
1416 |
+
Brecha=lambda x: (x['Hombres'] - x['Mujeres']) / x['Hombres'].replace(0, np.nan) * 100
|
1417 |
+
), "Brecha Salarial", "%", ".1f")
|
1418 |
]
|
1419 |
+
|
1420 |
+
@app.click(inputs=None, outputs=[global_desempleo, global_ingresos, global_brecha])
|
1421 |
+
def actualizar_analisis_global():
|
1422 |
+
return generar_analisis_global()
|
|
|
|
|
|
|
1423 |
|
1424 |
app.launch(debug=True)
|