Pandas-2-2-中文文档-十三-

Pandas 2.2 中文文档(十三)

原文:pandas.pydata.org/docs/

窗口操作

原文:pandas.pydata.org/docs/user_guide/window.html

pandas 包含一组紧凑的 API,用于执行窗口操作 - 一种在值的滑动分区上执行聚合的操作。该 API 的功能类似于groupby API,SeriesDataFrame调用具有必要参数的窗口方法,然后随后调用聚合函数。

In [1]: s = pd.Series(range(5))

In [2]: s.rolling(window=2).sum()
Out[2]: 
0    NaN
1    1.0
2    3.0
3    5.0
4    7.0
dtype: float64 

窗口由从当前观察值向后查看窗口长度组成。上述结果可以通过对数据的以下窗口分区求和来得出:

In [3]: for window in s.rolling(window=2):
 ...:    print(window)
 ...: 
0    0
dtype: int64
0    0
1    1
dtype: int64
1    1
2    2
dtype: int64
2    2
3    3
dtype: int64
3    3
4    4
dtype: int64 

概述

pandas 支持 4 种类型的窗口操作:

  1. 滚动窗口:对值进行通用固定或可变滑动窗口。

  2. 加权窗口:由scipy.signal库提供的加权非矩形窗口。

  3. 扩展窗口:对值进行累积窗口。

  4. 指数加权窗口:对值进行累积和指数加权窗口。

概念 方法 返回对象 支持基于时间的窗口 支持链式分组 支持表方法 支持在线操作
滚动窗口 rolling pandas.typing.api.Rolling 是(自 1.3 版本起)
加权窗口 rolling pandas.typing.api.Window
扩展窗口 expanding pandas.typing.api.Expanding 是(自 1.3 版本起)
指数加权窗口 ewm pandas.typing.api.ExponentialMovingWindow 是(自 1.2 版本起) 是(自 1.3 版本起)

如上所述,一些操作支持根据时间偏移量指定窗口:

In [4]: s = pd.Series(range(5), index=pd.date_range('2020-01-01', periods=5, freq='1D'))

In [5]: s.rolling(window='2D').sum()
Out[5]: 
2020-01-01    0.0
2020-01-02    1.0
2020-01-03    3.0
2020-01-04    5.0
2020-01-05    7.0
Freq: D, dtype: float64 

此外,一些方法支持将groupby操作与窗口操作链接在一起,该操作将首先按指定键对数据进行分组,然后对每个组执行窗口操作。

In [6]: df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a'], 'B': range(5)})

In [7]: df.groupby('A').expanding().sum()
Out[7]: 
 B
A 
a 0  0.0
 2  2.0
 4  6.0
b 1  1.0
 3  4.0 

注意

窗口操作目前仅支持数值数据(整数和浮点数),并且始终返回float64值。

警告

一些窗口聚合方法,meansumvarstd方法可能由于底层窗口算法累积和而受到数值不精确性的影响。当值的数量级不同时(1/np.finfo(np.double).eps),会导致截断。必须注意,大值可能会对不包括这些值的窗口产生影响。使用Kahan 求和算法来计算滚动求和以尽可能保持准确性。

自 1.3.0 版本起新增。

一些窗口操作还支持构造函数中的method='table'选项,该选项可以在整个DataFrame上执行窗口操作,而不是一次处理单个列或行。对于具有许多列或行(使用相应的axis参数)的DataFrame,这可以提供有用的性能优势,或者在窗口操作期间利用其他列。只有在相应的方法调用中指定了engine='numba'时,才能使用method='table'选项。

例如,可以通过在apply()中指定一个权重列来计算加权平均值

In [8]: def weighted_mean(x):
 ...:    arr = np.ones((1, x.shape[1]))
 ...:    arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
 ...:    return arr
 ...: 

In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba")  # noqa: E501
Out[10]: 
 0         1    2
0  1.000000  2.000000  1.0
1  1.800000  2.000000  1.0
2  3.333333  2.333333  1.0
3  1.555556  7.000000  1.0 

1.3 版本中新增。

一些窗口操作在构造窗口对象后还支持online方法,该方法返回一个新对象,支持传入新的DataFrameSeries对象,以使用新值继续窗口计算(即在线计算)。

新窗口对象上的方法必须首先调用聚合方法以“启动”在线计算的初始状态。然后,可以通过update参数传递新的DataFrameSeries对象来继续窗口计算。

In [11]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [12]: df.ewm(0.5).mean()
Out[12]: 
 0         1         2
0  1.000000  2.000000  0.600000
1  1.750000  2.750000  0.450000
2  2.615385  3.615385  0.276923
3  3.550000  4.550000  0.562500 
In [13]: online_ewm = df.head(2).ewm(0.5).online()

In [14]: online_ewm.mean()
Out[14]: 
 0     1     2
0  1.00  2.00  0.60
1  1.75  2.75  0.45

In [15]: online_ewm.mean(update=df.tail(1))
Out[15]: 
 0         1         2
3  3.307692  4.307692  0.623077 

所有窗口操作都支持一个min_periods参数,该参数规定窗口必须具有的非np.nan值的最小数量;否则,结果值为np.nan。对于基于时间的窗口,默认值为 1,对于固定窗口,默认为window

In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])

In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]: 
0    NaN
1    1.0
2    3.0
3    3.0
4    2.0
5    3.0
dtype: float64

In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]: 
0    NaN
1    NaN
2    3.0
3    3.0
4    NaN
5    NaN
dtype: float64

# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]: 
0   NaN
1   NaN
2   NaN
3   NaN
4   NaN
5   NaN
dtype: float64 

此外,所有窗口操作都支持aggregate方法,用于返回应用于窗口的多个聚合的结果。

In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})

In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]: 
 A                    B 
 sum mean       std   sum  mean       std
0   0.0  0.0       NaN  10.0  10.0       NaN
1   1.0  0.5  0.707107  21.0  10.5  0.707107
2   3.0  1.0  1.000000  33.0  11.0  1.000000
3   6.0  1.5  1.290994  46.0  11.5  1.290994
4  10.0  2.0  1.581139  60.0  12.0  1.581139 
```  ## 滚动窗口

通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。

```py
In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']

In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))

In [24]: s
Out[24]: 
2020-01-01    0
2020-01-03    1
2020-01-04    2
2020-01-05    3
2020-01-29    4
dtype: int64

# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]: 
2020-01-01    NaN
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    7.0
dtype: float64

# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]: 
2020-01-01    0.0
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    4.0
dtype: float64 

查看所有支持的聚合函数,请参阅滚动窗口函数。

居中窗口

默认情况下,标签设置为窗口的右边缘,但可以使用center关键字将标签设置��中心。

In [27]: s = pd.Series(range(10))

In [28]: s.rolling(window=5).mean()
Out[28]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [29]: s.rolling(window=5, center=True).mean()
Out[29]: 
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
5    5.0
6    6.0
7    7.0
8    NaN
9    NaN
dtype: float64 

这也可以应用于类似于日期时间的索引。

1.3.0 版本中新增。

In [30]: df = pd.DataFrame(
 ....:    {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
 ....: )
 ....: 

In [31]: df
Out[31]: 
 A
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4

In [32]: df.rolling("2D", center=False).mean()
Out[32]: 
 A
2020-01-01  0.0
2020-01-02  0.5
2020-01-03  1.5
2020-01-04  2.5
2020-01-05  3.5

In [33]: df.rolling("2D", center=True).mean()
Out[33]: 
 A
2020-01-01  0.5
2020-01-02  1.5
2020-01-03  2.5
2020-01-04  3.5
2020-01-05  4.0 
```  ### 滚动窗口端点

可以使用`closed`参数指定在滚动窗口计算中包含间隔端点的方式:

| 值 | 行为 |
| --- | --- |
| `'right'` | 关闭右端点 |
| `'left'` | 关闭左端点 |
| `'both'` | 关闭两个端点 |
| `'neither'` | 开放端点 |

例如,将右端点保持开放在许多需要确保没有来自当前信息到过去信息的污染的问题中非常有用。这允许滚动窗口计算统计数据“直到那个时间点”,但不包括那个时间点。

```py
In [34]: df = pd.DataFrame(
 ....:    {"x": 1},
 ....:    index=[
 ....:        pd.Timestamp("20130101 09:00:01"),
 ....:        pd.Timestamp("20130101 09:00:02"),
 ....:        pd.Timestamp("20130101 09:00:03"),
 ....:        pd.Timestamp("20130101 09:00:04"),
 ....:        pd.Timestamp("20130101 09:00:06"),
 ....:    ],
 ....: )
 ....: 

In [35]: df["right"] = df.rolling("2s", closed="right").x.sum()  # default

In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()

In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()

In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()

In [39]: df
Out[39]: 
 x  right  both  left  neither
2013-01-01 09:00:01  1    1.0   1.0   NaN      NaN
2013-01-01 09:00:02  1    2.0   2.0   1.0      1.0
2013-01-01 09:00:03  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:04  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:06  1    1.0   2.0   1.0      NaN 
```  ### 自定义窗口滚动

除了接受整数或偏移作为`window`参数外,`rolling`还接受一个`BaseIndexer`子类,允许用户定义用于计算窗口边界的自定义方法。`BaseIndexer`子类将需要定义一个`get_window_bounds`方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,`num_values`、`min_periods`、`center`、`closed`和`step`将自动传递给`get_window_bounds`,定义的方法必须始终接受这些参数。

例如,如果我们有以下`DataFrame`

```py
In [40]: use_expanding = [True, False, True, False, True]

In [41]: use_expanding
Out[41]: [True, False, True, False, True]

In [42]: df = pd.DataFrame({"values": range(5)})

In [43]: df
Out[43]: 
 values
0       0
1       1
2       2
3       3
4       4 

如果我们想要使用一个扩展窗口,其中use_expandingTrue,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer子类:

In [44]: from pandas.api.indexers import BaseIndexer

In [45]: class CustomIndexer(BaseIndexer):
 ....:     def get_window_bounds(self, num_values, min_periods, center, closed, step):
 ....:         start = np.empty(num_values, dtype=np.int64)
 ....:         end = np.empty(num_values, dtype=np.int64)
 ....:         for i in range(num_values):
 ....:             if self.use_expanding[i]:
 ....:                 start[i] = 0
 ....:                 end[i] = i + 1
 ....:             else:
 ....:                 start[i] = i
 ....:                 end[i] = i + self.window_size
 ....:         return start, end
 ....: 

In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

In [47]: df.rolling(indexer).sum()
Out[47]: 
 values
0     0.0
1     1.0
2     3.0
3     3.0
4    10.0 

您可以在这里查看其他BaseIndexer子类的示例。

在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer,它允许在非固定偏移(如BusinessDay)上进行滚动操作。

In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer

In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))

In [50]: offset = pd.offsets.BDay(1)

In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)

In [52]: df
Out[52]: 
 0
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4
2020-01-06  5
2020-01-07  6
2020-01-08  7
2020-01-09  8
2020-01-10  9

In [53]: df.rolling(indexer).sum()
Out[53]: 
 0
2020-01-01   0.0
2020-01-02   1.0
2020-01-03   2.0
2020-01-04   3.0
2020-01-05   7.0
2020-01-06  12.0
2020-01-07   6.0
2020-01-08   7.0
2020-01-09   8.0
2020-01-10   9.0 

对于一些问题,未来的知识可用于分析。例如,当每个数据点都是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能很有用。为此,可以使用FixedForwardWindowIndexer类。这个BaseIndexer子类实现了一个闭合的固定宽度前瞻性滚动窗口,我们可以按照以下方式使用它:

In [54]: from pandas.api.indexers import FixedForwardWindowIndexer

In [55]: indexer = FixedForwardWindowIndexer(window_size=2)

In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]: 
 0
2020-01-01   1.0
2020-01-02   3.0
2020-01-03   5.0
2020-01-04   7.0
2020-01-05   9.0
2020-01-06  11.0
2020-01-07  13.0
2020-01-08  15.0
2020-01-09  17.0
2020-01-10   9.0 

我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现这一点,如下面的示例所示:

In [57]: df = pd.DataFrame(
 ....:    data=[
 ....:        [pd.Timestamp("2018-01-01 00:00:00"), 100],
 ....:        [pd.Timestamp("2018-01-01 00:00:01"), 101],
 ....:        [pd.Timestamp("2018-01-01 00:00:03"), 103],
 ....:        [pd.Timestamp("2018-01-01 00:00:04"), 111],
 ....:    ],
 ....:    columns=["time", "value"],
 ....: ).set_index("time")
 ....: 

In [58]: df
Out[58]: 
 value
time 
2018-01-01 00:00:00    100
2018-01-01 00:00:01    101
2018-01-01 00:00:03    103
2018-01-01 00:00:04    111

In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [60]: reversed_df
Out[60]: 
 value
time 
2018-01-01 00:00:00  201.0
2018-01-01 00:00:01  101.0
2018-01-01 00:00:03  214.0
2018-01-01 00:00:04  111.0 
```  ### 滚动应用

`apply()`函数接受额外的`func`参数并执行通用的滚动计算。`func`参数应该是一个从 ndarray 输入产生单个值的函数。`raw`指定窗口是作为`Series`对象(`raw=False`)还是 ndarray 对象(`raw=True`)。

```py
In [61]: def mad(x):
 ....:    return np.fabs(x - x.mean()).mean()
 ....: 

In [62]: s = pd.Series(range(10))

In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]: 
0    NaN
1    NaN
2    NaN
3    1.0
4    1.0
5    1.0
6    1.0
7    1.0
8    1.0
9    1.0
dtype: float64 
```  ### Numba 引擎

此外,如果安装了 [Numba](https://numba.pydata.org/) 作为可选依赖项,`apply()` 可以利用 Numba。通过指定 `engine='numba'` 和 `engine_kwargs` 参数(`raw` 也必须设置为 `True`),可以使用 Numba 执行应用聚合。有关参数的一般用法和性能考虑,请参见使用 Numba 提升性能。

Numba 将应用于可能的两个例程:

1.  如果 `func` 是一个标准的 Python 函数,引擎将[JIT](https://numba.pydata.org/numba-doc/latest/user/overview.html)传递的函数。`func` 也可以是一个已经 JIT 的函数,在这种情况下,引擎将不会再次 JIT 函数。

1.  引擎将对应用函数应用于每个窗口的 for 循环进行 JIT。

`engine_kwargs` 参数是一个关键字参数字典,将传递给[numba.jit 装饰器](https://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#numba.jit)。这些关键字参数将应用于*传递的函数*(如果是标准的 Python 函数)和对每个窗口进行的应用循环。

版本 1.3.0 中的新功能。

`mean`、`median`、`max`、`min` 和 `sum` 也支持 `engine` 和 `engine_kwargs` 参数。### 二进制窗口函数

`cov()` 和 `corr()` 可以计算关于两个`Series`或任何`DataFrame`/`Series`或`DataFrame`/`DataFrame`组合的移动窗口统计。在每种情况下的行为如下:

+   两个`Series`:计算配对的统计量。

+   `DataFrame`/`Series`:计算 DataFrame 的每一列与传递的 Series 的统计量,从而返回一个 DataFrame。

+   `DataFrame`/`DataFrame`:默认情况下计算匹配列名的统计量,返回一个 DataFrame。如果传递了关键字参数 `pairwise=True`,则为每对列计算统计量,返回一个具有值为相关日期的`DataFrame`的`MultiIndex`(请参见下一节)。

例如:

```py
In [64]: df = pd.DataFrame(
 ....:    np.random.randn(10, 4),
 ....:    index=pd.date_range("2020-01-01", periods=10),
 ....:    columns=["A", "B", "C", "D"],
 ....: )
 ....: 

In [65]: df = df.cumsum()

In [66]: df2 = df[:4]

In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]: 
 A    B    C    D
2020-01-01  NaN  NaN  NaN  NaN
2020-01-02 -1.0  1.0 -1.0  1.0
2020-01-03  1.0  1.0  1.0 -1.0
2020-01-04 -1.0  1.0  1.0 -1.0 
```### 计算滚动配对协方差和相关性

在金融数据分析和其他领域,常常需要计算一组时间序列的协方差和相关矩阵。通常也对移动窗口的协方差和相关矩阵感兴趣。可以通过传递`pairwise`关键字参数来实现这一点,在`DataFrame`输入的情况下,将产生一个多级索引的`DataFrame`,其`index`是相关日期。在单个 DataFrame 参数的情况下,甚至可以省略`pairwise`参数:

注意

缺失值将被忽略,并且每个条目将使用成对完整观测值计算。

假设缺失数据是随机缺失的,这将导致对协方差矩阵的估计是无偏的。然而,对于许多应用程序,这种估计可能不可接受,因为估计的协方差矩阵不能保证是半正定的。这可能导致估计的相关性具有绝对值大于一,和/或不可逆的协方差矩阵。有关更多详细信息,请参阅[协方差矩阵的估计](https://en.wikipedia.org/w/index.php?title=Estimation_of_covariance_matrices)。

```py
In [68]: covs = (
 ....:    df[["B", "C", "D"]]
 ....:    .rolling(window=4)
 ....:    .cov(df[["A", "B", "C"]], pairwise=True)
 ....: )
 ....: 

In [69]: covs
Out[69]: 
 B         C         D
2020-01-01 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
 C       NaN       NaN       NaN
2020-01-02 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
...                ...       ...       ...
2020-01-09 B  0.342006  0.230190  0.052849
 C  0.230190  1.575251  0.082901
2020-01-10 A -0.333945  0.006871 -0.655514
 B  0.649711  0.430860  0.469271
 C  0.430860  0.829721  0.055300

[30 rows x 3 columns] 
```## 加权窗口

`.rolling`中的`win_type`参数生成了常用于滤波和频谱估计的加权窗口。`win_type`必须是一个对应于[scipy.signal 窗口函数](https://docs.scipy.org/doc/scipy/reference/signal.windows.html#module-scipy.signal.windows)的字符串。必须安装 Scipy 才能使用这些窗口,并且必须在聚合函数中指定 Scipy 窗口方法所需的补充参数。

```py
In [70]: s = pd.Series(range(10))

In [71]: s.rolling(window=5).mean()
Out[71]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64 

对于所有支持的聚合函数,请参阅加权窗口函数。## 扩展窗口

扩展窗口产生一个聚合统计量的值,其中包含截至该时间点的所有可用数据。由于这些计算是滚动统计的特例,因此在 pandas 中实现了以下两个调用是等效的:

In [74]: df = pd.DataFrame(range(5))

In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]: 
 0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

In [76]: df.expanding(min_periods=1).mean()
Out[76]: 
 0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0 

对于所有支持的聚合函数,请参阅扩展窗口函数。## 指数加权窗口

指数加权窗口类似于扩展窗口,但是每个先前点相对于当前点按指数方式加权。

一般来说,加权移动平均值计算如下

[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},]

其中(x_t)是输入,(y_t)是结果,(w_i)是权重。

对于所有支持的聚合函数,请参阅指数加权窗口函数。

EW 函数支持指数权重的两个变体。默认情况下,adjust=True使用权重(w_i = (1 - \alpha)^i),得到

[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)² + ... + (1 - \alpha)^t}]

当指定adjust=False时,移动平均值计算如下

[\begin{split}y_0 &= x_0 \ y_t &= (1 - \alpha) y_{t-1} + \alpha x_t,\end{split}]

这等价于使用权重

[\begin{split}w_i = \begin{cases} \alpha (1 - \alpha)^i & \text{if } i < t \ (1 - \alpha)^i & \text{if } i = t. \end{cases}\end{split}]

注意

有时这些方程以(\alpha' = 1 - \alpha)的形式书写,例如

[y_t = \alpha' y_{t-1} + (1 - \alpha') x_t.]

上述两个变体之间的差异是因为我们处理的是具有有限历史记录的系列。考虑一个具有无限历史记录的系列,其中adjust=True

[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...} {1 + (1 - \alpha) + (1 - \alpha)² + ...}]

注意分母是一个初始项为 1 且比率为(1 - \alpha)的几何级数,我们有

[\begin{split}y_t &= \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...} {\frac{1}{1 - (1 - \alpha)}}\ &= [x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...] \alpha \ &= \alpha x_t + [(1-\alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...]\alpha \ &= \alpha x_t + (1 - \alpha)[x_{t-1} + (1 - \alpha) x_{t-2} + ...]\alpha\ &= \alpha x_t + (1 - \alpha) y_{t-1}\end{split}]

这与上面的adjust=False表达式相同,因此显示了无穷级数的两个变体的等价性。当adjust=False时,我们有(y_0 = x_0)和(y_t = \alpha x_t + (1 - \alpha) y_{t-1})。因此,有一个假设,即(x_0)不是普通值,而是到目前为止无穷级数的指数加权矩。

必须满足(0 < \alpha \leq 1),虽然可以直接传递(\alpha),但通常更容易考虑 EW 矩的跨度质心(com)半衰期

