11.处理时间序列

Pandas是在金融建模背景下开发出来的,所以如你期待的,它包含相当大的工具集来处理日期,时间,和时间索引数据。我们将在这里讨论记录时间和日期的几种风格:

  • 时间戳 指特殊时刻(如,2015年7月4日上午7点)
  • 时间间隔和周期 指特定起始和结束点间的时间段。例如2015年。周期是时间间隔的特殊情形,在这里每个间隔有同样长时间,并且不会重叠(如,一天包含24小时)
  • 时间差或持续时间 指确切的时间长度(如,持续22.56秒)
    在本章,我们将介绍如何处理Pandas中的各种时间类型。短短的一章是无法对Python或Pandas时间工具进行完整介绍的,相反,本章旨在作为一个广泛的概述,使你作为一个用户应该如何处理时间序列。在对Pandas提供的特殊的工具讨论之前,我们将对Python中处理日期和时间的工具作一个简要介绍。列出一些适合深入学习的资源以后,我们回顾一些在Pandas中使用时间序列数据的简短例子。

Python中的日期和时间

Python世界中有许多种时间表达方式日期,时间,时间差,时隙。Pandas提供的实现序列工具对于数据科学应用可能使最有用的了,看看这些工具和Python中使用的其它包的关系很有帮助。

Python原生日期和时间:datetime和dateutil

处理时间和日期的Python基本对象位于内置的datetime模块。同第三方的dateutil模块一起,你可以使用它们快速的执行许多有用的时间日期功能。例如,你可以使用datetime类型手动构建一个日期:

from datetime import datetime
datetime(year=2015, month=7, day=4)
datetime.datetime(2015, 7, 4, 0, 0)

或者,使用dateutil模块,你可以从不同的字符串模式中解析出日期:

from dateutil import parser
date = parser.parse("4th of July, 2015")
date
datetime.datetime(2015, 7, 4, 0, 0)

一旦你有了一个datetime对象,你可以做像打印周几这样的事:

date.strftime('%A')
'Saturday'

在最后一行,我们使用一种标准的字符串格式代码来打印日期("%A"),参见Python 时间日期文档. strftime章。其它有用的日期工具文档可以在 dateutil的在线文档中找到。
另一个需要注意的包是 pytz, 它里面包括可以处理时间序列中最令人头疼部分:时区,的工具
datetime和dateutil的强大之处在于它们灵活和简单的语法:你可以使用这些对象及其内置方法来方便的执行几乎任何你感兴趣的操作。当你希望处理时间和日期的大数组时,这些工具不不好用了:就如Python数值变量列表比不过NumPy风格的数值数组,Python的日期时间对象列表与日期编码的类型数组相比也是次优的。

时间类型数组:NumPy 的datetime64

Python datetime格式的缺点激励NumPy团队添加一组原生时间序列数据类型到NumPy里。datetime64 dtype将日期编码成64位的整型,因此允许日期数组非常紧凑的表达。datetime64需要特殊的输入格式:

import numpy as np
date = np.array('2015-07-04', dtype=np.datetime64)
date
array(datetime.date(2015, 7, 4), dtype='datetime64[D]')

但是一旦我们有了这种日期格式,我们可以快捷的做矢量化操作:

date + np.arange(12)
array(['2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07',
       '2015-07-08', '2015-07-09', '2015-07-10', '2015-07-11',
       '2015-07-12', '2015-07-13', '2015-07-14', '2015-07-15'], dtype='datetime64[D]')

由于NumPy datetime64 数组内部的统一类型,相比直接工作在Python的datetime对象上,这种类型的操作在datetime64sh上要快很多,特别时当数组很大的时候(我们介绍过这种向量类型在Computation on NumPy Arrays: Universal Functions).)
datetime64和timedelta64 对象的一个细节时基于基础时间单元构建。因为datetime64对象受限于64位精度,时间编码的范围是基础时间单元的 2^{64}
例如,如果你想要一纳秒的时间精度,那么只能编码2^{64}纳秒的信息,即少于600年。NumPy将会从输入中推断出合适的单位;例如,这里是基于天的的datetime:

np.datetime64('2015-07-04')
numpy.datetime64('2015-07-04')

这个是基于分钟的datetime

np.datetime64('2015-07-04 12:00')
numpy.datetime64('2015-07-04T12:00')

