Usar Machine Learning para predecir el tiempo: Parte 1

    Parte 1: Recopilación de datos de Weather Underground

    Este es el primer artículo de una serie de varias partes sobre el uso de Python y el Machine Learning para crear modelos para predecir las temperaturas climáticas en función de los datos recopilados de Weather Underground. La serie estará compuesta por tres artículos diferentes que describen los aspectos principales de un proyecto de Machine Learning. Los temas a cubrir son:

    • Recopilación y procesamiento de datos (este artículo)
    • Modelos de regresión lineal (artículo 2)
    • Modelos de redes neuronales (artículo 3)

    Los datos utilizados en esta serie se recopilarán del servicio web API de nivel gratuito de Weather Underground. Utilizaré la biblioteca de solicitudes para interactuar con la API y obtener datos meteorológicos desde 2015 para la ciudad de Lincoln, Nebraska. Una vez recopilados, los datos deberán procesarse y agregarse en un formato que sea adecuado para el análisis de datos, y luego limpiarse.

    El segundo artículo se centrará en analizar las tendencias en los datos con el objetivo de seleccionar las características adecuadas para construir un modelo de regresión lineal utilizando las bibliotecas de Python statsmodels y scikit-learn . Discutiré la importancia de comprender los supuestos necesarios para usar un modelo de regresión lineal y demostraré cómo evaluar las características para construir un modelo robusto. Este artículo concluirá con una discusión sobre las pruebas y la validación del modelo de regresión lineal.

    El artículo final se centrará en el uso de redes neuronales. Compararé el proceso de construcción de un modelo de red neuronal, interpretando los resultados y la precisión general entre el modelo de regresión lineal construido en el artículo anterior y el modelo de red neuronal.

    Familiarizarse con Weather Underground

    Weather Underground es una empresa que recopila y distribuye datos sobre diversas mediciones meteorológicas en todo el mundo. La compañía ofrece una amplia gama de API que están disponibles para usos comerciales y no comerciales. En este artículo, describiré cómo extraer de manera programática datos meteorológicos diarios de Weather Underground utilizando su nivel de servicio gratuito disponible para fines no comerciales.

    Si desea seguir el tutorial, querrá registrarse para obtener su cuenta de desarrollador gratuita aquí. Esta cuenta proporciona una clave API para acceder al servicio web a una velocidad de 10 solicitudes por minuto y hasta un total de 500 solicitudes por día.

    Weather Underground proporciona muchas API de servicios web diferentes para acceder a los datos, pero la única que nos preocupa es su API de historial. La API de historial proporciona un resumen de varias mediciones meteorológicas para una ciudad y un estado en un día específico.

    El formato de la solicitud del recurso de la API de historial es el siguiente:

    http://api.wunderground.com/api/API_KEY/history_YYYYMMDD/q/STATE/CITY.json
    
    • API_KEY: La API_KEY que Weather Underground proporciona con su cuenta
    • YYYYMMDD: Una cadena que representa la fecha objetivo de su solicitud
    • STATE: La abreviatura de estado de dos letras en los Estados Unidos.
    • CITY: El nombre de la ciudad asociada con el estado que solicitó

    Realización de solicitudes a la API

    Para realizar solicitudes a la API de historial de Weather Underground y procesar los datos devueltos, utilizaré algunas bibliotecas estándar, así como algunas bibliotecas de terceros populares. A continuación se muestra una tabla de las bibliotecas que utilizaré y su descripción. Para obtener instrucciones de instalación, consulte la documentación enumerada.

    Biblioteca Descripción de uso Fuente

    datetimeUsado para incrementar nuestras solicitudes por díaBiblioteca estándar
    horaSe usa para retrasar las solicitudes de permanecer por debajo de 10 por minutoBiblioteca estándar
    coleccionesUse tuplas con nombre para la recopilación estructurada de datosBiblioteca estándar
    pandasSe utiliza para procesar, organizar y limpiar los datos.Biblioteca de terceros
    peticionesSe utiliza para realizar solicitudes en red a la API.Biblioteca de terceros
    matplotlibUtilizado para análisis gráficoBiblioteca de terceros

    Comencemos importando estas bibliotecas:

    from datetime import datetime, timedelta
    import time
    from collections import namedtuple
    import pandas as pd
    import requests
    import matplotlib.pyplot as plt
    

    Ahora definiré un par de constantes que representan mi API_KEYy el BASE_URLdel punto final de la API que solicitaré. Tenga en cuenta que deberá registrarse para obtener una cuenta con Weather Underground y recibir la suya propia API_KEY. Para cuando se publique este artículo, habré desactivado este.

    BASE_URLes una cadena con dos marcadores de posición representados por llaves. El primero {}se completará con el API_KEYy el segundo {}se reemplazará por una fecha con formato de cadena. Ambos valores se interpolarán en la BASE_URLcadena usando la función str.format (…) .

    API_KEY = '7052ad35e3c73564'
    BASE_URL = "http://api.wunderground.com/api/{}/history_{}/q/NE/Lincoln.json"
    

    A continuación, inicializaré la fecha objetivo al primer día del año en 2015. Luego, especificaré las características que me gustaría analizar de las respuestas devueltas por la API. Las características son simplemente las claves presentes en la history -> dailysummaryparte de la respuesta JSON. Esas características se usan para definir una namedtuplellamada DailySummaryque usaré para organizar los datos de la solicitud individual en una lista de tuplas DailySummary.

    target_date = datetime(2016, 5, 16)
    features = ["date", "meantempm", "meandewptm", "meanpressurem", "maxhumidity", "minhumidity", "maxtempm",
                "mintempm", "maxdewptm", "mindewptm", "maxpressurem", "minpressurem", "precipm"]
    DailySummary = namedtuple("DailySummary", features)
    

    En esta sección, haré las solicitudes reales a la API y recopilaré las respuestas exitosas utilizando la función definida a continuación. Esta función toma los parámetros url, api_key, target_datey days.

    def extract_weather_data(url, api_key, target_date, days):
        records = []
        for _ in range(days):
            request = BASE_URL.format(API_KEY, target_date.strftime('%Y%m%d'))
            response = requests.get(request)
            if response.status_code == 200:
                data = response.json()['history']['dailysummary'][0]
                records.append(DailySummary(
                    date=target_date,
                    meantempm=data['meantempm'],
                    meandewptm=data['meandewptm'],
                    meanpressurem=data['meanpressurem'],
                    maxhumidity=data['maxhumidity'],
                    minhumidity=data['minhumidity'],
                    maxtempm=data['maxtempm'],
                    mintempm=data['mintempm'],
                    maxdewptm=data['maxdewptm'],
                    mindewptm=data['mindewptm'],
                    maxpressurem=data['maxpressurem'],
                    minpressurem=data['minpressurem'],
                    precipm=data['precipm']))
            time.sleep(6)
            target_date += timedelta(days=1)
        return records
    

    Empiezo por definir una lista llamada registros que contendrá los datos analizados como DailySummary namedtuples. El ciclo for se define de modo que repita el ciclo durante el número de días pasados ​​a la función.

    Luego, la solicitud se formatea usando la str.format()función para interpolar el objeto con API_KEYformato de cadena y target_date. Una vez formateada, la variable de solicitud se pasa al get()método del requestsobjeto y la respuesta se asigna a una variable llamada response.

    Con la respuesta devuelta, quiero asegurarme de que la solicitud fue exitosa evaluando que el código de estado HTTP es igual a 200. Si tiene éxito, analizo el cuerpo de la respuesta en JSON usando el json()método del objeto de respuesta devuelto. Encadenado a la misma json()llamada de método, selecciono los índices del historial y las estructuras de resumen diario, luego tomo el primer elemento de la dailysummarylista y lo asigno a una variable nombrada data.

    Ahora que tengo la estructura de datos similar a un dictado a la que hace referencia la datavariable, puedo seleccionar los campos deseados y crear una nueva instancia de la DailySummary namedtupleque se adjunta a la recordslista.

    Finalmente, cada iteración del ciclo concluye llamando al sleepmétodo del módulo de tiempo para pausar la ejecución del ciclo durante seis segundos, lo que garantiza que no se realicen más de 10 solicitudes por minuto, manteniéndonos dentro de los límites de Weather Underground.

    Luego, target_datese incrementa en 1 día usando el timedeltaobjeto del datetimemódulo para que la siguiente iteración del ciclo recupere el resumen diario del día siguiente.

    El primer lote de solicitudes

    Sin más demora, iniciaré el primer conjunto de solicitudes para la solicitud diaria máxima asignada en la cuenta de desarrollador gratuita de 500. Luego, le sugiero que tome una recarga de su café (u otra bebida preferida) y se ponga al día con su televisor favorito mostrar porque la función tomará al menos una hora dependiendo de la latencia de la red. Con esto, hemos maximizado nuestras solicitudes del día, y esto es solo aproximadamente la mitad de los datos con los que trabajaremos.

    Entonces, regrese mañana donde terminaremos el último lote de solicitudes y luego podremos comenzar a trabajar en el procesamiento y formateo de los datos de una manera adecuada para nuestro proyecto de Machine Learning.

    records = extract_weather_data(BASE_URL, API_KEY, target_date, 500)
    

    Finalización de la recuperación de datos

    Bien, ahora que es un nuevo día, tenemos una pizarra limpia y hasta 500 solicitudes que se pueden realizar a la API de historial de Weather Underground. Nuestro lote de 500 solicitudes emitidas ayer comenzó el 1 de enero de 2015 y finalizó el 15 de mayo de 2016 (asumiendo que no tuvo ninguna solicitud fallida). Una vez más, iniciemos otro lote de 500 solicitudes, pero no me dejes por el día esta vez porque una vez que se recopile este último fragmento de datos, comenzaremos a formatearlo en un Pandas DataFrame y obtendremos características potencialmente útiles.

    # if you closed our terminal or Jupyter Notebook, reinitialize your imports and
    # variables first and remember to set your target_date to datetime(2016, 5, 16)
    records += extract_weather_data(BASE_URL, API_KEY, target_date, 500)
    

    Configurando nuestro Pandas DataFrame

    Ahora que tengo una lista de registros agradable y considerable de DailySummarytuplas con nombre, la usaré para construir un Pandas DataFrame . Pandas DataFrame es una estructura de datos muy útil para muchas tareas de programación que son más conocidas por limpiar y procesar datos que se utilizarán en proyectos (o experimentos) de Machine Learning.

    Utilizaré el Pandas.DataFrame(...)constructor de clases para crear una instancia de un objeto DataFrame. Los parámetros pasados ​​al constructor son registros que representan los datos para el DataFrame, la lista de características que también usé para definir los DailySummary namedtuples que especificarán las columnas del DataFrame. El set_index()método está encadenado a la instanciación de DataFrame para especificar la fecha como índice.

    df = pd.DataFrame(records, columns=features).set_index('date')
    

    Derivando las características

    Los proyectos de Machine Learning, también conocidos como experimentos, a menudo tienen algunas características que son un poco contradictorias. Con esto quiero decir que es muy útil tener conocimiento de la materia en el área bajo investigación para ayudar a seleccionar características significativas para investigar junto con una suposición reflexiva de patrones probables en los datos.

    Sin embargo, también he visto variables y patrones explicativos muy influyentes que surgen de tener presuposiciones casi ingenuas o al menos muy abiertas y mínimas sobre los datos. Tener la intuición basada en el conocimiento para saber dónde buscar características y patrones potencialmente útiles, así como la capacidad de buscar idiosincrasias imprevistas de manera imparcial, es una parte extremadamente importante de un proyecto de análisis exitoso.

    En este sentido, hemos seleccionado bastantes características al analizar los datos resumidos diarios devueltos para su uso en nuestro estudio. Sin embargo, espero que muchos de estos resulten ser poco informativos para predecir las temperaturas climáticas o sean candidatos inapropiados según el tipo de modelo que se utilice, pero el quid es que simplemente no se sabe hasta que se investigan rigurosamente los datos.

    Ahora no puedo decir que tenga un conocimiento significativo de meteorología o modelos de predicción meteorológica, pero hice una búsqueda mínima de trabajos anteriores sobre el uso del Machine Learning para predecir las temperaturas meteorológicas. Resulta que hay bastantes artículos de investigación sobre el tema y en 2016 Holmstrom, Liu y Vo describen el uso de Regresión lineal para hacer precisamente eso. En su artículo, Machine Learning Applied to Weather Forecasting , utilizaron datos meteorológicos de los dos días anteriores para las siguientes mediciones.

    • temperatura máxima
    • temperatura min
    • humedad media
    • presión atmosférica media

    Ampliaré su lista de funciones utilizando las que se enumeran a continuación y, en lugar de utilizar únicamente los dos días anteriores, volveré tres días atrás.

    • temperatura media
    • punto de rocío medio
    • presión media
    • humedad máxima
    • humedad mínima
    • punto de rocío máximo
    • punto de rocío mínimo
    • presión máxima
    • mi presión
    • precipitación

    Entonces, el siguiente paso es encontrar una manera de incluir estas nuevas características como columnas en nuestro DataFrame. Para hacerlo, crearé un subconjunto más pequeño del DataFrame actual para que sea más fácil trabajar con él mientras desarrollo un algoritmo para crear estas características. Haré un tmpDataFrame que consta de solo 10 registros y las características meantempmy meandewptm.

    tmp = df[['meantempm', 'meandewptm']].head(10)
    tmp
    

    fechameantempmmeandewptm2015-01-012015-01-022015-01-032015-01-042015-01-052015-01-062015-01-072015-01-082015-01-092015-01-10

    -6-12
    -6-9
    -4-11
    -14-19
    -9-14
    -10-15
    -16-22
    -7-12
    -11-19
    -6-12

    Analicemos lo que esperamos lograr y luego traduzcamos eso en código. Para cada día (fila) y para una característica determinada (columna), me gustaría encontrar el valor de esa característica N días antes. Para cada valor de N (1-3 en nuestro caso), quiero crear una nueva columna para esa característica que represente la medición del día N del día anterior.

    # 1 day prior
    N = 1
    
    # target measurement of mean temperature
    feature="meantempm"
    
    # total number of rows
    rows = tmp.shape[0]
    
    # a list representing Nth prior measurements of feature
    # notice that the front of the list needs to be padded with N
    # None values to maintain the constistent rows length for each N
    nth_prior_measurements = [None]*N + [tmp[feature][i-N] for i in range(N, rows)]
    
    # make a new column name of feature_N and add to DataFrame
    col_name = "{}_{}".format(feature, N)
    tmp[col_name] = nth_prior_measurements
    tmp
    

    datemeantempmmeandewptmmeantempm_12015-01-012015-01-022015-01-032015-01-042015-01-052015-01-062015-01-072015-01-082015-01-092015-01-10

    -6-12Ninguna
    -6-9-6
    -4-11-6
    -14-19-4
    -9-14-14
    -10-15-9
    -16-22-10
    -7-12-16
    -11-19-7
    -6-12-11

    Bien, parece que tenemos los pasos básicos necesarios para crear nuestras nuevas funciones. Ahora incluiré estos pasos en una función reutilizable y la pondré en funcionamiento para desarrollar todas las funciones deseadas.

    def derive_nth_day_feature(df, feature, N):
        rows = df.shape[0]
        nth_prior_measurements = [None]*N + [df[feature][i-N] for i in range(N, rows)]
        col_name = "{}_{}".format(feature, N)
        df[col_name] = nth_prior_measurements
    

    Ahora escribiré un ciclo para recorrer las características en la lista de características definidas anteriormente, y para cada característica que no sea “fecha” y para N días 1 a 3, llamaremos a nuestra función para agregar las características derivadas que queremos evaluar. para predecir temperaturas.

    for feature in features:
        if feature != 'date':
            for N in range(1, 4):
                derive_nth_day_feature(df, feature, N)
    

    Y por si acaso, echaré un vistazo a las columnas para asegurarme de que se vean como se esperaba.

    df.columns
    
    Index(['meantempm', 'meandewptm', 'meanpressurem', 'maxhumidity',
           'minhumidity', 'maxtempm', 'mintempm', 'maxdewptm', 'mindewptm',
           'maxpressurem', 'minpressurem', 'precipm', 'meantempm_1', 'meantempm_2',
           'meantempm_3', 'meandewptm_1', 'meandewptm_2', 'meandewptm_3',
           'meanpressurem_1', 'meanpressurem_2', 'meanpressurem_3',
           'maxhumidity_1', 'maxhumidity_2', 'maxhumidity_3', 'minhumidity_1',
           'minhumidity_2', 'minhumidity_3', 'maxtempm_1', 'maxtempm_2',
           'maxtempm_3', 'mintempm_1', 'mintempm_2', 'mintempm_3', 'maxdewptm_1',
           'maxdewptm_2', 'maxdewptm_3', 'mindewptm_1', 'mindewptm_2',
           'mindewptm_3', 'maxpressurem_1', 'maxpressurem_2', 'maxpressurem_3',
           'minpressurem_1', 'minpressurem_2', 'minpressurem_3', 'precipm_1',
           'precipm_2', 'precipm_3'],
          dtype="object")
    

    ¡Excelente! Parece que tenemos lo que necesitamos. Lo siguiente que quiero hacer es evaluar la calidad de los datos y limpiarlos cuando sea necesario.

    Limpieza de datos: la parte más importante

    Como dice el título de la sección, la parte más importante de un proyecto de análisis es asegurarse de que está utilizando datos de calidad. El dicho proverbial “entra basura, sale basura” es tan apropiado como siempre cuando se trata de Machine Learning. Sin embargo, la parte de limpieza de datos de un proyecto de análisis no es solo una de las partes más importantes, también es la que consume más tiempo y es la más laboriosa. Para asegurar la calidad de los datos para este proyecto, en esta sección buscaré identificar datos innecesarios, valores faltantes, consistencia de tipos de datos y valores atípicos, luego tomaré algunas decisiones sobre cómo manejarlos si surgen.

    Lo primero que quiero hacer es eliminar las columnas del DataFrame que no me interesan para reducir la cantidad de datos con los que estoy trabajando. El objetivo del proyecto es predecir la temperatura futura basándose en las mediciones meteorológicas de los últimos tres días. Con esto en mente, solo queremos mantener las temperaturas mínima, máxima y media de cada día más todas las nuevas variables derivadas que agregamos en las últimas secciones.

    # make list of original features without meantempm, mintempm, and maxtempm
    to_remove = [feature 
                 for feature in features 
                 if feature not in ['meantempm', 'mintempm', 'maxtempm']]
    
    # make a list of columns to keep
    to_keep = [col for col in df.columns if col not in to_remove]
    
    # select only the columns in to_keep and assign to df
    df = df[to_keep]
    df.columns
    
    Index(['meantempm', 'maxtempm', 'mintempm', 'meantempm_1', 'meantempm_2',
           'meantempm_3', 'meandewptm_1', 'meandewptm_2', 'meandewptm_3',
           'meanpressurem_1', 'meanpressurem_2', 'meanpressurem_3',
           'maxhumidity_1', 'maxhumidity_2', 'maxhumidity_3', 'minhumidity_1',
           'minhumidity_2', 'minhumidity_3', 'maxtempm_1', 'maxtempm_2',
           'maxtempm_3', 'mintempm_1', 'mintempm_2', 'mintempm_3', 'maxdewptm_1',
           'maxdewptm_2', 'maxdewptm_3', 'mindewptm_1', 'mindewptm_2',
           'mindewptm_3', 'maxpressurem_1', 'maxpressurem_2', 'maxpressurem_3',
           'minpressurem_1', 'minpressurem_2', 'minpressurem_3', 'precipm_1',
           'precipm_2', 'precipm_3'],
          dtype="object")
    

    Lo siguiente que quiero hacer es hacer uso de algunas funciones integradas de Pandas para comprender mejor los datos y, potencialmente, identificar algunas áreas en las que concentrar mi energía. La primera función es un método llamado DataFrame info()que, gran sorpresa … proporciona información sobre el DataFrame. Es de interés la columna “tipo de datos” de la salida.

    df.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 1000 entries, 2015-01-01 to 2017-09-27
    Data columns (total 39 columns):
    meantempm          1000 non-null object
    maxtempm           1000 non-null object
    mintempm           1000 non-null object
    meantempm_1        999 non-null object
    meantempm_2        998 non-null object
    meantempm_3        997 non-null object
    meandewptm_1       999 non-null object
    meandewptm_2       998 non-null object
    meandewptm_3       997 non-null object
    meanpressurem_1    999 non-null object
    meanpressurem_2    998 non-null object
    meanpressurem_3    997 non-null object
    maxhumidity_1      999 non-null object
    maxhumidity_2      998 non-null object
    maxhumidity_3      997 non-null object
    minhumidity_1      999 non-null object
    minhumidity_2      998 non-null object
    minhumidity_3      997 non-null object
    maxtempm_1         999 non-null object
    maxtempm_2         998 non-null object
    maxtempm_3         997 non-null object
    mintempm_1         999 non-null object
    mintempm_2         998 non-null object
    mintempm_3         997 non-null object
    maxdewptm_1        999 non-null object
    maxdewptm_2        998 non-null object
    maxdewptm_3        997 non-null object
    mindewptm_1        999 non-null object
    mindewptm_2        998 non-null object
    mindewptm_3        997 non-null object
    maxpressurem_1     999 non-null object
    maxpressurem_2     998 non-null object
    maxpressurem_3     997 non-null object
    minpressurem_1     999 non-null object
    minpressurem_2     998 non-null object
    minpressurem_3     997 non-null object
    precipm_1          999 non-null object
    precipm_2          998 non-null object
    precipm_3          997 non-null object
    dtypes: object(39)
    memory usage: 312.5+ KB
    

    Observe que el tipo de datos de cada columna es de tipo “objeto”. Necesitamos convertir todas estas columnas de características en flotantes para el tipo de análisis numérico que esperamos realizar. Para hacer esto, apply()usaré el método DataFrame para aplicar el to_numericmétodo Pandas a todos los valores del DataFrame. El error="coerce"parámetro llenará cualquier valor textual a NaN . Es común encontrar valores textuales en datos de la naturaleza que generalmente se originan en el recolector de datos donde faltan datos o no son válidos.

    df = df.apply(pd.to_numeric, errors="coerce")
    df.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 1000 entries, 2015-01-01 to 2017-09-27
    Data columns (total 39 columns):
    meantempm          1000 non-null int64
    maxtempm           1000 non-null int64
    mintempm           1000 non-null int64
    meantempm_1        999 non-null float64
    meantempm_2        998 non-null float64
    meantempm_3        997 non-null float64
    meandewptm_1       999 non-null float64
    meandewptm_2       998 non-null float64
    meandewptm_3       997 non-null float64
    meanpressurem_1    999 non-null float64
    meanpressurem_2    998 non-null float64
    meanpressurem_3    997 non-null float64
    maxhumidity_1      999 non-null float64
    maxhumidity_2      998 non-null float64
    maxhumidity_3      997 non-null float64
    minhumidity_1      999 non-null float64
    minhumidity_2      998 non-null float64
    minhumidity_3      997 non-null float64
    maxtempm_1         999 non-null float64
    maxtempm_2         998 non-null float64
    maxtempm_3         997 non-null float64
    mintempm_1         999 non-null float64
    mintempm_2         998 non-null float64
    mintempm_3         997 non-null float64
    maxdewptm_1        999 non-null float64
    maxdewptm_2        998 non-null float64
    maxdewptm_3        997 non-null float64
    mindewptm_1        999 non-null float64
    mindewptm_2        998 non-null float64
    mindewptm_3        997 non-null float64
    maxpressurem_1     999 non-null float64
    maxpressurem_2     998 non-null float64
    maxpressurem_3     997 non-null float64
    minpressurem_1     999 non-null float64
    minpressurem_2     998 non-null float64
    minpressurem_3     997 non-null float64
    precipm_1          889 non-null float64
    precipm_2          889 non-null float64
    precipm_3          888 non-null float64
    dtypes: float64(36), int64(3)
    memory usage: 312.5 KB
    

    Ahora que todos nuestros datos tienen el tipo de datos que deseo, me gustaría echar un vistazo a algunas estadísticas resumidas de las características y usar la regla de oro para verificar la existencia de valores atípicos extremos. El método DataFrame describe()producirá un DataFrame que contiene el recuento, la media, la desviación estándar, el mínimo, el percentil 25, el percentil 50 (o mediana), el percentil 75 y el valor máximo. Esta información puede ser muy útil para evaluar la distribución de los datos de características.

    Me gustaría agregar a esta información calculando otra columna de salida, que indica la existencia de valores atípicos. La regla general para identificar un valor atípico extremo es un valor que sea menor de 3 rangos intercuartílicos por debajo del percentil 25, o 3 rangos intercuartiles por encima del percentil 75. El rango intercuartílico es simplemente la diferencia entre el percentil 75 y el percentil 25.

    # Call describe on df and transpose it due to the large number of columns
    spread = df.describe().T
    
    # precalculate interquartile range for ease of use in next calculation
    IQR = spread['75%'] - spread['25%']
    
    # create an outliers column which is either 3 IQRs below the first quartile or
    # 3 IQRs above the third quartile
    spread['outliers'] = (spread['min']<(spread['25%']-(3*IQR)))|(spread['max'] > (spread['75%']+3*IQR))
    
    # just display the features containing extreme outliers
    spread.ix[spread.outliers,]
    

    recuento
    medio
    estándar
    mínimo
    25%
    50%
    75% valores atípicos
    máx .

    999.088.1071079.27305347.083.090.093.00100.00Cierto
    998.088.1022049.27640747.083.090.093.00100.00Cierto
    997.088.0932809.27677547.083.090.093.00100.00Cierto
    999.01019.9249257.751874993.01015.01019.01024.001055.00Cierto
    998.01019.9228467.755482993.01015.01019.01024.001055.00Cierto
    997.01019.9277837.757805993.01015.01019.01024.001055.00Cierto
    999.01012.3293297.882062956.01008.01012.01017.001035.00Cierto
    998.01012.3266537.885560956.01008.01012.01017.001035.00Cierto
    997.01012.3269817.889511956.01008.01012.01017.001035.00Cierto
    889.02.9082118.8743450.00.00.00.5195.76Cierto
    889.02.9082118.8743450.00.00.00.5195.76Cierto
    888.02.8888858.8606080.00.00.00.5195.76Cierto

    Evaluar el impacto potencial de los valores atípicos es una parte difícil de cualquier proyecto de análisis. Por un lado, debe preocuparse por la posibilidad de introducir artefactos de datos falsos que afectarán o sesgarán significativamente sus modelos. Por otro lado, los valores atípicos pueden ser extremadamente significativos para predecir los resultados que surgen en circunstancias especiales. Analizaremos cada uno de estos valores atípicos que contienen características y veremos si podemos llegar a una conclusión razonable sobre cómo tratarlos.

    El primer conjunto de características parece estar relacionado con la humedad máxima. Al observar los datos, puedo decir que el valor atípico para esta categoría de características se debe al valor mínimo aparentemente muy bajo. De hecho, este parece ser un valor bastante bajo y creo que me gustaría verlo más de cerca, preferiblemente de manera gráfica. Para hacer esto, usaré un histograma.

    %matplotlib inline
    plt.rcParams['figure.figsize'] = [14, 8]
    df.maxhumidity_1.hist()
    plt.title('Distribution of maxhumidity_1')
    plt.xlabel('maxhumidity_1')
    plt.show()
    

    Mirar el histograma de los valores de maxhumiditylos datos muestra una cierta desviación negativa. Querré tener esto en cuenta al seleccionar modelos de predicción y evaluar la fuerza del impacto de las humedades máximas. Muchos de los métodos estadísticos subyacentes asumen que los datos se distribuyen normalmente. Por ahora creo que los dejaré en paz, pero será bueno tener esto en cuenta y tener cierto escepticismo al respecto.

    A continuación, analizaré la distribución de la característica de presión mínima.

    df.minpressurem_1.hist()
    plt.title('Distribution of minpressurem_1')
    plt.xlabel('minpressurem_1')
    plt.show()
    

    Esta trama presenta otra característica interesante. A partir de esta gráfica, los datos son multimodales , lo que me lleva a creer que hay dos conjuntos muy diferentes de circunstancias ambientales aparentes en estos datos. Dudo en eliminar estos valores, ya que sé que los cambios de temperatura en esta zona del país pueden ser bastante extremos, especialmente entre las estaciones del año. Me preocupa que eliminar estos valores bajos pueda tener alguna utilidad explicativa, pero, una vez más, seré escéptico al respecto al mismo tiempo.

    La categoría final de características que contienen valores atípicos, la precipitación, es un poco más fácil de entender. Dado que los días secos (es decir, sin precipitaciones) son mucho más frecuentes, es sensato ver valores atípicos aquí. Para mí, esta no es una razón para eliminar estas funciones.

    El último problema de calidad de los datos que se debe abordar es el de los valores perdidos. Debido a la forma en que he construido el DataFrame, los valores faltantes están representados por NaN. Probablemente recordará que he introducido intencionalmente valores faltantes para los primeros tres días de los datos recopilados al derivar características que representan los tres días anteriores de mediciones. No es hasta el tercer día en que podemos comenzar a derivar esas características, por lo que claramente querré excluir esos primeros tres días del conjunto de datos.

    Mire nuevamente el resultado de la última vez que emití el  info método. Hay una columna de salida que enumera los valores no nulos para cada columna de características. Al observar esta información, puede ver que, en su mayor parte, las características contienen relativamente pocos valores faltantes (nulos / NaN), en su mayoría solo los que presenté. Sin embargo, parece que a las columnas de precipitación les falta una parte significativa de sus datos.

    La falta de datos plantea un problema porque la mayoría de los métodos de Machine Learning requieren conjuntos de datos completos sin ningún dato faltante. Aparte del problema de que muchos de los métodos de Machine Learning requieren datos completos, si eliminara todas las filas solo porque la función de precipitación contiene datos faltantes, estaría descartando muchas otras mediciones de funciones útiles.

    Como lo veo, tengo un par de opciones para lidiar con este problema de datos faltantes:

    • Simplemente puedo eliminar las filas que contienen los valores faltantes, pero como mencioné anteriormente, eliminar esa cantidad de datos elimina una gran cantidad de valor de los datos
    • Puedo completar los valores faltantes con un valor interpolado que es una estimación razonable de los valores reales.

    Como prefiero preservar la mayor cantidad de datos posible, donde hay un riesgo mínimo de introducir valores erróneos, voy a completar los valores de precipitación faltantes con el valor más común de cero. Creo que esta es una decisión razonable porque la gran mayoría de los valores en las mediciones de precipitación son cero.

    # iterate over the precip columns
    for precip_col in ['precipm_1', 'precipm_2', 'precipm_3']:
        # create a boolean array of values representing nans
        missing_vals = pd.isnull(df[precip_col])
        df[precip_col][missing_vals] = 0
    

    Ahora que he completado todos los valores faltantes que puedo, aunque tengo cuidado de no afectar negativamente la calidad, me sentiría cómodo simplemente eliminando los registros restantes que contienen valores faltantes del conjunto de datos. Es bastante fácil eliminar filas del DataFrame que contienen NaN. Todo lo que tengo que hacer es llamar al método dropna()y Pandas hará todo el trabajo por mí.

    df = df.dropna()
    

    Recursos

    ¿Quiere aprender las herramientas, el Machine Learning y el análisis de datos que se utilizan en este tutorial? Aquí hay algunos recursos excelentes para comenzar:

    Conclusión

    En este artículo he descrito el proceso de recopilación, limpieza y procesamiento de un conjunto de datos de tamaño razonable que se utilizará para los próximos artículos sobre un proyecto de Machine Learning en el que predecimos las temperaturas meteorológicas futuras.

    Si bien este será probablemente el más seco de los artículos que detienen este proyecto de Machine Learning, he intentado enfatizar la importancia de recopilar datos de calidad adecuados para un valioso experimento de Machine Learning.

    Gracias por leer y espero que esperen con interés los próximos artículos sobre este proyecto.

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *