利用Python进行数据分析笔记-时间序列(转换、索引、偏移)

时间序列指能在任何能在时间上观测到的数据。很多时间序列是有固定频率(fixed frequency)的,意思是数据点会遵照某种规律定期出现,比如每15秒,每5分钟,或每个月。时间序列也可能是不规律的(irregular),没有一个固定的时间规律。如何参照时间序列数据取决于我们要做什么样的应用,我们可能会遇到下面这些:

  • Timestamps(时间戳),具体的某一个时刻
  • Fixed periods(固定的时期),比如2007年的一月,或者2010年整整一年
  • Intervals of time(时间间隔),通常有一个开始和结束的时间戳。Periods(时期)可能被看做是Intervals(间隔)的一种特殊形式。
  • Experiment or elapsed time(实验或经过的时间);每一个时间戳都是看做是一个特定的开始时间(例如,在放入烤箱后,曲奇饼的直径在每一秒的变化程度)

日期和时间数据类型及其工具

import pandas as pd
import numpy as np
from datetime import datetime
# 获取时间
now = datetime.now()
now
datetime.datetime(2018, 5, 10, 13, 31, 51, 898458)
print(now.year,'年')
print(now.month,'月')
print(now.day,'日')
print(now.minute,'分')
print(now.minute,'秒')
2018 年
5 月
10 日
31 分
31 秒

datetime能保存日期和时间到微妙级别。timedelta表示两个不同的datetime对象之间的时间上的不同:

# 时间对比
delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15)
delta
datetime.timedelta(926, 56700)
print('时间差多少天:',delta.days)
print('时间差多少秒:',delta.seconds)
时间差多少天: 926
时间差多少秒: 56700

我们可以在一个datetime对象上,添加或减少一个或多个timedelta,这样可以产生新的变化后的对象

from datetime import timedelta

start = datetime(2011, 1, 7)
start + timedelta(12)    # 加12天
datetime.datetime(2011, 1, 19, 0, 0)
start - 2 * timedelta(12)   # 减24天
datetime.datetime(2010, 12, 14, 0, 0)

下表汇总了一些datetime模块中的数据类型:

1、字符串与时间的转换

我们可以对datetime对象,以及pandas的Timestamp对象进行格式化,这部分之后会介绍,使用str或strftime方法,传入一个特定的时间格式就能进行转换:

# 时间转字符串
stamp = datetime(2011, 1, 3)
str(stamp)
'2011-01-03 00:00:00'
stamp.strftime('%Y-%m-%d')
'2011-01-03'

下表是关于日期时间类型的格式:

我们可以利用上面的format codes(格式码;时间日期格式)把字符串转换为日期,这要用到datetime.strptime:

# 字符串转时间
value = '2011-01-03'
datetime.strptime(value, '%Y-%m-%d')
datetime.datetime(2011, 1, 3, 0, 0)
datestrs = ['7/6/2011', '8/6/2011']
[datetime.strptime(x, '%m/%d/%Y') for x in datestrs]
[datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]

对于一个一直的时间格式,使用datetime.strptime来解析日期是很好的方法。但是,如果每次都要写格式的话很烦人,尤其是对于一些比较常见的格式。在这种情况下,我们可以使用第三方库dateutil中的parser.parse方法(这个库会在安装pandas的时候自动安装)

from dateutil.parser import parse
parse('2011-01-03')
datetime.datetime(2011, 1, 3, 0, 0)
# dateutil能够解析很多常见的时间表示格式
parse('Jan 31, 1997 10:45 PM')
datetime.datetime(1997, 1, 31, 22, 45)

在国际上,日在月之前是很常见的(译者:美国是把月放在日前面的),所以我们可以设置dayfirst=True来指明最前面的是否是日

parse('6/12/2011', dayfirst=True)
datetime.datetime(2011, 12, 6, 0, 0)

pandas通常可以用于处理由日期组成的数组,不论是否是DataFrame中的行索引或列。to_datetime方法能解析很多不同种类的日期表示。标准的日期格式,比如ISO 8601,能被快速解析

# 使用to_datetime
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
pd.to_datetime(datestrs)
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00'], dtype='datetime64[ns]', freq=None)

