Предсказание погоды с помощью машинного обучения (часть 1)

машинное обучение

Обзор

   Эта глава является первой в серии руководств по прогнозированию погоды с помощью машинного обучения с использованием Python и машинного обучения для построения модели для прогнозирования температуры погоды на основе данных, собранных Weather Underground. Учебное пособие будет состоять из трех отдельных частей, охватывающих такие темы, как:

  • Сбор и обработка данных (эта статья)
  • Модели линейной регрессии (глава 2)
  • Модели нейронных сетей (глава 3)

   Данные, используемые в этом руководстве, будут получены из бесплатного API-сервиса Weather Underground. Я буду использовать библиотеку запросов Python для вызова API и получения данных о погоде из Линкольна, штат Небраска, за 2015 год. После сбора данные должны быть обработаны и объединены в подходящий формат, а затем очищены.   Вторая статья будет посвящена анализу тенденций в данных с целью выбора подходящих функций и построения модели линейной регрессии с использованием статистических моделей Python и библиотек scikit-learn. Я расскажу о построении модели линейной регрессии, необходимых предположениях, которые необходимо сделать, и покажу, как оценивать характеристики данных для построения надежной модели. И, наконец, завершить тестирование и проверку модели.   Последняя статья будет посвящена использованию нейронных сетей. Я сравню процесс, результаты, точность построения нейросетевой модели и построения модели линейной регрессии.

Описание Weather Underground

  Weather Underground — компания, которая собирает и распространяет различные данные измерений погоды по всему миру. Компания предоставляет большое количество API для коммерческого и некоммерческого использования. В этой статье я опишу, как получать ежедневные данные о погоде с помощью некоммерческого API. Итак, если вы следуете этому руководству, вам необходимо зарегистрировать их бесплатную учетную запись разработчика. Эта учетная запись предоставляет ключ API, этот ключ ограничен 10 в минуту и ​​500 запросами API в день.   API для получения исторических данных выглядит следующим образом:

http://api.wunderground.com/api/API_KEY/history_YYYYMMDD/q/STATE/CITY.json
  • API_KEY: получить зарегистрированную учетную запись
  • ГГГГММДД: дата, когда вы хотите получить данные о погоде.
  • ГОСУДАРСТВО: Аббревиатура штата
  • ГОРОД: Название города, который вы запрашивали

вызов API

   В этом руководстве используются следующие библиотеки Python при вызове API Weather Underground для получения исторических данных.

название

описывать

источник

datetime

Дата обработки

стандартная библиотека

time

время обработки

стандартная библиотека

collections

Используйте именованные кортежи библиотеки для структурирования данных

стандартная библиотека

pandas

Обработка данных

третья сторона

requests

Библиотека обработки HTTP-запросов

третья сторона

matplotlib

Библиотека чертежей

третья сторона

   Хорошо, давайте сначала импортируем эти библиотеки:

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

Далее определяем константы для сохранения API_KEY и BASE_URL.Обратите внимание, что API_KEY в примере недоступен, вам нужно зарегистрироваться и получить его самостоятельно. код показывает, как показано ниже:

API_KEY = '7052ad35e3c73564'  
# 第一个大括号是API_KEY,第二个是日期
BASE_URL = "http://api.wunderground.com/api/{}/history_{}/q/NE/Lincoln.json"

Затем мы инициализируем переменную, сохраняем дату и определяем список, указывающий данные, которые нужно извлечь из содержимого, возвращаемого API. Затем определите переменную DailySummary типа namedtuple для хранения возвращаемых данных. код показывает, как показано ниже:

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

Определите функцию, вызовите API и получите данные о днях, начиная с указанной target_date.Код выглядит следующим образом:

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

Во-первых, определите запись списка для хранения указанного выше DailySummary и используйте цикл for для обхода всех указанных дат. Затем создайте URL-адрес, инициируйте HTTP-запрос, получите возвращенные данные, используйте возвращенные данные, инициализируйте DailySummary и, наконец, сохраните его в записях. С помощью вывода этой функции можно получить и вернуть исторические данные о погоде за N дней, начиная с указанной даты.

Получите данные о погоде за 500 дней

  Из-за ограничений интерфейса API нам требуется два дня, чтобы получить данные за 500 дней. Вы также можете загрузить мои тестовые данные, чтобы сэкономить время.

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

Форматировать данные в формат Pandas DataFrame

   Мы используем список DailySummary для инициализации Pandas DataFrame. Тип данных DataFrame — это структура данных, часто используемая в области машинного обучения.

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

Извлечение признаков

  Машинное обучение является экспериментальным, поэтому вы можете столкнуться с противоречивыми данными или поведением. Поэтому вам необходимо иметь определенное представление о проблемной области, с которой вы имеете дело, прежде чем использовать машинное обучение для решения проблемы, чтобы вы могли лучше извлекать функции данных.   Я возьму следующие поля данных и использую данные за последние три дня в качестве прогноза.

  • mean temperature
  • mean dewpoint
  • mean pressure
  • max humidity
  • min humidity
  • max dewpoint
  • min dewpoint
  • max pressure
  • min pressure
  • precipitation

