【经典必看】时光印记:Pandas 时间序列处理的奥秘与实践
处理时间序列
Pandas 最初是在金融建模的背景下开发的,因此,正如你可能猜到的,它包含了一整套处理日期、时间和时间索引数据的工具。日期和时间数据有几种不同的形式,我们将在以下内容中讨论这些形式:
- 时间戳:指的是时间线上的特定时刻(例如,2021年7月4日上午7:00)。
- 时间间隔和周期:指的是从一个特定的开始点到结束点之间的一段时长;例如,2021年6月。周期通常指的是时间间隔的一个特例,每个间隔长度相同并且没有重叠(例如,由24小时组成的一天)。
- 时间差或持续时间:指的是一个确切的时长(例如,22.56秒的持续时间)。
本章将介绍如何在 Pandas 中处理这三种类型的时间/日期数据。这绝不是 Python 或 Pandas 中时间序列工具的完整指南,而是旨在为你提供一个广泛概览,介绍作为用户应该如何处理时间序列数据。我们将从简要讨论 Python 中处理日期和时间的工具开始,然后更具体地讨论 Pandas 提供的工具。在列出一些提供深度学习资源后,我们将回顾一些在 Pandas 中处理时间序列数据的简短示例。
Python中的日期和时间
Python 有自己的日期、时间、时间差和时间跨度表示方法。虽然 Pandas 提供的时间序列工具对于数据科学应用程序最为有用,但了解它们与其他 Python 工具的关系也很有帮助。
原生Python日期和时间:datetime和dateutil
Python 用于处理日期和时间的基本对象位于内置的 datetime 模块中。与第三方模块 dateutil 一起使用,你可以快速执行一系列有用的日期和时间操作。例如,你可以使用 datetime 类型手动构建一个日期:
from datetime import datetime
datetime(year=2021, month=7, day=4)
datetime.datetime(2021, 7, 4, 0, 0)
或者,使用 dateutil 模块,你可以从多种字符串格式解析日期:
from dateutil import parser
date = parser.parse("4th of July, 2021")
date
datetime.datetime(2021, 7, 4, 0, 0)
一旦你有了 datetime 对象,你就可以执行打印星期几等操作:
date.strftime('%A')
'Sunday'
这里我们使用了日期打印的标准字符串格式代码之一(‘%A’),你可以在 Python 的 datetime 文档的 strftime 部分阅读更多相关内容。其他有用日期工具的文档可以在 dateutil 的在线文档中找到。另一个需要注意的相关包是 pytz,它包含处理时间序列数据中最令人头痛的元素——时区的工具。
datetime 和 dateutil 的核心优势在于它们的灵活性和简单的语法:你可以使用这些对象及其内建方法轻松执行几乎所有你可能感兴趣的日期/时间操作。然而,当你需要处理大型日期和时间数组时,它们可能会显得能力不足:正如 Python 数字变量列表相对于 NumPy 样式的类型化数字数组来说是次优选择一样,Python datetime 对象列表相对于类型化的编码日期数组也是次优选择。
类型化的时间数组:NumPy的datetime64
NumPy 的 datetime64 数据类型将日期编码为 64 位整数,因此可以紧凑表示且高效操作日期数组。datetime64 需要特定的输入格式:
import numpy as np
date = np.array('2021-07-04', dtype=np.datetime64)
date
array('2021-07-04', dtype='datetime64[D]')
一旦我们以这种形式表示日期,就可以快速对其执行向量化操作:
date + np.arange(12)
array(['2021-07-04', '2021-07-05', '2021-07-06', '2021-07-07',
'2021-07-08', '2021-07-09', '2021-07-10', '2021-07-11',
'2021-07-12', '2021-07-13', '2021-07-14', '2021-07-15'],
dtype='datetime64[D]')
由于 NumPy datetime64 数组中是统一类型,这类操作要比直接操作 Python 的 datetime 对象快得多,尤其是数组非常大时(我们在“NumPy数组上的计算:通用函数”章节中介绍了这种向量化)。
datetime64 及相关的 timedelta64 对象的一个细节是它们基于一个基础时间单位构建。因为 datetime64 对象的精度限制在 64 位,所以可以编码的时间范围是
基础单位的
倍。换句话说,datetime64 强制了一个时间分辨率与最大时间跨度之间的权衡。
例如,如果你想获得 1 纳秒的时间分辨率,你只能表示大约
纳秒的时间范围,或者不到 600 年。NumPy 会根据输入推断所需的单位;例如,这是一个基于天的日期时间:
np.datetime64('2021-07-04')
numpy.datetime64('2021-07-04')
这是一个基于分钟的日期时间:
np.datetime64('2021-07-04 12:00')
numpy.datetime64('2021-07-04T12:00')
你可以使用许多格式代码之一来强制任何所需的基础单位;例如,这里我们将时间强制为基于纳秒:
np.datetime64('2021-07-04 12:59:59.50', 'ns')
numpy.datetime64('2021-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 在上述所有工具的基础上,提供了一个 Timestamp 对象,它结合了 datetime 和 dateutil 的易用性,以及 numpy.datetime64 的高效存储和向量化接口。从一组这些 Timestamp 对象,Pandas 可以构建一个可以用于索引 Series 或 DataFrame 中数据的 DatetimeIndex。
例如,我们可以使用 Pandas 工具重复上述演示。我们可以解析格式灵活的字符串日期,并使用格式化代码输出星期几,如下所示:
import pandas as pd
date = pd.to_datetime("4th of July, 2021")
date
Timestamp('2021-07-04 00:00:00')
date.strftime('%A')
'Sunday'
此外,我们还可以直接在这个对象上执行 NumPy 风格的向量化操作:
date + pd.to_timedelta(np.arange(12), 'D')
DatetimeIndex(['2021-07-04', '2021-07-05', '2021-07-06', '2021-07-07',
'2021-07-08', '2021-07-09', '2021-07-10', '2021-07-11',
'2021-07-12', '2021-07-13', '2021-07-14', '2021-07-15'],
dtype='datetime64[ns]', freq=None)
在下一节中,我们将更详细地讨论如何使用 Pandas 工具来处理时间序列数据。
Pandas时间序列:按时间索引
当开始按照时间戳索引数据时,Pandas 的时间序列工具才真正变得有用。例如,我们可以构建一个具有时间索引数据的 Series 对象:
index = pd.DatetimeIndex(['2020-07-04', '2020-08-04',
'2021-07-04', '2021-08-04'])
data = pd.Series([0, 1, 2, 3], index=index)
data
2020-07-04 0
2020-08-04 1
2021-07-04 2
2021-08-04 3
dtype: int64
现在我们有了这个时间序列数据,我们可以利用前几章中讨论的 Series 索引模式进行操作,传递可以转换成日期的值:
data['2020-07-04':'2021-07-04']
2020-07-04 0
2020-08-04 1
2021-07-04 2
dtype: int64
此外,还有一些特殊的仅日期索引操作,例如传递一个年份可以获得该年份的所有数据:
data['2021']
2021-07-04 2
2021-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
。
这些时间/日期对象中最基本的是 Timestamp
和 DatetimeIndex
对象。虽然这些类对象可以直接调用,但更常见的是使用 pd.to_datetime
函数,该函数可以解析多种格式。例如:
dates = pd.to_datetime([datetime(2021, 7, 3), '4th of July, 2021',
'2021-Jul-6', '07-07-2021', '20210708'])
dates
DatetimeIndex(['2021-07-03', '2021-07-04', '2021-07-06', '2021-07-07',
'2021-07-08'],
dtype='datetime64[ns]', freq=None)
任何 DatetimeIndex
都可以使用 to_period
函数转换为 PeriodIndex
,并且需要添加一个频率代码。例如,我们将日期转换为每日频率:
dates.to_period('D')
PeriodIndex(['2021-07-03', '2021-07-04', '2021-07-06', '2021-07-07',
'2021-07-08'],
dtype='period[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
参数,可以调整间距。例如,我们可以创建一个每小时的时间戳序列:
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')
频率和偏移
Pandas 时间序列工具的核心概念是频率或日期偏移。下表总结了主要的频率代码;与之前提到的每日(D)和每小时(H)代码一样,我们可以使用这些代码来指定任何所需的频率间距:
代码 | 描述 | 代码 | 描述 |
---|---|---|---|
D | 日历日 | B | 工作日 |
W | 周 | M | 月末 |
Q | 季末 | BQ | 工作季末 |
A | 年末 | BA | 工作年末 |
H | 小时 | BH | 工作小时 |
T | 分钟 | S | 秒 |
L | 毫秒 | U | 微秒 |
N | 纳秒 |
每月、每季和每年的频率都标记在指定时期的末尾。在这些代码后添加一个 S 后缀可以将它们标记在开头:
代码 | 描述 | 代码 | 描述 |
---|---|---|---|
MS | 月初 | BMS | 工作月初 |
QS | 季初 | BQS | 工作季初 |
AS | 年初 | BAS | 工作年初 |
此外,可以在任何季度或年度代码后添加一个三位数的月份代码来指定不同的月份:
- Q-JAN, BQ-FEB, QS-MAR, BQS-APR 等
- A-JAN, BA-FEB, AS-MAR, BAS-APR 等
还可以通过添加数字和多个代码来指定其他频率。例如,每隔2小时30分钟的频率可以组合小时(H)和分钟(T)代码来实现:
pd.timedelta_range(0, periods=6, freq="2H30T")
TimedeltaIndex(['0 days 00:00:00', '0 days 02:30:00', '0 days 05:00:00',
'0 days 07:30:00', '0 days 10:00:00', '0 days 12:30:00'],
dtype='timedelta64[ns]', freq='150T')
所有这些短代码都指的是 Pandas 时间序列偏移的具体实例,这些实例可以在 pd.tseries.offsets
模块中找到。例如,我们可以直接创建一个工作日偏移:
from pandas.tseries.offsets import BDay
pd.date_range('2015-07-01', periods=6, freq=BDay())
DatetimeIndex(['2015-07-01', '2015-07-02', '2015-07-03', '2015-07-06',
'2015-07-07', '2015-07-08'],
dtype='datetime64[ns]', freq='B')
有关频率和偏移的更详细讨论,请参阅 Pandas 文档中的 DateOffset 部分。
重采样、移位和窗口操作
在处理时间序列数据时,一个常见的需求是重新采样为更高的或更低的频率。这可以通过 resample
方法或更简单的 asfreq
方法来完成。两者的区别在于,resample
本质上是一个数据聚合操作,而 asfreq
是一个数据选择操作。
让我们比较一下当我们将 S&P 500 收盘价格数据降采样到每个业务年末时,两者返回的结果有何不同。例如,我们将数据重新采样为每个业务年末;结果如下图所示:
from pandas_datareader import data
sp500 = data.DataReader('^GSPC', start='2018', end='2022',
data_source='yahoo')
sp500.head()
For simplicity, we’ll use just the closing price:
sp500 = sp500['Close']
我们可以使用 plot
方法来可视化这一点,在进行正常的 Matplotlib 设置之后(参见第 4 部分);结果如以下图所示:
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
sp500.plot();
重采样和转换频率
在处理时间序列数据时,一个常见的需求是在更高的或更低的频率下重采样。这可以通过使用 resample
方法或更简单的 asfreq
方法来完成。两者之间的主要区别在于,resample
本质上是一种数据聚合,而 asfreq
本质上是一种数据选择。
让我们比较一下在将标准普尔 500 指数收盘价数据降采样时,这两种方法的返回结果。这里我们将数据重采样到每个营业年末;下图显示了结果:
sp500.plot(alpha=0.5, style='-')
sp500.resample('BA').mean().plot(style=':')
sp500.asfreq('BA').plot(style='--');
plt.legend(['input', 'resample', 'asfreq'],
loc='upper left');
注意区别:在每个点,resample
报告的是前一年的平均值,而 asfreq
报告的是年末的值。
在升采样时,resample
和 asfreq
大致等效,但 resample
提供了更多的选项。在这种情况下,默认情况下,两种方法都会将升采样的点留空,即用 NA 值填充。像 pd.fillna
函数(参见 Part 4: 处理缺失数据)一样,asfreq
接受一个 method
参数来指定值的插补方式。例如,我们将业务日数据升采样为每日频率(即包括周末);结果如下图所示:
fig, ax = plt.subplots(2, sharex=True)
data = sp500.iloc[:20]
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"]);
由于 S&P 500 数据仅在业务日存在,顶部面板中存在代表 NA 值的空隙。底部面板显示了填补空隙的两种策略:向前填充和向后填充。
时间移位
另一个常见的时间序列操作是数据的时间移位。Pandas 提供了 shift
方法,可以用于将数据按给定的条目数移位。对于定期采样的时间序列数据,这可以帮助我们探索随时间变化的趋势。
例如,我们将数据重新采样为每日值,并将数据向后移位 364 天,以计算 S&P 500 在时间上的年度投资回报率(结果如下图所示):
sp500 = sp500.asfreq('D', method='pad')
ROI = 100 * (sp500.shift(-365) - sp500) / sp500
ROI.plot()
plt.ylabel('% Return on Investment after 1 year');
最糟糕的一年回报率出现在 2019 年 3 月左右,新冠病毒相关市场崩盘恰好在一年后。正如你可能预料到的,最好的一年回报率出现在 2020 年 3 月,对于那些有足够预见力或运气在低位买入的人来说。
滚动窗口
计算滚动统计量是 Pandas 实现的第三种时间序列特定操作。这可以通过 Series
和 DataFrame
对象的 rolling
属性来完成,它返回一个类似于 groupby
操作的视图(参见聚合和分组)。这个滚动视图提供了许多默认的聚合操作。
例如,我们可以查看股票价格的一年中心滚动均值和中位数(结果如下图所示):
rolling = sp500.rolling(365, center=True)
data = pd.DataFrame({'input': sp500,
'one-year rolling_mean': rolling.mean(),
'one-year rolling_median': rolling.median()})
ax = data.plot(style=['-', '--', ':'])
ax.lines[0].set_alpha(0.3)
与 groupby
操作类似,aggregate
和 apply
方法可以用于自定义滚动计算。
进一步学习的资源
本章仅提供了 Pandas 提供的时间序列工具中一些最核心功能的简要概述;如需更深入的讨论,可以参考 Panda 在线文档的“时间序列/日期功能”部分。
另一个极佳的资源是 Wes McKinney(O’Reilly 出版社)撰写的《Python for Data Analysis》这本书。它是使用 Pandas 的宝贵资源,特别强调了在商业和金融上下文中使用时间序列工具,并更专注于商业日历、时区等主题的具体细节。
当然,您也可以使用 IPython 的帮助功能来探索和尝试这里讨论的函数和方法所提供的更多选项。我发现,这常常是学习新的 Python 工具的最佳方式。
示例:西雅图自行车计数可视化
作为一个更深入的时间序列数据处理示例,让我们来看看西雅图弗里蒙特桥的自行车计数情况。这些数据来自 2012 年底安装的自动自行车计数器,该计数器在桥的东西两侧人行道上配备了感应传感器。每小时的自行车计数可以从 http://data.seattle.gov 下载;弗里蒙特桥自行车计数数据集可在交通类别下找到。
本书中使用的 CSV 文件可如下下载:
# url = ('https://raw.githubusercontent.com/jakevdp/'
# 'bicycle-data/main/FremontBridge.csv')
# !curl -O {url}
一旦下载了这个数据集,我们可以使用 Pandas 将 CSV 输出读取到 DataFrame 中。我们将指定 Date 列作为索引,并希望这些日期能被自动解析:
data = pd.read_csv('FremontBridge.csv', index_col='Date', parse_dates=True)
data.head()
Fremont Bridge Total | Fremont Bridge East Sidewalk | Fremont Bridge West Sidewalk |
---|---|---|
Date | ||
2019-11-01 00:00:00 | 12.0 | 7.0 |
2019-11-01 01:00:00 | 7.0 | 0.0 |
2019-11-01 02:00:00 | 1.0 | 0.0 |
2019-11-01 03:00:00 | 6.0 | 6.0 |
2019-11-01 04:00:00 | 6.0 | 5.0 |
为了方便,我们将缩短列名:
data.columns = ['Total', 'East', 'West']
现在让我们来看一下这些数据的汇总统计信息:
data.dropna().describe()
Total | East | West |
---|---|---|
count | 147255.000000 | 147255.000000 |
mean | 110.341462 | 50.077763 |
std | 140.422051 | 64.634038 |
min | 0.000000 | 0.000000 |
25% | 14.000000 | 6.000000 |
50% | 60.000000 | 28.000000 |
75% | 145.000000 | 68.000000 |
max | 1097.000000 | 698.000000 |
可视化数据
通过可视化数据,我们可以更深入地了解数据集。让我们从绘制原始数据开始(见下图):
data.plot()
plt.ylabel('每小时自行车计数');
大约 15 万个每小时样本过于密集,我们难以从中得出太多有意义的结论。我们可以通过将数据重采样到更粗的网格上来获得更多的见解。让我们按周重采样(见下图):
weekly = data.resample('W').sum()
weekly.plot(style=['-', ':', '--'])
plt.ylabel('每周自行车计数');
这揭示了一些趋势:正如你所料,夏季的自行车使用量比冬季多,即使在特定季节内,自行车的使用量也从周到周有所不同(可能与天气有关;见深入讨论:线性回归,我们将在其中进一步探讨)。此外,COVID-19 疫情对通勤模式的影响自 2020 年初开始十分明显。
对数据进行聚合的另一个有用的方法是使用滚动平均值,利用 pd.rolling_mean
函数。这里我们将检查 30 天的滚动平均值,并确保窗口居中(见下图):
daily = data.resample('D').sum()
daily.rolling(30, center=True).sum().plot(style=['-', ':', '--'])
plt.ylabel('每小时平均计数');
由于窗口硬截止,结果呈现锯齿状。我们可以通过使用窗口函数(例如高斯窗口)来获得更平滑的滚动平均值版本,如下面的图所示。下面的代码指定了窗口的宽度(这里为 50 天)和高斯窗口的宽度(这里为 10 天):
daily.rolling(50, center=True, win_type='gaussian').sum(std=10).plot(style=['-', ':', '--']);
深入分析数据
虽然这些平滑的数据视图有助于了解数据的一般趋势,但它们隐藏了很多结构。例如,我们可能希望查看每天的时间对平均交通量的影响。我们可以通过使用前面讨论的“聚合和分组”中的 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:00 和下午 5:00 左右。这可能是工作日通勤交通量过桥的强烈证据。同时,数据还显示了方向性的差异:根据数据,东侧人行道在上午通勤时使用较多,而西侧人行道在下午通勤时使用较多。
我们还可能对基于一周中的不同日子的数据变化感兴趣。同样,我们可以使用简单的 groupby
来实现这一点(见下图):
by_weekday = data.groupby(data.index.dayofweek).mean()
by_weekday.index = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']
by_weekday.plot(style=['-', ':', '--']);
这显示了工作日和周末总量之间的显著差异,周一到周五的平均骑车人数大约是周六和周日的两倍。
考虑到这一点,让我们进行复合分组,比较工作日和周末的每小时趋势。我们从按是否周末和一天中的时间分组开始:
weekend = np.where(data.index.weekday < 5, 'Weekday', 'Weekend')
by_time = data.groupby([weekend, data.index.time]).mean()
现在我们将使用 Matplotlib 的多子图工具(在“多子图”中描述)来并排绘制两个面板,如下面的图所示:
fig, ax = plt.subplots(1, 2, figsize=(14, 5))
by_time.loc['Weekday'].plot(ax=ax[0], title='工作日', xticks=hourly_ticks, style=['-', ':', '--'])
by_time.loc['Weekend'].plot(ax=ax[1], title='周末', xticks=hourly_ticks, style=['-', ':', '--']);
结果显示,工作日呈现出双峰通勤模式,而周末则呈现出单峰休闲模式。深入分析这些数据,研究天气、温度、一年中的时间等因素对人们通勤模式的影响可能会非常有趣;有关进一步讨论,请参阅我的博客文章《西雅图的自行车骑行次数真的在增加吗?》,该文章使用了该数据集的一个子集。我们还将在深入讨论:线性回归中再次使用这个数据集。
参考资料
参考资料名称 | 链接 |
---|---|
Pandas 官方文档 - 时间序列/日期功能 | https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html |
《Python for Data Analysis》 - Wes McKinney | https://www.oreilly.com/library/view/python-for-data/9781491957653/ |
西雅图开放数据平台 | http://data.seattle.gov/ |
Fréemont Bridge 自行车计数数据集 | https://data.seattle.gov/Transportation/Fremont-Bridge-Bicycle-Counter/65db-xm6k |
Wes McKinney 的博客 | https://wesmckinney.com/blog/ |
Jake VanderPlas 的 GitHub | https://github.com/jakevdp/bicycle-data |
Matplotlib 官方文档 | https://matplotlib.org/stable/contents.html |
Python 数据科学手册 | https://jakevdp.github.io/PythonDataScienceHandbook/ |
西雅图交通局 | https://www.seattle.gov/transportation/ |
西雅图市府网站 - 自行车计数 | https://www.seattle.gov/bicycle-counts/ |
A beginner’s guide to time series analysis with Pandas | https://towardsdatascience.com/a-beginners-guide-to-time-series-analysis-with-pandas-bc1c52c406f1 |
Time Series Analysis and Forecasting in Python | https://www.datacamp.com/tutorial/Time_Series_Analysis_Forecasting_Python |
Pandas Time Series Data Structures | https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#time-series-data-structures |
数据可视化工具 | https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)