还能处理一些应该被判断为缺失的值(比如None, 空字符串之类的)

idx = pd.to_datetime(datestrs + [None])
idx
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)
idx[2]
NaT
pd.isnull(idx)
array([False, False,  True])

Nat(Not a Time)在pandas中,用于表示时间戳为空值(null value)。

dateutil.parse是一个很有用但不完美的工具。它可能会把一些字符串识别为日期,例如,’42’就会被解析为2042年加上今天的日期。

datetime对象还有一些关于地区格式(locale-specific formatting)的选项,用于处理不同国家或不同语言的问题。例如,月份的缩写在德国和法国,与英语是不同的。下表列出一些相关的选项:

时间序列基础

在pandas中,一个基本的时间序列对象,是一个用时间戳作为索引的Series,在pandas外部的话,通常是用python 字符串或datetime对象来表示的

dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),
         datetime(2011, 1, 7), datetime(2011, 1, 8), 
         datetime(2011, 1, 10), datetime(2011, 1, 12)]
ts = pd.Series(np.random.randn(6), index=dates)
ts
2011-01-02    1.404005
2011-01-05    0.269604
2011-01-07   -0.558070
2011-01-08    0.876070
2011-01-10    0.694803
2011-01-12   -0.599207
dtype: float64
# 每隔两个元素选一个元素
ts[::2]
2011-01-02    1.404005
2011-01-07   -0.558070
2011-01-10    0.694803
dtype: float64

pandas中的时间戳,是按numpy中的datetime64数据类型进行保存的,可以精确到纳秒的级别

ts.index.dtype
dtype('

1、索引,选择,取子集

当我们基于标签进行索引和选择时,时间序列就像是pandas.Series

ts
2011-01-02    1.404005
2011-01-05    0.269604
2011-01-07   -0.558070
2011-01-08    0.876070
2011-01-10    0.694803
2011-01-12   -0.599207
dtype: float64
# 通过时间序列索引
stamp = ts.index[2]
ts[stamp]
-0.5580701255970213

为了方便,我们可以直接传入一个字符串用来表示日期

ts['1/10/2011']
0.6948026143470746
ts['20110110']
0.6948026143470746

对于比较长的时间序列,我们可以直接传入一年或一年一个月,来进行数据选取

longer_ts = pd.Series(np.random.randn(1000),
                      index=pd.date_range('1/1/2000', periods=1000))  # periods表示周期
longer_ts[::50]  
2000-01-01   -1.434558
2000-02-20    0.199652
2000-04-10    0.396663
2000-05-30   -0.351714
2000-07-19   -1.464473
2000-09-07    0.113600
2000-10-27   -1.168503
2000-12-16   -0.395296
2001-02-04    0.109727
2001-03-26   -0.154458
2001-05-15    0.695305
2001-07-04    0.338459
2001-08-23    1.017848
2001-10-12    0.887390
2001-12-01    0.066979
2002-01-20    0.315712
2002-03-11    0.957264
2002-04-30    0.921923
2002-06-19    0.955736
2002-08-08    0.615709
Freq: 50D, dtype: float64
# 查看2001年的后5个数
longer_ts['2001'][-5:] 
2001-12-27    0.553635
2001-12-28    0.900810
2001-12-29   -1.792167
2001-12-30    0.599491
2001-12-31    0.271903
Freq: D, dtype: float64
# 查看2002年8月的前5个数
longer_ts['2002-8'][:5] 
2002-08-01    0.128209
2002-08-02    0.368129
2002-08-03    0.728122
2002-08-04    0.245300
2002-08-05   -0.685125
Freq: D, dtype: float64
# 利用datetime进行切片
longer_ts[datetime(2001, 1, 1)]
0.41985839386468266
# 按时间范围切片
longer_ts['12/28/2000':'1/3/2001']
2000-12-28   -0.756228
2000-12-29   -0.202390
2000-12-30    0.877150
2000-12-31   -1.073438
2001-01-01    0.419858
2001-01-02   -0.302687
2001-01-03   -0.777208
Freq: D, dtype: float64

记住,这种方式的切片得到的只是原来数据的一个视图,如果我们在切片的结果上进行更改的的,原来的数据也会变化。

有一个相等的实例方法(instance method)也能切片,truncate,能在两个日期上,对Series进行切片

longer_ts.truncate(before='12/28/2000', after='1/3/2001')
2000-12-28   -0.756228
2000-12-29   -0.202390
2000-12-30    0.877150
2000-12-31   -1.073438
2001-01-01    0.419858
2001-01-02   -0.302687
2001-01-03   -0.777208
Freq: D, dtype: float64

所有这些都适用于DataFrame,我们对行进行索引

dates = pd.date_range('1/1/2000', periods=100, freq='W-WED')   # periods表示周期, freq表示频率
long_df = pd.DataFrame(np.random.randn(100, 4),
                       index=dates,
                       columns=['Colorado', 'Texas',
                                'New York', 'Ohio'])
long_df.iloc[::20] 
Colorado Texas New York Ohio
2000-01-05 -0.207091 -1.458642 -0.406117 -0.153867
2000-05-24 -0.047038 -0.496946 -0.091025 0.693195
2000-10-11 0.088201 -0.193686 -1.444394 -1.315864
2001-02-28 -0.654497 0.093796 -0.819060 0.755123
2001-07-18 1.885355 -0.206915 -1.392564 -1.514281
long_df.loc['2001-5']
Colorado Texas New York Ohio
2001-05-02 -1.742909 -0.996392 0.824744 0.352815
2001-05-09 -0.951432 -0.326518 -0.767074 0.168574
2001-05-16 0.926389 0.064682 -0.253195 -0.081806
2001-05-23 1.592441 -2.418966 0.713259 -1.198133
2001-05-30 0.700246 0.593380 0.179941 0.127628

2、重复索引的时间序列

在某些数据中,可能会遇到多个数据在同一时间戳下的情况

dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000', 
                          '1/2/2000', '1/3/2000'])
