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

2024-05-24 16:20:18 浏览数 (2)

原文:pandas.pydata.org/docs/

窗口操作

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

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

代码语言:javascript复制
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 

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

代码语言:javascript复制
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 版本起)

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

代码语言:javascript复制
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操作与窗口操作链接在一起,该操作将首先按指定键对数据进行分组,然后对每个组执行窗口操作。

代码语言:javascript复制
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()中指定一个权重列来计算加权平均值。

代码语言:javascript复制
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对象来继续窗口计算。

代码语言:javascript复制
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 
代码语言:javascript复制
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

代码语言:javascript复制
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方法,用于返回应用于窗口的多个聚合的结果。

代码语言:javascript复制
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关键字将标签设置��中心。

代码语言:javascript复制
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 版本中新增。

代码语言:javascript复制
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子类:

代码语言:javascript复制
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)上进行滚动操作。

代码语言:javascript复制
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子类实现了一个闭合的固定宽度前瞻性滚动窗口,我们可以按照以下方式使用它:

代码语言:javascript复制
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 

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

代码语言:javascript复制
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 中实现了以下两个调用是等效的:

代码语言:javascript复制
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

代码语言:javascript复制
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_iright)²}{left(sum_{i=0}^t w_iright)² - 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 起)

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

代码语言:javascript复制
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操作与窗口操作链接在一起,首先按指定键对数据进行分组,然后对每个组执行窗口操作。

代码语言:javascript复制
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()中计算加权平均值。

代码语言:javascript复制
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对象,以继续进行窗口计算。

代码语言:javascript复制
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 
代码语言:javascript复制
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

代码语言:javascript复制
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方法,用于返回应用于窗口的多个聚合的结果。

代码语言:javascript复制
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 

滚动窗口

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

代码语言:javascript复制
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关键字将标签设置为中心。

代码语言:javascript复制
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 版本中的新功能。

代码语言:javascript复制
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子类:

代码语言:javascript复制
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)上进行滚动操作。

代码语言:javascript复制
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子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:

代码语言:javascript复制
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 

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

代码语言:javascript复制
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 中的新功能。

代码语言:javascript复制
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'

开放端点

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

代码语言:javascript复制
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

代码语言:javascript复制
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子类:

代码语言:javascript复制
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)上进行滚动操作。

代码语言:javascript复制
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子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:

代码语言:javascript复制
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 

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

代码语言:javascript复制
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)。

代码语言:javascript复制
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()还可以利用它作为可选依赖项。可以通过指定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,其值是相关日期(请参阅下一节)。

例如:

代码语言:javascript复制
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参数:

注意

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

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

代码语言:javascript复制
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 窗口方法所需的补充参数。

代码语言:javascript复制
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 中实现了以下两种调用是等效的:

代码语言:javascript复制
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

代码语言:javascript复制
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_iright)²}{left(sum_{i=0}^t w_iright)² - 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 支持:

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

代码语言:javascript复制
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) 

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

代码语言:javascript复制
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') 

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

代码语言:javascript复制
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') 

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

代码语言:javascript复制
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 

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

代码语言:javascript复制
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_datetime或date_range

时间增量

Timedelta

TimedeltaIndex

timedelta64[ns]

to_timedelta或timedelta_range

时间跨度

Period

PeriodIndex

period[freq]

Period或period_range

日期偏移

DateOffset

None

None

DateOffset

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

代码语言:javascript复制
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也可以直接支持时间组件作为数据本身。

代码语言:javascript复制
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数据。

代码语言:javascript复制
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对浮点数据的行为类似。

代码语言:javascript复制
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表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出。

例如:

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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标志:

代码语言:javascript复制
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

代码语言:javascript复制
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构造函数:

代码语言:javascript复制
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’以将索引的频率设置为创建时推断的频率:

代码语言:javascript复制
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参数以确保特定的解析。这也可能显著加快转换速度。

代码语言:javascript复制
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

代码语言:javascript复制
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] 

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

代码语言:javascript复制
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' 是在无法解析时引发异常:

代码语言:javascript复制
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(不是时间):

代码语言:javascript复制
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 参数指定的起始点计算出来的。

