原文:
pandas.pydata.org/docs/
窗口操作
原文:
pandas.pydata.org/docs/user_guide/window.html
pandas 包含一组紧凑的 API,用于执行窗口操作 - 一种在值的滑动分区上执行聚合的操作。该 API 的功能类似于groupby
API,Series
和DataFrame
调用具有必要参数的窗口方法,然后随后调用聚合函数。
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 种类型的窗口操作:
- 滚动窗口:对值进行通用固定或可变滑动窗口。
- 加权窗口:由
scipy.signal
库提供的加权非矩形窗口。 - 扩展窗口:对值进行累积窗口。
- 指数加权窗口:对值进行累积和指数加权窗口。
概念 | 方法 | 返回对象 | 支持基于时间的窗口 | 支持链式分组 | 支持表方法 | 支持在线操作 |
---|---|---|---|---|---|---|
滚动窗口 | 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
操作与窗口操作链接在一起,该操作将首先按指定键对数据进行分组,然后对每个组执行窗口操作。
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
值。
警告
一些窗口聚合方法,mean
,sum
,var
和std
方法可能由于底层窗口算法累积和而受到数值不精确性的影响。当值的数量级不同时(1/np.finfo(np.double).eps),会导致截断。必须注意,大值可能会对不包括这些值的窗口产生影响。使用Kahan 求和算法来计算滚动求和以尽可能保持准确性。
自 1.3.0 版本起新增。
一些窗口操作还支持构造函数中的method='table'
选项,该选项可以在整个DataFrame
上执行窗口操作,而不是一次处理单个列或行。对于具有许多列或行(使用相应的axis
参数)的DataFrame
,这可以提供有用的性能优势,或者在窗口操作期间利用其他列。只有在相应的方法调用中指定了engine='numba'
时,才能使用method='table'
选项。
例如,可以通过在apply()
中指定一个权重列来计算加权平均值。
In [8]: def weighted_mean(x):
...: arr = np.ones((1, x.shape[1]))
...: arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
...: return arr
...:
In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])
In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba") # noqa: E501
Out[10]:
0 1 2
0 1.000000 2.000000 1.0
1 1.800000 2.000000 1.0
2 3.333333 2.333333 1.0
3 1.555556 7.000000 1.0
1.3 版本中新增。
一些窗口操作在构造窗口对象后还支持online
方法,该方法返回一个新对象,支持传入新的DataFrame
或Series
对象,以使用新值继续窗口计算(即在线计算)。
新窗口对象上的方法必须首先调用聚合方法以“启动”在线计算的初始状态。然后,可以通过update
参数传递新的DataFrame
或Series
对象来继续窗口计算。
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
。
In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])
In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]:
0 NaN
1 1.0
2 3.0
3 3.0
4 2.0
5 3.0
dtype: float64
In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]:
0 NaN
1 NaN
2 3.0
3 3.0
4 NaN
5 NaN
dtype: float64
# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
dtype: float64
此外,所有窗口操作都支持aggregate
方法,用于返回应用于窗口的多个聚合的结果。
In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})
In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]:
A B
sum mean std sum mean std
0 0.0 0.0 NaN 10.0 10.0 NaN
1 1.0 0.5 0.707107 21.0 10.5 0.707107
2 3.0 1.0 1.000000 33.0 11.0 1.000000
3 6.0 1.5 1.290994 46.0 11.5 1.290994
4 10.0 2.0 1.581139 60.0 12.0 1.581139
```## 滚动窗口
通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。
```py
In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']
In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))
In [24]: s
Out[24]:
2020-01-01 0
2020-01-03 1
2020-01-04 2
2020-01-05 3
2020-01-29 4
dtype: int64
# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]:
2020-01-01 NaN
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 7.0
dtype: float64
# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]:
2020-01-01 0.0
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 4.0
dtype: float64
查看所有支持的聚合函数,请参阅滚动窗口函数。
居中窗口
默认情况下,标签设置为窗口的右边缘,但可以使用center
关键字将标签设置��中心。
In [27]: s = pd.Series(range(10))
In [28]: s.rolling(window=5).mean()
Out[28]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [29]: s.rolling(window=5, center=True).mean()
Out[29]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
5 5.0
6 6.0
7 7.0
8 NaN
9 NaN
dtype: float64
这也可以应用于类似于日期时间的索引。
1.3.0 版本中新增。
代码语言: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_expanding
为True
,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer
子类:
In [44]: from pandas.api.indexers import BaseIndexer
In [45]: class CustomIndexer(BaseIndexer):
....: def get_window_bounds(self, num_values, min_periods, center, closed, step):
....: start = np.empty(num_values, dtype=np.int64)
....: end = np.empty(num_values, dtype=np.int64)
....: for i in range(num_values):
....: if self.use_expanding[i]:
....: start[i] = 0
....: end[i] = i 1
....: else:
....: start[i] = i
....: end[i] = i self.window_size
....: return start, end
....:
In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)
In [47]: df.rolling(indexer).sum()
Out[47]:
values
0 0.0
1 1.0
2 3.0
3 3.0
4 10.0
您可以在这里查看其他BaseIndexer
子类的示例。
在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer
,它允许在非固定偏移(如BusinessDay
)上进行滚动操作。
In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer
In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))
In [50]: offset = pd.offsets.BDay(1)
In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)
In [52]: df
Out[52]:
0
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
2020-01-06 5
2020-01-07 6
2020-01-08 7
2020-01-09 8
2020-01-10 9
In [53]: df.rolling(indexer).sum()
Out[53]:
0
2020-01-01 0.0
2020-01-02 1.0
2020-01-03 2.0
2020-01-04 3.0
2020-01-05 7.0
2020-01-06 12.0
2020-01-07 6.0
2020-01-08 7.0
2020-01-09 8.0
2020-01-10 9.0
对于一些问题,未来的知识可用于分析。例如,当每个数据点都是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能很有用。为此,可以使用FixedForwardWindowIndexer
类。这个BaseIndexer
子类实现了一个闭合的固定宽度前瞻性滚动窗口,我们可以按照以下方式使用它:
In [54]: from pandas.api.indexers import FixedForwardWindowIndexer
In [55]: indexer = FixedForwardWindowIndexer(window_size=2)
In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]:
0
2020-01-01 1.0
2020-01-02 3.0
2020-01-03 5.0
2020-01-04 7.0
2020-01-05 9.0
2020-01-06 11.0
2020-01-07 13.0
2020-01-08 15.0
2020-01-09 17.0
2020-01-10 9.0
我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现这一点,如下面的示例所示:
代码语言: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
。
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 种类型的窗口操作:
- 滚动窗口:对数值进行通用的固定或可变滑动窗口。
- 加权窗口:由
scipy.signal
库提供的加权、非矩形窗口。 - 扩展窗口:对数值进行累积窗口。
- 指数加权窗口:对数值进行累积和指数加权的窗口。
概念 | 方法 | 返回对象 | 支持基于时间的窗口 | 支持链接的 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
操作与窗口操作链接在一起,首先按指定键对数据进行分组,然后对每个组执行窗口操作。
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
值。
警告
一些窗口聚合,mean
,sum
,var
和std
方法可能由于底层窗口算法累积和而遭受数值不精确性。当值的数量级不同时(1/np.finfo(np.double).eps),会导致截断。必须注意,大值可能会影响不包括这些值的窗口。使用Kahan 求和算法来计算滚动求和以尽可能保持准确性。
版本 1.3.0 中的新功能。
一些窗口操作在构造函数中还支持method='table'
选项,该选项可以在整个DataFrame
上执行窗口操作,而不是一次处理单个列或行。对于具有许多列或行(使用相应的axis
参数)的DataFrame
,或者在窗口操作期间利用其他列的能力,这可以提供有用的性能优势。只有在相应的方法调用中指定了engine='numba'
时,才能使用method='table'
选项。
例如,可以通过指定一个单独的权重列,在apply()
中计算加权平均值。
In [8]: def weighted_mean(x):
...: arr = np.ones((1, x.shape[1]))
...: arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
...: return arr
...:
In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])
In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba") # noqa: E501
Out[10]:
0 1 2
0 1.000000 2.000000 1.0
1 1.800000 2.000000 1.0
2 3.333333 2.333333 1.0
3 1.555556 7.000000 1.0
版本 1.3 中的新功能。
在构造窗口对象后,一些窗口操作还支持online
方法,该方法返回一个新对象,支持传入新的DataFrame
或Series
对象,以继续使用新值进行窗口计算(即在线计算)。
这些新窗口对象上的方法必须首先调用聚合方法来“启动”在线计算的初始状态。然后,可以通过update
参数传入新的DataFrame
或Series
对象,以继续进行窗口计算。
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
。
In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])
In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]:
0 NaN
1 1.0
2 3.0
3 3.0
4 2.0
5 3.0
dtype: float64
In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]:
0 NaN
1 NaN
2 3.0
3 3.0
4 NaN
5 NaN
dtype: float64
# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
dtype: float64
此外,所有窗口操作都支持aggregate
方法,用于返回应用于窗口的多个聚合的结果。
In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})
In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]:
A B
sum mean std sum mean std
0 0.0 0.0 NaN 10.0 10.0 NaN
1 1.0 0.5 0.707107 21.0 10.5 0.707107
2 3.0 1.0 1.000000 33.0 11.0 1.000000
3 6.0 1.5 1.290994 46.0 11.5 1.290994
4 10.0 2.0 1.581139 60.0 12.0 1.581139
滚动窗口
通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。
代码语言: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
关键字将标签设置为中心。
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_expanding
为True
,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer
子类:
In [44]: from pandas.api.indexers import BaseIndexer
In [45]: class CustomIndexer(BaseIndexer):
....: def get_window_bounds(self, num_values, min_periods, center, closed, step):
....: start = np.empty(num_values, dtype=np.int64)
....: end = np.empty(num_values, dtype=np.int64)
....: for i in range(num_values):
....: if self.use_expanding[i]:
....: start[i] = 0
....: end[i] = i 1
....: else:
....: start[i] = i
....: end[i] = i self.window_size
....: return start, end
....:
In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)
In [47]: df.rolling(indexer).sum()
Out[47]:
values
0 0.0
1 1.0
2 3.0
3 3.0
4 10.0
您可以在这里查看其他BaseIndexer
子类的示例
在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer
,它允许在非固定偏移(如BusinessDay
)上进行滚动操作。
In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer
In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))
In [50]: offset = pd.offsets.BDay(1)
In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)
In [52]: df
Out[52]:
0
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
2020-01-06 5
2020-01-07 6
2020-01-08 7
2020-01-09 8
2020-01-10 9
In [53]: df.rolling(indexer).sum()
Out[53]:
0
2020-01-01 0.0
2020-01-02 1.0
2020-01-03 2.0
2020-01-04 3.0
2020-01-05 7.0
2020-01-06 12.0
2020-01-07 6.0
2020-01-08 7.0
2020-01-09 8.0
2020-01-10 9.0
对于一些问题,未来的知识是可用于分析的。例如,当每个数据点是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能是有用的。为此目的提供了FixedForwardWindowIndexer
类。这个BaseIndexer
子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:
In [54]: from pandas.api.indexers import FixedForwardWindowIndexer
In [55]: indexer = FixedForwardWindowIndexer(window_size=2)
In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]:
0
2020-01-01 1.0
2020-01-02 3.0
2020-01-03 5.0
2020-01-04 7.0
2020-01-05 9.0
2020-01-06 11.0
2020-01-07 13.0
2020-01-08 15.0
2020-01-09 17.0
2020-01-10 9.0
我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现,如下面的示例所示:
代码语言: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_values
、min_periods
、center
、closed
和step
将自动传递给get_window_bounds
,并且定义的方法必须始终接受这些参数。
例如,如果我们有以下DataFrame
In [40]: use_expanding = [True, False, True, False, True]
In [41]: use_expanding
Out[41]: [True, False, True, False, True]
In [42]: df = pd.DataFrame({"values": range(5)})
In [43]: df
Out[43]:
values
0 0
1 1
2 2
3 3
4 4
如果我们想要使用一个扩展窗口,其中use_expanding
为True
,否则为大小为 1 的窗口,我们可以创建以下BaseIndexer
子类:
In [44]: from pandas.api.indexers import BaseIndexer
In [45]: class CustomIndexer(BaseIndexer):
....: def get_window_bounds(self, num_values, min_periods, center, closed, step):
....: start = np.empty(num_values, dtype=np.int64)
....: end = np.empty(num_values, dtype=np.int64)
....: for i in range(num_values):
....: if self.use_expanding[i]:
....: start[i] = 0
....: end[i] = i 1
....: else:
....: start[i] = i
....: end[i] = i self.window_size
....: return start, end
....:
In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)
In [47]: df.rolling(indexer).sum()
Out[47]:
values
0 0.0
1 1.0
2 3.0
3 3.0
4 10.0
您可以查看其他BaseIndexer
子类的示例这里
在这些示例中,一个值得注意的子类是VariableOffsetWindowIndexer
,它允许在非固定偏移(如BusinessDay
)上进行滚动操作。
In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer
In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))
In [50]: offset = pd.offsets.BDay(1)
In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)
In [52]: df
Out[52]:
0
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
2020-01-06 5
2020-01-07 6
2020-01-08 7
2020-01-09 8
2020-01-10 9
In [53]: df.rolling(indexer).sum()
Out[53]:
0
2020-01-01 0.0
2020-01-02 1.0
2020-01-03 2.0
2020-01-04 3.0
2020-01-05 7.0
2020-01-06 12.0
2020-01-07 6.0
2020-01-08 7.0
2020-01-09 8.0
2020-01-10 9.0
对于一些问题,未来的知识是可用于分析的。例如,当每个数据点都是从实验中读取的完整时间序列时,任务是提取潜在条件。在这些情况下,执行前瞻性滚动窗口计算可能很有用。为此,可以使用FixedForwardWindowIndexer
类。这个BaseIndexer
子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以按以下方式使用它:
In [54]: from pandas.api.indexers import FixedForwardWindowIndexer
In [55]: indexer = FixedForwardWindowIndexer(window_size=2)
In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]:
0
2020-01-01 1.0
2020-01-02 3.0
2020-01-03 5.0
2020-01-04 7.0
2020-01-05 9.0
2020-01-06 11.0
2020-01-07 13.0
2020-01-08 15.0
2020-01-09 17.0
2020-01-10 9.0
通过使用切片、应用滚动聚合,然后翻转结果,我们也可以实现这一点,如下例所示:
代码语言: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
)。
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 将应用于可能的两个例程:
- 如果
func
是标准 Python 函数,则引擎将JIT传递的函数。func
也可以是一个 JITed 函数,在这种情况下,引擎将不会再次 JIT 函数。 - 引擎将 JIT 应用函数应用于每个窗口的循环。
engine_kwargs
参数是一个关键字参数字典,将传递给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
,则为每对列计算统计信息,返回一个具有MultiIndex
的DataFrame
,其值是相关日期(请参阅下一节)。
例如:
代码语言: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 窗口方法所需的补充参数。
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
。
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 的datetime64
和timedelta64
数据类型,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 个与时间相关的概念:
- 日期时间:具有时区支持的特定日期和时间。类似于标准库中的
datetime.datetime
。 - 时间增量:绝对时间持续。类似于标准库中的
datetime.timedelta
。 - 时间跨度:由时间点和其关联频率定义的时间跨度。
- 日期偏移:尊重日历算术的相对时间持续。类似于
dateutil
包中的dateutil.relativedelta.relativedelta
。
概念 | 标量类 | 数组类 | pandas 数据类型 | 主要创建方法 |
---|---|---|---|---|
日期时间 | Timestamp | DatetimeIndex | datetime64[ns]或datetime64[ns, tz] | to_datetime或date_range |
时间增量 | Timedelta | TimedeltaIndex | timedelta64[ns] | to_timedelta或timedelta_range |
时间跨度 | Period | PeriodIndex | period[freq] | Period或period_range |
日期偏移 | DateOffset | None | None | DateOffset |
对于时间序列数据,通常将时间组件表示为Series
或DataFrame
的索引,以便可以根据时间元素执行操作。
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
然而,Series
和DataFrame
也可以直接支持时间组件作为数据本身。
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]
当传递到这些构造函数时,Series
和DataFrame
在datetime
、timedelta
和Period
数据方面具有扩展的数据类型支持和功能。但是,DateOffset
数据将被存储为object
数据。
In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]:
0 2011-01
1 2011-02
2 2011-03
dtype: period[M]
In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]:
0 <DateOffset>
1 <2 * DateOffsets>
dtype: object
In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]:
0 2011-01-31
1 2011-02-28
2 2011-03-31
dtype: datetime64[ns]
最后,pandas 将空日期时间、时间差和时间跨度表示为NaT
,这对于表示缺失或空日期值非常有用,并且与np.nan
对浮点数据的行为类似。
In [24]: pd.Timestamp(pd.NaT)
Out[24]: NaT
In [25]: pd.Timedelta(pd.NaT)
Out[25]: NaT
In [26]: pd.Period(pd.NaT)
Out[26]: NaT
# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False
```## 时间戳 vs. 时间跨度
时间戳数据是将值与时间点关联的最基本类型的时间序列数据。对于 pandas 对象,这意味着使用时间点。
```py
In [28]: import datetime
In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')
In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')
In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00')
然而,在许多情况下,将变量与时间跨度关联起来更为自然。Period
表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出。
例如:
代码语言: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')
Timestamp
和Period
可以用作索引。Timestamp
和Period
的列表会自动强制转换为DatetimeIndex
和PeriodIndex
。
In [34]: dates = [
....: pd.Timestamp("2012-05-01"),
....: pd.Timestamp("2012-05-02"),
....: pd.Timestamp("2012-05-03"),
....: ]
....:
In [35]: ts = pd.Series(np.random.randn(3), dates)
In [36]: type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex
In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
In [38]: ts
Out[38]:
2012-05-01 0.469112
2012-05-02 -0.282863
2012-05-03 -1.509059
dtype: float64
In [39]: periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]
In [40]: ts = pd.Series(np.random.randn(3), periods)
In [41]: type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex
In [42]: ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')
In [43]: ts
Out[43]:
2012-01 -1.135632
2012-02 1.212112
2012-03 -0.173215
Freq: M, dtype: float64
pandas 允许您捕获这两种表示形式并在它们之间进行转换。在内部,pandas 使用Timestamp
的实例表示时间戳,使用DatetimeIndex
的实例表示时间戳序列。对于常规时间跨度,pandas 使用Period
对象表示标量值,使用PeriodIndex
表示跨度序列。未来版本将更好地支持具有任意起始点和结束点的不规则间隔。 ## 转换为时间戳
要将Series
或类似列表的日期样式对象(例如字符串、时代或混合物)转换为to_datetime
函数。当传递一个Series
时,这将返回一个Series
(具有相同的索引),而类似列表将转换为DatetimeIndex
:
In [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]:
0 2009-07-31
1 2010-01-10
2 NaT
dtype: datetime64[ns]
In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)
如果您使用以日期为首的日期(即欧洲风格),您可以传递dayfirst
标志:
In [46]: pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)
In [47]: pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None)
警告
您可以在上面的示例中看到dayfirst
并不是严格的。如果日期不能以日期为首解析,它将被解析为如果dayfirst
为False
,同时还会引发警告。
如果将单个字符串传递给to_datetime
,它将返回一个单个Timestamp
。Timestamp
也可以接受字符串输入,但不接受像dayfirst
或format
这样的字符串解析选项,因此如果需要这些选项,请使用to_datetime
。
In [48]: pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')
In [49]: pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00')
您也可以直接使用DatetimeIndex
构造函数:
In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)
可以传递字符串‘infer’以将索引的频率设置为创建时推断的频率:
代码语言: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
参数以确保特定的解析。这也可能显著加快转换速度。
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
来组装成 Timestamps
的 Series
。
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,包括:
- 必需的:
year
、month
、day
- 可选的:
hour
、minute
、second
、millisecond
、microsecond
、nanosecond
无效数据
默认行为 errors='raise'
是在无法解析时引发异常:
In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
1097 result = _convert_and_box_cache(argc, cache_array)
1098 else:
-> 1099 result = convert_listlike(argc, format)
1100 else:
1101 result = convert_listlike(np.array([arg]), format)[0]
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:433, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
431 # `format` could be inferred, or user didn't ask for mixed-format parsing.
432 if format is not None and format != "mixed":
--> 433 return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
435 result, tz_parsed = objects_to_datetime64(
436 arg,
437 dayfirst=dayfirst,
(...)
441 allow_object=True,
442 )
444 if tz_parsed is not None:
445 # We can take a shortcut since the datetime64 numpy array
446 # is in UTC
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:467, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
456 def _array_strptime_with_fallback(
457 arg,
458 name,
(...)
462 errors: str,
463 ) -> Index:
464 """
465 Call array_strptime, with fallback behavior depending on 'errors'.
466 """
--> 467 result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
468 if tz_out is not None:
469 unit = np.datetime_data(result.dtype)[0]
File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()
File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()
File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()
ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1. You might want to try:
- passing `format` if your strings have a consistent format;
- passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
- passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.
传递 errors='coerce'
将无法解析的数据转换为 NaT
(不是时间):
In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)
Epoch 时间戳
pandas 支持将整数或浮点 epoch 时间转换为 Timestamp
和 DatetimeIndex
。默认单位是纳秒,因为这是 Timestamp
对象在内部存储的方式。然而,epoch 通常以另一个可以指定的 unit
存储。这些是从 origin
参数指定的起始点计算出来的。
In [59]: pd.to_datetime(
....: [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
....: )
....:
Out[59]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
In [60]: pd.to_datetime(
....: [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
....: unit="ms",
....: )
....:
Out[60]:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
注意
unit
参数不使用与上面讨论的 format
参数相同的字符串。可用的单位在 pandas.to_datetime()
的文档中列出。
使用指定了 tz
参数的 epoch 时间戳构建 Timestamp
或 DatetimeIndex
会引发 ValueError。如果你有另一个时区中的墙上时间的 epoch,你可以将 epoch 读取为时区无关的时间戳,然后本地化到适当的时区:
In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')
In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)
注意
Epoch 时间将四舍五入到最接近的纳秒。
警告
浮点 epoch 时间的转换可能导致不准确和意外的结果。Python 浮点数 在十进制中有大约 15 位数字的精度。在从浮点数转换为高精度 Timestamp
时进行四舍五入是不可避免的。实现精确精度的唯一方法是使用固定宽度类型(例如 int64)。
In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)
In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912')
另请参见
使用 origin 参数 ### 从时间戳到 epoch
要反转上述操作,即从 Timestamp
转换为 ‘unix’ epoch:
In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
In [66]: stamps
Out[66]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
我们减去 epoch(1970 年 1 月 1 日 UTC 的午夜)然后整除“unit”(1 秒)。
代码语言: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 时间。
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()
函数来创建一个DatetimeIndex
。date_range
的默认频率是日历日,而bdate_range
的默认频率是工作日:
In [75]: start = datetime.datetime(2011, 1, 1)
In [76]: end = datetime.datetime(2012, 1, 1)
In [77]: index = pd.date_range(start, end)
In [78]: index
Out[78]:
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
'2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
'2011-01-09', '2011-01-10',
...
'2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
'2011-12-31', '2012-01-01'],
dtype='datetime64[ns]', length=366, freq='D')
In [79]: index = pd.bdate_range(start, end)
In [80]: index
Out[80]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14',
...
'2011-12-19', '2011-12-20', '2011-12-21', '2011-12-22',
'2011-12-23', '2011-12-26', '2011-12-27', '2011-12-28',
'2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', length=260, freq='B')
便利函数如date_range
和bdate_range
可以利用各种 frequency aliases:
In [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
'2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
'2011-09-30', '2011-10-31',
...
'2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
'2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
'2094-03-31', '2094-04-30'],
dtype='datetime64[ns]', length=1000, freq='ME')
In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]:
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
'2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
'2013-01-01', '2013-04-01',
...
'2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
'2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
'2073-01-02', '2073-04-03'],
dtype='datetime64[ns]', length=250, freq='BQS-JAN')
date_range
和bdate_range
使得使用各种参数组合(如start
、end
、periods
和freq
)轻松生成一系列日期。开始和结束日期是严格包含的,因此不会生成指定范围之外的日期:
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')
指定start
、end
和periods
将从start
到end
生成一系列均匀间隔的日期,结果DatetimeIndex
中有periods
个元素:
In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[87]:
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
'2018-01-05'],
dtype='datetime64[ns]', freq=None)
In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]:
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
'2018-01-01 21:20:00', '2018-01-02 08:00:00',
'2018-01-02 18:40:00', '2018-01-03 05:20:00',
'2018-01-03 16:00:00', '2018-01-04 02:40:00',
'2018-01-04 13:20:00', '2018-01-05 00:00:00'],
dtype='datetime64[ns]', freq=None)
自定义频率范围
bdate_range
还可以通过使用weekmask
和holidays
参数生成一系列自定义频率日期。只有在传递自定义频率字符串时才会使用这些参数。
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
对象的并集非常快速(对于快速数据对齐很重要)。 - 通过属性(如
year
、month
等)快速访问日期字段。 - 使用
shift
方法在 pandas 对象上进行快速移位。
DatetimeIndex
对象具有常规Index
对象的所有基本功能,以及一系列用于简化频率处理的高级时间序列特定方法。
另请参阅
重新索引方法
注意
虽然 pandas 不强制您拥有排序的日期索引,但如果日期未排序,则其中一些方法可能会产生意外或不正确的行为。
DatetimeIndex
可以像常规索引一样使用,并提供其所有智能功能,如选择、切片等。
In [95]: rng = pd.date_range(start, end, freq="BME")
In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [97]: ts.index
Out[97]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
'2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
dtype='datetime64[ns]', freq='BME')
In [98]: ts[:5].index
Out[98]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31'],
dtype='datetime64[ns]', freq='BME')
In [99]: ts[::2].index
Out[99]:
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
'2011-09-30', '2011-11-30'],
dtype='datetime64[ns]', freq='2BME')
部分字符串索引
可以将日期和解析为时间戳的字符串作为索引参数传递:
代码语言: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
这种类型的切片也适用于具有DatetimeIndex
的DataFrame
。由于部分字符串选择是一种标签切片的形式,端点将被包括在内。这将包括在包含日期上匹配的时间:
警告
使用单个字符串对DataFrame
行进行索引(例如frame[dtstring]
)已从 pandas 1.2.0 开始弃用(由于不确定是索引行还是选择列而引起的歧义),并将在将来的版本中删除。仍支持使用.loc
进行等效操作(例如frame.loc[dtstring]
)。
In [105]: dft = pd.DataFrame(
.....: np.random.randn(100000, 1),
.....: columns=["A"],
.....: index=pd.date_range("20130101", periods=100000, freq="min"),
.....: )
.....:
In [106]: dft
Out[106]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
In [107]: dft.loc["2013"]
Out[107]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
这从月份的第一个时间开始,并包括该月份的最后日期和时间:
代码语言: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
部分字符串索引也适用于具有MultiIndex
的DataFrame
:
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
对象。
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
。
In [126]: series_second = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
.....: ),
.....: )
.....:
In [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
如果时间戳字符串被视为切片,则也可以使用.loc[]
对DataFrame
进行索引。
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
警告
然而,如果将字符串视为精确匹配,DataFrame
的[]
中的选择将是按列而不是按行进行的,请参见 Indexing Basics。例如,dft_minute['2011-12-31 23:59']
会引发KeyError
,因为'2012-12-31 23:59'
与索引具有相同的分辨率,并且没有这样的列名:
为了始终有明确的选择,无论行是被视为切片还是单个选择,请使用.loc
。
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
还要注意,DatetimeIndex
的分辨率不能低于天。
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
精确索引
如前一节所讨论的,使用部分字符串索引 DatetimeIndex
取决于周期的“准确性”,换句话说,间隔相对于索引分辨率的具体性。相比之下,使用 Timestamp
或 datetime
对象进行索引是精确的,因为这些对象具有确切的含义。这也遵循包括两个端点的语义。
这些 Timestamp
和 datetime
对象具有确切的 小时,分钟
和 秒
,即使它们没有明确指定(它们为 0
)。
In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
没有默认值。
代码语言: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 值,与切片返回任何部分匹配的日期不同:
In [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")
In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)
In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
Freq: W-SUN, dtype: float64
In [140]: ts2["2011-11":"2011-12"]
Out[140]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
2011-12-04 0.046611
2011-12-11 0.059478
2011-12-18 -0.286539
2011-12-25 0.841669
Freq: W-SUN, dtype: float64
即使是复杂的花式索引打破了 DatetimeIndex
的频率规律,也会导致一个 DatetimeIndex
,尽管频率会丢失:
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)
```## 时间/日期组件
有几个时间/日期属性可以从 `Timestamp` 或时间戳集合(如 `DatetimeIndex`)中访问。
| 属性 | 描述 |
| --- | --- |
| 年份 | 日期时间的年份 |
| 月份 | 日期时间的月份 |
| 天数 | 日期时间的天数 |
| 小时数 | 日期时间的小时数 |
| 分钟数 | 日期时间的分钟数 |
| 秒数 | 日期时间的秒数 |
| 微秒 | 日期时间的微秒 |
| 纳秒 | 日期时间的纳秒数 |
| 日期 | 返回日期时间.date(不包含时区信息) |
| 时间 | 返回日期时间.time(不包含时区信息) |
| timetz | 返回带有时区信息的本地时间日期.time |
| 年份中的日期 | 年份的序数日期 |
| 年份中的日期 | 年份的序数日期 |
| 年度周数 | 年份的周序数 |
| 周数 | 年份的周序数 |
| dayofweek | 一周中的日期编号,星期一=0,星期日=6 |
| day_of_week | 一周中的日期编号,星期一=0,星期日=6 |
| 工作日 | 一周中的日期编号,星期一=0,星期日=6 |
| 季度 | 日期的季度:1 月至 3 月=1,4 月至 6 月=2,等等 |
| 月份的天数 | 日期时间的月份的天数 |
| is_month_start | 逻辑指示是否月份的第一天(由频率定义) |
| is_month_end | 逻辑指示是否月份的最后一天(由频率定义) |
| is_quarter_start | 逻辑指示是否季度的第一天(由频率定义) |
| is_quarter_end | 逻辑指示是否季度的最后一天(由频率定义) |
| is_year_start | 逻辑指示是否年份的第一天(由频率定义) |
| is_year_end | 逻辑指示是否年份的最后一天(由频率定义) |
| 是否闰年 | 逻辑指示日期是否属于闰年 |
此外,如果您有一个具有日期时间值的`Series`,则可以通过`.dt`访问器访问这些属性,详细信息请参见.dt 访问器部分。
您可以从 ISO 8601 标准中获取 ISO 年的年、周和日组件:
```py
In [142]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
In [143]: idx.isocalendar()
Out[143]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
In [144]: idx.to_series().dt.isocalendar()
Out[144]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
```## DateOffset 对象
在前面的示例中,频率字符串(例如`'D'`)用于指定定义的频率:
当使用`date_range()`时,了解`DatetimeIndex`中的日期时间是如何间隔的
一个`Period`或`PeriodIndex`的频率
这些频率字符串映射到一个`DateOffset`对象及其子类。`DateOffset`类似于表示时间持续的`Timedelta`,但遵循特定的日历持续规则。例如,`Timedelta`的一天总是增加`datetimes` 24 小时,而`DateOffset`的一天将增加`datetimes`到第二天的同一时间,无论一天代表的是 23、24 还是 25 小时,都由于夏令时而变化。然而,所有一个小时或更小的`DateOffset`子类(`Hour`、`Minute`、`Second`、`Milli`、`Micro`、`Nano`)的行为类似于`Timedelta`,并且遵守绝对时间。
基本的`DateOffset`类似于`dateutil.relativedelta`([relativedelta 文档](https://dateutil.readthedocs.io/en/stable/relativedelta.html)),它将日期时间按指定的日历持续时间进行偏移。可以使用算术运算符(` `)执行偏移。
```py
# This particular day contains a day light savings time transition
In [145]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")
# Respects absolute time
In [146]: ts pd.Timedelta(days=1)
Out[146]: Timestamp('2016-10-30 23:00:00 0200', tz='Europe/Helsinki')
# Respects calendar time
In [147]: ts pd.DateOffset(days=1)
Out[147]: Timestamp('2016-10-31 00:00:00 0200', tz='Europe/Helsinki')
In [148]: friday = pd.Timestamp("2018-01-05")
In [149]: friday.day_name()
Out[149]: 'Friday'
# Add 2 business days (Friday --> Tuesday)
In [150]: two_business_days = 2 * pd.offsets.BDay()
In [151]: friday two_business_days
Out[151]: Timestamp('2018-01-09 00:00:00')
In [152]: (friday two_business_days).day_name()
Out[152]: 'Tuesday'
大多数DateOffsets
都有关联的频率字符串或偏移别名,可以传递给freq
关键字参数。下面列出了可用的日期偏移和相关的频率字符串:
日期偏移 | 频率字符串 | 描述 |
---|---|---|
DateOffset | None | 通用偏移类,默认为绝对 24 小时 |
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()
方法,用于将日期向前或向后移动到相对于偏移的有效偏移日期。例如,业务偏移将把落在周末(星期六和星期日)的日期向前滚动到星期一,因为业务偏移在工作日上运行。
In [153]: ts = pd.Timestamp("2018-01-06 00:00:00")
In [154]: ts.day_name()
Out[154]: 'Saturday'
# BusinessHour's valid offset dates are Monday through Friday
In [155]: offset = pd.offsets.BusinessHour(start="09:00")
# Bring the date to the closest offset date (Monday)
In [156]: offset.rollforward(ts)
Out[156]: Timestamp('2018-01-08 09:00:00')
# Date is brought to the closest offset date first and then the hour is added
In [157]: ts offset
Out[157]: Timestamp('2018-01-08 10:00:00')
这些操作默认保留时间(小时、分钟等)信息。要将时间重置为午夜,请在应用操作之前或之后使用normalize()
(取决于您是否希望在操作中包含时间信息)。
In [158]: ts = pd.Timestamp("2014-01-01 09:00")
In [159]: day = pd.offsets.Day()
In [160]: day ts
Out[160]: Timestamp('2014-01-02 09:00:00')
In [161]: (day ts).normalize()
Out[161]: Timestamp('2014-01-02 00:00:00')
In [162]: ts = pd.Timestamp("2014-01-01 22:00")
In [163]: hour = pd.offsets.Hour()
In [164]: hour ts
Out[164]: Timestamp('2014-01-01 23:00:00')
In [165]: (hour ts).normalize()
Out[165]: Timestamp('2014-01-01 00:00:00')
In [166]: (hour pd.Timestamp("2014-01-01 23:30")).normalize()
Out[166]: Timestamp('2014-01-02 00:00:00')
参数化偏移
创建时,某些偏移可以“参数化”以产生不同的行为。例如,用于生成每周数据的Week
偏移接受weekday
参数,这将导致生成的日期始终落在一周的特定某一天上:
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)
In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)
In [169]: d pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')
In [170]: d pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')
In [171]: (d pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4
In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00')
normalize
选项将对加法和减法有效。
In [173]: d pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')
In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00')
另一个示例是使用特定的结束月份对YearEnd
进行参数化:
In [175]: d pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')
In [176]: d pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00')
使用Series
/ DatetimeIndex
的偏移
可以将偏移与Series
或DatetimeIndex
一起使用,以将偏移应用于每个元素。
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]
如果偏移类直接映射到Timedelta
(Day
、Hour
、Minute
、Second
、Micro
、Milli
、Nano
),则可以像使用Timedelta
一样使用它 - 有关更多示例,请参阅 Timedelta 部分。
In [183]: s - pd.offsets.Day(2)
Out[183]:
0 2011-12-30
1 2011-12-31
2 2012-01-01
dtype: datetime64[ns]
In [184]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))
In [185]: td
Out[185]:
0 3 days
1 3 days
2 3 days
dtype: timedelta64[ns]
In [186]: td pd.offsets.Minute(15)
Out[186]:
0 3 days 00:15:00
1 3 days 00:15:00
2 3 days 00:15:00
dtype: timedelta64[ns]
请注意,某些偏移(例如BQuarterEnd
)没有矢量化实现。它们仍然可以使用,但可能计算速度较慢,并显示PerformanceWarning
。
In [187]: rng pd.offsets.BQuarterEnd()
Out[187]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None)
```### 自定义工作日
`CDay`或`CustomBusinessDay`类提供了一个参数化的`BusinessDay`类,可用于创建考虑本地节假日和本地周末惯例的自定义工作日日历。
作为一个有趣的例子,让我们看看埃及,那里遵守星期五至星期六的周末。
```py
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"
# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
.....: "2012-05-01",
.....: datetime.datetime(2013, 5, 1),
.....: np.datetime64("2014-05-01"),
.....: ]
.....:
In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
.....: holidays=holidays,
.....: weekmask=weekmask_egypt,
.....: )
.....:
In [191]: dt = datetime.datetime(2013, 4, 30)
In [192]: dt 2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00')
让我们映射到工作日名称:
代码语言: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
超出营业时间,则移至下一个营业时间,然后递增。如果结果超出营业时间结束,则剩余的小时将添加到下一个营业日。
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')
你还可以通过关键字指定start
和end
时间。参数必须是一个带有hour:minute
表示或datetime.time
实例的str
。将秒、微秒和纳秒作为营业时间导致ValueError
。
In [212]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))
In [213]: bh
Out[213]: <BusinessHour: bh=11:00-20:00>
In [214]: pd.Timestamp("2014-08-01 13:00") bh
Out[214]: Timestamp('2014-08-01 14:00:00')
In [215]: pd.Timestamp("2014-08-01 09:00") bh
Out[215]: Timestamp('2014-08-01 12:00:00')
In [216]: pd.Timestamp("2014-08-01 18:00") bh
Out[216]: Timestamp('2014-08-01 19:00:00')
将start
时间设置为晚于end
表示午夜营业时间。在这种情况下,营业时间超过午夜并延伸到第二天。有效的营业时间通过它是否从有效的BusinessDay
开始来区分。
In [217]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")
In [218]: bh
Out[218]: <BusinessHour: bh=17:00-09:00>
In [219]: pd.Timestamp("2014-08-01 17:00") bh
Out[219]: Timestamp('2014-08-01 18:00:00')
In [220]: pd.Timestamp("2014-08-01 23:00") bh
Out[220]: Timestamp('2014-08-02 00:00:00')
# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [221]: pd.Timestamp("2014-08-02 04:00") bh
Out[221]: Timestamp('2014-08-02 05:00:00')
# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [222]: pd.Timestamp("2014-08-04 04:00") bh
Out[222]: Timestamp('2014-08-04 18:00:00')
将BusinessHour.rollforward
和rollback
应用于非营业时间会导致下一个营业时间开始或前一天的结束。与其他偏移不同,BusinessHour.rollforward
可能根据定义产生与apply
不同的结果。
这是因为一天的营业时间结束等于下一天的营业时间开始。例如,在默认的营业时间(9:00 - 17:00)下,2014-08-01 17:00
和2014-08-04 09:00
之间没有间隙(0 分钟)。
# This adjusts a Timestamp to business hour edge
In [223]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')
In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessHour() pd.Timestamp('2014-08-01 17:00').
# And it is the same as BusinessHour() pd.Timestamp('2014-08-04 09:00')
In [225]: pd.offsets.BusinessHour() pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')
# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessDay() pd.Timestamp('2014-08-01')
# The result is the same as rollworward because BusinessDay never overlap.
In [227]: pd.offsets.BusinessHour() pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00')
BusinessHour
将星期六和星期日视为假期。要使用任意假期,您可以使用CustomBusinessHour
偏移,如下一节所述。 ### 自定义营业时间
CustomBusinessHour
是BusinessHour
和CustomBusinessDay
的混合体,允许您指定任意假期。CustomBusinessHour
的工作方式与BusinessHour
相同,只是它跳过指定的自定义假期。
In [228]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [229]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [230]: dt = datetime.datetime(2014, 1, 17, 15)
In [231]: dt bhour_us
Out[231]: Timestamp('2014-01-17 16:00:00')
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [232]: dt bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00')
您可以使用BusinessHour
和CustomBusinessDay
支持的关键字参数。
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_date
和 end_date
之间有效的时间戳。如果这些对于给定频率不是有效的时间戳,它们将会滚动到下一个值的 start_date
(分别是 end_date
的前一个值)。### 时期别名
一些字符串别名用于常见的时间序列频率。我们将这些别名称为时期别名。
别名 | 描述 |
---|---|
B | 工作日频率 |
D | 日历日频率 |
W | 每周频率 |
M | 每月频率 |
Q | 季度频率 |
Y | 每年频率 |
h | 每小时频率 |
min | 每分钟频率 |
s | 每秒频率 |
ms | 毫秒 |
us | 微秒 |
ns | 纳秒 |
自版本 2.2.0 起已弃用:别名 A
、H
、T
、S
、L
、U
和 N
已弃用,推荐使用别名 Y
、h
、min
、s
、ms
、us
和 ns
。
结合别名
正如我们之前所见,大多数函数中别名和偏移实例是可互换的:
代码语言: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_range
、bdate_range
的参数,DatetimeIndex
的构造函数,以及 pandas 中各种其他与时间序列相关的函数的参数。
锚定偏移语义
对于那些锚定在特定频率(MonthEnd
,MonthBegin
,WeekEnd
等)开始或结束的偏移量,以下规则适用于向前和向后滚动。
当n
不为 0 时,如果给定日期不在锚点上,则会被吸附到下一个(上一个)锚点,并向前或向后移动|n|-1
步。
In [243]: pd.Timestamp("2014-01-02") pd.offsets.MonthBegin(n=1)
Out[243]: Timestamp('2014-02-01 00:00:00')
In [244]: pd.Timestamp("2014-01-02") pd.offsets.MonthEnd(n=1)
Out[244]: Timestamp('2014-01-31 00:00:00')
In [245]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[245]: Timestamp('2014-01-01 00:00:00')
In [246]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[246]: Timestamp('2013-12-31 00:00:00')
In [247]: pd.Timestamp("2014-01-02") pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2014-05-01 00:00:00')
In [248]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[248]: Timestamp('2013-10-01 00:00:00')
如果给定日期在锚点上,则向前或向后移动|n|
个点。
In [249]: pd.Timestamp("2014-01-01") pd.offsets.MonthBegin(n=1)
Out[249]: Timestamp('2014-02-01 00:00:00')
In [250]: pd.Timestamp("2014-01-31") pd.offsets.MonthEnd(n=1)
Out[250]: Timestamp('2014-02-28 00:00:00')
In [251]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[251]: Timestamp('2013-12-01 00:00:00')
In [252]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[252]: Timestamp('2013-12-31 00:00:00')
In [253]: pd.Timestamp("2014-01-01") pd.offsets.MonthBegin(n=4)
Out[253]: Timestamp('2014-05-01 00:00:00')
In [254]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2013-10-01 00:00:00')
对于n=0
的情况,如果日期在锚点上,则日期不会移动,否则将向前滚动到下一个锚点。
In [255]: pd.Timestamp("2014-01-02") pd.offsets.MonthBegin(n=0)
Out[255]: Timestamp('2014-02-01 00:00:00')
In [256]: pd.Timestamp("2014-01-02") pd.offsets.MonthEnd(n=0)
Out[256]: Timestamp('2014-01-31 00:00:00')
In [257]: pd.Timestamp("2014-01-01") pd.offsets.MonthBegin(n=0)
Out[257]: Timestamp('2014-01-01 00:00:00')
In [258]: pd.Timestamp("2014-01-31") pd.offsets.MonthEnd(n=0)
Out[258]: Timestamp('2014-01-31 00:00:00')
节假日/节假日日历
假期和日历提供了一种简单的方式来定义与 CustomBusinessDay
一起使用的假日规则,或者在需要预定义一组假日的其他分析中使用。AbstractHolidayCalendar
类提供了返回假日列表所��的所有方法,只需在特定假日日历类中定义 rules
即可。此外,start_date
和 end_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
或添加到 datetime
或 Timestamp
对象中。
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')
范围由 AbstractHolidayCalendar
的 start_date
和 end_date
类属性定义。默认值如下所示。
In [269]: AbstractHolidayCalendar.start_date
Out[269]: Timestamp('1970-01-01 00:00:00')
In [270]: AbstractHolidayCalendar.end_date
Out[270]: Timestamp('2200-12-31 00:00:00')
这些日期可以通过设置属性为 datetime/Timestamp/string 来覆盖。
代码语言: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
提供了一个简单的接口来创建组合日历或具有额外规则的日历。
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay
In [275]: cal = get_calendar("ExampleCalendar")
In [276]: cal.rules
Out[276]:
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)]
In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)
In [278]: new_cal.rules
Out[278]:
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO( 1)>),
Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO( 2)>)]
```## 与时间序列相关的实例方法
### 移动 / 拖延
有时可能需要在时间序列中向前或向后移动值。用于此目的的方法是 `shift()`,可用于所有 pandas 对象。
```py
In [279]: ts = pd.Series(range(len(rng)), index=rng)
In [280]: ts = ts[:5]
In [281]: ts.shift(1)
Out[281]:
2012-01-01 NaN
2012-01-02 0.0
2012-01-03 1.0
Freq: D, dtype: float64
shift
方法接受一个 freq
参数,该参数可以接受一个 DateOffset
类或其他类似于 timedelta
的对象,也可以是一个 偏移别名。
当指定 freq
时,shift
方法会更改索引中的所有日期,而不是更改数据和索引的对齐方式:
In [282]: ts.shift(5, freq="D")
Out[282]:
2012-01-06 0
2012-01-07 1
2012-01-08 2
Freq: D, dtype: int64
In [283]: ts.shift(5, freq=pd.offsets.BDay())
Out[283]:
2012-01-06 0
2012-01-09 1
2012-01-10 2
dtype: int64
In [284]: ts.shift(5, freq="BME")
Out[284]:
2012-05-31 0
2012-05-31 1
2012-05-31 2
dtype: int64
请注意,当指定freq
时,由于数据未重新对齐,因此前导条目不再是 NaN。
频率转换
更改频率的主要函数是asfreq()
方法。对于DatetimeIndex
,这基本上只是一个薄的、但方便的reindex()
的包装器,它生成一个date_range
并调用reindex
。
In [285]: dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())
In [286]: ts = pd.Series(np.random.randn(3), index=dr)
In [287]: ts
Out[287]:
2010-01-01 1.494522
2010-01-06 -0.778425
2010-01-11 -0.253355
Freq: 3B, dtype: float64
In [288]: ts.asfreq(pd.offsets.BDay())
Out[288]:
2010-01-01 1.494522
2010-01-04 NaN
2010-01-05 NaN
2010-01-06 -0.778425
2010-01-07 NaN
2010-01-08 NaN
2010-01-11 -0.253355
Freq: B, dtype: float64
asfreq
提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。
In [289]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]:
2010-01-01 1.494522
2010-01-04 1.494522
2010-01-05 1.494522
2010-01-06 -0.778425
2010-01-07 -0.778425
2010-01-08 -0.778425
2010-01-11 -0.253355
Freq: B, dtype: float64
向前/向后填充
与asfreq
和reindex
相关的是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 可用的任何内置方法都可以作为返回对象的方法使用,包括sum
、mean
、std
、sem
、max
、min
、median
、first
、last
、ohlc
:
In [293]: ts.resample("5Min").mean()
Out[293]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [294]: ts.resample("5Min").ohlc()
Out[294]:
open high low close
2012-01-01 308 460 9 205
In [295]: ts.resample("5Min").max()
Out[295]:
2012-01-01 460
Freq: 5min, dtype: int64
对于降采样,closed
可以设置为‘left’或‘right’来指定区间的哪一端是闭合的:
In [296]: ts.resample("5Min", closed="right").mean()
Out[296]:
2011-12-31 23:55:00 308.000000
2012-01-01 00:00:00 250.454545
Freq: 5min, dtype: float64
In [297]: ts.resample("5Min", closed="left").mean()
Out[297]:
2012-01-01 251.03
Freq: 5min, dtype: float64
像label
这样的参数用于操作生成的标签。label
指定结果是用区间的开始还是结束标记的。
In [298]: ts.resample("5Min").mean() # by default label='left'
Out[298]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [299]: ts.resample("5Min", label="left").mean()
Out[299]:
2012-01-01 251.03
Freq: 5min, dtype: float64
警告
对于所有频率偏移,默认值为‘left’,除了‘ME’、‘YE’、‘QE’、‘BME’、‘BYE’、‘BQE’和‘W’,它们的默认值都为‘right’。
这可能会意外地导致向前查看,其中稍后时间的值被拉回到先前时间,如下例中使用BusinessDay
频率:
In [300]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()
In [301]: s.iloc[2] = pd.NaT
In [302]: s.dt.day_name()
Out[302]:
2000-01-01 Saturday
2000-01-02 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: D, dtype: object
# default: label='left', closed='left'
In [303]: s.resample("B").last().dt.day_name()
Out[303]:
1999-12-31 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: B, dtype: object
注意星期日的值如何被拉回到前一个星期五。要获得星期日的值被推到星期一的行为,请改用
代码语言: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
参数以插值填补创建的间隙:
# 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_method
为None
,那么中间值将被填充为NaN
。
由于resample
是基于时间的分组,以下是一种有效重新采样仅不全为NaN
的组的方法。
In [308]: rng = pd.date_range("2014-1-1", periods=100, freq="D") pd.Timedelta("1s")
In [309]: ts = pd.Series(range(100), index=rng)
如果我们想要重新采样到系列的完整范围:
代码语言: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
进行重新采样,默认情况下将对所有列使用相同的函数。
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
:
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
在重新采样的DataFrame
上,您可以传递一个函数列表以应用于每列,这将产生一个带有分层索引的聚合结果:
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
通过将字典传递给aggregate
,您可以对DataFrame
的列应用不同的聚合:
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:
代码语言: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
关键字。
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
同样,如果您希望按照MultiIndex
的日期时间级别重新采样,则可以将其名称或位置传递给level
关键字。
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
```### 通过组进行迭代
有了`Resampler`对象,通过分组数据进行迭代非常自然,并且类似于[`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"):
```py
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="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__
获取更多信息。### 使用origin
或offset
来调整箱子的起始点
分组的箱子根据时间序列起始点的当天开始时间进行调整。这适用于是天数的倍数(如30D
)或能够均匀分割一天的频率(如90s
或1min
)。这可能会导致某些不符合此标准的频率出现不一致。要更改此行为,可以使用参数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'
之后的结果并不相同:
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
在这里,我们可以看到,将origin
设置为'epoch'
时,根据时间序列的起始点,'2000-10-02 00:00:00'
之后的结果是相同的:
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
如果需要,可以使用自定义时间戳作为origin
:
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
如果需要,您可以使用offset
Timedelta 调整箱子,该 Timedelta 将添加到默认的origin
中。对于这个时间序列,这两个示例是等效的:
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
注意最后一个示例中对origin
使用'start'
而不是origin
。在这种情况下,origin
将被设置为时间序列的第一个值。