Обзор
Эта глава является первой в серии руководств по прогнозированию погоды с помощью машинного обучения с использованием 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()
Суммировать
Эта статья в основном знакомит с процессом сбора, обработки и очистки данных.Обработка, выполненная в этой статье, будет использоваться для обучения модели в следующей статье. Для вас эта статья может быть очень скучной и не очень сухой, но хорошие выборочные данные могут обучить хорошую модель, поэтому способность сбора и обработки выборочных данных напрямую влияет на эффект вашего последующего машинного обучения.