代码语言:javascript复制
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 读取为时区无关的时间戳,然后本地化到适当的时区:

代码语言:javascript复制
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)。

代码语言:javascript复制
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:

代码语言:javascript复制
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 秒)。

代码语言:javascript复制
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 时间。

代码语言:javascript复制
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的默认频率是工作日

代码语言:javascript复制
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:

代码语言:javascript复制
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)轻松生成一系列日期。开始和结束日期是严格包含的,因此不会生成指定范围之外的日期:

代码语言:javascript复制
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个元素:

代码语言:javascript复制
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参数生成一系列自定义频率日期。只有在传递自定义频率字符串时才会使用这些参数。

代码语言:javascript复制
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 年左右:

代码语言:javascript复制
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可以像常规索引一样使用,并提供其所有智能功能,如选择、切片等。

代码语言:javascript复制
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') 
部分字符串索引

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

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

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

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

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

代码语言:javascript复制
In [103]: ts["2011"]
Out[103]: 
2011-01-31    0.119209
2011-02-28   -1.044236
2011-03-31   -0.861849
2011-04-29   -2.104569
2011-05-31   -0.494929
2011-06-30    1.071804
2011-07-29    0.721555
2011-08-31   -0.706771
2011-09-30   -1.039575
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BME, dtype: float64

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

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

警告

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

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

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

[100000 rows x 1 columns]

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

[100000 rows x 1 columns] 

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

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

[84960 rows x 1 columns] 

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

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

[84960 rows x 1 columns] 

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

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

[83521 rows x 1 columns] 

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

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

[751 rows x 1 columns] 

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

代码语言:javascript复制
In [112]: dft2 = pd.DataFrame(
 .....:    np.random.randn(20, 1),
 .....:    columns=["A"],
 .....:    index=pd.MultiIndex.from_product(
 .....:        [pd.date_range("20130101", periods=10, freq="12h"), ["a", "b"]]
 .....:    ),
 .....: )
 .....: 

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

[20 rows x 1 columns]

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

In [115]: idx = pd.IndexSlice

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

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

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

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

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

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

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

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

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

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

不到一分钟的时间戳字符串会给出一个Series对象。

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

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

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

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

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

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

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

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

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

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

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

警告

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

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

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

还要注意,DatetimeIndex的分辨率不能低于天。

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

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

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

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

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

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

[83521 rows x 1 columns] 

没有默认值。

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

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

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

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

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

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

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

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

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

有几个时间/日期属性可以从 `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 小时

BDay 或 BusinessDay

'B'

工作日(周日)

CDay 或 CustomBusinessDay

'C'

自定义工作日

Week

'W'

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

WeekOfMonth

'WOM'

每月第 y 周的第 x 天

LastWeekOfMonth

'LWOM'

每月最后一周的第 x 天

MonthEnd

'ME'

日历月结束

MonthBegin

'MS'

日历月开始

BMonthEnd 或 BusinessMonthEnd

'BME'

工作月结束

BMonthBegin 或 BusinessMonthBegin

'BMS'

工作月开始

CBMonthEnd 或 CustomBusinessMonthEnd

'CBME'

自定义工作月结束

CBMonthBegin 或 CustomBusinessMonthBegin

'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()方法,用于将日期向前或向后移动到相对于偏移的有效偏移日期。例如,业务偏移将把落在周末(星期六和星期日)的日期向前滚动到星期一,因为业务偏移在工作日上运行。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

normalize选项将对加法和减法有效。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]: 
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
 '2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
 '2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
 '2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
 '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
 '2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
 dtype='datetime64[ns]', freq='CBMS') 

注意

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

自版本 2.2.0 起已弃用:别名 `H`、`BH`、`CBH`、`T`、`S`、`L`、`U` 和 `N` 已弃用,推荐使用别名 `h`、`bh`、`cbh`、`min`、`s`、`ms`、`us` 和 `ns`。

注意

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

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

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

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

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

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

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

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

别名

描述

B

工作日频率

D

日历日频率

W

每周频率

M

每月频率

Q

季度频率

Y

每年频率

h

每小时频率

min

每分钟频率

s

每秒频率

ms

毫秒

us

微秒

ns

纳秒

自版本 2.2.0 起已弃用:别名 AHTSLUN 已弃用,推荐使用别名 Yhminsmsusns

结合别名

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

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

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

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

代码语言:javascript复制
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]: 
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
 '2011-01-01 04:40:00', '2011-01-01 07:00:00',
 '2011-01-01 09:20:00', '2011-01-01 11:40:00',
 '2011-01-01 14:00:00', '2011-01-01 16:20:00',
 '2011-01-01 18:40:00', '2011-01-01 21:00:00'],
 dtype='datetime64[ns]', freq='140min')

In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]: 
DatetimeIndex([       '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
 '2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
 '2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
 '2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
 '2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
 dtype='datetime64[ns]', freq='86400000010us') 
锚定偏移

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

别名

描述

W-SUN

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

W-MON

每周频率(星期一)

W-TUE

每周频率(星期二)

W-WED

每周频率(星期三)

W-THU

每周频率(星期四)

W-FRI

每周频率(星期五)

W-SAT

每周频率(星期六)

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

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

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

季度频率,年底在一月

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

季度频率,年底在二月

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

季度频率,年底在三月

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

季度频率,年底在四月

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

季度频率,年底在五月

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

季度频率,年底在六月

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

季度频率,年底在七月

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

季度频率,年底在八月

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

季度频率,年底在九月

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

季度频率,年底在十月

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

季度频率,年底在十一月

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

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

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

每年频率,锚定在一月底

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

每年频率,锚定在二月底

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

每年频率,锚定在三月底

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

每年频率,锚定在四月底

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

每年频率,锚定在五月底

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

每年频率,锚定在六月底

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

每年频率,锚定在七月底

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

每年频率,锚定在八月底

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

每年频率,锚定在九月底

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

每年频率,锚定在十月底

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

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

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

锚定偏移语义

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

n不为 0 时,如果给定日期不在锚点上,则会被吸附到下一个(上一个)锚点,并向前或向后移动|n|-1步。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

规则

描述

nearest_workday

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

sunday_to_monday

将周日移至下周一

next_monday_or_tuesday

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

previous_friday

将周六和周日移至上周五

next_monday

将周六和周日移至下周一

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

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

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

In [261]: cal = ExampleCalendar()

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

提示:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [278]: new_cal.rules
Out[278]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO( 1)>),
 Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)] 
```## 与时间序列相关的实例方法

### 移动 / 拖延

有时可能需要在时间序列中向前或向后移动值。用于此目的的方法是 `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 方法会更改索引中的所有日期,而不是更改数据和索引的对齐方式:

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

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

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

请注意,当指定freq时,由于数据未重新对齐,因此前导条目不再是 NaN。

频率转换

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

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

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

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

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

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

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

asfreqreindex相关的是fillna(),该方法在缺失数据部分有文档记录。

转换为 Python 日期时间

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

警告

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

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

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

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

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

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

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

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

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

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

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

上采样

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

我们可以使用标准的 getitem 选择特定列或列。

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

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

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

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

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

代码语言:javascript复制
In [321]: r.agg(["sum", "mean"])
Out[321]: 
 A            ...          C 
 sum      mean  ...        sum      mean
2012-01-01 00:00:00  -6.088060 -0.033823  ... -14.660515 -0.081447
2012-01-01 00:03:00  10.243678  0.056909  ...  -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837  ...  -9.363825 -0.052021
2012-01-01 00:09:00  11.362228  0.063123  ... -11.975895 -0.066533
2012-01-01 00:12: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的列应用不同的聚合:

代码语言:javascript复制
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 

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

代码语言:javascript复制
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 

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

代码语言:javascript复制
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关键字。

代码语言:javascript复制
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关键字。

代码语言:javascript复制
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="nn")
 .....: 
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。

例如:

代码语言:javascript复制
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'之后的结果并不相同:

代码语言:javascript复制
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'之后的结果是相同的:

代码语言:javascript复制
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

代码语言:javascript复制
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中。对于这个时间序列,这两个示例是等效的:

代码语言:javascript复制
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将被设置为时间序列的第一个值。

0 人点赞