PASOS DE LA LIMPIEZA DE DATOS

Los datos limpios son un componente clave en el desarrollo de soluciones basadas en datos, ya que nos ayudan a aumentar la productividad, minimizar las molestias y brindar resultados de mayor calidad.

Carga de los datos

Se importan y organizan datos de diferentes fuentes para tener el conjunto inicial de datos.

Observaciones duplicadas

Los datos duplicados se eliminan solo si son producto de un error en la generación del conjunto de datos.

Estadísticas descriptivas básicas

Se calculan algunas medidas de tendencia central, dispersión y posición para identificar qué variables requieren algún tratamiento particular.

Variables no informativas

Con base en las estadísticas anteriores, se decide qué variables podrían ser no informativas.​

Valores atípicos

Se identifican y eliminan o imputan valores extremos que pudieran afectar ajustes posteriores.

Registros no informados

Monitorear y trabajar la presencia de valores no informados es recomendable.

ACERCA DE LA LIMPIEZA DE DATOS

El éxito o fracaso de los modelos y análisis de datos que se llevan a cabo está determinado en gran medida por la calidad de los datos con los que se construyen. En base a esto, es importante tener en cuenta los criterios de selección y tratamiento de las variables a considerar en el estudio:

CONSIDERACIONES TEÓRICAS

Análisis univariado, descripción de variables

Distribución de las variables: se analiza la distribución de las variables para detectar valores no válidos, así como concentraciones en determinados valores.
Variables numéricas: se calcula la media, la mediana, la desviación estándar y la posición.
Variables categóricas: se calcula la moda y la mediana, y se analiza la frecuencia observada en cada valor de la variable.
Outliers: se trata de detectar valores extremos.
Faltantes: se analiza el porcentaje de casos no reportados que presenta cada una de las variables.

Análisis temporal

Distribución de las variables: se analiza la distribución de las variables para detectar la ausencia de información en un periodo de tiempo, así como cambios estructurales en las variables.
Variables numéricas: se calcula la media, la mediana, el percentil 5 y el percentil 95. Se analiza la tendencia de la variable, así como posibles valores extremos.
Variables categóricas: para cada semestre se calcula la distribución de la variable. Se analizan los cambios estructurales entre las variables que podrían ser objeto de modelos predictivos (en una fase posterior si los modelos no alcanzan una buena calidad de pronóstico).

Análisis de coherencia

  • Se busca la consistencia de los datos, se analiza la calidad de la información y monitorea la integridad de la misma.
  • Se procura que el porcentaje de faltantes no sea alto (reducir la incorporación de incertidumbre o ruido en el modelo a pronosticar).
  • Se prioriza que las covariables representen una importante fuente de información (análisis de medidas de posición).

Análisis bivariado

  • Procurar que las variables explicativas sean interesantes desde el punto de vista del contexto (fácil acceso e interés).
  • Para variables continuas, que la tendencia de la variable sea clara (teniendo en cuenta que puede variar en función de los tramos muestreados).
  • En el caso de las variables alfanuméricas se prioriza que no exista una alta concentración en un valor y que se puedan realizar discriminaciones con diferentes comportamientos frente a la variable objetivo.

Tratamiento de los datos faltantes

Se admite que las variables no sean informadas en algunos momentos, y el tratamiento depende del porcentaje de registros no informados presente, generalmente los criterios giran en torno a las siguientes consideraciones:

  • Si hay más del 20% de datos faltantes → Se elimina la variable de estudio.
  • Si hay menos del 20% → Se procesan a partir de otros instantes de la variable o en función de su comportamiento e interacción con otras variables para los segmentos reportados, es decir, se imputan ya sea por regresión o por el valor promedio correspondiente al ciclo estacional anterior.
  • Cuando hay menos del 5% de datos no reportados, los registros pueden ser borrados, sin embargo, esto afecta la secuencia temporal de la serie y es preferible hacer imputaciones (si se trabaja con series temporales).

En ocasiones, aunque el porcentaje de registros no reportados es alto, es necesario modelar incluyéndolos, para esto se genera una variable dummy que refleje este efecto (Sujeto a consideración porque se podría considerar simular la variable, pero se requiere tiempo para procesar las variables o trabajarlas en tramos).

PASO 1. CARGA DE LOS DATOS

En el repositorio, se pueden encontrar todos los scripts asociados a esta sección. Se trabaja con datos vinculados a registros de vehículos en circulación, delincuencia, población y algunos indicadores económicos de distintas regiones de Chile, en la siguiente tabla se resumen los tópicos y enlaces para descargar los datos desde sus fuentes originales.

Tópico Descripción Periodicidad Fecha (años) Fuente
1 Población Población nacional total, INE (número de personas). Anual 2005 - 2022 Click
2 Siniestros de transito Siniestros de transito en rutas de Chile (número de personas involucrados en los siniestros). Mensual 2015 - 2022 Click
3 Vehiculos en circulación Parque de vehículos en circulación, por tipo, segun región (número de vehículos). Anual 2008 - 2021 Click
4 Registro de empresas y sociedades Número de constituciones en registro de empresas y sociedades a nivel regional (Cantidad). Mensual 2013 - 2022 Click
5 Consumo de hogares e IPSFL Consumo de hogares e IPSFL por región , volumen a precios del año anterior encadenado, referencia 2018 (miles de millones de pesos encadenados). Anual 2013 - 2021 Click
6 Producto Interno Bruto Producto interno bruto por región, volumen a precios del año anterior encadenado, series empalmadas, referencia 2018 (miles de millones de pesos encadenados). Anual 2013 - 2021 Click
7 Fuerza de trabajo Fuerza de trabajo (Miles de personas), Ocupados (Miles de personas) y Tasa de desocupación (%). Mensual 2010 - 2022 Click
8 Delitos Hechos delictivos que registraron las policías durante el período (Cantidad de registros). Mensual 2005 - 2022 Click

Se descargaron todos los registros de la fuente original y se guardaron en una carpeta para ser procesados de manera local. Para el proceso de carga y visualización inicial de los datos se implementaron las siguientes herramientas:

				
					# Libraries ############################################################################################################

import os
import glob
import pandas as pd
import numpy as np
from datetime import datetime
				
			
				
					# Local path ##########################################################################################################
# Directorio local donde se encuentran los datos descargados
path: str = './DataDrivenCulture/'

path_poblacion = path + 'Banco/Poblacion.xlsx'
path_empresa = path + 'Banco/Empresas.xlsx'
path_consumo = path + 'Banco/ConsumoHogares.xlsx'
path_PIB = path + 'Banco/PIB.xlsx'
path_trabajo = path + 'Banco/Fuerza_Trabajo.xlsx'
path_delito = path + 'Delincuencia/Delincuencia_Regiones.csv'
path_cars = [path + 'Transito/2021.xlsx', 
             path + 'Transito/2020.xlsx', 
             path + 'Transito/2019.xlsx',
             path + 'Transito/2018.xlsx', 
             path + 'Transito/2017.xlsx', 
             path + 'Transito/2016.xlsx',
             path + 'Transito/2015.xlsx', 
             path + 'Transito/2014.xlsx', 
             path + 'Transito/2013.xlsx',
             path + 'Transito/2012.xls', 
             path + 'Transito/2011.xls', 
             path + 'Transito/2010.xls',
             path + 'Transito/2009.xls', 
             path + 'Transito/2008.xls']
# URL #################################################################################################################
# En caso de preferir descargar los datos desde la URL se toma este caso como ejemplo.
accident_root: str = 'https://opendata.arcgis.com/api/v3/datasets/'
url_accident = [
    accident_root + '8eb8d87f868840e6aba4a0018ccab140_0/downloads/data?format=csv&spatialRefId=4326&where=1%3D1',
    accident_root + 'deeda8796b00416b8db0759afb29f028_0/downloads/data?format=csv&spatialRefId=4326&where=1%3D1',
    accident_root + '51207ebf95db4d5f98e312dd3222bce8_0/downloads/data?format=csv&spatialRefId=4326&where=1%3D1'
]
				
			

Se crearon 8 funciones para cargar los diferentes DataFrames disponibles, una función por cada tópico (población, empresa, consumo, delito, trabajo, PIB, accidentes, cars) y otra función para unir todos los DataFrames en uno solo. La idea es abarcar la mayor cantidad de datos posibles en un solo archivo e ir tomando secciones del mismo dependiendo del análisis que se quiere realizar, esto se debe a que con este DataFrame consolidado se realizan varios ajustes en distintas secciones del sitio web.

Así mismo, se crearon dos funciones para la estandarización de los datos, se abrevian los nombres de las regiones y se colocan en mayúsculas

 

Nombre de la Región Abreviación en el dataframe
1 Región de Arica y Parinacota AP
2 Región de Tarapacá TA
3 Región de Antofagasta AN
4 Región de Atacama AT
5 Región de Coquimbo CO
6 Región de Valparaíso VA
7 Región Metropolitana de Santiago RM
8 Región del Libertador General Bernardo O'Higgins LI
9 Región del Maule ML
10 Región de Ñuble NB
11 Región del Biobío BI
12 Región de La Araucanía AR
13 Región de Los Ríos LR
14 Región de Los Lagos LL
15 Región de Aysén del General Carlos Ibáñez del Campo AI
16 Región de Magallanes y de la Antártica Chilena MA
				
					
# Tools definitions ###################################################################################################

def clean_accents(text):
    """
    Return the text without accents.
    """
    accents = {'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u',
               'Á': 'A', 'É': 'E', 'Í': 'I', 'Ó': 'O', 'Ú': 'U'}
    for acc in accents:
        if acc in text:
            text = text.replace(acc, accents[acc])
        else:
            pass
    return text
    