注意时区被自动设置成代码执行计算机的本地时间。你可以强制使用一种你想要的基础时间单元;例如:我们强制使用基于纳秒的时间:

np.datetime64('2015-07-04 12:59:59.50', 'ns')
numpy.datetime64('2015-07-04T12:59:59.500000000')

下表来自于NumPy datetime64 documentation,列出了可用格式代码及其它们可以编码的相对和绝对时间时间跨度

Code Meaning Time span (relative) Time span (absolute)
Y Year ± 9.2e18 years [9.2e18 BC, 9.2e18 AD]
M Month ± 7.6e17 years [7.6e17 BC, 7.6e17 AD]
W Week ± 1.7e17 years [1.7e17 BC, 1.7e17 AD]
D Day ± 2.5e16 years [2.5e16 BC, 2.5e16 AD]
h Hour ± 1.0e15 years [1.0e15 BC, 1.0e15 AD]
m Minute ± 1.7e13 years [1.7e13 BC, 1.7e13 AD]
s Second ± 2.9e12 years [ 2.9e9 BC, 2.9e9 AD]
ms Millisecond ± 2.9e9 years [ 2.9e6 BC, 2.9e6 AD]
us Microsecond ± 2.9e6 years [290301 BC, 294241 AD]
ns Nanosecond ± 292 years [ 1678 AD, 2262 AD]
ps Picosecond ± 106 days [ 1969 AD, 1970 AD]
fs Femtosecond ± 2.6 hours [ 1969 AD, 1970 AD]
as Attosecond ± 9.2 seconds [ 1969 AD, 1970 AD]

对于我们现实世界中的时间类型,有用的默认值是datetime64[ns],因为它可以使用合适的精度编码有用的现代日期。
最后,我们要注意尽管datetime64数据类型解决了一些Python datetime类型的不足之处,但它缺少datetime特别是dateutil提供的那些便利的方法。更多信息参见NumPy's datetime64 documentation.

pandas的日期和时间:两全齐美

Pandas在前面讨论过的工具之上提供了Timestamp对象,它结合了datetime和dateutil的易用性以及numpy.datetime64的存储效率和矢量化接口。从一组Timestamp对象,Pandas能构建出可以被用来索引Series或DataFrame数据的DatetimeIndex;后面我们会见到许多例子。
例如,我们使用Pandas工具重新演示上面的例子。我们可以解析灵活的字符串日期格式,并且使用格式代码输出是周几:

import pandas as pd
date = pd.to_datetime("4th of July, 2015")
date
Timestamp('2015-07-04 00:00:00')
date.strftime('%A')
'Saturday'

另外,我们可以直接在同样对象上,做NumPy风格的矢量化操作:

date + pd.to_timedelta(np.arange(12), 'D')
DatetimeIndex(['2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07',
               '2015-07-08', '2015-07-09', '2015-07-10', '2015-07-11',
               '2015-07-12', '2015-07-13', '2015-07-14', '2015-07-15'],
              dtype='datetime64[ns]', freq=None)

下一部分,我们仔细看看使用Pandas工具操纵时间序列数据。

Pandas 时间序列:按时间排序

Pandas时间序列非常有用的地方是当你用时间戳进行索引的时候。例如,我们可以构建使用时间索引的Series对象:

index = pd.DatetimeIndex(['2014-07-04', '2014-08-04',
                          '2015-07-04', '2015-08-04'])
data = pd.Series([0, 1, 2, 3], index=index)
data
2014-07-04    0
2014-08-04    1
2015-07-04    2
2015-08-04    3
dtype: int64

现在我们在Series中有这样数据,我们可以使用之前讨论过任何Series索引模式,输入值被强制成日期:

data['2014-07-04':'2015-07-04']
2014-07-04    0
2014-08-04    1
2015-07-04    2
dtype: int64

还有额外特殊的日期索引操作,例如传递一个年份来获取那年的所以数据:

data['2015']
2015-07-04    2
2015-08-04    3
dtype: int64

后面,我们会看到其它日期作为索引的便利性的例子。但首先,仔细看一下可以时间序列数据结构.

Pandas时间序列数据结构

这部分将介绍用于处理时间序列数据的基础Pandas数据结构:

  • 对于时间戳,Pandas提供了Timestamp类型。前面提到过,它基本可以替代Python原生的datetime,并且它时基于更高效的numpy.datetime64 数据类型。对应的索引结构是DatetimeIndex.
  • 对于时间周期,Pandas提供了Period类型。它基于numpy.datetime64编码固定频率周期间隔。对应的索引结构是PeriodIndex.
  • 对于时间差或持续时间, Pandas提供了Timedelta类型,它对应的索引结构是TimedeltaIndex.

这些日期/时间对象中最基本的是Timestamp和DatetimeIndex对象。尽管这些对象可以被直接调用,通常使用pd.to_datetime()函数,它可以解析不同的格式。传递单日期给pd.to_datetime()产生一个Timestamp,传递一串日期,默认产生DatetimeIndex:

dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015',
                       '2015-Jul-6', '07-07-2015', '20150708'])
dates
DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07',
               '2015-07-08'],
              dtype='datetime64[ns]', freq=None)

任何DatetimeIndex都可以使用带频率代码参数的to_period()函数转换成PeriodIndex;使用'D'表示日频率:

dates.to_period('D')
PeriodIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07',
             '2015-07-08'],
            dtype='int64', freq='D')

当一个日期减去另一个日期时,就创建了 TimedeltaIndex:

dates - dates[0]
TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)

规则序列:pd.date_range()

为了更方便的创建规则日期序列,Pandas位这个目的提供了几个函数:pd.date_range()用于时间戳,pd.period_range()用于周期,pd.timedelta_range()用于时间差。我们见过Python的range()和NumPy的np.arange()将起点,终点和可选步长转变成序列。类似的,pd.date_range()接受起始日期,截止日期,和可选的周期代码来创建日期规则序列。默认情况下,频率时一天:

pd.date_range('2015-07-03', '2015-07-10')
DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
               '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
              dtype='datetime64[ns]', freq='D')

或者不必指定起始和终止日期,而时起始点和周期数量:

pd.date_range('2015-07-03', periods=8)
DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
               '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
              dtype='datetime64[ns]', freq='D')

间隔可以通过改变freq参数来修改,默认间隔是D。例如,我们构建一系列以小时为间隔的时间戳:

pd.date_range('2015-07-03', periods=8, freq='H')
DatetimeIndex(['2015-07-03 00:00:00', '2015-07-03 01:00:00',
               '2015-07-03 02:00:00', '2015-07-03 03:00:00',
               '2015-07-03 04:00:00', '2015-07-03 05:00:00',
               '2015-07-03 06:00:00', '2015-07-03 07:00:00'],
              dtype='datetime64[ns]', freq='H')

为了创建Period和Timedelta值,可以使用函数pd.period_range()和pd.timedelta_range()。这里时一下月为周期的例子:

pd.period_range('2015-07', periods=8, freq='M')
PeriodIndex(['2015-07', '2015-08', '2015-09', '2015-10', '2015-11', '2015-12',
             '2016-01', '2016-02'],
            dtype='int64', freq='M')

按小时增加的持续时间序列:

pd.timedelta_range(0, periods=10, freq='H')
TimedeltaIndex(['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00',
                '05:00:00', '06:00:00', '07:00:00', '08:00:00', '09:00:00'],
               dtype='timedelta64[ns]', freq='H')

所有这些需要理解Pandas的频率代码,下一部分有我们的总结。

频率和偏移

这些Pandas时间序列工具的基础是频率或日期偏移概念。如同我们上面看到的D(天)和H(小时)代码,我们可以使用这类代码来指定任何想要的周期间隔。下表总结了可用的主要编码:

Code Description Code Description
D Calendar day B Business day
W Weekly
M Month end BM Business month end
Q Quarter end BQ Business quarter end
A Year end BA Business year end
H Hours BH Business hours
T Minutes
S Seconds
L Milliseonds
U Microseconds
N nanoseconds

月度,季度,年度频率标记在指定周期的后面。通过添加S后缀在周期后面,周期将从对应的第一天开始计算:

| Code | Description || Code | Description |
|---------|------------------------||---------|------------------------|
| MS | Month start ||BMS | Business month start |
| QS | Quarter start ||BQS | Business quarter start |
| AS | Year start ||BAS | Business year start |

此外,可以通过添加三个字母的月份代码为后缀来更改用于标记任何季度或年度代码的结束的最后月份:

  • Q-JAN, BQ-FEB, QS-MAR, BQS-APR, etc.
  • A-JAN, BA-FEB, AS-MAR, BAS-APR, etc.

同样的,周周期的分割点可以同添加三字母的工作日代码来改变:

  • W-SUN, W-MON, W-TUE, W-WED, etc.

在这之上,代码可以和数值相结合来指定其它周期。例如对于2小时30分钟,我们可以相如下这样结合小时(H)和分钟(T):

pd.timedelta_range(0, periods=9, freq="2H30T")
TimedeltaIndex(['00:00:00', '02:30:00', '05:00:00', '07:30:00', '10:00:00',
                '12:30:00', '15:00:00', '17:30:00', '20:00:00'],
               dtype='timedelta64[ns]', freq='150T')

所有这些短码都是指Pandas时间序列偏移的具体实例,它可以在pd.tseries.offsets模块中发现。例如,我们像如下这样创建一个工作日偏移:

from pandas.tseries.offsets import BDay
pd.date_range('2015-07-01', periods=5, freq=BDay())
DatetimeIndex(['2015-07-01', '2015-07-02', '2015-07-03', '2015-07-06',
               '2015-07-07'],
              dtype='datetime64[ns]', freq='B')

更多过于频率和偏移的用法讨论,参见Pandas文档的"DateOffset"部分

重采样、移位和窗口

使用日期和时间作为索引来直观的组织和访问数据的能力是Pandas时间管理工具重要功能。除了索引数据的一般用处(操作时自动对齐,按直觉的数据切片和访问),Pandas提供了几种额外的时间序列相关操作。
使用股票价格数据作为用例,我们来看其中的几个方法。因为Pandas大部分时在金融环境下开发的,它包含了一些特殊工具用于处理金融数据。例如,伴生的pandas-datareader包(通过conda install pandas-datareader来安装),知道如果从不同数据源(包括,雅虎金融,Google金融等)导入数据。这类我们导入的是谷歌收盘价记录:

from pandas_datareader import data

goog = data.DataReader('GOOG', start='2004', end='2016',
                       data_source='google')
goog.head()
            Open    High    Low     Close   Volume
Date                    
2004-08-19  49.96   51.98   47.93   50.12   NaN
2004-08-20  50.69   54.49   50.20   54.10   NaN
2004-08-23  55.32   56.68   54.47   54.65   NaN
2004-08-24  55.56   55.74   51.73   52.38   NaN
2004-08-25  52.43   53.95   51.89   52.95   NaN

为了简单起见,我们只使用收盘价:

goog = goog['Close']

在正常的设置好Matplotlib样板(参见Chapter 4)后,我们就可以使用plot()进行可视化了:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set()
goog.plot();
03.11-Working-with-Time-Series_110_0.png

重新采样和转换频率

一个对时间序列常见的需求是以更高或更低的频率进行采样。这个需求可以使用resample()方法或更简单的asfreq()方法来实现。两者之间的主要区别是resample()本质上是数据聚合,而asfreq()是数据选择。
看一眼谷歌收盘价格,让我们比较在数据下采样时两者的返回值。这里我们将都商业年末数据重新采样:

goog.plot(alpha=0.5, style='-')
goog.resample('BA').mean().plot(style=':')
goog.asfreq('BA').plot(style='--');
plt.legend(['input', 'resample', 'asfreq'],
           loc='upper left');
03.11-Working-with-Time-Series_113_0.png

注意区别:在每个点,resample报告的是前一年数据的均值,而asfreq报告的是当年的值。

对于上采样,resample()和asfreq()大体相当,尽管resample有更多的可用选项。在本例中,两种方法默认都是将上采样点置为空,置为NA值。如同之前pd。fillna()函数讨论过的,asfred()接收一个方法参数来指定如何插值。这里,我们将重新采样工作日为日频率(包括周末):

fig, ax = plt.subplots(2, sharex=True)
data = goog.iloc[:10]

data.asfreq('D').plot(ax=ax[0], marker='o')

data.asfreq('D', method='bfill').plot(ax=ax[1], style='-o')
data.asfreq('D', method='ffill').plot(ax=ax[1], style='--o')
ax[1].legend(["back-fill", "forward-fill"]);
03.11-Working-with-Time-Series_118_0.png

上面板是默认情况:非工作日设置为空,没有在图中显示出来。下面板显示两个填充策略的不同:前向填充和后向填充。

时间移动

