(四)Python时间序列整理

一、Python处理时间序列数据的库

numpy, pandas -> 可以处理各种时间类型
datetime
time
calendar

二、Python时间序列数据类型

2.1 基础类型:时间戳和时间段

库名时间序列数据类型
datetimedatetime, timedelta
numpydatetime64, timedelta64
pandas
scikitstimeseries

pandas支持4种常见的时间概念:

  1. 日期时间 ( Datetime): 带时区的日期时间, 类似于标准库的 datetime.datetime
  2. 时间差 (Timedelta ) : 绝对时间周期, 类似于标准库的 datetime.timedelta
  3. 时间段 ( Timespan ): 在某一时点以指定频率定义的时间跨度
  4. 日期偏移 ( Dateoffset ): 与日历运算对应的时间段, 类似 dateutil 的 dateutil.relativedelta.relativedelta
时间概念标量类数组类Pandas数据类型主要构建方法
DatetimeTimestampDatetimeIndexdatetime64[ns] or datetime64[ns, tz]to_datetime或 date_range
TimedeltaTimedeltaTimedeltaIndextimedelta64[ns]to_timedelta 或 timedelta_range
TimespanPeriodPeriodIndexperiod[freq]Period 或 period_range
DateoffsetDateOffsetNoneNonepd.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

TimestampPeriod 可以用作索引。作为索引的 TimestampPeriod 列表则被强制转换为对应的 DatetimeIndexPeriodIndex

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()  # 查看星期几
PropertyDescription
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 OffsetFrequency StringDescription
DateOffsetNone
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

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

tseries文档

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

参考文档

设定 weekmaskholidays 参数,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不能自定义解析参数,如 dayfirstformat

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 支持把整数或浮点数纪元时间转换为 TimestampDatetimeIndex。鉴于 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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值