def replace_region(df) -> object:
    """
    Retorna el dataframe asociado con los nombres de regiones estandarizados.
    :rtype: DataFrame
    """
    df = df.str.upper().str.strip()
    df = pd.Series([clean_accents(i) for i in list(df)])
    df = df.str.replace("REGION ANTOFAGASTA", "AN") \
        .str.replace("REGION ARAUCANIA", "AR") \
        .str.replace("REGION ARICA Y PARINACOTA", "AP") \
        .str.replace("REGION ATACAMA", "AT") \
        .str.replace("REGION AYSEN DEL GRL. CARLOS IBAÑEZ DEL CAMPO", "AI") \
        .str.replace("REGION BIO BIO", "BI") \
        .str.replace("REGION COQUIMBO", "CO") \
        .str.replace("REGION DE LOS RIOS", "LR") \
        .str.replace("REGION LIB.B.O'HIGGINS", "LI") \
        .str.replace("REGION LOS LAGOS", "LL") \
        .str.replace("REGION MAGALLANES Y ANTARTICA CHILENA", "MA") \
        .str.replace("REGION MAULE", "ML") \
        .str.replace("REGION TARAPACA", "TA") \
        .str.replace("REGION VALPARAISO", "VA") \
        .str.replace("REGION ÑUBLE", "NB") \
        .str.replace("REGION DE ARICA Y PARINACOTA", "AP") \
        .str.replace("REGION DE TARAPACA", "TA") \
        .str.replace("REGION DE ANTOFAGASTA", "AN") \
        .str.replace("REGION DE ATACAMA", "AT") \
        .str.replace("REGION DE COQUIMBO", "CO") \
        .str.replace("REGION DE VALPARAISO", "VA") \
        .str.replace("REGION DEL LIBERTADOR GENERAL BERNARDO O'HIGGINS", "LI") \
        .str.replace("REGION DEL MAULE", "ML") \
        .str.replace("REGION DEL BIOBIO", "BI") \
        .str.replace("REGION DE ÑUBLE", "NB") \
        .str.replace("REGION DE LA ARAUCANIA", "AR") \
        .str.replace("REGION DE LOS LAGOS", "LL") \
        .str.replace("REGION DE AYSEN DEL GENERAL CARLOS IBAÑEZ DEL CAMPO", "AI") \
        .str.replace("REGION DE MAGALLANES Y LA ANTARTICA CHILENA", "MA") \
        .str.replace("REGION DEL LIBERTADOR GENERAL BERNARDO OHIGGINS", "LI") \
        .str.replace("REGION DE MAGALLANES Y DE LA ANTARTICA CHILENA", "MA") \
        .str.replace("REGION DE MAULE", "ML") \
        .str.replace("REGION DEL LIBERTADOR GENERAL BERNARDO  O'HIGGINS", "LI") \
        .str.replace("I DE TARAPACA", "TA") \
        .str.replace("II DE ANTOFAGASTA", "AN") \
        .str.replace("III DE ATACAMA", "AT") \
        .str.replace("IV DE COQUIMBO", "CO") \
        .str.replace("V DE VALPARAISO", "VA") \
        .str.replace("VI DE O'HIGGINS", "LI") \
        .str.replace("VII DEL MAULE", "ML") \
        .str.replace("VIII DEL BIOBIO", "BI") \
        .str.replace("IX DE LA ARAUCANIA", "AR") \
        .str.replace("X DE LOS LAGOS", "LL") \
        .str.replace("XI DE AYSEN", "AI") \
        .str.replace("XII DE MAGALLANES Y DE LA ANTARTICA CHILENA", "MA") \
        .str.replace("XIV DE LOS RIOS", "LR") \
        .str.replace("XV DE ARICA Y PARINACOTA", "AP") \
        .str.replace("XI AYSEN", "AI") \
        .str.replace("XII MAGALLANES Y LA ANTARTICA", "MA") \
        .str.replace("XV ARICA Y PARINACOTA", "AP") \
        .str.replace("XI AISEN", "AI") \
        .str.replace("XII MAGALLANES Y ANTARTICA", "MA") \
        .str.replace("VIII DEL BIO-BIO", "BI") \
        .str.replace("REGION DEL LIB. BERNARDO OHIGGINS", "LI") \
        .str.replace("REGION DE AYSEN", "AI") \
        .str.replace("REGION DE MAGALLANES", "MA") \
        .str.replace("REGION METROPOLITANA DE SANTIAGO", "RM") \
        .str.replace("XIII METROPOLITANA", "RM") \
        .str.replace("METROPOLITANA DE SANTIAGO", "RM") \
        .str.replace("REGION METROPOLITANA", "RM")
    return df
				
			

2. Datos de población

				
					
def population_load():
    """
    Return the data frame associated with population.
    """
    df_pop = pd.read_excel(path_poblacion, skiprows=2)
    # Se pivotea la tabla 
    df_pop = pd.melt(df_pop,
                     id_vars=df_pop.columns[[1]],
                     value_vars=df_pop.columns[2:],
                     value_name='Population')
    # Se define el formato y cambio de nombre
    df_pop['variable'] = pd.to_datetime(df_pop['variable']).dt.strftime('%Y')
    df_pop.rename(columns={'variable': 'Year', 'Descripción series': 'Region'},
                  inplace=True)
    # La funcion replace_region estandariza el formato de los nombres de las regiones
    df_pop['Region'] = replace_region(df_pop['Region'])
    df_pop['Year'] = pd.to_datetime(df_pop['Year']).dt.strftime('%Y')
    return df_pop
				
			

2. Datos de siniestros de transito

accident-g0d02d45dd_640
				
					
def accident_load():
    """
    Return the data frame associated with car accidents.
    """
    df_accident: DataFrame = pd.DataFrame()
    # Se itera sobre la lista de enlaces web
    for i in range(len(url_accident)):
        df_load = pd.read_csv(url_accident[i],
                              usecols=['Fecha', 'Región',
                                       'Fallecidos', 'Graves',
                                       'Menos_Grav', 'Leves'],
                              parse_dates=['Fecha'])
        df_accident = df_accident.append(df_load)
    # Se aplica np.nansum para calcular el total de lesionados.
    df_accident['Lesionados_total'] = df_accident[
        ['Graves', 'Menos_Grav', 'Leves']].apply(np.nansum, axis=1)
    df_accident.rename(columns={'Región': 'Region'}, inplace=True)
    df_accident['Month'] = df_accident['Fecha'].dt.strftime('%m')
    df_accident['Year'] = df_accident['Fecha'].dt.strftime('%Y')
    df_accident = df_accident.reset_index(drop=True)
    # Se aplica groupby().sum() ya que los datos estan dados en formato 
    # dd/mm/yyyy HH:MM y se quieren mensuales.
    df_accident = df_accident.groupby(['Region', 'Year', 'Month'],
                                      as_index=False).sum()
    # La funcion replace_region estandariza el formato de los nombres de las regiones
    df_accident['Region'] = replace_region(df_accident['Region'])
    return df_accident
				
			

3. Datos de vehiculos en circulación

				
					


def car_load():
    """
    Return the data frame associated with car population.
    """
    # A year counter is created since the data does not have the corresponding
    # column.
    counter = 2021
    # The titles of the tables of the source of origin are identified, not all
    # the tables have the same format, so several lists of titles must be created.
    Column_0 = ['Año', 'Sector', 'Localidad', 'Total', 'Motorizados', 'No_motorizados', 'Automóvil_StatioWagon',
                'Todo_Terreno', 'Furgón', 'Minibús', 'Camioneta', 'Motocicleta_y_Similares', 'Otros_con_Motor1',
                'Otros_sin_Motor', 'Taxi_Básico', 'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                'Minibús_furgón escolar_y_trabajadores', 'Taxi_bus', 'Bus_Transporte_Colectivo',
                'Bus_transporte_escolar_y_trabajadores', 'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola',
                'Otros_con_Motor2', 'Remolque_Semirremolque']

    Column_1 = ['Año', 'Sector', 'Localidad', 'Total', 'Motorizados', 'No_motorizados', 'Automóvil_StatioWagon',
                'Todo_Terreno', 'Furgón', 'Minibús', 'Camioneta', 'Motocicleta_y_Similares', 'Otros_con_Motor1',
                'Otros_sin_Motor', 'Taxi_Básico', 'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                'Minibús_furgón_escolar_y_trabajadores', 'Bus_Transporte_Colectivo',
                'Bus_transporte_escolar_y_trabajadores', 'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola', 
                'Otros_con_Motor2', 'Remolque_Semirremolque']

    Column_2 = ['Año', 'Sector', 'Localidad', 'Total', 'Automóvil_StatioWagon', 'Todo_Terreno', 'Furgón', 'Minibús',
                'Camioneta', 'Motocicleta_y_Similares', 'Otros_con_Motor1', 'Otros_sin_Motor', 'Taxi_Básico',
                'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                'Minibús_furgón_escolar_y_trabajadores', 'Bus_Transporte_Colectivo',
                'Bus_transporte_escolar_y_trabajadores', 'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola', 
                'Otros_con_Motor2', 'Remolque_Semirremolque']

    Column_3 = ['Año', 'Sector', 'Localidad', 'Total', 'Automóvil_StatioWagon', 'Todo_Terreno', 'Furgón', 'Minibús', 
                'Camioneta', 'Motocicleta_y_Similares', 'Otros_con_Motor1', 'Otros_sin_Motor', 'Taxi_Básico', 
                'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                'Minibús_furgón_escolar_y_trabajadores', 'Bus_Transporte_Colectivo', 
                'Bus_transporte_escolar_y_trabajadores', 'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola', 
                'Otros_con_Motor2', 'Remolque_Semirremolque']

    Column_4 = ['Año', 'Sector', 'Localidad', 'Total', 'Automóvil_StatioWagon_TodoTerreno', 'Furgón', 'Minibús',
                'Camioneta', 'Motocicleta_y_Similares', 'Otros_con_Motor1', 'Otros_sin_Motor', 'Taxi_Básico', 
                'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo', 'Bus_Transporte_Colectivo',
                'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola', 'Otros_con_Motor2', 'Remolque_Semirremolque']

    Common_list: list[str | Any] = ['Año', 'Sector', 'Localidad', 'Automóvil_StatioWagon_TodoTerreno', 'Furgón', 
                                    'Minibús', 'Camioneta', 'Motocicleta_y_Similares', 'Otros_sin_Motor', 
                                    'Taxi_Básico', 'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                                    'Bus_Transporte_Colectivo', 'Camión_Simple', 'Tractocamión', 'Tractor_Agrícola',
                                    'Remolque_Semirremolque']
    df_car = pd.DataFrame()

    for url in range(len(path_cars)):
        if url < 4:
            df_load = pd.read_excel(path_cars[url], sheet_name='4', skiprows=4)
            df_load.columns = Column_4
        elif url == 4:
            df_load = pd.read_excel(path_cars[url], sheet_name='4', skiprows=3)
            df_load.columns = Column_3
        elif url == 5:
            df_load = pd.read_excel(path_cars[url], sheet_name='Cuadro 2.2', skiprows=4)
            df_load.columns = Column_2
        elif 6 <= url < 10:
            df_load = pd.read_excel(path_cars[url], sheet_name='Cuadro 2', skiprows=4)
            df_load.columns = Column_1
        elif url == 10:
            df_load = pd.read_excel(path_cars[url], sheet_name='Cuadro 2', skiprows=9)
            df_load.columns = Column_1
        else:
            df_load = pd.read_excel(path_cars[url], sheet_name='CUADRO 2', skiprows=9)
            df_load.columns = Column_0

        # Asignar el DataFrame a una variable diferente en cada iteración
        df_load[df_load.columns[0]] = counter
        # Se remueven caracteres los identificados en el dataframe que causan errores en la lectura
        # de los datos
        df_load.replace(['-',
                         '    ',
                         '                  -',
                         '                      -',
                         '                          -',
                         '                            -',
                         '                             -'],
                        [np.nan, '', '', '', '', '', ''],
                        inplace=True)
        if url > 3:
            df_load['Automóvil_StatioWagon_TodoTerreno'] = pd.to_numeric(df_load['Automóvil_StatioWagon']) \
                                                           + pd.to_numeric(df_load['Todo_Terreno'])
        else:
            pass
        df_load = df_load[Common_list]
        df_car = df_car.append(df_load)
        df_car['Localidad'] = df_car['Localidad'].str.upper().str.strip()
        counter -= 1

    df_car = df_car.reset_index(drop=True)
    df_car['Localidad'] = df_car['Localidad'].str.upper().str.strip()
    df_car['Sector'] = df_car['Sector'].replace('Región', 'Region')
    lista_region_2 = list(df_car[(df_car['Sector'] == 'Region')]['Localidad'].unique())
    # Se toma solo la información de las regiones
    df_car = df_car[df_car.Localidad.isin(lista_region_2)]
    df_car.rename(columns={'Localidad': 'Region',
                           'Año': 'Year'}, inplace=True)
    df_car.drop('Sector', axis=1, inplace=True)
    df_car.iloc[:, 2:] = df_car.iloc[:, 2:].astype('float32')
    df_car['Year'] = pd.to_datetime(df_car['Year'].astype('str')).dt.strftime('%Y')
    df_car['Region'] = df_car['Region'].str.upper().str.strip()
    df_car['Region'] = [clean_accents(i) for i in list(df_car['Region'])]
    df_car['Region'] = replace_region(df_car['Region']).values
    # Se consolidan los diferentes tipos de transportes de carga en uno solo para 
    # reducir la presencia de nan
    df_car['Transporte_de_carga'] = df_car[['Camión_Simple', 'Tractocamión', 'Tractor_Agrícola',
                                            'Remolque_Semirremolque']].apply(np.nansum, axis=1)
    return df_car.drop(['Camión_Simple', 'Tractocamión', 'Tractor_Agrícola', 'Remolque_Semirremolque'], axis=1)

				
			