[\begin{split}\alpha = \begin{cases} \frac{2}{s + 1}, & \text{对于跨度}\ s \geq 1\ \frac{1}{1 + c}, & \text{对于质心}\ c \geq 0\ 1 - \exp^{\frac{\log 0.5}{h}}, & \text{对于半衰期}\ h > 0 \end{cases}\end{split}]

必须精确指定跨度质心半衰期alpha中的一个来使用 EW 函数:

  • 跨度对应于通常称为“N 天 EW 移动平均”的概念。

  • 质心具有更具物理意义的解释,可以按照跨度来考虑:(c = (s - 1) / 2)。

  • 半衰期是指数权重减少到一半所需的时间段。

  • Alpha直接指定平滑因子。

您还可以指定halflife,以时间间隔可转换的单位来指定观察值衰减到一半所需的时间,同时指定一系列times

In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})

In [78]: df
Out[78]: 
 B
0  0.0
1  1.0
2  2.0
3  NaN
4  4.0

In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]

In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]: 
 B
0  0.000000
1  0.585786
2  1.523889
3  1.523889
4  3.233686 

以下公式用于计算具有时间输入向量的指数加权均值:

[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},]

指数加权窗口也有一个ignore_na参数,用于确定中间空值如何影响权重的计算。当ignore_na=False(默认)时,权重是基于绝对位置计算的,因此中间的空值会影响结果。当ignore_na=True时,通过忽略中间的空值来计算权重。例如,假设adjust=True,如果ignore_na=False,则3, NaN, 5的加权平均值将被计算为

[\frac{(1-\alpha)² \cdot 3 + 1 \cdot 5}{(1-\alpha)² + 1}.]

ignore_na=True时,加权平均值将被计算为

[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.]

var()std()cov()函数有一个bias参数,指定结果是否应包含有偏或无偏的统计数据。例如,如果bias=True,则ewmvar(x)被计算为ewmvar(x) = ewma(x**2) - ewma(x)**2;而如果bias=False(默认),有偏方差统计数据将通过去偏因子进行缩放

[\frac{\left(\sum_{i=0}^t w_i\right)²}{\left(\sum_{i=0}^t w_i\right)² - \sum_{i=0}^t w_i²}.]

(对于(w_i = 1),这将减少到通常的(N / (N - 1))因子,其中(N = t + 1)。)有关更多详细信息,请参阅维基百科上的加权样本方差。 ## 概述

pandas 支持 4 种类型的窗口操作:

  1. 滚动窗口:对数值进行通用的固定或可变滑动窗口。

  2. 加权窗口:由scipy.signal库提供的加权、非矩形窗口。

  3. 扩展窗口:对数值进行累积窗口。

  4. 指数加权窗口:对数值进行累积和指数加权的窗口。

概念 方法 返回对象 支持基于时间的窗口 支持链接的 groupby 支持表方法 支持在线操作
滚动窗口 rolling pandas.typing.api.Rolling 是(自版本 1.3 起)
加权窗口 rolling pandas.typing.api.Window
扩展窗口 expanding pandas.typing.api.Expanding 是(自版本 1.3 起)
指数加权窗口 ewm pandas.typing.api.ExponentialMovingWindow 是(自版本 1.2 起) 是(自版本 1.3 起)

如上所述,一些操作支持基于时间偏移量指定窗口:

In [4]: s = pd.Series(range(5), index=pd.date_range('2020-01-01', periods=5, freq='1D'))

In [5]: s.rolling(window='2D').sum()
Out[5]: 
2020-01-01    0.0
2020-01-02    1.0
2020-01-03    3.0
2020-01-04    5.0
2020-01-05    7.0
Freq: D, dtype: float64 

此外,一些方法支持将groupby操作与窗口操作链接在一起,首先按指定键对数据进行分组,然后对每个组执行窗口操作。

In [6]: df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a'], 'B': range(5)})

In [7]: df.groupby('A').expanding().sum()
Out[7]: 
 B
A 
a 0  0.0
 2  2.0
 4  6.0
b 1  1.0
 3  4.0 

注意

窗口操作目前仅支持数值数据(整数和浮点数)���并且始终返回float64值。

警告

一些窗口聚合,meansumvarstd方法可能由于底层窗口算法累积和而遭受数值不精确性。当值的数量级不同时(1/np.finfo(np.double).eps),会导致截断。必须注意,大值可能会影响不包括这些值的窗口。使用Kahan 求和算法来计算滚动求和以尽可能保持准确性。

版本 1.3.0 中的新功能。

一些窗口操作在构造函数中还支持method='table'选项,该选项可以在整个DataFrame上执行窗口操作,而不是一次处理单个列或行。对于具有许多列或行(使用相应的axis参数)的DataFrame,或者在窗口操作期间利用其他列的能力,这可以提供有用的性能优势。只有在相应的方法调用中指定了engine='numba'时,才能使用method='table'选项。

例如,可以通过指定一个单独的权重列,在apply()中计算加权平均值

In [8]: def weighted_mean(x):
 ...:    arr = np.ones((1, x.shape[1]))
 ...:    arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
 ...:    return arr
 ...: 

In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba")  # noqa: E501
Out[10]: 
 0         1    2
0  1.000000  2.000000  1.0
1  1.800000  2.000000  1.0
2  3.333333  2.333333  1.0
3  1.555556  7.000000  1.0 

版本 1.3 中的新功能。

在构造窗口对象后,一些窗口操作还支持online方法,该方法返回一个新对象,支持传入新的DataFrameSeries对象,以继续使用新值进行窗口计算(即在线计算)。

这些新窗口对象上的方法必须首先调用聚合方法来“启动”在线计算的初始状态。然后,可以通过update参数传入新的DataFrameSeries对象,以继续进行窗口计算。

In [11]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [12]: df.ewm(0.5).mean()
Out[12]: 
 0         1         2
0  1.000000  2.000000  0.600000
1  1.750000  2.750000  0.450000
2  2.615385  3.615385  0.276923
3  3.550000  4.550000  0.562500 
In [13]: online_ewm = df.head(2).ewm(0.5).online()

In [14]: online_ewm.mean()
Out[14]: 
 0     1     2
0  1.00  2.00  0.60
1  1.75  2.75  0.45

In [15]: online_ewm.mean(update=df.tail(1))
Out[15]: 
 0         1         2
3  3.307692  4.307692  0.623077 

所有窗口操作都支持min_periods参数,该参数规定窗口必须具有的非np.nan值的最小数量;否则,结果值为np.nan。对于基于时间的窗口,默认值为 1,对于固定窗口,默认值为window

In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])

In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]: 
0    NaN
1    1.0
2    3.0
3    3.0
4    2.0
5    3.0
dtype: float64

In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]: 
0    NaN
1    NaN
2    3.0
3    3.0
4    NaN
5    NaN
dtype: float64

# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]: 
0   NaN
1   NaN
2   NaN
3   NaN
4   NaN
5   NaN
dtype: float64 

此外,所有窗口操作都支持aggregate方法,用于返回应用于窗口的多个聚合的结果。

In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})

In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]: 
 A                    B 
 sum mean       std   sum  mean       std
0   0.0  0.0       NaN  10.0  10.0       NaN
1   1.0  0.5  0.707107  21.0  10.5  0.707107
2   3.0  1.0  1.000000  33.0  11.0  1.000000
3   6.0  1.5  1.290994  46.0  11.5  1.290994
4  10.0  2.0  1.581139  60.0  12.0  1.581139 

滚动窗口

通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。

In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']

In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))

In [24]: s
Out[24]: 
2020-01-01    0
2020-01-03    1
2020-01-04    2
2020-01-05    3
2020-01-29    4
dtype: int64

# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]: 
2020-01-01    NaN
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    7.0
dtype: float64

# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]: 
2020-01-01    0.0
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    4.0
dtype: float64 

有关所有支持的聚合函数,请参见滚动窗口函数。

居中窗口

默认情况下,标签设置为窗口的右边缘,但可以使用center关键字将标签设置为中心。

In [27]: s = pd.Series(range(10))

In [28]: s.rolling(window=5).mean()
Out[28]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [29]: s.rolling(window=5, center=True).mean()
Out[29]: 
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
5    5.0
6    6.0
7    7.0
8    NaN
9    NaN
dtype: float64 

这也可以应用于类似日期时间的索引。

1.3.0 版本中的新功能。

In [30]: df = pd.DataFrame(
 ....:    {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
 ....: )
 ....: 

In [31]: df
Out[31]: 
 A
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4

In [32]: df.rolling("2D", center=False).mean()
Out[32]: 
 A
2020-01-01  0.0
2020-01-02  0.5
2020-01-03  1.5
2020-01-04  2.5
2020-01-05  3.5

In [33]: df.rolling("2D", center=True).mean()
Out[33]: 
 A
2020-01-01  0.5
2020-01-02  1.5
2020-01-03  2.5
2020-01-04  3.5
2020-01-05  4.0 
```  ### 滚动窗口端点

可以使用`closed`参数指定在滚动窗口计算中包含间隔端点:

| 值 | 行为 |
| --- | --- |
| `'right'` | 关闭右端点 |
| `'left'` | 关闭左端点 |
| `'both'` | 关闭两个端点 |
| `'neither'` | 开放端点 |

例如,正确地开放右端点在许多需要确保当前信息不会影响过去信息的问题中非常有用。这允许滚动窗口计算统计数据“直到那个时间点”,但不包括那个时间点。

```py
In [34]: df = pd.DataFrame(
 ....:    {"x": 1},
 ....:    index=[
 ....:        pd.Timestamp("20130101 09:00:01"),
 ....:        pd.Timestamp("20130101 09:00:02"),
 ....:        pd.Timestamp("20130101 09:00:03"),
 ....:        pd.Timestamp("20130101 09:00:04"),
 ....:        pd.Timestamp("20130101 09:00:06"),
 ....:    ],
 ....: )
 ....: 

In [35]: df["right"] = df.rolling("2s", closed="right").x.sum()  # default

In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()

In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()

In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()

In [39]: df
Out[39]: 
 x  right  both  left  neither
2013-01-01 09:00:01  1    1.0   1.0   NaN      NaN
2013-01-01 09:00:02  1    2.0   2.0   1.0      1.0
2013-01-01 09:00:03  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:04  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:06  1    1.0   2.0   1.0      NaN 
```  ### 自定义窗口滚动

除了接受整数或偏移作为`window`参数外,`rolling`还接受一个`BaseIndexer`子类,允许用户定义计算窗口边界的自定义方法。`BaseIndexer`子类需要定义一个`get_window_bounds`方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,`num_values`、`min_periods`、`center`、`closed`和`step`将自动传递给`get_window_bounds`,定义的方法必须始终接受这些参数。

例如,如果我们有以下`DataFrame`

```py
In [40]: use_expanding = [True, False, True, False, True]

In [41]: use_expanding
Out[41]: [True, False, True, False, True]

In [42]: df = pd.DataFrame({"values": range(5)})

In [43]: df
Out[43]: 
 values
0       0
1       1
2       2
3       3
4       4 

如果我们想要使用一个扩展窗口,其中use_expandingTrue,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer子类:

In [44]: from pandas.api.indexers import BaseIndexer

In [45]: class CustomIndexer(BaseIndexer):
 ....:     def get_window_bounds(self, num_values, min_periods, center, closed, step):
 ....:         start = np.empty(num_values, dtype=np.int64)
 ....:         end = np.empty(num_values, dtype=np.int64)
 ....:         for i in range(num_values):
 ....:             if self.use_expanding[i]:
 ....:                 start[i] = 0
 ....:                 end[i] = i + 1
 ....:             else:
 ....:                 start[i] = i
 ....:                 end[i] = i + self.window_size
 ....:         return start, end
 ....: 

In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

In [47]: df.rolling(indexer).sum()
Out[47]: 
 values
0     0.0
1     1.0
2     3.0
3     3.0
4    10.0 

您可以在这里查看其他BaseIndexer子类的示例

在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer,它允许在非固定偏移(如BusinessDay)上进行滚动操作。

In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer

In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))

In [50]: offset = pd.offsets.BDay(1)

In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)

In [52]: df
Out[52]: 
 0
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4
2020-01-06  5
2020-01-07  6
2020-01-08  7
2020-01-09  8
2020-01-10  9

In [53]: df.rolling(indexer).sum()
Out[53]: 
 0
2020-01-01   0.0
2020-01-02   1.0
2020-01-03   2.0
2020-01-04   3.0
2020-01-05   7.0
2020-01-06  12.0
2020-01-07   6.0
2020-01-08   7.0
2020-01-09   8.0
2020-01-10   9.0 

对于一些问题,未来的知识是可用于分析的。例如,当每个数据点是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能是有用的。为此目的提供了FixedForwardWindowIndexer类。这个BaseIndexer子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:

In [54]: from pandas.api.indexers import FixedForwardWindowIndexer

In [55]: indexer = FixedForwardWindowIndexer(window_size=2)

In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]: 
 0
2020-01-01   1.0
2020-01-02   3.0
2020-01-03   5.0
2020-01-04   7.0
2020-01-05   9.0
2020-01-06  11.0
2020-01-07  13.0
2020-01-08  15.0
2020-01-09  17.0
2020-01-10   9.0 

我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现,如下面的示例所示:

In [57]: df = pd.DataFrame(
 ....:    data=[
 ....:        [pd.Timestamp("2018-01-01 00:00:00"), 100],
 ....:        [pd.Timestamp("2018-01-01 00:00:01"), 101],
 ....:        [pd.Timestamp("2018-01-01 00:00:03"), 103],
 ....:        [pd.Timestamp("2018-01-01 00:00:04"), 111],
 ....:    ],
 ....:    columns=["time", "value"],
 ....: ).set_index("time")
 ....: 

In [58]: df
Out[58]: 
 value
time 
2018-01-01 00:00:00    100
2018-01-01 00:00:01    101
2018-01-01 00:00:03    103
2018-01-01 00:00:04    111

In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [60]: reversed_df
Out[60]: 
 value
time 
2018-01-01 00:00:00  201.0
2018-01-01 00:00:01  101.0
2018-01-01 00:00:03  214.0
2018-01-01 00:00:04  111.0 
```  ### 滚动应用

`apply()` 函数接受额外的 `func` 参数并执行通用的滚动计算。`func` 参数应该是一个从 ndarray 输入产生单个值的函数。`raw` 指定窗口是作为 `Series` 对象 (`raw=False`) 还是 ndarray 对象 (`raw=True`)。

```py
In [61]: def mad(x):
 ....:    return np.fabs(x - x.mean()).mean()
 ....: 

In [62]: s = pd.Series(range(10))

In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]: 
0    NaN
1    NaN
2    NaN
3    1.0
4    1.0
5    1.0
6    1.0
7    1.0
8    1.0
9    1.0
dtype: float64 
``` ### Numba 引擎

此外,如果安装了 Numba 作为可选依赖项,`apply()` 可以利用 [Numba](https://numba.pydata.org/)。通过指定 `engine='numba'` 和 `engine_kwargs` 参数(`raw` 也必须设置为 `True`),可以使用 Numba 执行 apply 聚合。参见使用 Numba 提升性能以获取参数的一般用法和性能考虑。

Numba 将应用于可能的两个例程:

1.  如果 `func` 是标准 Python 函数,则引擎将[JIT](https://numba.pydata.org/numba-doc/latest/user/overview.html)传入的函数。`func` 也可以是一个已经 JIT 的函数,此时引擎将不会再次 JIT 函数。

1.  引擎将 JIT 应用于将 apply 函数应用于每个窗口的循环。

`engine_kwargs` 参数是一个关键字参数字典,将传递给 [numba.jit 装饰器](https://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#numba.jit)。这些关键字参数将应用于*传入的函数*(如果是标准 Python 函数)和应用于每个窗口的 apply 循环。

新版本 1.3.0 中新增功能。

`mean`、`median`、`max`、`min` 和 `sum` 也支持 `engine` 和 `engine_kwargs` 参数。### 二进制窗口函数

`cov()` 和 `corr()` 可以计算关于两个 `Series` 或任何 `DataFrame`/`Series` 或 `DataFrame`/`DataFrame` 的移动窗口统计信息。在每种情况下的行为如下:

+   两个 `Series`:计算配对的统计信息。

+   `DataFrame`/`Series`:使用传入的 Series 计算 DataFrame 的每一列的统计信息,从而返回一个 DataFrame。

+   `DataFrame`/`DataFrame`:默认情况下,计算匹配列名的统计量,返回一个 DataFrame。如果传递了关键字参数`pairwise=True`,则为每对列计算统计量,返回一个具有`MultiIndex`的`DataFrame`,其值是相关日期(请参见下一节)。

例如:

```py
In [64]: df = pd.DataFrame(
 ....:    np.random.randn(10, 4),
 ....:    index=pd.date_range("2020-01-01", periods=10),
 ....:    columns=["A", "B", "C", "D"],
 ....: )
 ....: 

In [65]: df = df.cumsum()

In [66]: df2 = df[:4]

In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]: 
 A    B    C    D
2020-01-01  NaN  NaN  NaN  NaN
2020-01-02 -1.0  1.0 -1.0  1.0
2020-01-03  1.0  1.0  1.0 -1.0
2020-01-04 -1.0  1.0  1.0 -1.0 
```  ### 计算滚动成对协方差和相关性

在金融数据分析和其他领域,通常会为一组时间序列计算协方差和相关性矩阵。通常还对移动窗口协方差和相关性矩阵感兴趣。可以通过传递`pairwise`关键字参数来实现这一点,在`DataFrame`输入的情况下,将产生一个具有`index`为相关日期的多重索引的`DataFrame`。在单个 DataFrame 参数的情况下,甚至可以省略`pairwise`参数:

注意

缺失值将被忽略,并且每个条目都是使用成对完整观察值计算的。

假设缺失数据是随机缺失的,这将导致协方差矩阵的估计是无偏的。然而,对于许多应用程序来说,这种估计可能不可接受,因为估计的协方差矩阵不能保证是正半定的。这可能导致估计的相关性具有绝对值大于一,和/或一个不可逆的协方差矩阵。更多细节请参见[协方差矩阵的估计](https://en.wikipedia.org/w/index.php?title=Estimation_of_covariance_matrices)。

```py
In [68]: covs = (
 ....:    df[["B", "C", "D"]]
 ....:    .rolling(window=4)
 ....:    .cov(df[["A", "B", "C"]], pairwise=True)
 ....: )
 ....: 

In [69]: covs
Out[69]: 
 B         C         D
2020-01-01 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
 C       NaN       NaN       NaN
2020-01-02 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
...                ...       ...       ...
2020-01-09 B  0.342006  0.230190  0.052849
 C  0.230190  1.575251  0.082901
2020-01-10 A -0.333945  0.006871 -0.655514
 B  0.649711  0.430860  0.469271
 C  0.430860  0.829721  0.055300

[30 rows x 3 columns] 
```  ### 居中窗口

默认情况下,标签被设置在窗口的右边缘,但是有一个`center`关键字可用,因此标签可以设置在中心位置。

```py
In [27]: s = pd.Series(range(10))

In [28]: s.rolling(window=5).mean()
Out[28]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [29]: s.rolling(window=5, center=True).mean()
Out[29]: 
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
5    5.0
6    6.0
7    7.0
8    NaN
9    NaN
dtype: float64 

这也可以应用于类似日期时间的索引。

版本 1.3.0 中的新功能。

