Skip to content

Commit 5640147

Browse files
committed
feat: autonomos LPGC 2010-2026 + domanda coworking
ingest combinato E58015B_000054 (mensile 2010-2025) + C00069A_000005 (trimestrale 2026) - parquet/economia/autonomos_lpgc.parquet: 114 righe - parquet/economia/autonomos_sectores_lpgc.parquet: 2398 righe, 22 settori - sql/027_autonomos_trend.sql: evoluzione annuale - sql/028_demanda_coworking.sql: autonomi + micro-imprese + popolazione - sql/029_autonomos_sectores.sql: distribuzione per settore economico Target coworking LPGC 2025: 24.670 autonomi + 9.102 micro-imprese = ~33.800
1 parent 77d05a0 commit 5640147

8 files changed

Lines changed: 211 additions & 2 deletions

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.PHONY: all refresh population employment urbanismo tourism gtfs \
22
renta atestados geografia poblacion_secciones sitycleta gtfs_stops \
3-
empresas \
3+
empresas autonomos \
44
queries status venv
55

66
VENV = .venv
@@ -17,7 +17,7 @@ venv: $(VENV)
1717

1818
# ETL targets — each downloads raw data and converts to Parquet
1919
refresh: population employment urbanismo tourism gtfs renta atestados \
20-
geografia poblacion_secciones sitycleta gtfs_stops empresas
20+
geografia poblacion_secciones sitycleta gtfs_stops empresas autonomos
2121

2222
population: venv
2323
$(PYTHON) ingest/istac_population.py
@@ -57,6 +57,9 @@ gtfs_stops: venv
5757
empresas: venv
5858
$(PYTHON) ingest/istac_empresas.py
5959

60+
autonomos: venv
61+
$(PYTHON) ingest/istac_autonomos.py
62+
6063
# Analysis
6164
queries: venv
6265
$(PYTHON) bin/run_queries.py

bin/run_status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
("geografia_hierarchy", "parquet/geografia/dim_hierarchy.parquet"),
3030
("urbanismo_ZUSO", "parquet/urbanismo/32ffdaab_ZUSO.parquet"),
3131
("empresas_lpgc", "parquet/economia/empresas_lpgc.parquet"),
32+
("autonomos_lpgc", "parquet/economia/autonomos_lpgc.parquet"),
3233
]
3334

3435
print("=== datasets ===")