4. Datos de registro de empresas y sociedades

				
					
def empresa_load():
    """
    Return the data frame associated with Registration of companies.
    """
    df_empresas = pd.read_excel(path_empresa, skiprows=2)
    # Transpose the columns you want
    df_empresas = pd.melt(df_empresas,
                          id_vars=df_empresas.columns[[1]],
                          value_vars=df_empresas.columns[2:],
                          value_name='Reg_Empresas_Sociedades')
    # The text of the variable Year is extracted
    df_empresas.rename(columns={'variable': 'Date', 'Descripción series': 'Region'},
                       inplace=True)
    df_empresas['Date'] = pd.to_datetime(df_empresas['Date'])
    df_empresas['Year'] = df_empresas['Date'].dt.strftime('%Y')
    df_empresas['Month'] = df_empresas['Date'].dt.strftime('%m')
    df_empresas['Region'] = replace_region(df_empresas['Region'])
    return df_empresas.drop('Date', axis=1)
				
			

5. Datos del consumo de hogares

				
					
def consumo_load():
    """
    Return the data frame associated with Registration of companies.
    """
    df_consumo = pd.read_excel(path_consumo, skiprows=2)
    # Transpose the columns you want
    df_consumo: DataFrame = pd.melt(df_consumo,
                                    id_vars=df_consumo.columns[[1]],
                                    value_vars=df_consumo.columns[2:],
                                    value_name='Consumo_Hogares_IPSFL')
    # The text of the variable Year is extracted
    df_consumo.rename(columns={'variable': 'Date', 'Descripción series': 'Region'},
                      inplace=True)
    df_consumo['Date'] = pd.to_datetime(df_consumo['Date'])
    df_consumo['Year'] = df_consumo['Date'].dt.strftime('%Y')
    df_consumo['Region'] = replace_region(df_consumo['Region'])
    return df_consumo.drop('Date', axis=1)
				
			

6. Datos del Producto Interno Bruto

				
					
def PIB_load():
    """
    Return the data frame associated with Registration of companies.
    """
    df_PIB: DataFrame = pd.read_excel(path_PIB, skiprows=2)
    # Transpose the columns you want
    df_PIB = pd.melt(df_PIB,
                     id_vars=df_PIB.columns[[1]],
                     value_vars=df_PIB.columns[2:],
                     value_name='PIB')
    # The text of the variable Year is extracted
    df_PIB.rename(inplace=True,
                  columns={'variable': 'Date', 'Descripción series': 'Region'})
    df_PIB['Date'] = pd.to_datetime(df_PIB["Date"])
    df_PIB['Year'] = df_PIB['Date'].dt.strftime('%Y')
    df_PIB['Region'] = replace_region(df_PIB['Region'])
    return df_PIB.drop('Date', axis=1)
				
			

7. Datos de la fuerza de trabajo

				
					
def fuerza_trabajo_load():
    """
    Return the data frame associated with Labor force.
    """
    df_Trabajo = pd.read_excel(path_trabajo)
    df_Trabajo['Year'] = pd.to_datetime(df_Trabajo['Year'])
    df_Trabajo['Month'] = df_Trabajo['Year'].dt.strftime('%m')
    df_Trabajo['Year'] = df_Trabajo['Year'].dt.strftime('%Y')

    return df_Trabajo
				
			

8. Datos de hechos delictivos

				
					
def delito_load():
    """
    Return the data frame associated with crimes.
    """
    df_delito = pd.read_csv(path_delito, index_col=0, thousands=',', encoding='latin-1').reset_index()
    df_delito[['Region', 'Year']] = df_delito[['Region', 'Year']].astype('str')
    df_delito['Year'] = pd.to_datetime(df_delito['Year']).dt.strftime('%Y')
    df_delito['Month'] = df_delito['Month'].astype('str').str.rjust(2, '0')
    df_delito['Region'] = replace_region(df_delito['Region'])
    return df_delito

				
			

Dataframe consolidado

				
					
def df_merge(df_accident: DataFrame = accident_load(),
             df_population: DataFrame = population_load(),
             df_car: DataFrame = car_load(),
             df_delito: DataFrame = delito_load(),
             df_trabajo: DataFrame = fuerza_trabajo_load(),
             df_PIB: DataFrame = PIB_load(),
             df_Consumo: DataFrame = consumo_load(),
             df_Empresa: DataFrame = empresa_load()
             ) -> DataFrame:
    """
    Returns the consolidated dataframe.
    """
    # The union of the first two df is made (by 'Year' and 'Region')
    df_Region_I = pd.merge(pd.merge(
        pd.merge(df_population, df_car, on=['Year', 'Region'], how='outer', suffixes=('', '_drop')),
        df_PIB, on=['Year', 'Region'], how='outer', suffixes=('', '_drop')),
        df_Consumo, on=['Year', 'Region'], how='outer', suffixes=('', '_drop'))
    df_Region_II = pd.merge(pd.merge(
        pd.merge(df_delito, df_accident, on=['Year', 'Region', 'Month'], how='outer', suffixes=('', '_drop')),
        df_trabajo, on=['Year', 'Region', 'Month'], how='outer', suffixes=('', '_drop')),
        df_Empresa, on=['Year', 'Region', 'Month'], how='outer', suffixes=('', '_drop'))

    df_Region = pd.merge(df_Region_II, df_Region_I, on=['Year', 'Region'], how='outer', suffixes=('', '_drop'))

    df_Region.drop([col for col in df_Region.columns if 'drop' in col], axis=1, inplace=True)
    Regiones_list = ['AP', 'TA', 'AN', 'AT', 'CO', 'VA', 'RM', 'LI',
                     'ML', 'NB', 'BI', 'AR', 'LR', 'LL', 'AI', 'MA']
    df_Region = df_Region[df_Region['Region'].isin(Regiones_list)]

    # Ordenamos un poco las columnas
    df_Region = df_Region.loc[:,['Region', 'Year', 'Month', 'Population', 'Fallecidos', 'Graves', 'Menos_Grav',
                                 'Leves', 'Lesionados_total', 'Automóvil_StatioWagon_TodoTerreno', 'Furgón', 'Minibús',
                                 'Camioneta', 'Motocicleta_y_Similares', 'Otros_sin_Motor', 'Taxi_Básico',
                                 'Taxi_Colectivo', 'Taxi_Turismo', 'Minibús_Transporte_Colectivo',
                                 'Bus_Transporte_Colectivo', 'Transporte_de_carga',
                                 'Grupo de delitos de mayor connotación social',
                                 'Homicidios', 'Hurtos', 'Lesiones leves', 'Lesiones menos graves graves o gravísimas',
                                 'Otros robos con fuerza', 'Robo con violencia o intimidación',
                                 'Robo de objetos de o desde vehículo', 'Robo de vehículo motorizado',
                                 'Robo en lugar habitado', 'Robo en lugar no habitado', 'Robo por sorpresa',
                                 'Violaciones', 'Grupo de Infracción a ley de armas', 'Abandono de armas',
                                 'Hallazgo de armas o explosivos', 'Otros ley de armas', 'Porte de armas',
                                 'Tenencia ilegal de armas o explosivos', 'Grupo de Incivilidades', 'Amenazas',
                                 'Comercio ambulante o clandestino', 'Consumo alcohol vía pública', 'Daños',
                                 'Desórdenes', 'Ebriedad', 'Otras incivilidades', 'Riña pública', 'Ruidos molestos',
                                 'Abigeato', 'Abusos sexuales y otros delitos sexuales',
                                 'Grupo de Violencia intrafamiliar', 'Violencia intrafamiliar a adulto mayor',
                                 'Violencia intrafamiliar a hombre', 'Violencia intrafamiliar a mujer',
                                 'Violencia intrafamiliar a niño', 'Violencia intrafamiliar no clasificado',
                                 'Receptación', 'Robo frustrado', 'PIB', 'Consumo_Hogares_IPSFL',
                                 'Fuerza_de_trabajo_(Miles_de_personas)', 'Ocupados_(Miles_de_personas)',
                                 'Tasa_de_desocupación_(%)', 'Reg_Empresas_Sociedades']]
    df_Region.columns = df_Region.columns.str.replace(' ', '_')
    return df_Region
				
			