In [30]: df = pd.DataFrame(
 ....:    {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
 ....: )
 ....: 

In [31]: df
Out[31]: 
 A
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4

In [32]: df.rolling("2D", center=False).mean()
Out[32]: 
 A
2020-01-01  0.0
2020-01-02  0.5
2020-01-03  1.5
2020-01-04  2.5
2020-01-05  3.5

In [33]: df.rolling("2D", center=True).mean()
Out[33]: 
 A
2020-01-01  0.5
2020-01-02  1.5
2020-01-03  2.5
2020-01-04  3.5
2020-01-05  4.0 

滚动窗口端点

在滚动窗口计算中包含区间端点可以通过closed参数指定:

行为
'right' 关闭右端点
'left' 关闭左端点
'both' 关闭两个端点
'neither' 开放端点

例如,将右端点保持开放在许多需要确保没有来自当前信息到过去信息的污染的问题中是有用的。这允许滚动窗口计算统计量“直到那个时间点”,但不包括那个时间点。

In [34]: df = pd.DataFrame(
 ....:    {"x": 1},
 ....:    index=[
 ....:        pd.Timestamp("20130101 09:00:01"),
 ....:        pd.Timestamp("20130101 09:00:02"),
 ....:        pd.Timestamp("20130101 09:00:03"),
 ....:        pd.Timestamp("20130101 09:00:04"),
 ....:        pd.Timestamp("20130101 09:00:06"),
 ....:    ],
 ....: )
 ....: 

In [35]: df["right"] = df.rolling("2s", closed="right").x.sum()  # default

In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()

In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()

In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()

In [39]: df
Out[39]: 
 x  right  both  left  neither
2013-01-01 09:00:01  1    1.0   1.0   NaN      NaN
2013-01-01 09:00:02  1    2.0   2.0   1.0      1.0
2013-01-01 09:00:03  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:04  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:06  1    1.0   2.0   1.0      NaN 

自定义窗口滚动

除了接受整数或偏移作为window参数外,rolling还接受一个BaseIndexer子类,允许用户定义用于计算窗口边界的自定义方法。BaseIndexer子类将需要定义一个get_window_bounds方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,num_valuesmin_periodscenterclosedstep将自动传递给get_window_bounds,并且定义的方法必须始终接受这些参数。

例如,如果我们有以下DataFrame

In [40]: use_expanding = [True, False, True, False, True]

In [41]: use_expanding
Out[41]: [True, False, True, False, True]

In [42]: df = pd.DataFrame({"values": range(5)})

In [43]: df
Out[43]: 
 values
0       0
1       1
2       2
3       3
4       4 

如果我们想要使用一个扩展窗口,其中use_expandingTrue,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer子类:

In [44]: from pandas.api.indexers import BaseIndexer

In [45]: class CustomIndexer(BaseIndexer):
 ....:     def get_window_bounds(self, num_values, min_periods, center, closed, step):
 ....:         start = np.empty(num_values, dtype=np.int64)
 ....:         end = np.empty(num_values, dtype=np.int64)
 ....:         for i in range(num_values):
 ....:             if self.use_expanding[i]:
 ....:                 start[i] = 0
 ....:                 end[i] = i + 1
 ....:             else:
 ....:                 start[i] = i
 ....:                 end[i] = i + self.window_size
 ....:         return start, end
 ....: 

In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

In [47]: df.rolling(indexer).sum()
Out[47]: 
 values
0     0.0
1     1.0
2     3.0
3     3.0
4    10.0 

您可以查看其他BaseIndexer子类的示例这里

在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer,它允许在非固定偏移(如BusinessDay)上进行滚动操作。

In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer

In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))

In [50]: offset = pd.offsets.BDay(1)

In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)

In [52]: df
Out[52]: 
 0
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4
2020-01-06  5
2020-01-07  6
2020-01-08  7
2020-01-09  8
2020-01-10  9

In [53]: df.rolling(indexer).sum()
Out[53]: 
 0
2020-01-01   0.0
2020-01-02   1.0
2020-01-03   2.0
2020-01-04   3.0
2020-01-05   7.0
2020-01-06  12.0
2020-01-07   6.0
2020-01-08   7.0
2020-01-09   8.0
2020-01-10   9.0 

对于一些问题,未来的知识是可用于分析的。例如,当每个数据点都是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能很有用。为此,可以使用FixedForwardWindowIndexer类。这个BaseIndexer子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:

In [54]: from pandas.api.indexers import FixedForwardWindowIndexer

In [55]: indexer = FixedForwardWindowIndexer(window_size=2)

In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]: 
 0
2020-01-01   1.0
2020-01-02   3.0
2020-01-03   5.0
2020-01-04   7.0
2020-01-05   9.0
2020-01-06  11.0
2020-01-07  13.0
2020-01-08  15.0
2020-01-09  17.0
2020-01-10   9.0 

通过使用切片、应用滚动聚合,然后翻转结果,我们也可以实现这一点,如下例所示:

In [57]: df = pd.DataFrame(
 ....:    data=[
 ....:        [pd.Timestamp("2018-01-01 00:00:00"), 100],
 ....:        [pd.Timestamp("2018-01-01 00:00:01"), 101],
 ....:        [pd.Timestamp("2018-01-01 00:00:03"), 103],
 ....:        [pd.Timestamp("2018-01-01 00:00:04"), 111],
 ....:    ],
 ....:    columns=["time", "value"],
 ....: ).set_index("time")
 ....: 

In [58]: df
Out[58]: 
 value
time 
2018-01-01 00:00:00    100
2018-01-01 00:00:01    101
2018-01-01 00:00:03    103
2018-01-01 00:00:04    111

In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [60]: reversed_df
Out[60]: 
 value
time 
2018-01-01 00:00:00  201.0
2018-01-01 00:00:01  101.0
2018-01-01 00:00:03  214.0
2018-01-01 00:00:04  111.0 

滚动应用

apply()函数接受额外的func参数并执行通用的滚动计算。func参数应该是一个从 ndarray 输入中产生单个值的函数。raw指定窗口是作为Series对象(raw=False)还是 ndarray 对象(raw=True)。

In [61]: def mad(x):
 ....:    return np.fabs(x - x.mean()).mean()
 ....: 

In [62]: s = pd.Series(range(10))

In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]: 
0    NaN
1    NaN
2    NaN
3    1.0
4    1.0
5    1.0
6    1.0
7    1.0
8    1.0
9    1.0
dtype: float64 

Numba 引擎

此外,如果已安装Numbaapply()还可以利用它作为可选依赖项。可以通过指定engine='numba'engine_kwargs参数来使用 Numba 执行应用聚合(raw也必须设置为True)。有关参数的一般用法和性能考虑,请参见使用 Numba 增强性能。

Numba 将应用于可能的两个例程:

  1. 如果 func 是标准 Python 函数,则引擎将JIT传递的函数。func 也可以是一个 JITed 函数,在这种情况下,引擎将不会再次 JIT 函数。

  2. 引擎将 JIT 应用函数应用于每个窗口的循环。

engine_kwargs 参数是一个关键字参数字典,将传递给numba.jit 装饰器。这些关键字参数将应用于传递的函数(如果是标准 Python 函数)和对每个窗口的应用循环。

版本 1.3.0 中的新功能。

meanmedianmaxminsum 也支持 engineengine_kwargs 参数。

二进制窗口函数

cov()corr() 可以计算关于两个Series或任何DataFrame/SeriesDataFrame/DataFrame的移动窗口统计。在每种情况下的行为如下:

  • 两个Series:计算配对的统计信息。

  • DataFrame/Series:计算 DataFrame 的每一列与传递的 Series 的统计信息,从而返回一个 DataFrame。

  • DataFrame/DataFrame:默认情况下计算匹配列名的统计信息,返回一个 DataFrame。如果传递了关键字参数 pairwise=True,则为每对列计算统计信息,返回一个具有MultiIndexDataFrame,其值是相关日期(请参阅下一节)。

例如:

In [64]: df = pd.DataFrame(
 ....:    np.random.randn(10, 4),
 ....:    index=pd.date_range("2020-01-01", periods=10),
 ....:    columns=["A", "B", "C", "D"],
 ....: )
 ....: 

In [65]: df = df.cumsum()

In [66]: df2 = df[:4]

In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]: 
 A    B    C    D
2020-01-01  NaN  NaN  NaN  NaN
2020-01-02 -1.0  1.0 -1.0  1.0
2020-01-03  1.0  1.0  1.0 -1.0
2020-01-04 -1.0  1.0  1.0 -1.0 

计算滚动成对协方差和相关性

在金融数据分析和其他领域,通常会为一组时间序列计算协方差和相关矩阵。通常还会对移动窗口协方差和相关矩阵感兴趣。这可以通过传递pairwise关键字参数来实现,对于DataFrame输入,将产生一个多索引的DataFrame,其index是相关日期。对于单个 DataFrame 参数的情况,甚至可以省略pairwise参数:

注意

忽略缺失值,并使用成对完整观测值计算每个条目。

假设缺失数据是随机缺失的,这将导致对协方差矩阵的估计是无偏的。然而,对于许多应用程序来说,这种估计可能是不可接受的,因为估计的协方差矩阵不能保证是半正定的。这可能导致估计的相关性具有绝对值大于一的情况,和/或一个不可逆的协方差矩阵。更多详情请参见协方差矩阵的估计

In [68]: covs = (
 ....:    df[["B", "C", "D"]]
 ....:    .rolling(window=4)
 ....:    .cov(df[["A", "B", "C"]], pairwise=True)
 ....: )
 ....: 

In [69]: covs
Out[69]: 
 B         C         D
2020-01-01 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
 C       NaN       NaN       NaN
2020-01-02 A       NaN       NaN       NaN
 B       NaN       NaN       NaN
...                ...       ...       ...
2020-01-09 B  0.342006  0.230190  0.052849
 C  0.230190  1.575251  0.082901
2020-01-10 A -0.333945  0.006871 -0.655514
 B  0.649711  0.430860  0.469271
 C  0.430860  0.829721  0.055300

[30 rows x 3 columns] 

加权窗口

.rolling中的win_type参数生成了在滤波和频谱估计中常用的加权窗口。win_type必须是一个字符串,对应于scipy.signal 窗口函数。必须安装 Scipy 才能使用这些窗口,并且必须在聚合函数中指定 Scipy 窗口方法所需的补充参数。

In [70]: s = pd.Series(range(10))

In [71]: s.rolling(window=5).mean()
Out[71]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64 

对于所有支持的聚合函数,请参见加权窗口函数。

扩展窗口

扩展窗口提供了一个聚合统计量的值,其中包含截至该时间点的所有可用数据。由于这些计算是滚动统计的一个特例,因此在 pandas 中实现了以下两种调用是等效的:

In [74]: df = pd.DataFrame(range(5))

In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]: 
 0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

In [76]: df.expanding(min_periods=1).mean()
Out[76]: 
 0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0 

对于所有支持的聚合函数,请参见扩展窗口函数。

指数加权窗口

指数加权窗口类似于扩展窗口,但是每个先前点相对于当前点被指数加权降低。

一般来说,加权移动平均值计算如下

[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},]

其中(x_t)是输入,(y_t)是结果,(w_i)是权重。

对于所有支持的聚合函数,请参见指数加权窗口函数。

EW 函数支持指数权重的两个变体。默认情况下,adjust=True使用权重(w_i = (1 - \alpha)^i),得到

[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)² + ... + (1 - \alpha)^t}]

当指定adjust=False时,移动平均值计算如下

[\begin{split}y_0 &= x_0 \ y_t &= (1 - \alpha) y_{t-1} + \alpha x_t,\end{split}]

这等价于使用权重

[\begin{split}w_i = \begin{cases} \alpha (1 - \alpha)^i & \text{如果 } i < t \ (1 - \alpha)^i & \text{如果 } i = t. \end{cases}\end{split}]

注意

这些方程有时以(\alpha' = 1 - \alpha)的形式编写,例如

[y_t = \alpha' y_{t-1} + (1 - \alpha') x_t.]

以上两个变体之间的差异是因为我们处理的是具有有限历史的系列。考虑一个具有adjust=True的无限历史的系列:

[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...} {1 + (1 - \alpha) + (1 - \alpha)² + ...}]

注意分母是一个几何级数,初始项为 1,比率为(1 - \alpha),我们有

[\begin{split}y_t &= \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...} {\frac{1}{1 - (1 - \alpha)}}\ &= [x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...] \alpha \ &= \alpha x_t + [(1-\alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ...]\alpha \ &= \alpha x_t + (1 - \alpha)[x_{t-1} + (1 - \alpha) x_{t-2} + ...]\alpha\ &= \alpha x_t + (1 - \alpha) y_{t-1}\end{split}]

这与上面的adjust=False表达式相同,因此显示了无限级数的两个变体的等价性。当指定adjust=False时,我们有(y_0 = x_0)和(y_t = \alpha x_t + (1 - \alpha) y_{t-1})。因此,有一个假设,即(x_0)不是普通值,而是无限系列在那一点的指数加权瞬时值。

必须满足(0 < \alpha \leq 1),虽然可以直接传递(\alpha),但通常更容易考虑 EW 瞬时的跨度质心(com)半衰期

[\begin{split}\alpha = \begin{cases} \frac{2}{s + 1}, & \text{对于跨度}\ s \geq 1\ \frac{1}{1 + c}, & \text{对于质心}\ c \geq 0\ 1 - \exp^{\frac{\log 0.5}{h}}, & \text{对于半衰期}\ h > 0 \end{cases}\end{split}]

必须向 EW 函数精确指定跨度质心半衰期alpha中的一个:

  • 跨度对应于通常称为“N 天 EW 移动平均”的内容。

  • 质心具有更物理的解释,可以��虑为跨度:(c = (s - 1) / 2)。

  • 半衰期是指数权重减少到一半所需的时间段。

  • Alpha直接指定平滑因子。

您还可以指定halflife,以时间间隔可转换的单位来指定观察值衰减到一半所需的时间,同时指定一系列times

In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})

In [78]: df
Out[78]: 
 B
0  0.0
1  1.0
2  2.0
3  NaN
4  4.0

In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]

In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]: 
 B
0  0.000000
1  0.585786
2  1.523889
3  1.523889
4  3.233686 

以下公式用于计算具有时间输入向量的指数加权均值:

[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},]

ExponentialMovingWindow 还有一个ignore_na参数,用于确定中间空值如何影响权重的计算。当ignore_na=False(默认值)时,权重是基于绝对位置计算的,因此中间空值会影响结果。当ignore_na=True时,权重是通过忽略中间空值计算的。例如,假设adjust=True,如果ignore_na=False,则3, NaN, 5的加权平均值将被计算为

[\frac{(1-\alpha)² \cdot 3 + 1 \cdot 5}{(1-\alpha)² + 1}.]

ignore_na=True,则加权平均值将被计算为

[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.]

var()std()cov()函数有一个bias参数,指定结果是否应包含有偏或无偏统计数据。例如,如果bias=True,则ewmvar(x)被计算为ewmvar(x) = ewma(x**2) - ewma(x)**2;而如果bias=False(默认值),有偏方差统计数据将通过去偏因子进行缩放。

[\frac{\left(\sum_{i=0}^t w_i\right)²}{\left(\sum_{i=0}^t w_i\right)² - \sum_{i=0}^t w_i²}.]

(对于(w_i = 1),这将缩减为通常的(N / (N - 1))因子,其中(N = t + 1)。请参阅维基百科上的加权样本方差以获取更多详细信息。

时间序列/日期功能

原文:pandas.pydata.org/docs/user_guide/timeseries.html

pandas 包含了广泛的功能和特性,用于处理各个领域的时间序列数据。使用 NumPy 的datetime64timedelta64数据类型,pandas 已经整合了许多其他 Python��(如scikits.timeseries)的功能,并为操作时间序列数据创造了大量新功能。

例如,pandas 支持:

从各种来源和格式解析时间序列信息

In [1]: import datetime

In [2]: dti = pd.to_datetime(
 ...:    ["1/1/2018", np.datetime64("2018-01-01"), datetime.datetime(2018, 1, 1)]
 ...: )
 ...: 

In [3]: dti
Out[3]: DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None) 

生成固定频率日期和时间跨度的序列

In [4]: dti = pd.date_range("2018-01-01", periods=3, freq="h")

In [5]: dti
Out[5]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00',
 '2018-01-01 02:00:00'],
 dtype='datetime64[ns]', freq='h') 

操纵和转换带有时区信息的日期时间

In [6]: dti = dti.tz_localize("UTC")

In [7]: dti
Out[7]: 
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 01:00:00+00:00',
 '2018-01-01 02:00:00+00:00'],
 dtype='datetime64[ns, UTC]', freq='h')

In [8]: dti.tz_convert("US/Pacific")
Out[8]: 
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 17:00:00-08:00',
 '2017-12-31 18:00:00-08:00'],
 dtype='datetime64[ns, US/Pacific]', freq='h') 

对时间序列重新采样或转换为特定频率

In [9]: idx = pd.date_range("2018-01-01", periods=5, freq="h")

In [10]: ts = pd.Series(range(len(idx)), index=idx)

In [11]: ts
Out[11]: 
2018-01-01 00:00:00    0
2018-01-01 01:00:00    1
2018-01-01 02:00:00    2
2018-01-01 03:00:00    3
2018-01-01 04:00:00    4
Freq: h, dtype: int64

In [12]: ts.resample("2h").mean()
Out[12]: 
2018-01-01 00:00:00    0.5
2018-01-01 02:00:00    2.5
2018-01-01 04:00:00    4.0
Freq: 2h, dtype: float64 

使用绝对或相对时间增量进行日期和时间算术运算

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

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

# Add 1 day
In [15]: saturday = friday + pd.Timedelta("1 day")

In [16]: saturday.day_name()
Out[16]: 'Saturday'

# Add 1 business day (Friday --> Monday)
In [17]: monday = friday + pd.offsets.BDay()

In [18]: monday.day_name()
Out[18]: 'Monday' 

pandas 提供了一套相对紧凑和自包含的工具,用于执行上述任务以及更多任务。

概述

pandas 涵盖了 4 个与时间相关的概念:

  1. 日期时间:具有时区支持的特定日期和时间。类似于标准库中的datetime.datetime

  2. 时间增量:绝对时间持续。类似于标准库中的datetime.timedelta

  3. 时间跨度:由时间点和其关联频率定义的时间跨度。

  4. 日期偏移:尊重日历算术的相对时间持续。类似于dateutil包中的dateutil.relativedelta.relativedelta

概念 标量类 数组类 pandas 数据类型 主要创建方法
日期时间 Timestamp DatetimeIndex datetime64[ns]datetime64[ns, tz] to_datetimedate_range
时间增量 Timedelta TimedeltaIndex timedelta64[ns] to_timedeltatimedelta_range
时间跨度 Period PeriodIndex period[freq] Periodperiod_range
日期偏移 DateOffset None None DateOffset

对于时间序列数据,通常将时间组件表示为SeriesDataFrame的索引,以便可以根据时间元素执行操作。

In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]: 
2000-01-01    0
2000-01-02    1
2000-01-03    2
Freq: D, dtype: int64 

然而,SeriesDataFrame也可以直接支持时间组件作为数据本身。

In [20]: pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
dtype: datetime64[ns] 

当传递到这些构造函数时,SeriesDataFramedatetimetimedeltaPeriod数据方面具有扩展的数据类型支持和功能。但是,DateOffset数据将被存储为object数据。

In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]: 
0    2011-01
1    2011-02
2    2011-03
dtype: period[M]

In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]: 
0         <DateOffset>
1    <2 * DateOffsets>
dtype: object

In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]: 
0   2011-01-31
1   2011-02-28
2   2011-03-31
dtype: datetime64[ns] 

最后,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

# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False 
```  ## 时间戳 vs. 时间跨度

时间戳数据是将值与时间点关联的最基本类型的时间序列数据。对于 pandas 对象,这意味着使用时间点。

```py
In [28]: import datetime

In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')

In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')

In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00') 

然而,在许多情况下,将变量与时间跨度关联起来更为自然。Period表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出。

例如:

In [32]: pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')

In [33]: pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D') 

TimestampPeriod可以用作索引。TimestampPeriod的列表会自动强制转换为DatetimeIndexPeriodIndex

In [34]: dates = [
 ....:    pd.Timestamp("2012-05-01"),
 ....:    pd.Timestamp("2012-05-02"),
 ....:    pd.Timestamp("2012-05-03"),
 ....: ]
 ....: 

In [35]: ts = pd.Series(np.random.randn(3), dates)

In [36]: type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex

In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

In [38]: ts
Out[38]: 
2012-05-01    0.469112
2012-05-02   -0.282863
2012-05-03   -1.509059
dtype: float64

In [39]: periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]

In [40]: ts = pd.Series(np.random.randn(3), periods)

In [41]: type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex

In [42]: ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')

In [43]: ts
Out[43]: 
2012-01   -1.135632
2012-02    1.212112
2012-03   -0.173215
Freq: M, dtype: float64 

pandas 允许您捕获这两种表示形式并在它们之间进行转换。在内部,pandas 使用Timestamp的实例表示时间戳,使用DatetimeIndex的实例表示时间戳序列。对于常规时间跨度,pandas 使用Period对象表示标量值,使用PeriodIndex表示跨度序列。未来版本将更好地支持具有任意起始点和结束点的不规则间隔。 ## 转换为时间戳

要将Series或类似列表的日期样式对象(例如字符串、时代或混合物)转换为to_datetime函数。当传递一个Series时,这将返回一个Series(具有相同的索引),而类似列表将转换为DatetimeIndex

In [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]: 
0   2009-07-31
1   2010-01-10
2          NaT
dtype: datetime64[ns]

In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None) 

如果您使用以日期为首的日期(即欧洲风格),您可以传递dayfirst标志:

In [46]: pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)

In [47]: pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None) 

警告

您可以在上面的示例中看到dayfirst并不是严格的。如果日期不能以日期为首解析,它将被解析为如果dayfirstFalse,同时还会引发警告。

如果将单个字符串传递给to_datetime,它将返回一个单个TimestampTimestamp也可以接受字符串输入,但不接受像dayfirstformat这样的字符串解析选项,因此如果需要这些选项,请使用to_datetime

In [48]: pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')

In [49]: pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00') 

您也可以直接使用DatetimeIndex构造函数:

In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None) 

可以传递字符串‘infer’以将索引的频率设置为创建时推断的频率:

In [51]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[51]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D') 

提供一个格式参数

