Función de preproceso¶

En el taller anterior vimos toda una serie de pasos para preprocesar los datos de COVUD-19. En esta actividad lo único que vamos a hacer es definir un par de funciones que realizan todo el flujo de preproceso. De esta forma podemos repetir todo el procedimiento de forma fácil.

In [1]:
import os # hablar con el sistema operativo
import glob # listar directorios y ese tipo de operaciones
import itertools # herramientas para iterar objetos
from pathlib import Path # manipular rutas a directorios
import zipfile # comprimir y descomprimir archivos
import numpy as np # operaciones vectorizadas
import pandas as pd # DataFrames
from datetime import timedelta, date, datetime # Manejar fechas
import openpyxl # leer/escribir archivos de exel
import requests # Hablar con direcciones web
import logging

Bajar y guardar datos¶

In [2]:
def bajar_datos_salud(directorio_datos='data/', fecha='10-01-2022'):
    '''
        Descarga el archivo de datos y los diccionarios para la fecha solicitada.
    '''
    fecha = datetime.strptime(fecha, "%d-%m-%Y")
    url_salud_historicos = 'http://datosabiertos.salud.gob.mx/gobmx/salud/datos_abiertos/historicos/'    
    archivo_nombre = f'{fecha.strftime("%y%m%d")}COVID19MEXICO.csv.zip'
    archivo_ruta = os.path.join(directorio_datos, archivo_nombre)
    url_diccionario = 'http://datosabiertos.salud.gob.mx/gobmx/salud/datos_abiertos/diccionario_datos_covid19.zip'
    diccionario_ruta = os.path.join(directorio_datos, 'diccionario.zip')
    if os.path.exists(archivo_ruta):
        logging.debug(f'Ya existe {archivo_nombre}')
    else:
        print(f'Bajando datos {fecha.strftime("%d.%m.%Y")}')
        url_dia = "{}/{}/datos_abiertos_covid19_{}.zip".format(fecha.strftime('%Y'),
                                                                fecha.strftime('%m'),
                                                                fecha.strftime('%d.%m.%Y'))
        url = url_salud_historicos + url_dia
        r = requests.get(url, allow_redirects=True)
        open(archivo_ruta, 'wb').write(r.content)
        r = requests.get(url_diccionario, allow_redirects=True)
        open(diccionario_ruta, 'wb').write(r.content)
        with zipfile.ZipFile(diccionario_ruta, 'r') as zip_ref:
          zip_ref.extractall(directorio_datos)

Preprocesar¶

