文章目录
一、Python处理时间序列数据的库
numpy, pandas -> 可以处理各种时间类型
datetime
time
calendar
二、Python时间序列数据类型
2.1 基础类型:时间戳和时间段
库名 | 时间序列数据类型 |
---|---|
datetime | datetime, timedelta |
numpy | datetime64, timedelta64 |
pandas | |
scikits | timeseries |
pandas支持4种常见的时间概念:
- 日期时间 ( Datetime): 带时区的日期时间, 类似于标准库的
datetime.datetime
- 时间差 (Timedelta ) : 绝对时间周期, 类似于标准库的
datetime.timedelta
- 时间段 ( Timespan ): 在某一时点以指定频率定义的时间跨度
- 日期偏移 ( Dateoffset ): 与日历运算对应的时间段, 类似 dateutil 的
dateutil.relativedelta.relativedelta
时间概念 | 标量类 | 数组类 | Pandas数据类型 | 主要构建方法 |
---|---|---|---|---|
Datetime | Timestamp | DatetimeIndex | datetime64[ns] or datetime64[ns, tz] | to_datetime或 date_range |
Timedelta | Timedelta | TimedeltaIndex | timedelta64[ns] | to_timedelta 或 timedelta_range |
Timespan | Period | PeriodIndex | period[freq] | Period 或 period_range |
Dateoffset | DateOffset | None | None | pd.DateOffset() |
-
两个datetime类型的时间戳 可以时间相减;一个时间戳类型 可以直接加上一个 timedelta类型
-
时间戳
timestamp
特定的时刻
固定时期period
如 2017年1月 或 2010年全年
时间间隔interval
由 起始时间 和 结束时间表示
(period 可以看做是 interval 的特例) -
pandas.Timestamp 的最大最小值
pd.Timestamp.min
Timestamp('1677-09-21 00:12:43.145225')
pd.Timestamp.max
Timestamp('2262-04-11 23:47:16.854775807')
2.1.1 创建时间戳 Timestamp
- pandas.Timestamp
pd.Timestamp(datetime.datetime(2012, 5, 1))
'''
Timestamp('2012-05-01 00:00:00')
'''
pd.Timestamp('2012-05-01')
'''
Timestamp('2012-05-01 00:00:00')
'''
pd.Timestamp(2012, 5, 1)
'''
Timestamp('2012-05-01 00:00:00')
'''
dates = [pd.Timestamp('2012-05-01'), pd.Timestamp('2012-05-02'), pd.Timestamp('2012-05-03')]
ts = pd.Series(np.random.randn(3), dates)
type(ts.index)
'''
pandas.core.indexes.datetimes.DatetimeIndex
'''
ts
'''
2012-05-01 0.469112
2012-05-02 -0.282863
2012-05-03 -1.509059
'''
- datetime.datetime
datetime.datetime(2019,1,1) # 输出 datetime.datetime(2019, 1, 1, 0, 0)
- numpy.datetime64
np.datetime64('2018-01-01') # 输出 numpy.datetime64('2018-01-01')
2.1.2 pandas创建时间段 Period
- 大多数情况下,用时间段改变变量 更自然。 Period 表示的时间段 更直观, 还可以用 日期时间格式的字符串 进行推断
pd.Period('2011-01')
'''
Period('2011-01', 'M')
'''
pd.Period('2012-05', freq='D')
'''
Period('2012-05-01', 'D')
'''
periods = [pd.Period('2012-01'), pd.Period('2012-02'), pd.Period('2012-03')]
ts = pd.Series(np.random.randn(3), periods)
type(ts.index)
'''
pandas.core.indexes.period.PeriodIndex
'''
ts.index
'''
PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]', freq='M')
'''
ts
'''
2012-01 -1.135632
2012-02 1.212112
2012-03 -0.173215
'''
2.1.3 pandas创建时间索引 DatetimeIndex、PeriodIndex
Timestamp
与 Period
可以用作索引。作为索引的 Timestamp
与 Period
列表则被强制转换为对应的 DatetimeIndex
与 PeriodIndex
。
Pandas 可以识别这两种表现形式,并在两者之间进行转化。
- Pandas 后台用
Timestamp
实例代表时间戳
,用DatetimeIndex
实例代表时间戳序列
。- Pandas 用
Period
对象表示符合规律的时间段标量值
,用PeriodIndex
表示时间段序列
。未来版本将支持用任意起止时间实现不规律时间间隔。
- pd.DatetimeIndex
pd.DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05']) # 构建时间戳索引
# 输出 DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)
# 创建 DatetimeIndex 时,传递字符串 infer 即可推断索引的频率(如果没有指定,DatetimeIndex里的freq会是None)
pd.DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], freq='infer')
# 输出 DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D')
2.2 用日期进行索引
对已经解析成datetime的日期字段(或索引),可以进行切片。
ts['1/31/2011']
ts[datetime.datetime(2011, 12, 25):]
ts['10/31/2011':'12/31/2011']
ts['2011']
'''
2011-01-31 0.119209
2011-02-28 -1.044236
2011-03-31 -0.861849
2011-04-29 -2.104569
2011-05-31 -0.494929
2011-06-30 1.071804
2011-07-29 0.721555
2011-08-31 -0.706771
2011-09-30 -1.039575
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BM, dtype: float64
'''
ts['2011-6']
'''
2011-06-30 1.071804
Freq: BM, dtype: float64
'''
- 例子
dft = pd.DataFrame(np.random.randn(100000, 1), columns=['A'], index=pd.date_range('20130101', periods=100000, freq='T'))
# 索引的时候可以任意组合粒度
dft['2013']
dft['2013-1':'2013-2']
dft['2013-1':'2013-2-28']
dft['2013-1':'2013-2-28 00:00:00']
dft['2013-1-15':'2013-1-15 12:30:00']
index = pd.date_range(start = '2019-01-01', periods = 12, freq = pd.offsets.MonthBegin())
ser_1 = pd.Series(np.random.randn(12), index = index)
ser_1
'''
2019-01-01 1.222325
2019-02-01 0.041146
2019-03-01 0.647153
2019-04-01 0.237300
2019-05-01 0.449988
2019-06-01 -0.246733
2019-07-01 -0.699097
2019-08-01 1.334139
2019-09-01 1.900882
2019-10-01 -1.299197
2019-11-01 -0.037303
2019-12-01 0.249758
Freq: MS, dtype: float64
'''
display(ser_1.truncate(before = '2019-04-01'))
# 等价于 ser_1['2019-04-01':]
'''
2019-04-01 0.237300
2019-05-01 0.449988
2019-06-01 -0.246733
2019-07-01 -0.699097
2019-08-01 1.334139
2019-09-01 1.900882
2019-10-01 -1.299197
2019-11-01 -0.037303
2019-12-01 0.249758
Freq: MS, dtype: float64
'''
- 一个多重索引的例子
dft2 = pd.DataFrame(np.random.randn(20, 1),
columns=['A'],
index=pd.MultiIndex.from_product(
[pd.date_range('20130101', periods=10, freq='12H'),
['a', 'b']]))
dft2.loc['2013-01-05']
idx = pd.IndexSlice
dft2 = dft2.swaplevel(0, 1).sort_index()
dft2.loc[idx[:, '2013-01-05'], :]
2.3 日期空值 NaT
Pandas 用 NaT
表示日期时间、时间差及时间段的空值,代表了缺失日期或空日期的值,类似于浮点数的 np.nan
。
In [24]: pd.Timestamp(pd.NaT)
Out[24]: NaT
In [25]: pd.Timedelta(pd.NaT)
Out[25]: NaT
In [26]: pd.Period(pd.NaT)
Out[26]: NaT
# 与 np.nan 一样,pd.NaT 不等于 pd.NaT
In [27]: pd.NaT == pd.NaT
Out[27]: False
三、查看时间属性(待更新)
friday = pd.Timestamp('2018-01-01')
friday.day_name() # 查看星期几
Property | Description |
---|---|
year | |
month | |
day | |
hour | |
minute | |
second | |
microsecond | |
nanosecond | |
date | |
time | |
timez | |
dayofyear | |
weekofyear | |
week | |
dayofweek | |
weekday | |
quarter | |
days_in_month | |
is_month_start | |
is_month_end | |
is_quarter_start | |
is_quarter_end | |
is_year_start | |
is_yearend | |
is_leap_year |
idx = pd.date_range(start='2019-12-29', freq='D', periods=4)
idx.isocalendar()
'''
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
'''
idx.to_series().dt.isocalendar()
'''
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
'''
四、处理时间序列数据
4.1 采样/重采样、时间频率
# 生成小时数据
idx = pd.date_range('2019-01-01', periods=5, freq = 'H')
idx
"""
输出:
DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 01:00:00',
'2019-01-01 02:00:00', '2019-01-01 03:00:00',
'2019-01-01 04:00:00'],
dtype='datetime64[ns]', freq='H')
"""
ts = pd.Series(range(len(idx)), index = idx)
ts
"""
输出:
2019-01-01 00:00:00 0
2019-01-01 01:00:00 1
2019-01-01 02:00:00 2
2019-01-01 03:00:00 3
2019-01-01 04:00:00 4
Freq: H, dtype: int64
"""
# 重采样(对value指定聚合函数)
ts.resample('2H').mean()
"""
输出:
2019-01-01 00:00:00 0.5
2019-01-01 02:00:00 2.5
2019-01-01 04:00:00 4.0
Freq: 2H, dtype: float64
"""
# 生成天数据
pd.Series(pd.date_range('2000', freq = 'D', periods = 3))
"""
输出:
0 2000-01-01
1 2000-01-02
2 2000-01-03
dtype: datetime64[ns]
"""
pd.Series(range(3), index = pd.date_range('2000', freq = 'D', periods = 3) )
"""
输出:
2000-01-01 0
2000-01-02 1
2000-01-03 2
Freq: D, dtype: int64
"""
# 生成月份数据
pd.Series(pd.period_range('1/1/2011', freq = 'M', periods=3))
"""
输出:
0 2011-01
1 2011-02
2 2011-03
dtype: period[M]
"""
# 注意 如果是用date_range的话, 生成的是每个月的最后一天
pd.Series(pd.date_range('1/1/2011', freq = 'M', periods=3))
"""
输出:
0 2011-01-31
1 2011-02-28
2 2011-03-31
dtype: datetime64[ns]
"""
# Series 与 DataFrame 提供了 datetime、timedelta 、Period 扩展类型与专有用法,不过,Dateoffset 则保存为 object。
pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
"""
输出:
0 <DateOffset>
1 <2 * DateOffsets>
dtype: object
"""
# 通过 start 和 end 生成指定时间范围内的时间
pd.date_range('2000-01-01', '2000-06-01')
pd.date_range('2000-01-01', '2000-06-01', freq = 'BM') # 生成由 每月最后一个工作日 构成的一组时间序列
pd.date_range('2000-01-01', '2000-06-01', freq = 'M') # 生成由 每月最后一天 构成的一组时间序列
# date_range 默认的频率是日历日,bdate_range 的默认频率是工作日:
start = datetime.datetime(2011, 1, 1)
end = datetime.datetime(2011, 1, 10)
index = pd.date_range(start, end)
index
"""
输出:
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
'2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
'2011-01-09', '2011-01-10'],
dtype='datetime64[ns]', freq='D')
"""
# 只选出【工作日】
index = pd.bdate_range(start, end)
index
"""
输出:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10'],
dtype='datetime64[ns]', freq='B')
"""
# freq 也可以设置为 小时、分 等的偏移量, 如
freq = '4h', freq = '4h30min'
# 指定的 起始 和 结束日期,可能带有时间,可以用 normalize 参数 进行标准化
pd.date_range('2000-01-01 05:10;30‘, periods = 50, normalieze = True)
4.1.1 pandas.date_range生成时间序列
- date_range 和 bdate_range 可以调用各种
频率别名
In [80]: pd.date_range(start, periods=1000, freq='M')
Out[80]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
'2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
'2011-09-30', '2011-10-31',
...
'2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
'2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
'2094-03-31', '2094-04-30'],
dtype='datetime64[ns]', length=1000, freq='M')
In [81]: pd.bdate_range(start, periods=250, freq='BQS')
Out[81]:
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
'2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
'2013-01-01', '2013-04-01',
...
'2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
'2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
'2073-01-02', '2073-04-03'],
dtype='datetime64[ns]', length=250, freq='BQS-JAN')
In [82]: pd.date_range(start, end, freq='BM')
Out[82]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
'2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
dtype='datetime64[ns]', freq='BM')
In [83]: pd.date_range(start, end, freq='W')
Out[83]:
DatetimeIndex(['2011-01-02', '2011-01-09', '2011-01-16', '2011-01-23',
'2011-01-30', '2011-02-06', '2011-02-13', '2011-02-20',
'2011-02-27', '2011-03-06', '2011-03-13', '2011-03-20',
'2011-03-27', '2011-04-03', '2011-04-10', '2011-04-17',
'2011-04-24', '2011-05-01', '2011-05-08', '2011-05-15',
'2011-05-22', '2011-05-29', '2011-06-05', '2011-06-12',
'2011-06-19', '2011-06-26', '2011-07-03', '2011-07-10',
'2011-07-17', '2011-07-24', '2011-07-31', '2011-08-07',
'2011-08-14', '2011-08-21', '2011-08-28', '2011-09-04',
'2011-09-11', '2011-09-18', '2011-09-25', '2011-10-02',
'2011-10-09', '2011-10-16', '2011-10-23', '2011-10-30',
'2011-11-06', '2011-11-13', '2011-11-20', '2011-11-27',
'2011-12-04', '2011-12-11', '2011-12-18', '2011-12-25',
'2012-01-01'],
dtype='datetime64[ns]', freq='W-SUN')
In [84]: pd.bdate_range(end=end, periods=20)
Out[84]:
DatetimeIndex(['2011-12-05', '2011-12-06', '2011-12-07', '2011-12-08',
'2011-12-09', '2011-12-12', '2011-12-13', '2011-12-14',
'2011-12-15', '2011-12-16', '2011-12-19', '2011-12-20',
'2011-12-21', '2011-12-22', '2011-12-23', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', freq='B')
In [85]: pd.bdate_range(start=start, periods=20)
Out[85]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14', '2011-01-17', '2011-01-18',
'2011-01-19', '2011-01-20', '2011-01-21', '2011-01-24',
'2011-01-25', '2011-01-26', '2011-01-27', '2011-01-28'],
dtype='datetime64[ns]', freq='B')
- 采样频率freq/offset参数值
Date Offset | Frequency String | Description |
---|---|---|
DateOffset | None | |
BDay | ‘B’ | |
CDay | ‘C’ | |
Week | ‘W’ | |
WeekofMonth | ‘WOM’ | |
LastWeekMonth | ‘LWOM’ | |
MonthEnd | ‘M’ | |
MonthBegin | ‘MS’(等价于 freq = pd.offsets.MonthBegin() ) | |
BMonthEnd | ‘BM’ | |
BMonthBegin | ‘BMS’ | |
CBMonthEnd | ‘CBM’ | |
CBMonthBegin | ‘CBMS’ | |
QuarterEnd | ’Q‘ | |
QuarterBegin | ’QS‘ | |
BQuarterEnd | ’BQ‘ | |
BQuarterBegin | ‘BQS’ | |
YearEnd | ‘A’ | |
YearBegin | ‘AS’或’BYS’ | |
BYearEnd | ‘BA’ | |
BYearBegin | ‘BAS’ | |
Day | ‘D’ | |
Hour | ‘H’ | |
Minute | ‘T’或’min’ | |
Second | ‘S’ |
- 复合参数值
pd.date_range(start, periods=10, freq='2h20min')
'''
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140T')
'''
pd.date_range(start, periods=10, freq='1D10U')
'''
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010U')
'''
4.1.2 重采样 resample
将 高频率数据 聚合到 低频率数据,称为 降采样 (downsampling
),比如 日->月
将 低频率数据 转换到 高频率数据,称为 升采样 (upsampling
),比如 月->日
- 注意:如 W-WED(每周三) 转换为 W-FRI(每周五),既不是升采样,也不是降采样
1)降采样(需要指定聚合函数)
index = pd.date_range('1/1/2000', periods=9, freq='T')
series = pd.Series(range(9), index=index)
series
'''
2000-01-01 00:00:00 0
2000-01-01 00:01:00 1
2000-01-01 00:02:00 2
2000-01-01 00:03:00 3
2000-01-01 00:04:00 4
2000-01-01 00:05:00 5
2000-01-01 00:06:00 6
2000-01-01 00:07:00 7
2000-01-01 00:08:00 8
'''
series.resample('3T').sum()
'''
2000-01-01 00:00:00 3
2000-01-01 00:03:00 12
2000-01-01 00:06:00 21
'''
# 降采样聚合后,默认会用做标签,如果需要用右标签需要指定label='right'
# 一般时间粒度下的label默认值都是'left',但也有默认值是'right'的('M', 'A', 'Q', 'BM', 'BA', 'BQ', 'W')
series.resample('3T', label='right').sum()
'''
2000-01-01 00:03:00 3
2000-01-01 00:06:00 12
2000-01-01 00:09:00 21
'''
series.resample('3T', label='right', closed='right').sum()
'''
2000-01-01 00:00:00 0
2000-01-01 00:03:00 6
2000-01-01 00:06:00 15
2000-01-01 00:09:00 15
'''
# 统计每个月 最后一个交易日情况 (business day)
apple_month = data9.resample('BM').last()
apple_month.head()
# 也可以自定义指定聚合函数
def customer_resampler(array_like):
return np.sum(array_like)+5
series.resample('3T').apply(customer_resampler)
'''
2000-01-01 00:00:00 8
2000-01-01 00:03:00 17
2000-01-01 00:06:00 26
'''
df.resample('M', how = 'mean', kind = 'period')
#等价于
ts.groupby(lambda x: x.month).mean()
ts.groupby(lambda x: x.weekday).mean()
- 如果时间字段不是index,则可以通过on参数来指定要resample的时间字段。
df.resample('M', on='week_starting').mean()
- 如果改变了采样频率,且希望是连续的日期。那么,对于没有值的日期,可以用fill函数自动填充对应的值。
# 填补连续日期中的缺失值
## 方法一: 用索引来做转换
data.set_index(pd.to_datetime(data['report_date'])) # 将日期字段作为索引, 可以通过 data.index 查看索引的数据类型
pdate = pd.date_range(start = '2019-01-01', end = '2019-01-31')
data_new = data.reset_index(pdate, fill_value = 0) # 用生成的连续日期pdate作为索引, 没有值的日期填上0
## 方法二: 使用DataFrame的 resample方法 按照"天"重采样
## resample含义: 改变数据的时间频率, 比如把"天"数据变成"月", 或者把"小时"变成"分钟" ( 可以是粒度变细, 也可以是粒度变粗 )
## 常见的频率可以搜索 "Offset Alias"
data = data.set_index(pd.to_datetime(data['report_date'])).drop('report_date', axis = 1) # 同样, 将日期字段作为索引
data_new2 = data.resample('D').mean().fillna(0) # 由于采样可能会让区间变成一个值, 所以需要指定mean等聚合方法
2)升采样
s = pd.Series([1,2], index=pd.period_range('2012-01-01', freq='A', periods=2)
'''
2012 1
2013 2
Freq: A-DEC, dtype: int64
'''
s.resample('Q', convention='start').asfreq()
'''
2012Q1 1.0
2012Q2 NaN
2012Q3 NaN
2012Q4 NaN
2013Q1 2.0
2013Q2 NaN
2013Q3 NaN
2013Q4 NaN
'''
4.1.3 日期偏移量 offset
pandas.tseries.offset
pandas.offsets
pandas.DateOffset()
1)pandas.tseries.offsets
from pandas.tseries.offsets import Hour, Minute;
four_hour = Hour(4)
# 利用 锚点偏移量 对日期 进行位移
from pandas.tseries.offsets import Day, MonthEnd
offset = MonthEnd();
offset.rollforward(now);
offset.rollbackward(now)
# 结合groupby使用
df.groupby(offset.rollforward).mean()
# 更简单实现上面这个功能的还是 resample
df.resample('M', how = 'mean')
2)pandas.offsets
friday = pd.Timestamp('2018-01-05')
friday.day_name()
'''
'Friday'
'''
saturday = friday+pd.Timedelta('1 day')
monday = friday+pd.offsets.BDay()
ts - pd.offsets.Day(2)
td + pd.offsets.Minute(15)
pd.Timestamp('2014-01-02') - pd.offsets.MonthBegin(n=1)
pd.Timestamp('2014-01-02') - pd.offsets.MonthEnd(n=1)
3)pandas.DateOffset()
ts = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki')
# 注意一下两个返回的结果不一样
ts + pd.Timedelta(days=1)
'''
Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')
'''
ts + pd.DateOffset(days=1)
'''
Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
'''
ts + pd.DateOffset(months=2)
4.1.4 自定义节假日或工作日 custom holiday
设定 weekmask
与 holidays
参数,bdate_range
还可以生成自定义频率日期范围。这些参数只用于传递自定义字符串。
[88]: weekmask = 'Mon Wed Fri'
In [89]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]
In [90]: pd.bdate_range(start, end, freq='C', weekmask=weekmask, holidays=holidays)
Out[90]:
DatetimeIndex(['2011-01-03', '2011-01-07', '2011-01-10', '2011-01-12',
'2011-01-14', '2011-01-17', '2011-01-19', '2011-01-21',
'2011-01-24', '2011-01-26',
...
'2011-12-09', '2011-12-12', '2011-12-14', '2011-12-16',
'2011-12-19', '2011-12-21', '2011-12-23', '2011-12-26',
'2011-12-28', '2011-12-30'],
dtype='datetime64[ns]', length=154, freq='C')
In [91]: pd.bdate_range(start, end, freq='CBMS', weekmask=weekmask)
Out[91]:
DatetimeIndex(['2011-01-03', '2011-02-02', '2011-03-02', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-02', '2011-10-03', '2011-11-02', '2011-12-02'],
dtype='datetime64[ns]', freq='CBMS')
再比如我们定义埃及的工作日是周日~周四。其中5.1是节假日
weekmask_egypt = 'Sun Mon Tue Wed Thu'
holidays = ['2012-05-01',datetime.datetime(2013, 5, 1),np.datetime64('2014-05-01')]
bday_egypt = pd.offsets.CustomBusinessDay(holidays=holidays,weekmask=weekmask_egypt)
# 用一天来测试一下
dt = datetime.datetime(2013, 4, 30)
dt + 2 * bday_egypt
# 生成指定条件下的工作日
dts = pd.date_range(dt, periods=5, freq=bday_egypt)
pd.Series(dts.weekday, dts).map(pd.Series('Mon Tue Wed Thu Fri Sat Sun'.split()))
holiday calendar 可以提供节假日的列表
from pandas.tseries.holiday import USFederalHolidayCalendar
bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
dt = datetime.datetime(2014, 1, 17)
dt + bday_us
在自定义节假日的前提下,取出每个月的第一个工作日
bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
dt = datetime.datetime(2013, 12, 17)
dt + bmth_us
pd.date_range(start='20100101', end='20120101', freq=bmth_us)
'''
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
'''
4.1.5 移动时间方法 ts.shift()(计算同环比)
ts = pd.Series(np.random.randn(10), index = pd.date_range('2000-01-01', periods = 10))
ts / ts.shift(1, freq = 'D') - 1 --天环比
ts / ts.shift(1, freq = 'M') - 1 --月环比
ts / ts.shift(1, freq = '12M') - 1 --月同比
ts / ts.shift(1, freq = '3D') - 1
ts.shift(5, freq=pd.offsets.BDay())
ts.shift(5, freq='BM')
4.1.6 频率转换方法 ts.asfreq()
dr = pd.date_range('1/1/2010', periods=3, freq=3 * pd.offsets.BDay())
ts = pd.Series(np.random.randn(3), index=dr)
ts.asfreq(pd.offsets.BDay())
ts.asfreq(pd.offsets.BDay(), method='pad') # 如果是升采样,数据粒度增加了,可以指定pad来填充NaN的数据
4.2 日期转换
4.2.1 pandas.to_datetime 解析
pandas能够解析 时间格式字符串, np.datetime64, datetime.datetime 等多种时间序列数据。
pd.to_datetime 和 pd.Timestamp,如果传入的只是简单的单字符串,两者转换成时间戳的结果是一样的。但是pd.Timestamp不能自定义解析参数,如 dayfirst
或format
pd.to_datetime('2020/01/01')
pd.Timestamp('2020/01/01')
- 简单的自动解析
dti = pd.to_datetime(['1/1/2018', np.datetime64('2018-01-01'), datetime.datetime(2018,1,1,)])
dti
# 输出 DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None)
pd.to_datetime(pd.Series(['Jul 31, 2009', '2010-01-10', None]))
"""
0 2009-07-31
1 2010-01-10
2 NaT
dtype: datetime64[ns]
"""
# 解析欧式日期(日-月-年),要用 dayfirst 关键字参数 ( 相当于说 欧式日期 日在月前面):
# 但同时, dayfirst 并没有那么严苛,如果不能把第一个数解析为日,就会以 dayfirst 为 False 进行解析。
pd.to_datetime(['04-01-2012 10:00'], dayfirst=True)
# 输出 DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)
pd.to_datetime(['14-01-2012', '01-14-2012'], dayfirst=True)
# 输出 DatetimeIndex(['2012-01-14', '2012-01-14'], dtype='datetime64[ns]', freq=None)
- 传递format参数, 实现精准转换
# 要实现精准转换,除了传递 datetime 字符串,还要指定 format 参数,指定此参数还可以加速转换速度
pd.to_datetime('2010/11/12', format='%Y/%m/%d')
# 输出 Timestamp('2010-11-12 00:00:00')
pd.to_datetime('12-11-2010 00:00', format='%d-%m-%Y %H:%M')
# 输出 Timestamp('2010-11-12 00:00:00')
- 还可以把
DataFrame里的整数或字符串
组合成Timestamp Series
df = pd.DataFrame({'year': [2015, 2016],
'month': [2, 3],
'day': [4, 5],
'hour': [2, 3]})
pd.to_datetime(df)
"""
输出:
0 2015-02-04 02:00:00
1 2016-03-05 03:00:00
dtype: datetime64[ns]
"""
# 只传递 DataFrame中的某些列 也可以
pd.to_datetime(df[['year', 'month', 'day']])
"""
输出:
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
"""
pd.to_datetime 查找列名里日期时间组件的标准名称,包括:
必填:year、month、day
可选:
hour、minute、second、
millisecond、microsecond、
nanosecond
- 当日期格式解析有问题时, 可以通过
error='raise'
触发错误抛出
# 默认值 `error = 'raise'` 会`触发错误`
pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
# errors='ignore' 返回原始输入:
pd.to_datetime(['2009/07/31', 'asd'], errors='ignore')
# 输出 Index(['2009/07/31', 'asd'], dtype='object')
# errors='coerce' 把无法解析的数据转换为 NaT,即不是时间(Not a Time):
pd.to_datetime(['2009/07/31', 'asd'], errors='coerce')
# 输出 DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)
- 使用 to_datetime时, 可以指定
单位 unit
pandas 支持把整数或浮点数纪元时间转换为
Timestamp
与DatetimeIndex
。鉴于Timestamp
对象内部存储方式,这种转换的默认单位是纳秒
。不过,一般都会用指定其它时间单位unit
来存储纪元数据,纪元时间从origin
参数指定的时点开始计算。
pd.to_datetime([1349720105, 1349806505, 1349892905,
1349979305, 1350065705], unit='s')
"""
输出:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
"""
pd.to_datetime([1349720105100, 1349720105200, 1349720105300,
1349720105400, 1349720105500], unit='ms')
"""
输出:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
"""
# unit 默认是纳秒, 那么 给到这么长的时间戳, 就能正确解析到纳秒的时间
pd.to_datetime(1262347200000000000)
# 输出 Timestamp('2010-01-01 12:00:00')
- 转换相对日期
默认值为 origin='unix'
,即 1970-01-01 00:00:00
,一般把这个时点称为 unix 纪元
或 POSIX
时间。
pd.to_datetime([1, 2, 3], unit='D', origin=pd.Timestamp('1960-01-01'))
'''
DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)
'''
pd.to_datetime([1, 2, 3], unit='D')
'''
DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)
'''
4.2.2 时间戳 转换为字符串
- strftime
datetime.datetime.strftime(datetime.datetime.strptime('1900-01-01', '%Y-%m-%d'), '%Y%m%d')
4.2.3 字符串 转换为时间戳
- strptime
import datetime
import time
time.strptime(value, '%Y-%m-%d')
datetime.datetime.strptime('1900-01-01', '%Y-%m-%d')
datetime.datetime.strptime('1900-01-01', '%Y-%m-%d').year
datetime.datetime.strptime('1900-01-01', '%Y-%m-%d').month
datetime.datetime.strptime('1900-01-01', '%Y-%m-%d').day
# 将 14JUN09:17:58:34 这种格式的字符串, 转换为时间戳
def to_time(t):
out_t = time.mktime(time.strptime(t, '%d%b%y:%H:%M:%S'))
return out_t
a = '14JUN09:17:58:34'
print(to_time(a))
'''
1244973514.0 # mktime出来的结果是以秒为单位的
'''
- dateutil.parser.parse
这个包非常强大,几乎可以解析 所有 人类能够理解的 日期表示形式
from dateutil.parser import parse
parse('Jan, 31, 2000 10:45 AM')
- pandas.to_datetime
pandas 的 to_datetime 方法,也可以快速 批量 解析日期格式
datestrs = ['2000-01-01', '2010-01-01']
pd.to_datetime(datestrs)
4.2.4 日期转换为unix时间
unix时间的开始是1970-01-01 00:00:00 ,以秒来计
所以通过倒减计算秒数,可以反推出unix时间
stamps = pd.date_range('2012-10-08 18:15:05', periods=4, freq='D')
stamps
'''
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
'''
(stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta('1s')
'''
Int64Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
'''
4.2.5 时间戳 转换为纪元
首先与纪元开始时点(1970 年 1 月 1 日午夜,UTC)相减,然后以 1 秒为时间单位(unit='1s'
)取底整除。
stamps = pd.date_range('2012-10-08 18:15:05', periods=4, freq='D')
(stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta('1s')
# 输出 Int64Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
4.3 带时区的时间数据
- 获取当前的时间
# 方法一: 输出结果是 的字符串
import time
time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
# 输出: '2019-12-07 18:11:06'
time.localtime(time.time())
# 输出: time.struct_time(tm_year=2019, tm_mon=12, tm_mday=7, tm_hour=18, tm_min=10, tm_sec=36, tm_wday=5, tm_yday=341, tm_isdst=0)
# 方法二:
import datetime
datetime.datetime.now()
# 输出: datetime.datetime(2019, 12, 7, 18, 10, 32, 500984)
# 这种输出形式 可以通过 year, month, day, hour, minute, second 来获取对应的 年月日 时分秒
datetime.datetime.now().year
datetime.datetime.now().month
datetime.datetime.now().day
datetime.datetime.now().hour
datetime.datetime.now().minute
datetime.datetime.now().second
- 时区转换
df.index.tz #查看时区
df.index.tz_localize('UTC') #指定本地时区
df.index.tz_convert('US/Eastern') #转换为其他时区的时间
dti = dti.tz_localize('UTC') # 将 dti中现在存的时间, 打上UTC时区
dti
"""
输出:
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 00:00:00+00:00',
'2018-01-01 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq=None)
"""
dti.tz_convert('US/Pacific') # 将UTC时间, 转换为 US/Pacific时间
"""
输出:
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 16:00:00-08:00',
'2017-12-31 16:00:00-08:00'],
dtype='datetime64[ns, US/Pacific]', freq=None)
"""
4.4 日期与时间计算
- 用绝对或相对时间差 计算日期与时间
friday = pd.Timestamp('2018-01-05')
friday.day_name() # 输出 'Friday'
saturday = friday + pd.Timedelta('1 day') # 添加1个日历日
saturday.day_name() # 输出 'Saturday'
monday = friday + pd.offsets.BDay() # 指定添加1个工作日
monday.day_name() # 输出 'Monday'
- 计算两个日期的间隔
- 方法一: 用 np.datetime64 这种类型
# 日期相减, 计算两个日期之间间隔几小时
df['diff_time'] = (df['tm_1'] - df['tm_2']).values/np.timedelta64(1, 'h')
# 日期相减, 计算两个日期之间间隔几天
df['diff_time'] = (df['tm_1'] - df['tm_2']).values/np.timedelta64(1, 'D')
- 方法二: 用datetime相减, 得到 Timedelta 类型, 然后调用 Timedelta类型的 days, seconds 等属性
date1 = '2018-11-26 09:30:45'
date2 = '16/Nov/2018:08:44:34'
d1 = datetime.datetime.strptime(date1, '%Y-%m-%d %H:%M:%S')
d2 = datetime.datetime.strptime(date2, '%d/%b/%Y:%H:%M:%S')
d = d1 - d2
print('相差的天数:{}'.format(d.days))
print('相差的秒数:{}'.format(d.seconds))
# 如果是时间戳的索引,也能直接相减
# 数据中 最早的日期和最晚的日期 相差多少天
print((data9.index.max()-data9.index.min()).days)
- 三种日期类型各自的 定义和转换 (np.datetime64, datetime, timestamp)
4.5 日期构造
- 简单的日期构造
import datetime
import time
# 将日期中的年份 -100, 再用 datetime.date 构造回日期
datetime.date(datetime.datetime.strptime('1900-01-01', '%Y-%m-%d').year+100, 1, 1)
# 将 datetime 小时部分去掉, 只保留日期部分
all_user['time'] = pd.to_datetime(all_user['time']).dt.date
# 将 两列日期相减 得到的timedelta 只保留天的部分
(pd.to_datetime(data['date1']) - pd.to_datetime(data['date2'])).days # 只保留天
(pd.to_datetime(data['date1']) - pd.to_datetime(data['date2'])).total_seconds() # 保留为秒
# 构造当前年第一天
datetime.date(datetime.datetime.now().year, 1, 1)
# 构造当前月第一天
datetime.date(datetime.datetime.now().year, datetime.datetime.now().month, 1)
- 年份如果只有后两位的话,pandas会自动加成20前缀。但有时候是不对的,需要定义一个函数来修复这个bug
import datetime
def fix_century(x):
year = x.year - 100 if x.year>1989 else x.year
return datetime.date(year, x.month, x.day)
data6['Yr_Mo_Dy'] = data6['Yr_Mo_Dy'].apply(fix_century) # 手动把年份减了100年,这样就正常了, 不过字段类型是object
data6['Yr_Mo_Dy'] = pd.to_datetime(data6['Yr_Mo_Dy']) # 将字段类型转化为 datetime64
data6 = data6.set_index('Yr_Mo_Dy') # 将日期字段设置为索引
4.6 日期查询
#查看日期位数为6的记录
#方法一:使用字符串的endswith函数
df[df['日期'].map(lambda x:x.strftime("%Y-%m-%d").endswith("6"))]
#方法二:用datetime模块
from datetime import datetime
df[df['日期'].map(lambda x:str(datetime.date(x)).endswith('16'))]
五、一般常用的时间获取
def get_common_dt_var():
from datetime import datetime, date,timedelta
import dateutil.relativedelta
common_dt_var = {}
current_ts = datetime.now()
common_dt_var['current_ts_str'] = current_ts.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
common_dt_var['current_ts_unix'] = int(current_ts.timestamp())
common_dt_var['current_hour'] = current_ts.hour
today = date.today()
common_dt_var['today_dt_str'] = today.strftime("%Y-%m-%d")
common_dt_var['yesterday_dt_str'] = (today - timedelta(days=1)).strftime("%Y-%m-%d")
first_day_of_current_mon_dt = today.replace(day=1)
common_dt_var['first_day_of_current_mon_dt_str'] = first_day_of_current_mon_dt.strftime("%Y-%m-%d")
first_day_of_last_mon_dt = first_day_of_current_mon_dt + dateutil.relativedelta.relativedelta(months=-1)
common_dt_var['first_day_of_last_mon_dt_str'] = first_day_of_last_mon_dt.strftime("%Y-%m-%d")
common_dt_var['first_day_of_this_year_dt_str'] = first_day_of_current_mon_dt.replace(month=1).strftime("%Y-%m-%d")
return common_dt_var
get_common_dt_var()