Рубрики
Без рубрики

Использование машинного обучения для прогнозирования погоды: Часть 1

Автор оригинала: Adam McQuistan.

Использование машинного обучения для прогнозирования погоды: Часть 1

Часть 1: Сбор Данных О Погоде Под Землей

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

  1. Сбор и обработка данных (данная статья)
  2. Линейные регрессионные модели ( статья 2 )
  3. Нейросетевые модели ( статья 3 )

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

Вторая статья будет посвящена анализу тенденций в данных с целью выбора подходящих признаков для построения модели Линейной регрессии с использованием библиотек statsmodels и scikit-learn Python. Я расскажу о важности понимания допущений, необходимых для использования модели линейной регрессии, и продемонстрирую, как оценить характеристики для построения надежной модели. Эта статья завершится обсуждением тестирования и валидации модели линейной регрессии.

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

Знакомство с погодой под землей

Weather Underground – это компания, которая собирает и распространяет данные о различных погодных измерениях по всему миру. Компания предоставляет широкий спектр API, доступных как для коммерческого, так и для некоммерческого использования. В этой статье я опишу, как программно извлекать ежедневные данные о погоде из Weather Underground, используя их бесплатный уровень сервиса, доступный для некоммерческих целей.

Если вы хотите следовать вместе с учебником, вы захотите зарегистрироваться на их бесплатную учетную запись разработчика здесь. Эта учетная запись предоставляет API-ключ для доступа к веб-сервису со скоростью 10 запросов в минуту и в общей сложности до 500 запросов в день.

Weather Underground предоставляет множество различных API веб-сервисов для доступа к данным, но нас будет интересовать их API истории. API истории предоставляет сводку различных погодных измерений для города и штата в определенный день.

Формат запроса ресурса history API выглядит следующим образом:

http://api.wunderground.com/api/API_KEY/history_YYYYMMDD/q/STATE/CITY.json
  • API_KEY : API_KEY, который Weather Underground предоставляет вместе с вашей учетной записью
  • YYYY MM DD : Строка, представляющая целевую дату вашего запроса
  • STATE : Двухбуквенная аббревиатура штата в Соединенных Штатах
  • CITY : Название города, связанного с запрашиваемым вами штатом

Выполнение запросов к API

Чтобы сделать запросы к API Weather Underground history и обработать возвращенные данные, я буду использовать несколько стандартных библиотек, а также некоторые популярные сторонние библиотеки. Ниже приведена таблица библиотек, которые я буду использовать, и их описание. Инструкции по установке см. в приведенной документации.

Стандартная библиотека дата и время Используется для увеличения наших запросов на день
Стандартная библиотека время Используется для задержки запросов на пребывание менее 10 в минуту
Стандартная библиотека коллекции Используйте namedtuples для структурированного сбора данных
Сторонняя Библиотека панды Используется для обработки, организации и очистки данных
Сторонняя Библиотека запросы Используется для выполнения сетевых запросов к API
Сторонняя Библиотека 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, которую я буду запрашивать. Примечание. Вам нужно будет зарегистрироваться в Weather Underground и получить свой собственный API_KEY . К тому времени, как эта статья будет опубликована, я ее уже деактивирую.

BASE_URL – это строка с двумя заполнителями, представленными фигурными скобками. Первый {} будет заполнен ключом API_KEY , а второй {} будет заменен строковой датой. Оба значения будут интерполированы в строку BASE_URL с помощью функции str.format (…) .

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

Затем я инициализирую целевую дату до первого дня 2015 года. Затем я укажу функции, которые я хотел бы проанализировать из ответов, возвращенных из API. Функции-это просто ключи, присутствующие в части history - > dailysummary ответа JSON. Эти функции используются для определения namedtuple called Daily Summary , который я буду использовать для организации данных отдельного запроса в списке 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)

В этом разделе я буду делать фактические запросы к API и собирать успешные ответы, используя функцию, определенную ниже. Эта функция принимает параметры url , api_key , target_date и 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

Я начинаю с определения списка под названием records, который будет содержать проанализированные данные как DailySummary | namedtuple s. Цикл for определяется таким образом, что он повторяет цикл в течение количества дней, переданных функции.

Затем запрос форматируется с помощью функции str.format() для интерполяции объекта API_KEY и строки в формате target_date . После форматирования переменная запроса передается методу get() объекта requests , а ответ присваивается переменной с именем response .

С возвращенным ответом я хочу убедиться, что запрос был успешным, оценив, что код состояния HTTP равен 200. Если это успешно, то я разбираю тело ответа в JSON, используя метод json() возвращаемого объекта ответа. Прикованный к одному и тому же вызову метода json() я выбираю индексы структур history и daily summary, затем хватаю первый элемент в списке dailysummary и назначаю его переменной с именем data .

