本章是使用机器学习预测天气系列教程的第一部分,使用 Python 和机器学习来构建模型,根据从 Weather Underground 收集的数据来预测天气温度。该教程将由三个不同的部分组成,涵盖的主题是:
本教程中使用的数据将从 Weather Underground 的免费层 API 服务中收集。我将使用 python 的 requests 库来调用 API,得到从 2015 年起 Lincoln, Nebraska 的天气数据。 一旦收集完成,数据将需要进行处理并汇总转成合适的格式,然后进行清理。
第二篇文章将重点分析数据中的趋势,目标是选择合适的特性并使用 python 的 statsmodels 和 scikit-learn 库来构建线性回归模型。 我将讨论构建线性回归模型,必须进行必要的假设,并演示如何评估数据特征以构建一个健壮的模型。 并在最后完成模型的测试与验证。
最后的文章将着重于使用神经网络。 我将比较构建神经网络模型和构建线性回归模型的过程,结果,准确性。
Weather Underground 是一家收集和分发全球各种天气测量数据的公司。 该公司提供了大量的 API,可用于商业和非商业用途。 在本文中,我将介绍如何使用非商业 API 获取每日天气数据。所以,如果你跟随者本教程操作的话,您需要注册他们的免费开发者帐户。 此帐户提供了一个 API 密钥,这个密钥限制,每分钟 10 个,每天 500 个 API 请求。
获取历史数据的 API 如下:
- http://api.wunderground.com/api/API_KEY/history_YYYYMMDD/q/STATE/CITY.json
本教程调用 Weather Underground API 获取历史数据时,用到如下的 python 库。
名称 | 描述 | 来源 |
---|---|---|
datetime | 处理日期 | 标准库 |
time | 处理时间 | 标准库 |
collections | 使用该库的 namedtuples 来结构化数据 | 标准库 |
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"
然后我们初始化一个变量,存储日期,然后定义一个 list,指明要从 API 返回的内容里获取的数据。然后定义一个 namedtuple 类型的变量 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,获取指定 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
首先,定义个 list records,用来存放上述的 DailySummary,使用 for 循环来遍历指定的所有日期。然后生成 url,发起 HTTP 请求,获取返回的数据,使用返回的数据,初始化 DailySummary,最后存放到 records 里。通过这个函数的出,就可以获取到指定日期开始的 N 天的历史天气数据,并返回。
由于 API 接口的限制,我们需要两天的时间才能获取到 500 天的数据。你也可以下载我的测试数据,来节约你的时间。
- records = extract_weather_data(BASE_URL, API_KEY, target_date, 500)
我们使用 DailySummary 列表来初始化 Pandas DataFrame。DataFrame 数据类型是机器学习领域经常会用到的数据结构。
- df = pd.DataFrame(records, columns = features).set_index('date')
机器学习是带有实验性质的,所以,你可能遇到一些矛盾的数据或者行为。因此,你需要在你用机器学习处理问题是,你需要对处理的问题领域有一定的了解,这样可以更好的提取数据特征。
我将采用如下的数据字段,并且,使用过去三天的数据作为预测。
首先我需要在 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')
数据清洗时机器学习过程中最重要的一步,而且非常的耗时、费力。本教程中,我们会去掉不需要的样本、数据不完整的样本,查看数据的一致性等。
首先去掉我不感兴趣的数据,来减少样本集。我们的目标是根据过去三天的天气数据预测天气温度,因此我们只保留 min, max, mean 三个字段的数据。
- # 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
注意:每一行的数据类型都是 object,我们需要把数据转成 float。
- 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
现在得到我想要的数据了。接下来我们调用 describe() 函数,这个函数会返回一个 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()
查看 maxhumidity 字段的直方图,数据表现出相当多的负偏移。 在选择预测模型和评估最大湿度影响的强度时,我会牢记这一点。 许多基本的统计方法都假定数据是正态分布的。 现在我们暂时不管它,但是记住这个异常特性。
接下来我们看另外一个字段的直方图
- 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()
这篇文章主要介绍了数据的收集、处理、清洗的流程,本篇文章处理完的处理,将用于下篇文章的模型训练。
对你来说,这篇文章可能很枯燥,没啥干货,但好的样本数据,才能训练处好的模型,因此,样本数据的收集和处理能力,直接影响你后面的机器学习的效果。
来源: https://segmentfault.com/a/1190000012617327