dup_ts = pd.Series(np.random.randn(5), index=dates)
dup_ts
2000-01-01    1.350517
2000-01-02   -0.646460
2000-01-02    0.503535
2000-01-02    1.518830
2000-01-03   -0.276995
dtype: float64

我们通过is_unique属性来查看index是否是唯一值

# 判断索引值是否唯一
dup_ts.index.is_unique
False

对这个时间序列取索引的的话, 要么得到标量,要么得到切片,这取决于时间戳是否是重复的

dup_ts['1/2/2000'] 
2000-01-02   -0.646460
2000-01-02    0.503535
2000-01-02    1.518830
dtype: float64
# 索引的索引
dup_ts['1/2/2000'][2]
1.5188299993953172

3、生成日期范围

date_range默认会生成按日频度的时间戳,会保留开始或结束的时间戳。

pd.date_range(start='2012-04-01 12:56:3', periods=6)
DatetimeIndex(['2012-04-01 12:56:03', '2012-04-02 12:56:03',
               '2012-04-03 12:56:03', '2012-04-04 12:56:03',
               '2012-04-05 12:56:03', '2012-04-06 12:56:03'],
              dtype='datetime64[ns]', freq='D')
pd.date_range(end='2012-04-06', periods=6)
DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03', '2012-04-04',
               '2012-04-05', '2012-04-06'],
              dtype='datetime64[ns]', freq='D')
# 设定频度
pd.date_range('2000-01-01', '2000-12-01', freq='BM')
DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', '2000-04-28',
               '2000-05-31', '2000-06-30', '2000-07-31', '2000-08-31',
               '2000-09-29', '2000-10-31', '2000-11-30'],
              dtype='datetime64[ns]', freq='BM')

时间序列频度:

有些时候我们的时间序列数据带有小时,分,秒这样的信息,但我们想要让这些时间戳全部归一化到午夜(normalized to midnight, 即晚上0点),这个时候要用到normalize选项

nor_date = pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True)
nor_date
DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
               '2012-05-06'],
              dtype='datetime64[ns]', freq='D')