Una vez cargadas todas las funciones, se usa la función df_merge para unirlos, la unión demora unos 8.51 segundos en generarse, el DataFrame resultante contiene 3456 filas y 69 variables. 

 

				
					# Se define el DataFrame ya consolidado como df_Region:
start_time = datetime.now()
df_Region = df_merge(df_accident=accident_load(),
                     df_population=population_load(),
                     df_car=car_load(),
                     df_delito= delito_load(),
                     df_trabajo=fuerza_trabajo_load(),
                     df_PIB=PIB_load(),
                     df_Consumo=consumo_load(),
                     df_Empresa=empresa_load())
print('df_Empresa: Processing time (hh:mm:ss.ms) {}'.format(datetime.now() - start_time))
df_Region
				
			
Region Year Month Population Fallecidos Graves Menos_Grav Leves Lesionados_total Automóvil_StatioWagon_TodoTerreno Furgón Minibús Camioneta Motocicleta_y_Similares Otros_sin_Motor Taxi_Básico Taxi_Colectivo Taxi_Turismo Minibús_Transporte_Colectivo Bus_Transporte_Colectivo Camión_Simple Tractocamión Tractor_Agrícola Remolque_Semirremolque Grupo de delitos de mayor connotación social Homicidios Hurtos Lesiones leves Lesiones menos graves graves o gravísimas Otros robos con fuerza Robo con violencia o intimidación Robo de objetos de o desde vehículo Robo de vehículo motorizado Robo en lugar habitado Robo en lugar no habitado Robo por sorpresa Violaciones Grupo de Infracción a ley de armas Abandono de armas Hallazgo de armas o explosivos Otros ley de armas Porte de armas Tenencia ilegal de armas o explosivos Grupo de Incivilidades Amenazas Comercio ambulante o clandestino Consumo alcohol vía pública Daños Desórdenes Ebriedad Otras incivilidades Riña pública Ruidos molestos Abigeato Abusos sexuales y otros delitos sexuales Grupo de Violencia intrafamiliar Violencia intrafamiliar a adulto mayor Violencia intrafamiliar a hombre Violencia intrafamiliar a mujer Violencia intrafamiliar a niño Violencia intrafamiliar no clasificado Receptación Robo frustrado PIB Consumo_Hogares_IPSFL Fuerza_de_trabajo_(Miles_de_personas) Ocupados_(Miles_de_personas) Tasa_de_desocupación_(%) Reg_Empresas_Sociedades FIELD71
0 AP 2005 01 201749.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 444.0 1.0 93.0 116.0 30.0 6.0 44.0 27.0 26.0 54.0 27.0 17.0 3.0 7.0 0.0 1.0 0.0 5.0 1.0 435.0 40.0 30.0 49.0 93.0 22.0 178.0 23.0 0.0 0.0 1.0 6.0 125.0 0.0 7.0 115.0 3.0 0.0 7.0 7.0 NaN NaN NaN NaN NaN NaN
1 AP 2005 02 201749.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 403.0 0.0 115.0 92.0 26.0 5.0 31.0 26.0 14.0 51.0 30.0 13.0 0.0 11.0 0.0 0.0 0.0 11.0 0.0 410.0 57.0 18.0 42.0 76.0 25.0 164.0 26.0 0.0 2.0 1.0 9.0 112.0 2.0 12.0 95.0 3.0 0.0 4.0 10.0 NaN NaN NaN NaN NaN NaN
2 AP 2005 03 201749.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 433.0 0.0 119.0 89.0 25.0 11.0 28.0 35.0 32.0 59.0 22.0 11.0 2.0 10.0 0.0 0.0 0.0 9.0 1.0 464.0 55.0 27.0 72.0 74.0 20.0 171.0 43.0 0.0 2.0 0.0 8.0 116.0 2.0 12.0 98.0 4.0 0.0 2.0 20.0 NaN NaN NaN NaN NaN NaN
3 AP 2005 04 201749.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 411.0 0.0 95.0 88.0 22.0 6.0 35.0 36.0 21.0 68.0 28.0 10.0 2.0 7.0 0.0 0.0 0.0 5.0 2.0 437.0 40.0 11.0 64.0 80.0 27.0 193.0 22.0 0.0 0.0 0.0 9.0 127.0 6.0 11.0 105.0 5.0 0.0 3.0 12.0 NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3452 MA 2022 09 181143.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 156.0 0.0 43.0 56.0 19.0 1.0 4.0 4.0 5.0 11.0 8.0 0.0 5.0 2.0 0.0 0.0 1.0 1.0 0.0 240.0 86.0 0.0 15.0 99.0 1.0 8.0 31.0 0.0 0.0 1.0 13.0 102.0 1.0 19.0 79.0 3.0 0.0 2.0 0.0 NaN NaN 98.307092 92.424925 5.983462 132.0
3453 MA 2022 10 181143.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 98.360188 93.000401 5.449142 124.0
3454 MA 2022 11 181143.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 98.831505 93.493908 5.400704 114.0
3455 MA 2022 12 181143.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 99.737670 95.788979 3.959076 113.0

3456 rows x 69 columns

 

Finalmente, se guarda el archivo en un formato más liviano para procesarlo en el Paso 2. A continuación se muestran tres formatos para guardar el DataFrame, el más liviano y rápido de procesar es el parquet.

 

				
					
# Formato tradicional
df_Region.to_csv("./df_Region.csv")
# Otras opciones de guardado
df_Region.to_pickle("./df_Region.pkl")
df_Region.to_feather("./df_Region.feather")
# Formato mas liviano (OPTIMO)
df_Region.to_parquet("./df_Region.parquet") 
				
			

Si deseas descargar el DataFrame df_Region puedes hacerlo ingresando aquí.

 

PASO 2. OBSERVACIONES DUPLICADAS

El método pandas.DataFrame.drop_duplicates() se usa para eliminar filas duplicadas; en nuestro ejemplo, mantenemos las filas del DataFrame anterior con la dimensión 3456 filas y 69 columnas porque no se detectaron filas duplicadas. 

 

				
					# Using DataFrame.drop_duplicates() to keep first duplicate row
df = df.drop_duplicates(keep='first')
df.shape
				
			

PASO 3. ESTADÍSTICAS DESCRIPTIVAS BÁSICAS

Las estadísticas descriptivas resumen o describen las características de un conjunto de datos, generalmente se dividen en:

  • Medidas de tendencia central: describen el centro del conjunto de datos (media, mediana, moda),
  • Medidas de variabilidad: describen la dispersión de los datos (varianza, rango, desviación estándar, asimetría, curtosis) y
  • Medidas de posición: se usan para clasificar una observación dentro de la población o muestra (cuartiles, deciles y percentiles).

Las estadísticas descriptivas suelen representarse de forma gráfica y/o tabular, en esta etapa solo utilizaremos la forma tabular, en secciones posteriores se implementarán gráficos. Con la función describe_all se calculan algunas medidas de tendencia central, dispersión y posición del DataFrame guardado en la sección anterior. 

 

Con esta función se incorporan algunas métricas a la matriz de salida de cálculo de estadísticas descriptivas de Pandas pd.describe(), tales como TYPE, NAN, NAN_PROPORTION,   y DECISION, para llevar un conteo de los valores no informados, la proporción de los mismos, el tipo de variable contenida en el DataFrame y la decisión que se pudiera tomar a partir de la cantidad de registros no informados. 

Antes de implementarla, se cargan los datos guardados en la sección anterior y se toma un segmento de los mismos, esto debido a que no se trabajará con todos en esta sección debido a que tienen periodicidades diferentes. Se toma el periodo que va desde el año 2015 hasta el año 2021, continuación se muestra la tabla de salida: 

 

				
					

def describe_all(df, p=0.10, save=True):
    """
    The describe_all function is used to describe a variable and identify the decision to make given the presence of
    NaN, the criteria is, if there is more than 50% NaN, the decision is to remove the variable
    df: column of the data frame to process
    p: proportion of NaN to consider for exclusion == True
    save: bool
    :type df: DataFrame
    :type p: float
    :type save: bool
    """
    describe_df = df.describe(include='all')
    describe_df.loc['type'] = df.dtypes
    describe_df.loc['NaN'] = df['Year'].count() - describe_df.loc['count']
    describe_df.loc['NaN_proportion'] = (describe_df.loc['NaN'] / df['Year'].count())
    describe_df.loc['interquartile_range'] = describe_df.loc['75%']-describe_df.loc['25%']
    describe_df.loc['Decision_p'] = 'Remove'
    describe_df.loc['Decision_IQR'] = 'Remove'
    Describe_df = pd.DataFrame(describe_df.T)
    Describe_df.loc[Describe_df['NaN_proportion'] <= p, 'Decision_p'] = 'Work it'
    Describe_df.loc[Describe_df['interquartile_range'] != 0, 'Decision_IQR'] = 'Work it'
    consequence = ['Fallecidos','Graves','Menos_Grav', 'Leves']
    Describe_df.loc[consequence,'Decision_IQR']='Work it'
    if save:
        Describe_df.to_csv(r'./Descriptivo.csv',
                           index=True, header=True)
    else:
        pass
    return Describe_df

