Parte 1: Recopilación de datos de Weather Underground
Contenido
- 1 Parte 1: Recopilación de datos de Weather Underground
- 2 Familiarizarse con Weather Underground
- 3 Realización de solicitudes a la API
- 4 El primer lote de solicitudes
- 5 Finalización de la recuperación de datos
- 6 Configurando nuestro Pandas DataFrame
- 7 Derivando las características
- 8 Limpieza de datos: la parte más importante
- 9 Recursos
- 10 Conclusión
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 cuentaYYYYMMDD
: Una cadena que representa la fecha objetivo de su solicitudSTATE
: 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
Te puede interesar:Usar Machine Learning para predecir el tiempo: Parte 2datetime | Usado para incrementar nuestras solicitudes por día | Biblioteca estándar |
hora | Se usa para retrasar las solicitudes de permanecer por debajo de 10 por minuto | Biblioteca estándar |
colecciones | Use tuplas con nombre 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 API. | Biblioteca de terceros |
matplotlib | Utilizado para análisis gráfico | Biblioteca 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_KEY
y el BASE_URL
del 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_URL
es una cadena con dos marcadores de posición representados por llaves. El primero {}
se completará con el API_KEY
y el segundo {}
se reemplazará por una fecha con formato de cadena. Ambos valores se interpolarán en la BASE_URL
cadena 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 -> dailysummary
parte de la respuesta JSON. Esas características se usan para definir una namedtuple
llamada DailySummary
que 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_date
y 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
namedtuple
s. 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_KEY
formato de cadena y target_date
. Una vez formateada, la variable de solicitud se pasa al get()
método del requests
objeto 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 dailysummary
lista 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 data
variable, puedo seleccionar los campos deseados y crear una nueva instancia de la DailySummary
namedtuple
que se adjunta a la records
lista.
Finalmente, cada iteración del ciclo concluye llamando al sleep
mé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_date
se incrementa en 1 día usando el timedelta
objeto del datetime
mó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 DailySummary
tuplas 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
namedtuple
s 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.
Te puede interesar:Tutorial de la biblioteca Python zlib- 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 tmp
DataFrame que consta de solo 10 registros y las características meantempm
y 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 | -12 | Ninguna |
-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.
Te puede interesar:Importaciones circulares de PythonLo 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_numeric
mé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.0 | 88.107107 | 9.273053 | 47.0 | 83.0 | 90.0 | 93.00 | 100.00 | Cierto |
998.0 | 88.102204 | 9.276407 | 47.0 | 83.0 | 90.0 | 93.00 | 100.00 | Cierto |
997.0 | 88.093280 | 9.276775 | 47.0 | 83.0 | 90.0 | 93.00 | 100.00 | Cierto |
999.0 | 1019.924925 | 7.751874 | 993.0 | 1015.0 | 1019.0 | 1024.00 | 1055.00 | Cierto |
998.0 | 1019.922846 | 7.755482 | 993.0 | 1015.0 | 1019.0 | 1024.00 | 1055.00 | Cierto |
997.0 | 1019.927783 | 7.757805 | 993.0 | 1015.0 | 1019.0 | 1024.00 | 1055.00 | Cierto |
999.0 | 1012.329329 | 7.882062 | 956.0 | 1008.0 | 1012.0 | 1017.00 | 1035.00 | Cierto |
998.0 | 1012.326653 | 7.885560 | 956.0 | 1008.0 | 1012.0 | 1017.00 | 1035.00 | Cierto |
997.0 | 1012.326981 | 7.889511 | 956.0 | 1008.0 | 1012.0 | 1017.00 | 1035.00 | Cierto |
889.0 | 2.908211 | 8.874345 | 0.0 | 0.0 | 0.0 | 0.51 | 95.76 | Cierto |
889.0 | 2.908211 | 8.874345 | 0.0 | 0.0 | 0.0 | 0.51 | 95.76 | Cierto |
888.0 | 2.888885 | 8.860608 | 0.0 | 0.0 | 0.0 | 0.51 | 95.76 | Cierto |
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 maxhumidity
los 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.
Te puede interesar:Explicación de @classmethod y @staticmethod de Pythondf.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:
- Análisis de datos con Pandas y Python
- Requisitos previos de aprendizaje profundo: regresión lineal en Python
- Bootcamp de Python para ciencia de datos y Machine Learning
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.
Te puede interesar:Relaciones recursivas de modelos en DjangoSi 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.