Теперь, когда у меня есть dict-подобная структура данных, на которую ссылается переменная data , я могу выбрать нужные поля и создать новый экземпляр DailySummary | namedtuple , который добавляется к списку records .

Наконец, каждая итерация цикла завершается вызовом метода sleep модуля time, чтобы приостановить выполнение цикла на шесть секунд, гарантируя, что в минуту будет сделано не более 10 запросов, удерживая нас в пределах Weather Underground.

Затем target_date увеличивается на 1 день с помощью объекта timedelta модуля datetime , так что следующая итерация цикла извлекает ежедневную сводку за следующий день.

Первая партия запросов

Без дальнейших проволочек я начну первый набор запросов на максимально выделенный ежедневный запрос под бесплатным аккаунтом разработчика в размере 500. Затем я предлагаю вам снова выпить кофе (или другой любимый напиток) и заняться любимым телешоу, потому что эта функция займет не менее часа в зависимости от задержки сети. Таким образом, мы исчерпали наши запросы на день, и это только половина данных, с которыми мы будем работать.

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

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

Завершение поиска данных

Итак, теперь, когда наступил новый день, у нас есть чистый лист и до 500 запросов, которые можно сделать к API Weather Underground history. Наша партия из 500 запросов, выданных вчера, началась 1 января 2015 года и закончилась 15 мая 2016 года (при условии, что у вас не было никаких неудачных запросов). Еще раз давайте запустим еще одну партию из 500 запросов, но на этот раз не оставляйте меня на целый день, потому что, как только этот последний кусок данных будет собран, мы начнем форматировать его в фрейм данных Pandas и извлекать потенциально полезные функции.

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

Настройка фрейма данных наших панд

Теперь, когда у меня есть хороший и значительный список записей Daily Summary именованных кортежей, я буду использовать его для создания Pandas DataFrame . Фрейм данных Pandas-это очень полезная структура данных для многих задач программирования, которые наиболее широко известны для очистки и обработки данных, используемых в проектах машинного обучения (или экспериментах).

Я буду использовать Панд.DataFrame(...) конструктор класса для создания экземпляра объекта DataFrame. Параметры, передаваемые конструктору, представляют собой записи, представляющие данные для фрейма данных, список функций, который я также использовал для определения DailySummary | namedtuple s, который будет указывать столбцы фрейма данных. Метод set_index() привязан к экземпляру фрейма данных, чтобы указать дату в качестве индекса.

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

Вывод функций

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

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

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

Сейчас я не могу сказать, что обладаю значительными знаниями в области метеорологии или моделей прогнозирования погоды, но я сделал минимальный поиск предыдущей работы по использованию машинного обучения для прогнозирования погодных температур. Как оказалось, существует довольно много исследовательских статей на эту тему, и в 2016 году Холмстром, Лю и Во описывают использование линейной регрессии именно для этого. В своей статье Машинное обучение Применительно к прогнозированию погоды они использовали данные о погоде за предыдущие два дня для следующих измерений.

  • максимальная температура
  • минимальная температура
  • средняя влажность
  • среднее атмосферное давление

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

  • средняя температура
  • средняя точка росы
  • среднее давление
  • максимальная влажность
  • минимальная влажность
  • максимальная точка росы
  • минимальная точка росы
  • максимальное давление
  • минимальное давление
  • осадок

Итак, следующий шаг-найти способ включить эти новые функции в качестве столбцов в наш фрейм данных. Для этого я сделаю меньшее подмножество текущего фрейма данных, чтобы облегчить работу с ним при разработке алгоритма создания этих функций. Я сделаю фрейм данных tmp , состоящий всего из 10 записей и функций mean temp и meandewptm .

tmp = df[['meantempm', 'meandewptm']].head(10)
tmp
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

Давайте разберем то, что мы надеемся достичь, а затем переведем это в код. Для каждого дня (строка) и для данного объекта (столбец) Я хотел бы найти значение для этой функции за N дней до этого. Для каждого значения N (1-3 в нашем случае) Я хочу сделать новый столбец для этой функции, представляющий измерение N-го предыдущего дня.

# 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
2015-01-01 -6 -12 Никто
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

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

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

Теперь я напишу цикл для перебора функций в списке функций, определенном ранее, и для каждой функции, которая не является “датой”, и для N дней с 1 по 3 мы вызовем нашу функцию, чтобы добавить производные функции, которые мы хотим оценить для прогнозирования температуры.

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() который, большой сюрприз… предоставляет информацию о фрейме данных. Интерес представляет столбец “тип данных” выходных данных.

df.info()

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

Обратите внимание, что тип данных каждого столбца имеет тип “объект”. Нам нужно преобразовать все эти столбцы признаков в поплавки для того типа численного анализа, который мы надеемся выполнить. Для этого я буду использовать метод apply() DataFrame, чтобы применить метод Pandas to_numeric ко всем значениям фрейма данных. Параметр error='coerce' заполнит любые текстовые значения в NaNs . Обычно в данных из дикой природы можно найти текстовые значения, которые обычно исходят от сборщика данных, где данные отсутствуют или недействительны.

df = df.apply(pd.to_numeric, errors='coerce')
df.info()

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

Теперь, когда все наши данные имеют нужный мне тип данных, я хотел бы взглянуть на некоторые сводные статистические данные функций и использовать эмпирическое статистическое правило для проверки наличия экстремальных выбросов. Метод фрейма данных describe() создаст фрейм данных, содержащий количество, среднее значение, стандартное отклонение, мин, 25-й процентиль, 50-й процентиль (или медиану), 75-й процентиль и максимальное значение. Это может быть очень полезной информацией для оценки распределения данных признаков.

Я хотел бы добавить к этой информации еще один выходной столбец, указывающий на существование выбросов. Эмпирическое правило для определения экстремального выброса-это значение, которое меньше 3 межквартильных диапазонов ниже 25-го процентиля или 3 межквартильных диапазона выше 75-го процентиля. Межквартильный диапазон-это просто разница между 75-м процентилем и 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,]
83.0 90.0 93.00 100.00 47.0 9.273053 88.107107 999.0 True max humidity_1
83.0 90.0 93.00 100.00 47.0 9.276407 88.102204 998.0 True max humidity_2
83.0 90.0 93.00 100.00 47.0 9.276775 88.093280 997.0 True max humidity_3
1015.0 1019.0 1024.00 1055.00 993.0 7.751874 1019.924925 999.0 True максимальное давление_1
1015.0 1019.0 1024.00 1055.00 993.0 7.755482 1019.922846 998.0 True максимальное давление_2
1015.0 1019.0 1024.00 1055.00 993.0 7.757805 1019.927783 997.0 True максимальное давление_3
1008.0 1012.0 1017.00 1035.00 956.0 7.882062 1012.329329 999.0 True минимальное давление_1
1008.0 1012.0 1017.00 1035.00 956.0 7.885560 1012.326653 998.0 True минимальное давление_2
1008.0 1012.0 1017.00 1035.00 956.0 7.889511 1012.326981 997.0 True минимальное давление_3
0.0 0.0 0.51 95.76 0.0 8.874345 2.908211 889.0 True precip_1
0.0 0.0 0.51 95.76 0.0 8.874345 2.908211 889.0 True precip_2
0.0 0.0 0.51 95.76 0.0 8.860608 2.888885 888.0 True осадок_3

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

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

%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 , данные демонстрируют довольно большой отрицательный перекос. Я хочу иметь это в виду при выборе моделей прогнозирования и оценке силы воздействия максимальной влажности. Многие из лежащих в основе статистических методов предполагают, что данные распределены нормально. На данный момент я думаю, что оставлю их в покое, но будет хорошо иметь это в виду и иметь определенную долю скептицизма по этому поводу.

Далее я рассмотрю распределение характеристик минимального давления.

df.minpressurem_1.hist()
plt.title('Distribution of minpressurem_1')
plt.xlabel('minpressurem_1')
plt.show()
Гистограмма минимального давления

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

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

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

Посмотрите еще раз на выходные данные с последнего раза, когда я выпустил метод info . Существует столбец выходных данных, в котором перечислены ненулевые значения для каждого столбца объектов. Глядя на эту информацию, вы можете видеть, что по большей части функции содержат относительно немного отсутствующих (null/NaN) значений, в основном только те, которые я ввел. Однако в колонках осадков, по-видимому, отсутствует значительная часть их данных.

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

Как я вижу у меня есть несколько вариантов решения этой проблемы недостающих данных:

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

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

# 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

Теперь, когда я заполнил все недостающие значения, которые я могу, будучи осторожным, чтобы не негативно повлиять на качество, я был бы удобен просто удалить оставшиеся записи, содержащие недостающие значения из набора данных. Довольно легко удалить строки из фрейма данных, содержащего NAN. Все, что мне нужно сделать, это вызвать метод dropna () , и Панды сделают всю работу за меня.

df = df.dropna()

Ресурсы

Хотите изучить инструменты, машинное обучение и анализ данных, используемые в этом учебнике? Вот несколько замечательных ресурсов, которые помогут вам начать работу:

Вывод

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

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

Спасибо за чтение, и я надеюсь, что вы с нетерпением ждете предстоящих статей по этому проекту.

Ищете части 2 и 3 этой серии? Вот так:

  • Использование машинного обучения для прогнозирования погоды: Часть 2
  • Использование машинного обучения для прогнозирования погоды: Часть 3