# Se cargan los datos.
df_Region = pd.read_csv("./df_Region.csv")

# Se describe la parte de los datos con los que se quiere trabajar.
describe_all(df_Region[(df_Region['Year']>'2014') &
                       (df_Region['Year']<='2021')])
				
			

69 rows x 17 columns

 

En la tabla se puede observar que de las 69 variables disponibles en el DataFrame 3 son cualitativas y 66 cuantitativas, asimismo se puede observar que del total de 1344 registros esperados en cada variable, en algunas se contabilizan menor cantidad de registros, por lo que hay presencia de valores faltantes, la variable que presenta mayor número de valores faltantes es Tractor_Agrícola, así mismo se observan algunos valores faltantes en las variables asociadas al tipo de lesión en siniestros de tránsito. Con relación a las medidas de posición y tendencia central se puede observar que las variables presentan sesgos importantes y en consecuencia una desviación estándar bastante alta. En la siguiente etapa se identificarán las variables que pueden ser excluidas del análisis y como se tratarán los valores faltantes. 

 

Variable count unique top freq mean std min 25% 50% 75% max type NaN NaN_proportion interquartile_range Decision_p Decision_IQR
Region 1344 16 AP 84 NaN NaN NaN NaN NaN NaN NaN object 0 0.0 NaN Work it Work it
Year 1344 7 2015 192 NaN NaN NaN NaN NaN NaN NaN object 0 0.0 NaN Work it Work it
Month 1344 12 01 112 NaN NaN NaN NaN NaN NaN NaN object 0 0.0 NaN Work it Work it
Population 1344.0 NaN NaN NaN 1174580.857143 1768084.362532 104097.0 325497.0 735916.5 1031659.75 8242459.0 float64 0.0 0.0 706162.75 Work it Work it
Fallecidos 1301.0 NaN NaN NaN 4.421214 4.004149 0.0 1.0 3.0 7.0 21.0 float64 43.0 0.031994 6.0 Work it Work it
Graves 1301.0 NaN NaN NaN 11.348194 9.825214 0.0 4.0 9.0 16.0 63.0 float64 43.0 0.031994 12.0 Work it Work it
Menos_Grav 1301.0 NaN NaN NaN 6.468101 5.798403 0.0 2.0 5.0 10.0 30.0 float64 43.0 0.031994 8.0 Work it Work it
Leves 1301.0 NaN NaN NaN 58.40123 49.898008 0.0 18.0 47.0 84.0 265.0 float64 43.0 0.031994 66.0 Work it Work it
Lesionados_total 1301.0 NaN NaN NaN 76.217525 63.358015 0.0 25.0 63.0 108.0 336.0 float64 43.0 0.031994 83.0 Work it Work it
Automóvil_StatioWagon_TodoTerreno 1308.0 NaN NaN NaN 224362.125 339702.84375 18694.0 60909.0 114871.0 201954.0 1569108.0 float32 36.0 0.026786 141045.0 Work it Work it
Furgón 1308.0 NaN NaN NaN 12966.632812 24352.710938 492.0 1603.0 5198.0 12264.0 114705.0 float32 36.0 0.026786 10661.0 Work it Work it
Minibús 1308.0 NaN NaN NaN 1739.91748 1649.870361 174.0 567.0 965.0 2496.0 7399.0 float32 36.0 0.026786 1929.0 Work it Work it
Camioneta 1308.0 NaN NaN NaN 60416.824219 60968.617188 7875.0 18852.0 46989.0 76979.0 285490.0 float32 36.0 0.026786 58127.0 Work it Work it
Motocicleta_y_Similares 1308.0 NaN NaN NaN 12213.59668 23390.869141 462.0 2116.0 4738.0 12301.0 123575.0 float32 36.0 0.026786 10185.0 Work it Work it
Otros_sin_Motor 1308.0 NaN NaN NaN 2277.54126 2324.756592 134.0 522.0 1591.0 2915.0 10997.0 float32 36.0 0.026786 2393.0 Work it Work it
Taxi_Básico 1308.0 NaN NaN NaN 2172.550537 5458.430664 194.0 390.0 613.0 846.0 24461.0 float32 36.0 0.026786 456.0 Work it Work it
Taxi_Colectivo 1308.0 NaN NaN NaN 3853.816406 3483.369141 236.0 1320.0 3379.0 4590.0 14432.0 float32 36.0 0.026786 3270.0 Work it Work it
Taxi_Turismo 1308.0 NaN NaN NaN 587.715576 944.708435 11.0 139.0 267.0 595.0 4322.0 float32 36.0 0.026786 456.0 Work it Work it
Minibús_Transporte_Colectivo 1308.0 NaN NaN NaN 2361.0 3440.179688 170.0 699.0 1244.0 2750.0 20145.0 float32 36.0 0.026786 2051.0 Work it Work it
Bus_Transporte_Colectivo 1308.0 NaN NaN NaN 3442.651367 3796.36792 192.0 1233.0 2375.0 3807.0 17975.0 float32 36.0 0.026786 2574.0 Work it Work it
Camión_Simple 1308.0 NaN NaN NaN 9806.761719 10918.499023 1414.0 3505.0 7124.0 12302.0 49584.0 float32 36.0 0.026786 8797.0 Work it Work it
Tractocamión 1308.0 NaN NaN NaN 3170.477051 3118.336914 218.0 1137.0 2006.0 4090.0 14446.0 float32 36.0 0.026786 2953.0 Work it Work it
Tractor_Agrícola 1296.0 NaN NaN NaN 543.777771 573.47229 1.0 48.25 376.5 876.75 2174.0 float32 48.0 0.035714 828.5 Work it Work it
Remolque_Semirremolque 1308.0 NaN NaN NaN 4979.981445 4655.186035 374.0 2009.0 3432.0 6980.0 20923.0 float32 36.0 0.026786 4971.0 Work it Work it
Grupo_de_delitos_de_mayor_connotación_social 1344.0 NaN NaN NaN 2632.417411 4540.314393 82.0 707.75 1342.5 2164.0 24181.0 float64 0.0 0.0 1456.25 Work it Work it
Homicidios 1344.0 NaN NaN NaN 3.322917 6.51511 0.0 0.0 1.0 3.0 56.0 float64 0.0 0.0 3.0 Work it Work it
Hurtos 1344.0 NaN NaN NaN 776.970982 1153.382969 22.0 202.25 421.5 796.0 6841.0 float64 0.0 0.0 593.75 Work it Work it
Lesiones_leves 1344.0 NaN NaN NaN 293.747024 423.122425 13.0 87.75 196.0 277.5 2479.0 float64 0.0 0.0 189.75 Work it Work it
Lesiones_menos_graves_graves_o_gravísimas 1344.0 NaN NaN NaN 80.200893 110.131187 4.0 26.0 50.0 76.0 708.0 float64 0.0 0.0 50.0 Work it Work it
Otros_robos_con_fuerza 1344.0 NaN NaN NaN 32.761905 61.919963 0.0 7.0 15.0 30.0 381.0 float64 0.0 0.0 23.0 Work it Work it
Robo_con_violencia_o_intimidación 1344.0 NaN NaN NaN 348.811756 914.5507 0.0 45.0 99.0 160.0 6249.0 float64 0.0 0.0 115.0 Work it Work it
Robo_de_objetos_de_o_desde_vehículo 1344.0 NaN NaN NaN 276.765625 635.719974 0.0 47.0 109.0 175.0 3678.0 float64 0.0 0.0 128.0 Work it Work it
Robo_de_vehículo_motorizado 1344.0 NaN NaN NaN 136.067708 289.03014 0.0 21.0 45.0 112.25 1704.0 float64 0.0 0.0 91.25 Work it Work it
Robo_en_lugar_habitado 1344.0 NaN NaN NaN 273.689732 347.357691 3.0 82.75 162.5 318.0 2149.0 float64 0.0 0.0 235.25 Work it Work it
Robo_en_lugar_no_habitado 1344.0 NaN NaN NaN 232.179315 327.298568 2.0 67.0 139.0 239.25 3037.0 float64 0.0 0.0 172.25 Work it Work it
Robo_por_sorpresa 1344.0 NaN NaN NaN 160.24628 352.318099 0.0 24.0 56.0 104.0 1841.0 float64 0.0 0.0 80.0 Work it Work it
Violaciones 1344.0 NaN NaN NaN 17.653274 26.895712 0.0 5.0 10.0 18.0 231.0 float64 0.0 0.0 13.0 Work it Work it
Grupo_de_Infracción_a_ley_de_armas 1344.0 NaN NaN NaN 69.067708 112.904017 0.0 19.0 35.0 64.0 887.0 float64 0.0 0.0 45.0 Work it Work it
Abandono_de_armas 1344.0 NaN NaN NaN 0.05878 0.330117 0.0 0.0 0.0 0.0 5.0 float64 0.0 0.0 0.0 Work it Remove
Hallazgo_de_armas_o_explosivos 1344.0 NaN NaN NaN 1.901786 3.168516 0.0 0.0 1.0 2.0 21.0 float64 0.0 0.0 2.0 Work it Work it
Otros_ley_de_armas 1344.0 NaN NaN NaN 16.190476 27.808826 0.0 3.0 8.0 18.0 439.0 float64 0.0 0.0 15.0 Work it Work it
Porte_de_armas 1344.0 NaN NaN NaN 47.177827 88.679401 0.0 9.0 21.0 42.0 654.0 float64 0.0 0.0 33.0 Work it Work it
Tenencia_ilegal_de_armas_o_explosivos 1344.0 NaN NaN NaN 3.738839 5.958365 0.0 1.0 2.0 4.0 41.0 float64 0.0 0.0 3.0 Work it Work it
Grupo_de_Incivilidades 1344.0 NaN NaN NaN 2590.508929 3555.286328 99.0 777.75 1750.0 2694.75 21583.0 float64 0.0 0.0 1917.0 Work it Work it
Amenazas 1344.0 NaN NaN NaN 634.510417 975.564708 32.0 171.0 384.5 598.25 5262.0 float64 0.0 0.0 427.25 Work it Work it
Comercio_ambulante_o_clandestino 1344.0 NaN NaN NaN 161.916667 438.495314 0.0 7.0 35.0 114.0 3739.0 float64 0.0 0.0 107.0 Work it Work it
Consumo_alcohol_vía_pública 1344.0 NaN NaN NaN 631.606399 983.813929 0.0 132.0 310.0 740.25 7688.0 float64 0.0 0.0 608.25 Work it Work it
Daños 1344.0 NaN NaN NaN 577.102679 761.356865 36.0 185.0 363.5 607.25 4344.0 float64 0.0 0.0 422.25 Work it Work it
Desórdenes 1344.0 NaN NaN NaN 23.251488 59.620898 0.0 3.0 8.0 20.0 1117.0 float64 0.0 0.0 17.0 Work it Work it
Ebriedad 1344.0 NaN NaN NaN 243.110863 264.128878 2.0 89.75 163.0 316.0 1921.0 float64 0.0 0.0 226.25 Work it Work it
Otras_incivilidades 1344.0 NaN NaN NaN 254.438244 319.243688 8.0 67.0 145.5 265.0 1917.0 float64 0.0 0.0 198.0 Work it Work it
Riña_pública 1344.0 NaN NaN NaN 19.974702 21.215821 0.0 6.0 13.0 24.0 128.0 float64 0.0 0.0 18.0 Work it Work it
Ruidos_molestos 1344.0 NaN NaN NaN 44.59747 55.031374 0.0 3.0 26.0 64.0 316.0 float64 0.0 0.0 61.0 Work it Work it
Abigeato 1344.0 NaN NaN NaN 18.185268 20.804928 0.0 2.0 11.0 28.0 125.0 float64 0.0 0.0 26.0 Work it Work it
Abusos_sexuales_y_otros_delitos_sexuales 1344.0 NaN NaN NaN 59.799851 88.620986 2.0 18.0 33.0 61.0 687.0 float64 0.0 0.0 43.0 Work it Work it
Grupo_de_Violencia_intrafamiliar 1344.0 NaN NaN NaN 634.730655 827.521407 46.0 229.0 389.0 671.25 4973.0 float64 0.0 0.0 442.25 Work it Work it
Violencia_intrafamiliar_a_adulto_mayor 1344.0 NaN NaN NaN 14.626488 25.221203 0.0 3.0 8.0 14.0 196.0 float64 0.0 0.0 11.0 Work it Work it
Violencia_intrafamiliar_a_hombre 1344.0 NaN NaN NaN 94.97247 130.806075 4.0 31.75 56.0 102.0 783.0 float64 0.0 0.0 70.25 Work it Work it
Violencia_intrafamiliar_a_mujer 1344.0 NaN NaN NaN 487.218006 620.42136 31.0 174.75 306.5 521.25 3618.0 float64 0.0 0.0 346.5 Work it Work it
Violencia_intrafamiliar_a_niño 1344.0 NaN NaN NaN 24.290923 36.911753 0.0 7.0 14.0 25.0 240.0 float64 0.0 0.0 18.0 Work it Work it
Violencia_intrafamiliar_no_clasificado 1344.0 NaN NaN NaN 13.622768 25.706948 0.0 3.0 7.0 14.0 332.0 float64 0.0 0.0 11.0 Work it Work it
Receptación 1344.0 NaN NaN NaN 44.630952 71.569621 0.0 12.0 25.0 41.0 476.0 float64 0.0 0.0 29.0 Work it Work it
Robo_frustrado 1344.0 NaN NaN NaN 45.310268 81.377702 0.0 9.0 21.0 40.0 512.0 float64 0.0 0.0 31.0 Work it Work it
PIB 1344.0 NaN NaN NaN 10407.497467 17784.675871 1050.441136 2373.04526 5316.867697 8917.322293 82857.681622 float64 0.0 0.0 6544.277034 Work it Work it
Consumo_Hogares_IPSFL 1344.0 NaN NaN NaN 7112.840091 14568.218439 479.389591 1509.786 3753.810556 4357.701281 71604.390215 float64 0.0 0.0 2847.915281 Work it Work it
Fuerza_de_trabajo_(Miles_de_personas) 1344.0 NaN NaN NaN 572.320604 914.33451 55.487947 158.9883 341.414386 484.627522 4321.237051 float64 0.0 0.0 325.639222 Work it Work it
Ocupados_(Miles_de_personas) 1344.0 NaN NaN NaN 528.178481 839.766771 49.969041 144.932392 314.448727 450.426301 3977.141045 float64 0.0 0.0 305.493908 Work it Work it
Tasa_de_desocupación_(%) 1344.0 NaN NaN NaN 7.075024 2.345473 1.091505 5.638536 7.180471 8.314839 16.128285 float64 0.0 0.0 2.676303 Work it Work it
Reg_Empresas_Sociedades 1300.0 NaN NaN NaN 574.432308 1028.449712 31.0 128.75 282.0 519.5 7463.0 float64 44.0 0.032738 390.75 Work it Work it

PASO 4. VARIABLES NO INFORMATIVAS

En esta sección se hace uso de las estadísticas descriptivas calculadas en el paso anterior. Las variables que tienen más del 10% de información faltante se cuentan y se excluyen del estudio. Es importante señalar que esta es una medida preestablecida al inicio del estudio, no es generalizable a todos los casos, es decisión del investigador sobre los criterios de limpieza que decida tomar.

Adicionalmente, si para las variables alfanuméricas existe una alta concentración en un valor y, a priori, no es posible observar que se pueden realizar discriminaciones con diferentes comportamientos frente a la variable objetivo y con una población suficiente, se eliminan del estudio. 

En la última columna de la tabla se puede observar la decisión a tomar respecto a la exclusión de la variable con base en los criterios antes mencionados.

 

				
					# Se cargan los datos.
df_Region = pd.read_parquet("./df_Region.parquet")

# Se describe la parte de los datos con los que se quiere trabajar.
describe_all(df_Region[(df_Region['Year']>'2014') &
                       (df_Region['Year']<='2021')])
				
			
Variable count unique top freq mean std min 25% 50% 75% max type NaN NaN_proportion interquartile_range Decision_p Decision_IQR
Fallecidos 1301.0 NaN NaN NaN 4.421214 4.004149 0.0 1.0 3.0 7.0 21.0 float64 43.0 0.031994 6.0 Work it Work it
Graves 1301.0 NaN NaN NaN 11.348194 9.825214 0.0 4.0 9.0 16.0 63.0 float64 43.0 0.031994 12.0 Work it Work it
Menos_Grav 1301.0 NaN NaN NaN 6.468101 5.798403 0.0 2.0 5.0 10.0 30.0 float64 43.0 0.031994 8.0 Work it Work it
Leves 1301.0 NaN NaN NaN 58.40123 49.898008 0.0 18.0 47.0 84.0 265.0 float64 43.0 0.031994 66.0 Work it Work it
Lesionados_total 1301.0 NaN NaN NaN 76.217525 63.358015 0.0 25.0 63.0 108.0 336.0 float64 43.0 0.031994 83.0 Work it Work it
Automóvil_StatioWagon_TodoTerreno 1308.0 NaN NaN NaN 224362.125 339702.84375 18694.0 60909.0 114871.0 201954.0 1569108.0 float32 36.0 0.026786 141045.0 Work it Work it
Furgón 1308.0 NaN NaN NaN 12966.632812 24352.710938 492.0 1603.0 5198.0 12264.0 114705.0 float32 36.0 0.026786 10661.0 Work it Work it
Minibús 1308.0 NaN NaN NaN 1739.91748 1649.870361 174.0 567.0 965.0 2496.0 7399.0 float32 36.0 0.026786 1929.0 Work it Work it
Camioneta 1308.0 NaN NaN NaN 60416.824219 60968.617188 7875.0 18852.0 46989.0 76979.0 285490.0 float32 36.0 0.026786 58127.0 Work it Work it
Motocicleta_y_Similares 1308.0 NaN NaN NaN 12213.59668 23390.869141 462.0 2116.0 4738.0 12301.0 123575.0 float32 36.0 0.026786 10185.0 Work it Work it
Otros_sin_Motor 1308.0 NaN NaN NaN 2277.54126 2324.756592 134.0 522.0 1591.0 2915.0 10997.0 float32 36.0 0.026786 2393.0 Work it Work it
Taxi_Básico 1308.0 NaN NaN NaN 2172.550537 5458.430664 194.0 390.0 613.0 846.0 24461.0 float32 36.0 0.026786 456.0 Work it Work it
Taxi_Colectivo 1308.0 NaN NaN NaN 3853.816406 3483.369141 236.0 1320.0 3379.0 4590.0 14432.0 float32 36.0 0.026786 3270.0 Work it Work it
Taxi_Turismo 1308.0 NaN NaN NaN 587.715576 944.708435 11.0 139.0 267.0 595.0 4322.0 float32 36.0 0.026786 456.0 Work it Work it
Minibús_Transporte_Colectivo 1308.0 NaN NaN NaN 2361.0 3440.179688 170.0 699.0 1244.0 2750.0 20145.0 float32 36.0 0.026786 2051.0 Work it Work it
Bus_Transporte_Colectivo 1308.0 NaN NaN NaN 3442.651367 3796.36792 192.0 1233.0 2375.0 3807.0 17975.0 float32 36.0 0.026786 2574.0 Work it Work it
Camión_Simple 1308.0 NaN NaN NaN 9806.761719 10918.499023 1414.0 3505.0 7124.0 12302.0 49584.0 float32 36.0 0.026786 8797.0 Work it Work it
Tractocamión 1308.0 NaN NaN NaN 3170.477051 3118.336914 218.0 1137.0 2006.0 4090.0 14446.0 float32 36.0 0.026786 2953.0 Work it Work it
Tractor_Agrícola 1296.0 NaN NaN NaN 543.777771 573.47229 1.0 48.25 376.5 876.75 2174.0 float32 48.0 0.035714 828.5 Work it Work it
Remolque_Semirremolque 1308.0 NaN NaN NaN 4979.981445 4655.186035 374.0 2009.0 3432.0 6980.0 20923.0 float32 36.0 0.026786 4971.0 Work it Work it
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Abandono_de_armas 1344.0 NaN NaN NaN 0.05878 0.330117 0.0 0.0 0.0 0.0 5.0 float64 0.0 0.0 0.0 Work it Remove
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Reg_Empresas_Sociedades 1300.0 NaN NaN NaN 574.432308 1028.449712 31.0 128.75 282.0 519.5 7463.0 float64 44.0 0.032738 390.75 Work it Work it