# 可以看到小时,分,秒全部变为0
nor_date[2]
Timestamp('2012-05-04 00:00:00', freq='D')
# 设定频度为4小时
pd.date_range('2000-01-01', '2000-01-03 23:59', freq='4H')
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 04:00:00',
               '2000-01-01 08:00:00', '2000-01-01 12:00:00',
               '2000-01-01 16:00:00', '2000-01-01 20:00:00',
               '2000-01-02 00:00:00', '2000-01-02 04:00:00',
               '2000-01-02 08:00:00', '2000-01-02 12:00:00',
               '2000-01-02 16:00:00', '2000-01-02 20:00:00',
               '2000-01-03 00:00:00', '2000-01-03 04:00:00',
               '2000-01-03 08:00:00', '2000-01-03 12:00:00',
               '2000-01-03 16:00:00', '2000-01-03 20:00:00'],
              dtype='datetime64[ns]', freq='4H')
# 设定频度为1.5小时
pd.date_range('2000-01-01', periods=10, freq='1h30min')
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00',
               '2000-01-01 03:00:00', '2000-01-01 04:30:00',
               '2000-01-01 06:00:00', '2000-01-01 07:30:00',
               '2000-01-01 09:00:00', '2000-01-01 10:30:00',
               '2000-01-01 12:00:00', '2000-01-01 13:30:00'],
              dtype='datetime64[ns]', freq='90T')

一个有用的类(class)是月中的第几周(Week of month),用WOM表示。我们想得到每个月的第三个星期五:

# 频度为每个月的第三个星期五
rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI')
rng
DatetimeIndex(['2012-01-20', '2012-02-17', '2012-03-16', '2012-04-20',
               '2012-05-18', '2012-06-15', '2012-07-20', '2012-08-17'],
              dtype='datetime64[ns]', freq='WOM-3FRI')
# 日期范围也能通过时区集合(time zone set)来创建
pd.date_range('3/9/2012 9:30', periods=10, freq='D', tz='UTC')
DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00',
               '2012-03-11 09:30:00+00:00', '2012-03-12 09:30:00+00:00',
               '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00',
               '2012-03-15 09:30:00+00:00', '2012-03-16 09:30:00+00:00',
               '2012-03-17 09:30:00+00:00', '2012-03-18 09:30:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='D')

4、数据偏移(提前与推后)

偏移(shifting)表示按照时间把数据向前或向后推移。Series和DataFrame都有一个shift方法实现偏移,索引(index)不会被更改

ts = pd.Series(np.random.randn(4),
               index=pd.date_range('1/1/2000', periods=4, freq='M'))
ts
2000-01-31   -0.447984
2000-02-29    0.735111
2000-03-31    0.212321
2000-04-30   -0.987703
Freq: M, dtype: float64
# 位移时,会引入缺失值
ts.shift(-2)
2000-01-31    0.212321
2000-02-29   -0.987703
2000-03-31         NaN
2000-04-30         NaN
Freq: M, dtype: float64

shift的一个普通的用法是计算时间序列的百分比变化,可以表示为

ts / ts.shift(1) - 1
2000-01-31         NaN
2000-02-29   -2.640931
2000-03-31   -0.711171
2000-04-30   -5.651929
Freq: M, dtype: float64

因为普通的shift不会对index进行修改,一些数据会被丢弃。因此如果频度是已知的,可以把频度传递给shift,这样的话时间戳会自动变化

ts.shift(2)
2000-01-31         NaN
2000-02-29         NaN
2000-03-31   -0.447984
2000-04-30    0.735111
Freq: M, dtype: float64
ts.shift(2, freq='M')
2000-03-31   -0.447984
2000-04-30    0.735111
2000-05-31    0.212321
2000-06-30   -0.987703
Freq: M, dtype: float64
# 偏移到月底
from pandas.tseries.offsets import Day, MonthEnd

now = datetime(2011, 11, 17)
offset = MonthEnd()  # 移到月底
now + offset
Timestamp('2011-11-30 00:00:00')
# 向前偏移一个月
offset.rollback(now)
Timestamp('2011-10-31 00:00:00')
offset.rollforward(now)
Timestamp('2011-11-30 00:00:00')
datetime.now()
datetime.datetime(2018, 5, 11, 8, 45, 22, 629877)

你可能感兴趣的:(数据分析,利用Python进行数据分析)