In [6]:
def carga_datos_covid19_MX(data_dir = 'data/', fecha='210505', resolver_claves='si_no_binarias', entidad='09'):
    """
        Lee en un DataFrame el CSV con el reporte de casos de la Secretaría de Salud de México publicado en una fecha dada. Esta función
        también lee el diccionario de datos que acompaña a estas publicaciones para preparar algunos campos, en particular permite la funcionalidad
        de generar columnas binarias para datos con valores 'SI', 'No'.

        **Nota**: En esta versión la ruta esta y nombre de los archivos es fija. Asumimos que existe un directorio '/content/'
        donde se encuentran todos los archivos.

        **Nota 2**: Por las actualizaciones a los formatos de datos, esta función sólo va a servir para archivos posteriores a 20-11-28

        resolver_claves: 'sustitucion', 'agregar', 'si_no_binarias', 'solo_localidades'. Resuelve los valores del conjunto de datos usando el
        diccionario de datos y los catálogos. 'sustitucion' remplaza los valores en las columnas, 'agregar'
        crea nuevas columnas. 'si_no_binarias' cambia valores SI, NO, No Aplica, SE IGNORA, NO ESPECIFICADO por 1, 0, 0, 0, 0 respectivamente.

    """
    fecha_formato = '201128'
    nuevo_formato = True
    fecha_carga = pd.to_datetime(fecha, yearfirst=True)
    if fecha_carga < datetime.strptime('20-11-28', "%y-%m-%d"):
      raise ValueError('La fecha debe ser posterior a 20-11-28.')
    
    catalogos=f'{data_dir}{fecha_formato} Catalogos.xlsx'
    descriptores=f'{data_dir}{fecha_formato} Descriptores.xlsx'    
    data_file = os.path.join(data_dir, f'{fecha}COVID19MEXICO.csv.zip')
    print(data_file)
    df = pd.read_csv(data_file, dtype=object, encoding='latin-1')
    if entidad is not None:
      df = df[df['ENTIDAD_RES'] == entidad]
    # Hay un error y el campo OTRA_COMP es OTRAS_COMP según los descriptores
    df.rename(columns={'OTRA_COM': 'OTRAS_COM'}, inplace=True)
    # Asignar clave única a municipios
    df['MUNICIPIO_RES'] = df['ENTIDAD_RES'] + df['MUNICIPIO_RES']
    df['CLAVE_MUNICIPIO_RES'] = df['MUNICIPIO_RES']
    # Leer catalogos
    nombres_catalogos = ['Catálogo de ENTIDADES',
                         'Catálogo MUNICIPIOS',
                         'Catálogo RESULTADO',
                         'Catálogo SI_NO',
                         'Catálogo TIPO_PACIENTE']
    if nuevo_formato:
        nombres_catalogos.append('Catálogo CLASIFICACION_FINAL')
        nombres_catalogos[2] = 'Catálogo RESULTADO_LAB'

    dict_catalogos = pd.read_excel(catalogos,
                              nombres_catalogos,
                              dtype=str,
                              engine='openpyxl')

    entidades = dict_catalogos[nombres_catalogos[0]]
    municipios = dict_catalogos[nombres_catalogos[1]]
    tipo_resultado = dict_catalogos[nombres_catalogos[2]]
    cat_si_no = dict_catalogos[nombres_catalogos[3]]
    cat_tipo_pac = dict_catalogos[nombres_catalogos[4]]
    # Arreglar los catálogos que tienen mal las primeras líneas
    dict_catalogos[nombres_catalogos[2]].columns = ["CLAVE", "DESCRIPCIÓN"]
    dict_catalogos[nombres_catalogos[5]].columns = ["CLAVE", "CLASIFICACIÓN", "DESCRIPCIÓN"]

    if nuevo_formato:
        clasificacion_final = dict_catalogos[nombres_catalogos[5]]


    # Resolver códigos de entidad federal
    cols_entidad = ['ENTIDAD_RES', 'ENTIDAD_UM', 'ENTIDAD_NAC']
    df['CLAVE_ENTIDAD_RES'] = df['ENTIDAD_RES']
    df[cols_entidad] = df[cols_entidad].replace(to_replace=entidades['CLAVE_ENTIDAD'].values,
                                               value=entidades['ENTIDAD_FEDERATIVA'].values)

    # Construye clave unica de municipios de catálogo para resolver nombres de municipio
    municipios['CLAVE_MUNICIPIO'] = municipios['CLAVE_ENTIDAD'] + municipios['CLAVE_MUNICIPIO']

    # Resolver códigos de municipio
    municipios_dict = dict(zip(municipios['CLAVE_MUNICIPIO'], municipios['MUNICIPIO']))
    df['MUNICIPIO_RES'] = df['MUNICIPIO_RES'].map(municipios_dict.get)

    # Resolver resultados
    if nuevo_formato:
        df.rename(columns={'RESULTADO_LAB': 'RESULTADO'}, inplace=True)
        tipo_resultado['DESCRIPCIÓN'].replace({'POSITIVO A SARS-COV-2': 'Positivo SARS-CoV-2'}, inplace=True)

    tipo_resultado = dict(zip(tipo_resultado['CLAVE'], tipo_resultado['DESCRIPCIÓN']))
    df['RESULTADO'] = df['RESULTADO'].map(tipo_resultado.get)
    clasificacion_final = dict(zip(clasificacion_final['CLAVE'], clasificacion_final['CLASIFICACIÓN']))
    df['CLASIFICACION_FINAL'] = df['CLASIFICACION_FINAL'].map(clasificacion_final.get)
    # Resolver datos SI - NO

    # Necesitamos encontrar todos los campos que tienen este tipo de dato y eso
    # viene en los descriptores, en el campo FORMATO_O_FUENTE
    descriptores = pd.read_excel(f'{data_dir}201128 Descriptores_.xlsx',
                                 index_col='Nº',
                                 engine='openpyxl')
    descriptores.columns = list(map(lambda col: col.replace(' ', '_'), descriptores.columns))
    descriptores['FORMATO_O_FUENTE'] = descriptores.FORMATO_O_FUENTE.str.strip()

    datos_si_no = descriptores.query('FORMATO_O_FUENTE == "CATÁLOGO: SI_ NO"')
    cat_si_no['DESCRIPCIÓN'] = cat_si_no['DESCRIPCIÓN'].str.strip()

    campos_si_no = datos_si_no.NOMBRE_DE_VARIABLE
    nuevos_campos_si_no = campos_si_no

    if resolver_claves == 'agregar':
        nuevos_campos_si_no = [nombre_var + '_NOM' for nombre_var in campos_si_no]
    elif resolver_claves == 'si_no_binarias':
        nuevos_campos_si_no = [nombre_var + '_BIN' for nombre_var in campos_si_no]
        cat_si_no['DESCRIPCIÓN'] = list(map(lambda val: 1 if val == 'SI' else 0, cat_si_no['DESCRIPCIÓN']))

    df[nuevos_campos_si_no] = df[datos_si_no.NOMBRE_DE_VARIABLE].replace(
                                                to_replace=cat_si_no['CLAVE'].values,
                                                value=cat_si_no['DESCRIPCIÓN'].values)

    # Resolver tipos de paciente
    cat_tipo_pac = dict(zip(cat_tipo_pac['CLAVE'], cat_tipo_pac['DESCRIPCIÓN']))
    df['TIPO_PACIENTE'] = df['TIPO_PACIENTE'].map(cat_tipo_pac.get)

    df = procesa_fechas(df)

    return df