除了必需的日期时间字符串外,还可以传递一个format参数以确保特定的解析。这也可能显著加快转换速度。

In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')

In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00') 

有关在指定 format 选项时可用的选择的更多信息,请参阅 Python datetime 文档

从多个 DataFrame 列中组装日期时间

你也可以传递一个整数或字符串列的 DataFrame 来组装成 TimestampsSeries

In [54]: df = pd.DataFrame(
 ....:    {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
 ....: )
 ....: 

In [55]: pd.to_datetime(df)
Out[55]: 
0   2015-02-04 02:00:00
1   2016-03-05 03:00:00
dtype: datetime64[ns] 

你只需传递需要组装的列。

In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]: 
0   2015-02-04
1   2016-03-05
dtype: datetime64[ns] 

pd.to_datetime 会查找列名中日期时间组件的标准设计 ations,包括:

  • 必需的:yearmonthday

  • 可选的:hourminutesecondmillisecondmicrosecondnanosecond

无效数据

默认行为 errors='raise' 是在无法解析时引发异常:

In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
  1097         result = _convert_and_box_cache(argc, cache_array)
  1098     else:
-> 1099         result = convert_listlike(argc, format)
  1100 else:
  1101     result = convert_listlike(np.array([arg]), format)[0]

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:433, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
  431 # `format` could be inferred, or user didn't ask for mixed-format parsing.
  432 if format is not None and format != "mixed":
--> 433     return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
  435 result, tz_parsed = objects_to_datetime64(
  436     arg,
  437     dayfirst=dayfirst,
   (...)
  441     allow_object=True,
  442 )
  444 if tz_parsed is not None:
  445     # We can take a shortcut since the datetime64 numpy array
  446     # is in UTC

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:467, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
  456 def _array_strptime_with_fallback(
  457     arg,
  458     name,
   (...)
  462     errors: str,
  463 ) -> Index:
  464  """
  465 Call array_strptime, with fallback behavior depending on 'errors'.
  466 """
--> 467     result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
  468     if tz_out is not None:
  469         unit = np.datetime_data(result.dtype)[0]

File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1\. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this. 

传递 errors='coerce' 将无法解析的数据转换为 NaT(不是时间):

In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None) 

Epoch 时间戳

pandas 支持将整数或浮点 epoch 时间转换为 TimestampDatetimeIndex。默认单位是纳秒,因为这是 Timestamp 对象在内部存储的方式。然而,epoch 通常以另一个可以指定的 unit 存储。这些是从 origin 参数指定的起始点计算出来的。

In [59]: pd.to_datetime(
 ....:    [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
 ....: )
 ....: 
Out[59]: 
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)

In [60]: pd.to_datetime(
 ....:    [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
 ....:    unit="ms",
 ....: )
 ....: 
Out[60]: 
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 参数不使用与上面讨论的 format 参数相同的字符串。可用的单位在 pandas.to_datetime() 的文档中列出。

使用指定了 tz 参数的 epoch 时间戳构建 TimestampDatetimeIndex 会引发 ValueError。如果你有另一个时区中的墙上时间的 epoch,你可以将 epoch 读取为时区无关的时间戳,然后本地化到适当的时区:

In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')

In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None) 

注意

Epoch 时间将四舍五入到最接近的纳秒。

警告

浮点 epoch 时间的转换可能导致不准确和意外的结果。Python 浮点数 在十进制中有大约 15 位数字的精度。在从浮点数转换为高精度 Timestamp 时进行四舍五入是不可避免的。实现精确精度的唯一方法是使用固定宽度类型(例如 int64)。

In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)

In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912') 

另请参见

使用 origin 参数 ### 从时间戳到 epoch

要反转上述操作,即从 Timestamp 转换为 ‘unix’ epoch:

In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")

In [66]: stamps
Out[66]: 
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') 

我们减去 epoch(1970 年 1 月 1 日 UTC 的午夜)然后整除“unit”(1 秒)。

In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64') 
```  ### 使用 `origin` 参数

使用 `origin` 参数,可以指定创建 `DatetimeIndex` 的替代起始点。例如,要使用 1960-01-01 作为起始日期:

```py
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None) 

默认设置为 origin='unix',默认为 1970-01-01 00:00:00。通常称为 ‘unix epoch’ 或 POSIX 时间。

In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None) 
```  ## 生成时间戳范围

要生成带有时间戳的索引,您可以使用`DatetimeIndex`或`Index`构造函数,并传入一个日期时间对象列表:

```py
In [70]: dates = [
 ....:    datetime.datetime(2012, 5, 1),
 ....:    datetime.datetime(2012, 5, 2),
 ....:    datetime.datetime(2012, 5, 3),
 ....: ]
 ....: 

# Note the frequency information
In [71]: index = pd.DatetimeIndex(dates)

In [72]: index
Out[72]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

# Automatically converted to DatetimeIndex
In [73]: index = pd.Index(dates)

In [74]: index
Out[74]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None) 

在实践中,这变得非常繁琐,因为我们经常需要一个带有大量时间戳的非常长的索引。如果我们需要定期频率的时间戳,我们可以使用date_range()bdate_range()函数来创建一个DatetimeIndexdate_range的默认频率是日历日,而bdate_range的默认频率是工作日

In [75]: start = datetime.datetime(2011, 1, 1)

In [76]: end = datetime.datetime(2012, 1, 1)

In [77]: index = pd.date_range(start, end)

In [78]: index
Out[78]: 
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',
 ...
 '2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
 '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
 '2011-12-31', '2012-01-01'],
 dtype='datetime64[ns]', length=366, freq='D')

In [79]: index = pd.bdate_range(start, end)

In [80]: index
Out[80]: 
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-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]', length=260, freq='B') 

便利函数如date_rangebdate_range可以利用各种 frequency aliases:

In [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]: 
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='ME')

In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]: 
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') 

date_rangebdate_range使得使用各种参数组合(如startendperiodsfreq)轻松生成一系列日期。开始和结束日期是严格包含的,因此不会生成指定范围之外的日期:

In [83]: pd.date_range(start, end, freq="BME")
Out[83]: 
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='BME')

In [84]: pd.date_range(start, end, freq="W")
Out[84]: 
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 [85]: pd.bdate_range(end=end, periods=20)
Out[85]: 
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 [86]: pd.bdate_range(start=start, periods=20)
Out[86]: 
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') 

指定startendperiods将从startend生成一系列均匀间隔的日期,结果DatetimeIndex中有periods个元素:

In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[87]: 
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
 '2018-01-05'],
 dtype='datetime64[ns]', freq=None)

In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
 '2018-01-01 21:20:00', '2018-01-02 08:00:00',
 '2018-01-02 18:40:00', '2018-01-03 05:20:00',
 '2018-01-03 16:00:00', '2018-01-04 02:40:00',
 '2018-01-04 13:20:00', '2018-01-05 00:00:00'],
 dtype='datetime64[ns]', freq=None) 

自定义频率范围

bdate_range还可以通过使用weekmaskholidays参数生成一系列自定义频率日期。只有在传递自定义频率字符串时才会使用这些参数。

In [89]: weekmask = "Mon Wed Fri"

In [90]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]

In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]: 
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 [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]: 
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') 

另请参阅

自定义工作日 ## 时间戳限制

时间戳表示的限制取决于所选择的分辨率。对于纳秒分辨率,使用 64 位整数表示的时间跨度限制在大约 584 年左右:

In [93]: pd.Timestamp.min
Out[93]: Timestamp('1677-09-21 00:12:43.145224193')

In [94]: pd.Timestamp.max
Out[94]: Timestamp('2262-04-11 23:47:16.854775807') 

选择秒分辨率时,可用范围增加到+/- 2.9e11 年。不同分辨率可以通过as_unit相互转换。

另请参阅

表示超出范围的时间跨度 ## 索引

DatetimeIndex的主要用途之一是作为 pandas 对象的索引。DatetimeIndex类包含许多与时间序列相关的优化:

  • 预先计算并缓存了各种偏移的大量日期范围,以便在生成后续日期范围时非常快速(只需抓取一个片段)。

  • 在 pandas 对象上使用shift方法进行快速移位。

  • 具有相同频率的重叠DatetimeIndex对象的并集非常快速(对于快速数据对齐很重要)。

  • 通过属性(如yearmonth等)快速访问日期字段。

  • 使用shift方法在 pandas 对象上进行快速移位。

DatetimeIndex对象具有常规Index对象的所有基本功能,以及一系列用于简化频率处理的高级时间序列特定方法。

另请参阅

重新索引方法

注意

虽然 pandas 不强制您拥有排序的日期索引,但如果日期未排序,则其中一些方法可能会产生意外或不正确的行为。

DatetimeIndex可以像常规索引一样使用,并提供其所有智能功能,如选择、切片等。

In [95]: rng = pd.date_range(start, end, freq="BME")

In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [97]: ts.index
Out[97]: 
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='BME')

In [98]: ts[:5].index
Out[98]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
 '2011-05-31'],
 dtype='datetime64[ns]', freq='BME')

In [99]: ts[::2].index
Out[99]: 
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
 '2011-09-30', '2011-11-30'],
 dtype='datetime64[ns]', freq='2BME') 

部分字符串索引

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

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 

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

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])。

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] 

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

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] 

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

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] 

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

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] 

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

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

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 偏移量。

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对象。

In [123]: series_minute["2011-12-31 23"]
Out[123]: 
2011-12-31 23:59:00    1
dtype: int64 

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

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

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进行索引。

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[]中的选择将是按列而不是按行进行的,请参见 Indexing Basics。例如,dft_minute['2011-12-31 23:59']会引发KeyError,因为'2012-12-31 23:59'与索引具有相同的分辨率,并且没有这样的列名:

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

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的分辨率不能低于天。

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

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] 

没有默认值。

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 值,与切片返回任何部分匹配的日期不同:

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,尽管频率会丢失:

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) 
```  ## 时间/日期组件

有几个时间/日期属性可以从 `Timestamp` 或时间戳集合(如 `DatetimeIndex`)中访问。

| 属性 | 描述 |
| --- | --- |
| 年份 | 日期时间的年份 |
| 月份 | 日期时间的月份 |
| 天数 | 日期时间的天数 |
| 小时数 | 日期时间的小时数 |
| 分钟数 | 日期时间的分钟数 |
| 秒数 | 日期时间的秒数 |
| 微秒 | 日期时间的微秒 |
| 纳秒 | 日期时间的纳秒数 |
| 日期 | 返回日期时间.date(不包含时区信息) |
| 时间 | 返回日期时间.time(不包含时区信息) |
| timetz | 返回带有时区信息的本地时间日期.time |
| 年份中的日期 | 年份的序数日期 |
| 年份中的日期 | 年份的序数日期 |
| 年度周数 | 年份的周序数 |
| 周数 | 年份的周序数 |
| dayofweek | 一周中的日期编号,星期一=0,星期日=6 |
| day_of_week | 一周中的日期编号,星期一=0,星期日=6 |
| 工作日 | 一周中的日期编号,星期一=0,星期日=6 |
| 季度 | 日期的季度:1 月至 3 月=1,4 月至 6 月=2,等等 |
| 月份的天数 | 日期时间的月份的天数 |
| is_month_start | 逻辑指示是否月份的第一天(由频率定义) |
| is_month_end | 逻辑指示是否月份的最后一天(由频率定义) |
| is_quarter_start | 逻辑指示是否季度的第一天(由频率定义) |
| is_quarter_end | 逻辑指示是否季度的最后一天(由频率定义) |
| is_year_start | 逻辑指示是否年份的第一天(由频率定义) |
| is_year_end | 逻辑指示是否年份的最后一天(由频率定义) |
| 是否闰年 | 逻辑指示日期是否属于闰年 |

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

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

```py
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`中的日期时间是如何间隔的

+   一个`Period`或`PeriodIndex`的频率

这些频率字符串映射到一个`DateOffset`对象及其子类。`DateOffset`类似于表示时间持续的`Timedelta`,但遵循特定的日历持续规则。例如,`Timedelta`的一天总是增加`datetimes` 24 小时,而`DateOffset`的一天将增加`datetimes`到第二天的同一时间,无论一天代表的是 23、24 还是 25 小时,都由于夏令时而变化。然而,所有一个小时或更小的`DateOffset`子类(`Hour`、`Minute`、`Second`、`Milli`、`Micro`、`Nano`)的行为类似于`Timedelta`,并且遵守绝对时间。

基本的`DateOffset`类似于`dateutil.relativedelta`([relativedelta 文档](https://dateutil.readthedocs.io/en/stable/relativedelta.html)),它将日期时间按指定的日历持续时间进行偏移。可以使用算术运算符(`+`)执行偏移。

```py
# 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 None 通用偏移类,默认为绝对 24 小时
BDayBusinessDay 'B' 工作日(周日)
CDayCustomBusinessDay 'C' 自定义工作日
Week 'W' 一周,可选择以一周中的某一天为锚点
WeekOfMonth 'WOM' 每月第 y 周的第 x 天
LastWeekOfMonth 'LWOM' 每月最后一周的第 x 天
MonthEnd 'ME' 日历月结束
MonthBegin 'MS' 日历月开始
BMonthEndBusinessMonthEnd 'BME' 工作月结束
BMonthBeginBusinessMonthBegin 'BMS' 工作月开始
CBMonthEndCustomBusinessMonthEnd 'CBME' 自定义工作月结束
CBMonthBeginCustomBusinessMonthBegin 'CBMS' 自定义工作月开始
SemiMonthEnd 'SME' 每月 15 日(或其他日期)和日历月结束
SemiMonthBegin 'SMS' 每月 15 日(或其他日期)和日历月开始
QuarterEnd 'QE' 日历季度结束
QuarterBegin 'QS' 日历季度开始
BQuarterEnd 'BQE 商业季度结束
BQuarterBegin 'BQS' 商业季度开始
FY5253Quarter 'REQ' 零售(又称 52-53 周)季度
YearEnd 'YE' 日历年度结束
YearBegin 'YS''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()方法,用于将日期向前或向后移动到相对于偏移的有效偏移日期。例如,业务偏移将把落在周末(星期六和星期日)的日期向前滚动到星期一,因为业务偏移在工作日上运行。

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()(取决于您是否希望在操作中包含时间信息)。

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参数,这将导致生成的日期始终落在一周的特定某一天上:

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选项将对加法和减法有效。

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进行参数化:

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一起使用,以将偏移应用于每个元素。

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 部分。

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

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

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

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 

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

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

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

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超出营业时间,则移至下一个营业时间,然后递增。如果结果超出营业时间结束,则剩余的小时将添加到下一个营业日。

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

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开始来区分。

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 分钟)。

# 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相同,只是它跳过指定的自定义假期。

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支持的关键字参数。

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 起已弃用:别名 `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

结合别名

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

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

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

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步。

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|个点。

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的情况,如果日期在锚点上,则日期不会移动,否则将向前滚动到下一个锚点。

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 日),如果假期落在周末或其他非观察日,观察规则将决定何时观察该假期。定义的观察规则包括:

规则 描述
nearest_workday 将周六移至周五,周日移至周一
sunday_to_monday 将周日移至下周一
next_monday_or_tuesday 将周六移至周一,周日/周一移至周二
previous_friday 将周六和周日移至上周五
next_monday 将周六和周日移至下周一

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

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 对象中。

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 类属性定义。默认值如下所示。

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 来覆盖。

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 提供了一个简单的接口来创建组合日历或具有额外规则的日历。

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)>)] 
```  ## 与时间序列相关的实例方法

### 移动 / 拖延

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

```py
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 的对象,也可以是一个 偏移别名。

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

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

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提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。

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 文档。

基础知识

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

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’来指定区间的哪一端是闭合的:

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指定结果是用区间的开始还是结束标记的。

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频率:

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 

注意星期日的值如何被拉回到前一个星期五。要获得星期日的值被推到星期一的行为,请改用

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参数以插值填补创建的间隙:

# 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是基于时间的分组,以下是一种有效重新采样仅不全为NaN的组的方法。

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) 

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

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 

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

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、分组 API 和窗口 API,Resampler可以选择性地重新采样。

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

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 选择特定列或列。

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

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上,您可以传递一个函数列表以应用于每列,这将产生一个带有分层索引的聚合结果:

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:00  33.541257  0.186340  ...  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  ...  -5.004580 -0.050046

[6 rows x 6 columns] 

通过将字典传递给aggregate,您可以对DataFrame的列应用不同的聚合:

In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:

In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

此外,您还可以为每列单独指定多个聚合函数。

In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]: 
 A                   B 
 sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312 

如果一个DataFrame没有日期时间索引,而你想要根据帧中的日期时间列进行重新采样,可以传递给on关键字。

In [325]: df = pd.DataFrame(
 .....:    {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
 .....:    index=pd.MultiIndex.from_arrays(
 .....:        [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
 .....:        names=["v", "d"],
 .....:    ),
 .....: )
 .....: 

In [326]: df
Out[326]: 
 date  a
v d 
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]: 
 a
date 
2015-01-31  6
2015-02-28  4 

同样,如果您希望按照MultiIndex的日期时间级别重新采样,则可以将其名称或位置传递给level关键字。

In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]: 
 a
d 
2015-01-31  6
2015-02-28  4 
```  ### 通过组进行迭代

有了`Resampler`对象,通过分组数据进行迭代非常自然,并且类似于[`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"):

```py
In [329]: small = pd.Series(
 .....:    range(6),
 .....:    index=pd.to_datetime(
 .....:        [
 .....:            "2017-01-01T00:00:00",
 .....:            "2017-01-01T00:30:00",
 .....:            "2017-01-01T00:31:00",
 .....:            "2017-01-01T01:00:00",
 .....:            "2017-01-01T03:00:00",
 .....:            "2017-01-01T03:05:00",
 .....:        ]
 .....:    ),
 .....: )
 .....: 

In [330]: resampled = small.resample("h")

In [331]: for name, group in resampled:
 .....:    print("Group: ", name)
 .....:    print("-" * 27)
 .....:    print(group, end="\n\n")
 .....: 
Group:  2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00    0
2017-01-01 00:30:00    1
2017-01-01 00:31:00    2
dtype: int64

Group:  2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00    3
dtype: int64

Group:  2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00    4
2017-01-01 03:05:00    5
dtype: int64 

查看通过组进行迭代或Resampler.__iter__获取更多信息。### 使用originoffset来调整箱子的起始点

分组的箱子根据时间序列起始点的当天开始时间进行调整。这适用于是天数的倍数(如30D)或能够均匀分割一天的频率(如90s1min)。这可能会导致某些不符合此标准的频率出现不一致。要更改此行为,可以使用参数origin指定一个固定的 Timestamp。

例如:

In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

In [333]: middle = "2000-10-02 00:00:00"

In [334]: rng = pd.date_range(start, end, freq="7min")

In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

In [336]: ts
Out[336]: 
2000-10-01 23:30:00     0
2000-10-01 23:37:00     3
2000-10-01 23:44:00     6
2000-10-01 23:51:00     9
2000-10-01 23:58:00    12
2000-10-02 00:05:00    15
2000-10-02 00:12:00    18
2000-10-02 00:19:00    21
2000-10-02 00:26:00    24
Freq: 7min, dtype: int64 

在这里,我们可以看到,当使用origin的默认值('start_day')时,根据时间序列的起始点,'2000-10-02 00:00:00'之后的结果并不相同:

In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]: 
2000-10-01 23:14:00     0
2000-10-01 23:31:00     9
2000-10-01 23:48:00    21
2000-10-02 00:05:00    54
2000-10-02 00:22:00    24
Freq: 17min, dtype: int64

In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]: 
2000-10-02 00:00:00    33
2000-10-02 00:17:00    45
Freq: 17min, dtype: int64 

在这里,我们可以看到,将origin设置为'epoch'时,根据时间序列的起始点,'2000-10-02 00:00:00'之后的结果是相同的:

In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]: 
2000-10-01 23:18:00     0
2000-10-01 23:35:00    18
2000-10-01 23:52:00    27
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64

In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]: 
2000-10-01 23:52:00    15
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64 

如果需要,可以使用自定义时间戳作为origin

In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

如果需要,您可以使用offset Timedelta 调整箱子,该 Timedelta 将添加到默认的origin中。对于这个时间序列,这两个示例是等效的:

In [343]: ts.resample("17min", origin="start").sum()
Out[343]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

注意最后一个示例中对origin使用'start'而不是origin。在这种情况下,origin将被设置为时间序列的第一个值。

向后重新采样

版本 1.3.0 中的新功能。

有时,我们需要调整箱子的开始而不是结束,以便使用给定的freq进行向后重新采样。向后重新采样默认将closed设置为'right',因为最后一个值应被视为最后一个箱子的边缘点。

我们可以将origin设置为'end'。特定Timestamp索引的值表示从当前Timestamp减去freq到当前Timestamp的右闭区间的重新采样结果。

