Uso del aprendizaje automático para predecir el clima: Parte 1

Este es el primer artículo de una serie de varias partes sobre el uso de Python y Machine Learning para crear modelos para predecir las temperaturas climáticas en función de los datos recopilados...

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 aprendizaje automático para crear modelos para predecir las temperaturas climáticas en función de los datos recopilados de Weather Underground. La serie constará de tres artículos diferentes que describen los principales aspectos de un proyecto de Machine Learning. Los temas a tratar son:

  1. Recopilación y procesamiento de datos (este artículo)
  2. Linear regression models (Artículo 2)
  3. Neural network models (artículo 3)

Los datos utilizados en esta serie se recopilarán del servicio web API de nivel gratuito de Weather Underground. Usaré la biblioteca de solicitudes para interactuar con la API para 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 de los datos con el fin de seleccionar características apropiadas para construir un modelo de Regresión lineal utilizando los [modelos estadisticos](http ://www.statsmodels.org/stable/index.html) y scikit-aprender bibliotecas de Python. Discutiré la importancia de comprender las suposiciones necesarias para usar un modelo de regresión lineal y demostraré cómo evaluar las características para construir un modelo sólido. Este artículo concluirá con una discusión sobre la prueba y validación del modelo de regresión lineal.

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

Familiarizarse con Weather Underground

Clima subterráneo es una empresa que recopila y distribuye datos sobre diversas mediciones meteorológicas en todo el mundo. La empresa proporciona una gran cantidad de API que están disponibles para usos comerciales y no comerciales. En este artículo, describiré cómo extraer datos meteorológicos diarios mediante programación de Weather Underground utilizando su nivel gratuito de servicio 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 razón de 10 solicitudes por minuto y hasta un total de 500 solicitudes en un día.

Weather Underground proporciona muchas API de servicios web diferentes para acceder a los datos, pero la que nos preocupará 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 API de historial es el siguiente:

1
http://api.wunderground.com/api/API_KEY/history_YYYYMMDD/q/STATE/CITY.json
  • API_KEY: La API_KEY que proporciona Weather Underground con su cuenta
  • YYYYMMDD: una cadena que representa la fecha objetivo de su solicitud
  • ESTADO: La abreviatura de estado de dos letras en los Estados Unidos
  • CIUDAD: 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 populares de terceros. A continuación se muestra una tabla de las bibliotecas que usaré y su descripción. Para obtener instrucciones de instalación, consulte la documentación indicada.

Biblioteca Descripción de la fuente de uso


fecha y hora Se utiliza para incrementar nuestras solicitudes por día Biblioteca estándar tiempo Se usa para retrasar las solicitudes para permanecer por debajo de 10 por minuto Biblioteca estándar colecciones Utilice namedtuples para la recopilación estructurada de datos Biblioteca estándar pandas Se utiliza para procesar, organizar y limpiar los datos Biblioteca de terceros peticiones Se utiliza para realizar solicitudes en red a la biblioteca de terceros de API matplotlib Utilizado para el análisis gráfico Biblioteca de terceros

Comencemos importando estas bibliotecas:

1
2
3
4
5
6
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_KEY y la BASE_URL del extremo de la API que estaré solicitando. Tenga en cuenta que deberá registrarse para obtener una cuenta con Weather Underground y recibir su propia API_KEY. Para cuando se publique este artículo, habré desactivado este.

BASE_URL es una cadena con dos marcadores de posición representados por corchetes. El primer {} se llenará con API_KEY y el segundo {} se reemplazará por una fecha con formato de cadena. Ambos valores se interpolarán en la cadena BASE_URL utilizando la función formato de cadena (...).

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

A continuación, inicializaré la fecha objetivo para el primer día del año en 2015. Luego, especificaré las funciones que me gustaría analizar de las respuestas devueltas por la API. Las funciones son simplemente las claves presentes en la parte historia -> resumen diario de la respuesta JSON. Esas características se usan para definir una tupla con nombre llamada DailySummary que usaré para organizar los datos de la solicitud individual en una lista de tuplas DailySummary.

1
2
3
4
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, realizaré 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_date y days.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

Comienzo definiendo una lista llamada registros que contendrá los datos analizados como DailySummary namedtuples. El bucle for se define de modo que itera sobre el bucle durante el número de días pasados ​​a la función.

Luego, la solicitud se formatea utilizando la función str.format() para interpolar el objeto API_KEY y el objeto target_date con formato de cadena. Una vez formateada, la variable de solicitud se pasa al método get() del objeto requests 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 es exitoso, analizo el cuerpo de la respuesta en JSON usando el método json() del objeto de respuesta devuelto. Encadenado a la misma llamada al método json(), selecciono los índices del historial y las estructuras de resumen diario, luego tomo el primer elemento en la lista dailysummary y lo asigno a una variable llamada data.

Ahora que tengo la estructura de datos tipo dict a la que hace referencia la variable data, puedo seleccionar los campos deseados e instanciar una nueva instancia de DailySummary namedtuple que se adjunta a la lista de registros.

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

Luego, target_date se incrementa en 1 día usando el objeto timedelta del módulo datetime para que la próxima iteración del ciclo recupere el resumen diario para el 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 TV favorita. mostrar porque la función tardará 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 aprendizaje automático.

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

Terminando la recuperación de datos

Ok, 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 esta vez no me dejen para el día porque una vez que se recopile este último fragmento de datos, comenzaremos a formatearlo en un Pandas DataFrame y derivar funciones potencialmente útiles. .

1
2
3
# 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)

Configuración de nuestro Pandas DataFrame

Ahora que tengo una buena y considerable lista de registros de tuplas con nombre DailySummary, la usaré para construir un [Marco de datos de pandas](https://pandas.pydata.org/pandas-docs/stable/dsintro. html#marco de datos). 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 para usar en proyectos (o experimentos) de aprendizaje automático.

Utilizaré el constructor de la clase Pandas.DataFrame(...) para instanciar 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 el DailySummary namedtuples que especificará las columnas del DataFrame. El método set_index() está encadenado a la creación de instancias de DataFrame para especificar la fecha como índice.

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

Derivación de las funciones {#derivación de las funciones}

Los proyectos de aprendizaje automático, también conocidos como experimentos, a menudo tienen algunas características que son un poco contradictorias. Con esto quiero decir que es bastante ú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 que surgen patrones y variables explicativas muy influyentes a partir 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 analítico exitoso.

En este sentido, hemos seleccionado bastantes características mientras analizamos los datos de resumen diario devueltos para usar en nuestro estudio. Sin embargo, espero que muchos de estos demuestren ser poco informativos para predecir las temperaturas del clima o 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 del clima, pero hice una búsqueda mínima de trabajos previos sobre el uso de Machine Learning para predecir las temperaturas del clima. Resulta que hay bastantes artículos de investigación sobre el tema y en 2016 Holmstrom, Liu y Vo describen el uso de la regresión lineal para hacer precisamente eso. En su artículo, Aprendizaje automático aplicado a la previsión meteorológica, utilizaron datos meteorológicos de los dos días anteriores para las siguientes mediciones.

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

Ampliaré su lista de características usando las que se enumeran a continuación, y en lugar de usar solo los dos días anteriores, regresaré tres días.

  • 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
  • presión mínima
  • 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 funciones. Crearé un DataFrame tmp que constará de solo 10 registros y las funciones meantempm y meandewptm.

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

fecha meantempm meandewptm


2015-01-01 -6 -12 2015-01-02 -6 -9 2015-01-03 -4 -11 2015-01-04 -14 -19 2015-01-05 -9 -14 2015-01-06 -10 -15 2015-01-07 -16 -22 2015-01-08 -7 -12 2015-01-09 -11 -19 2015-01-10 -6 -12

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 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

fecha meantempm meandewptm meantempm_1


2015-01-01 -6 -12 Ninguno 2015-01-02 -6 -9 -6 2015-01-03 -4 -11 -6 2015-01-04 -14 -19 -4 2015-01-05 -9 -14 -14 2015-01-06 -10 -15 -9 2015-01-07 -16 -22 -10 2015-01-08 -7 -12 -16 2015-01-09 -11 -19 -7 2015-01-10 -6 -12 -11

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

1
2
3
4
5
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 funciones en la lista de funciones definida anteriormente, y para cada función que no sea "fecha" y para N días 1 a 3 llamaremos a nuestra función para agregar las funciones derivadas que desea evaluar para predecir las temperaturas.

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

Y, en buena medida, echaré un vistazo a las columnas para asegurarme de que se vean como se esperaba.

1
df.columns
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 {#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, "basura que entra, basura que sale", es tan apropiado como siempre cuando se trata de aprendizaje automático. Sin embargo, la parte de limpieza de datos de un proyecto de análisis no es solo una de las partes más importantes, sino que también es la que consume más tiempo y es la más laboriosa. Para garantizar la calidad de los datos para este proyecto, en esta sección buscaré identificar datos innecesarios, valores faltantes, consistencia de los tipos de datos y valores atípicos, y 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 en función de 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 obtener una mejor comprensión de los datos y potencialmente identificar algunas áreas en las que concentrar mi energía. La primera función es un método de DataFrame llamado info() que, gran sorpresa... proporciona información sobre el DataFrame. De interés es la columna "tipo de datos" de la salida.

1
df.info()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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, usaré el método apply() DataFrame para aplicar el método to_numeric de Pandas a todos los valores del DataFrame. El parámetro error='coerce' llenará cualquier valor textual a NaN. Es común encontrar valores textuales en datos de la naturaleza que generalmente se originan en el recopilador de datos donde faltan datos o no son válidos.

1
2
df = df.apply(pd.to_numeric, errors='coerce')
df.info()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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 quiero, me gustaría echar un vistazo a algunas estadísticas resumidas de las funciones y usar la regla general estadística para verificar la existencia de valores atípicos extremos. El método de DataFrame describe() producirá un DataFrame que contiene el conteo, 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 puede ser información 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, indicando la existencia de valores atípicos. La regla general para identificar un valor atípico extremo es un valor inferior a 3 rangos intercuartílicos por debajo del percentil 25, o 3 rangos intercuartílicos por encima del percentil 75. El rango intercuartílico es simplemente la diferencia entre el percentil 75 y el percentil 25.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 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,]
               count   mean          std        min     25%      50%      75%       max       outliers

maxhumidity_1 999.0 88.107107 9.273053 47.0 83.0 90.0 93.00 100.00 Verdadero maxhumidity_2 998.0 88.102204 9.276407 47.0 83.0 90.0 93.00 100.00 Verdadero maxhumidity_3 997.0 88.093280 9.276775 47.0 83.0 90.0 93.00 100.00 Verdadero maxpressionm_1 999.0 1019.924925 7.751874 993.0 1015.0 1019.0 1024.00 1055.00 Verdadero maxpressionm_2 998.0 1019.922846 7.755482 993.0 1015.0 1019.0 1024.00 1055.00 Verdadero maxpressionm_3 997.0 1019.927783 7.757805 993.0 1015.0 1019.0 1024.00 1055.00 Verdadero minpresionm_1 999.0 1012.329329 7.882062 956.0 1008.0 1012.0 1017.00 1035.00 Verdadero minpresionm_2 998.0 1012.326653 7.885560 956.0 1008.0 1012.0 1017.00 1035.00 Verdadero minpresionm_3 997.0 1012.326981 7.889511 956.0 1008.0 1012.0 1017.00 1035.00 Verdadero precipm_1 889,0 2,908211 8,874345 0,0 0,0 0,0 0,51 95,76 Verdadero precipm_2 889,0 2,908211 8,874345 0,0 0,0 0,0 0,51 95,76 Verdadero precipm_3 888.0 2.888885 8.860608 0.0 0.0 0.0 0.51 95.76 Verdadero

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 significativamente o sesgarán sus modelos. Por otro lado, los valores atípicos pueden ser extremadamente significativos para predecir los resultados que surgen en circunstancias especiales. Discutiremos 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. Mirando 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, parece ser un valor bastante bajo y creo que me gustaría echarle un vistazo más de cerca, preferiblemente de forma gráfica. Para hacer esto usaré un histograma.

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

Max humidity histogram{.img-responsive}

Al observar el histograma de los valores de maxhumidity, los datos muestran un cierto sesgo negativo. 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 suponen que los datos se distribuyen normalmente. Por ahora creo que los dejaré solos, pero será bueno tener esto en cuenta y tener cierto escepticismo al respecto.

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

1
2
3
4
df.minpressurem_1.hist()
plt.title('Distribution of minpressurem_1')
plt.xlabel('minpressurem_1')
plt.show()

Min pressure histogram{.img-responsive}

Esta parcela exhibe otra característica interesante. 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 área del país pueden ser bastante extremos, especialmente entre las estaciones del año. Me preocupa que la eliminación de 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í, esto no es motivo para eliminar estas funciones.

El último problema de calidad de datos que se debe abordar es el de los valores faltantes. Debido a la forma en que construí el DataFrame, los valores faltantes están representados por NaN. Probablemente recordará que introduje 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 que podemos comenzar a derivar esas características, por lo que claramente querré excluir esos primeros tres días del conjunto de datos.

Vuelva a mirar el resultado de la última vez que emití el método info . 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 introduje. Sin embargo, parece que a las columnas de precipitación les falta una parte significativa de sus datos.

Los datos faltantes plantean un problema porque la mayoría de los métodos de aprendizaje automático requieren conjuntos de datos completos sin datos faltantes. Además del problema de que muchos de los métodos de aprendizaje automático requieren datos completos, si tuviera que eliminar todas las filas solo porque la función de precipitación contiene datos faltantes, estaría descartando muchas otras medidas de funciones útiles.

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

  1. Simplemente puedo eliminar las filas que contienen los valores faltantes, pero como mencioné anteriormente, descartar esa cantidad de datos elimina mucho valor de los datos.
  2. Puedo completar los valores que faltan con un valor interpolado que es una estimación razonable de los valores verdaderos.

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

1
2
3
4
5
# 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 llenado todos los valores que faltan que puedo, teniendo 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 contiene NaN. Todo lo que tengo que hacer es llamar al método dropna() y Pandas hará todo el trabajo por mí.

1
df = df.dropna()

Recursos

¿Quiere conocer las herramientas, el aprendizaje automático y el análisis de datos utilizados en este tutorial? Aquí hay algunos recursos excelentes para comenzar:

Conclusión

En este artículo, describí el proceso de recopilación, limpieza y procesamiento de un conjunto de datos de tamaño razonablemente bueno para usar en los próximos artículos sobre un proyecto de aprendizaje automático en el que predecimos las temperaturas climáticas futuras.

Si bien este probablemente será el más seco de los artículos que detengan este proyecto de aprendizaje automático, he tratado de enfatizar la importancia de recopilar datos de calidad adecuados para un valioso experimento de aprendizaje automático.

Gracias por leer y espero que estés pendiente de los próximos artículos sobre este proyecto.

¿Buscas las partes 2 y 3 de esta serie? Aqui tienes: