數據科學 IPython 筆記本 7.14 處理時間序列

7.14 處理時間序列

原文:Working with Time Series

譯者:飛龍

協議:CC BY-NC-SA 4.0

本節是《Python 數據科學手冊》(Python Data Science Handbook)的摘錄。

Pandas 是在金融建模的背景下開發的,正如你所料,它包含一組相當廣泛的工具,用于處理日期,時間和時間索引數據。日期和時間數據有幾種,我們將在這里討論:

  • 時間戳:引用特定時刻(例如,2015 年 7 月 4 日上午 7:00)。
  • 時間間隔(interval)和時間段:引用特定開始和結束點之間的時間長度;例如,2015 年。時間段通常引用時間間隔的特殊情況,其中每個間隔具有統一的長度并且不重疊(例如,構成每天的 24 小時長的時間段)。
  • 時間增量或間隔(duration):引用確切的時間長度(例如,間隔為 22.56 秒)。

在本節中,我們將介紹如何在 Pandas 中使用這些類型的日期/時間數據。這個簡短的章節絕不是 Python 或 Pandas 中可用的時間序列工具的完整指南,而是用戶應如何處理時間序列的廣泛概述。

我們將首先簡要討論 Python 中處理日期和時間的工具,然后再更具體地討論 Pandas 提供的工具。在列出了一些更深入的資源之后,我們將回顧一些在 Pandas 中處理時間序列數據的簡短示例。

Python 中的日期和時間

Python 世界有許多可用的日期,時間,增量和時間跨度表示。雖然 Pandas 提供的時間序列工具往往對數據科學應用最有用,但查看它們與 Python 中使用的其他包的關系會很有幫助。

Python 原生日期和時間:datetimedateutil

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 datetime文檔的strftime部分。其他有用的日期工具的文檔,可以在dateutil的在線文檔中找到。需要注意的一個相關包是pytz,其中包含用于處理時區的工具,它是大部分時間序列數據的令人頭疼的部分。

datetimedateutil的強大之處,是它們的靈活性和簡單的語法:你可以使用這些對象及其內置方法,輕松執行你可能感興趣的幾乎任何操作。
他們的缺陷是當你處理大量的日期和時間的時候:

正如 Python 數值變量的列表不如 NumPy 風格的數值數組,與編碼日期的類型化數組相比,Python 日期時間對象的列表不是最優的。

時間的類型化數組:NumPy 的datetime64

Python 的日期時間格式的缺陷,啟發了 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對象快得多,特別是當數組變大時(我們在“NumPy 數組的計算:通用函數”中介紹了這種類型的向量化)。

datetime64timedelta64對象的一個細節是,它們建立在基本時間單位上。因為datetime64對象限制為 64 位精度,所以可編碼時間的范圍是這個基本單位的2^64倍。換句話說,datetime64在時間分辨率和最大時間跨度之間進行權衡。

例如,如果你想要納秒的時間分辨率,你只有足夠的信息來編碼2^64納秒或不到 600 年的范圍。NumPy 將從輸入中推斷出所需的單位;例如,這是基于日期的日期時間:

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

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

這是基于分鐘的日期時間:

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文檔,列出了可用的格式代碼,以及它們可以編碼的相對和絕對時間跨度:

代碼 含義 時間跨度(相對) 時間跨度(絕對)
Y ± 9.2e18 年 [9.2e18 BC, 9.2e18 AD]
M ± 7.6e17 年 [7.6e17 BC, 7.6e17 AD]
W 星期 ± 1.7e17 年 [1.7e17 BC, 1.7e17 AD]
D ± 2.5e16 年 [2.5e16 BC, 2.5e16 AD]
h 小時 ± 1.0e15 年 [1.0e15 BC, 1.0e15 AD]
m 分鐘 ± 1.7e13 年 [1.7e13 BC, 1.7e13 AD]
s 秒鐘 ± 2.9e12 年 [ 2.9e9 BC, 2.9e9 AD]
ms 毫秒 ± 2.9e9 年 [ 2.9e6 BC, 2.9e6 AD]
us 微秒 ± 2.9e6 年 [290301 BC, 294241 AD]
ns 納秒 ± 292 年 [ 1678 AD, 2262 AD]
ps 皮秒 ± 106 天 [ 1969 AD, 1970 AD]
fs 飛秒 ± 2.6 小時 [ 1969 AD, 1970 AD]
as 阿秒 ± 9.2 秒 [ 1969 AD, 1970 AD]

對于我們在現實世界中看到的數據類型,有用的默認值是datetime64[ns],因為它可以編碼現代日期的有用范圍,具有相當好的精度。

最后,我們將注意到,雖然datetime64數據類型解決了 Python 內置datetime類型的一些缺陷,但它缺少datetime提供的許多便利方法和函數。特別是dateutil。更多信息可以在 NumPy 的datetime64文檔中找到。

Pandas 中的日期和時間:兩全其美

例如,我們可以使用 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類型。 Timedelta是 Python 原生datetime.timedelta類型的更有效的替代品,它基于numpy.timedelta64。相關的索引結構是TimedeltaIndex

這些日期/時間對象中,最基本的是TimestampDatetimeIndex對象。雖然可以直接調用這些類對象,但更常見的是使用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')
'''

要創建PeriodTimedelta值的常規序列,非常相似的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(小時)代碼一樣,我們可以使用這些代碼來指定任何所需的頻率間隔。下表總結了可用的主要代碼:

代碼 描述 代碼 描述
D 日歷日 B 商業日
W 星期
M 月份 BM 商業月份
Q 季度 BQ 商業季度
A 年度 BA 商業年度
H 小時 BH 商業小時
T 分鐘
S 秒鐘
L 毫秒
U 微秒
N 納秒

月度,季度和年度的頻率都標記在指定時間段的末尾。通過為這些中的任何一個添加S后綴,它們將在開頭標記:

代碼 描述 代碼 描述
MS 月份的起始 BMS 商業月份的起始
QS 季度的起始 BQS 商業季度的起始
AS 年度的起始 BAS 商業年度的起始

此外,你可以通過添加三個字母的月份代碼作為后綴,來更改用于標記任何季度或年度代碼的月份:

  • Q-JANBQ-FEBQS-MARBQS-APR,以及其他。
  • A-JANBA-FEBAS-MARBAS-APR,以及其他。

同樣,可以通過添加三個字母的星期代碼,來修改每周頻率的分割點:

  • W-SUNW-MONW-TUEW-WED,以及其他。

除此之外,代碼可以與數字組合以指定其他頻率。例如,對于 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')
'''
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 文檔的“日期偏移”部分。

重采樣,平移和窗口化

使用日期和時間作為索引,來直觀地組織和訪問數據的能力,是 Pandas 時間序列工具的重要組成部分。一般情況下,索引數據的優勢(操作期間的自動對齊,直觀的數據切片和訪問等)仍然有效,并且 Pandas 提供了一些額外的時間序列特定的操作。

我們將以一些股票價格數據為例,看看其中的一些。由于 Pandas 主要是在金融環境中開發的,因此它包含一些非常具體的金融數據工具。

例如,附帶的pandas-datareader包(可通過conda install pandas-datareader安裝)知道如何從許多可用來源導入金融數據,包括 Yahoo finance,Google Finance 等。在這里,我們將加載 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 樣板設置之后,我們可以使用plot()方法將其可視化(參見第四章)):

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set()
goog.plot();
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');
png

注意區別:在每一點,resample報告前一年的平均值,而asfreq報告年末的值。

對于上采樣,resample()asfreq()在很大程度上是等效的,盡管resample有更多可用的選項。在這種情況下,兩種方法的默認設置是將上采樣點留空,即填充 NA 值。就像之前討論過的pd.fillna()函數一樣,asfreq()接受一個method參數來指定值的估算方式。在這里,我們將以每日頻率(即包括周末)重新采樣商業日數據:

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"]);
png

頂部面板是默認值:非工作日保留為 NA 值,并且不會顯示在圖表上。底部面板顯示填補空白的兩種策略之間的差異:向前填充和向后填充。

時間平移

另一種常見的時間序列特定的操作是按時間平移數據。Pandas 有兩個密切相關的計算方法:shift()tshift()。簡而言之,它們之間的區別在于,shift()平移數據,而tshift()平移索引。在這兩種情況下,平移都指定為頻率的倍數。

在這里,我們使用shift()tshift()來平移 900 天;

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

# 對數據應用頻率
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])

# 圖例和注解
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');
png

我們在這里看到shift(900)將數據移動 900 天,將其中的一些移出圖的末尾(并在另一端留下 NA 值),而``tshift(900)將索引移動 900 天。