In [345]: ts.resample('17min', origin='end').sum()
Out[345]: 
2000-10-01 23:35:00     0
2000-10-01 23:52:00    18
2000-10-02 00:09:00    27
2000-10-02 00:26:00    63
Freq: 17min, dtype: int64 

此外,与'start_day'选项相反,支持end_day。这将把origin设置为最大Timestamp的午夜。

In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]: 
2000-10-01 23:38:00     3
2000-10-01 23:55:00    15
2000-10-02 00:12:00    45
2000-10-02 00:29:00    45
Freq: 17min, dtype: int64 

以上结果使用2000-10-02 00:29:00作为最后一个箱子的右边缘,因为以下计算。

In [347]: ceil_mid = rng.max().ceil('D')

In [348]: freq = pd.offsets.Minute(17)

In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00') 
```  ## 时间跨度表示

在 pandas 中,时间的常规间隔由`Period`对象表示,而`Period`对象的序列被收集在`PeriodIndex`中,可以使用便利函数`period_range`创建。

### 期间

`Period`表示一段时间(例如,一天,一个月,一个季度等)。您可以通过使用频率别名来指定`freq`关键字来指定跨度。因为`freq`表示`Period`的跨度,所以不能像“-3D”那样是负数。

```py
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')

In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')

In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')

In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h') 

从期间中添加和减去整数会按照其自身频率移动期间。不允许在具有不同freq(跨度)的Period之间进行算术运算。

In [355]: p = pd.Period("2012", freq="Y-DEC")

In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')

In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')

In [358]: p = pd.Period("2012-01", freq="2M")

In [359]: p + 2
Out[359]: Period('2012-05', '2M')

In [360]: p - 1
Out[360]: Period('2011-11', '2M')

In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False 

如果Period频率是每天或更高(Dhminsmsusns),则可以添加offsets和类似于timedelta的内容,如果结果具有相同的频率,则可以添加。否则,将引发ValueError

In [362]: p = pd.Period("2014-07-01 09:00", freq="h")

In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')

In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')

In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h') 
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()

ValueError: Cannot losslessly convert units

The above exception was the direct cause of the following exception:

IncompatibleFrequency  Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)

File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

IncompatibleFrequency: Input cannot be converted to Period(freq=h) 

如果Period具有其他频率,则只能添加相同的offsets。否则,将引发ValueError

In [367]: p = pd.Period("2014-07", freq="M")

In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M') 
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency  Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)

File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()

File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

IncompatibleFrequency: Input has different freq=3M from Period(freq=M) 

具有相同频率的Period实例之间的差异将返回它们之间的频率单位数:

In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12> 

PeriodIndex 和 period_range

Period对象的常规序列可以收集在PeriodIndex中,可以使用period_range便利函数构建:

In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")

In [372]: prng
Out[372]: 
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
 '2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
 '2012-01'],
 dtype='period[M]') 

PeriodIndex构造函数也可以直接使用:

In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

传递乘以的频率会输出一个具有乘以跨度的Period序列。

In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]') 

如果startendPeriod对象,则它们将用作与PeriodIndex构造函数的频率匹配的锚定端点。

In [375]: pd.period_range(
 .....:    start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
 .....: )
 .....: 
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]') 

就像DatetimeIndex一样,PeriodIndex也可以用于索引 pandas 对象:

In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)

In [377]: ps
Out[377]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64 

PeriodIndex支持与Period相同规则的加法和减法。

In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

In [379]: idx
Out[379]: 
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
 '2014-07-01 12:00', '2014-07-01 13:00'],
 dtype='period[h]')

In [380]: idx + pd.offsets.Hour(2)
Out[380]: 
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
 '2014-07-01 14:00', '2014-07-01 15:00'],
 dtype='period[h]')

In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")

In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')

In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]') 

PeriodIndex有自己的名为period的 dtype,请参考 Period Dtypes。

期间 dtype

PeriodIndex具有自定义的period dtype。这是类似于时区感知 dtype(datetime64[ns, tz])的 pandas 扩展 dtype。

period dtype 保存freq属性,并且用period[freq]表示,如period[D]period[M],使用频率字符串。

In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")

In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')

In [386]: pi.dtype
Out[386]: period[M] 

period dtype 可以在.astype(...)中使用。它允许更改PeriodIndexfreq,如.asfreq(),并将DatetimeIndex转换为PeriodIndex,如to_period()

# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')

# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)

In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')

In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

PeriodIndex 部分字符串索引

PeriodIndex 现在支持具有非单调索引的部分字符串切片。

您可以像DatetimeIndex一样向SeriesDataFrame传递日期和字符串,具有PeriodIndex,有关详细信息,请参考 DatetimeIndex 部分字符串索引。

In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507

In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]: 
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]: 
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64 

传递表示低于PeriodIndex的频率的字符串将返回部分切片数据。

In [395]: ps["2011"]
Out[395]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

In [396]: dfp = pd.DataFrame(
 .....:    np.random.randn(600, 1),
 .....:    columns=["A"],
 .....:    index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
 .....: )
 .....: 

In [397]: dfp
Out[397]: 
 A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
...                    ...
2013-01-01 18:55  0.522460
2013-01-01 18:56  0.118710
2013-01-01 18:57  0.167517
2013-01-01 18:58  0.922883
2013-01-01 18:59  1.721104

[600 rows x 1 columns]

In [398]: dfp.loc["2013-01-01 10h"]
Out[398]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59  0.802298

[60 rows x 1 columns] 

DatetimeIndex一样,结果将包括端点。下面的示例从 10:00 开始切片数据到 11:59。

In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56  1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58  0.578798
2013-01-01 11:59 -0.685496

[120 rows x 1 columns] 

使用 PeriodIndex 进行频率转换和重采样

PeriodPeriodIndex的频率可以通过asfreq方法进行转换。让我们从 2011 财政年度开始,截至 12 月:

In [400]: p = pd.Period("2011", freq="Y-DEC")

In [401]: p
Out[401]: Period('2011', 'Y-DEC') 

我们可以将其转换为月度频率。使用how参数,我们可以指定是返回起始月份还是结束月份:

In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')

In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M') 

提供了‘s’和‘e’的简写形式以方便使用:

In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')

In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M') 

转换为“超期”(例如,年度频率是季度频率的超期)将自动返回包含输入期间的超期:

In [406]: p = pd.Period("2011-12", freq="M")

In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV') 

请注意,由于我们转换为年度频率,年终在十一月,因此 2011 年 12 月的月度期间实际上在 2012 年 Y-NOV 期间。

具有锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年第一季度可能从 2010 年开始,或者在 2011 年的几个月内开始。通过锚定频率,pandas 适用于所有季度频率 Q-JANQ-DEC

Q-DEC 定义常规日历季度:

In [408]: p = pd.Period("2012Q1", freq="Q-DEC")

In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')

In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D') 

Q-MAR 定义了年度财政年度结束于三月:

In [411]: p = pd.Period("2011Q4", freq="Q-MAR")

In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')

In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D') 
```  ## 转换表示方式

时间戳数据可以使用 `to_period` 转换为 PeriodIndex 数据,反之亦然使用 `to_timestamp`:

```py
In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")

In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [416]: ts
Out[416]: 
2012-01-31    1.931253
2012-02-29   -0.184594
2012-03-31    0.249656
2012-04-30   -0.978151
2012-05-31   -0.873389
Freq: ME, dtype: float64

In [417]: ps = ts.to_period()

In [418]: ps
Out[418]: 
2012-01    1.931253
2012-02   -0.184594
2012-03    0.249656
2012-04   -0.978151
2012-05   -0.873389
Freq: M, dtype: float64

In [419]: ps.to_timestamp()
Out[419]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64 

请记住,‘s’ 和 ‘e’ 可以用于返回期间开始或结束的时间戳:

In [420]: ps.to_timestamp("D", how="s")
Out[420]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64 

在期间和时间戳之间进行转换可以使用一些方便的算术函数。在以下示例中,我们将将年终在十一月的季度频率转换为季度结束后一个月的月底上午 9 点:

In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")

In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)

In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9

In [424]: ts.head()
Out[424]: 
1990-03-01 09:00   -0.109291
1990-06-01 09:00   -0.637235
1990-09-01 09:00   -1.735925
1990-12-01 09:00    2.096946
1991-03-01 09:00   -1.039926
Freq: h, dtype: float64 
```  ## 表示超出范围的时间段

如果您的数据超出了 `Timestamp` 的范围,请参阅时间戳限制,然后您可以使用 `PeriodIndex` 和/或 `Periods` 的 `Series` 进行计算。

```py
In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")

In [426]: span
Out[426]: 
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
 '1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
 '1215-01-09', '1215-01-10',
 ...
 '1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
 '1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
 '1380-12-31', '1381-01-01'],
 dtype='period[D]', length=60632) 

要从基于 int64 的 YYYYMMDD 表示形式转换。

In [427]: s = pd.Series([20121231, 20141130, 99991231])

In [428]: s
Out[428]: 
0    20121231
1    20141130
2    99991231
dtype: int64

In [429]: def conv(x):
 .....:    return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
 .....: 

In [430]: s.apply(conv)
Out[430]: 
0    2012-12-31
1    2014-11-30
2    9999-12-31
dtype: period[D]

In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D') 

这些可以轻松转换为 PeriodIndex

In [432]: span = pd.PeriodIndex(s.apply(conv))

In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]') 
```  ## 时区处理

pandas 提供了丰富的支持,使用 `pytz` 和 `dateutil` 库或标准库中的 [`datetime.timezone`](https://docs.python.org/3/library/datetime.html#datetime.timezone "(在 Python v3.12 中)") 对象,可以处理不同时区的时间戳。

### 处理时区

默认情况下,pandas 对象不考虑时区:

```py
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")

In [435]: rng.tz is None
Out[435]: True 

要将这些日期本地化到时区(为一个无时区日期分配特定的时区),您可以使用 tz_localize 方法或 date_range() 中的 tz 关键字参数,TimestampDatetimeIndex。您可以传递 pytzdateutil 时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回 pytz 时区对象。要返回 dateutil 时区对象,请在字符串之前添加 dateutil/

  • pytz 中,您可以使用 from pytz import common_timezones, all_timezones 找到常见(以及不太常见)的时区列表。

  • dateutil 使用操作系统时区,因此没有固定的列表可用。对于常见时区,名称与 pytz 相同。

In [436]: import dateutil

# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")

In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')

# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=dateutil.tz.tzutc(),
 .....: )
 .....: 

In [443]: rng_utc.tz
Out[443]: tzutc() 
# datetime.timezone
In [444]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=datetime.timezone.utc,
 .....: )
 .....: 

In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc 

请注意,UTC时区在dateutil中是一个特殊情况,应该显式构造为dateutil.tz.tzutc的实例。您也可以首先显式构造其他时区对象。

In [446]: import pytz

# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")

In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)

In [450]: rng_pytz.tz == tz_pytz
Out[450]: True

# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")

In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)

In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True 

要将一个时区感知的 pandas 对象从一个时区转换到另一个时区,您可以使用tz_convert方法。

In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]: 
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
 '2012-03-07 19:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 

注意

当使用pytz时区时,DatetimeIndex将构造一个不同的时区对象,而对于相同的时区输入,Timestamp将构造一个不同的时区对象。一个DatetimeIndex可以保存一组具有不同 UTC 偏移的Timestamp对象,而不能简洁地由一个pytz时区实例表示,而一个Timestamp代表一个具有特定 UTC 偏移的时间点。

In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD> 

警告

要谨慎处理库之间的转换。对于一些时区,pytzdateutil对时区的定义不同。这对于不寻常的时区比‘标准’时区如US/Eastern更有问题。

警告

请注意,跨时间区库版本的时区定义可能不被视为相等。这可能会在使用一个版本本地化的存储数据并在不同版本上操作时出现问题。请参见这里如何处理这种情况。

警告

对于pytz时区,直接将一个时区对象传递给datetime.datetime构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern')))。相反,需要使用pytz时区对象上的localize方法对日期时间进行本地化。

警告

请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自的政府更改。

警告

如果您使用的日期超过 2038-01-18,由于底层库中当前存在的年 2038 问题导致的缺陷,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,DST 转换将会被应用。

例如,对于两个处于英国夏令时的日期(通常为 GMT+1),以下断言都为真:

In [459]: d_2037 = "2037-03-31T010101"

In [460]: d_2038 = "2038-03-31T010101"

In [461]: DST = "Europe/London"

In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT") 

在幕后,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndexTimestamp的值将被本地化到时区。然而,具有相同 UTC 值的时间戳即使在不同时区中仍被视为相等:

In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')

In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')

In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True 

不同时区中的Series之间的操作将产生 UTC Series,将数据对齐到 UTC 时间戳上:

In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [470]: eastern = ts_utc.tz_convert("US/Eastern")

In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [472]: result = eastern + berlin

In [473]: result
Out[473]: 
2013-01-01 00:00:00+00:00    0
2013-01-02 00:00:00+00:00    2
2013-01-03 00:00:00+00:00    4
Freq: D, dtype: int64

In [474]: result.index
Out[474]: 
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
 '2013-01-03 00:00:00+00:00'],
 dtype='datetime64[ns, UTC]', freq='D') 

要删除时区信息,请使用tz_localize(None)tz_convert(None)tz_localize(None)将删除时区,得到本地时间表示。tz_convert(None)将在转换为 UTC 时间后删除时区。

In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")

In [476]: didx
Out[476]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
 '2014-08-01 11:00:00-04:00'],
 dtype='datetime64[ns, US/Eastern]', freq='h')

In [477]: didx.tz_localize(None)
Out[477]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
 '2014-08-01 11:00:00'],
 dtype='datetime64[ns]', freq=None)

In [478]: didx.tz_convert(None)
Out[478]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq='h')

# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq=None) 

Fold

对于模糊时间,pandas 支持显式指定仅关键字 fold 参数。由于夏令时,当从夏季时间转换到冬季时间时,一个挂钟时间可能发生两次;fold 描述 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime(有关详细信息,请参阅datetime 文档)或从Timestamp构造或从组件构造(见下文)。仅支持dateutil时区(请参阅dateutil 文档以了解处理模糊日期时间的dateutil方法),因为pytz时区不支持 fold(请参阅pytz 文档以了解pytz如何处理模糊日期时间的详细信息)。要使用pytz本地化模糊日期时间,请使用Timestamp.tz_localize()。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议在本地化模糊日期时间时依赖于Timestamp.tz_localize()

In [480]: pd.Timestamp(
 .....:    datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
 .....:    tz="dateutil/Europe/London",
 .....:    fold=0,
 .....: )
 .....: 
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [481]: pd.Timestamp(
 .....:    year=2019,
 .....:    month=10,
 .....:    day=27,
 .....:    hour=1,
 .....:    minute=30,
 .....:    tz="dateutil/Europe/London",
 .....:    fold=1,
 .....: )
 .....: 
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London') 
```  ### 本地化时的模糊时间

`tz_localize`可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时导致某些时间在一天内发生两次(“时钟回拨”)。以下选项可用:

+   `'raise'`:引发`pytz.AmbiguousTimeError`(默认行为)

+   `'infer'`:尝试根据时间戳的单调性确定正确的偏移量

+   `'NaT'`:用`NaT`替换模糊时间

+   `bool`:`True`表示 DST 时间,`False`表示非 DST 时间。支持用于时间序列的`bool`值的类似数组。

```py
In [482]: rng_hourly = pd.DatetimeIndex(
 .....:    ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
 .....: )
 .....: 

这将失败,因为存在模糊的时间('11/06/2011 01:00'

In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError  Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument 

通过指定以下内容来处理这些模糊的时间。

In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
 '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 
```  ### 本地化不存在的时间

DST 转换也可能会将当地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前调整”)。可以通过`nonexistent`参数控制具有不存在时间的时间序列的本地化行为。可用的选项如下:

+   `'raise'`:引发`pytz.NonExistentTimeError`(默认行为)

+   `'NaT'`:用`NaT`替换不存在的时间

+   `'shift_forward'`:将不存在的时间向前移动到最近的真实时间

+   `'shift_backward'`:将不存在的时间向后移动到最近的真实时间

+   timedelta 对象:通过 timedelta 持续时间移动不存在的时间

```py
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 is a nonexistent time 

本地化不存在的时间将默认引发错误。

In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError  Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

NonExistentTimeError: 2015-03-29 02:30:00 

将不存在的时间转换为NaT或移动时间。

In [489]: dti
Out[489]: 
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
 '2015-03-29 04:30:00'],
 dtype='datetime64[ns]', freq='h')

In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]: 
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]: 
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
 '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]: 
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]: 
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None) 
```  ### 时区系列操作

具有**naive**值的`Series`以`datetime64[ns]`的 dtype 表示。

```py
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))

In [495]: s_naive
Out[495]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[ns] 

具有aware值的Seriesdatetime64[ns, tz]的 dtype 表示,其中tz是时区

In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))

In [497]: s_aware
Out[497]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

这两个Series的时区信息可以通过.dt访问器进行操作,参见 dt 访问器部分。

例如,将 naive 时间戳本地化和转换为时区感知。

In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

时间区域信息也可以使用astype方法进行操作。该方法可以在不同的时区感知 dtype 之间进行转换。

# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]: 
0   2013-01-01 06:00:00+01:00
1   2013-01-02 06:00:00+01:00
2   2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET] 

注意

Series上使用Series.to_numpy(),返回数据的 NumPy 数组。 NumPy 当前不支持时区(即使在本地时区打印!),因此对于时区感知数据,将返回时间戳的对象数组:

In [500]: s_naive.to_numpy()
Out[500]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
 '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

In [501]: s_aware.to_numpy()
Out[501]: 
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
 dtype=object) 

通过转换为时间戳的对象数组,它保留了时区信息。例如,当转换回 Series 时:

In [502]: pd.Series(s_aware.to_numpy())
Out[502]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

但是,如果您想要一个实际的 NumPydatetime64[ns]数组(其值已转换为 UTC),而不是对象数组,您可以指定dtype参数:

In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
 '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]') 
```  ## 概述

pandas 捕获了 4 个通用的与时间相关的概念:

1.  日期时间:具有时区支持的特定日期和时间。类似于标准库中的`datetime.datetime`。  

1.  时间增量:绝对时间持续时间。类似于标准库中的`datetime.timedelta`。

1.  时间跨度:由时间点及其关联频率定义的时间跨度。

1.  日期偏移量:一种尊重日历算术的相对时间持续。类似于`dateutil`包中的`dateutil.relativedelta.relativedelta`。

| 概念 | 标量类 | 数组类 | pandas 数据类型 | 主要创建方法 |
| --- | --- | --- | --- | --- |
| 日期时间 | `Timestamp` | `DatetimeIndex` | `datetime64[ns]`或`datetime64[ns, tz]` | `to_datetime`或`date_range` |
| 时间增量 | `Timedelta` | `TimedeltaIndex` | `timedelta64[ns]` | `to_timedelta`或`timedelta_range` |
| 时间跨度 | `Period` | `PeriodIndex` | `period[freq]` | `Period`或`period_range` |
| 日期偏移量 | `DateOffset` | `None` | `None` | `DateOffset` |

对于时间序列数据,习惯上将时间分量表示为`Series`或`DataFrame`的索引,以便可以针对时间元素进行操作。

```py
In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]: 
2000-01-01    0
2000-01-02    1
2000-01-03    2
Freq: D, dtype: int64 

然而,SeriesDataFrame也可以直接支持时间组件作为数据本身。

In [20]: pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
dtype: datetime64[ns] 

当传递到这些构造函数时,SeriesDataFrame支持datetimetimedeltaPeriod数据的扩展数据类型支持和功能。但是,DateOffset数据将以object数据存储。

In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]: 
0    2011-01
1    2011-02
2    2011-03
dtype: period[M]

In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]: 
0         <DateOffset>
1    <2 * DateOffsets>
dtype: object

In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]: 
0   2011-01-31
1   2011-02-28
2   2011-03-31
dtype: datetime64[ns] 

最后,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

# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False 

时间戳与时间跨度

时间戳数据是与时间点关联值的最基本类型的时间序列数据。对于 pandas 对象,这意味着使用时间点。

In [28]: import datetime

In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')

In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')

In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00') 

然而,在许多情况下,将变量的变化与时间跨度关联起来更自然。由Period表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出来。

例如:

In [32]: pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')

In [33]: pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D') 

TimestampPeriod可以用作索引。TimestampPeriod的列表将自动强制转换为DatetimeIndexPeriodIndex

In [34]: dates = [
 ....:    pd.Timestamp("2012-05-01"),
 ....:    pd.Timestamp("2012-05-02"),
 ....:    pd.Timestamp("2012-05-03"),
 ....: ]
 ....: 

In [35]: ts = pd.Series(np.random.randn(3), dates)

In [36]: type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex

In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

In [38]: ts
Out[38]: 
2012-05-01    0.469112
2012-05-02   -0.282863
2012-05-03   -1.509059
dtype: float64

In [39]: periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]

