pandas(8)

《Python数据科学手册》读书笔记

处理时间序列

由于 Pandas 最初是为金融模型而创建的, 因此它拥有一些功能非常强
大的日期、 时间、 带时间索引数据的处理工具。 本节将介绍的日期与时
间数据主要包含三类。

  • 时间戳表示某个具体的时间点(例如 2015 年 7 月 4 日上午 7
    点) 。

  • 时间间隔与周期表示开始时间点与结束时间点之间的时间长度, 例
    如 2015 年(指的是 2015 年 1 月 1 日至 2015 年 12 月 31 日这段时
    间间隔) 。 周期通常是指一种特殊形式的时间间隔, 每个间隔长度
    相同, 彼此之间不会重叠(例如, 以 24 小时为周期构成每一
    天) 。

  • 时间增量(time delta) 或持续时间(duration) 表示精确的时间长
    度(例如, 某程序运行持续时间 22.56 秒) 。

Python的日期与时间工具

在 Python 标准库与第三方库中有许多可以表示日期、 时间、 时间增量
和时间跨度(timespan) 的工具。

  1. 原生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'

datetime 和 dateutil 模块在灵活性与易用性方面都表现出色,
你可以用这些对象及其相应的方法轻松完成你感兴趣的任意操作。
但如果你处理的时间数据量比较大, 那么速度就会比较慢。 就像之
前介绍过的 Python 的原生列表对象没有 NumPy 中已经被编码的数
值类型数组的性能好一样, Python 的原生日期对象同样也没有
NumPy 中已经被编码的日期(encoded dates) 类型数组的性能好。

  1. 时间类型数组: NumPy的datetime64类型

datetime64 类型将日期编码为 64 位整
数, 这样可以让日期数组非常紧凑(节省内存) 。 datetime64 需
要在设置日期时确定具体的输入类型:

import numpy as np
date = np.array('2015-07-04', dtype=np.datetime64)
date
array('2015-07-04', dtype='datetime64[D]')
import numpy as np
date = np.array('2015-07-04', dtype=np.datetime64)
date
array('2015-07-04', 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 对象的运算速度快很
多, 尤其是在处理较大数组时。

datetime64 与 timedelta64 对象的一个共同特点是, 它们都是
在 基本时间单位(fundamental time unit) 的基础上建立的。 由于
datetime64 对象是 64 位精度, 所以可编码的时间范围可以是基
本单元的 264 倍。 也就是说, datetime64 在时间精度(time
resolution) 与最大时间跨度(maximum time span) 之间达成了一
种平衡。

比如你想要一个时间纳秒(nanosecond, ns) 级的时间精度, 那么
你就可以将时间编码到 0~264 纳秒或 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')
  • 日期与时间单位格式代码
代码 含义 时间跨度 (相对) 时间跨度 (绝对)
Y 年(year) ± 9.2e18 年 [9.2e18 BC, 9.2e18 AD]
M 月(month) ± 7.6e17 年 [7.6e17 BC, 7.6e17 AD]
W 周(week) ± 1.7e17 年 [1.7e17 BC, 1.7e17 AD]
D 日(day) ± 2.5e16 年 [2.5e16 BC, 2.5e16 AD]
h 时(hour) ± 1.0e15 年 [1.0e15 BC, 1.0e15 AD]
m 分(minute) ± 1.7e13 年 [1.7e13 BC, 1.7e13 AD]
s 秒(second) ± 2.9e12 年 [ 2.9e9 BC, 2.9e9 AD]ms 毫秒(millisecond) ± 2.9e9 年 [ 2.9e6 BC, 2.9e6 AD]
us 微秒(microsecond) ± 2.9e6 年 [290301 BC, 294241 AD]
ns 纳秒(nanosecond) ± 292 年 [ 1678 AD, 2262 AD]
ps 皮秒(picosecond) ± 106 天 [ 1969 AD, 1970 AD]
fs 飞秒(femtosecond) ± 2.6 小时 [ 1969 AD, 1970 AD]
as 原秒(attosecond) ± 9.2 秒 [ 1969 AD, 1970 AD]

对于日常工作中的时间数据类型, 默认单位都用纳秒
datetime64[ns], 因为用它来表示时间范围精度可以满足绝大部
分需求。

最后还需要说明一点, 虽然 datetime64 弥补了 Python 原生的
datetime 类型的不足, 但它缺少了许多 datetime(尤其是
dateutil) 原本具备的便捷方法与函数, 具体内容请参考 NumPy
的 datetime64 文档

(http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html) 。

  1. Pandas的日期与时间工具: 理想与现实的最佳解决方案

Pandas 所有关于日期与时间的处理方法全部都是通过 Timestamp
对象实现的, 它利用 numpy.datetime64 的有效存储和向量化接
口将 datetime 和 dateutil 的易用性有机结合起来。 Pandas 通过
一组 Timestamp 对象就可以创建一个可以作为 Series 或
DataFrame 索引的 DatetimeIndex,。

 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 时间序列工具非常适合用来处理带时间戳的索引数据。 例如,
可以通过一个时间索引数据创建一个 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

另外, 还有一些仅在此类 Series 上可用的取值操作, 例如直接通过年
份切片获取该年的数据:

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() 函数, 它可以
解析许多日期与时间格式。 对 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='period[D]', 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')

此外, 日期范围不一定非是开始时间与结束时间, 也可以是开始时间与
周期数 periods:

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

如果要创建一个有规律的周期或时间间隔序列, 有类似的函数
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='period[M]', 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 时间序列工具的基础是时间频率或偏移量(offset) 代码。 就像
之前见过的 D(day) 和 H(hour) 代码, 可以用这些代码设置任意
需要的时间间隔。

  • Pandas频率代码
代码 描述 代码 描述
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 小时
T 分钟(minutes)
S 秒(seconds)
L 毫秒(milliseonds)
U 微秒(microseconds)
N 纳秒(nanoseconds)

月、 季、 年频率都是具体周期的结束时间(月末、 季末、 年末) , 而有
一些以 S(start, 开始) 为后缀的代码表示日期开始 。

  • 带开始索引的频率代码
代码 频率
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 等。

  • A-JAN、 BA-FEB、 AS-MAR、 BAS-APR 等。

同理, 也可以在后面加三位星期缩写字母来改变一周的开始时间。

  • W-SUN、 W-MON、 W-TUE、 W-WED 等。

在这些代码的基础上, 还可以将频率组合起来创建的新的周期。 例如,
可以用小时(H) 和分钟(T) 的组合来实现 2 小时 30 分钟:

 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')
发布了57 篇原创文章 · 获赞 63 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/weixin_41503009/article/details/104185507