def procesa_fechas(covid_df):
    df = covid_df.copy()

    df['FECHA_INGRESO'] = pd.to_datetime(df['FECHA_INGRESO'])
    df['FECHA_SINTOMAS'] = pd.to_datetime(df['FECHA_SINTOMAS'])
    df['FECHA_DEF'] = pd.to_datetime(df['FECHA_DEF'], 'coerce')
    df['DEFUNCION'] = (df['FECHA_DEF'].notna()).astype(int)
    df['EDAD'] = df['EDAD'].astype(int)

    df.set_index('FECHA_INGRESO', drop=False, inplace=True)
    df['AÑO_INGRESO'] = df.index.year
    df['MES_INGRESO'] = df.index.month
    df['DIA_SEMANA_INGRESO'] = df.index.weekday
    df['SEMANA_AÑO_INGRESO'] = df.index.week
    df['DIA_MES_INGRESO'] = df.index.day
    df['DIA_AÑO_INGRESO'] = df.index.dayofyear

    return df

Bajar y preprocesar usando nuestras funciones¶

In [10]:
ayer = datetime.now() - timedelta(2)
bajar_datos_salud(fecha=ayer.strftime('%d-%m-%Y'))
df = carga_datos_covid19_MX(fecha=ayer.strftime('%y%m%d'), entidad='09')
df
Bajando datos 18.01.2022
data/220118COVID19MEXICO.csv.zip
/tmp/ipykernel_3563/19327198.py:132: FutureWarning: weekofyear and week have been deprecated, please use DatetimeIndex.isocalendar().week instead, which returns a Series.  To exactly reproduce the behavior of week and weekofyear and return an Index, you may call pd.Int64Index(idx.isocalendar().week)
  df['SEMANA_AÑO_INGRESO'] = df.index.week