In [40]: ts = pd.Series(np.random.randn(3), periods)

In [41]: type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex

In [42]: ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')

In [43]: ts
Out[43]: 
2012-01   -1.135632
2012-02    1.212112
2012-03   -0.173215
Freq: M, dtype: float64 

pandas 允许您捕获两种表示形式并在它们之间进行转换。在底层,pandas 使用Timestamp的实例表示时间戳,并使用DatetimeIndex的实例表示时间戳序列。对于常规时间跨度,pandas 使用Period对象表示标量值,并使用PeriodIndex表示跨度序列。未来版本将更好地支持具有任意开始和结束点的不规则间隔。

转换为时间戳

要将Series或类似列表的日期对象(例如字符串、时间戳或混合对象)转换为日期时间对象,您可以使用to_datetime函数。当传递一个Series时,它会返回一个相同索引的Series,而列表则会被转换为DatetimeIndex

In [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]: 
0   2009-07-31
1   2010-01-10
2          NaT
dtype: datetime64[ns]

In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None) 

如果使用以日期开头的日期(即欧洲风格),您可以传递dayfirst标志:

In [46]: pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)

In [47]: pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None) 

警告

如上例所示,dayfirst不是严格的。如果日期无法解析为以天为首的日期,它将被解析为dayfirstFalse,同时还会引发警告。

如果将单个字符串传递给to_datetime,它将返回单个TimestampTimestamp也可以接受字符串输入,但它不接受像dayfirstformat这样的字符串解析选项,因此如果需要这些选项,请使用to_datetime

In [48]: pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')

In [49]: pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00') 

您也可以直接使用DatetimeIndex构造函数:

In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None) 

可以传递字符串“infer”以设置索引的频率为创建时的推断频率:

In [51]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[51]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D') 

提供格式参数

除了必需的日期时间字符串之外,还可以传递一个format参数以确保特定的解析。这也可能显著加快转换速度。

In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')

In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00') 

有关在指定format选项时可用的选项的更多信息,请参阅 Python datetime 文档

从多个 DataFrame 列组装日期时间

您还可以传递一个整数或字符串列的DataFrame以组装为TimestampsSeries

In [54]: df = pd.DataFrame(
 ....:    {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
 ....: )
 ....: 

In [55]: pd.to_datetime(df)
Out[55]: 
0   2015-02-04 02:00:00
1   2016-03-05 03:00:00
dtype: datetime64[ns] 

您可以只传递您需要组装的列。

In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]: 
0   2015-02-04
1   2016-03-05
dtype: datetime64[ns] 

pd.to_datetime会查找列名中 datetime 组件的标准标识,包括:

  • 必需的:yearmonthday

  • 可选的:hourminutesecondmillisecondmicrosecondnanosecond

无效的数据

默认行为errors='raise'是在无法解析时引发异常:

In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
  1097         result = _convert_and_box_cache(argc, cache_array)
  1098     else:
-> 1099         result = convert_listlike(argc, format)
  1100 else:
  1101     result = convert_listlike(np.array([arg]), format)[0]

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:433, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
  431 # `format` could be inferred, or user didn't ask for mixed-format parsing.
  432 if format is not None and format != "mixed":
--> 433     return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
  435 result, tz_parsed = objects_to_datetime64(
  436     arg,
  437     dayfirst=dayfirst,
   (...)
  441     allow_object=True,
  442 )
  444 if tz_parsed is not None:
  445     # We can take a shortcut since the datetime64 numpy array
  446     # is in UTC

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:467, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
  456 def _array_strptime_with_fallback(
  457     arg,
  458     name,
   (...)
  462     errors: str,
  463 ) -> Index:
  464  """
  465 Call array_strptime, with fallback behavior depending on 'errors'.
  466 """
--> 467     result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
  468     if tz_out is not None:
  469         unit = np.datetime_data(result.dtype)[0]

File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1\. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this. 

传递errors='coerce'以将无法解析的数据转换为NaT(不是时间):

In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None) 

纪元时间戳

pandas 支持将整数或浮点数纪元时间转换为TimestampDatetimeIndex。默认单位是纳秒,因为Timestamp对象在内部存储时是以纳秒为单位的。然而,纪元时间通常以另一个单位存储,可以指定。这些是从origin参数指定的起始点计算出来的。

In [59]: pd.to_datetime(
 ....:    [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
 ....: )
 ....: 
Out[59]: 
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)

In [60]: pd.to_datetime(
 ....:    [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
 ....:    unit="ms",
 ....: )
 ....: 
Out[60]: 
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参数不使用与上述讨论的format参数相同的字符串)。 可用单位在pandas.to_datetime()的文档中列出。

使用tz参数指定了 epoch 时间戳的TimestampDatetimeIndex构造会引发 ValueError。如果你有另一个时区中的墙上时间的 epoch,你可以将 epoch 读取为时区不敏感的时间戳,然后本地化到适当的时区:

In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')

In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None) 

注意

Epoch 时间将四舍五入到最近的纳秒。

警告

将 float 型 epoch 时间转换可能导致不准确和意外的结果。Python floats在十进制中有约 15 位数字精度。在从浮点数到高精度Timestamp的转换过程中进行舍入是不可避免的。实现精确精度的唯一方法是使用固定宽度的类型(例如 int64)。

In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)

In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912') 

另请参阅

使用 origin 参数 ### 从时间戳到 epoch

要反转上述操作,即从Timestamp转换为‘unix’ epoch:

In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")

In [66]: stamps
Out[66]: 
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') 

我们减去纪元(1970 年 1 月 1 日 UTC 午夜),然后进行“单位”(1 秒)的地板除法。

In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64') 
```  ### 使用`origin`参数

使用`origin`参数,可以指定一个替代创建`DatetimeIndex`的起始点。例如,要使用 1960-01-01 作为起始日期:

```py
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None) 

默认设置为origin='unix',默认为1970-01-01 00:00:00。通常称为“unix 纪元”或 POSIX 时间。

In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None) 
```  ### 提供 format 参数

除了必需的 datetime 字符串之外,还可以传递一个`format`参数以确保特定的解析。这也可能显著加快转换速度。

```py
In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')

In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00') 

有关在指定format选项时可用选择的更多信息,请参阅 Python datetime 文档

从多个 DataFrame 列中组装 datetime

你还可以传递一个整数或字符串列的DataFrame以组装成TimestampsSeries

In [54]: df = pd.DataFrame(
 ....:    {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
 ....: )
 ....: 

In [55]: pd.to_datetime(df)
Out[55]: 
0   2015-02-04 02:00:00
1   2016-03-05 03:00:00
dtype: datetime64[ns] 

你只需要传递你需要组装的列。

In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]: 
0   2015-02-04
1   2016-03-05
dtype: datetime64[ns] 

pd.to_datetime查找列名中 datetime 组件的标准设计,包括:

  • 必需:yearmonthday

  • 可选:hourminutesecondmillisecondmicrosecondnanosecond

无效数据

默认行为,errors='raise',是在不可解析时引发异常:

In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
  1097         result = _convert_and_box_cache(argc, cache_array)
  1098     else:
-> 1099         result = convert_listlike(argc, format)
  1100 else:
  1101     result = convert_listlike(np.array([arg]), format)[0]

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:433, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
  431 # `format` could be inferred, or user didn't ask for mixed-format parsing.
  432 if format is not None and format != "mixed":
--> 433     return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
  435 result, tz_parsed = objects_to_datetime64(
  436     arg,
  437     dayfirst=dayfirst,
   (...)
  441     allow_object=True,
  442 )
  444 if tz_parsed is not None:
  445     # We can take a shortcut since the datetime64 numpy array
  446     # is in UTC

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:467, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
  456 def _array_strptime_with_fallback(
  457     arg,
  458     name,
   (...)
  462     errors: str,
  463 ) -> Index:
  464  """
  465 Call array_strptime, with fallback behavior depending on 'errors'.
  466 """
--> 467     result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
  468     if tz_out is not None:
  469         unit = np.datetime_data(result.dtype)[0]

File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1\. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this. 

传递errors='coerce'以将不可解析的数据转换为NaT(不是时间):

In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None) 

Epoch 时间戳

pandas 支持将整数或浮点时代转换为TimestampDatetimeIndex。默认单位为纳秒,因为这是Timestamp对象在内部存储的方式。但是,时代通常以另一个可以指定的unit存储。这些是从由origin参数指定的起始点计算得出的。

In [59]: pd.to_datetime(
 ....:    [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
 ....: )
 ....: 
Out[59]: 
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)

In [60]: pd.to_datetime(
 ....:    [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
 ....:    unit="ms",
 ....: )
 ....: 
Out[60]: 
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参数不使用与上面讨论的format参数相同的字符串。可以在pandas.to_datetime() 的文档中找到可用的单位。

使用指定了tz参数的时代时间戳构造TimestampDatetimeIndex 将引发 ValueError。如果您在另一个时区的壁钟时间中有时代,您可以将时代读取为时区不可知的时间戳,然后本地化到适当的时区:

In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')

In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None) 

注意

时代时间将四舍五入到最接近的纳秒。

警告

浮点时代转换可能导致不准确和意外的结果。 Python 浮点数 在十进制中具有约 15 位数字精度。在从浮点数转换为高精度Timestamp时进行四舍五入是不可避免的。实现精确精度的唯一方法是使用固定宽度的类型(例如 int64)。

In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)

In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912') 

另请参阅

使用起始参数

从时间戳到时代

要反转上述操作,即从Timestamp转换为‘unix’时代:

In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")

In [66]: stamps
Out[66]: 
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') 

我们减去时代(1970 年 1 月 1 日 UTC 的午夜),然后除以“unit”(1 秒)。

In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64') 

使用 origin 参数

使用origin参数,可以为创建DatetimeIndex指定替代起始点。例如,要使用 1960-01-01 作为起始日期:

In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None) 

默认设置为origin='unix',默认为1970-01-01 00:00:00。通常称为“unix 时代”或 POSIX 时间。

In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None) 

生成时间戳范围

要生成带有时间戳的索引,您可以使用DatetimeIndexIndex构造函数,并传递一个日期时间对象列表:

In [70]: dates = [
 ....:    datetime.datetime(2012, 5, 1),
 ....:    datetime.datetime(2012, 5, 2),
 ....:    datetime.datetime(2012, 5, 3),
 ....: ]
 ....: 

# Note the frequency information
In [71]: index = pd.DatetimeIndex(dates)

In [72]: index
Out[72]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

# Automatically converted to DatetimeIndex
In [73]: index = pd.Index(dates)

In [74]: index
Out[74]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None) 

在实践中,这变得非常繁琐,因为我们经常需要一个非常长的索引,其中包含大量的时间戳。如果我们需要按照固定频率生成时间戳,我们可以使用date_range()bdate_range() 函数来创建DatetimeIndexdate_range的默认频率是日历日,而bdate_range的默认频率是工作日

In [75]: start = datetime.datetime(2011, 1, 1)

In [76]: end = datetime.datetime(2012, 1, 1)

In [77]: index = pd.date_range(start, end)

In [78]: index
Out[78]: 
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',
 ...
 '2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
 '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
 '2011-12-31', '2012-01-01'],
 dtype='datetime64[ns]', length=366, freq='D')

In [79]: index = pd.bdate_range(start, end)

In [80]: index
Out[80]: 
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-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]', length=260, freq='B') 

诸如 date_rangebdate_range 这样的便利函数可以利用各种频率别名:

In [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]: 
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='ME')

In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]: 
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') 

date_rangebdate_range 可以轻松生成一系列日期范围,使用各种参数组合如 startendperiodsfreq。开始和结束日期是严格包含的,因此不会生成指定范围之外的日期:

In [83]: pd.date_range(start, end, freq="BME")
Out[83]: 
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='BME')

In [84]: pd.date_range(start, end, freq="W")
Out[84]: 
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 [85]: pd.bdate_range(end=end, periods=20)
Out[85]: 
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 [86]: pd.bdate_range(start=start, periods=20)
Out[86]: 
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') 

指定 startendperiods 将生成一系列从 startend 的均匀间隔日期,结果为 DatetimeIndex 中的 periods 个元素:

In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[87]: 
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
 '2018-01-05'],
 dtype='datetime64[ns]', freq=None)

In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
 '2018-01-01 21:20:00', '2018-01-02 08:00:00',
 '2018-01-02 18:40:00', '2018-01-03 05:20:00',
 '2018-01-03 16:00:00', '2018-01-04 02:40:00',
 '2018-01-04 13:20:00', '2018-01-05 00:00:00'],
 dtype='datetime64[ns]', freq=None) 

自定义频率范围

bdate_range 还可以通过使用 weekmaskholidays 参数生成一系列自定义频率日期。只有在传递自定义频率字符串时才会使用这些参数。

In [89]: weekmask = "Mon Wed Fri"

In [90]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]

In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]: 
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 [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]: 
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') 

另请参阅

自定义工作日 ### 自定义频率范围

bdate_range 还可以通过使用 weekmaskholidays 参数生成一系列自定义频率日期。只有在传递自定义频率字符串时才会使用这些参数。

In [89]: weekmask = "Mon Wed Fri"

In [90]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]

In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]: 
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 [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]: 
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') 

另请参阅

自定义工作日

时间戳限制

时间戳表示的限制取决于所选择的分辨率。对于纳秒分辨率,使用 64 位整数表示的时间跨度限制在大约 584 年:

In [93]: pd.Timestamp.min
Out[93]: Timestamp('1677-09-21 00:12:43.145224193')

In [94]: pd.Timestamp.max
Out[94]: Timestamp('2262-04-11 23:47:16.854775807') 

选择秒分辨率时,可用范围增长到 +/- 2.9e11 年。不同分辨率���以通过 as_unit 相互转换。

另请参阅

表示超出范围的跨度

索引

DatetimeIndex 的主要用途之一是作为 pandas 对象的索引。DatetimeIndex 类包含许多与时间序列相关的优化:

  • 大量各种偏移量的日期范围在内部预先计算并缓存,以便快速生成后续日期范围(只需抓取一个片段)。

  • 在 pandas 对象上使用 shift 方法进行快速移位。

  • 具有相同频率的重叠 DatetimeIndex 对象的并集非常快速(对于快速数据对齐很重要)。

  • 通过属性(如 yearmonth 等)快速访问日期字段。

  • snap 等正规化函数和非常快速的 asof 逻辑。

DatetimeIndex 对象具有常规 Index 对象的所有基本功能,以及一系列用于简化频率处理的高级时间序列特定方法。

另请参阅

重新索引方法

注意

虽然 pandas 不强制要求您具有排序的日期索引,但如果日期未排序,则其中一些方法可能会出现意外或不正确的行为。

DatetimeIndex 可以像常规索引一样使用,并提供所有智能功能,如选择、切片等。

In [95]: rng = pd.date_range(start, end, freq="BME")

In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [97]: ts.index
Out[97]: 
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='BME')

In [98]: ts[:5].index
Out[98]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
 '2011-05-31'],
 dtype='datetime64[ns]', freq='BME')

In [99]: ts[::2].index
Out[99]: 
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
 '2011-09-30', '2011-11-30'],
 dtype='datetime64[ns]', freq='2BME') 

部分字符串索引

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

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 

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

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])仍受支持。

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] 

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

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] 

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

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] 

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

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] 

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

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

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 偏移。

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对象。

In [123]: series_minute["2011-12-31 23"]
Out[123]: 
2011-12-31 23:59:00    1
dtype: int64 

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

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

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

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

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的分辨率不能比天更精确。

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

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] 

没有默认值。

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,而切片则返回任何部分匹配的日期:

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,尽管频率会丢失:

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) 

部分字符串索引

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

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 

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

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])仍然受支持。

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] 

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

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] 

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

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] 

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

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] 

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

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

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 偏移量。

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 对象:

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 对象。

In [123]: series_minute["2011-12-31 23"]
Out[123]: 
2011-12-31 23:59:00    1
dtype: int64 

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

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

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

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

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的分辨率不能比日更不精确。

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

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] 

没有默认值。

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 值,与切片不同,后者返回任何部分匹配的日期:

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,尽管频率丢失了:

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 年份的年、周和日组件:

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.relativedeltarelativedelta 文档)会按照指定的日历持续时间移动日期时间。可以使用算术运算符(+)执行移位。

# 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 小时
BDayBusinessDay '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()方法,用于将日期向前或向后移动到相对于偏移量的有效日期。例如,商业偏移将周末(星期六和星期日)落在的日期向前推到星期一,因为商业偏移是在工作日上操作的。

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()(取决于您是否希望时间信息包含在操作中)。

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参数,这个参数使生成的日期总是在一周的特定某一天:

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选项将对添加和减去操作有效。

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参数化为特定的结束月份:

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一起使用,以将偏移量应用于每个元素。

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 部分。

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警告。

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

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

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 

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

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

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

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超出营业时间,移动到下一个营业时间然后递增。如果结果超出营业时间结束,剩余的小时将添加到下一个营业日。

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

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开始来区分。

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 分钟)。

# 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相同,只是跳过指定的自定义假期。

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支持的关键字参数。

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

组合别名

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

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

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

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个额外步骤。

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|个点。

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的情况,如果日期在锚点上,则不移动,否则向前滚动到下一个锚点。

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 将周六和周日移至下一个周一

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

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对象中。

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类属性定义。默认值如下所示。

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 来覆盖这些日期。

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提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。

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参数,这将导致生成的日期始终落在一周的特定某一天:

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选项将对加法和减法产生影响。

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进行参数化:

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一起使用,以将偏移应用于每个元素。

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 部分了解更多示例。

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

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 类,可用于创建考虑到本地节假日和本地周末惯例的定制工作日日历。

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

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

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

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 

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

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

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

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 超出营业时间,则移到下一个营业时间然后递增。如果结果超出营业时间结束,则剩余时间添加到下一个营业日。

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

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 开始来区分。

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 分钟)。

# 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相同,只是它跳过指定的自定义假期。

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支持的关键字参数。

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不是某个月的第一天,则最后返回的时间戳将是对应月份的第一天。

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

合并别名

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

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

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

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个额外步骤。

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|个点。

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时,如果日期在锚点上,则不移动,否则向前滚动到下一个锚点。

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 将周六和周日移动到下一个周一

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

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对象中。

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类属性定义。默认值如下所示。

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 来覆盖。

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提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。

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 对象。

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方法会更改索引中的所有日期,而不是更改数据和索引的对齐:

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

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提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。

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 对象。

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方法会更改索引中的所有日期,而不是更改数据和索引的对齐:

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

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 提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。

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 文档。

基础知识

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

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’以指定间隔的哪一端是封闭的:

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 指定结果是用间隔的开始还是结束标记。

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 频率:

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 

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

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参数以插值填补创建的间隙:

# 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的组的方法。

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) 

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

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 

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

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进行重新采样,默认情况下将对所有列执行相同的函数。

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 方法,我们可以选择特定的列或多列。

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

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上,您可以传递一个函数列表以应用于每列���从而产生具有分层索引的聚合结果:

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:00  33.541257  0.186340  ...  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  ...  -5.004580 -0.050046

[6 rows x 6 columns] 

通过将字典传递给aggregate,您可以对DataFrame的列应用不同的聚合:

In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:

In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

此外,您还可以为每列单独指定多个聚合函数。

In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]: 
 A                   B 
 sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312 

如果一个DataFrame没有 datetimelike 索引,而是您想要基于帧中的 datetimelike 列进行重新采样,可以传递给on关键字。

In [325]: df = pd.DataFrame(
 .....:    {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
 .....:    index=pd.MultiIndex.from_arrays(
 .....:        [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
 .....:        names=["v", "d"],
 .....:    ),
 .....: )
 .....: 

In [326]: df
Out[326]: 
 date  a
v d 
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]: 
 a
date 
2015-01-31  6
2015-02-28  4 

同样,如果您希望按照MultiIndex的 datetimelike 级别重新采样,可以将其名称或位置传递给level关键字。

In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]: 
 a
d 
2015-01-31  6
2015-02-28  4 
```  ### 遍历组

有了`Resampler`对象,通过分组数据进行迭代非常自然,并且类似于[`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"):