這種類型轉換的常見背景,是計算隨時間的差異。 例如,我們使用移位值來計算 Google 股票在數據集過程中的一年投資回報:

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

這有助于我們看到谷歌股票的總體趨勢:到目前為止,投資谷歌的最有利的時期(回想一下,不出所料)在其 IPO 后不久,以及在 2009 年中期經濟衰退期間。

滾動窗口

滾動統計量是 Pandas 實現的第三種時間序列特定的操作。
這些可以通過SeriesDataFrame對象的rolling()屬性來完成,它返回一個視圖,類似于我們在groupby操作中看到的東西(參見“聚合和分組”)。這個滾動視圖默認提供許多聚合操作。

例如,以下是 Google 股票價格的一年中心化滾動均值和標準差:

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

與分組操作一樣,aggregate()apply()方法可用于自定義滾動計算。

在哪里了解更多

本節僅簡要概述了 Pandas 提供的時間序列工具的一些最基本功能;更完整的討論請參閱 Pandas 在線文檔的“時間序列/日期”部分。另一個優秀的資源是 Wes McKinney 的書籍《利用 Python 進行數據分析》(Python for Data Analysis (OReilly 2012))。

雖然現在已有幾年歷史,但它是 Pandas 用法的寶貴資源。特別是,本書重點講解商業和金融環境中的時間序列工具,并更多地關注商業日歷,時區和相關主題的特定細節。

與往常一樣,你也可以使用 IPython 幫助功能,來探索和嘗試可用于此處討論的函數和方法的更多選項。 我發現這通常是學習新 Python 工具的最佳方式。

示例:可視化西雅圖自行車數量

作為處理時間序列數據的一個更為復雜的例子,讓我們來看看西雅圖Fremont Bridge的自行車數量。這些數據來自于 2012 年底安裝的自動化自行車計數器,在橋的東西側人行道上設有感應式傳感器。每小時自行車計數可以從 http://data.seattle.gov/ 下載;這是數據集的直接鏈接

截至 2016 年夏季,CSV 可以按如下方式下載:

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

下載此數據集后,我們可以使用 Pandas 將 CSV 讀入DataFrame。我們將指定,我們希望Date作為索引,并且我們希望自動解析這些日期:

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');
png

大約 25,000 小時的樣本太密集了,我們無法理解。我們可以通過將數據重采樣到更粗糙的網格,來獲得更多見解。讓我們按周重采樣:

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

這向我們展示了一些有趣的季節性趨勢:正如你所料,人們在夏天騎自行車比冬季更多,甚至在特定的季節內,自行車的使用每周也不同(可能取決于天氣;參見“深度:線性回歸”,我們在那里進一步探索它)。

另一種方便的匯總數據的方法是滾動均值,使用pd.rolling_mean()函數。在這里,我們將對數據進行 30 天的滾動操作,確保窗口居中:

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

結果的鋸齒狀是由于窗口的硬截斷造成的。我們可以使用窗口函數(例如,高斯窗口)獲得更平滑的滾動平均版本。以下代碼指定了窗口的寬度(我們選擇了 50 天)和窗口內的高斯寬度(我們選擇了 10 天):

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

深挖數據

雖然這些平滑的數據視圖對于了解數據的總體趨勢很有用,但它們隱藏了許多有趣的結構。例如,我們可能希望,將平均流量視為一天中的時間的函數。我們可以使用“聚合和分組”中討論的GroupBy功能來執行此操作:

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

每小時流量是一個強烈的雙峰分布,早上 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=[':', '--', '-']);
png

這顯示了工作日和周末數量之間的強烈差異,周一至周五過橋的平均騎手數量是周六和周日的兩倍。

考慮到這一點,讓我們執行復合的GroupBy,看一下工作日和周末的每小時趨勢。我們首先按照標記周末的標志,和一天中的時間分組:

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

現在我們將使用“多個子圖”中描述的一些 Matplotlib 工具,來并排繪制兩個面板:

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=[':', '--', '-']);
png

結果非常有趣:我們在工作日期間看到雙峰通勤模式,在周末看到單峰休閑模式。更詳細地挖掘這些數據,并檢查天氣,溫度,一年中的時間,以及其他因素對人們通勤模式的影響,將會很有趣;進一步的討論請參閱我的博客文章“Is Seattle Really Seeing an Uptick In Cycling?”,它使用這些數據的一個子集。我們還將在“深入:線性回歸”中的建模環境中,回顧這個數據集。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380