Во-первых, мне нужно добавить некоторые поля в DataFrame, чтобы сохранить новые поля данных.Для удобства тестирования я создал переменную tmp для хранения 10 данных, и эти данные имеют атрибуты meantempm и meandewptm. код показывает, как показано ниже:

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

Для каждой строки данных мы получаем данные, соответствующие предыдущему дню, предыдущим двум дням и предыдущим трем дням соответственно.Есть эта строка, которая названа в честь атрибута _index.Код выглядит следующим образом:

# 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

Теперь мы инкапсулируем описанный выше процесс обработки в функцию, которую удобно вызывать.

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

Что ж, теперь мы берем данные за последние три дня по всем признакам и помещаем их в этот банк.

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

После обработки все наши характеристики данных теперь такие:

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')

Очистка данных

  Очистка данных — самый важный шаг в процессе машинного обучения, и он очень трудоемкий и трудоемкий. В этом руководстве мы удалим ненужные образцы, образцы с неполными данными, проверим согласованность данных и т. д.   Сначала удалите данные, которые мне не интересны, чтобы уменьшить выборку. Наша цель — предсказать температуру погоды на основе данных о погоде за последние три дня, поэтому мы сохраняем только данные трех полей: минимум, максимум и среднее значение.

# 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')

Чтобы лучше наблюдать за данными, мы используем некоторые встроенные функции Pandas для просмотра информации о данных.Во-первых, мы используем функцию info (), которая будет выводить информацию о данных, хранящуюся в DataFrame.

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

Примечание. Тип данных каждой строки — объект, нам нужно преобразовать данные в число с плавающей запятой.

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

Теперь я получаю нужные мне данные. Затем мы вызываем функцию description(), которая возвращает DataFrame, который содержит общую информацию, среднее значение, стандартное отклонение, минимум, 25%, 50%, 75% и максимальную информацию о данных.

   Затем используйте метод квартилей, чтобы удалить очень маленькие данные из 25% данных и очень большие данные из 75% данных.

# 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,]

   Оценка потенциального воздействия выбросов является сложной частью любого аналитического проекта. С одной стороны, вы должны быть обеспокоены возможностью введения поддельных выборок данных, которые серьезно повлияют на вашу модель. С другой стороны, выбросы очень важны для прогнозирования результатов, возникающих в особых случаях. Мы обсудим каждый из выбросов, содержащих функции, и посмотрим, сможем ли мы сделать разумные выводы о том, как с ними бороться.

   Первый набор характеристик связан с максимальной влажностью. Глядя на данные, я вижу, что выбросы для этого класса объектов являются очень низкими минимумами. Эти данные выглядят бесполезными, и я думаю, что хотел бы рассмотреть их поближе, желательно в графическом виде. Для этого я бы использовал гистограмму.

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

Глядя на гистограмму поля максимальной влажности, можно увидеть, что данные демонстрируют небольшой отрицательный сдвиг. Я учитываю это при выборе прогностической модели и оценке силы эффекта максимальной влажности. Многие основные статистические методы предполагают, что данные распределены нормально. Давайте пока проигнорируем это, но будем помнить об этом аномальном свойстве.

   Далее смотрим на гистограмму другого поля

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

   Последняя проблема с качеством данных, которую необходимо решить, — это отсутствующие значения. Поскольку, когда я создаю DataFrame, недостающие значения представлены NaN. Как вы, возможно, помните, я намеренно ввел пропущенные значения для первых трех дней сбора данных, выведя функции, которые представляют измерения за предыдущие три дня. Мы не можем начать получать эти функции до третьего дня, поэтому, очевидно, я хотел бы исключить эти первые три дня из набора данных. Оглядываясь назад на информацию, выводимую вышеприведенной функцией info(), вы можете видеть, что существует очень мало функций данных, содержащих значения NaN, за исключением нескольких полей, которые я упомянул, в основном их нет. Поскольку для машинного обучения требуется целостность данных выборочного поля, потому что, если мы удалим выборку из-за того, что поле осадков пусто, это приведет к недоступности большого количества выборок В этом случае мы можем указать пустое поле осадков. образец заполняется значением. Исходя из опыта и чтобы минимизировать влияние заполненных значений на модель, я решил заполнить пустое поле осадков значением 0.

# 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

После заполнения значения мы можем удалить образцы, значение поля которых пусто, просто вызовите функцию dropna().

df = df.dropna()

Суммировать

  Эта статья в основном знакомит с процессом сбора, обработки и очистки данных.Обработка, выполненная в этой статье, будет использоваться для обучения модели в следующей статье.   Для вас эта статья может быть очень скучной и не очень сухой, но хорошие выборочные данные могут обучить хорошую модель, поэтому способность сбора и обработки выборочных данных напрямую влияет на эффект вашего последующего машинного обучения.

Английский оригинал

Зайди в мой блог,ловец змей говорит