Código fuente para dane_data.geih.indicadores

"""Indicadores de mercado laboral a partir de la GEIH armonizada."""

from __future__ import annotations

from typing import Iterable

import numpy as np
import pandas as pd

from ..stats import _as_list, peso_mensual_promedio, weighted_mean, weighted_quantile

GLOSARIO = {
    "PET": "Población en edad de trabajar. Desde 2022 (marco 2018): 15 años o más. "
           "Antes: 12+ en zonas urbanas y 10+ en rurales.",
    "FT": "Fuerza de trabajo (antes PEA): personas ocupadas + desocupadas.",
    "TGP": "Tasa global de participación = FT / PET × 100. ¿Qué parte de quienes pueden trabajar participa en el mercado laboral?",
    "TO": "Tasa de ocupación = Ocupados / PET × 100.",
    "TD": "Tasa de desocupación = Desocupados / FT × 100. El 'desempleo'.",
    "Informalidad": "Ocupados que NO están afiliados a salud y cotizando a pensión, sobre el total de ocupados × 100. "
                    "Es una definición por seguridad social; la oficial del DANE usa además tamaño de empresa.",
    "Ingreso laboral": "INGLABO: ingresos laborales mensuales (pesos corrientes) imputados por el DANE.",
}
_COLUMNAS = ["pet", "fuerza_trabajo", "ocupados", "desocupados", "inactivos", "tgp", "to", "td",
             "tasa_informalidad", "ingreso_medio", "ingreso_mediano", "n_muestra"]


[documentos] def indicadores(df: pd.DataFrame, por: str | Iterable[str] | None = None, anual: bool = True) -> pd.DataFrame: r""" Indicadores del mercado laboral, ponderados como los calcula el DANE. Parámetros ---------- df Microdatos armonizados de ``load``. Necesita ``anio``, ``mes``, ``fex``, ``pet``, ``fuerza_trabajo``, ``ocupado``, ``desocupado``, ``inactivo``, ``formal`` e ``ingreso_laboral`` (las de ``VARIABLES_INDICADORES``), más las variables de ``por``. por Variable o lista de variables para desagregar, por ejemplo ``"sexo"`` o ``["zona", "nivel_educativo"]``. anual Si True, un resultado por año: los niveles son el promedio de los meses del año. Si False, uno por mes. Devuelve -------- DataFrame Una fila por año (o mes) y grupo, con: * ``pet``, ``fuerza_trabajo``, ``ocupados``, ``desocupados``, ``inactivos``: personas (promedio mensual si ``anual=True``). * ``tgp``, ``to``, ``td``: tasas global de participación, de ocupación y de desocupación, en %. * ``tasa_informalidad``: % de los ocupados sin afiliación a salud y cotización a pensión. * ``ingreso_medio``, ``ingreso_mediano``: ingreso laboral mensual de los ocupados con ingreso positivo, en pesos corrientes. * ``n_muestra``: personas encuestadas (sin expandir) en el grupo. Véase también ------------- serie_indicadores : Lo mismo para muchos años, calculado año por año y guardado. load : Cargar los microdatos. GLOSARIO : Definiciones de cada indicador. Notas ----- Cada nivel es la suma del factor de expansión :math:`w_i` de las personas del grupo. Si ``anual=True``, :math:`w_i` se divide por el número de meses del año en ``df``, así que el nivel es el promedio de los niveles mensuales: .. math:: \mathrm{TGP} = 100\,\frac{FT}{PET}, \qquad \mathrm{TO} = 100\,\frac{O}{PET}, \qquad \mathrm{TD} = 100\,\frac{D}{FT} donde :math:`FT = O + D` es la fuerza de trabajo, :math:`O` los ocupados y :math:`D` los desocupados. Las tasas anuales son entonces cocientes de promedios mensuales, como las publica el DANE, y no promedios de las tasas mensuales. La informalidad es una definición por seguridad social (ver ``formal`` en la guía de variables); la oficial del DANE usa además el tamaño de la empresa. La mediana es la de ``dane_data.weighted_quantile``. Ejemplos -------- Cinco personas con el mismo peso: dos ocupadas (una formal), una desocupada, una inactiva y una menor de la edad de trabajar. >>> import pandas as pd >>> import dane_data as dd >>> df = pd.DataFrame({ ... "anio": 2024, "mes": 1, "fex": 100.0, ... "pet": [1, 1, 1, 1, 0], ... "fuerza_trabajo": [1, 1, 1, 0, 0], ... "ocupado": [1, 1, 0, 0, 0], ... "desocupado": [0, 0, 1, 0, 0], ... "inactivo": [0, 0, 0, 1, 0], ... "formal": [1, 0, None, None, None], ... "ingreso_laboral": [2.0e6, 1.2e6, None, None, None], ... }) >>> dd.geih.indicadores(df)[["anio", "pet", "ocupados", "tgp", "to", "td", "tasa_informalidad"]].round(1) anio pet ocupados tgp to td tasa_informalidad 0 2024 400.0 200.0 75.0 50.0 33.3 50.0 Con la GEIH de 2024: >>> df = dd.geih.load(2024, variables=dd.geih.VARIABLES_INDICADORES) # doctest: +SKIP >>> dd.geih.indicadores(df, por="sexo")[["anio", "sexo", "tgp", "td"]].round(1) # doctest: +SKIP anio sexo tgp td 0 2024 Hombre 76.4 8.2 1 2024 Mujer 52.4 12.7 """ por = _as_list(por) time_keys = ["anio"] if anual else ["anio", "mes"] keys = time_keys + por # Solo las sumas ponderadas que hacen falta: copiar el frame entero duplica la memoria # con muchos años. w = (peso_mensual_promedio(df) if anual else df["fex"]).to_numpy("float64") def es(col: str) -> np.ndarray: return df[col].to_numpy("float64", na_value=0) == 1 ocupado = es("ocupado") sumas = pd.DataFrame({ "pet": np.where(es("pet"), w, 0.0), "fuerza_trabajo": np.where(es("fuerza_trabajo"), w, 0.0), "ocupados": np.where(ocupado, w, 0.0), "desocupados": np.where(es("desocupado"), w, 0.0), "inactivos": np.where(es("inactivo"), w, 0.0), "informales": np.where(ocupado & (df["formal"].to_numpy("float64", na_value=np.nan) == 0), w, 0.0), }, index=df.index) g = sumas.groupby([df[k] for k in keys], observed=True, dropna=False) res = g.sum() res["n_muestra"] = g.size() with np.errstate(divide="ignore", invalid="ignore"): res["tgp"] = 100 * res["fuerza_trabajo"] / res["pet"] res["to"] = 100 * res["ocupados"] / res["pet"] res["td"] = 100 * res["desocupados"] / res["fuerza_trabajo"] res["tasa_informalidad"] = 100 * res["informales"] / res["ocupados"] ingreso = df["ingreso_laboral"].to_numpy("float64", na_value=np.nan) con_ingreso = ocupado & (ingreso > 0) if con_ingreso.any(): ocup = df.loc[con_ingreso, keys].assign(_y=ingreso[con_ingreso], _w=w[con_ingreso]) # Solo _y y _w: con las columnas de grupo dentro, pandas 2 avisa que dejarán de pasarse a apply gi = ocup.groupby(keys, observed=True, dropna=False)[["_y", "_w"]] res["ingreso_medio"] = gi.apply(lambda x: weighted_mean(x["_y"], x["_w"])) res["ingreso_mediano"] = gi.apply(lambda x: weighted_quantile(x["_y"], x["_w"], 0.5)) else: res["ingreso_medio"] = np.nan res["ingreso_mediano"] = np.nan res = res.replace([np.inf, -np.inf], np.nan).reset_index() return res[keys + _COLUMNAS]