另一个时间序列特殊操作是按时间移动数据。Pandas有两个紧密联系的方法用来计算移动:shift()和tshift(),两者之间的区别是shift()移动数据,而tshift()移动索引。两种情况下,移位都是以频率倍数来指定的。

fig, ax = plt.subplots(3, sharey=True)

# apply a frequency to the data
goog = goog.asfreq('D', method='pad')

goog.plot(ax=ax[0])
goog.shift(900).plot(ax=ax[1])
goog.tshift(900).plot(ax=ax[2])

# legends and annotations
local_max = pd.to_datetime('2007-11-05')
offset = pd.Timedelta(900, 'D')

ax[0].legend(['input'], loc=2)
ax[0].get_xticklabels()[2].set(weight='heavy', color='red')
ax[0].axvline(local_max, alpha=0.3, color='red')

ax[1].legend(['shift(900)'], loc=2)
ax[1].get_xticklabels()[2].set(weight='heavy', color='red')
ax[1].axvline(local_max + offset, alpha=0.3, color='red')

ax[2].legend(['tshift(900)'], loc=2)
ax[2].get_xticklabels()[1].set(weight='heavy', color='red')
ax[2].axvline(local_max + offset, alpha=0.3, color='red');
03.11-Working-with-Time-Series_123_0.png

我们看到shift(900)移动了900天的数据,把它的一些数据推到图表的结尾(并且在另一端留下空值),而tshift(900)移动了900天的索引数据。
这类移动的一般用于计算基于时间的不同。例如,我们使用移动数据来计算按年的谷歌股票投资回报:

ROI = 100 * (goog.tshift(-365) / goog - 1)
ROI.plot()
plt.ylabel('% Return on Investment');
03.11-Working-with-Time-Series_126_0.png

这帮我们看清Google股票的整体趋势:截止目前为止,投资Google最赚钱的时间是其IPO之后,和2009衰退中期。

移动窗口

移动统计是Pandas实现的第三类特殊时间序列操作。它可用公共Series和DataFrame对象属性的rolling()函数来实现,该函数返回值同我们见到的groupby操作(见Aggregation and Grouping).很类似。
移动视图有几个默认的聚合操作。
例如,这是谷歌股票历年价格窗口中心移动均值和标准差。

rolling = goog.rolling(365, center=True)

data = pd.DataFrame({'input': goog,
                     'one-year rolling_mean': rolling.mean(),
                     'one-year rolling_std': rolling.std()})
ax = data.plot(style=['-', '--', ':'])
ax.lines[0].set_alpha(0.3)
03.11-Working-with-Time-Series_131_0.png

同group-by操作一样,aggregate()和apply()方法也可以被用来定制移动窗口计算。

进一步学习

这部分提供了对Pandas时间序列工具种最基本功能的简要总结;想要更完整的讨论,参见Pandas在线文档"Time Series/Date" section
另一个优秀的资源是Wes McKinney写的Python for Data Analysis。虽然距现在已经几年了,这本书是使用Pandas及其重要的资源。特别的该书强调了时间序列工具在商业和金融环境中的使用,并且特别关注了商业日历,时区等主题的细节。
一如往常,你可以使用Ipython的帮助功能来探索和尝试这里面讨论的功能和函数的可用选项。我发现这经常是学习Python新工具最后的方式。

例子:可视化西雅图自行车数量

作为一个更多处理时间序列数据的例子,让我们看看西雅图 Fremont桥自行车通过数量。数据来自于自动自行车计数器,它于2012年下半年安装,在桥的东西人行道有感应传感器。小时自行车数量可用从http://data.seattle.gov/下载。
CSV文件可用通过如下方式下载:

# !curl -o FremontBridge.csv https://data.seattle.gov/api/views/65db-xm6k/rows.csv?accessType=DOWNLOAD

数据集下载完毕后,我们使用Pandas来读取CSV文件生成一个DataFrame。我们指定时间作为索引,并且希望日期被自动解析:

data = pd.read_csv('FremontBridge.csv', index_col='Date', parse_dates=True)
data.head()
      Fremont Bridge West Sidewalk  Fremont Bridge East Sidewalk
Date        
2012-10-03 00:00:00     4.0               9.0
2012-10-03 01:00:00     4.0               6.0
2012-10-03 02:00:00     1.0               1.0
2012-10-03 03:00:00     2.0               3.0
2012-10-03 04:00:00     6.0               1.0