ingest/istac_autonomos.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
Ingest: población autónoma (cuenta propia) desde ISTAC.
3+
4+
Combina due fonti complementari:
5+
1. E58015B_000054 v1.76: afiliaciones por situación empleo (2010-2025, mensual)
6+
2. C00069A_000005 v1.12: población ocupada registrada (2011-2026, trimestral)
7+
8+
Output: parquet/economia/autonomos_lpgc.parquet
9+
"""
10+
import os
11+
import pandas as pd
12+
import requests
13+
14+
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15+
OUT = os.path.join(BASE, "parquet", "economia")
16+
os.makedirs(OUT, exist_ok=True)
17+
18+
ISTAC_API = "https://datos.canarias.es/api/estadisticas/statistical-resources/v1.0/datasets/ISTAC"
19+
MUNI = "35016"
20+
21+
22+
def download_csv(url, name):
23+
print(f"[{name}] downloading...", flush=True)
24+
try:
25+
resp = requests.get(url, timeout=120)
26+
resp.raise_for_status()
27+
resp.encoding = "utf-8"
28+
return pd.read_csv(pd.io.common.StringIO(resp.text), dtype={"LUGAR_COTIZACION_CODE": str})
29+
except Exception as e:
30+
print(f" x {e}")
31+
return None
32+
33+
34+
print("\n=== Autónomos LPGC ===")
35+
36+
# --- 1. E58015B_000054 (2010-2025, mensual, por sector) ---
37+
df1 = download_csv(
38+
f"{ISTAC_API}/E58015B_000054/1.76.csv",
39+
"afiliaciones_situacion_empleo"
40+
)
41+
if df1 is not None:
42+
mask = (
43+
(df1["LUGAR_COTIZACION_CODE"] == MUNI) &
44+
(df1["SITUACION_EMPLEO_CODE"] == "EMPLEOS_CUENTA_PROPIA") &
45+
(df1["ACTIVIDAD_ECONOMICA_CODE"] == "_T") &
46+
(df1["SEXO_CODE"] == "_T")
47+
)
48+
lpgc1 = df1[mask].copy()
49+
lpgc1 = lpgc1.rename(columns={
50+
"TIME_PERIOD#es": "periodo",
51+
"TIME_PERIOD_CODE": "periodo_code",
52+
"OBS_VALUE": "valor",
53+
})
54+
lpgc1["year"] = lpgc1["periodo_code"].str[:4].astype(int)
55+
lpgc1["valor"] = pd.to_numeric(lpgc1["valor"], errors="coerce")
56+
lpgc1["fuente"] = "E58015B"
57+
lpgc1["tipo"] = "autonomos"
58+
lpgc1 = lpgc1[["year", "periodo", "periodo_code", "valor", "fuente", "tipo"]]
59+
print(f" E58015B: {len(lpgc1)} rows, {lpgc1['year'].min()}-{lpgc1['year'].max()}")
60+
61+
# --- 2. C00069A_000005 (2011-2026, trimestral) ---
62+
df2 = download_csv(
63+
f"{ISTAC_API}/C00069A_000005/1.12.csv",
64+
"poblacion_ocupada_registrada"
65+
)
66+
if df2 is not None:
67+
mask = (
68+
(df2["TERRITORIO_CODE"] == MUNI) &
69+
(df2["SITUACION_LABORAL_REGISTRADA_CODE"] == "SELF_REG") &
70+
(df2["SEXO_CODE"] == "_T")
71+
)
72+
lpgc2 = df2[mask].copy()
73+
lpgc2 = lpgc2.rename(columns={
74+
"TIME_PERIOD#es": "periodo",
75+
"TIME_PERIOD_CODE": "periodo_code",
76+
"OBS_VALUE": "valor",
77+
})
78+
lpgc2["year"] = lpgc2["periodo_code"].str[:4].astype(int)
79+
lpgc2["valor"] = pd.to_numeric(lpgc2["valor"], errors="coerce")
80+
lpgc2["fuente"] = "C00069A"
81+
lpgc2["tipo"] = "autonomos"
82+
lpgc2 = lpgc2[["year", "periodo", "periodo_code", "valor", "fuente", "tipo"]]
83+
print(f" C00069A: {len(lpgc2)} rows, {lpgc2['year'].min()}-{lpgc2['year'].max()}")
84+
85+
# --- 3. Combine ---
86+
if df1 is not None and df2 is not None:
87+
# Keep E58015B for 2010-2025, C00069A for 2026
88+
mask1 = lpgc1["year"] <= 2025
89+
mask2 = lpgc2["year"] >= 2025 # small overlap for cross-check
90+
combined = pd.concat([lpgc1[mask1], lpgc2[mask2]], ignore_index=True)
91+
combined = combined.drop_duplicates(subset=["year", "periodo_code", "tipo"])
92+
print(f" Combinado: {len(combined)} rows ({lpgc1['year'].min()}-{lpgc2['year'].max()})")
93+
elif df1 is not None:
94+
combined = lpgc1
95+
elif df2 is not None:
96+
combined = lpgc2
97+
else:
98+
print(" x No data from any source")
99+
exit(1)
100+
101+
combined = combined.sort_values(["year", "periodo_code"])
102+
103+
out_path = os.path.join(OUT, "autonomos_lpgc.parquet")
104+
combined.to_parquet(out_path, index=False)
105+
print(f"\n -> Saved: {out_path} ({len(combined)} rows)")
106+
107+
# Preview
108+
print("\n --- Ultimos datos ---")
109+
last = combined[combined.year == combined.year.max()]
110+
for _, row in last.iterrows():
111+
if pd.notna(row["valor"]):
112+
print(f" {row['periodo_code']} | {row['periodo']:35s} | {int(row['valor']):>6} autonomos")
113+
114+
# --- 4. Also save sector data for analysis (E58015B only) ---
115+
if df1 is not None:
116+
mask_sect = (
117+
(df1["LUGAR_COTIZACION_CODE"] == MUNI) &
118+
(df1["SITUACION_EMPLEO_CODE"] == "EMPLEOS_CUENTA_PROPIA") &
119+
(df1["ACTIVIDAD_ECONOMICA_CODE"] != "_T") &
120+
(df1["SEXO_CODE"] == "_T")
121+
)
122+
sect = df1[mask_sect].copy()
123+
sect = sect.rename(columns={
124+
"TIME_PERIOD#es": "periodo",
125+
"TIME_PERIOD_CODE": "periodo_code",
126+
"ACTIVIDAD_ECONOMICA#es": "sector",
127+
"ACTIVIDAD_ECONOMICA_CODE": "sector_code",
128+
"OBS_VALUE": "valor",
129+
})
130+
sect["year"] = sect["periodo_code"].str[:4].astype(int)
131+
sect["valor"] = pd.to_numeric(sect["valor"], errors="coerce")
132+
out_sect = os.path.join(OUT, "autonomos_sectores_lpgc.parquet")
133+
sect.to_parquet(out_sect, index=False)
134+
print(f"\n -> + sectores: {out_sect} ({len(sect)} rows, {sect['sector_code'].nunique()} sectores)")
135+
136+
print("\nDone.")
5.69 KB
Binary file not shown.
19.3 KB
Binary file not shown.

sql/027_autonomos_trend.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Evolución de autónomos en LPGC (2010-2026)
2+
-- Target principal del coworking: trabajadores por cuenta propia
3+
-- Fuente: ISTAC E58015B_000054 + C00069A_000005
4+
5+
SELECT year,
6+
round(avg(valor), 0) AS autonomos_medios,
7+
min(valor) AS minimo,
8+
max(valor) AS maximo,
9+
count(*) AS periodos
10+
FROM read_parquet('parquet/economia/autonomos_lpgc.parquet')
11+
GROUP BY year
12+
ORDER BY year;

sql/028_demanda_coworking.sql

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- Demanda potencial de coworking en LPGC (2012-2026)
2+
-- Cruza autónomos (cuenta propia) + micro-empresas (1-9 asalariados)
3+
-- + población total para ratio por mil habitantes
4+
-- Fuente: ISTAC E58015B_000054 + E58028A_000005 + población
5+
6+
WITH autonomos AS (
7+
SELECT year, round(avg(valor), 0) AS autonomos_medios
8+
FROM read_parquet('parquet/economia/autonomos_lpgc.parquet')
9+
GROUP BY year
10+
),
11+
micro AS (
12+
SELECT year, round(avg(valor), 0) AS micro_empresas
13+
FROM read_parquet('parquet/economia/empresas_lpgc.parquet')
14+
WHERE estrato_code = '1T9' AND valor IS NOT NULL AND valor > 0
15+
GROUP BY year
16+
),
17+
poblacion AS (
18+
SELECT year, value AS poblacion
19+
FROM read_parquet('parquet/poblacion/poblacion_serie_historica.parquet')
20+
WHERE measure_code = 'POBLACION'
21+
)
22+
SELECT a.year,
23+
a.autonomos_medios,
24+
m.micro_empresas,
25+
a.autonomos_medios + m.micro_empresas AS demanda_coworking,
26+
p.poblacion,
27+
round((a.autonomos_medios + m.micro_empresas) * 1000.0 / p.poblacion, 1) AS demanda_por_mil_hab
28+
FROM autonomos a
29+
JOIN micro m ON a.year = m.year
30+
JOIN poblacion p ON a.year = p.year
31+
ORDER BY a.year;

sql/029_autonomos_sectores.sql

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-- Distribución sectorial de autónomos en LPGC (2025)
2+
-- ¿En qué sectores económicos trabajan los autónomos?
3+
-- Fuente: ISTAC E58015B_000054
4+
5+
WITH ultimo_anio AS (
6+
SELECT max(year) AS anio
7+
FROM read_parquet('parquet/economia/autonomos_sectores_lpgc.parquet')
8+
),
9+
sectores AS (
10+
SELECT sector, sector_code,
11+
round(avg(valor), 0) AS media_autonomos
12+
FROM read_parquet('parquet/economia/autonomos_sectores_lpgc.parquet')
13+
WHERE year = (SELECT anio FROM ultimo_anio)
14+
AND valor IS NOT NULL
15+
GROUP BY sector, sector_code
16+
),
17+
total AS (
18+
SELECT round(sum(media_autonomos), 0) AS total_autonomos
19+
FROM sectores
20+
)
21+
SELECT s.sector,
22+
s.sector_code,
23+
s.media_autonomos,
24+
round(s.media_autonomos * 100.0 / t.total_autonomos, 1) AS pct
25+
FROM sectores s, total t
26+
ORDER BY media_autonomos DESC;

0 commit comments

Comments
 (0)