固定时间是指一个时间点,如2020年11月11日00:00:00。在计算机中,时间多用时间戳(Timestamp)表示,它指的是格林威治时间1970年1月1日00时00分00秒起至当下的总秒数。
Python的官网库datetime支持创建和处理时间:
import datetime
# 当前时间
datetime.datetime.now()
# datetime.datetime(2022, 9, 29, 9, 46, 20, 679667)
# 指定时间
datetime.datetime(2020, 11, 1, 19)
# datetime.datetime(2020, 11, 1, 19, 0)
# 指定时间
datetime.datetime(year=2020, month=11, day=11)
# datetime.datetime(2020, 11, 11, 0, 0)
pd.Timestamp()是Pandas定义时间的主要函数,代替Python中的datetime.datetime()对象。
(1)使用datetime.datetime函数:
import datetime
# 至少需要年、月、日
pd.Timestamp(datetime.datetime(2020, 6, 8))
# Timestamp('2020-06-08 00:00:00')
# 指定时、分、秒
pd.Timestamp(datetime.datetime(2020, 6, 8, 16, 17, 18))
# Timestamp('2020-06-08 16:17:18')
(2)指定时间字符串:
pd.Timestamp('2012-05-01')
# Timestamp('2012-05-01 00:00:00')
pd.Timestamp('2017-01-01T12')
# Timestamp('2017-01-01 12:00:00')
(3)指定时间位置数字,可依次定义year、month、day、hour、minute、second、microsecond:
pd.Timestamp(2012, 5, 1)
# Timestamp('2012-05-01 00:00:00')
pd.Timestamp(year=2017, month=1, day=1, hour=12)
# Timestamp('2017-01-01 12:00:00')
(4)解析时间戳:
pd.Timestamp(1513393355.5, unit='s') # 单位为秒
# Timestamp('2017-12-16 03:02:35.500000')
(5)用tz指定时区,需要记住北京时间值为Asia/Shanghai:
pd.Timestamp(1513393355, unit='s', tz='US/Pacific')
# Timestamp('2017-12-15 19:02:35-0800', tz='US/Pacific')
pd.Timestamp(1513393355, unit='s', tz='Asia/Shanghai')
# Timestamp('2017-12-16 11:02:35+0800', tz='Asia/Shanghai')
(6)获取到当前时间,从而可通过属性取到今天的日期、年份等信息:
pd.Timestamp('today')
pd.Timestamp('now') # 效果同上
# Timestamp('2022-09-29 09:59:37.361192')
pd.Timestamp('today').date() # 只取日期
# datetime.date(2022, 9, 29)
(7)通过当前时间计算出昨天、明天等信息:
# 昨天
pd.Timestamp('now') - pd.Timedelta(days=1)
# Timestamp('2022-09-28 10:01:06.419889')
# 明天
pd.Timestamp('now') + pd.Timedelta(days=1)
# Timestamp('2022-09-30 10:01:40.628220')
# 当月初,一日
pd.Timestamp('now').replace(day=1)
# Timestamp('2022-09-01 10:03:20.150130')
(8)pd.to_datetime() 也可以实现上述功能,不过常用在时间转换上。
pd.to_datetime('now')
# Timestamp('2022-09-29 02:04:34.651490')
(9)由于Pandas以纳秒粒度表示时间戳,因此可以使用64位整数表示时间跨度限制为大约584年,意味着能表示的时间范围有最早和最晚的限制:
pd.Timestamp.min
# Timestamp('1677-09-21 00:12:43.145225')
pd.Timestamp.max
# Timestamp('2262-04-11 23:47:16.854775807')
不过,Pandas给出了一个解决方案:使用PeriodIndex解决。
(1)定义一个当前时间:
time = pd.Timestamp('now')
# Timestamp('2022-09-29 10:07:48.470740')
(2)以下是丰富的时间属性:
time.asm8 # 返回Numpy datetime64格式(以纳秒为单位)
# numpy.datetime64('2022-09-29T10:07:48.470740000')
time.dayofweek # 3 (周几,周一为0)
time.dayofyear # 272 (一年的第几天)
time.days_in_month # 30 (当月有多少天)
time.daysinmonth # 30 同上
time.freqstr # None (周期字符)
time.is_leap_year # False (是否闰年)
time.is_month_end # False (是否当月最后一天)
time.is_month_start # False (是否当月第一天)
time.is_quarter_end # False (是否当季最后一天)
time.is_quarter_start # False (是否当季第一天)
time.is_year_end # False (是否当年最后一天)
time.is_year_start # False (是否当年第一天)
time.quarter # 3 (当前季度数)
time.tz # None(当前时区别名,如果指定,会返回类似<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
time.week # 39 (当前周数)
time.weekofyear # 39 同上
time.day # 29 (日)
time.fold # 0
time.freq # None (频度周期)
time.hour # 10(时)
time.microsecond #470740(微秒)
time.minute # 7
time.month # 9
time.nanosecond # 0
time.second # 48
time.tzinfo # None
time.value # 1664446068470740000
time.year # 2022
取当前时间,并指定时区为北京时间:
time = pd.Timestamp('now', tz='Asia/Shanghai')
# Timestamp('2022-09-29 10:19:34.762386+0800', tz='Asia/Shanghai')
(1)转换为指定时区:
time.astimezone('UTC')
# Timestamp('2022-09-29 02:19:34.762386+0000', tz='UTC')
(2)转换单位,向上舍入:
time.ceil('s') # 转为以秒为单位
# Timestamp('2022-09-29 10:19:35+0800', tz='Asia/Shanghai')
time.ceil('ns') # 转为以纳秒为单位
time.ceil('d') # 保留日
time.ceil('h') # 保留时
# Timestamp('2022-09-29 11:00:00+0800', tz='Asia/Shanghai')
(3)转换单位,向下舍入:
time.floor('h') # 保留时
# Timestamp('2022-09-29 10:00:00+0800', tz='Asia/Shanghai')
(4)转换单位,四舍五入:
time.round('h') # 保留时
# Timestamp('2022-09-29 10:00:00+0800', tz='Asia/Shanghai')
(5)返回星期和月份名:
time.day_name() # 'Thursday'
time.month_name() # 'September'
(6)将时间戳规范为午夜,保留tz信息:
time.normalize()
# Timestamp('2022-09-29 00:00:00+0800', tz='Asia/Shanghai')
(7)将时间元素替换datetime.replace,可处理微秒:
time.replace(year=2019) # 替换年份
# Timestamp('2019-09-29 10:19:34.762386+0800', tz='Asia/Shanghai')
time.replace(month=8) # 替换月份
# Timestamp('2022-08-29 10:19:34.762386+0800', tz='Asia/Shanghai')
(8)转换为周期类型,丢弃时区:
time.to_period(freq='h') # 周期为小时
# Period('2022-09-29 10:00', 'H')
(9)转换为指定时区:
time.tz_convert('UTC')
# Timestamp('2022-09-29 02:19:34.762386+0000', tz='UTC')
(10)本地化时区转换:
time = pd.Timestamp('now')
time.tz_localize('Asia/Shanghai')
# Timestamp('2022-09-29 10:30:31.041733+0800', tz='Asia/Shanghai')
time.tz_localize(None) # 删除时区
# Timestamp('2022-09-29 10:30:54.821096')
(1)对于时间的缺失值,有专门的NaT来表示:
pd.Timestamp(pd.NaT)
# NaT
pd.Timedelta(pd.NaT)
# NaT
pd.Period(pd.NaT)
# NaT
# 类似np.nna
pd.NaT == pd.NaT # False
(2)NaT可以代表固定时间、时长、时间周期为空的情况,类似于np.nan可以参与到时间的各种计算中:
pd.NaT + pd.Timestamp('20200101')
# NaT
pd.NaT + pd.Timedelta('2 days')
# NaT
两个固定时间相减会得到时间差或者时长。
pd.Timedelta() 对象表示时间差,也就是时长,以差异单位表示,例如天、小时等。既可以是正数,又可以是负数。
(1)两个固定时间相减会产生时间差:
pd.Timestamp('2020-11-01 15') - pd.Timestamp('2020-11-01 14')
# Timedelta('0 days 01:00:00')
(2)传入字符串:
pd.Timedelta('1 days')
# Timedelta('1 days 00:00:00')
pd.Timedelta('1 days 00:00:00')
# Timedelta('1 days 00:00:00')
pd.Timedelta('1 days 2 min 3 us')
# Timedelta('1 days 00:02:00.000003')
(3)用关键字参数指定时间:
pd.Timedelta(days=5, seconds=10)
# Timedelta('5 days 00:00:10')
# 可以将指定分钟转化为天和小时
pd.Timedelta(minutes=3242)
# Timedelta('2 days 06:02:00')
(4)使用带周期量的偏移量别名:
pd.Timedelta('1D')
# Timedelta('1 days 00:00:00')
pd.Timedelta('2W')
# Timedelta('14 days 00:00:00')
pd.Timedelta('1D2H3M4S')
# Timedelta('1 days 02:03:04')
(5)带单位的整型数字:
# 一天
pd.Timedelta(1, unit='d')
# Timedelta('1 days 00:00:00')
pd.Timedelta(100, unit='s')
# Timedelta('0 days 00:01:40')
(6)使用Python内置的datetime.timedelta或者Numpy的np.timedelta64:
pd.Timedelta(datetime.timedelta(days=1, minutes=10))
# Timedelta('1 days 00:10:00')
pd.Timedelta(np.timedelta64(100, 'ns'))
# Timedelta('0 days 00:00:00.000000100')
(7)负值:
pd.Timedelta('-1min')
# Timedelta('-1 days +23:59:00')
(8)缺失值:
pd.Timedelta('nan')
# NaT
pd.Timedelta('nat')
# NaT
(9)标准字符串(ISO 8601 Duration strings):
pd.Timedelta('P0DT0H1M0S')
# Timedelta('0 days 00:01:00')
pd.Timedelta('P0DT0H0M0.000000123S')
# Timedelta('0 days 00:00:00.000000123')
(10)使用时间偏移对象DateOffsets(Day, Hour, Minute, Second, Milli, Micro, Nano)直接创建:
# 两分钟
pd.Timedelta(pd.offsets.Minute(2))
# Timedelta('0 days 00:02:00')
(11)pd.to_timedelta()也可以完成上述操作,不过大多用于时长类型的数据转换上:
pd.to_timedelta(pd.offsets.Day(3))
# Timedelta('3 days 00:00:00')
pd.to_timedelta('15.5min')
# Timedelta('0 days 00:15:30')
pd.to_timedelta(124524564574835)
# Timedelta('1 days 10:35:24.564574835')
和时间戳数据一样,时长数据的存储也有上下限:
pd.Timedelta.min
# Timedelta('-106752 days +00:12:43.145224193')
pd.Timedelta.max
# Timedelta('106751 days 23:47:16.854775807')
如果想处理更大的时长数据,可以将其转换为一定单位的数字类型。
(1)相加,多个时长累积为一个更长的时长:
pd.Timedelta(pd.offsets.Day(1)) + pd.Timedelta(pd.offsets.Hour(5))
# Timedelta('1 days 05:00:00')
(2)相减:
pd.Timedelta(pd.offsets.Day(1)) - pd.Timedelta(pd.offsets.Hour(5))
# Timedelta('0 days 19:00:00')
(3)固定时间与时长相加减会得到一个新的固定时间:
pd.Timestamp('2020-11-11') - pd.Timedelta(pd.offsets.Day(1))
# Timestamp('2020-11-10 00:00:00')
时长数据中可以解析出指定时间计数单位的值,如小时、秒。
tdt = pd.Timedelta('10 days 9 min 3 sec')
tdt.days # 10
tdt.seconds # 543
(-tdt).days # -11
tdt.value # 864543000000000 时间戳
时长数据可以作为索引(TimedeltaIndex),使用的场景比较少。比如在一项体育运动中,分别有2分钟完成、4分钟完成等。
将众多固定时间组织起来就形成了时间序列,即时序数据。
DatetimeIndex是时间索引对象,一般由to_datetime() 或date_range() 来创建:
(1)使用to_datetime()
pd.to_datetime(['11/1/2020', # 类时间字符串
np.datetime64('2020-11-02'), # Numpy的事件类型
datetime.datetime(2020, 11, 3)]) # Python自带时间类型
# DatetimeIndex(['2020-11-01', '2020-11-02', '2020-11-03'], dtype='datetime64[ns]', freq=None)
(2)date_range() 可以给定开始或者结束时间,并给定周期数据、周期频率,会自动生成在此范围内的时间索引数据:
# 默认频率为天
pd.date_range('2020-01-01', periods=10)
pd.date_range('2020-01-01', '2020-01-10') # 同上
pd.date_range(end='2020-01-10', periods=10) # 同上
"""
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
'2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
'2020-01-09', '2020-01-10'],
dtype='datetime64[ns]', freq='D')
"""
(3)pd.bdate_range()生成数据可以跳过周六日,实现工作日的时间索引序列:
# 频率为工作日
pd.bdate_range('2020-11-1', periods=10)
"""
DatetimeIndex(['2020-11-02', '2020-11-03', '2020-11-04', '2020-11-05',
'2020-11-06', '2020-11-09', '2020-11-10', '2020-11-11',
'2020-11-12', '2020-11-13'],
dtype='datetime64[ns]', freq='B')
"""
创建包含时序的Series和DataFrame与创建普通的Series和DataFrame一样,将时序索引序列作为索引或者将时间列转换为时间类型。
(1)创建Series:
# 生成时序索引
tidx = pd.date_range('2020-11-1', periods=10)
# 应用时序索引
s = pd.Series(range(len(tidx)), index=tidx)
"""
2020-11-01 0
2020-11-02 1
2020-11-03 2
2020-11-04 3
2020-11-05 4
2020-11-06 5
2020-11-07 6
2020-11-08 7
2020-11-09 8
2020-11-10 9
Freq: D, dtype: int64
"""
将时间作为Series内容,则序列的数据类型为datetime64[ns]:
pd.Series(tidx)
"""
0 2020-11-01
1 2020-11-02
2 2020-11-03
3 2020-11-04
4 2020-11-05
5 2020-11-06
6 2020-11-07
7 2020-11-08
8 2020-11-09
9 2020-11-10
dtype: datetime64[ns]
"""
(2)创建DataFrame:
# 索引
tidx = pd.date_range('2020-11-1', periods=10)
# 应用索引生成DataFrame
df = pd.DataFrame({'A': range(len(tidx)), 'B': range(len(tidx))[::-1]},
index=tidx)
"""
A B
2020-11-01 0 9
2020-11-02 1 8
2020-11-03 2 7
2020-11-04 3 6
2020-11-05 4 5
2020-11-06 5 4
2020-11-07 6 3
2020-11-08 7 2
2020-11-09 8 1
2020-11-10 9 0
"""
# 时序索引数据创建
idx = pd.date_range('1/1/2020', '12/1/2021', freq='H')
ts = pd.Series(np.random.randn(len(idx)), index=idx)
"""
2020-01-01 00:00:00 -0.204984
2020-01-01 01:00:00 -0.693256
...
2021-11-30 23:00:00 -0.086156
2021-12-01 00:00:00 1.237712
Freq: H, Length: 16801, dtype: float64
"""
(1)使用[ ]、loc,按切片的操作对数据进行访问:
# 指定区间
ts[5:10]
"""
2020-01-01 05:00:00 -1.098832
2020-01-01 06:00:00 1.156500
2020-01-01 07:00:00 0.127538
2020-01-01 08:00:00 -0.077853
2020-01-01 09:00:00 2.063492
Freq: H, dtype: float64
"""
# 只筛选2020年
ts['2020']
(2)支持传入时间字符和各种时间对象:
# 指定天,以下结果相同
ts['11/30/2020']
ts['2020-11-30']
ts['20201130']
# 指定时间点
ts[datetime.datetime(2020, 11, 30)]
ts[pd.Timestamp(2020, 11, 30)]
ts[pd.Timestamp('2020-11-30')]
ts[np.datetime64('2020-11-30')]
(3)使用部分字符查询一定范围内的数据:
ts['2021'] # 查询整个2021年
ts['2021-6'] # 查询2021年6月
ts['2021-6':'2021-10'] # 查询2021年6月到10月
dft['2021-1':'2021-2-28 00:00:00'] # 精确时间
df2.loc['2020-01-05']
# 索引选择器
idx = pd.IndexSlice
dft2.loc[idx[:, '2020-01-05'], :]
# 带时区,原数据时区可能不是这个
df['2020-01-01 12:00:00+04:00:':'2020-01-01 13:00:00+04:00']
(4)使用ts.resolution查看序列的粒度(频率):
# 时间粒度(频率)
ts.index.resulution
# 'hour'
(5)使用df.truncate()对时间序列进行截取:
# 给定开始时间和结束时间
ts.truncate(before='2020-11-10 11:20', after='2020-12')
由于时间格式样式比较多,很多情况下,Pandas并不能自动将时序数据识别为时间类型,因此需要专门对数据进行时间类型转换:
(1)astype只能针对相对标准的时间格式:
s = pd.Series(['2020-11-01 01:10', '2020-11-11 11:10'])
"""
0 2020-11-01 01:10
1 2020-11-11 11:10
dtype: object
"""
①从数据内容上看,s符合时序格式,但要想让它成为时间类型,需要用astype进行转换:
s.astype('datetime64[ns]')
"""
0 2020-11-01 01:10:00
1 2020-11-11 11:10:00
dtype: datetime64[ns]
"""
②修改频率:
# 转为时间类型,指定频率为天
s.astype('datetime64[D]')
"""
0 2020-11-01
1 2020-11-11
dtype: datetime64[ns]
"""
③指定时区:
# 转为时间类型, 指定时区为北京时间
s.astype('datetime64[ns, Asia/Shanghai]')
"""
0 2020-11-01 01:10:00+08:00
1 2020-11-11 11:10:00+08:00
dtype: datetime64[ns, Asia/Shanghai]
"""
(2)使用pd.to_datetime()
①转换时间类型:
pd.to_datetime(s)
②将多列组合成一个时间进行转换:
df = pd.DataFrame({'year': [2020, 2020, 2020],
'month': [10, 11, 12],
'day': [10, 11, 12]})
"""
year month day
0 2020 10 10
1 2020 11 11
2 2020 12 12
"""
# 转为时间类型
pd.to_datetime(df)
pd.to_datetime(df[['year', 'month', 'day']]) # 同上
"""
0 2020-10-10
1 2020-11-11
2 2020-12-12
dtype: datetime64[ns]
"""
③对于Series,pd.to_datetime()会智能识别其时间格式并进行转换:
s = pd.Series(['2020-11-01 01:10', '2020-11-11 11:10', None])
pd.to_datetime(s)
"""
0 2020-11-01 01:10:00
1 2020-11-11 11:10:00
2 NaT
dtype: datetime64[ns]
"""
④对于列表,pd.to_datetime()也会智能识别其时间格式并转为时间序列索引:
pd.to_datetime(['2020/11/11', '2020/12/12'])
# DatetimeIndex(['2020-11-11', '2020-12-12'], dtype='datetime64[ns]', freq=None)
pd.to_datetime(['1-10-2020 10:00'], dayfirst=True) # 按日期在前解析
# DatetimeIndex(['2020-10-01 10:00:00'], dtype='datetime64[ns]', freq=None)
(3)使用pd.DatetimeIndex直接转为时间序列索引:
# 转为时间序列索引,自动推断频率
pd.DatetimeIndex(['20201101', '20201102'], freq='infer')
# DatetimeIndex(['2020-11-01', '2020-11-02'], dtype='datetime64[ns]', freq=None)
(4)针对单个时间,使用pd.Timestamp()转换为时间格式:
pd.to_datetime('2020/11/12')
# Timestamp('2020-11-12 00:00:00')
pd.Timestamp('2020/11/12')
# Timestamp('2020-11-12 00:00:00')
(1)如果原数据的格式是不规范的时间规范数据,可以通过格式映射来将其转为时间数据:
# 不规则格式转换时间
pd.to_datetime('2020_11_11', format='%Y_%m_%d', errors='ignore')
# Timestamp('2020-11-11 00:00:00')
以上时间数据用下划线连接各个部分,形式不规范,需要通过format参数来匹配此格式,将对应部分分配给年月日。
(2)更多实例如下:
# 让系统自己推断时间格式
pd.to_datetime('20200101', infer_datetime_format=True, errors='ignore')
# Timestamp('2020-01-01 00:00:00')
# 将errors参数设置为coerce,将不会忽略错误,返回空值
pd.to_datetime('20200101', format='%Y%m%d', errors='coerce')
# Timestamp('2020-01-01 00:00:00')
# 与书本不一致
# 列转为字符串,再改为时间类型
#pd.to_datetime(df.d.astype(str), format='%m/%d/%Y')
# 其他
pd.to_datetime('2020/11/12', format='%Y/%m/%d')
# Timestamp('2020-11-12 00:00:00')
pd.to_datetime('01-01-2020 00:00', format='%d-%m-%Y %H:%M')
# Timestamp('2020-01-01 00:00:00')
# 对时间戳进行转换,需要给出时间单位,一般为秒
pd.to_datetime(1490195805, unit='s')
# Timestamp('2017-03-22 15:16:45')
可以将数字列表转换为时间:
pd.to_datetime([10, 11, 12, 15], unit='D', origin=pd.Timestamp('2020-11-01'))
# DatetimeIndex(['2020-11-11', '2020-11-12', '2020-11-13', '2020-11-16'], dtype='datetime64[ns]', freq=None)
.dt.< method >可以以time.dt.xxx的形式来访问时间序列数据的属性和调用他们的方法,返回对应值的序列。
# 创建时间
s = pd.Series(pd.date_range('2020-11-01', periods=5, freq='d'))
# 对应的星期几
s.dt.day_name()
"""
0 Sunday
1 Monday
2 Tuesday
3 Wednesday
4 Thursday
dtype: object
"""
以下列出时间访问器的一些属性和方法:
# 时间访问操作
s.dt.date
s.dt.time
s.dt.timetz
# 以下为时间各成分的值
s.dt.year
s.dt.month
s.dt.day
s.dt.hour
s.dt.minute
s.dt.second
s.dt.microsecond
s.dt.nanosecond
# 以下为与周、月、年相关的属性
s.dt.week
s.dt.weekofyear
s.dt.dayofweek
s.dt.weekday
s.dt.dayofyear
s.dt.quarter # 季度数
s.dt.is_month_start
s.dt.is_month_end
s.dt.is_quarter_start
s.dt.is_quarter_end
s.dt.is_year_start
s.dt.is_year_end
s.dt.is_leap_year # 是否闰年
s.dt.daysinmonth # 当月有多少天
s.dt.days_in_month
s.dt.tz
s.dt.freq # 频率
# 以下为转换方法
s.dt.to_period
s.dt.to_pydatetime
s.dt.tz_localize
s.dt.tz_convert
s.dt.normalize
s.dt.strftime
s.dt.round(freq='D') # 类似四舍五入
s.dt.floor(freq='D') # 向下舍入为天
s.dt.ceil(freq='D') # 向上舍入为天
s.dt.month_name
s.dt.day_name
s.dt.start_time
s.dt.end_time
s.dt.days
s.dt.seconds
s.dt.components # 各时间成分的值
s.dt.to_pytimedelta # 转为Python时间格式
s.dt.total_seconds # 总秒数
# 个别用法举例
# 将时间转为UTC时间,再转为美国东部时间
s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
# 输出时间显示格式
s.dt.strftime('%Y/%m/%d')
时长数据访问器可以解析出时长的相关属性,最终产出一个结果序列:
# 创建数据
ts = pd.Series(pd.to_timedelta(np.arange(5), unit='hour'))
"""
0 0 days 00:00:00
1 0 days 01:00:00
2 0 days 02:00:00
3 0 days 03:00:00
4 0 days 04:00:00
dtype: timedelta64[ns]
"""
# 计算秒数
ts.dt.seconds
"""
0 0
1 3600
2 7200
3 10800
4 14400
dtype: int64
"""
# 转为Python时间格式
ts.dt.to_pytimedelta()
"""
array([datetime.timedelta(0), datetime.timedelta(0, 3600),
datetime.timedelta(0, 7200), datetime.timedelta(0, 10800),
datetime.timedelta(0, 14400)], dtype=object)
"""
(1)shift()方法可以在时序对象上实现向上或向下移动:
rng = pd.date_range('2020-11-01', '2020-11-04')
ts = pd.Series(range(len(rng)), index=rng)
"""
2020-11-01 0
2020-11-02 1
2020-11-03 2
2020-11-04 3
Freq: D, dtype: int64
"""
# 向上移动一位
ts.shift(-1)
"""
2020-11-01 1.0
2020-11-02 2.0
2020-11-03 3.0
2020-11-04 NaN
Freq: D, dtype: float64
"""
(2)shift方法接受freq频率参数,该参数可以接受DateOffset类或者其他类似timedelta的对象,也可以接受偏移别名:
# 向上移动一个工作日,11-01是周日
ts.shift(-1, freq='B')
"""
2020-10-30 0
2020-10-30 1
2020-11-02 2
2020-11-03 3
dtype: int64
"""
更换时间频率是将时间序列由一个频率单位更换为另一个频率单位,实现时间粒度的变化。
主要通过asfreq()方法来实现。
# 创建时间:频率为自然日的时间序列
rng = pd.date_range('2020-11-01', '2020-12-01')
ts = pd.Series(range(len(rng)), index=rng)
"""
2020-11-01 0
...
2020-12-01 30
Freq: D, dtype: int64
"""
(1)将频率变为更细的粒度,会产生缺失值:
# 频率转为12小时
ts.asfreq(pd.offsets.Hour(12))
"""
2020-11-01 00:00:00 0.0
2020-11-01 12:00:00 NaN
2020-11-02 00:00:00 1.0
2020-11-02 12:00:00 NaN
2020-11-03 00:00:00 2.0
...
2020-11-29 00:00:00 28.0
2020-11-29 12:00:00 NaN
2020-11-30 00:00:00 29.0
2020-11-30 12:00:00 NaN
2020-12-01 00:00:00 30.0
Freq: 12H, Length: 61, dtype: float64
"""
(2)对于缺失值可以用指定值或者指定方法来填充:
# 使用指定值填充
ts.asfreq(freq='12H', fill_value=0)
# 使用指定方法填充
ts.asfreq(pd.offsets.Hour(12), method='pad') # 重复上值
DateOffset类似于时间TimeDelta,但它使用日历中时间日期的规则,而不是直接进行时间性质的算术计算。
比如工作日就是常见的应用,周四办事,承诺三个工作日内完结,不是最迟周日,而是跳过周六周日,最迟周二办完。
(1)通过夏令时理解DateOffset对象
有些地区使用夏令时,每日偏移时间有可能是23或24小时,甚至25个小时。
# 生成一个指定的时间,芬兰赫尔辛基时间执行夏令时
t = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki')
# Timestamp('2016-10-30 00:00:00+0300', tz='Europe/Helsinki')
t + pd.Timedelta(days=1) # 增加一个自然天
# Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')
t + pd.DateOffset(days=1) # 增加一个时间偏移天
# Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
可以发现,与时长Timedelta不同,时间偏移DateOffset不是数学意义上的增加或减少,而是根据实际生活的日历对现有时间进行偏移。时长可以独立存在,作为业务的一个数据指标,而时间偏移DateOffset的意义是找到一个时间起点,对它进行时间移动。
(2)增加两个工作日
# 定义一个日期
d = pd.Timestamp('2020-10-30')
d # Timestamp('2020-10-30 00:00:00')
d.day_name() # 'Friday'
# 定义2个工作日时间偏移变量
two_business_days = 2 * pd.offsets.BDay()
# 增加两个工作日
two_business_days.apply(d)
d + two_business_days # 同上
# Timestamp('2020-11-03 00:00:00')
# 取增加两个工作日后的星期
(d + two_business_days).day_name() # 'Tuesday'
(3)所有的日期偏移对象都在pandas.tseries.offsets下,其中pandas.tseries.offsets.DateOffset是标准的日期范围时间偏移类型,默认是一个日历日。
from pandas.tseries.offsets import DateOffset
ts = pd.Timestamp('2020-01-01 09:10:11')
ts + DateOffset(months=3)
# Timestamp('2020-04-01 09:10:11')
ts + DateOffset(hours=2)
# Timestamp('2020-01-01 11:10:11')
ts + DateOffset() # 默认为1天
# Timestamp('2020-01-02 09:10:11')
DateOffset基本都支持频率字符串或偏移别名,传入freq参数,时间偏移的子类、子对象都支持时间偏移的相关操作。有效的日期偏移及频率字符串见表(略)。
可以将日期偏移别名组合,如3W(三周)、1h30min。
(1)Offset通过计算支持向前或向后偏移:
ts = pd.Timestamp('2020-06-06 00:00:00')
ts.day_name() # 'Saturday'
# 定义一个工作小时偏移,默认是周一到周五9~17点,我们从10点开始
offset = pd.offsets.BusinessHour(start='10:00')
# 向前偏移一个工作小时,是一个周一,跳过了周日
offset.rollforward(ts) # Timestamp('2020-06-08 10:00:00')
# 向前偏移至最近的工作日,小时也会增加
ts + offset # Timestamp('2020-06-08 11:00:00')
# 向后偏移,会在周五下班前的一个小时
offset.rollback(ts) # Timestamp('2020-06-05 17:00:00')
ts - pd.offsets.Day(1) # 昨日 Timestamp('2020-06-05 00:00:00')
ts - pd.offsets.Week(weekday=0) # 上个周一 Timestamp('2020-06-01 00:00:00')
ts - pd.offsets.MonthEnd() - pd.offsets.MonthBegin() # Timestamp('2020-05-01 00:00:00')
# 先回到上个月末,再从上个月末回到上个月初
(2)时间偏移操作会保留小时和分钟,有时候我们不在意具体时间,可以使用normalize进行标准化到午夜0点:
offset.rollback(ts).normalize()
# Timestamp('2020-06-05 00:00:00')
apply可以使偏移对象应用到一个时间上:
ts = pd.Timestamp('2020-06-01 09:00')
day = pd.offsets.Day() # 定义偏移对象
day.apply(ts) # Timestamp('2020-06-02 09:00:00')
(1)之前我们只偏移了偏移对象的一个单位,可以传入参数来偏移多个单位和对象中的其他单位:
import datetime
d = datetime.datetime(2020,6, 1,9, 0)
d + pd.offsets.Week() # 偏移一周
#Timestamp('2020-06-08 09:00:00')
d + pd.offsets.Week(weekday=4) # 偏移4周中的日期
# Timestamp('2020-06-05 09:00:00')
(2)参数也支持标准化:
d + pd.offsets.Week(normalize=True)
# Timestamp('2020-06-08 00:00:00')
(3)YearEnd支持用参数month指定月份:
d + pd.offsets.YearEnd()
# Timestamp('2020-12-31 09:00:00')
d + pd.offsets.YearEnd(month=6)
# Timestamp('2020-06-30 09:00:00')
(1)当使用日期作为索引的DataFrame时,此函数可以基于日期偏移量使用last选择最后几行,使用first选择前几行:
i = pd.date_range('2018-04-09', periods=4, freq='2D')
ts = pd.DataFrame({'A': [1, 2, 3, 4]}, index=i)
"""
A
2018-04-09 1
2018-04-11 2
2018-04-13 3
2018-04-15 4
"""
# 取最后三天,返回最近3天的数据
# 而不是数据集中最近3天的数据,因此未返回2018-04-11的数据
ts.last('3D')
"""
A
2018-04-13 3
2018-04-15 4
"""
# 前3天
ts.first('3D')
(2)可以用at_time() 来指定时间:
# 指定时间
ts.at_time('12:00')
(3)用between_time() 来指定时间区间:
ts.between_time('0:15', '0:45')
可以对Series或DatetimeIndex时间索引序列应用时间偏移,与其他时间序列数据一样,时间偏移后的数据一般作为索引。
(1)序列与时间偏移操作:
rng = pd.date_range('2020-01-01', '2020-01-03')
s = pd.Series(rng)
rng
# DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype='datetime64[ns]', freq='D')
s
"""
0 2020-01-01
1 2020-01-02
2 2020-01-03
dtype: datetime64[ns]
"""
rng + pd.DateOffset(months=2)
# DatetimeIndex(['2020-03-01', '2020-03-02', '2020-03-03'], dtype='datetime64[ns]', freq=None)
s + pd.DateOffset(months=2)
"""
0 2020-03-01
1 2020-03-02
2 2020-03-03
dtype: datetime64[ns]
"""
(2)序列与时长的操作:
s - pd.offsets.Day(2)
"""
0 2019-12-30
1 2019-12-31
2 2020-01-01
dtype: datetime64[ns]
"""
td = s - pd.Series(pd.date_range('2019-12-29', '2019-12-31'))
td
"""
0 3 days
1 3 days
2 3 days
dtype: timedelta64[ns]
"""
td + pd.offsets.Minute(15)
"""
0 3 days 00:15:00
1 3 days 00:15:00
2 3 days 00:15:00
dtype: timedelta64[ns]
"""
总结:
时长只能和pd.offsets.xxx运算; eg:td
时间序列数据只能和pd.DateOffset(xxx)运算。 eg:s
锚点是网页制作中超级链接的一种,又叫命名锚记,像一个迅速定位器。
对于某些频率,可以指定锚定后缀,让它支持在一定的时间开始或结束,比如可以将周频率从默认的周日调到周一’W-MON’,具体见表14-2(略)。
(1)对于固定在特定频率开始或结束(MonthEnd、MonthBegin、WeekEnd等)的偏移,向前和向后移动的规则是:当n不为0时,如果给定日期不在锚点上,则它会捕捉到下一个(上一个)锚点,并向前或向后移动|n|-1步。
pd.Timestamp('2020-01-02') + pd.offsets.MonthBegin(n=1)
# Timestamp('2020-02-01 00:00:00')
pd.Timestamp('2020-01-02') + pd.offsets.MonthEnd(n=1)
# Timestamp('2020-01-31 00:00:00')
pd.Timestamp('2020-01-02') - pd.offsets.MonthBegin(n=1)
# Timestamp('2020-01-01 00:00:00')
pd.Timestamp('2020-01-02') - pd.offsets.MonthEnd(n=1)
# Timestamp('2019-12-31 00:00:00')
eg:pd.Timestamp(‘2020-01-02’) + pd.offsets.MonthBegin(n=1)
n=1,则偏移|n|-1=0步,直接变成下一个月的月初。
(2)如果给定的日期在锚点上,则向前或向后移动|n|步:
给定日期在锚点上,指的应该是:如果日期恰好在月初,偏移至月初(即MonthBegin)。
pd.Timestamp('2020-01-01') + pd.offsets.MonthBegin(n=1)
# Timestamp('2020-02-01 00:00:00')
pd.Timestamp('2020-01-31') + pd.offsets.MonthEnd(n=1)
# Timestamp('2020-02-29 00:00:00')
(3)对于n=0的情况,如果在锚点上,则日期不会移动,否则会移动到下一个锚点:
pd.Timestamp('2020-01-01') + pd.offsets.MonthBegin(n=0)
# Timestamp('2020-01-01 00:00:00')
pd.Timestamp('2020-01-02') + pd.offsets.MonthBegin(n=0)
# Timestamp('2020-02-01 00:00:00')
(1)可以想Cday或CustomBusinessDay类传入节假日参数自定义一个工作日偏移对象:
weekmask_egypt = 'Sun Mon Tue Wed Thu'
# 定义出五一劳动节的日期(05-01)
holidays = ['2018-05-01', datetime.datetime(2019, 5, 1),
np.datetime64('2020-05-01')]
# 自定义工作日中传入休假日期,一个正常星期工作日的顺序
# holidays传入假期,weekmask传入工作日
bday_egypt = pd.offsets.CustomBusinessDay(holidays=holidays,
weekmask=weekmask_egypt)
# 指定一个日期
dt = datetime.datetime(2020, 4, 30)
# 偏移两个工作日,跳过了休假日
dt + 2 * bday_egypt
# Timestamp('2020-05-04 00:00:00')
我们输出星期对照观察,发现跳过了2020年5月1日(定义的休假日)和2020年5月2日(定义的工作周中为休息日):
# 输出时序及星期几
idx = pd.date_range(dt, periods=5, freq=bday_egypt)
pd.Series(idx.weekday+1, index=idx)
"""
2020-04-30 4
2020-05-03 7
2020-05-04 1
2020-05-05 2
2020-05-06 3
Freq: C, dtype: int64
"""
(2)BusinessHour表是开始和结束工作的小时时间,默认的工作时间是09:00-17:00,与时间相加超过一个小时会移到下一个小时,超过一天会移动到下一个工作日。
# 定义一个工作时间
bh = pd.offsets.BusinessHour()
bh # <BusinessHour: BH=09:00-17:00>
# 2020-08-01是周六
pd.Timestamp('2020-08-01 10:00').weekday() # 5
①在周末增加一个工作小时:
# 增加一个工作小时 (周末ver.)
pd.Timestamp('2020-08-01 10:00') + bh
# Timestamp('2020-08-03 10:00:00')
# 因为1号和2号都是周末,被跳过
# 相当于作了一个增加操作,因此在时间上没有变动
②在正常工作日(已经到了上班点 增加一个工作小时:
# 增加一个工作小时 (工作日ver.)
pd.Timestamp('2020-08-03 10:00') + bh
# Timestamp('2020-08-03 11:00:00')
③在正常工作日(未到上班点 增加一个工作小时:
# 一旦计算就开始上班
# 等同于pd.Timestamp('2020-08-03 09:00') + bh
pd.Timestamp('2020-08-03 08:00') + bh
# Timestamp('2020-08-03 10:00:00')
④在周五(计算后已经下班) 增加一个工作小时:
# 计算后已经下班,就移到下一个工作小时(跳过周末)
# 2020-07-31 周五
pd.Timestamp('2020-07-31 16:00') + bh
# Timestamp('2020-08-03 09:00:00')
(3)对于BusinessHour表自定义开始和结束工作的时间,格式必须是hour:minute字符串,不支持秒、微秒、纳秒。
# 11点开始上班
bh = pd.offsets.BusinessHour(start='11:00', end=datetime.time(20))
bh # <BusinessHour: BH=11:00-20:00>
(4)对于BusinessHour表,如果start时间晚于end时间表示夜班工作时间,此时,工作时间将从午夜延至第二天。
bh = pd.offsets.BusinessHour(start='17:00', end='09:00')
bh # <BusinessHour: BH=17:00-09:00>
pd.Timestamp('2014-08-01 17:00') + bh
# Timestamp('2014-08-01 18:00:00')
# 2014-08-02 周六
# 但是由于工作时间从周五17:00开始,因此也有效
pd.Timestamp('2014-08-02 04:00') + bh
# Timestamp('2014-08-02 05:00:00')
时间偏移与时长的不同是它是真实日历上的时间移动,在数据分析中时间偏移的意义是大于时长的。另外,通过继承pandas.tseries.holiday.AbstractHolidayCalendar创建子类,可以自定义假期日历,完成更为复杂的时间偏移操作。
注意区分:pd.DateOffset和pd.offsets.xxx
pandas中的Period()对象表示一个时间段,比如一年、一个月。与时间长度不同,它表示一个具体的时间区间,有时间起点和周期频率。
(1)利用pd.Period() 创建时间段对象:
# 创建一个时间段(年)
pd.Period('2020')
# Period('2020', 'A-DEC')
# 创建一个时间段(季度)
pd.Period('2020Q4')
# Period('2020Q4', 'Q-DEC')
以第一个为例,返回对象有两个值:第一个是这个时间段的起始时间,第二个字符串"A-DEC"中的A指年度(Annual),DEC指12月(December)。这个时间段对象代表一个在2020年结束语12月的全年时间段。
(2)传入更多参数:
# 2020-01-01全天的时间段
pd.Period(year=2020, freq='D')
# Period('2020-01-01', 'D')
# 一周
pd.Period('20201101', freq='W')
# Period('2020-10-26/2020-11-01', 'W-SUN')
# 默认周期,对应到最细粒度--分钟
pd.Period('2020-11-11 23:00')
# Period('2020-11-11 23:00', 'T')
# 指定周期
pd.Period('2020-11-11 23:00', 'D')
# Period('2020-11-11', 'D')
# 定义时间段
p = pd.Period('2020Q4')
(1)获取开始和结束时间:
# 开始与结束时间
p.start_time
# Timestamp('2020-10-01 00:00:00')
p.end_time
# Timestamp('2020-12-31 23:59:59.999999999')
(2)如果当前时间段不符合业务实际,可以转换频率:
p.asfreq('D') # 转换频率为天
# Period('2020-12-31', 'D')
p.asfreq('D', how='start')
# Period('2020-10-01', 'D')
(3)其他属性如下:
p.freq # <QuarterEnd: startingMonth=12>(时间偏移对象)
p.freqstr # 'Q-DEC' (时间偏移别名)
p.is_leap_year # True 是否闰年
p.to_timestamp() # Timestamp('2020-10-01 00:00:00')
# 以下日期取时间段内最后一天 (即针对2020.12.31)
p.day # 31
p.dayofweek # 3 周四
p.dayofyear # 366 一年中的第几天
p.hour # 0
p.week
p.minute
p.qyear # 2020 财年(财经年度)
p.year
p.days_in_month # 31 当月第几天
p.daysinmonth # 31 当月共多少天
p.strftime('%Y年%m月') # 格式化时间
(1)时间段可以做加减法,表示将此时间段前移或后移相应单位:
# 在2020Q4上增加一个周期
pd.Period('2020Q4') + 1
# Period('2021Q1', 'Q-DEC')
(2)时间段对象也可以和时间偏移对象做加减:
# 增加一小时
pd.Period('20200101 15') + pd.offsets.Hour(1)
# Period('2020-01-01 16:00', 'H')
tips:如果偏移量频率与时间段不同,则其单位要大于时间段频率,否则会报错:
pd.Period('20200101 14') + pd.offsets.Minute(10)
# 偏移量频率分钟,小于时间段频率,因此报错
# IncompatibleFrequency: Input cannot be converted to Period(freq=H)
(3)时间段和时间差相加减:
pd.Period('20200101 14') + pd.Timedelta('1 days')
# Period('2020-01-02 14:00', 'H')
(4)相同频率的时间段实例之差将返回它们之间的频率单位数:
pd.Period('20200101 14') - pd.Period('20200101 10')
# <4 * Hours>
pd.Period('2020Q4') - pd.Period('2020Q1')
# <3 * QuarterEnds: startingMonth=12>
(1)类似于时间范围pd.date_range()生成时序索引数据,pd.period_range() 可以生成时间段索引数据:
# 生成时间段索引对象
pd.period_range('2020-11-01 10:00', periods=10, freq='H')
"""
PeriodIndex(['2020-11-01 10:00', '2020-11-01 11:00', '2020-11-01 12:00',
'2020-11-01 13:00', '2020-11-01 14:00', '2020-11-01 15:00',
'2020-11-01 16:00', '2020-11-01 17:00', '2020-11-01 18:00',
'2020-11-01 19:00'],
dtype='period[H]', freq='H')
"""
上例生成了时间段索引对象,它从2020年11月1日10点开始,频率为小时,共有10个周期,数据类型period[H]可以看到频率。时间段索引对象可以用于时序索引,也可以用于Series和DataFrame中的数据。
(2)指定开始和结束时间:
pd.period_range('2020Q1', '2021Q4', freq='Q-NOV')
"""
PeriodIndex(['2020Q1', '2020Q2', '2020Q3', '2020Q4', '2021Q1', '2021Q2',
'2021Q3', '2021Q4'],
dtype='period[Q-NOV]', freq='Q-NOV')
"""
上例定义了一个从2020年第一季度到2021第四季度共8个季度的时间段,一年以11月为最后时间。
(3)通过时间段对象来定义:
pd.period_range(start=pd.Period('2020Q1', freq='Q'),
end=pd.Period('2021Q2', freq='Q'), freq='M')
"""
PeriodIndex(['2020-03', '2020-04', '2020-05', '2020-06', '2020-07', '2020-08',
'2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02',
'2021-03', '2021-04', '2021-05', '2021-06'],
dtype='period[M]', freq='M')
"""
(4)时间段索引可以应用于数据中:
pd.Series(pd.period_range('2020Q1', '2021Q4', freq='Q-NOV'))
"""
0 2020Q1
1 2020Q2
2 2020Q3
3 2020Q4
4 2021Q1
5 2021Q2
6 2021Q3
7 2021Q4
dtype: period[Q-NOV]
"""
pd.Series(range(8), index=pd.period_range('2020Q1', '2021Q4', freq='Q-NOV'))
"""
2020Q1 0
2020Q2 1
2020Q3 2
2020Q4 3
2021Q1 4
2021Q2 5
2021Q3 6
2021Q4 7
Freq: Q-NOV, dtype: int64
"""
数据查询方法和时序查询一致,支持切片操作:
# 建立索引数据
s = pd.Series(1, index=pd.period_range('2020-10-01 10:00', '2021-10-01 10:00', freq='H'))
"""
2020-10-01 10:00 1
2020-10-01 11:00 1
2020-10-01 12:00 1
2020-10-01 13:00 1
2020-10-01 14:00 1
..
2021-10-01 06:00 1
2021-10-01 07:00 1
2021-10-01 08:00 1
2021-10-01 09:00 1
2021-10-01 10:00 1
Freq: H, Length: 8761, dtype: int64
"""
s['2020']
"""
2020-10-01 10:00 1
2020-10-01 11:00 1
2020-10-01 12:00 1
2020-10-01 13:00 1
2020-10-01 14:00 1
..
2020-12-31 19:00 1
2020-12-31 20:00 1
2020-12-31 21:00 1
2020-12-31 22:00 1
2020-12-31 23:00 1
Freq: H, Length: 2198, dtype: int64
"""
s['2020-10':'2020-11']
"""
2020-10-01 10:00 1
2020-10-01 11:00 1
2020-10-01 12:00 1
2020-10-01 13:00 1
2020-10-01 14:00 1
..
2020-11-30 19:00 1
2020-11-30 20:00 1
2020-11-30 21:00 1
2020-11-30 22:00 1
2020-11-30 23:00 1
Freq: H, Length: 1454, dtype: int64
"""
astype() 可以在几种数据之间自由转换。
(1)DatetimeIndex转PeriodIndex:
ts = pd.date_range('20201101', periods=100)
"""
DatetimeIndex(['2020-11-01', '2020-11-02', '2020-11-03', '2020-11-04',
...
'2021-02-05', '2021-02-06', '2021-02-07', '2021-02-08'],
dtype='datetime64[ns]', freq='D')
"""
# 转为PeriodIndex,频率为月
ts.astype('period[M]')
"""
PeriodIndex(['2020-11', '2020-11', '2020-11', '2020-11', '2020-11', '2020-11',
...
'2021-02', '2021-02', '2021-02', '2021-02'],
dtype='period[M]', freq='M')
"""
(2)PeriodIndex转DatetimeIndex:
ts = pd.period_range('2020-11', periods=100, freq='M')
"""
PeriodIndex(['2020-11', '2020-12', '2021-01', '2021-02', '2021-03', '2021-04',
...
'2028-11', '2028-12', '2029-01', '2029-02'],
dtype='period[M]', freq='M')
"""
# 转为DatetimeIndex
ts.astype('datetime64[ns]')
"""
DatetimeIndex(['2020-11-01', '2020-12-01', '2021-01-01', '2021-02-01',
...
'2028-11-01', '2028-12-01', '2029-01-01', '2029-02-01'],
dtype='datetime64[ns]', freq='MS')
"""
(3)PeriodIndex转换频率:
# 频率从月转为季度
ts.astype('period[Q]')
"""
PeriodIndex(['2020Q4', '2020Q4', '2021Q1', '2021Q1', '2021Q1', '2021Q2',
...
'2028Q4', '2028Q4', '2029Q1', '2029Q1'],
dtype='period[Q-DEC]', freq='Q-DEC')
"""
本节介绍一些通用时间操作和高级功能。
Pandas使用pytz或dateutil库或标准库中的datetime.timezone对象为使用不同时区的时间戳提供了丰富的支持。
(1)查看所有时区及时区的字符名称:
import pytz
print(pytz.common_timezones) # 显示所有时区
print(pytz.timezone) # <function timezone at 0x000001F618F99E18>
(2)如果没有指定,时间一般不带时区:
ts = pd.date_range('11/11/2020 00:00', periods=10, freq='D')
ts.tz is None # True
(3)简单的时区指定,中国通用的北京时区使用’Asia/Shanghai’定义:
pd.date_range('2020-01-01', periods=10, freq='D', tz='Asia/Shanghai')
"""
DatetimeIndex(['2020-01-01 00:00:00+08:00', '2020-01-02 00:00:00+08:00',
'2020-01-03 00:00:00+08:00', '2020-01-04 00:00:00+08:00',
'2020-01-05 00:00:00+08:00', '2020-01-06 00:00:00+08:00',
'2020-01-07 00:00:00+08:00', '2020-01-08 00:00:00+08:00',
'2020-01-09 00:00:00+08:00', '2020-01-10 00:00:00+08:00'],
dtype='datetime64[ns, Asia/Shanghai]', freq='D')
"""
pd.Timestamp('2020-01-01', tz='Asia/Shanghai')
# Timestamp('2020-01-01 00:00:00+0800', tz='Asia/Shanghai')
(4)指定时区的更多方法:
# 使用pytz
rng_pytz = pd.date_range('11/11/2020 00:00', periods=3,
freq='D', tz='Europe/London')
rng_pytz.tz
# <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
还可以使用dateutil支持,使用dateutil指定为UTC时间,在这里不再赘述。
(5)从一个时区转为另外一个时区,使用tz_convert:
rng_pytz.tz_convert('US/Eastern')
"""
DatetimeIndex(['2020-11-10 19:00:00-05:00', '2020-11-11 19:00:00-05:00',
'2020-11-12 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq='D')
"""
在数据格式解析、输出格式和格式转换过程中,需要用标识符来匹配日期元素的位置,Pandas使用了Python的格式化符号系统。
import locale # 保证格式化能够正常执行
locale.setlocale(locale.LC_CTYPE, 'chinese')
# 解析时间格式
pd.to_datetime('2020*11*12', format='%Y*%m*%d')
# Timestamp('2020-11-12 00:00:00')
# 输出的时间格式
pd.Timestamp('now').strftime('%Y年%m月%d日')
# Timestamp('2022-10-26 17:35:41.008394')
# '2022年10月26日'
Python中日期和时间的格式化符号见表14-3(略)。
Pandas可以对时序数据按不同的频率进行重采样操作。
(1)原时序数据频率为分钟,使用resample() 可以按5分钟、15分钟、半小时等频率分组,然后完成聚合计算。
idx = pd.date_range('2020-01-01', periods=500, freq='Min')
ts = pd.Series(range(len(idx)), index=idx)
"""
2020-01-01 00:00:00 0
2020-01-01 00:01:00 1
2020-01-01 00:02:00 2
2020-01-01 00:03:00 3
2020-01-01 00:04:00 4
...
2020-01-01 08:15:00 495
2020-01-01 08:16:00 496
2020-01-01 08:17:00 497
2020-01-01 08:18:00 498
2020-01-01 08:19:00 499
Freq: T, Length: 500, dtype: int64
"""
# 每5分钟进行一次聚合
ts.resample('5Min').sum()
"""
2020-01-01 00:00:00 10
2020-01-01 00:05:00 35
2020-01-01 00:10:00 60
2020-01-01 00:15:00 85
2020-01-01 00:20:00 110
...
2020-01-01 07:55:00 2385
2020-01-01 08:00:00 2410
2020-01-01 08:05:00 2435
2020-01-01 08:10:00 2460
2020-01-01 08:15:00 2485
Freq: 5T, Length: 100, dtype: int64
"""
(2)可以指定许多不同参数来控制频率转换和重采样操作。通过类似于groupby聚合后的各种统计函数来实现数据的分组聚合,包括sum、mean、std、sem、max、min、median、first、last和ohlc。
ts.resample('5Min').mean() # 平均
"""
2020-01-01 00:00:00 2
2020-01-01 00:05:00 7
2020-01-01 00:10:00 12
2020-01-01 00:15:00 17
2020-01-01 00:20:00 22
...
2020-01-01 07:55:00 477
2020-01-01 08:00:00 482
2020-01-01 08:05:00 487
2020-01-01 08:10:00 492
2020-01-01 08:15:00 497
Freq: 5T, Length: 100, dtype: int64
"""
(3)ohlc,又叫美国线(Open-High-Low-Close chart, OHLC chart),可以呈现类似股票的开盘价、最高价、最低价和收盘价。
# 两小时频率的美国线
ts.resample('2h').ohlc()
"""
open high low close
2020-01-01 00:00:00 0 119 0 119
2020-01-01 02:00:00 120 239 120 239
2020-01-01 04:00:00 240 359 240 359
2020-01-01 06:00:00 360 479 360 479
2020-01-01 08:00:00 480 499 480 499
"""
(4)closed参数可以设为“left”或“right”,以指定开闭区间的哪一端:
ts.resample('2h', closed='left').mean()
"""
2020-01-01 00:00:00 59.5
2020-01-01 02:00:00 179.5
2020-01-01 04:00:00 299.5
2020-01-01 06:00:00 419.5
2020-01-01 08:00:00 489.5
Freq: 2H, dtype: float64
"""
(5)label参数可以控制输出结果显示左还是右,但不像closed那样影响计算结果。
ts.resample('5Min').mean() # 默认label='left'
"""
2020-01-01 00:00:00 2
2020-01-01 00:05:00 7
2020-01-01 00:10:00 12
2020-01-01 00:15:00 17
2020-01-01 00:20:00 22
...
2020-01-01 07:55:00 477
2020-01-01 08:00:00 482
2020-01-01 08:05:00 487
2020-01-01 08:10:00 492
2020-01-01 08:15:00 497
Freq: 5T, Length: 100, dtype: int64
"""
ts.resample('5Min', label='right').mean()
"""
2020-01-01 00:05:00 2
2020-01-01 00:10:00 7
2020-01-01 00:15:00 12
2020-01-01 00:20:00 17
2020-01-01 00:25:00 22
...
2020-01-01 08:00:00 477
2020-01-01 08:05:00 482
2020-01-01 08:10:00 487
2020-01-01 08:15:00 492
2020-01-01 08:20:00 497
Freq: 5T, Length: 100, dtype: int64
"""
上采样一般应用在图形图像学中,目的是放大图像。由于原数据有限,放大图像后需要对缺失值进行内插值填充。在时序数据同样存在着类似的问题。
上例中的数据频率是分钟,对其按30s重采样:
ts.head(3).resample('30S').asfreq()
"""
2020-01-01 00:00:00 0.0
2020-01-01 00:00:30 NaN
2020-01-01 00:01:00 1.0
2020-01-01 00:01:30 NaN
2020-01-01 00:02:00 2.0
Freq: 30S, dtype: float64
"""
此时,发现由于原数据粒度不够,出现缺失值,需要用**.ffill()和.bfill()**来计算填充值:
# 补充的值和前值一样
ts.head(3).resample('30S').ffill()
"""
2020-01-01 00:00:00 0
2020-01-01 00:00:30 0
2020-01-01 00:01:00 1
2020-01-01 00:01:30 1
2020-01-01 00:02:00 2
Freq: 30S, dtype: int64
"""
# 补充的值和后值一样
ts.head(3).resample('30S').bfill()
(1)重采样适用于相关的统计聚合方法:
df = pd.DataFrame(np.random.randn(1000, 3),
index=pd.date_range('1/1/2020', freq='S', periods=1000),
columns=['A', 'B', 'C'])
# 生成Resampler重采样对象
r = df.resample('3T')
r.mean()
"""
A B C
2020-01-01 00:00:00 -0.039264 0.081512 0.063693
2020-01-01 00:03:00 -0.023642 0.027389 -0.127066
2020-01-01 00:06:00 0.022440 -0.100795 0.181529
2020-01-01 00:09:00 0.115326 0.046553 0.043965
2020-01-01 00:12:00 -0.067841 0.089492 0.065904
2020-01-01 00:15:00 -0.088980 -0.061358 -0.039684
"""
(2)多个聚合方式:
r['A'].agg([np.sum, np.mean, np.std])
r.agg([np.sum, np.mean]) # 每个列
# 不同的聚合方式
r.agg({'A': np.sum,
'B': lambda x: np.std(x, ddof=1)})
# 用字符指定
r.agg({'A': 'sum', 'B': 'std'})
r.agg({'A': ['sum', 'std'], 'B': ['mean', 'std']})
(3)如果索引不是时间,可以指定采样的时间列:
# date是一个普通列
df.resample('M', on='date').sum()
df.resample('M', level='d').sum() # 多层索引
(4)迭代采样对象:
# r是重采样对象
for name, group in r:
print("Group: ", name)
print("-" * 20)
print(group, end='\n\n')
(1)to_period() 将DatetimeIndex转化为PeriodIndex:
pd.date_range('1/1/2020', periods=5)
"""
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
'2020-01-05'],
dtype='datetime64[ns]', freq='D')
"""
# 转换为时间周期
pd.date_range('1/1/2020', periods=5).to_period()
"""
PeriodIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
'2020-01-05'],
dtype='period[D]', freq='D')
"""
(2)to_timestamp()将默认周期的开始时间转换为DatetimeIndex:
pd.period_range('1/1/2020', periods=5)
"""
PeriodIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
'2020-01-05'],
dtype='period[D]', freq='D')
"""
# 转换为时序索引
pd.period_range('1/1/2020', periods=5).to_timestamp()
"""
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
'2020-01-05'],
dtype='datetime64[ns]', freq='D')
"""
Pandas原生支持的时间范围大约在1677-2262年,如果分析时间不在这个区间,应该怎么办呢?
# 定义一个超限时间周期
pd.period_range('1111-01-01', '8888-01-01', freq='D')
"""
PeriodIndex(['1111-01-01', '1111-01-02', '1111-01-03', '1111-01-04',
'1111-01-05', '1111-01-06', '1111-01-07', '1111-01-08',
'1111-01-09', '1111-01-10',
...
'8887-12-23', '8887-12-24', '8887-12-25', '8887-12-26',
'8887-12-27', '8887-12-28', '8887-12-29', '8887-12-30',
'8887-12-31', '8888-01-01'],
dtype='period[D]', length=2840493, freq='D')
"""
可以正常计算和使用。还可以将时间以数字形式保存,在计算的时候再转换为周期数据:
(pd.Series([123_1111, 2008_10_01, 8888_12_12])
# 将整型转为时间周期类型
.apply(lambda x: pd.Period(year=x // 10000,
month=x // 100 % 100,
day=x % 100,
freq='D')
)
)
"""
0 0123-11-11
1 2008-10-01
2 8888-12-12
dtype: period[D]
"""
Pandas.Interval可以解决数字区间和时间区间的相关问题,它实现一个名为Interval的不可变对象,该对象是一个有界的切片状间隔。
(1)Interval对象构建:
pd.Interval(left=0, right=5, closed='right')
# Interval(0, 5, closed='right')
# 4是否在1~10之间
4 in pd.Interval(1, 10) # True
(2)参数定义如下:
(3)Interval可以对数字、固定时间、时长起作用
①构建数字类型间隔的方法:
iv = pd.Interval(left=0, right=5) # Interval(0, 5, closed='right')
# 可以检查元素是否属于
3.5 in iv # True
②创建时间区间间隔:
# 定义2020年的区间
year_2020 = pd.Interval(pd.Timestamp('2020-01-01 00:00:00'),
pd.Timestamp('2021-01-01 00:00:00'),
closed='left')
# 检查指定时间是否在此区间内
pd.Timestamp('2020-01-01 00:00') in year_2020 # True
# 2020年时间区间的长度
year_2020.length
# Timedelta('366 days 00:00:00')
③创建时长区间间隔:
# 定义一个时长区间,3秒到1天
time_deltas = pd.Interval(pd.Timedelta('3 seconds'),
pd.Timedelta('1 days'),
closed='both')
# 时长区间长度
time_deltas.length
# Timedelta('0 days 23:59:57')
(4)pd.Interval支持以下属性:
# 区间闭合之处
iv.closed # 'right'
# 检查间隔是否在左侧关闭
iv.closed_left # False
# 间隔是否为空,表示该间隔不包含任何点
iv.is_empty # False
# 间隔的左边界
iv.left # 0
# 间隔的中点
iv.mid
# 间隔的长度
iv.length
# 间隔是否在左侧为开区间
iv.open_left # True
(5)其中,Interval.is_empty指示间隔是否为空,表示该间隔不包含任何点。
pd.Interval(0, 1, closed='right').is_empty # False
# 不包含任何点的间隔为空
pd.Interval(0, 0, closed='right').is_empty # True
pd.Interval(0, 0, closed='left').is_empty # True
pd.Interval(0, 0, closed='neither').is_empty # True
# 包含单个点的间隔不为空
pd.Interval(0, 0, closed='both').is_empty # False
# 一个IntervalArray或IntervalIndex返回一个布尔ndarray
# 它在位置上指示Interval是否为空
ivs = [pd.Interval(0, 0, closed='neither'),
pd.Interval(1, 2, closed='neither')]
pd.arrays.IntervalArray(ivs).is_empty
# array([ True, False])
# 缺失值不为空
ivs = [pd.Interval(0, 0, closed='neither'), np.nan]
pd.IntervalIndex(ivs).is_empty
# array([ True, False])
(6)pd.Interval.overlaps检查两个Interval对象是否重叠。如果两个间隔至少共享一个公共点(包括封闭的端点),则它们重叠。
①重叠:
i1 = pd.Interval(0, 2)
i2 = pd.Interval(1, 3)
i1.overlaps(i2) # True
②共享封闭端点的间隔重叠:
i4 = pd.Interval(0, 1, closed='both')
i5 = pd.Interval(1, 2, closed='both')
i4.overlaps(i5) # True
③只有共同的开放端点的间隔不重叠:
i6 = pd.Interval(1, 2, closed='neither')
i4.overlaps(i6) # False
(7)间隔对象能使用+和*与一个固定值进行计算,此操作将同时应用于对象的两个边界,结果取决于绑定边界值数据的类型。
iv
# Interval(0, 5, closed='right')
shifted_iv = iv + 3
# Interval(3, 8, closed='right')
extended_iv = iv * 10.0
# Interval(0.0, 50.0, closed='right')
(8)Pandas不支持两个区间的合并、取交集等操作,可以使用Python的第三方库portion实现。