```py
In [329]: small = pd.Series(
 .....:    range(6),
 .....:    index=pd.to_datetime(
 .....:        [
 .....:            "2017-01-01T00:00:00",
 .....:            "2017-01-01T00:30:00",
 .....:            "2017-01-01T00:31:00",
 .....:            "2017-01-01T01:00:00",
 .....:            "2017-01-01T03:00:00",
 .....:            "2017-01-01T03:05:00",
 .....:        ]
 .....:    ),
 .....: )
 .....: 

In [330]: resampled = small.resample("h")

In [331]: for name, group in resampled:
 .....:    print("Group: ", name)
 .....:    print("-" * 27)
 .....:    print(group, end="\n\n")
 .....: 
Group:  2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00    0
2017-01-01 00:30:00    1
2017-01-01 00:31:00    2
dtype: int64

Group:  2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00    3
dtype: int64

Group:  2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00    4
2017-01-01 03:05:00    5
dtype: int64 

查看 遍历分组 或 Resampler.__iter__ 了解更多。### 使用 originoffset 调整箱的开始

分组的箱根据时间序列起始点的日期开始进行调整。这在频率是天的倍数(如 30D)或者能够均匀分割一天的频率(如 90s1min)时效果很好。这可能会导致一些不符合此标准的频率出现不一致。要更改此行为,您可以使用参数 origin 指定一个固定的时间戳。

例如:

In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

In [333]: middle = "2000-10-02 00:00:00"

In [334]: rng = pd.date_range(start, end, freq="7min")

In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

In [336]: ts
Out[336]: 
2000-10-01 23:30:00     0
2000-10-01 23:37:00     3
2000-10-01 23:44:00     6
2000-10-01 23:51:00     9
2000-10-01 23:58:00    12
2000-10-02 00:05:00    15
2000-10-02 00:12:00    18
2000-10-02 00:19:00    21
2000-10-02 00:26:00    24
Freq: 7min, dtype: int64 

在使用默认值 ('start_day') 的 origin 时,我们可以看到,根据时间序列的开始,'2000-10-02 00:00:00' 之后的结果是不相同的:

In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]: 
2000-10-01 23:14:00     0
2000-10-01 23:31:00     9
2000-10-01 23:48:00    21
2000-10-02 00:05:00    54
2000-10-02 00:22:00    24
Freq: 17min, dtype: int64

In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]: 
2000-10-02 00:00:00    33
2000-10-02 00:17:00    45
Freq: 17min, dtype: int64 

在将 origin 设置为 'epoch' 时,我们可以看到,根据时间序列的开始,'2000-10-02 00:00:00' 之后的结果是相同的:

In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]: 
2000-10-01 23:18:00     0
2000-10-01 23:35:00    18
2000-10-01 23:52:00    27
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64

In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]: 
2000-10-01 23:52:00    15
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64 

如果需要,您可以使用自定义时间戳来设置 origin

In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

如果需要,您可以通过添加到默认 originoffset 时间增量来调整箱。对于这个时间序列,这两个示例是等效的:

In [343]: ts.resample("17min", origin="start").sum()
Out[343]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

请注意在最后一个示例中使用 'start' 作为 origin。在这种情况下,origin 将设置为时间序列的第一个值。

向后重采样

版本 1.3.0 中的新功能。

与调整箱的开始不同,有时我们需要固定箱的结束,以便使用给定的 freq 进行向后重采样。向后重采样默认将 closed 设置为 'right',因为最后一个值应被视为最后一个箱的边界点。

我们可以将 origin 设置为 'end'。特定 Timestamp 索引的值表示从当前 Timestamp 减去 freq 到当前 Timestamp 的右闭区间的重采样结果。

In [345]: ts.resample('17min', origin='end').sum()
Out[345]: 
2000-10-01 23:35:00     0
2000-10-01 23:52:00    18
2000-10-02 00:09:00    27
2000-10-02 00:26:00    63
Freq: 17min, dtype: int64 

此外,与 'start_day' 选项相反,支持 end_day。这将将原点设置为最大 Timestamp 的午夜。

In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]: 
2000-10-01 23:38:00     3
2000-10-01 23:55:00    15
2000-10-02 00:12:00    45
2000-10-02 00:29:00    45
Freq: 17min, dtype: int64 

由于以下计算,上述结果使用 2000-10-02 00:29:00 作为最后一个箱的右边缘。

In [347]: ceil_mid = rng.max().ceil('D')

In [348]: freq = pd.offsets.Minute(17)

In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00') 

基础知识

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

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’ 来指定区间的哪一端是闭合的:

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 指定结果是用区间的开始还是结束标记的。

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’ 的 labelclosed 是相同的,除了 ‘ME’、‘YE’、‘QE’、‘BME’、‘BYE’、‘BQE’ 和 ‘W’,它们的默认值都是 ‘right’。

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

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 

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

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参数,以插值填补创建的间隙:

# 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是基于时间的分组操作,以下是一种有效地仅重新采样不全为NaN的组的方法。

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) 

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

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 

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

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、分组 API 和窗口 API,Resampler可以选择性地重新采样。

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

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 选择特定列或列。

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

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上,您可以传递要应用于每列的函数列表,这将产生具有分层索引的聚合结果:

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:00  33.541257  0.186340  ...  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  ...  -5.004580 -0.050046

[6 rows x 6 columns] 

通过将字典传递给aggregate,您可以对DataFrame的列应用不同的聚合方式:

In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:

In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]: 
 A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312 

此外,您还可以为每列单独指定多个聚合函数。

In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]: 
 A                   B 
 sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312 

如果DataFrame没有日期时间索引,而是您想要基于帧中的日期时间列进行重新采样,则可以将其传递给on关键字。

In [325]: df = pd.DataFrame(
 .....:    {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
 .....:    index=pd.MultiIndex.from_arrays(
 .....:        [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
 .....:        names=["v", "d"],
 .....:    ),
 .....: )
 .....: 

In [326]: df
Out[326]: 
 date  a
v d 
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]: 
 a
date 
2015-01-31  6
2015-02-28  4 

类似地,如果您希望按照MultiIndex的 datetimelike 级别重新采样,可以将其名称或位置传递给level关键字。

In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]: 
 a
d 
2015-01-31  6
2015-02-28  4 

遍历分组

拥有Resampler对象后,通过分组数据进行迭代非常自然,并且类似于itertools.groupby()的功能:

In [329]: small = pd.Series(
 .....:    range(6),
 .....:    index=pd.to_datetime(
 .....:        [
 .....:            "2017-01-01T00:00:00",
 .....:            "2017-01-01T00:30:00",
 .....:            "2017-01-01T00:31:00",
 .....:            "2017-01-01T01:00:00",
 .....:            "2017-01-01T03:00:00",
 .....:            "2017-01-01T03:05:00",
 .....:        ]
 .....:    ),
 .....: )
 .....: 

In [330]: resampled = small.resample("h")

In [331]: for name, group in resampled:
 .....:    print("Group: ", name)
 .....:    print("-" * 27)
 .....:    print(group, end="\n\n")
 .....: 
Group:  2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00    0
2017-01-01 00:30:00    1
2017-01-01 00:31:00    2
dtype: int64

Group:  2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00    3
dtype: int64

Group:  2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00    4
2017-01-01 03:05:00    5
dtype: int64 

查看遍历分组或Resampler.__iter__获取更多信息。

使用originoffset调整 bins 的开始

分组的 bins 根据时间序列起始点的当天开始进行调整。这适用于是天数的倍数(如30D)或者能够均匀分割一天的频率(如90s1min)。这可能会导致一些不符合这些标准的频率出现不一致。要更改此行为,可以使用参数origin指定一个固定的 Timestamp。

例如:

In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

In [333]: middle = "2000-10-02 00:00:00"

In [334]: rng = pd.date_range(start, end, freq="7min")

In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

In [336]: ts
Out[336]: 
2000-10-01 23:30:00     0
2000-10-01 23:37:00     3
2000-10-01 23:44:00     6
2000-10-01 23:51:00     9
2000-10-01 23:58:00    12
2000-10-02 00:05:00    15
2000-10-02 00:12:00    18
2000-10-02 00:19:00    21
2000-10-02 00:26:00    24
Freq: 7min, dtype: int64 

在这里我们可以看到,当使用具有默认值'start_day'origin时,'2000-10-02 00:00:00'之后的结果取决于时间序列的开始:

In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]: 
2000-10-01 23:14:00     0
2000-10-01 23:31:00     9
2000-10-01 23:48:00    21
2000-10-02 00:05:00    54
2000-10-02 00:22:00    24
Freq: 17min, dtype: int64

In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]: 
2000-10-02 00:00:00    33
2000-10-02 00:17:00    45
Freq: 17min, dtype: int64 

在这里我们可以看到,当将origin设置为'epoch'时,'2000-10-02 00:00:00'之后的结果取决于时间序列的开始:

In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]: 
2000-10-01 23:18:00     0
2000-10-01 23:35:00    18
2000-10-01 23:52:00    27
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64

In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]: 
2000-10-01 23:52:00    15
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64 

如果需要,可以使用自定义时间戳作为origin

In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

如果需要,您可以通过添加到默认originoffset Timedelta 来调整 bins。对于这个时间序列,这两个示例是等效的:

In [343]: ts.resample("17min", origin="start").sum()
Out[343]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64 

注意在最后一个示例中将origin设置为'start'的用法。在这种情况下,origin将被设置为时间序列的第一个值。

向后重新采样

版本 1.3.0 中新增。

有时,我们需要固定 bins 的结束而不是调整 bins 的开始,以便使用给定的freq进行向后重新采样。向后重新采样默认将closed设置为'right',因为最后一个值应被视为最后一个 bin 的边界点。

我们可以将origin设置为'end'。特定Timestamp索引的值代表从当前Timestamp减去freq到当前Timestamp的右闭区间的重新采样结果。

In [345]: ts.resample('17min', origin='end').sum()
Out[345]: 
2000-10-01 23:35:00     0
2000-10-01 23:52:00    18
2000-10-02 00:09:00    27
2000-10-02 00:26:00    63
Freq: 17min, dtype: int64 

此外,与'start_day'选项相比,支持end_day。这将将原点设置为最大Timestamp的午夜。

In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]: 
2000-10-01 23:38:00     3
2000-10-01 23:55:00    15
2000-10-02 00:12:00    45
2000-10-02 00:29:00    45
Freq: 17min, dtype: int64 

以上结果使用2000-10-02 00:29:00作为最后一个 bin 的右边缘,因为以下计算。

In [347]: ceil_mid = rng.max().ceil('D')

In [348]: freq = pd.offsets.Minute(17)

In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00') 

时间跨度表示

在 pandas 中,Period对象表示正规时间间隔,而Period对象的序列则被收集在PeriodIndex中,可以使用便利函数period_range创建。

时期

Period表示一段时间(例如,一天,一个月,一个季度等)。您可以通过使用频率别名来指定freq关键字来指定跨度。因为freq表示Period的跨度,所以不能像“-3D”那样是负数。

In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')

In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')

In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')

In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h') 

从周期中添加和减去整数会按照其自身频率移动周期。不允许在具有不同freq(跨度)的Period之间进行算术运算。

In [355]: p = pd.Period("2012", freq="Y-DEC")

In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')

In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')

In [358]: p = pd.Period("2012-01", freq="2M")

In [359]: p + 2
Out[359]: Period('2012-05', '2M')

In [360]: p - 1
Out[360]: Period('2011-11', '2M')

In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False 

如果Period频率为每天或更高(Dhminsmsusns),则如果结果可以具有相同的频率,则可以添加offsets和类似于timedelta的内容。否则,将引发ValueError

In [362]: p = pd.Period("2014-07-01 09:00", freq="h")

In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')

In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')

In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h') 
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()

ValueError: Cannot losslessly convert units

The above exception was the direct cause of the following exception:

IncompatibleFrequency  Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)

File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

IncompatibleFrequency: Input cannot be converted to Period(freq=h) 

如果Period具有其他频率,只能添加相同的offsets。否则,将引发ValueError

In [367]: p = pd.Period("2014-07", freq="M")

In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M') 
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency  Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)

File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()

File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

IncompatibleFrequency: Input has different freq=3M from Period(freq=M) 

具有相同频率的Period实例之间的差异将返回它们之间的频率单位数:

In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12> 

PeriodIndex 和 period_range

Period对象的常规序列可以收集在PeriodIndex中,可以使用period_range便捷函数构建:

In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")

In [372]: prng
Out[372]: 
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
 '2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
 '2012-01'],
 dtype='period[M]') 

PeriodIndex构造函数也可以直接使用:

In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

传递乘以频率的输出将返回一个具有乘以跨度的Period序列。

In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]') 

如果startendPeriod对象,则它们将用作与PeriodIndex构造函数频率匹配的锚定端点。

In [375]: pd.period_range(
 .....:    start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
 .....: )
 .....: 
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]') 

就像DatetimeIndex一样,PeriodIndex也可以用于索引 pandas 对象:

In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)

In [377]: ps
Out[377]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64 

PeriodIndex支持与Period相同规则的加法和减法。

In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

In [379]: idx
Out[379]: 
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
 '2014-07-01 12:00', '2014-07-01 13:00'],
 dtype='period[h]')

In [380]: idx + pd.offsets.Hour(2)
Out[380]: 
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
 '2014-07-01 14:00', '2014-07-01 15:00'],
 dtype='period[h]')

In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")

In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')

In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]') 

PeriodIndex有自己的名为period的 dtype,请参考 Period Dtypes。

Period dtypes

PeriodIndex具有自定义的period dtype。这是类似于时区感知 dtype(datetime64[ns, tz])的 pandas 扩展 dtype。

period dtype 保存freq属性,并且用period[freq]表示,如period[D]period[M],使用频率字符串。

In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")

In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')

In [386]: pi.dtype
Out[386]: period[M] 

period dtype 可以在.astype(...)中使用。它允许您像.asfreq()一样更改PeriodIndexfreq,并将DatetimeIndex转换为PeriodIndex,如to_period()

# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')

# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)

In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')

In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

PeriodIndex 部分字符串索引

PeriodIndex 现在支持具有非单调索引的部分字符串切片。

您可以像DatetimeIndex一样向SeriesDataFrame传递日期和字符串,使用PeriodIndex,有关详细信息,请参考 DatetimeIndex 部分字符串索引。

In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507

In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]: 
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]: 
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64 

传递表示低于PeriodIndex频率的字符串将返回部分切片数据。

In [395]: ps["2011"]
Out[395]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

In [396]: dfp = pd.DataFrame(
 .....:    np.random.randn(600, 1),
 .....:    columns=["A"],
 .....:    index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
 .....: )
 .....: 

In [397]: dfp
Out[397]: 
 A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
...                    ...
2013-01-01 18:55  0.522460
2013-01-01 18:56  0.118710
2013-01-01 18:57  0.167517
2013-01-01 18:58  0.922883
2013-01-01 18:59  1.721104

[600 rows x 1 columns]

In [398]: dfp.loc["2013-01-01 10h"]
Out[398]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59  0.802298

[60 rows x 1 columns] 

DatetimeIndex一样,结果中将包括端点。下面的示例从 10:00 开始切片数据到 11:59。

In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56  1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58  0.578798
2013-01-01 11:59 -0.685496

[120 rows x 1 columns] 

使用 PeriodIndex 进行频率转换和重采样

可以通过asfreq方法将PeriodPeriodIndex的频率转换。让我们从 2011 财政年度开始,截至 12 月:

In [400]: p = pd.Period("2011", freq="Y-DEC")

In [401]: p
Out[401]: Period('2011', 'Y-DEC') 

我们可以将其转换为月度频率。使用how参数,我们可以指定返回起始还是结束月份:

In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')

In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M') 

提供了‘s’和‘e’的简写形式以方便使用:

In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')

In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M') 

转换为“超级周期”(例如,年度频率是季度频率的超级周期)将自动返回包含输入周期的超级周期:

In [406]: p = pd.Period("2011-12", freq="M")

In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV') 

请注意,由于我们转换为以十一月结束的年度频率,因此 2011 年 12 月的月份实际上属于 2012 年的 Y-NOV 期间。

使用锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年的第一季度可能在 2010 年开始,也可能在 2011 年的几个月内开始。通过锚定频率,pandas 可以处理所有季度频率Q-JANQ-DEC

Q-DEC定义了常规的日历季度:

In [408]: p = pd.Period("2012Q1", freq="Q-DEC")

In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')

In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D') 

Q-MAR定义了财政年度截止于三月:

In [411]: p = pd.Period("2011Q4", freq="Q-MAR")

In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')

In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D') 

Period

Period表示一段时间(例如一天、一个月、一个季度等)。您可以通过使用类似下面的频率别名的freq关键字来指定这段时间。因为freq表示Period的时间跨度,所以它不能像“-3D”那样是负数。

In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')

In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')

In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')

In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h') 

从期间中加减整数会按照其自身频率移动期间。不允许在具有不同freq(时间跨度)的Period之间进行算术运算。

In [355]: p = pd.Period("2012", freq="Y-DEC")

In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')

In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')

In [358]: p = pd.Period("2012-01", freq="2M")

In [359]: p + 2
Out[359]: Period('2012-05', '2M')

In [360]: p - 1
Out[360]: Period('2011-11', '2M')

In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False 

如果Period的频率是每天或更高(Dhminsmsusns),则可以添加offsets和类似于timedelta的内容,如果结果具有相同的频率,则可以添加。否则,将引发ValueError

In [362]: p = pd.Period("2014-07-01 09:00", freq="h")

In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')

In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')

In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h') 
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()

ValueError: Cannot losslessly convert units

The above exception was the direct cause of the following exception:

IncompatibleFrequency  Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)

File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

IncompatibleFrequency: Input cannot be converted to Period(freq=h) 

如果Period具有其他频率,则只能添加相同的offsets。否则,将引发ValueError

In [367]: p = pd.Period("2014-07", freq="M")

In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M') 
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency  Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)

File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()

File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

IncompatibleFrequency: Input has different freq=3M from Period(freq=M) 

使用相同频率的Period实例之间的差异将返回它们之间的频率单位数:

In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12> 

PeriodIndex 和 period_range

Period对象的常规序列可以收集在PeriodIndex中,可以使用period_range便利函数构建:

In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")

In [372]: prng
Out[372]: 
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
 '2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
 '2012-01'],
 dtype='period[M]') 

PeriodIndex构造函数也可以直接使用:

In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

传递乘以的频率会输出一个具有乘以时间跨度的Period序列。

In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]') 

如果startendPeriod对象,则它们将被用作与PeriodIndex构造函数的频率匹配的锚定端点。

In [375]: pd.period_range(
 .....:    start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
 .....: )
 .....: 
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]') 

就像DatetimeIndex一样,PeriodIndex也可以用于索引 pandas 对象:

In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)

In [377]: ps
Out[377]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64 

PeriodIndex支持与Period相同的加法和减法规则。

In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

In [379]: idx
Out[379]: 
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
 '2014-07-01 12:00', '2014-07-01 13:00'],
 dtype='period[h]')

In [380]: idx + pd.offsets.Hour(2)
Out[380]: 
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
 '2014-07-01 14:00', '2014-07-01 15:00'],
 dtype='period[h]')

In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")

In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')

In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]') 

PeriodIndex有自己的名为period的数据类型,请参考 Period 数据类型。

Period 数据类型

PeriodIndex具有自定义的period数据类型。这是一种类似于时区感知数据类型(datetime64[ns, tz])的 pandas 扩展数据类型。

period数据类型保存了freq属性,并用period[freq]表示,例如period[D]period[M],使用频率字符串。

In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")

In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')

In [386]: pi.dtype
Out[386]: period[M] 

period数据类型可以在.astype(...)中使用。它允许您像.asfreq()那样更改PeriodIndexfreq,并像to_period()那样将DatetimeIndex转换为PeriodIndex

# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')

# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)

In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')

In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]') 

PeriodIndex 部分字符串索引

PeriodIndex 现在支持对非单调索引进行部分字符串切片。

您可以像DatetimeIndex一样向SeriesDataFrame传递日期和字符串,使用PeriodIndex。有关详细信息,请参考 DatetimeIndex 部分字符串索引。

In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507

In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]: 
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]: 
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64 

传递表示低于PeriodIndex的频率的字符串将返回部分切片数据。

In [395]: ps["2011"]
Out[395]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

In [396]: dfp = pd.DataFrame(
 .....:    np.random.randn(600, 1),
 .....:    columns=["A"],
 .....:    index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
 .....: )
 .....: 

In [397]: dfp
Out[397]: 
 A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
...                    ...
2013-01-01 18:55  0.522460
2013-01-01 18:56  0.118710
2013-01-01 18:57  0.167517
2013-01-01 18:58  0.922883
2013-01-01 18:59  1.721104

[600 rows x 1 columns]

In [398]: dfp.loc["2013-01-01 10h"]
Out[398]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59  0.802298

[60 rows x 1 columns] 

DatetimeIndex一样,结果将包括端点。下面的示例从 10:00 开始切片数据到 11:59。

In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]: 
 A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56  1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58  0.578798
2013-01-01 11:59 -0.685496

[120 rows x 1 columns] 

使用PeriodIndex进行频率转换和重采样

PeriodPeriodIndex的频率可以通过asfreq方法进行转换。让我们从 2011 财政年度开始,截至 12 月:

In [400]: p = pd.Period("2011", freq="Y-DEC")

In [401]: p
Out[401]: Period('2011', 'Y-DEC') 

我们可以将其转换为月度频率。使用how参数,我们可以指定返回起始月份还是结束月份:

In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')

In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M') 

提供了‘s’和‘e’的简写以方便使用:

In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')

In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M') 

转换为“超期”(例如,年度频率是季度频率的超期)将自动返回包含输入期间的超期:

In [406]: p = pd.Period("2011-12", freq="M")

In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV') 

请注意,由于我们转换为以 11 月结束的年度频率,因此 2011 年 12 月的月度期间实际上在 2012 年 Y-NOV 期间。

具有锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年第一季度可能从 2010 年开始,或者在 2011 年的几个月内开始。通过锚定频率,pandas 适用于所有季度频率Q-JANQ-DEC

Q-DEC定义了常规日历季度:

In [408]: p = pd.Period("2012Q1", freq="Q-DEC")

In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')

In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D') 

Q-MAR定义了以 3 月结束的财政年度:

In [411]: p = pd.Period("2011Q4", freq="Q-MAR")

In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')

In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D') 

在不同表示之间转换

时间戳数据可以使用to_period转换为 PeriodIndex 数据,反之亦然使用to_timestamp

In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")

In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [416]: ts
Out[416]: 
2012-01-31    1.931253
2012-02-29   -0.184594
2012-03-31    0.249656
2012-04-30   -0.978151
2012-05-31   -0.873389
Freq: ME, dtype: float64

In [417]: ps = ts.to_period()

In [418]: ps
Out[418]: 
2012-01    1.931253
2012-02   -0.184594
2012-03    0.249656
2012-04   -0.978151
2012-05   -0.873389
Freq: M, dtype: float64

In [419]: ps.to_timestamp()
Out[419]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64 

请记住,‘s’和‘e’可用于返回期间的开始或结束时间戳:

In [420]: ps.to_timestamp("D", how="s")
Out[420]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64 

在周期和时间戳之间进行转换可以使用一些方便的算术函数。在下面的示例中,我们将以 11 月结束的年度季度频率转换为季度结束后月底的上午 9 点:

In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")

In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)

In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9

In [424]: ts.head()
Out[424]: 
1990-03-01 09:00   -0.109291
1990-06-01 09:00   -0.637235
1990-09-01 09:00   -1.735925
1990-12-01 09:00    2.096946
1991-03-01 09:00   -1.039926
Freq: h, dtype: float64 

表示超出范围的跨度

如果您的数据超出了Timestamp的范围,请参阅 Timestamp 限制,那么您可以使用PeriodIndex和/或PeriodsSeries进行计算。

In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")

In [426]: span
Out[426]: 
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
 '1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
 '1215-01-09', '1215-01-10',
 ...
 '1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
 '1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
 '1380-12-31', '1381-01-01'],
 dtype='period[D]', length=60632) 

转换为基于int64的 YYYYMMDD 表示。

In [427]: s = pd.Series([20121231, 20141130, 99991231])

In [428]: s
Out[428]: 
0    20121231
1    20141130
2    99991231
dtype: int64

In [429]: def conv(x):
 .....:    return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
 .....: 

In [430]: s.apply(conv)
Out[430]: 
0    2012-12-31
1    2014-11-30
2    9999-12-31
dtype: period[D]

In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D') 

这些可以轻松转换为PeriodIndex

In [432]: span = pd.PeriodIndex(s.apply(conv))

In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]') 

时区处理

pandas 提供了丰富的支持,可以使用pytzdateutil库或标准库中的datetime.timezone对象来处理不同时区的时间戳。

处理时区

默认情况下,pandas 对象不考虑时区:

In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")

In [435]: rng.tz is None
Out[435]: True 

要将这些日期本地化到特定时区(为一个无时区日期分配特定时区),您可以使用 tz_localize 方法或 date_range()TimestampDatetimeIndex 中的 tz 关键字参数。您可以传递 pytzdateutil 时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回 pytz 时区对象。要返回 dateutil 时区对象,请在字符串前附加 dateutil/

  • pytz 中,您可以使用 from pytz import common_timezones, all_timezones 找到常见(和不太常见)时区列表。

  • dateutil 使用操作系统的时区,因此没有固定的可用列表。对于常见时区,名称与 pytz 相同。

In [436]: import dateutil

# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")

In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')

# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=dateutil.tz.tzutc(),
 .....: )
 .....: 

In [443]: rng_utc.tz
Out[443]: tzutc() 
# datetime.timezone
In [444]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=datetime.timezone.utc,
 .....: )
 .....: 

In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc 

请注意,UTC 时区在 dateutil 中是一个特殊情况,应明确构造为 dateutil.tz.tzutc 的实例。您也可以首先明确构造其他时区对象。

In [446]: import pytz

# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")

In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)

In [450]: rng_pytz.tz == tz_pytz
Out[450]: True

# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")

In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)

In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True 

要将一个时区感知的 pandas 对象从一个时区转换为另一个时区,您可以使用 tz_convert 方法。

In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]: 
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
 '2012-03-07 19:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 

注意

当使用 pytz 时区时,DatetimeIndex 会为相同的时区输入构造一个不同的时区对象,而 Timestamp 则不同。DatetimeIndex 可以保存可能具有不同 UTC 偏移的一组 Timestamp 对象,并且不能用一个 pytz 时区实例简洁地表示,而一个 Timestamp 代表具有特定 UTC 偏移的一个时间点。

In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD> 

警告

要注意在库之间进行转换。对于一些时区,pytzdateutil 对时区的定义不同。这对于不寻常的时区比‘标准’时区(如 US/Eastern)更成问题。

警告

请注意,跨版本的时区库可能不被视为相等。这可能会在使用一个版本本地化的存储数据与使用不同版本操作时出现问题。请参阅此处了解如何处理这种情况。

警告

对于 pytz 时区,直接将时区对象传递给 datetime.datetime 构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern')))。相反,需要使用 pytz 时区对象上的 localize 方法对日期时间进行本地化。

警告

请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自政府更改。

警告

如果您使用的日期超过 2038-01-18,由于底层库中当前存在的由 2038 年问题引起的缺陷,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,夏令时转换将会被应用。

例如,对于处于英国夏令时的两个日期(因此通常为 GMT+1),以下断言都为真:

In [459]: d_2037 = "2037-03-31T010101"

In [460]: d_2038 = "2038-03-31T010101"

In [461]: DST = "Europe/London"

In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT") 

在底层,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndexTimestamp的值将其字段(天、小时、分钟等)本地化到时区。然而,具有相同 UTC 值的时间戳即使位于不同时区仍被视为相等:

In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')

In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')

In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True 

在不同时区之间的Series操作将产生 UTC Series,将数据对齐到 UTC 时间戳上:

In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [470]: eastern = ts_utc.tz_convert("US/Eastern")

In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [472]: result = eastern + berlin

In [473]: result
Out[473]: 
2013-01-01 00:00:00+00:00    0
2013-01-02 00:00:00+00:00    2
2013-01-03 00:00:00+00:00    4
Freq: D, dtype: int64

In [474]: result.index
Out[474]: 
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
 '2013-01-03 00:00:00+00:00'],
 dtype='datetime64[ns, UTC]', freq='D') 

要删除时区信息,请使用tz_localize(None)tz_convert(None)tz_localize(None)将删除时区,得到本地时间表示。tz_convert(None)将在转换为 UTC 时间后删除时区。

In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")

In [476]: didx
Out[476]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
 '2014-08-01 11:00:00-04:00'],
 dtype='datetime64[ns, US/Eastern]', freq='h')

In [477]: didx.tz_localize(None)
Out[477]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
 '2014-08-01 11:00:00'],
 dtype='datetime64[ns]', freq=None)

In [478]: didx.tz_convert(None)
Out[478]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq='h')

# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq=None) 

折叠

对于模糊时间,pandas 支持显式指定仅关键字参数 fold。由于夏令时,从夏季时间到冬季时间转换时,一个挂钟时间可能会出现两次;fold 描述了 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime构造(有关详细信息,请参阅datetime 文档)或从Timestamp构造或从组件构造(见下文)。仅支持dateutil时区(请参阅dateutil 文档以了解处理模糊日期时间的dateutil方法)因为pytz时区不支持 fold(请参阅pytz 文档以了解pytz如何处理模糊日期时间的详细信息)。要使用pytz本地化模糊日期时间,请使用Timestamp.tz_localize()。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议依赖于Timestamp.tz_localize()来本地化模糊日期时间。

In [480]: pd.Timestamp(
 .....:    datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
 .....:    tz="dateutil/Europe/London",
 .....:    fold=0,
 .....: )
 .....: 
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [481]: pd.Timestamp(
 .....:    year=2019,
 .....:    month=10,
 .....:    day=27,
 .....:    hour=1,
 .....:    minute=30,
 .....:    tz="dateutil/Europe/London",
 .....:    fold=1,
 .....: )
 .....: 
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London') 
```  ### 本地化时的模糊时间

`tz_localize`可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时(DST)导致某些时间在一天内发生两次(“时钟回拨”)。以下选项可用:

+   `'raise'`: 抛出`pytz.AmbiguousTimeError`(默认行为)

+   `'infer'`: 尝试根据时间戳的单调性确定正确的偏移量

+   `'NaT'`: 用`NaT`替换模糊时间

+   `bool`: `True`表示夏令时,`False`表示非夏令时。支持类似数组的`bool`值序列。

```py
In [482]: rng_hourly = pd.DatetimeIndex(
 .....:    ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
 .....: )
 .....: 

由于存在模糊时间('11/06/2011 01:00'),这将失败。

In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError  Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument 

通过指定以下内容来处理这些模糊时间。

In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
 '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 
```  ### 本地化时不存在的时间

夏令时转换也可能使本地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前跳转”)。通过`nonexistent`参数可以控制本地化具有不存在时间的时间序列的行为。以下选项可用:

+   `'raise'`: 抛出`pytz.NonExistentTimeError`(默认行为)

+   `'NaT'`: 用`NaT`替换不存在的时间

+   `'shift_forward'`: 将不存在的时间向前移动到最接近的真实时间

+   `'shift_backward'`: 将不存在的时间向后移动到最接近的真实时间

+   timedelta 对象:通过时间间隔持续时间将不存在的时间向后移动

```py
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 is a nonexistent time 

默认情况下,本地化不存在的时间会引发错误。

In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError  Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

NonExistentTimeError: 2015-03-29 02:30:00 

将不存在的时间转换为NaT或移动时间。

In [489]: dti
Out[489]: 
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
 '2015-03-29 04:30:00'],
 dtype='datetime64[ns]', freq='h')

In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]: 
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]: 
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
 '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]: 
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]: 
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None) 
```  ### 时区 Series 操作

具有时区**naive**值的`Series`表示为`datetime64[ns]`的 dtype。

```py
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))

In [495]: s_naive
Out[495]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[ns] 

具有时区aware值的Series表示为datetime64[ns, tz]的 dtype,其中tz是时区

In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))

In [497]: s_aware
Out[497]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

这两个Series的时区信息可以通过.dt访问器进行操作,参见 dt 访问器部分。

例如,将 naive 时间戳本地化并转换为时区感知。

In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

也可以使用astype方法来操作时区信息。此方法可以在不同的时区感知 dtype 之间转换。

# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]: 
0   2013-01-01 06:00:00+01:00
1   2013-01-02 06:00:00+01:00
2   2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET] 

注意

使用Series.to_numpy()Series上,返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管在本地时区打印!),因此对于时区感知数据,返回时间戳的对象数组:

In [500]: s_naive.to_numpy()
Out[500]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
 '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

In [501]: s_aware.to_numpy()
Out[501]: 
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
 dtype=object) 

通过转换为时间戳的对象数组,它保留了时区信息。例如,当转换回 Series 时:

In [502]: pd.Series(s_aware.to_numpy())
Out[502]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

但是,如果您想要一个实际的 NumPydatetime64[ns]数组(值转换为 UTC)而不是对象数组,可以指定dtype参数:

In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
 '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]') 

处理时区

默认情况下,pandas 对象不具有时区感知:

In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")

In [435]: rng.tz is None
Out[435]: True 

要将这些日期本地化到时区(为 naive 日期分配特定时区),可以使用tz_localize方法或date_range()TimestampDatetimeIndex中的tz关键字参数。您可以传递pytzdateutil时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回pytz时区对象。要返回dateutil时区对象,请在字符串之前附加dateutil/

  • pytz中,您可以使用from pytz import common_timezones, all_timezones找到常见(以及不太常见)时区的列表。

  • dateutil使用操作系统时区,因此没有固定的可用列表。对于常见时区,名称与pytz相同。

In [436]: import dateutil

# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")

In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')

# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=dateutil.tz.tzutc(),
 .....: )
 .....: 

In [443]: rng_utc.tz
Out[443]: tzutc() 
# datetime.timezone
In [444]: rng_utc = pd.date_range(
 .....:    "3/6/2012 00:00",
 .....:    periods=3,
 .....:    freq="D",
 .....:    tz=datetime.timezone.utc,
 .....: )
 .....: 

In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc 

请注意,UTC时区是dateutil中的特殊情况,应明确构造为dateutil.tz.tzutc的实例。您也可以首先明确构造其他时区对象。

In [446]: import pytz

# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")

In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)

In [450]: rng_pytz.tz == tz_pytz
Out[450]: True

# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")

In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)

In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True 

要将一个时区感知的 pandas 对象从一个时区转换为另一个时区,可以使用tz_convert方法。

In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]: 
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
 '2012-03-07 19:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 

注意

使用pytz时区时,DatetimeIndex将为相同时区输入构造一个不同的时区对象,而Timestamp将为具有特定 UTC 偏移的一个时间点。一个DatetimeIndex可以保存一组具有不同 UTC 偏移的Timestamp对象,无法用一个pytz时区实例简洁地表示,而一个Timestamp代表一个具有特定 UTC 偏移的时间点。

In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD> 

警告

谨慎处理不同库之间的转换。对于一些时区,pytzdateutil 对时区的定义可能不同。这对于不寻常的时区而言可能是一个问题,而对于像US/Eastern这样的‘标准’时区则不太会出现这个问题。

警告

请注意,跨版本的时区库可能不会被视为相等。当使用一个版本本地化存储数据并使用另一个版本进行操作时,可能会出现问题。查看这里以了解如何处理这种情况。

警告

对于pytz时区,直接将时区对象传递给datetime.datetime构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern')))。相反,需要使用pytz时区对象上的localize方法对日期进行本地化。

警告

请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自的政府更改。

警告

如果你使用的日期超过了 2038-01-18,由于当前底层库存在的年 2038 问题,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,那么夏令时转换将会被应用。

例如,对于处于英国夏令时的两个日期(通常为 GMT+1),以下两个断言都会评估为真:

In [459]: d_2037 = "2037-03-31T010101"

In [460]: d_2038 = "2038-03-31T010101"

In [461]: DST = "Europe/London"

In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT") 

在幕后,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndexTimestamp的值将其字段(日期、小时、分钟等)本地化到时区。然而,即使处于不同时区,具有相同 UTC 值的时间戳仍被视为相等:

In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')

In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')

In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True 

在不同时区之间的Series操作将产生 UTC Series,将数据对齐到 UTC 时间戳上:

In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [470]: eastern = ts_utc.tz_convert("US/Eastern")

In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [472]: result = eastern + berlin

In [473]: result
Out[473]: 
2013-01-01 00:00:00+00:00    0
2013-01-02 00:00:00+00:00    2
2013-01-03 00:00:00+00:00    4
Freq: D, dtype: int64

In [474]: result.index
Out[474]: 
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
 '2013-01-03 00:00:00+00:00'],
 dtype='datetime64[ns, UTC]', freq='D') 

要删除时区信息,请使用tz_localize(None)tz_convert(None)tz_localize(None)将删除时区,产生本地时间表示。tz_convert(None)将在转换为 UTC 时间后删除时区。

In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")

In [476]: didx
Out[476]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
 '2014-08-01 11:00:00-04:00'],
 dtype='datetime64[ns, US/Eastern]', freq='h')

In [477]: didx.tz_localize(None)
Out[477]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
 '2014-08-01 11:00:00'],
 dtype='datetime64[ns]', freq=None)

In [478]: didx.tz_convert(None)
Out[478]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq='h')

# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
 '2014-08-01 15:00:00'],
 dtype='datetime64[ns]', freq=None) 

折叠

对于模糊时间,pandas 支持显式指定仅关键字的 fold 参数。由于夏令时,当从夏季转换到冬季时间时,一个挂钟时间可能会发生两次;fold 描述了 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime(有关详细信息,请参见datetime 文档)或从Timestamp构建,或从组件构建(见下文)。仅支持dateutil时区(有关处理模糊日期时间的dateutil方法,请参见dateutil 文档)因为pytz时区不支持 fold(有关pytz处理模糊日期时间的详细信息,请参见pytz 文档)。要使用pytz本地化模糊日期时间,请使用Timestamp.tz_localize()。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议依赖于Timestamp.tz_localize()

In [480]: pd.Timestamp(
 .....:    datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
 .....:    tz="dateutil/Europe/London",
 .....:    fold=0,
 .....: )
 .....: 
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [481]: pd.Timestamp(
 .....:    year=2019,
 .....:    month=10,
 .....:    day=27,
 .....:    hour=1,
 .....:    minute=30,
 .....:    tz="dateutil/Europe/London",
 .....:    fold=1,
 .....: )
 .....: 
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London') 

本地化时的模糊时间

tz_localize可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时(DST)导致一天内的某些时间发生两次(“时钟回拨”)。有以下选项可用:

  • 'raise': 引发pytz.AmbiguousTimeError(默认行为)

  • 'infer': 尝试根据时间戳的单调性确定正确的偏移量

  • 'NaT': 用NaT替换模糊时间

  • bool: True表示 DST 时间,False表示非 DST 时间。支持bool值的数组形式用于一系列时间。

In [482]: rng_hourly = pd.DatetimeIndex(
 .....:    ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
 .....: )
 .....: 

这将失败,因为存在模糊时间('11/06/2011 01:00'

In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError  Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument 

通过指定以下方式处理这些模糊时间。

In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
 '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None)

In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
 '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
 dtype='datetime64[ns, US/Eastern]', freq=None) 

本地化时的不存在时间

DST 转换也可能会将本地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前跳转”)。通过nonexistent参数可以控制本地化具有不存在时间的时间序列的行为。以下选项可用:

  • 'raise':引发pytz.NonExistentTimeError(默认行为)

  • 'NaT':用NaT替换不存在的时间

  • 'shift_forward':将不存在的时间向前移动到最接近的真实时间

  • 'shift_backward':将不存在的时间向后移动到最接近的真实时间

  • timedelta 对象:通过时间增量将不存在的时间移动

In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 is a nonexistent time 

默认情况下,对不存在的时间进行本地化将引发错误。

In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError  Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
  286 @doc(DatetimeArray.tz_localize)
  287 def tz_localize(
  288     self,
   (...)
  291     nonexistent: TimeNonexistent = "raise",
  292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
  294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
  78 @wraps(meth)
  79 def method(self, *args, **kwargs):
  80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
  83     flags = self._ndarray.flags
  84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
  1085     tz = timezones.maybe_get_tz(tz)
  1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
  1089         self.asi8,
  1090         tz,
  1091         ambiguous=ambiguous,
  1092         nonexistent=nonexistent,
  1093         creso=self._creso,
  1094     )
  1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
  1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

NonExistentTimeError: 2015-03-29 02:30:00 

将不存在的时间转换为NaT或者移动时间。

In [489]: dti
Out[489]: 
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
 '2015-03-29 04:30:00'],
 dtype='datetime64[ns]', freq='h')

In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]: 
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]: 
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
 '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]: 
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]: 
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
 '2015-03-29 04:30:00+02:00'],
 dtype='datetime64[ns, Europe/Warsaw]', freq=None) 

时区 Series 操作

具有时区naive值的Series表示为datetime64[ns]类型。

In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))

In [495]: s_naive
Out[495]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[ns] 

具有时区aware值的Series表示为datetime64[ns, tz]类型,其中tz是时区

In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))

In [497]: s_aware
Out[497]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

这两个Series的时区信息可以通过.dt访问器进行操作,参见 dt 访问器部分。

例如,将一个朴素时间戳本地化并转换为时区感知时间。

In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

也可以使用astype方法来操作时区信息。该方法可以在不同的时区感知数据类型之间进行转换。

# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]: 
0   2013-01-01 06:00:00+01:00
1   2013-01-02 06:00:00+01:00
2   2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET] 

注意

Series上使用Series.to_numpy(),返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管它在本地时区打印!),因此对于时区感知数据,返回 Timestamps 对象数组:

In [500]: s_naive.to_numpy()
Out[500]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
 '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

In [501]: s_aware.to_numpy()
Out[501]: 
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
 Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
 dtype=object) 

通过将时间戳转换为 Timestamps 对象数组,可以保留时区信息。例如,当转换回 Series 时:

In [502]: pd.Series(s_aware.to_numpy())
Out[502]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern] 

但是,如果您想要一个实际的 NumPy datetime64[ns]数组(将值转换为 UTC)而不是对象数组,可以指定dtype参数:

In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
 '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]') 
posted @ 2024-06-26 10:31  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报