68 rows x 17 columns

 

El proceso se hace en varias fases, primero se eliminan las variables que presentan las siguientes condiciones:

Condición 1. Variables que cuentan con más del 10% de registros no informados. Ninguna variable cumple esta condición, el conteo de “Tractor_Agrícola” es quien presenta mayor cantidad de registros no informados, pero se informa una proporción del 1 – 0.035714 de los registros totales (3.57% de valores faltantes) por lo tanto, conservamos la variable.

Condición 2: Variables con rango intercuartílico (IQR) igual a cero, excluyendo las que están asociadas a variables objetivos tales como [‘Fallecidos’,’Leionados_total’]. Se puede observar que el IQR de la variable “Abandono_de_armas” es de 0, lo que indica que existe una alta concentración en torno al valor “0” y, a priori, no es posible realizar discriminaciones con diferentes comportamientos frente a las variables objetivo, por esta razón se elimina del estudio. 

Luego de eliminar estas variables nos quedamos con 68 columnas en nuestro DataFrame. Posteriormente, considerando que se tienen muchas variables y pocas observaciones, se puede reducir la dimensionalidad  implementando alguna técnica de agrupación multivariada, sin embargo, en el DataFrame se encuentran agrupadas desde la fuente las variables asociadas a delitos de la siguiente manera;

Grupo de delitos de mayor connotación social

  • Homicidios.
  • Hurtos.
  • Lesiones leves.
  • Lesiones menos graves graves o gravísimas.
  • Otros robos con fuerza.
  • Robo con violencia o intimidación.
  • Robo de objetos de o desde vehículo.
  • Robo de vehículo motorizado.
  • Robo en lugar habitado.
  • Robo en lugar no habitado.
  • Robo por sorpresa.
  • Violaciones.

Grupo de Violencia intrafamiliar

  • Violencia intrafamiliar a adulto mayor.
  • Violencia intrafamiliar a hombre.
  • Violencia intrafamiliar a mujer.
  • Violencia intrafamiliar no clasificado.
  • Violencia intrafamiliar a niño.

 

Grupo de Infracción a ley de armas

  • Abandono de armas.
  • Hallazgo de armas o explosivos.
  • Otros ley de armas.
  • Porte de armas.
  • Tenencia ilegal de armas o explosivos

 

Grupo de Incivilidades

  • Amenazas.
  • Comercio ambulante o clandestino.
  • Consumo alcohol vía pública.
  • Daños.
  • Desórdenes.
  • Ebriedad.
  • Otras incivilidades.
  • Riña pública.
  • Ruidos molestos.

Abusos sexuales y otros delitos sexuales

Abigeato

Receptación

Robo frustrado

Muchas de estas variables se encuentran altamente correlacionadas, así que se tomarán solo los conteos por grupo delictual y los delitos se excluirán de esta fase, (podrían considerarse en procesos posteriores, pero de momento menos puede ser más dado el tamaño muestral), en este sentido se excluyen 31 variables asociadas a delitos y se conservan los grupos delictuales resaltados previamente.

Con relación a los registros de siniestros de tránsito, se tiene la variable  Lesionados_total, la cual resulta de la suma de “Graves”, Menos_Grav y “Leves”, por lo que se encuentran altamente correlacionadas y se pueden eliminar las últimas 3 que ya están contenidas en la primera.

 

				
					# Eliminación de variables que cumplen con la condición 1
describe_df = display(describe_all(df_Region,p=0.10, save=True))
df_clean = df_Region.drop(list((describe_df[describe_df['Decision_p']=='Remove']).index),axis=1)
# Eliminación de variables que cumplen con la condición 2
df_clean = df_clean.drop(list((describe_df[describe_df['Decision_IQR']=='Remove']).index),axis=1)
# Variables duplicadas o altamente correlacionadas con otras que las contienen
df_Region.drop(['Homicidios', 'Hurtos', 'Lesiones_leves', 'Lesiones_menos_graves_graves_o_gravísimas',
                'Otros_robos_con_fuerza', 'Robo_con_violencia_o_intimidación', 'Robo_de_objetos_de_o_desde_vehículo',
                'Robo_de_vehículo_motorizado', 'Robo_en_lugar_habitado', 'Robo_en_lugar_no_habitado',
                'Robo_por_sorpresa', 'Violaciones','Abandono_de_armas','Hallazgo_de_armas_o_explosivos', 
                'Otros_ley_de_armas', 'Porte_de_armas', 'Tenencia_ilegal_de_armas_o_explosivos', 'Amenazas',
                'Comercio_ambulante_o_clandestino', 'Consumo_alcohol_vía_pública', 'Daños', 'Desórdenes', 'Ebriedad',
                'Otras_incivilidades', 'Riña_pública', 'Ruidos_molestos','Violencia_intrafamiliar_a_adulto_mayor',
                'Violencia_intrafamiliar_a_hombre', 'Violencia_intrafamiliar_a_mujer', 
                'Violencia_intrafamiliar_a_niño', 'Violencia_intrafamiliar_no_clasificado', 'Graves', 'Menos_Grav',
                'Leves'],axis=1, inplace=True)
				
			

El DataFrame ahora contiene 35 columnas y 1344 filas. Se puede reducir aún más excluyendo las variables que no sostienen una relación fuerte con la objetivo (Lesionados_total ) pero ese proceso de descarte se realizara más adelante en la etapa de construcción de modelos.

PASO 5. VALORES ATÍPICOS

Los outliers o valores atípicos son esas observaciones con valores muy diferentes al resto de la muestra.  Ellos pueden provenir de diferentes fuentes de errores o deberse a una probabilidad aleatoria inherente al proceso. En ocasiones estos valores pueden afectar los ajustes que se estén realizando de los datos, entre los problemas más comunes causados por outliers se tienen: el incremento de la varianza del error, incidir en el rechazo o aceptación de una prueba de hipótesis, causar sesgos en los ajustes, quebrantar supuestos de distribución. Es por ello que suele considerarse la posibilidad de eliminarlos o sustituirlos como opciones convenientes y previas a la realización de cálculos más avanzados. La consideración que toma cada investigador con relación al tratamiento de outliers está ligada a consecuencias tanto positivas como negativas, dependiendo de múltiples factores, algunos de los más importantes son:

  • El tamaño de la muestra: cuando se tienen muestras muy pequeñas, reducirlas aún más al eliminar los datos atípicos puede ser contraproducente.
  • La técnica que se desea implementar: en ocasiones, la dispersión que aportan los datos puede ser capturada por la técnica sin la necesidad de remover los outliers, quizás es conveniente hacer ajustes previos al tratamiento de outliers y posteriores a este para ver si el mismo realmente aporta un beneficio al estudio.
  • La distribución de los datos: cuando se tienen datos distribuidos normalmente, usar el rango intercuartílico para identificar valores atípicos (tradicionalmente se utilizan boxplots) es una opción simple y puede ser conveniente cuando se ajustan, por ejemplo, modelos de regresión, sin embargo, ante la presencia de datos que no están normalmente distribuidos, resulta más conveniente utilizar otras técnicas de identificación de outliers, como los asociados a la minería de datos, mediante el análisis de agrupaciones o mediante los cálculos de distancias.

Tipos de outliers

Los valores atípicos suelen clasificarse en grupos, entre los más comunes se encuentran:

  1. Valores atípicos que surgen de errores en los datos: asociados a los procesos de codificación y/o lectura de los datos, normalmente se corrigen en las fases de lectura de las estadísticas descriptivas básicas, por ejemplo un valor máximo de 154 metros en la variable estatura de personas adultas (metros) es lógico interpretarlo como un error, se puede reemplazar en caso de identificarse cuál es el valor real, imputarse, marcarse como dato no informado o eliminarse. 
  2. Valores atípicos que surgen de procesos aleatorios: hay observaciones extremas que ocurren como consecuencia de eventos extraordinarios, pero inherentes al proceso aleatorio, en estos casos se suele dejar el valor para incorporar la variabilidad que aporta al análisis, pero también se realizan experimentos con y sin los valores atípicos para medir su impacto en la precisión de los resultados.
  3. Valores atípicos que surgen de ausencias en el proceso: hay observaciones que provienen del resultado de la combinación de factores inherentes al proceso de consolidación de la base de datos y en ocasiones uno de esos factores se omite, esto suele ocurrir con frecuencia cuando se trabaja con modelos de series temporales cuyo insumo proviene de diversas fuentes y una de sus fuentes marca los valores ausentes con cifras como por ejemplo el valor -99.99 que se encuentra en los datos de la variable SOIEn estos casos conviene tener presente el momento de disponibilidad de la información para elaborar estrategias de consolidación de nuestras bases de datos que estén en armonía con la disponibilidad del insumo, en otros casos se extienden los análisis a crear técnicas para imputar los registros no informados mediante técnicas de pronóstico. A la final, la decisión que se tome con relación a estos valores atípicos depende de la habilidad del investigador para hacer uso de la información y los recursos disponibles.

Detección de outliers

En la actualidad existen diversas técnicas para identificar valores atípicos, estas se clasifican según el número de variables que intervienen en el proceso:

Detección univariante de outliers: bajo el paradigma univariante, la detección de outliers suele realizarse a partir de intervalos que de alguna manera se establecen a priori, o a partir de las desviaciones que presentan los datos con relación al rango intercuartílico, sin embargo, también hay procesos más elaborados como los diagramas de control de procesos estadísticos, conocidos como cartas de control, pero no serán implementadas acá, solo se utilizarán los métodos gráficos tradicionales (histogramas de frecuencias, dispersión, boxplots).

Detección multivariante de outliers: bajo este paradigma, los valores atípicos parten de la desviación que se detecta a partir de la interacción en un conjunto de variables, es decir, la detección de outliers mediante técnicas multivariantes implica el cálculo de relaciones entre las diferentes variables que intervienen en el proceso, este cálculo puede ser basado en medidas de distancias o correlaciones. 

 

