Pandas 2.2 中文官方教程和指南(二十一·三)

2024-05-24 16:23:23 浏览数 (1)

部分字符串索引

可以将日期和解析为时间戳的字符串作为索引参数传递:

代码语言:javascript复制
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428

In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]: 
2011-12-30    0.56702
Freq: BME, dtype: float64

In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]: 
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BME, dtype: float64 

为了方便访问更长的时间序列,也可以将年份或年份和月份作为字符串传递:

代码语言:javascript复制
In [103]: ts["2011"]
Out[103]: 
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: BME, dtype: float64

In [104]: ts["2011-6"]
Out[104]: 
2011-06-30    1.071804
Freq: BME, dtype: float64 

这种切片方式也适用于具有DatetimeIndexDataFrame。由于部分字符串选择是一种标签切片的形式,端点将被包括在内。这将包括在包含日期上匹配时间:

警告

使用单个字符串对DataFrame行进行索引(例如frame[dtstring])已在 pandas 1.2.0 中弃用(由于不确定是索引行还是选择列而存在歧义),并将在将来的版本中删除。相应的.loc(例如`frame.loc[dtstring])仍受支持。

代码语言:javascript复制
In [105]: dft = pd.DataFrame(
 .....:    np.random.randn(100000, 1),
 .....:    columns=["A"],
 .....:    index=pd.date_range("20130101", periods=100000, freq="min"),
 .....: )
 .....: 

In [106]: dft
Out[106]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

In [107]: dft.loc["2013"]
Out[107]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns] 

这从月初开始,包括月底的日期和时间:

代码语言:javascript复制
In [108]: dft["2013-1":"2013-2"]
Out[108]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns] 

这指定了一个包括最后一天所有时间的停止时间:

代码语言:javascript复制
In [109]: dft["2013-1":"2013-2-28"]
Out[109]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns] 

这指定了一个精确的停止时间(与上述不同):

代码语言:javascript复制
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns] 

我们在包含的端点上停止,因为它是索引的一部分:

代码语言:javascript复制
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]: 
 A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00  0.941451
2013-01-15 00:02:00  1.559365
2013-01-15 00:03:00  1.034374
2013-01-15 00:04:00 -1.480656
...                       ...
2013-01-15 12:26:00  0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00  0.066510
2013-01-15 12:30:00 -0.003945

[751 rows x 1 columns] 

DatetimeIndex部分字符串索引也适用于具有MultiIndexDataFrame

代码语言:javascript复制
In [112]: 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"]]
 .....:    ),
 .....: )
 .....: 

In [113]: dft2
Out[113]: 
 A
2013-01-01 00:00:00 a -0.298694
 b  0.823553
2013-01-01 12:00:00 a  0.943285
 b -1.479399
2013-01-02 00:00:00 a -1.643342
...                         ...
2013-01-04 12:00:00 b  0.069036
2013-01-05 00:00:00 a  0.122297
 b  1.422060
2013-01-05 12:00:00 a  0.370079
 b  1.016331

[20 rows x 1 columns]

In [114]: dft2.loc["2013-01-05"]
Out[114]: 
 A
2013-01-05 00:00:00 a  0.122297
 b  1.422060
2013-01-05 12:00:00 a  0.370079
 b  1.016331

In [115]: idx = pd.IndexSlice

In [116]: dft2 = dft2.swaplevel(0, 1).sort_index()

In [117]: dft2.loc[idx[:, "2013-01-05"], :]
Out[117]: 
 A
a 2013-01-05 00:00:00  0.122297
 2013-01-05 12:00:00  0.370079
b 2013-01-05 00:00:00  1.422060
 2013-01-05 12:00:00  1.016331 

使用字符串索引进行切片也遵守 UTC 偏移。

代码语言:javascript复制
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))

In [119]: df
Out[119]: 
 0
2019-01-01 00:00:00-08:00  0

In [120]: df["2019-01-01 12:00:00 04:00":"2019-01-01 13:00:00 04:00"]
Out[120]: 
 0
2019-01-01 00:00:00-08:00  0 
```### 切片 vs. 精确匹配

使用作为索引参数的相同字符串,根据索引的分辨率,可以将其视为切片或精确匹配。如果字符串比索引不准确,则将其视为切片,否则视为精确匹配。

考虑一个具有分钟分辨率索引的`Series`对象:

```py
In [121]: series_minute = pd.Series(
 .....:    [1, 2, 3],
 .....:    pd.DatetimeIndex(
 .....:        ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
 .....:    ),
 .....: )
 .....: 

In [122]: series_minute.index.resolution
Out[122]: 'minute' 

比分钟精度低的时间戳字符串会给出一个Series对象。

代码语言:javascript复制
In [123]: series_minute["2011-12-31 23"]
Out[123]: 
2011-12-31 23:59:00    1
dtype: int64 

具有分钟分辨率(或更精确)的时间戳字符串会给出一个标量,即不会转换为切片。

代码语言:javascript复制
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1

In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1 

如果索引分辨率为秒,则具有分钟精度的时间戳会给出一个Series

代码语言:javascript复制
In [126]: series_second = pd.Series(
 .....:    [1, 2, 3],
 .....:    pd.DatetimeIndex(
 .....:        ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
 .....:    ),
 .....: )
 .....: 

In [127]: series_second.index.resolution
Out[127]: 'second'

In [128]: series_second["2011-12-31 23:59"]
Out[128]: 
2011-12-31 23:59:59    1
dtype: int64 

如果将时间戳字符串视为切片,它也可以用于使用.loc[]索引DataFrame

代码语言:javascript复制
In [129]: dft_minute = pd.DataFrame(
 .....:    {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
 .....: )
 .....: 

In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]: 
 a  b
2011-12-31 23:59:00  1  4 

警告

但是,如果将字符串视为精确匹配,DataFrame[]中的选择将按列而不是按行进行,参见索引基础知识。例如,dft_minute['2011-12-31 23:59']将引发KeyError,因为'2012-12-31 23:59'的分辨率与索引相同,没有这样的列名:

为了始终具有明确的选择,无论行是作为切片还是单个选择,都使用.loc

代码语言:javascript复制
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64 

还要注意,DatetimeIndex的分辨率不能比天更精确。

代码语言:javascript复制
In [132]: series_monthly = pd.Series(
 .....:    [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
 .....: )
 .....: 

In [133]: series_monthly.index.resolution
Out[133]: 'day'

In [134]: series_monthly["2011-12"]  # returns Series
Out[134]: 
2011-12-01    1
dtype: int64 
精确索引

如前一节所讨论的,使用部分字符串索引DatetimeIndex取决于周期的“准确性”,换句话说,与索引的分辨率相比间隔的具体性。相比之下,使用Timestampdatetime对象进行索引是精确的,因为这些对象具有确切的含义。这些也遵循包括两个端点的语义。

这些Timestampdatetime对象具有精确的小时,分钟,即使它们没有明确指定(它们为0)。

代码语言:javascript复制
In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns] 

没有默认值。

代码语言:javascript复制
In [136]: dft[
 .....:    datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
 .....:        2013, 2, 28, 10, 12, 0
 .....:    )
 .....: ]
 .....: 
Out[136]: 
 A
2013-01-01 10:12:00  0.565375
2013-01-01 10:13:00  0.068184
2013-01-01 10:14:00  0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00  0.931536
...                       ...
2013-02-28 10:08:00  0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00  0.139348
2013-02-28 10:11:00  0.085288
2013-02-28 10:12:00  0.950146

[83521 rows x 1 columns] 
截断和花式索引

提供了一个类似于切片的 truncate() 方便函数。请注意,truncate 假设 DatetimeIndex 中未指定日期组件的任何部分的值为 0,而切片则返回任何部分匹配的日期:

代码语言:javascript复制
In [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")

In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)

In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
Freq: W-SUN, dtype: float64

In [140]: ts2["2011-11":"2011-12"]
Out[140]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
2011-12-04    0.046611
2011-12-11    0.059478
2011-12-18   -0.286539
2011-12-25    0.841669
Freq: W-SUN, dtype: float64 

即使是破坏了 DatetimeIndex 频率规律的复杂花式索引也会导致一个 DatetimeIndex,尽管频率会丢失:

代码语言:javascript复制
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None) 
部分字符串索引

可以将日期和解析为时间戳的字符串作为索引参数传递:

代码语言:javascript复制
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428

In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]: 
2011-12-30    0.56702
Freq: BME, dtype: float64

In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]: 
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BME, dtype: float64 

为了方便访问更长的时间序列,你也可以将年份或年份和月份作为字符串传递:

代码语言:javascript复制
In [103]: ts["2011"]
Out[103]: 
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: BME, dtype: float64

In [104]: ts["2011-6"]
Out[104]: 
2011-06-30    1.071804
Freq: BME, dtype: float64 

这种类型的切片也适用于具有 DatetimeIndexDataFrame。由于部分字符串选择是一种标签切片形式,因此端点将被包括在内。这将包括在包含日期的匹配时间:

警告

使用单个字符串通过 getitem(例如 frame[dtstring])对 DataFrame 行进行索引在 pandas 1.2.0 中已弃用(因为它存在将行索引与列选择混淆的歧义),并将在将来的版本中删除。使用 .loc 相当于(例如 frame.loc[dtstring])仍然受支持。

代码语言:javascript复制
In [105]: dft = pd.DataFrame(
 .....:    np.random.randn(100000, 1),
 .....:    columns=["A"],
 .....:    index=pd.date_range("20130101", periods=100000, freq="min"),
 .....: )
 .....: 

In [106]: dft
Out[106]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

In [107]: dft.loc["2013"]
Out[107]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns] 

这从月份的第一天开始,并包括月份的最后日期和时间:

代码语言:javascript复制
In [108]: dft["2013-1":"2013-2"]
Out[108]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns] 

这指定了一个包含最后一天所有时间的停止时间:

代码语言:javascript复制
In [109]: dft["2013-1":"2013-2-28"]
Out[109]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns] 

这指定了一个确切的停止时间(与上述不同):

代码语言:javascript复制
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns] 

我们在包含的结束点停止,因为它是索引的一部分:

代码语言:javascript复制
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]: 
 A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00  0.941451
2013-01-15 00:02:00  1.559365
2013-01-15 00:03:00  1.034374
2013-01-15 00:04:00 -1.480656
...                       ...
2013-01-15 12:26:00  0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00  0.066510
2013-01-15 12:30:00 -0.003945

[751 rows x 1 columns] 

DatetimeIndex 部分字符串索引也适用于具有 MultiIndexDataFrame

代码语言:javascript复制
In [112]: 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"]]
 .....:    ),
 .....: )
 .....: 

In [113]: dft2
Out[113]: 
 A
2013-01-01 00:00:00 a -0.298694
 b  0.823553
2013-01-01 12:00:00 a  0.943285
 b -1.479399
2013-01-02 00:00:00 a -1.643342
...                         ...
2013-01-04 12:00:00 b  0.069036
2013-01-05 00:00:00 a  0.122297
 b  1.422060
2013-01-05 12:00:00 a  0.370079
 b  1.016331

[20 rows x 1 columns]

In [114]: dft2.loc["2013-01-05"]
Out[114]: 
 A
2013-01-05 00:00:00 a  0.122297
 b  1.422060
2013-01-05 12:00:00 a  0.370079
 b  1.016331

In [115]: idx = pd.IndexSlice

In [116]: dft2 = dft2.swaplevel(0, 1).sort_index()

In [117]: dft2.loc[idx[:, "2013-01-05"], :]
Out[117]: 
 A
a 2013-01-05 00:00:00  0.122297
 2013-01-05 12:00:00  0.370079
b 2013-01-05 00:00:00  1.422060
 2013-01-05 12:00:00  1.016331 

使用字符串索引进行切片也会尊重 UTC 偏移量。

代码语言:javascript复制
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))

In [119]: df
Out[119]: 
 0
2019-01-01 00:00:00-08:00  0

In [120]: df["2019-01-01 12:00:00 04:00":"2019-01-01 13:00:00 04:00"]
Out[120]: 
 0
2019-01-01 00:00:00-08:00  0 
切片 vs. 精确匹配

使用相同的字符串作为索引参数时,根据索引的分辨率,它可以被视为切片或精确匹配。如果字符串的精度低于索引,则将其视为切片,否则视为精确匹配。

考虑一个具有分钟分辨率索引的 Series 对象:

代码语言:javascript复制
In [121]: series_minute = pd.Series(
 .....:    [1, 2, 3],
 .....:    pd.DatetimeIndex(
 .....:        ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
 .....:    ),
 .....: )
 .....: 

In [122]: series_minute.index.resolution
Out[122]: 'minute' 

低于分钟的时间戳字符串给出一个 Series 对象。

代码语言:javascript复制
In [123]: series_minute["2011-12-31 23"]
Out[123]: 
2011-12-31 23:59:00    1
dtype: int64 

具有分钟分辨率(或更高)的时间戳字符串会给出一个标量,即不会转换为切片。

代码语言:javascript复制
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1

In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1 

如果索引分辨率是秒,那么分钟精度的时间戳将给出一个 Series

代码语言:javascript复制
In [126]: series_second = pd.Series(
 .....:    [1, 2, 3],
 .....:    pd.DatetimeIndex(
 .....:        ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
 .....:    ),
 .....: )
 .....: 

In [127]: series_second.index.resolution
Out[127]: 'second'

In [128]: series_second["2011-12-31 23:59"]
Out[128]: 
2011-12-31 23:59:59    1
dtype: int64 

如果时间戳字符串被视为切片,则也可以使用 .loc[] 索引 DataFrame

代码语言:javascript复制
In [129]: dft_minute = pd.DataFrame(
 .....:    {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
 .....: )
 .....: 

In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]: 
 a  b
2011-12-31 23:59:00  1  4 

警告

但是,如果字符串被视为精确匹配,则 DataFrame[] 中的选择将以列而不是行为基础,参见 索引基础知识。例如,dft_minute['2011-12-31 23:59'] 将引发 KeyError,因为 '2012-12-31 23:59' 的分辨率与索引相同,并且没有具有这样名称的列:

为了始终有明确的选择,无论行是被视为切片还是单个选择,都可以使用 .loc

代码语言:javascript复制
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64 

还要注意,DatetimeIndex的分辨率不能比日更不精确。

代码语言:javascript复制
In [132]: series_monthly = pd.Series(
 .....:    [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
 .....: )
 .....: 

In [133]: series_monthly.index.resolution
Out[133]: 'day'

In [134]: series_monthly["2011-12"]  # returns Series
Out[134]: 
2011-12-01    1
dtype: int64 
精确索引

如前所述,使用部分字符串索引DatetimeIndex取决于“精确度”(即间隔相对于索引分辨率的特定程度)。相比之下,使用Timestampdatetime对象进行索引是精确的,因为这些对象具有确切的含义。这些也遵循包含两个端点的语义。

这些Timestampdatetime对象具有确切的小时、分钟,即使它们没有明确指定(它们为0)。

代码语言:javascript复制
In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]: 
 A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns] 

没有默认值。

代码语言:javascript复制
In [136]: dft[
 .....:    datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
 .....:        2013, 2, 28, 10, 12, 0
 .....:    )
 .....: ]
 .....: 
Out[136]: 
 A
2013-01-01 10:12:00  0.565375
2013-01-01 10:13:00  0.068184
2013-01-01 10:14:00  0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00  0.931536
...                       ...
2013-02-28 10:08:00  0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00  0.139348
2013-02-28 10:11:00  0.085288
2013-02-28 10:12:00  0.950146

[83521 rows x 1 columns] 
截断和花式索引

提供了一个truncate()便捷函数,类似于切片。请注意,truncate假定在DatetimeIndex中的任何未指定的日期组件中为 0 值,与切片不同,后者返回任何部分匹配的日期:

代码语言:javascript复制
In [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")

In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)

In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
Freq: W-SUN, dtype: float64

In [140]: ts2["2011-11":"2011-12"]
Out[140]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
2011-12-04    0.046611
2011-12-11    0.059478
2011-12-18   -0.286539
2011-12-25    0.841669
Freq: W-SUN, dtype: float64 

即使是复杂的花式索引也会破坏DatetimeIndex频率的规律,但结果仍然是DatetimeIndex,尽管频率丢失了:

代码语言:javascript复制
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None) 

时间/日期组件

有几个时间/日期属性可以从TimestampDatetimeIndex等时间戳集合中访问。

属性

描述

year

日期时间的年份

month

日期时间的月份

day

日期时间的天数

hour

日期时间的小时数

minute

日期时间的分钟数

second

日期时间的秒数

microsecond

日期时间的微秒数

nanosecond

日期时间的纳秒数

date

返回 datetime.date(不包含时区信息)

time

返回 datetime.time(不包含时区信息)

timetz

返回带有时区信息的本地时间 datetime.time

dayofyear

年份中的日序数

day_of_year

年份中的日序数

weekofyear

年份中的周序数

week

年份中的周序数

dayofweek

一周中的星期几,星期一=0,星期日=6 的编号

day_of_week

一周中的星期几,星期一=0,星期日=6 的编号

weekday

一周中的星期几,星期一=0,星期日=6 的编号

quarter

日期的季度:1 表示 1 月至 3 月,2 表示 4 月至 6 月,依此类推

days_in_month

日期所在月份的天数

is_month_start

逻辑值,指示是否月份的第一天(由频率定义)

is_month_end

逻辑值,指示是否月份的最后一天(由频率定义)

is_quarter_start

逻辑值,指示是否季度的第一天(由频率定义)

is_quarter_end

逻辑值,指示是否季度的最后一天(由频率定义)

is_year_start

逻辑值,指示是否年份的第一天(由频率定义)

is_year_end

逻辑值,指示是否年份的最后一天(由频率定义)

is_leap_year

逻辑值,指示日期是否属于闰年

此外,如果您有一个具有日期时间值的 Series,则可以通过 .dt 访问器访问这些属性,详细信息请参见 .dt 访问器 部分。

您可以从 ISO 8601 标准获得 ISO 年份的年、周和日组件:

代码语言:javascript复制
In [142]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)

In [143]: idx.isocalendar()
Out[143]: 
 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

In [144]: idx.to_series().dt.isocalendar()
Out[144]: 
 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 

DateOffset 对象

在前面的例子中,频率字符串(例如 'D')用于指定定义的频率:

  • 当使用 date_range() 时,DatetimeIndex 中的日期时间间隔。
  • PeriodPeriodIndex 的频率。

这些频率字符串映射到一个 DateOffset 对象及其子类。DateOffset 类似于 Timedelta,表示一段时间的持续时间,但遵循特定的日历持续时间规则。例如,Timedelta 的一天始终会将 datetimes 增加 24 小时,而 DateOffset 的一天将会将 datetimes 增加到次日的同一时间,无论一天是否由于夏令时而表示为 23、24 或 25 小时。但是,所有表示小时或更小单位(HourMinuteSecondMilliMicroNano)的 DateOffset 子类都像 Timedelta 一样遵循绝对时间。

基本的 DateOffset 行为类似于 dateutil.relativedelta(relativedelta 文档)会按照指定的日历持续时间移动日期时间。可以使用算术运算符( )执行移位。

代码语言:javascript复制
# This particular day contains a day light savings time transition
In [145]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")

# Respects absolute time
In [146]: ts   pd.Timedelta(days=1)
Out[146]: Timestamp('2016-10-30 23:00:00 0200', tz='Europe/Helsinki')

# Respects calendar time
In [147]: ts   pd.DateOffset(days=1)
Out[147]: Timestamp('2016-10-31 00:00:00 0200', tz='Europe/Helsinki')

In [148]: friday = pd.Timestamp("2018-01-05")

In [149]: friday.day_name()
Out[149]: 'Friday'

# Add 2 business days (Friday --> Tuesday)
In [150]: two_business_days = 2 * pd.offsets.BDay()

In [151]: friday   two_business_days
Out[151]: Timestamp('2018-01-09 00:00:00')

In [152]: (friday   two_business_days).day_name()
Out[152]: 'Tuesday' 

大多数 DateOffsets 都有关联的频率字符串或偏移别名,可以传递到 freq 关键字参数中。下面列出了可用的日期偏移量及其关联的频率字符串:

日期偏移量

频率字符串

描述

DateOffset

通用偏移类,默认为绝对 24 小时

BDay 或 BusinessDay

'B'

工作日(周日至周五)

CDay or CustomBusinessDay

'C'

自定义工作日

Week

'W'

一周,可选择以一周中的某一天为锚点

WeekOfMonth

'WOM'

每月第 x 周的第 y 天

LastWeekOfMonth

'LWOM'

每月最后一周的第 x 天

MonthEnd

'ME'

日历月末

MonthBegin

'MS'

日历月开始

BMonthEnd or BusinessMonthEnd

'BME'

工作月末

BMonthBegin or BusinessMonthBegin

'BMS'

工作月开始

CBMonthEnd or CustomBusinessMonthEnd

'CBME'

自定义工作月末

CBMonthBegin or CustomBusinessMonthBegin

'CBMS'

自定义工作月开始

SemiMonthEnd

'SME'

每月 15 日(或其他日子)和日历月末

SemiMonthBegin

'SMS'

15 号(或其他日期)和月初

QuarterEnd

'QE'

季度末

QuarterBegin

'QS'

季度初

BQuarterEnd

'BQE

业务季度末

BQuarterBegin

'BQS'

业务季度初

FY5253Quarter

'REQ'

零售(又称 52-53 周)季度

YearEnd

'YE'

年末

YearBegin

'YS' or 'BYS'

年初

BYearEnd

'BYE'

业务年末

BYearBegin

'BYS'

业务年初

FY5253

'RE'

零售(又称 52-53 周)年

Easter

None

复活节假期

BusinessHour

'bh'

工作小时

CustomBusinessHour

'cbh'

自定义工作小时

Day

'D'

一天

Hour

'h'

一小时

Minute

'min'

一分钟

Second

's'

一秒钟

Milli

'ms'

一毫秒

Micro

'us'

一微秒

Nano

'ns'

一纳秒

DateOffsets另外还有rollforward()rollback()方法,用于将日期向前或向后移动到相对于偏移量的有效日期。例如,商业偏移将周末(星期六和星期日)落在的日期向前推到星期一,因为商业偏移是在工作日上操作的。

代码语言:javascript复制
In [153]: ts = pd.Timestamp("2018-01-06 00:00:00")

In [154]: ts.day_name()
Out[154]: 'Saturday'

# BusinessHour's valid offset dates are Monday through Friday
In [155]: offset = pd.offsets.BusinessHour(start="09:00")

# Bring the date to the closest offset date (Monday)
In [156]: offset.rollforward(ts)
Out[156]: Timestamp('2018-01-08 09:00:00')

# Date is brought to the closest offset date first and then the hour is added
In [157]: ts   offset
Out[157]: Timestamp('2018-01-08 10:00:00') 

默认情况下,这些操作会保留时间(小时、分钟等)信息。要将时间重置为午夜,请在应用操作之前或之后使用normalize()(取决于您是否希望时间信息包含在操作中)。

代码语言:javascript复制
In [158]: ts = pd.Timestamp("2014-01-01 09:00")

In [159]: day = pd.offsets.Day()

In [160]: day   ts
Out[160]: Timestamp('2014-01-02 09:00:00')

In [161]: (day   ts).normalize()
Out[161]: Timestamp('2014-01-02 00:00:00')

In [162]: ts = pd.Timestamp("2014-01-01 22:00")

In [163]: hour = pd.offsets.Hour()

In [164]: hour   ts
Out[164]: Timestamp('2014-01-01 23:00:00')

In [165]: (hour   ts).normalize()
Out[165]: Timestamp('2014-01-01 00:00:00')

In [166]: (hour   pd.Timestamp("2014-01-01 23:30")).normalize()
Out[166]: Timestamp('2014-01-02 00:00:00') 
参数化偏移量

创建时,一些偏移量可以被“参数化”以产生不同的行为。例如,用于生成每周数据的Week偏移接受一个weekday参数,这个参数使生成的日期总是在一周的特定某一天:

代码语言:javascript复制
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)

In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)

In [169]: d   pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')

In [170]: d   pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')

In [171]: (d   pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4

In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00') 

normalize选项将对添加和减去操作有效。

代码语言:javascript复制
In [173]: d   pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')

In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00') 

另一个例子是将YearEnd参数化为特定的结束月份:

代码语言:javascript复制
In [175]: d   pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')

In [176]: d   pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00') 
使用Series / DatetimeIndex偏移

可以将偏移量与SeriesDatetimeIndex一起使用,以将偏移量应用于每个元素。

代码语言:javascript复制
In [177]: rng = pd.date_range("2012-01-01", "2012-01-03")

In [178]: s = pd.Series(rng)

In [179]: rng
Out[179]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')

In [180]: rng   pd.DateOffset(months=2)
Out[180]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq=None)

In [181]: s   pd.DateOffset(months=2)
Out[181]: 
0   2012-03-01
1   2012-03-02
2   2012-03-03
dtype: datetime64[ns]

In [182]: s - pd.DateOffset(months=2)
Out[182]: 
0   2011-11-01
1   2011-11-02
2   2011-11-03
dtype: datetime64[ns] 

如果偏移类直接映射到TimedeltaDayHourMinuteSecondMicroMilliNano),则可以像使用Timedelta一样使用它们-有关更多示例,请参阅 Timedelta 部分。

代码语言:javascript复制
In [183]: s - pd.offsets.Day(2)
Out[183]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[ns]

In [184]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))

In [185]: td
Out[185]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[ns]

In [186]: td   pd.offsets.Minute(15)
Out[186]: 
0   3 days 00:15:00
1   3 days 00:15:00
2   3 days 00:15:00
dtype: timedelta64[ns] 

请注意,某些偏移量(例如BQuarterEnd)没有矢量化实现。它们仍然可以使用,但可能计算速度较慢,并显示PerformanceWarning警告。

代码语言:javascript复制
In [187]: rng   pd.offsets.BQuarterEnd()
Out[187]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None) 
```### 自定义工作日

`CDay`或`CustomBusinessDay`类提供了一个参数化的`BusinessDay`类,可以用于创建自定义的工作日日历,考虑到当地的假日和当地的周末惯例。

作为一个有趣的例子,让我们看看埃及,那里遵循星期五到星期六的周末。

```py
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"

# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
 .....:    "2012-05-01",
 .....:    datetime.datetime(2013, 5, 1),
 .....:    np.datetime64("2014-05-01"),
 .....: ]
 .....: 

In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
 .....:    holidays=holidays,
 .....:    weekmask=weekmask_egypt,
 .....: )
 .....: 

In [191]: dt = datetime.datetime(2013, 4, 30)

In [192]: dt   2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00') 

让我们映射到星期几的名称:

代码语言:javascript复制
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)

In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]: 
2013-04-30    Tue
2013-05-02    Thu
2013-05-05    Sun
2013-05-06    Mon
2013-05-07    Tue
Freq: C, dtype: object 

假日日历可用于提供假日列表。有关更多信息,请参阅假日日历部分。

代码语言:javascript复制
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt   bday_us
Out[198]: Timestamp('2014-01-21 00:00:00') 

可以像通常一样定义尊重某个假日日历的月度偏移量。

代码语言:javascript复制
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)

In [201]: dt   bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')

# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]: 
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') 

频率字符串‘C’用于指示使用 CustomBusinessDay DateOffset,需要注意的是,由于 CustomBusinessDay 是一个带参数的类型,CustomBusinessDay 的实例可能不同,这不能从‘C’频率字符串中检测出来。因此,用户需要确保在用户的应用程序中一致使用‘C’频率字符串。### 营业时间

BusinessHour类提供了在BusinessDay上表示营业时间的方式,允许使用特定的开始和结束时间。

默认情况下,BusinessHour使用 9:00 - 17:00 作为营业时间。添加BusinessHour将按小时频率递增Timestamp。如果目标Timestamp超出营业时间,移动到下一个营业时间然后递增。如果结果超出营业时间结束,剩余的小时将添加到下一个营业日。

代码语言:javascript复制
In [203]: bh = pd.offsets.BusinessHour()

In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>

# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4

In [206]: pd.Timestamp("2014-08-01 10:00")   bh
Out[206]: Timestamp('2014-08-01 11:00:00')

# Below example is the same as: pd.Timestamp('2014-08-01 09:00')   bh
In [207]: pd.Timestamp("2014-08-01 08:00")   bh
Out[207]: Timestamp('2014-08-01 10:00:00')

# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00")   bh
Out[208]: Timestamp('2014-08-04 09:00:00')

# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30")   bh
Out[209]: Timestamp('2014-08-04 09:30:00')

# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00")   pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')

# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00")   pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00') 

您还可以通过关键字指定startend时间。参数必须是具有hour:minute表示或datetime.time实例的str。将秒、微秒和纳秒指定为营业时间会导致ValueError

代码语言:javascript复制
In [212]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))

In [213]: bh
Out[213]: <BusinessHour: bh=11:00-20:00>

In [214]: pd.Timestamp("2014-08-01 13:00")   bh
Out[214]: Timestamp('2014-08-01 14:00:00')

In [215]: pd.Timestamp("2014-08-01 09:00")   bh
Out[215]: Timestamp('2014-08-01 12:00:00')

In [216]: pd.Timestamp("2014-08-01 18:00")   bh
Out[216]: Timestamp('2014-08-01 19:00:00') 

start时间晚于end表示午夜营业时间。在这种情况下,营业时间超过午夜并延伸到第二天。有效的营业时间由是否从有效的BusinessDay开始来区分。

代码语言:javascript复制
In [217]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")

In [218]: bh
Out[218]: <BusinessHour: bh=17:00-09:00>

In [219]: pd.Timestamp("2014-08-01 17:00")   bh
Out[219]: Timestamp('2014-08-01 18:00:00')

In [220]: pd.Timestamp("2014-08-01 23:00")   bh
Out[220]: Timestamp('2014-08-02 00:00:00')

# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [221]: pd.Timestamp("2014-08-02 04:00")   bh
Out[221]: Timestamp('2014-08-02 05:00:00')

# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [222]: pd.Timestamp("2014-08-04 04:00")   bh
Out[222]: Timestamp('2014-08-04 18:00:00') 

对超出营业时间的应用BusinessHour.rollforwardrollback将导致下一个营业时间开始或前一天的结束。与其他偏移不同,BusinessHour.rollforward可能根据定义产生与apply不同的结果。

这是因为一天的营业时间结束等于下一天的营业时间开始。例如,在默认营业时间(9:00 - 17:00)下,2014-08-01 17:002014-08-04 09:00之间没有间隙(0 分钟)。

代码语言:javascript复制
# This adjusts a Timestamp to business hour edge
In [223]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')

In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessHour()   pd.Timestamp('2014-08-01 17:00').
# And it is the same as BusinessHour()   pd.Timestamp('2014-08-04 09:00')
In [225]: pd.offsets.BusinessHour()   pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')

# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessDay()   pd.Timestamp('2014-08-01')
# The result is the same as rollworward because BusinessDay never overlap.
In [227]: pd.offsets.BusinessHour()   pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00') 

BusinessHour将星期六和星期日视为假期。要使用任意假期,可以使用CustomBusinessHour偏移量,如下一小节所述。### 自定义营业时间

CustomBusinessHourBusinessHourCustomBusinessDay的混合体,允许您指定任意假期。CustomBusinessHour的工作方式与BusinessHour相同,只是跳过指定的自定义假期。

代码语言:javascript复制
In [228]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [229]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [230]: dt = datetime.datetime(2014, 1, 17, 15)

In [231]: dt   bhour_us
Out[231]: Timestamp('2014-01-17 16:00:00')

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [232]: dt   bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00') 

您可以使用BusinessHourCustomBusinessDay支持的关键字参数。

代码语言:javascript复制
In [233]: bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")

# Monday is skipped because it's a holiday, business hour starts from 10:00
In [234]: dt   bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00') 
```### 偏移别名

给出了一些常见时间序列频率的字符串别名。我们将这些别名称为*偏移别名*。

| Alias | 描述 |
| --- | --- |
| B | 营业日频率 |
| C | 自定义营业日频率 |
| D | 日历日频率 |
| W | 每周频率 |
| ME | 月末频率 |
| SME | 半月末频率(15 号和月末) |
| BME | 营业月末频率 |
| CBME | 自定义营业月末频率 |
| MS | 月初频率 |
| SMS | 半月初频率(1 号和 15 号) |
| BMS | 营业月初频率 |
| CBMS | 自定义业务月开始频率 |
| QE | 季度结束频率 |
| BQE | 业务季度结束频率 |
| QS | 季度开始频率 |
| BQS | 业务季度开始频率 |
| YE | 年结束频率 |
| BYE | 营业年结束频率 |
| YS | 年开始频率 |
| BYS | 营业年开始频率 |
| h | 每小时频率 |
| bh | 营业时间频率 |
| cbh | 自定义营业小时频率 |
| min | 每分钟频率 |
| s | 每秒频率 |
| ms | 毫秒 |
| us | 微秒 |
| ns | 纳秒 |

自版本 2.2.0 开始已弃用:别名`H`,`BH`,`CBH`,`T`,`S`,`L`,`U`和`N`已弃用,转而使用别名`h`,`bh`,`cbh`,`min`,`s`,`ms`,`us`和`ns`。

注意

> 当使用上述偏移别名时,应注意诸如`date_range()`、`bdate_range()`等函数将仅返回在`start_date`和`end_date`定义的区间内的时间戳。如果`start_date`不对应频率,则返回的时间戳将从下一个有效时间戳开始,对于`end_date`,返回的时间戳将停止在上一个有效时间戳处。

例如,对于偏移`MS`,如果`start_date`不是月份的第一天,则返回的时间戳将从下个月的第一天开始。如果`end_date`不是月份的第一天,则最后返回的时间戳将是对应月份的第一天。

```py
In [235]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")

In [236]: dates_lst_1
Out[236]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')

In [237]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")

In [238]: dates_lst_2
Out[238]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS') 

在上面的例子中,我们可以看到date_range()bdate_range()仅返回在start_dateend_date之间的有效时间戳。如果这些对于给定频率不是有效的时间戳,则会滚动到start_date的下一个值(分别为end_date的上一个值) ### 期别别名

一些常见的时间序列频率都有一些字符串别名。我们将这些别名称为周期别名

别名

描述

B

工作日频率

D

日历日频率

W

每周频率

M

每月频率

Q

每季频率

Y

每年频率

h

每小时频率

min

每分钟频率

s

每秒频率

ms

毫秒

us

微秒

ns

纳秒

自版本 2.2.0 开始已弃用:别名AHTSLUN已弃用,转而使用别名Yhminsmsusns

组合别名

正如我们之前所见,别名和偏移实例在大多数函数中是可以互换的:

代码语言:javascript复制
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
 '2011-01-07'],
 dtype='datetime64[ns]', freq='B')

In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
 '2011-01-07'],
 dtype='datetime64[ns]', freq='B') 

您可以组合日和日内偏移量:

代码语言:javascript复制
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]: 
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='140min')

In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]: 
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='86400000010us') 
锚定的偏移

对于某些频率,您可以指定锚定后缀:

别名

描述

W-SUN

每周频率(星期日)。与‘W’相同

W-MON

每周频率(星期一)

W-TUE

每周频率(星期二)

W-WED

每周频率(星期三)

W-THU

每周频率(星期四)

W-FRI

每周频率(星期五)

W-SAT

每周频率(星期六)

(B)Q(E)(S)-DEC

季度频率,年份以十二月结束。与‘QE’相同

(B)Q(E)(S)-JAN

季度频率,年份以一月结束

(B)Q(E)(S)-FEB

季度频率,年份以二月结束

(B)Q(E)(S)-MAR

季度频率,年份以三月结束

(B)Q(E)(S)-APR

季度频率,年份以四月结束

(B)Q(E)(S)-MAY

季度频率,年份以五月结束

(B)Q(E)(S)-JUN

季度频率,年份以六月结束

(B)Q(E)(S)-JUL

季度频率,年份以七月结束

(B)Q(E)(S)-AUG

季度频率,年份以八月结束

(B)Q(E)(S)-SEP

季度频率,年份以九月结束

(B)Q(E)(S)-OCT

季度频率,年份以十月结束

(B)Q(E)(S)-NOV

季度频率,年份以十一月结束

(B)Y(E)(S)-DEC

每年频率,锚定在十二月底。与‘YE’相同

(B)Y(E)(S)-JAN

每年频率,锚定在一月底

(B)Y(E)(S)-FEB

每年频率,锚定在二月底

(B)Y(E)(S)-MAR

每年频率,锚定在三月底

(B)Y(E)(S)-APR

每年频率,锚定在四月底

(B)Y(E)(S)-MAY

每年频率,锚定在五月底

(B)Y(E)(S)-JUN

每年频率,锚定在六月底

(B)Y(E)(S)-JUL

每年频率,锚定在七月底

(B)Y(E)(S)-AUG

每年频率,锚定在八月底

(B)Y(E)(S)-SEP

每年频率,锚定在九月底

(B)Y(E)(S)-OCT

每年频率,锚定在十月底

(B)Y(E)(S)-NOV

每年频率,锚定在十一月底

这些可以用作date_rangebdate_range的参数,DatetimeIndex的构造函数,以及 pandas 中各种其他与时间序列相关的函数。

锚定偏移语义

对于那些锚定到特定频率的起始或结束的偏移量(MonthEndMonthBeginWeekEnd等),以下规则适用于向前和向后滚动。

n不为 0 时,如果给定的日期不在一个锚点上,则它将捕捉到下一个(上一个)锚点,并向前或向后移动|n|-1个额外步骤。

代码语言:javascript复制
In [243]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=1)
Out[243]: Timestamp('2014-02-01 00:00:00')

In [244]: pd.Timestamp("2014-01-02")   pd.offsets.MonthEnd(n=1)
Out[244]: Timestamp('2014-01-31 00:00:00')

In [245]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[245]: Timestamp('2014-01-01 00:00:00')

In [246]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[246]: Timestamp('2013-12-31 00:00:00')

In [247]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2014-05-01 00:00:00')

In [248]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[248]: Timestamp('2013-10-01 00:00:00') 

如果给定的日期一个锚点上,它会向前或向后移动|n|个点。

代码语言:javascript复制
In [249]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=1)
Out[249]: Timestamp('2014-02-01 00:00:00')

In [250]: pd.Timestamp("2014-01-31")   pd.offsets.MonthEnd(n=1)
Out[250]: Timestamp('2014-02-28 00:00:00')

In [251]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[251]: Timestamp('2013-12-01 00:00:00')

In [252]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[252]: Timestamp('2013-12-31 00:00:00')

In [253]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=4)
Out[253]: Timestamp('2014-05-01 00:00:00')

In [254]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2013-10-01 00:00:00') 

对于n=0的情况,如果日期在锚点上,则不移动,否则向前滚动到下一个锚点。

代码语言:javascript复制
In [255]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=0)
Out[255]: Timestamp('2014-02-01 00:00:00')

In [256]: pd.Timestamp("2014-01-02")   pd.offsets.MonthEnd(n=0)
Out[256]: Timestamp('2014-01-31 00:00:00')

In [257]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=0)
Out[257]: Timestamp('2014-01-01 00:00:00')

In [258]: pd.Timestamp("2014-01-31")   pd.offsets.MonthEnd(n=0)
Out[258]: Timestamp('2014-01-31 00:00:00') 
节假日 / 节假日日历

假期和日历提供了一种简单的方式来定义假期规则,以便与CustomBusinessDay或其他需要预定义假期集合的分析一起使用。AbstractHolidayCalendar类提供了返回假期列表的所有必要方法,只需在特定假期日历类中定义rules即可。此外,start_dateend_date类属性确定生成假期的日期范围。应该在AbstractHolidayCalendar类上进行覆盖,以使该范围适用于所有日历子类。USFederalHolidayCalendar是唯一存在的日历,主要用作开发其他日历的示例。

对于固定日期(例如美国阵亡将士纪念日或 7 月 4 日)的假期,观察规则确定了如果假期落在周末或其他非观察日时,该假期何时被观察。定义的观察规则有:

Rule

Description

nearest_workday

将周六移至周五,周日移至周一

sunday_to_monday

将周日移至下一个周一

next_monday_or_tuesday

将周六移至周一,周日/周一移至周二

previous_friday

将周六和周日移至上一个星期五”

next_monday

将周六和周日移至下一个周一

假期和假期日历如何定义的示例:

代码语言:javascript复制
In [259]: from pandas.tseries.holiday import (
 .....:    Holiday,
 .....:    USMemorialDay,
 .....:    AbstractHolidayCalendar,
 .....:    nearest_workday,
 .....:    MO,
 .....: )
 .....: 

In [260]: class ExampleCalendar(AbstractHolidayCalendar):
 .....:    rules = [
 .....:        USMemorialDay,
 .....:        Holiday("July 4th", month=7, day=4, observance=nearest_workday),
 .....:        Holiday(
 .....:            "Columbus Day",
 .....:            month=10,
 .....:            day=1,
 .....:            offset=pd.DateOffset(weekday=MO(2)),
 .....:        ),
 .....:    ]
 .....: 

In [261]: cal = ExampleCalendar()

In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None) 

提示:

weekday=MO(2) 等同于 2 * Week(weekday=2)

使用此日历,创建索引或进行偏移算术会跳过周末和假期(例如,阵亡将士纪念日/7 月 4 日)。例如,以下定义了使用ExampleCalendar创建自定义工作日偏移。与任何其他偏移一样,它可以用于创建DatetimeIndex或添加到datetimeTimestamp对象中。

代码语言:javascript复制
In [263]: pd.date_range(
 .....:    start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
 .....: ).to_pydatetime()
 .....: 
Out[263]: 
array([datetime.datetime(2012, 7, 2, 0, 0),
 datetime.datetime(2012, 7, 3, 0, 0),
 datetime.datetime(2012, 7, 5, 0, 0),
 datetime.datetime(2012, 7, 6, 0, 0),
 datetime.datetime(2012, 7, 9, 0, 0),
 datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)

In [264]: offset = pd.offsets.CustomBusinessDay(calendar=cal)

In [265]: datetime.datetime(2012, 5, 25)   offset
Out[265]: Timestamp('2012-05-29 00:00:00')

In [266]: datetime.datetime(2012, 7, 3)   offset
Out[266]: Timestamp('2012-07-05 00:00:00')

In [267]: datetime.datetime(2012, 7, 3)   2 * offset
Out[267]: Timestamp('2012-07-06 00:00:00')

In [268]: datetime.datetime(2012, 7, 6)   offset
Out[268]: Timestamp('2012-07-09 00:00:00') 

范围由AbstractHolidayCalendarstart_dateend_date类属性定义。默认值如下所示。

代码语言:javascript复制
In [269]: AbstractHolidayCalendar.start_date
Out[269]: Timestamp('1970-01-01 00:00:00')

In [270]: AbstractHolidayCalendar.end_date
Out[270]: Timestamp('2200-12-31 00:00:00') 

可以通过将属性设置为 datetime/Timestamp/string 来覆盖这些日期。

代码语言:javascript复制
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)

In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)

In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None) 

每个日历类都可以通过名称使用get_calendar函数访问,该函数返回一个假期类实例。任何导入的日历类都将自动通过此函数可用。此外,HolidayCalendarFactory提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。

代码语言:javascript复制
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay

In [275]: cal = get_calendar("ExampleCalendar")

In [276]: cal.rules
Out[276]: 
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)]

In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)

In [278]: new_cal.rules
Out[278]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO( 1)>),
 Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)] 
参数化偏移

创建时,某些偏移可以“参数化”以产生不同的行为。例如,用于生成每周数据的Week偏移接受一个weekday参数,这将导致生成的日期始终落在一周的特定某一天:

代码语言:javascript复制
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)

In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)

In [169]: d   pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')

In [170]: d   pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')

In [171]: (d   pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4

In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00') 

normalize选项将对加法和减法产生影响。

代码语言:javascript复制
In [173]: d   pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')

In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00') 

另一个示例是使用特定结束月份对YearEnd进行参数化:

代码语言:javascript复制
In [175]: d   pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')

In [176]: d   pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00') 
使用Series / DatetimeIndex进行偏移

偏移可以与SeriesDatetimeIndex一起使用,以将偏移应用于每个元素。

代码语言:javascript复制
In [177]: rng = pd.date_range("2012-01-01", "2012-01-03")

In [178]: s = pd.Series(rng)

In [179]: rng
Out[179]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')

In [180]: rng   pd.DateOffset(months=2)
Out[180]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq=None)

In [181]: s   pd.DateOffset(months=2)
Out[181]: 
0   2012-03-01
1   2012-03-02
2   2012-03-03
dtype: datetime64[ns]

In [182]: s - pd.DateOffset(months=2)
Out[182]: 
0   2011-11-01
1   2011-11-02
2   2011-11-03
dtype: datetime64[ns] 

如果偏移类直接映射到 TimedeltaDayHourMinuteSecondMicroMilliNano),则可以像 Timedelta 一样使用它 - 请参阅 Timedelta 部分了解更多示例。

代码语言:javascript复制
In [183]: s - pd.offsets.Day(2)
Out[183]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[ns]

In [184]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))

In [185]: td
Out[185]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[ns]

In [186]: td   pd.offsets.Minute(15)
Out[186]: 
0   3 days 00:15:00
1   3 days 00:15:00
2   3 days 00:15:00
dtype: timedelta64[ns] 

请注意,某些偏移量(例如 BQuarterEnd)没有矢量化实现。它们仍然可以使用,但可能计算速度明显较慢,并且会显示 PerformanceWarning

代码语言:javascript复制
In [187]: rng   pd.offsets.BQuarterEnd()
Out[187]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None) 
自定义工作日

CDayCustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建考虑到本地节假日和本地周末惯例的定制工作日日历。

作为一个有趣的例子,让我们看一下埃及,那里观察到周五至周六的周末。

代码语言:javascript复制
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"

# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
 .....:    "2012-05-01",
 .....:    datetime.datetime(2013, 5, 1),
 .....:    np.datetime64("2014-05-01"),
 .....: ]
 .....: 

In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
 .....:    holidays=holidays,
 .....:    weekmask=weekmask_egypt,
 .....: )
 .....: 

In [191]: dt = datetime.datetime(2013, 4, 30)

In [192]: dt   2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00') 

让我们映射到工作日名称:

代码语言:javascript复制
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)

In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]: 
2013-04-30    Tue
2013-05-02    Thu
2013-05-05    Sun
2013-05-06    Mon
2013-05-07    Tue
Freq: C, dtype: object 

节假日日历可用于提供节假日列表。有关更多信息,请参阅节假日日历部分。

代码语言:javascript复制
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt   bday_us
Out[198]: Timestamp('2014-01-21 00:00:00') 

可以以通常的方式定义尊重某个节假日日历的月度偏移量。

代码语言:javascript复制
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)

In [201]: dt   bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')

# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]: 
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') 

注意

频率字符串 ‘C’ 用于指示使用 CustomBusinessDay DateOffset,重要的是要注意,由于 CustomBusinessDay 是一个参数化类型,CustomBusinessDay 的实例可能会有所不同,这在 ‘C’ 频率字符串中是不可检测到的。因此,用户需要确保在用户应用程序中一致使用 ‘C’ 频率字符串。

营业时间

BusinessHour 类在 BusinessDay 上提供了营业时间的表示,允许使用特定的开始和结束时间。

默认情况下,BusinessHour 使用 9:00 - 17:00 作为营业时间。添加 BusinessHour 将按小时频率递增 Timestamp。如果目标 Timestamp 超出营业时间,则移到下一个营业时间然后递增。如果结果超出营业时间结束,则剩余时间添加到下一个营业日。

代码语言:javascript复制
In [203]: bh = pd.offsets.BusinessHour()

In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>

# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4

In [206]: pd.Timestamp("2014-08-01 10:00")   bh
Out[206]: Timestamp('2014-08-01 11:00:00')

# Below example is the same as: pd.Timestamp('2014-08-01 09:00')   bh
In [207]: pd.Timestamp("2014-08-01 08:00")   bh
Out[207]: Timestamp('2014-08-01 10:00:00')

# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00")   bh
Out[208]: Timestamp('2014-08-04 09:00:00')

# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30")   bh
Out[209]: Timestamp('2014-08-04 09:30:00')

# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00")   pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')

# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00")   pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00') 

您还可以通过关键字指定 startend 时间。参数必须是具有 hour:minute 表示或 datetime.time 实例的 str。将秒、微秒和纳秒指定为营业时间会导致 ValueError

代码语言:javascript复制
In [212]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))

In [213]: bh
Out[213]: <BusinessHour: bh=11:00-20:00>

In [214]: pd.Timestamp("2014-08-01 13:00")   bh
Out[214]: Timestamp('2014-08-01 14:00:00')

In [215]: pd.Timestamp("2014-08-01 09:00")   bh
Out[215]: Timestamp('2014-08-01 12:00:00')

In [216]: pd.Timestamp("2014-08-01 18:00")   bh
Out[216]: Timestamp('2014-08-01 19:00:00') 

传递 start 时间晚于 end 表示午夜营业时间。在这种情况下,营业时间超过午夜并且重叠到第二天。有效的营业时间由是否从有效的 BusinessDay 开始来区分。

代码语言:javascript复制
In [217]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")

In [218]: bh
Out[218]: <BusinessHour: bh=17:00-09:00>

In [219]: pd.Timestamp("2014-08-01 17:00")   bh
Out[219]: Timestamp('2014-08-01 18:00:00')

In [220]: pd.Timestamp("2014-08-01 23:00")   bh
Out[220]: Timestamp('2014-08-02 00:00:00')

# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [221]: pd.Timestamp("2014-08-02 04:00")   bh
Out[221]: Timestamp('2014-08-02 05:00:00')

# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [222]: pd.Timestamp("2014-08-04 04:00")   bh
Out[222]: Timestamp('2014-08-04 18:00:00') 

对超出营业时间的应用 BusinessHour.rollforwardrollback 将导致下一个营业时间开始或前一天的结束。与其他偏移不同,BusinessHour.rollforward 可能会根据定义产生与 apply 不同的结果。

这是因为一天的营业时间结束等于下一天的营业时间开始。例如,在默认营业时间(9:00 - 17:00)下,2014-08-01 17:002014-08-04 09:00 之间没有间隙(0 分钟)。

代码语言:javascript复制
# This adjusts a Timestamp to business hour edge
In [223]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')

In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessHour()   pd.Timestamp('2014-08-01 17:00').
# And it is the same as BusinessHour()   pd.Timestamp('2014-08-04 09:00')
In [225]: pd.offsets.BusinessHour()   pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')

# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessDay()   pd.Timestamp('2014-08-01')
# The result is the same as rollworward because BusinessDay never overlap.
In [227]: pd.offsets.BusinessHour()   pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00') 

BusinessHour将周六和周日视为假期。要使用任意假期,您可以使用CustomBusinessHour偏移量,如下一小节所述。

自定义工作时间

CustomBusinessHourBusinessHourCustomBusinessDay的混合体,允许您指定任意假期。CustomBusinessHour的工作方式与BusinessHour相同,只是它跳过指定的自定义假期。

代码语言:javascript复制
In [228]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [229]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [230]: dt = datetime.datetime(2014, 1, 17, 15)

In [231]: dt   bhour_us
Out[231]: Timestamp('2014-01-17 16:00:00')

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [232]: dt   bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00') 

您可以使用BusinessHourCustomBusinessDay支持的关键字参数。

代码语言:javascript复制
In [233]: bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")

# Monday is skipped because it's a holiday, business hour starts from 10:00
In [234]: dt   bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00') 
偏移别名

一些常见的时间序列频率有一些字符串别名。我们将这些别名称为偏移别名

别名

描述

B

工作日频率

C

自定义工作日频率

D

日历日频率

W

每周频率

ME

月结束频率

SME

半月结束频率(15 号和月底)

BME

业务月结束频率

CBME

自定义业务月结束频率

MS

月初频率

SMS

半月初频率(1 号和 15 号)

BMS

业务月初频率

CBMS

自定义业务月初频率

QE

季度结束频率

BQE

业务季度结束频率

QS

季度开始频率

BQS

业务季度开始频率

YE

年结束频率

BYE

业务年结束频率

YS

年开始频率

BYS

业务年开始频率

h

小时频率

bh

工作小时频率

cbh

自定义工作小时频率

min

分钟频率

s

每秒频率

ms

毫秒

us

微秒

ns

纳秒

自 2.2.0 版本起弃用:别名HBHCBHTSLUN已弃用,推荐使用别名hbhcbhminsmsusns

注意

在使用上述偏移别名时,应注意诸如date_range()bdate_range()等函数只会返回在start_dateend_date定义的间隔内的时间戳。如果start_date不对应频率,则返回的时间戳将从下一个有效时间戳开始,对于end_date也是一样,返回的时间戳将在前一个有效时间戳停止。

例如,对于偏移量MS,如果start_date不是月份的第一天,则返回的时间戳将从下个月的第一天开始。如果end_date不是某个月的第一天,则最后返回的时间戳将是对应月份的第一天。

代码语言:javascript复制
In [235]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")

In [236]: dates_lst_1
Out[236]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')

In [237]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")

In [238]: dates_lst_2
Out[238]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS') 

我们可以看到在上面的例子中date_range()bdate_range()将只返回start_dateend_date之间的有效时间戳。如果这些对于给定频率不是有效的时间戳,它将滚动到start_date的下一个值(分别是end_date的前一个值)

周期别名

一些常见时间序列频率的字符串别名被赋予了。我们将这些别名称为周期别名

别名

描述

B

工作日频率

D

日历日频率

W

每周频率

M

每月频率

Q

季度频率

Y

每年频率

h

每小时频率

min

每分钟频率

s

每秒频率

ms

毫秒

us

微秒

ns

纳秒

自版本 2.2.0 起弃用:别名AHTSLUN已被弃用,取而代之的是别名Yhminsmsusns

合并别名

正如我们之前所看到的,别名和偏移实例在大多数函数中是可互换的:

代码语言:javascript复制
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
 '2011-01-07'],
 dtype='datetime64[ns]', freq='B')

In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
 '2011-01-07'],
 dtype='datetime64[ns]', freq='B') 

您可以组合一起日和日内偏移:

代码语言:javascript复制
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]: 
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='140min')

In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]: 
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='86400000010us') 
锚定偏移

对于一些频率,您可以指定一个锚定后缀:

别名

描述

W-SUN

每周频率(周日)。与‘W’相同

W-MON

每周频率(周一)

W-TUE

每周频率(周二)

W-WED

每周频率(周三)

W-THU

每周频率(周四)

W-FRI

每周频率(周五)

W-SAT

每周频率(周六)

(B)Q(E)(S)-DEC

季度频率,年底在十二月。与‘QE’相同

(B)Q(E)(S)-JAN

季度频率,年底在一月

(B)Q(E)(S)-FEB

季度频率,年底在二月

(B)Q(E)(S)-MAR

季度频率,年底在三月

(B)Q(E)(S)-APR

季度频率,年底在四月

(B)Q(E)(S)-MAY

季度频率,年底在五月

(B)Q(E)(S)-JUN

季度频率,年底在六月

(B)Q(E)(S)-JUL

季度频率,年底在七月

(B)Q(E)(S)-AUG

季度频率,年底在八月

(B)Q(E)(S)-SEP

季度频率,年底在九月

(B)Q(E)(S)-OCT

季度频率,年底在十月

(B)Q(E)(S)-NOV

季度频率,年底在十一月

(B)Y(E)(S)-DEC

年度频率,锚定在十二月底。与‘YE’相同

(B)Y(E)(S)-JAN

年度频率,锚定在一月底

(B)Y(E)(S)-FEB

年度频率,锚定在二月底

(B)Y(E)(S)-MAR

年度频率,锚定在三月底

(B)Y(E)(S)-APR

年度频率,锚定在四月底

(B)Y(E)(S)-MAY

年度频率,锚定在五月底

(B)Y(E)(S)-JUN

年度频率,锚定在六月底

(B)Y(E)(S)-JUL

年度频率,锚定在七月底

(B)Y(E)(S)-AUG

年度频率,8 月底锚定

(B)Y(E)(S)-SEP

年度频率,9 月底锚定

(B)Y(E)(S)-OCT

年度频率,10 月底锚定

(B)Y(E)(S)-NOV

年度频率,11 月底锚定

这些可以作为date_rangebdate_range的参数,也可以作为DatetimeIndex的构造函数,以及 pandas 中其他各种与时间序列相关的函数。

锚定偏移量语义

对于那些锚定在特定频率的开始或结束(MonthEndMonthBeginWeekEnd等)的偏移量,以下规则适用于向前和向后滚动。

n不为 0 时,如果给定日期不在锚点上,则将其捕捉到下一个(上一个)锚点,并向前或向后移动|n|-1个额外步骤。

代码语言:javascript复制
In [243]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=1)
Out[243]: Timestamp('2014-02-01 00:00:00')

In [244]: pd.Timestamp("2014-01-02")   pd.offsets.MonthEnd(n=1)
Out[244]: Timestamp('2014-01-31 00:00:00')

In [245]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[245]: Timestamp('2014-01-01 00:00:00')

In [246]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[246]: Timestamp('2013-12-31 00:00:00')

In [247]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2014-05-01 00:00:00')

In [248]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[248]: Timestamp('2013-10-01 00:00:00') 

如果给定日期锚点上,它将向前或向后移动|n|个点。

代码语言:javascript复制
In [249]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=1)
Out[249]: Timestamp('2014-02-01 00:00:00')

In [250]: pd.Timestamp("2014-01-31")   pd.offsets.MonthEnd(n=1)
Out[250]: Timestamp('2014-02-28 00:00:00')

In [251]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[251]: Timestamp('2013-12-01 00:00:00')

In [252]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[252]: Timestamp('2013-12-31 00:00:00')

In [253]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=4)
Out[253]: Timestamp('2014-05-01 00:00:00')

In [254]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2013-10-01 00:00:00') 

n=0时,如果日期在锚点上,则不移动,否则向前滚动到下一个锚点。

代码语言:javascript复制
In [255]: pd.Timestamp("2014-01-02")   pd.offsets.MonthBegin(n=0)
Out[255]: Timestamp('2014-02-01 00:00:00')

In [256]: pd.Timestamp("2014-01-02")   pd.offsets.MonthEnd(n=0)
Out[256]: Timestamp('2014-01-31 00:00:00')

In [257]: pd.Timestamp("2014-01-01")   pd.offsets.MonthBegin(n=0)
Out[257]: Timestamp('2014-01-01 00:00:00')

In [258]: pd.Timestamp("2014-01-31")   pd.offsets.MonthEnd(n=0)
Out[258]: Timestamp('2014-01-31 00:00:00') 
假期/假日日历

假期和日历提供了一种简单的方式来定义假期规则,以便与CustomBusinessDay或其他需要预定义假期集合的分析一起使用。AbstractHolidayCalendar类提供了返回假期列表所需的所有方法,只需在特定假期日历类中定义rules即可。此外,start_dateend_date类属性确定生成假期的日期范围。这些应该在AbstractHolidayCalendar类上被覆盖,以使范围适用于所有日历子类。USFederalHolidayCalendar是唯一存在的日历,主要用作开发其他日历的示例。

对于在固定日期发生的假期(例如,美国阵亡将士纪念日或 7 月 4 日),一个遵守规则确定了如果假期落在周末或其他非观察日时如何观察。定义的遵守规则有:

Rule

Description

nearest_workday

将周六移动到周五,周日移动到周一

sunday_to_monday

将周日移动到下一个周一

next_monday_or_tuesday

将周六移动到周一,周日/周一移动到周二

previous_friday

将周六和周日移动到上一个星期五”

next_monday

将周六和周日移动到下一个周一

定义假期和假日日历的示例:

代码语言:javascript复制
In [259]: from pandas.tseries.holiday import (
 .....:    Holiday,
 .....:    USMemorialDay,
 .....:    AbstractHolidayCalendar,
 .....:    nearest_workday,
 .....:    MO,
 .....: )
 .....: 

In [260]: class ExampleCalendar(AbstractHolidayCalendar):
 .....:    rules = [
 .....:        USMemorialDay,
 .....:        Holiday("July 4th", month=7, day=4, observance=nearest_workday),
 .....:        Holiday(
 .....:            "Columbus Day",
 .....:            month=10,
 .....:            day=1,
 .....:            offset=pd.DateOffset(weekday=MO(2)),
 .....:        ),
 .....:    ]
 .....: 

In [261]: cal = ExampleCalendar()

In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None) 

提示:

weekday=MO(2) 等同于 2 * Week(weekday=2)

使用此日历,创建索引或进行偏移算术时会跳过周末和假期(例如,阵亡将士纪念日/7 月 4 日)。例如,以下定义了使用ExampleCalendar的自定义工作日偏移量。与任何其他偏移量一样,它可以用于创建DatetimeIndex或添加到datetimeTimestamp对象中。

代码语言:javascript复制
In [263]: pd.date_range(
 .....:    start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
 .....: ).to_pydatetime()
 .....: 
Out[263]: 
array([datetime.datetime(2012, 7, 2, 0, 0),
 datetime.datetime(2012, 7, 3, 0, 0),
 datetime.datetime(2012, 7, 5, 0, 0),
 datetime.datetime(2012, 7, 6, 0, 0),
 datetime.datetime(2012, 7, 9, 0, 0),
 datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)

In [264]: offset = pd.offsets.CustomBusinessDay(calendar=cal)

In [265]: datetime.datetime(2012, 5, 25)   offset
Out[265]: Timestamp('2012-05-29 00:00:00')

In [266]: datetime.datetime(2012, 7, 3)   offset
Out[266]: Timestamp('2012-07-05 00:00:00')

In [267]: datetime.datetime(2012, 7, 3)   2 * offset
Out[267]: Timestamp('2012-07-06 00:00:00')

In [268]: datetime.datetime(2012, 7, 6)   offset
Out[268]: Timestamp('2012-07-09 00:00:00') 

范围由AbstractHolidayCalendarstart_dateend_date类属性定义。默认值如下所示。

代码语言:javascript复制
In [269]: AbstractHolidayCalendar.start_date
Out[269]: Timestamp('1970-01-01 00:00:00')

In [270]: AbstractHolidayCalendar.end_date
Out[270]: Timestamp('2200-12-31 00:00:00') 

这些日期可以通过将属性设置为 datetime/Timestamp/string 来覆盖。

代码语言:javascript复制
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)

In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)

In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None) 

每个日历类都可以通过名称使用get_calendar函数访问,该函数返回一个节假日类实例。任何导入的日历类都将自动通过此函数可用。此外,HolidayCalendarFactory提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。

代码语言:javascript复制
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay

In [275]: cal = get_calendar("ExampleCalendar")

In [276]: cal.rules
Out[276]: 
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)]

In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)

In [278]: new_cal.rules
Out[278]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO( 1)>),
 Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)] 

与时间序列相关的实例方法

Shifting / lagging

有时可能需要将时间序列中的值向前或向后移动。用于此操作的方法是shift(),可用于所有 pandas 对象。

代码语言:javascript复制
In [279]: ts = pd.Series(range(len(rng)), index=rng)

In [280]: ts = ts[:5]

In [281]: ts.shift(1)
Out[281]: 
2012-01-01    NaN
2012-01-02    0.0
2012-01-03    1.0
Freq: D, dtype: float64 

shift方法接受一个freq参数,可以接受一个DateOffset类或其他类似timedelta的对象,也可以是一个 offset alias。

当指定freq时,shift方法会更改索引中的所有日期,而不是更改数据和索引的对齐:

代码语言:javascript复制
In [282]: ts.shift(5, freq="D")
Out[282]: 
2012-01-06    0
2012-01-07    1
2012-01-08    2
Freq: D, dtype: int64

In [283]: ts.shift(5, freq=pd.offsets.BDay())
Out[283]: 
2012-01-06    0
2012-01-09    1
2012-01-10    2
dtype: int64

In [284]: ts.shift(5, freq="BME")
Out[284]: 
2012-05-31    0
2012-05-31    1
2012-05-31    2
dtype: int64 

请注意,当指定freq时,前导条目不再是 NaN,因为数据没有被重新对齐。

频率转换

更改频率的主要函数是asfreq()方法。对于DatetimeIndex,这基本上只是reindex()的一个薄包装,它生成一个date_range并调用reindex

代码语言:javascript复制
In [285]: dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())

In [286]: ts = pd.Series(np.random.randn(3), index=dr)

In [287]: ts
Out[287]: 
2010-01-01    1.494522
2010-01-06   -0.778425
2010-01-11   -0.253355
Freq: 3B, dtype: float64

In [288]: ts.asfreq(pd.offsets.BDay())
Out[288]: 
2010-01-01    1.494522
2010-01-04         NaN
2010-01-05         NaN
2010-01-06   -0.778425
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -0.253355
Freq: B, dtype: float64 

asfreq提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。

代码语言:javascript复制
In [289]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]: 
2010-01-01    1.494522
2010-01-04    1.494522
2010-01-05    1.494522
2010-01-06   -0.778425
2010-01-07   -0.778425
2010-01-08   -0.778425
2010-01-11   -0.253355
Freq: B, dtype: float64 
向前/向后填充

asfreqreindex相关的是fillna(),该方法在 missing data section 中有文档记录。

转换为 Python 日期时间

DatetimeIndex可以使用to_pydatetime方法转换为 Python 本机datetime.datetime对象的数组。

Shifting / lagging

有时可能需要将时间序列中的值向前或向后移动。用于此操作的方法是shift(),可用于所有 pandas 对象。

代码语言:javascript复制
In [279]: ts = pd.Series(range(len(rng)), index=rng)

In [280]: ts = ts[:5]

In [281]: ts.shift(1)
Out[281]: 
2012-01-01    NaN
2012-01-02    0.0
2012-01-03    1.0
Freq: D, dtype: float64 

shift方法接受一个freq参数,可以接受一个DateOffset类或其他类似timedelta的对象,也可以是一个 offset alias。

当指定freq时,shift方法会更改索引中的所有日期,而不是更改数据和索引的对齐:

代码语言:javascript复制
In [282]: ts.shift(5, freq="D")
Out[282]: 
2012-01-06    0
2012-01-07    1
2012-01-08    2
Freq: D, dtype: int64

In [283]: ts.shift(5, freq=pd.offsets.BDay())
Out[283]: 
2012-01-06    0
2012-01-09    1
2012-01-10    2
dtype: int64

In [284]: ts.shift(5, freq="BME")
Out[284]: 
2012-05-31    0
2012-05-31    1
2012-05-31    2
dtype: int64 

请注意,当指定freq时,前导条目不再是 NaN,因为数据没有被重新对齐。

频率转换

更改频率的主要函数是 asfreq() 方法。对于 DatetimeIndex,这基本上只是一个薄的但方便的包装器,围绕 reindex() 生成一个 date_range 并调用 reindex

代码语言:javascript复制
In [285]: dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())

In [286]: ts = pd.Series(np.random.randn(3), index=dr)

In [287]: ts
Out[287]: 
2010-01-01    1.494522
2010-01-06   -0.778425
2010-01-11   -0.253355
Freq: 3B, dtype: float64

In [288]: ts.asfreq(pd.offsets.BDay())
Out[288]: 
2010-01-01    1.494522
2010-01-04         NaN
2010-01-05         NaN
2010-01-06   -0.778425
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -0.253355
Freq: B, dtype: float64 

asfreq 提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。

代码语言:javascript复制
In [289]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]: 
2010-01-01    1.494522
2010-01-04    1.494522
2010-01-05    1.494522
2010-01-06   -0.778425
2010-01-07   -0.778425
2010-01-08   -0.778425
2010-01-11   -0.253355
Freq: B, dtype: float64 
向前/向后填充

asfreqreindex 相关的是 fillna(),该方法在 缺失数据部分有详细说明。

转换为 Python 日期时间

DatetimeIndex 可以使用 to_pydatetime 方法转换为 Python 原生的 datetime.datetime 对象数组。

重采样

pandas 在频率转换期间执行重采样操作(例如,将秒数据转换为 5 分钟数据)具有简单、强大和高效的功能。这在金融应用中非常常见,但不限于此。

resample() 是基于时间的分组,后跟对每个组的减少方法。查看一些 示例 以了解一些高级策略。

resample() 方法可以直接从 DataFrameGroupBy 对象中使用,请参阅 groupby 文档。

基础知识
代码语言:javascript复制
In [290]: rng = pd.date_range("1/1/2012", periods=100, freq="s")

In [291]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

In [292]: ts.resample("5Min").sum()
Out[292]: 
2012-01-01    25103
Freq: 5min, dtype: int64 

resample 函数非常灵活,允许您指定许多不同的参数来控制频率转换和重采样操作。

通过 GroupBy 可用的任何内置方法都可以作为返回对象的方法使用,包括 summeanstdsemmaxminmedianfirstlastohlc

代码语言:javascript复制
In [293]: ts.resample("5Min").mean()
Out[293]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

In [294]: ts.resample("5Min").ohlc()
Out[294]: 
 open  high  low  close
2012-01-01   308   460    9    205

In [295]: ts.resample("5Min").max()
Out[295]: 
2012-01-01    460
Freq: 5min, dtype: int64 

对于降采样,closed 可以设置为‘left’或‘right’以指定间隔的哪一端是封闭的:

代码语言:javascript复制
In [296]: ts.resample("5Min", closed="right").mean()
Out[296]: 
2011-12-31 23:55:00    308.000000
2012-01-01 00:00:00    250.454545
Freq: 5min, dtype: float64

In [297]: ts.resample("5Min", closed="left").mean()
Out[297]: 
2012-01-01    251.03
Freq: 5min, dtype: float64 

label 这样的参数用于操作生成的标签。label 指定结果是用间隔的开始还是结束标记。

代码语言:javascript复制
In [298]: ts.resample("5Min").mean()  # by default label='left'
Out[298]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

In [299]: ts.resample("5Min", label="left").mean()
Out[299]: 
2012-01-01    251.03
Freq: 5min, dtype: float64 

警告

对于所有频率偏移,默认值为‘left’,除了‘ME’、‘YE’、‘QE’、‘BME’、‘BYE’、‘BQE’和‘W’,它们的默认值都是‘right’。

这可能会意外地导致向前查看,其中稍后时间的值被拉回到先前时间,如下例所示,使用 BusinessDay 频率:

代码语言:javascript复制
In [300]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()

In [301]: s.iloc[2] = pd.NaT

In [302]: s.dt.day_name()
Out[302]: 
2000-01-01     Saturday
2000-01-02       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: D, dtype: object

# default: label='left', closed='left'
In [303]: s.resample("B").last().dt.day_name()
Out[303]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object 

注意星期日的值被拉回到上一个星期五。要使星期日的值推到星期一,可以使用以下方法:

代码语言:javascript复制
In [304]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[304]: 
2000-01-03       Sunday
2000-01-04      Tuesday
2000-01-05    Wednesday
2000-01-06          NaN
Freq: B, dtype: object 

axis参数可以设置为 0 或 1,并允许您重新采样DataFrame的指定轴。

kind可以设置为‘timestamp’或‘period’,以将生成的索引转换为时间戳和时间跨度表示。默认情况下,resample保留输入表示。

当重新采样周期数据时,convention可以设置为‘start’或‘end’(详细信息如下)。它指定了低频周期如何转换为高频周期。

上采样

对于上采样,您可以指定一种上采样方式和limit参数以插值填补创建的间隙:

代码语言:javascript复制
# from secondly to every 250 milliseconds
In [305]: ts[:2].resample("250ms").asfreq()
Out[305]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250      NaN
2012-01-01 00:00:00.500      NaN
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250ms, dtype: float64

In [306]: ts[:2].resample("250ms").ffill()
Out[306]: 
2012-01-01 00:00:00.000    308
2012-01-01 00:00:00.250    308
2012-01-01 00:00:00.500    308
2012-01-01 00:00:00.750    308
2012-01-01 00:00:01.000    204
Freq: 250ms, dtype: int64

In [307]: ts[:2].resample("250ms").ffill(limit=2)
Out[307]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250    308.0
2012-01-01 00:00:00.500    308.0
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250ms, dtype: float64 
稀疏重新采样

稀疏时间序列是指您要重新采样的时间相对于点数要少得多的时间序列。简单地对稀疏系列进行上采样可能会产生大量中间值。当您不想使用填充这些值的方法时,例如fill_methodNone,那么中间值将被填充为NaN

由于resample是基于时间的 groupby,以下是一种有效地仅重新采样不全为NaN的组的方法。

代码语言:javascript复制
In [308]: rng = pd.date_range("2014-1-1", periods=100, freq="D")   pd.Timedelta("1s")

In [309]: ts = pd.Series(range(100), index=rng) 

如果我们想要重新采样到系列的完整范围:

代码语言:javascript复制
In [310]: ts.resample("3min").sum()
Out[310]: 
2014-01-01 00:00:00     0
2014-01-01 00:03:00     0
2014-01-01 00:06:00     0
2014-01-01 00:09:00     0
2014-01-01 00:12:00     0
 ..
2014-04-09 23:48:00     0
2014-04-09 23:51:00     0
2014-04-09 23:54:00     0
2014-04-09 23:57:00     0
2014-04-10 00:00:00    99
Freq: 3min, Length: 47521, dtype: int64 

相反,我们可以仅重新采样那些具有点的组,如下所示:

代码语言:javascript复制
In [311]: from functools import partial

In [312]: from pandas.tseries.frequencies import to_offset

In [313]: def round(t, freq):
 .....:    freq = to_offset(freq)
 .....:    td = pd.Timedelta(freq)
 .....:    return pd.Timestamp((t.value // td.value) * td.value)
 .....: 

In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]: 
2014-01-01     0
2014-01-02     1
2014-01-03     2
2014-01-04     3
2014-01-05     4
 ..
2014-04-06    95
2014-04-07    96
2014-04-08    97
2014-04-09    98
2014-04-10    99
Length: 100, dtype: int64 
聚合

resample()方法返回一个pandas.api.typing.Resampler实例。类似于聚合 API、groupby API 和 window API,Resampler可以被选择性地重新采样。

对于DataFrame进行重新采样,默认情况下将对所有列执行相同的函数。

代码语言:javascript复制
In [315]: df = pd.DataFrame(
 .....:    np.random.randn(1000, 3),
 .....:    index=pd.date_range("1/1/2012", freq="s", periods=1000),
 .....:    columns=["A", "B", "C"],
 .....: )
 .....: 

In [316]: r = df.resample("3min")

In [317]: r.mean()
Out[317]: 
 A         B         C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00  0.056909  0.146731 -0.024320
2012-01-01 00:06:00 -0.058837  0.047046 -0.052021
2012-01-01 00:09:00  0.063123 -0.026158 -0.066533
2012-01-01 00:12:00  0.186340 -0.003144  0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046 

通过标准的 getitem 方法,我们可以选择特定的列或多列。

代码语言:javascript复制
In [318]: r["A"].mean()
Out[318]: 
2012-01-01 00:00:00   -0.033823
2012-01-01 00:03:00    0.056909
2012-01-01 00:06:00   -0.058837
2012-01-01 00:09:00    0.063123
2012-01-01 00:12:00    0.186340
2012-01-01 00:15:00   -0.085954
Freq: 3min, Name: A, dtype: float64

In [319]: r[["A", "B"]].mean()
Out[319]: 
 A         B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00  0.056909  0.146731
2012-01-01 00:06:00 -0.058837  0.047046
2012-01-01 00:09:00  0.063123 -0.026158
2012-01-01 00:12:00  0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287 

您可以传递一个函数列表或字典进行聚合,输出一个DataFrame

代码语言:javascript复制
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]: 
 sum      mean       std
2012-01-01 00:00:00  -6.088060 -0.033823  1.043263
2012-01-01 00:03:00  10.243678  0.056909  1.058534
2012-01-01 00:06:00 -10.590584 -0.058837  0.949264
2012-01-01 00:09:00  11.362228  0.063123  1.028096
2012-01-01 00:12:00  33.541257  0.186340  0.884586
2012-01-01 00:15:00  -8.595393 -0.085954  1.035476 

在重新采样的DataFrame上,您可以传递一个函数列表以应用于每列���从而产生具有分层索引的聚合结果:

代码语言:javascript复制
In [321]: r.agg(["sum", "mean"])
Out[321]: 
 A            ...          C 
 sum      mean  ...        sum      mean
2012-01-01 00:00:00  -6.088060 -0.033823  ... -14.660515 -0.081447
2012-01-01 00:03:00  10.243678  0.056909  ...  -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837  ...  -9.363825 -0.052021
2012-01-01 00:09:00  11.362228  0.063123  ... -11.975895 -0.066533
2012-01-01 00:12:

0 人点赞