Out[10]:
FECHA_ACTUALIZACION ID_REGISTRO ORIGEN SECTOR ENTIDAD_UM SEXO ENTIDAD_NAC ENTIDAD_RES MUNICIPIO_RES TIPO_PACIENTE ... TOMA_MUESTRA_ANTIGENO_BIN MIGRANTE_BIN UCI_BIN DEFUNCION AÑO_INGRESO MES_INGRESO DIA_SEMANA_INGRESO SEMANA_AÑO_INGRESO DIA_MES_INGRESO DIA_AÑO_INGRESO
FECHA_INGRESO
2020-07-06 2022-01-18 z12d63 2 12 CIUDAD DE MÉXICO 2 CIUDAD DE MÉXICO CIUDAD DE MÉXICO VENUSTIANO CARRANZA AMBULATORIO ... 0 0 0 0 2020 7 0 28 6 188
2020-09-23 2022-01-18 z13788 1 12 CIUDAD DE MÉXICO 1 CIUDAD DE MÉXICO CIUDAD DE MÉXICO CUAJIMALPA DE MORELOS AMBULATORIO ... 0 0 0 0 2020 9 2 39 23 267
2020-06-15 2022-01-18 z2b144 2 12 CIUDAD DE MÉXICO 1 MÉXICO CIUDAD DE MÉXICO AZCAPOTZALCO AMBULATORIO ... 0 0 0 0 2020 6 0 25 15 167
2020-12-21 2022-01-18 z526b3 2 12 CIUDAD DE MÉXICO 1 CIUDAD DE MÉXICO CIUDAD DE MÉXICO TLALPAN AMBULATORIO ... 1 0 0 0 2020 12 0 52 21 356
2020-04-22 2022-01-18 z3d1e2 2 12 CIUDAD DE MÉXICO 1 CIUDAD DE MÉXICO CIUDAD DE MÉXICO GUSTAVO A. MADERO AMBULATORIO ... 0 0 0 0 2020 4 2 17 22 113
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2021-10-11 2022-01-18 m00073e 2 12 MÉXICO 2 NO ESPECIFICADO CIUDAD DE MÉXICO CUAUHTÉMOC AMBULATORIO ... 1 0 0 0 2021 10 0 41 11 284
2021-10-13 2022-01-18 m030623 2 12 MÉXICO 2 MÉXICO CIUDAD DE MÉXICO TLÁHUAC AMBULATORIO ... 1 0 0 0 2021 10 2 41 13 286
2021-10-13 2022-01-18 m049633 2 12 MÉXICO 1 CIUDAD DE MÉXICO CIUDAD DE MÉXICO GUSTAVO A. MADERO AMBULATORIO ... 0 0 0 0 2021 10 2 41 13 286
2021-10-13 2022-01-18 m160d02 2 12 MÉXICO 2 CIUDAD DE MÉXICO CIUDAD DE MÉXICO GUSTAVO A. MADERO AMBULATORIO ... 1 0 0 0 2021 10 2 41 13 286
2021-10-14 2022-01-18 m0da9ec 2 12 MÉXICO 1 CIUDAD DE MÉXICO CIUDAD DE MÉXICO GUSTAVO A. MADERO AMBULATORIO ... 1 0 0 0 2021 10 3 41 14 287

4279037 rows × 69 columns

Guardando el resultado¶

Listo, con nuestras funciones tenemos ya nuestros datos preprocesados, ahora vamos a guardarlos para poder utlizarlos rápidamente en otros notebooks. En general tenemos muchas opciones para guardar los datos, csv, por ejemplo. En esta ocasión vamos a usar un formato nativo de Python el pickle, que es una forma de serializar un objeto de Python. Pandas nos provee una función para guardar directamente un dataframe como pickle:

In [6]:
df.to_pickle("data/datos_covid_ene19.pkl")

En la documentación de to_pickle pueden ver las opcioones completas.

In [11]:
 
In [ ]: