赞
踩
原文:HTML
翻译:我
在本教程中,我们将学习pandas库中功能强大的时间序列工具,并学习制作类似上图的漂亮图表!
pandas最初是为金融时间序列(例如每日股票市场价格)开发的,其强大而灵活的数据结构可应用于任何领域的时间序列数据,包括商业,科学,工程,公共卫生等。借助这些工具,可以轻松组织,转换,分析和可视化任何粒度级别的数据——在感兴趣的特定时间段检查详细信息,并缩小范围以探索不同时间范围的变化,例如每月或每年的汇总,重复发生的模式和长期趋势。
在最广义的定义中,时间序列是在不同时间点测量值的任何数据集。许多时间序列以特定的频率均匀地间隔开,例如每小时的天气预报,每天的网站访问次数或每月的销售总额。时间序列的间隔和间隔也可能不规则,例如计算机系统的事件日志中的时间戳数据或911紧急呼叫的历史记录。pandas时间序列工具同样适用于两种时间序列。
本教程将主要关注时间序列分析的数据整理和可视化方面。使用时间序列的能源数据,我们将看到基于时间的索引编制,重采样和滚动窗口之类的技术如何帮助我们探索电力需求和可再生能源供应随时间的变化。我们将涵盖以下主题:
我们将使用Python 3.6,pandas,matplotlib和seaborn。为了充分利用本教程,需要熟悉pandas和matplotlib的基础知识。
在本教程中,我们将处理德国的开放式电力系统数据(OPSD)的每日时间序列,近年来,该数据一直在迅速扩大其可再生能源的生产。数据集包括2006-2017年全国范围的用电量,风力发电和太阳能发电总量。可以在此处下载数据。
电力生产和消耗报告为千兆瓦时(GWh)的每日总计。数据文件的列为:
我们将使用pandas时间序列工具来回答以下问题,以探索德国的电力消耗和生产随时间的变化:
在深入研究OPSD数据之前,让我们简要介绍一下用于处理日期和时间的主要pandas数据结构。在pandas中,单个时间点表示为时间戳(Timestamp)。我们可以使用to_datetime()函数根据各种日期/时间格式的字符串创建时间戳。让我们导入pandas并将一些日期和时间转换为时间戳。
import pandas as pd
pd.to_datetime('2018-01-15 3:45pm')
Timestamp('2018-01-15 15:45:00')
pd.to_datetime('7/8/1952')
Timestamp('1952-07-08 00:00:00')
如我们所见,to_datetime()
根据输入自动推断出日期/时间格式。在上面的示例中,模糊日期 7/8/1952
被假定为月/日/年,并被解释为1952年7月8日。另外,我们可以使用 dayfirst
参数告诉pandas将日期解释为1952年8月7日。
pd.to_datetime('7/8/1952, dayfirst=True)
Timestamp('1952-08-07 00:00:00')
如果我们提供一个字符串列表或字符串数组作为 to_datetime()
的输入,它将在DatetimeIndex对象中返回日期/时间值的序列,这是支持大多数pandas时间序列功能的核心数据结构。
pd.to_datetime(['2018-01-05', '7/8/1952', 'Oct 10, 1995'])
DatetimeIndex(['2018-01-05', '1952-07-08', '1995-10-10'], dtype='datetime64[ns]', freq=None)
在上面的DatetimeIndex中,数据类型datetime64 [ns]表示基础数据存储为64位整数,以纳秒(ns)为单位。此数据结构允许pandas紧凑地存储大的日期/时间值序列,并使用NumPy datetime64数组有效地执行矢量化操作。
如果我们要处理的所有字符串都具有相同的日期/时间格式,则可以使用format参数显式指定它。对于非常大的数据集,与默认行为(对于每个单独的字符串分别推断格式)相比,这可以大大提高 to_datetime()
的性能。可以使用Python内置的datetime模块中 strftime()
和strptime()
函数中的任何格式代码。下面的示例使用格式代码 %m
(数字月份),%d
(每月的日期)和 %y
(两位数字的年份)指定格式。
pd.to_datetime(['2/25/10', '8/6/17', '12/15/12'], format='%m/%d/%y')
DatetimeIndex(['2010-02-25', '2017-08-06', '2012-12-15'], dtype='datetime64[ns]', freq=None)
除了代表各个时间点的Timestamp和DatetimeIndex对象外,pandas还包括代表持续时间(例如125秒)和时间段(例如2018年11月)的数据结构。有关这些数据结构的更多信息,这里有一个不错的摘要。在本教程中,我们将使用DatetimeIndexes,这是pandas时间序列最常见的数据结构。
为了处理pandas中的时间序列数据,我们使用DatetimeIndex作为DataFrame(或Series)的索引。让我们看看如何使用我们的OPSD数据集执行此操作。首先,我们使用read_csv()函数将数据读取到DataFrame中,然后显示其形状。
opsd_daily = pd.read_csv('opsd_germany_daily.csv')
opsd_daily.shape
(4383, 5)
DataFrame有4383行,涵盖从2006年1月1日到2017年12月31日的时间段。要查看数据的外观,让我们使用head()和tail()方法显示前三行和后三行。
接下来,让我们检查每一列的数据类型。
opsd_daily.dtypes
Date datetime64[ns]
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object
现在,“日期”列是正确的数据类型,现在将其设置为DataFrame的索引。
opsd_daily = opsd_daily.set_index('Date')
opsd_daily.head(3)
opsd_daily.index
DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04',
'2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08',
'2006-01-09', '2006-01-10',
...
'2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25',
'2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29',
'2017-12-30', '2017-12-31'],
dtype='datetime64[ns]', name='Date', length=4383, freq=None)
opsd_daily = pd.read_csv('opsd_germany_daily.csv', index_col=0, parse_dates=True)
另外,我们可以使用read_csv()函数的index_col和parse_dates参数将上述步骤合并为一行。这通常是一个有用的快捷方式。
opsd_daily = pd.read_csv('opsd_germany_daily.csv', index_col=0, parse_dates=True)
现在,我们的DataFrame索引是DatetimeIndex,我们可以使用所有pandas基于时间的强大索引来处理和分析我们的数据,这将在以下部分中看到。
DatetimeIndex的另一个有用方面是,各个日期/时间组件都可以作为属性使用,例如年,月,日等。让我们在opsd_daily中添加更多列,其中包含年,月和周日的名称。
# Add columns with year, month, and weekday name
opsd_daily['Year'] = opsd_daily.index.year
opsd_daily['Month'] = opsd_daily.index.month
opsd_daily['Weekday Name'] = opsd_daily.index.weekday_name
# Display a random sampling of 5 rows
opsd_daily.sample(5, random_state=0)
基于时间的索引是pandas时间序列最强大,最便捷的功能之一,即使用日期和时间直观地组织和访问我们的数据。通过基于时间的索引,我们可以使用日期/时间格式的字符串通过loc访问器在DataFrame中选择数据。索引的工作方式类似于使用loc的基于标准标签的标准索引,但是具有一些附加功能。
例如,我们可以使用诸如“2017-08-10”之类的字符串选择一天的数据。
opsd_daily.loc['2017-08-10']
Consumption 1351.49
Wind 100.274
Solar 71.16
Wind+Solar 171.434
Year 2017
Month 8
Weekday Name Thursday
Name: 2017-08-10 00:00:00, dtype: object
我们还可以选择一整天,例如“ 2014-01-20”:“ 2014-01-22”。与使用loc的基于标签的常规索引一样,切片包含两个端点。
opsd_daily.loc['2014-01-20':'2014-01-22']
pandas时间序列的另一个非常方便的功能是部分字符串索引,在这里我们可以选择部分匹配给定字符串的所有日期/时间。例如,我们可以使用 opsd_daily.loc ['2006']
选择2006年全年,或使用 opsd_daily.loc ['2012-02']
选择2012年2月整个月。
opsd_daily.loc['2012-02']
······
使用pandas和matplotlib,我们可以轻松地可视化时间序列数据。在本节中,我们将介绍一些示例和一些有用的自定义时序图。首先,让我们导入matplotlib。
import matplotlib.pyplot as plt
我们将对绘图使用seaborn样式,然后将默认图形大小调整为适合时间序列绘图的形状。
import seaborn as sns
# Use seaborn style defaults and set the default figure size
sns.set(rc={'figure.figsize':(11, 4)})
让我们使用DataFrame的plot()方法创建德国每日用电量的全时序列的线图。
opsd_daily['Consumption'].plot(linewidth=0.5);
我们可以看到plot()方法为x轴选择了很好的刻度位置(每两年)和标签(年份),这很有帮助。但是,由于有这么多的数据点,线图比较拥挤且难以阅读。让我们将数据绘制成点图,然后查看太阳和风的时间序列。
cols_plot = ['Consumption', 'Solar', 'Wind']
axes = opsd_daily[cols_plot].plot(marker='.', alpha=0.5, linestyle='None', figsize=(11, 9), subplots=True)
for ax in axes:
ax.set_ylabel('Daily Totals (GWh)')
我们已经可以看到一些有趣的模式:
所有这三个时间序列都清楚地显示出周期性(在时间序列分析中通常称为季节性),其中,模式会以规则的时间间隔一次又一次地重复。消耗,太阳和风的时间序列在每年的时间尺度上在高值和低值之间振荡,这与一年中天气的季节性变化相对应。但是,一般而言,季节性不必与气象季节相对应。例如,零售销售数据通常表现出年度季节性,在11月和12月,直到假期之前,销售都有所增加。
季节性也可能在其他时间范围内发生。上图显示德国的用电量每周可能会有一些季节性变化,与工作日和周末相对应。让我们绘制一年中的时间序列,以进行进一步调查。
ax = opsd_daily.loc['2017', 'Consumption'].plot()
ax.set_ylabel('Daily Consumption (GWh)');
正如我们所怀疑的那样,平日的消费最高,而周末的最低。
为了更好地显示上图中的每周用电季节性,最好在每周的时间范围内(而不是在每个月的第一天)使用垂直网格线。我们可以使用matplotlib.dates自定义我们的PLOT,因此让我们导入该模块。
import matplotlib.dates as mdates
由于与DataFrame的plot()方法相比,matplotlib.dates中日期/时间刻度的处理方式略有不同,因此我们直接在matplotlib中创建绘图。然后,我们使用mdates.WeekdayLocator()和mdates.MONDAY将x轴刻度设置为每周的第一个星期一。我们还使用mdates.DateFormatter()改进了刻度标签的格式,并使用了前面看到的格式代码。
fig, ax = plt.subplots()
ax.plot(opsd_daily.loc['2017-01':'2017-02', 'Consumption'], marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)')
ax.set_title('Jan-Feb 2017 Electricity Consumption')
# Set x-axis major ticks to weekly interval, on Mondays
ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MONDAY))
# Format x-tick labels as 3-letter month name and day number
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d'));
现在,我们在每个星期一都有垂直网格线和格式良好的刻度标签,因此我们可以轻松分辨出哪几天是工作日和周末。
还有许多其他方式可以使时间序列可视化,具体取决于尝试探索的模式-散点图,热图,直方图等。在以下各节中,我们将看到其他可视化示例,包括以某种方式转换的时间序列数据的可视化,例如聚合或平滑的数据。
接下来,让我们用箱形图进一步探索数据的季节性,使用seaborn的boxplot()函数按不同的时间段对数据进行分组并显示每个组的分布。我们将首先按月对数据进行分组,以可视化年度季节性。
fig, axes = plt.subplots(3, 1, figsize=(11, 10), sharex=True)
for name, ax in zip(['Consumption', 'Solar', 'Wind'], axes):
sns.boxplot(data=opsd_daily, x='Month', y=name, ax=ax)
ax.set_ylabel('GWh')
ax.set_title(name)
# Remove the automatic x-axis label from all but the bottom subplot
if ax != axes[-1]:
ax.set_xlabel('')
这些箱形图确认了我们在较早的图中看到的年度季节性,并提供了其他一些见解:
接下来,让我们按星期几对耗电时间序列进行分组,以探讨每周的季节性。
sns.boxplot(data=opsd_daily, x='Weekday Name', y='Consumption');
不出所料,工作日的用电量明显高于周末。工作日的异常值较低可能是在假日期间。
本节简要介绍了时间序列的季节性。稍后我们将看到,对数据应用滚动窗口还可以帮助可视化不同时间尺度上的季节性。其他用于分析季节性的技术包括自相关图(autocorrelation plots),该图绘制了时间序列与自身在不同时间滞后的相关系数。
具有较强季节性的时间序列通常可以用将信号分解为季节性和长期趋势的模型很好地表示,并且这些模型可用于预测时间序列的未来值。此类模型的一个简单示例就是经典的季节性分解(classical seasonal decomposition),如教程所示。一个更复杂的示例是Facebook的Prophet模型,该模型使用曲线拟合来分解时间序列,要考虑到多个时间尺度上的季节性,假日影响,突然的变化点和长期趋势,如教程所示。
当时间序列的数据点在时间上均匀间隔(例如每小时,每天,每月等)时,该时间序列可以与pandas的频率相关联。例如,让我们使用date_range()函数以每天的频率创建一个从1998-03-10到1998-03-15的均匀间隔的日期序列。
pd.date_range('1998-03-10', '1998-03-15', freq='D')
DatetimeIndex(['1998-03-10', '1998-03-11', '1998-03-12', '1998-03-13',
'1998-03-14', '1998-03-15'],
dtype='datetime64[ns]', freq='D')
所得的DatetimeIndex具有值为“ D”的属性freq,表示每日频率。pandas的可用频率包括每小时(‘H’),日历每天(‘D’),商业的每天(‘B’),每周(‘W’),每月(‘M’),每季度(‘Q’),每年(‘A’),还有许多其他内容。也可以将频率指定为任何基本频率的倍数,例如每五天为“5D”。
再举一个例子,让我们以每小时的频率创建一个日期范围,指定开始日期和期间数,而不是开始日期和结束日期。
pd.date_range('2004-09-20', periods=8, freq='H')
DatetimeIndex(['2004-09-20 00:00:00', '2004-09-20 01:00:00',
'2004-09-20 02:00:00', '2004-09-20 03:00:00',
'2004-09-20 04:00:00', '2004-09-20 05:00:00',
'2004-09-20 06:00:00', '2004-09-20 07:00:00'],
dtype='datetime64[ns]', freq='H')
现在,让我们再来看一下opsd_daily时间序列的DatetimeIndex。
opsd_daily.index
DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04',
'2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08',
'2006-01-09', '2006-01-10',
...
'2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25',
'2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29',
'2017-12-30', '2017-12-31'],
dtype='datetime64[ns]', name='Date', length=4383, freq=None)
我们可以看到它没有频率(freq = None)。这是有道理的,因为该索引是从CSV文件中的日期序列创建的,而没有为时间序列明确指定任何频率。
如果我们知道我们的数据应处于特定频率,则可以使用DataFrame的asfreq()方法分配频率。如果数据中缺少任何日期/时间,则将为这些日期/时间添加新行,这些行要么为空(NaN),要么根据指定的数据填充方法(如正向填充或插值)进行填充。
为了解其工作原理,让我们创建一个新的DataFrame,其中仅包含2013年2月3日,6日和8日的“消费”数据。
# To select an arbitrary sequence of date/time values from a pandas time series,
# we need to use a DatetimeIndex, rather than simply a list of date/time strings
times_sample = pd.to_datetime(['2013-02-03', '2013-02-06', '2013-02-08'])
# Select the specified dates and just the Consumption column
consum_sample = opsd_daily.loc[times_sample, ['Consumption']].copy()
consum_sample
现在,我们使用asfreq()方法将DataFrame转换为每日频率,其中一列用于未填充的数据,一列用于向前填充的数据。
# Convert the data to daily frequency, without filling any missings
consum_freq = consum_sample.asfreq('D')
# Create a column with missings forward filled
consum_freq['Consumption - Forward Fill'] = consum_sample.asfreq('D', method='ffill')
consum_freq
在“消费”列中,我们有原始数据,其consum_sample DataFrame中缺少的任何日期的NaN值。在“消耗-前向填充”列中,缺失已被前向填充,这意味着最后一个值将在缺失行中重复,直到出现下一个非缺失值。
如果要进行任何需要均匀间隔的数据且没有丢失的时间序列分析,则需要使用asfreq()将时间序列转换为指定的频率,并使用适当的方法填充所有丢失的数据。
将时间序列数据重新采样到较低或较高的频率通常很有用。重采样到较低的频率(下采样)通常涉及聚合操作——例如,根据每日数据计算每月销售总额。我们在本教程中使用的每日OPSD数据是从原始的每小时时间序列中缩减采样的。重采样到较高频率(上采样)的情况比较少见,并且通常涉及插值法或其他数据填充方法,例如,将每小时天气数据插值到10分钟间隔以输入到科学模型中。
我们将在这里着重于下采样,探索它如何帮助我们在各种时间尺度上分析我们的OPSD数据。我们使用DataFrame的resample()方法,该方法将DatetimeIndex拆分为多个时间段,并按时间段对数据进行分组。resample()方法返回一个Resampler对象,类似于pandas GroupBy对象。然后,我们可以对每个时间段的数据组应用聚合方法,例如mean(),mid(),sum()等。
例如,让我们将数据重新采样为每周平均时间序列。
# Specify the data columns we want to include (i.e. exclude Year, Month, Weekday Name)
data_columns = ['Consumption', 'Wind', 'Solar', 'Wind+Solar']
# Resample to weekly frequency, aggregating with mean
opsd_weekly_mean = opsd_daily[data_columns].resample('W').mean()
opsd_weekly_mean.head(3)
上面标记为2006-01-01的第一行包含时间仓2006-01-01至2006-01-07中包含的所有数据的平均值。第二行标记为2006-01-08,其中包含2006-01-08至2006-01-14时间段的平均值数据,依此类推。默认情况下,向下采样的时间序列的每一行都标有时间仓的右边缘。
根据构造,我们的每周时间序列的数据点是每日时间序列的1/7。我们可以通过比较两个DataFrame的行数来确认这一点。
print(opsd_daily.shape[0])
print(opsd_weekly_mean.shape[0])
4383
627
让我们绘制一个六个月内的每日和每周的太阳时间序列,以进行比较。
# Start and end of the date range to extract
start, end = '2017-01', '2017-06'
# Plot daily and weekly resampled time series together
fig, ax = plt.subplots()
ax.plot(opsd_daily.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(opsd_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.set_ylabel('Solar Production (GWh)')
ax.legend();
我们可以看到,每周平均时间序列比每日时间序列更平滑,因为在重采样中平均了较高的频率变异性。
现在,让我们将数据重新采样到每月一次,汇总总和而不是平均值。与使用mean()进行聚合(将所有丢失数据的任何时间段的输出都设置为NaN)不同,sum()的默认行为将输出0作为丢失数据之和。我们使用min_count参数来更改此行为。
# Compute the monthly sums, setting the value to NaN for any month which has
# fewer than 28 days of data
opsd_monthly = opsd_daily[data_columns].resample('M').sum(min_count=28)
opsd_monthly.head(3)
可能会注意到,每月重新采样的数据标有每个月的月底(右边的条边),而每周重新采样的数据标有左边的条边。默认情况下,对于每月,每季度和每年的频率,重新采样的数据都标记有右侧bin边缘,对于所有其他频率,则标记有左侧bin边缘。可以使用resample()文档中列出的参数来调整此行为和其他各种选项。
现在,我们将用电量绘制成线形图,将风能和太阳能发电量绘制成堆积面积图,从而探索每月的时间序列。
fig, ax = plt.subplots()
ax.plot(opsd_monthly['Consumption'], color='black', label='Consumption')
opsd_monthly[['Wind', 'Solar']].plot.area(ax=ax, linewidth=0)
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.legend()
ax.set_ylabel('Monthly Total (GWh)');
在这个月度时间尺度上,我们可以清楚地看到每个时间序列中的年度季节性,而且很明显,用电量在一段时间内一直相当稳定,风能生产一直在稳定增长,其中风能+太阳能占所消耗电能的比重越来越大。
让我们通过重新采样每年的频率并计算每年的风能+太阳能与消耗量之比,来进一步探索这一点。
# Compute the annual sums, setting the value to NaN for any year which has
# fewer than 360 days of data
opsd_annual = opsd_daily[data_columns].resample('A').sum(min_count=360)
# The default index of the resampled DataFrame is the last day of each year,
# ('2006-12-31', '2007-12-31', etc.) so to make life easier, set the index
# to the year component
opsd_annual = opsd_annual.set_index(opsd_annual.index.year)
opsd_annual.index.name = 'Year'
# Compute the ratio of Wind+Solar to Consumption
opsd_annual['Wind+Solar/Consumption'] = opsd_annual['Wind+Solar'] / opsd_annual['Consumption']
opsd_annual.tail(3)
最后,让我们用条形图绘制风电与太阳能在年度用电量中所占的比例。
# Plot from 2012 onwards, because there is no solar production data in earlier years
ax = opsd_annual.loc[2012:, 'Wind+Solar/Consumption'].plot.bar(color='C0')
ax.set_ylabel('Fraction')
ax.set_ylim(0, 0.3)
ax.set_title('Wind + Solar Share of Annual Electricity Consumption')
plt.xticks(rotation=0);
我们可以看到,风能+太阳能生产占年度用电量的比例已从2012年的约15%增长到2017年的约27%。
滚动窗口操作是时间序列数据的另一个重要转换。与下采样类似,滚动窗口将数据划分为时间窗口,并且每个窗口中的数据都使用mean(),median(),sum()等函数进行汇总。但是,与下采样不同,在下采样中时间段不重叠并且输出的频率低于输入的频率,滚动窗口以与数据相同的频率重叠和“滚动”,因此转换后的时间序列与原始时间序列的频率相同。
默认情况下,窗口中的所有数据点在聚合中的权重均相等,但是可以通过指定窗口类型(例如高斯,三角形和其他)来更改此值。我们将在此处继续使用标准的等重窗口。
让我们使用rolling()方法来计算每日数据的7天滚动平均值。我们使用center = True参数在每个窗口的中点标记标签,因此滚动窗口为:
# Compute the centered 7-day rolling mean
opsd_7d = opsd_daily[data_columns].rolling(7, center=True).mean()
opsd_7d.head(10)
我们可以看到第一个非丢失滚动平均值在2006-01-04上,因为这是第一个滚动窗口的中点。
为了形象化滚动平均值和重采样之间的差异,让我们更新我们较早的2017年1月至6月太阳能发电量图,以包括7天滚动平均值,每周平均重采样时间序列和原始每日数据。
# Start and end of the date range to extract
start, end = '2017-01', '2017-06'
# Plot daily, weekly resampled, and 7-day rolling mean time series together
fig, ax = plt.subplots()
ax.plot(opsd_daily.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(opsd_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.plot(opsd_7d.loc[start:end, 'Solar'],
marker='.', linestyle='-', label='7-d Rolling Mean')
ax.set_ylabel('Solar Production (GWh)')
ax.legend();
我们可以看到,滚动平均时间序列中的数据点与每日数据具有相同的间隔,但是由于平均了较高的频率可变性,因此曲线更加平滑。在滚动平均时间序列中,高峰和低谷往往与每日时间序列的高峰和低谷紧密对齐。 相反,由于重新采样的时间序列具有较粗的粒度,因此每周重新采样的时间序列中的高峰和低谷与每日时间序列的排列不太紧密。
除了较高的频率可变性(例如季节性和噪声)以外,时间序列数据通常还表现出一些缓慢的渐进可变性。可视化这些趋势的一种简单方法是在不同的时间范围内使用滚动方式。
滚动平均值趋向于通过平均远高于窗口大小的频率上的变化并在等于窗口大小的时间尺度上平均任何季节性来平滑时间序列。这允许探索数据中的低频变化。由于我们的耗电量时间序列具有每周和每年的季节性,因此让我们来看一下这两个时间尺度上的滚动平均值。
我们已经计算了7天的滚动平均值,因此现在我们计算OPSD数据的365天的滚动平均值。
# The min_periods=360 argument accounts for a few isolated missing days in the
# wind and solar production time series
opsd_365d = opsd_daily[data_columns].rolling(window=365, center=True, min_periods=360).mean()
让我们绘制7天和365天的滚动平均用电量以及每日时间序列。
# Plot daily, 7-day rolling mean, and 365-day rolling mean time series
fig, ax = plt.subplots()
ax.plot(opsd_daily['Consumption'], marker='.', markersize=2, color='0.6',
linestyle='None', label='Daily')
ax.plot(opsd_7d['Consumption'], linewidth=2, label='7-d Rolling Mean')
ax.plot(opsd_365d['Consumption'], color='0.2', linewidth=3,
label='Trend (365-d Rolling Mean)')
# Set x-ticks to yearly interval and add legend and labels
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.legend()
ax.set_xlabel('Year')
ax.set_ylabel('Consumption (GWh)')
ax.set_title('Trends in Electricity Consumption');
我们可以看到,为期7天的滚动均值使所有的每周季节性都变得平滑,同时保留了年度季节性。 7天的滚动平均值显示,虽然冬季的用电量通常较高,而夏季则较低,但每年冬季在12月底和1月初的节假日期间,其用电量会急剧下降数周。
纵观365天的滚动平均时间序列,我们可以看到电力消耗的长期趋势相当平稳,在2009年和2012-2013年之间有几个异常的低电量消耗时期。
现在,让我们看一下风能和太阳能生产的趋势。
# Plot 365-day rolling mean time series of wind and solar power
fig, ax = plt.subplots()
for nm in ['Wind', 'Solar', 'Wind+Solar']:
ax.plot(opsd_365d[nm], label=nm)
# Set x-ticks to yearly interval, adjust y-axis limits, add legend and labels
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.set_ylim(0, 400)
ax.legend()
ax.set_ylabel('Production (GWh)')
ax.set_title('Trends in Electricity Production (365-d Rolling Means)');
随着德国继续扩大在这些领域的能力,我们可以看到太阳能发电的增长趋势很小,而风力发电的增长趋势很大。
我们已经学习了如何使用基于时间的索引,重采样和滚动窗口等技术来处理,分析和可视化pandas中的时间序列数据。将这些技术应用于我们的OPSD数据集,我们获得了有关德国电力消耗和生产的季节性,趋势和其他有趣特征的见解。
我们尚未涉及的其他可能有用的主题包括时区处理和time shift。 如果想了解有关在pandas中使用时间序列数据的更多信息,可以查阅《 Python数据科学手册》,博客文章以及官方文档。 如果对使用时序数据进行预测和机器学习感兴趣,我们将在以后的博客文章中介绍这些主题,敬请期待!
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。