为了方便,我们进一步处理数据集,缩短列名称,添加一个"Total"列:

data.columns = ['West', 'East']
data['Total'] = data.eval('West + East')

现在让我们看看数据的总结统计:

data.dropna().describe()
            West            East          Total
count   35752.000000    35752.000000    35752.000000
mean    61.470267           54.410774   115.881042
std       82.588484         77.659796   145.392385
min       0.000000          0.000000    0.000000
25%       8.000000          7.000000    16.000000
50%       33.000000         28.000000   65.000000
75%       79.000000         67.000000   151.000000
max       825.000000        717.000000  1186.000000

可视化数据

通过可视化我们可以获取对数据集更多的了解。让我们由画原始数据开始:

%matplotlib inline
import seaborn; seaborn.set()
data.plot()
plt.ylabel('Hourly Bicycle Count');
03.11-Working-with-Time-Series_151_0.png

约25,000小时的采样太密集了。我们可以通过粗粒度重新采样数据来看得更清楚.让我们按周统计:

weekly = data.resample('W').sum()
weekly.plot(style=[':', '--', '-'])
plt.ylabel('Weekly bicycle count');
03.11-Working-with-Time-Series_154_0.png

这里显示出一些有趣的季节趋势:如你所想,夏天人们骑自行车比冬天多,并且即使在同样季节,每周的使用量也不同。
另一种手动聚合数据的方式是利用pd.rolling_mean()函数来做rolling平均。这里做一个30天的数据rolling平均,确保中心窗口:

daily = data.resample('D').sum()
daily.rolling(30, center=True).sum().plot(style=[':', '--', '-'])
plt.ylabel('mean hourly count');
03.11-Working-with-Time-Series_157_0.png

结果的锯齿状是由于窗口的硬切断。我们可以使用窗口函数(如Gaussian窗口)来得到一个平滑rolling平均版本。下面的代码既指定了窗口宽度(50天)也指定了窗口内的高斯宽度(10天)

daily.rolling(50, center=True,
              win_type='gaussian').sum(std=10).plot(style=[':', '--', '-']);
03.11-Working-with-Time-Series_160_0.png

挖掘数据

这些光滑的视图对于获得数据大体趋势很有用,但它们隐藏了许多有趣的细节。例如,我想要看一下每天平均交通量。我们可以使用在Aggregation and Grouping讨论的Groupby功能

by_time = data.groupby(data.index.time).mean()
hourly_ticks = 4 * 60 * 60 * np.arange(6)
by_time.plot(xticks=hourly_ticks, style=[':', '--', '-']);

小时交通量是典型的双峰分布,峰值在早上8点和晚上5点。这是跨桥通勤证据的重要组成部分。这也进一步证明了西人行道和东人行道的不同:前者(通常是去市中心)峰值时在早晨,后者(通常时离开市中心),峰值在晚上。
我们好奇的是每周的情况是怎样。再次,我们使用简单的groupby:

by_weekday = data.groupby(data.index.dayofweek).mean()
by_weekday.index = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']
by_weekday.plot(style=[':', '--', '-']);
03.11-Working-with-Time-Series_166_0.png

这显示了工作日与周末的巨大区别,周一到周五的骑行人数是周末的二倍。
记住这些,我们来做一个符合的GroupBy来看一下工作日对周末的小时趋势。我们使用一个标签来标记分组周末和其它工作日:

weekend = np.where(data.index.weekday < 5, 'Weekday', 'Weekend')
by_time = data.groupby([weekend, data.index.time]).mean()

现在我们使用Matplotlib工具Multiple Subplots来并排的绘制两个面板:

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(14, 5))
by_time.ix['Weekday'].plot(ax=ax[0], title='Weekdays',
                           xticks=hourly_ticks, style=[':', '--', '-'])
by_time.ix['Weekend'].plot(ax=ax[1], title='Weekends',
                           xticks=hourly_ticks, style=[':', '--', '-']);
03.11-Working-with-Time-Series_172_0.png

结果非常有趣:我们看到在工作日是一个双峰通勤模型,在周末是一个单峰游憩模式。发掘数据中更多细节会很有趣,并且检查天气,温度,一年中时间,及其它因素对人们出行模式的影响;对于进一步讨论,参见博客 "Is Seattle Really Seeing an Uptick In Cycling?"

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容