Uso de diagramas univariados para detectar outliers

 Los diagramas de cajas son en esencia un método paramétrico basado en el principio de normalidad que identifica valores atípicos de acuerdo a la distancia que presenta el mismo outlier con relación al primer (Q1) o tercer cuartil (Q3) de la distribución de los datos (rango intercuartílico – prueba de Tukey). Cuando se usan Boxplot para identificar los outliers se observa cuando algunos valores no se corresponden con los restantes al encontrarse distantes a los intervalos de confianza. Por ejemplo, en la figura 1 se observa un Boxplot para los registros de personas lesionadas en el mes en siniestros de tránsito de Chile según la Región de ocurrencia, allí se observa la presencia de algunos valores atípicos, pero con excepción de los que se presentan en la Región de Antofagasta (AN) no parece evidenciarse un problema serio de outliers para la variable explicativa, sin embargo, se mostrará una posible forma de hacer la limpieza.

 

				
					
# Se toma la parte de los datos con los que se quiere trabajar.
df_sample = (df_Region[(df_Region['Year']>'2014') &
                       (df_Region['Year']<='2021')])
                       
params = {'axes.titlesize':'26',
          'xtick.labelsize':'20',
          'ytick.labelsize':'22',
          'xtick.color':'#222222',
          'ytick.color':'#222222',
          'figure.figsize': (30,8),
          'figure.dpi': 100
          }
plt.rcParams.update(params)
plt.figure(facecolor='#E7E7E7', alpha=0.8)
point = dict(markerfacecolor='#000000', marker='.')
plt.style.use('ggplot')
fig = df_sample.boxplot(column=['Lesionados_total'],
                 color='#000000',
                 flierprops=point,
                 by=['Region'],
                 rot = 90)
fig_axes = fig.get_figure()
fig_axes.set_facecolor("#E7E7E7")
fig_axes.suptitle('')
plt.ylabel('Frequencia',size=24, color='#222222')
plt.xlabel("Lesionados en el mes por Región", size=24, color='#222222')
plt.title('')
plt.savefig('./Output/boxplot_Lesionados.png', bbox_inches='tight')
plt.show()
				
			

Figura 1. Diagrama de cajas de frecuencias observadas del número de fallecidos en siniestros de tránsito según región.

 

Figura 2. Histograma de frecuencias observadas del número de fallecidos por siniestro de tránsito (considera todas las regiones).

 

Figura 3. Diagrama de dispersión del número de fallecidos por siniestro de tránsito

 

Cuando se va a realizar la limpieza de outliers conviene hacerse varias preguntas, algunas de estas son:

  • ¿Los datos se encuentran sesgados?,
  • ¿Se está trabajando con series temporales?,
  • ¿Hay alguna agrupación que se debe considerar antes de aplicar algún filtro?.

Cuando se trabaja con series temporales tiene importancia la presencia de estacionalidad y autocorrelación de las series, por lo que la eliminación de registros no es lo más correcto para estos casos, ya que se altera el ciclo que acompaña a los datos, para estos casos es más conveniente la imputación. Por otro lado, cuando los datos tienen una estructura segmentada o responden a alguna agrupación asociada, puede ser conveniente tomar en cuenta la misma antes de aplicar cualquier filtro. En el caso de los registros de lesiones mensuales por Región, se deben aplicar los filtros por región porque la distribución puede variar por zona geográfica, esto es más claro si se comparan las distribuciones de frecuencias de los registros de lesiones mensuales por accidentes de tránsito para cada región:

Región de Tarapaca (TA)

Región de Antofagasta (AN)

Region de Atacama (AT)

hist_Lesionados_AT

Region de Coquimbo (CO)

hist_Lesionados_CO

Región de Valparaiso (VA)

Región del Libertador General Bernardo O'higgins (LI)

Region del Maule (ML)

Region del Biobio (BI)

Región de la Araucania (AR)

Región de Los Lagos (LL)

Region de Aysen del General Carlos Ibañez del Campo (AI)

Region de Magallanes y de la Antartica Chilena (MA)

Región Metropolitana de Santiago (MR)

Región de Los Rios (LR)

Region de Arica y Parinacota (AP)

Region de Ñuble (NB)

Se puede observar que la distribución de frecuencias es diferente cuando se trabajan los histogramas por región que cuando se consolidan los registros de todas las regiones en un solo histograma (Figura 2), se pudiera aplicar el rango intercuartílico en la mayoría de los casos para limpiar los outlier, para ello se puede implementar la función clean_outliers() y los boxplots por región quedarían de la siguiente manera: 

 

				
					
def clean_outliers(df, var_clean='Lesionados_total'):
    df_no_outliers = []
    for i in df.Region.astype('object').unique():
        Q = df[df['Region']== i ][var_clean].quantile([.25, .75])
        LS = ((1.5*(Q.values[1]-Q.values[0]))+Q.values[1])
        LI = (Q.values[0]-(0.5*(Q.values[1]-Q.values[0])))
        df_filtered = df[(df['Region']== i) &
                            (df[var_clean]<=LS) &
                            (df[var_clean]>=LI)]
        df_no_outliers.append(df_filtered)
    df_no_outliers = pd.concat(df_no_outliers)
    return df_no_outliers
				
			

Figura 5. Diagrama de cajas de frecuencias observadas del número de fallecidos en siniestros de tránsito según región, posterior al tratamiento de outliers.

 

PASO 6. REGISTROS NO INFORMADOS

Los datos faltantes son aquellos registros que no se encuentran informados en la base de datos. Las razones por las que se presentan datos faltantes pueden ser múltiples, pero suelen atribuirse a causas aleatorias o no aleatorias.

Tipos de registros no informados

  • Registros no informados de manera aleatoria (MAR: Missing At Random): en estos casos la ausencia del registro se presenta debido a la presencia de los valores si informados de otras variables. 
  • Registros no informados de manera no aleatoria (MNAR: Missing Not At Random): ocurre cuando la ausencia del registro es producto del valor de los mismos registros no observados. Es el caso más complejo debido a que la ausencia del registro es producto de causas sistémicas que deben identificarse y ser tratadas.

En la práctica hay dos aproximaciones para tratar los registros no informados:

  • Eliminar los casos no informados de la muestra o eliminar las variables que presentan los datos faltantes.
  • Imputar los registros no informados, consiste en estimar dichos valores mediante la implementación de diversos métodos.

Métodos de imputación

Los métodos de imputación se basan en la estimación del registro no informado a partir del comportamiento de los registros si informados, bien sea de la misma variable o de las otras variables del estudio.

Entre los principales procedimientos para imputar se encuentran:

  • Imputación por la media: se basa en sustituir los valores ausentes por la media de los valores válidos.
  •  Imputación por el vecino más cercano: se basa en sustituir los valores faltantes por la media de todos los valores situados a k-distancias del registro no informado.
  • Imputación por regresión: se basa en estimar los registros no informados considerando la relación que sostienen los si informados de la variable con otras mediante Análisis de Regresión.

En términos generales, imputar registros puede también tener consecuencias negativas, como lo son el distorsionar la distribución de la variable, afectar la correlación

y la varianza de las mismas, sin embargo, también puede ayudar en el proceso de ajuste cuando no se cuenta con suficientes registros, ayuda a reducir el sesgo que se produce por la ausencia de los datos y hacer el procedimiento más consistente.

A continuación se tomarán los registros que quedaron de la matriz anterior (29 variables y 1344 filas) y se observarán los valores faltantes

				
					
# Se selecciona la parte de los datos con los que se quiere trabajar.
df_sample = (df_Region[(df_Region['Year']>'2014') &
                       (df_Region['Year']<='2021')])
# Se imprimen los nan
df_sample[df_sample.isna().values]
				
			
Region Year Month Population Fallecidos Lesionados_total Automóvil_StatioWagon_TodoTerreno Furgón Minibús Camioneta Motocicleta_y_Similares Otros_sin_Motor Taxi_Básico Taxi_Colectivo Taxi_Turismo Minibús_Transporte_Colectivo Bus_Transporte_Colectivo Transporte_de_carga Grupo_de_delitos_de_mayor_connotación_social Grupo_de_Infracción_a_ley_de_armas Grupo_de_Incivilidades Abigeato Abusos_sexuales_y_otros_delitos_sexuales Grupo_de_Violencia_intrafamiliar Receptación Robo_frustrado PIB Consumo_Hogares_IPSFL Fuerza_de_trabajo_(Miles_de_personas) Ocupados_(Miles_de_personas) Tasa_de_desocupación_(%) Reg_Empresas_Sociedades
NB 2015 01 493464.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 894.0 15.0 865.0 15.0 25.0 368.0 13.0 17.0 2087.852542 1567.86672 237.838431 220.145929 7.438874 NaN
NB 2015 01 493464.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 894.0 15.0 865.0 15.0 25.0 368.0 13.0 17.0 2087.852542 1567.86672 237.838431 220.145929 7.438874 NaN
NB 2015 01 493464.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 894.0 15.0 865.0 15.0 25.0 368.0 13.0 17.0 2087.852542 1567.86672 237.838431 220.145929 7.438874 NaN
NB 2015 01 493464.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 894.0 15.0 865.0 15.0 25.0 368.0 13.0 17.0 2087.852542 1567.86672 237.838431 220.145929 7.438874 NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...

En este caso no se implementará ningún método de imputación debido a que los valores faltantes se asocian a registros para la Región de Ñuble previos al año 2018, considerando que el decreto promulgatorio de la ley que crea la Región de Ñuble entró en vigencia en el año 2018, tiene sentido que estos valores no se encuentren informados. 

				
					# Se remueven los nan
df_sample.dropna(inplace=True)
# Se imprime la dimensión de la matriz final
df_sample.shape
				
			

Se remueven los registros no informados y se imprime la dimensión de la matriz final, la cual cuenta con 1293 filas y 29 columnas. Ahora, luego de hacer la limpieza de los registros, se continúa con la siguiente etapa, un análisis descriptivo más exhaustivo.

Si deseas descargar el DataFrame df_Region final puedes hacerlo ingresando aquí.

 

Consultoría académica

Diseño de soluciones basadas en estadísticas

Diseño de soluciones basadas en ML

Proceso de limpieza de datos