Pandas-2-2-中文文档-十三-
Pandas 2.2 中文文档(十三)
窗口操作
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
窗口由从当前观察值向后查看窗口长度组成。上述结果可以通过对数据的以下窗口分区求和来得出:
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 版本起) |
如上所述,一些操作支持根据时间偏移量指定窗口:
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
In [13]: online_ewm = df.head(2).ewm(0.5).online()
In [14]: online_ewm.mean()
Out[14]:
0 1 2
0 1.00 2.00 0.60
1 1.75 2.75 0.45
In [15]: online_ewm.mean(update=df.tail(1))
Out[15]:
0 1 2
3 3.307692 4.307692 0.623077
所有窗口操作都支持一个min_periods
参数,该参数规定窗口必须具有的非np.nan
值的最小数量;否则,结果值为np.nan
。对于基于时间的窗口,默认值为 1,对于固定窗口,默认为window
。
In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])
In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]:
0 NaN
1 1.0
2 3.0
3 3.0
4 2.0
5 3.0
dtype: float64
In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]:
0 NaN
1 NaN
2 3.0
3 3.0
4 NaN
5 NaN
dtype: float64
# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
dtype: float64
此外,所有窗口操作都支持aggregate
方法,用于返回应用于窗口的多个聚合的结果。
In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})
In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]:
A B
sum mean std sum mean std
0 0.0 0.0 NaN 10.0 10.0 NaN
1 1.0 0.5 0.707107 21.0 10.5 0.707107
2 3.0 1.0 1.000000 33.0 11.0 1.000000
3 6.0 1.5 1.290994 46.0 11.5 1.290994
4 10.0 2.0 1.581139 60.0 12.0 1.581139
``` ## 滚动窗口
通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。
```py
In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']
In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))
In [24]: s
Out[24]:
2020-01-01 0
2020-01-03 1
2020-01-04 2
2020-01-05 3
2020-01-29 4
dtype: int64
# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]:
2020-01-01 NaN
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 7.0
dtype: float64
# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]:
2020-01-01 0.0
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 4.0
dtype: float64
查看所有支持的聚合函数,请参阅滚动窗口函数。
居中窗口
默认情况下,标签设置为窗口的右边缘,但可以使用center
关键字将标签设置��中心。
In [27]: s = pd.Series(range(10))
In [28]: s.rolling(window=5).mean()
Out[28]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [29]: s.rolling(window=5, center=True).mean()
Out[29]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
5 5.0
6 6.0
7 7.0
8 NaN
9 NaN
dtype: float64
这也可以应用于类似于日期时间的索引。
1.3.0 版本中新增。
In [30]: df = pd.DataFrame(
....: {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
....: )
....:
In [31]: df
Out[31]:
A
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
In [32]: df.rolling("2D", center=False).mean()
Out[32]:
A
2020-01-01 0.0
2020-01-02 0.5
2020-01-03 1.5
2020-01-04 2.5
2020-01-05 3.5
In [33]: df.rolling("2D", center=True).mean()
Out[33]:
A
2020-01-01 0.5
2020-01-02 1.5
2020-01-03 2.5
2020-01-04 3.5
2020-01-05 4.0
``` ### 滚动窗口端点
可以使用`closed`参数指定在滚动窗口计算中包含间隔端点的方式:
| 值 | 行为 |
| --- | --- |
| `'right'` | 关闭右端点 |
| `'left'` | 关闭左端点 |
| `'both'` | 关闭两个端点 |
| `'neither'` | 开放端点 |
例如,将右端点保持开放在许多需要确保没有来自当前信息到过去信息的污染的问题中非常有用。这允许滚动窗口计算统计数据“直到那个时间点”,但不包括那个时间点。
```py
In [34]: df = pd.DataFrame(
....: {"x": 1},
....: index=[
....: pd.Timestamp("20130101 09:00:01"),
....: pd.Timestamp("20130101 09:00:02"),
....: pd.Timestamp("20130101 09:00:03"),
....: pd.Timestamp("20130101 09:00:04"),
....: pd.Timestamp("20130101 09:00:06"),
....: ],
....: )
....:
In [35]: df["right"] = df.rolling("2s", closed="right").x.sum() # default
In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()
In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()
In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()
In [39]: df
Out[39]:
x right both left neither
2013-01-01 09:00:01 1 1.0 1.0 NaN NaN
2013-01-01 09:00:02 1 2.0 2.0 1.0 1.0
2013-01-01 09:00:03 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:04 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:06 1 1.0 2.0 1.0 NaN
``` ### 自定义窗口滚动
除了接受整数或偏移作为`window`参数外,`rolling`还接受一个`BaseIndexer`子类,允许用户定义用于计算窗口边界的自定义方法。`BaseIndexer`子类将需要定义一个`get_window_bounds`方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,`num_values`、`min_periods`、`center`、`closed`和`step`将自动传递给`get_window_bounds`,定义的方法必须始终接受这些参数。
例如,如果我们有以下`DataFrame`
```py
In [40]: use_expanding = [True, False, True, False, True]
In [41]: use_expanding
Out[41]: [True, False, True, False, True]
In [42]: df = pd.DataFrame({"values": range(5)})
In [43]: df
Out[43]:
values
0 0
1 1
2 2
3 3
4 4
如果我们想要使用一个扩展窗口,其中use_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
我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现这一点,如下面的示例所示:
In [57]: df = pd.DataFrame(
....: data=[
....: [pd.Timestamp("2018-01-01 00:00:00"), 100],
....: [pd.Timestamp("2018-01-01 00:00:01"), 101],
....: [pd.Timestamp("2018-01-01 00:00:03"), 103],
....: [pd.Timestamp("2018-01-01 00:00:04"), 111],
....: ],
....: columns=["time", "value"],
....: ).set_index("time")
....:
In [58]: df
Out[58]:
value
time
2018-01-01 00:00:00 100
2018-01-01 00:00:01 101
2018-01-01 00:00:03 103
2018-01-01 00:00:04 111
In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]
In [60]: reversed_df
Out[60]:
value
time
2018-01-01 00:00:00 201.0
2018-01-01 00:00:01 101.0
2018-01-01 00:00:03 214.0
2018-01-01 00:00:04 111.0
``` ### 滚动应用
`apply()`函数接受额外的`func`参数并执行通用的滚动计算。`func`参数应该是一个从 ndarray 输入产生单个值的函数。`raw`指定窗口是作为`Series`对象(`raw=False`)还是 ndarray 对象(`raw=True`)。
```py
In [61]: def mad(x):
....: return np.fabs(x - x.mean()).mean()
....:
In [62]: s = pd.Series(range(10))
In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]:
0 NaN
1 NaN
2 NaN
3 1.0
4 1.0
5 1.0
6 1.0
7 1.0
8 1.0
9 1.0
dtype: float64
``` ### Numba 引擎
此外,如果安装了 [Numba](https://numba.pydata.org/) 作为可选依赖项,`apply()` 可以利用 Numba。通过指定 `engine='numba'` 和 `engine_kwargs` 参数(`raw` 也必须设置为 `True`),可以使用 Numba 执行应用聚合。有关参数的一般用法和性能考虑,请参见使用 Numba 提升性能。
Numba 将应用于可能的两个例程:
1. 如果 `func` 是一个标准的 Python 函数,引擎将[JIT](https://numba.pydata.org/numba-doc/latest/user/overview.html)传递的函数。`func` 也可以是一个已经 JIT 的函数,在这种情况下,引擎将不会再次 JIT 函数。
1. 引擎将对应用函数应用于每个窗口的 for 循环进行 JIT。
`engine_kwargs` 参数是一个关键字参数字典,将传递给[numba.jit 装饰器](https://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#numba.jit)。这些关键字参数将应用于*传递的函数*(如果是标准的 Python 函数)和对每个窗口进行的应用循环。
版本 1.3.0 中的新功能。
`mean`、`median`、`max`、`min` 和 `sum` 也支持 `engine` 和 `engine_kwargs` 参数。### 二进制窗口函数
`cov()` 和 `corr()` 可以计算关于两个`Series`或任何`DataFrame`/`Series`或`DataFrame`/`DataFrame`组合的移动窗口统计。在每种情况下的行为如下:
+ 两个`Series`:计算配对的统计量。
+ `DataFrame`/`Series`:计算 DataFrame 的每一列与传递的 Series 的统计量,从而返回一个 DataFrame。
+ `DataFrame`/`DataFrame`:默认情况下计算匹配列名的统计量,返回一个 DataFrame。如果传递了关键字参数 `pairwise=True`,则为每对列计算统计量,返回一个具有值为相关日期的`DataFrame`的`MultiIndex`(请参见下一节)。
例如:
```py
In [64]: df = pd.DataFrame(
....: np.random.randn(10, 4),
....: index=pd.date_range("2020-01-01", periods=10),
....: columns=["A", "B", "C", "D"],
....: )
....:
In [65]: df = df.cumsum()
In [66]: df2 = df[:4]
In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]:
A B C D
2020-01-01 NaN NaN NaN NaN
2020-01-02 -1.0 1.0 -1.0 1.0
2020-01-03 1.0 1.0 1.0 -1.0
2020-01-04 -1.0 1.0 1.0 -1.0
```### 计算滚动配对协方差和相关性
在金融数据分析和其他领域,常常需要计算一组时间序列的协方差和相关矩阵。通常也对移动窗口的协方差和相关矩阵感兴趣。可以通过传递`pairwise`关键字参数来实现这一点,在`DataFrame`输入的情况下,将产生一个多级索引的`DataFrame`,其`index`是相关日期。在单个 DataFrame 参数的情况下,甚至可以省略`pairwise`参数:
注意
缺失值将被忽略,并且每个条目将使用成对完整观测值计算。
假设缺失数据是随机缺失的,这将导致对协方差矩阵的估计是无偏的。然而,对于许多应用程序,这种估计可能不可接受,因为估计的协方差矩阵不能保证是半正定的。这可能导致估计的相关性具有绝对值大于一,和/或不可逆的协方差矩阵。有关更多详细信息,请参阅[协方差矩阵的估计](https://en.wikipedia.org/w/index.php?title=Estimation_of_covariance_matrices)。
```py
In [68]: covs = (
....: df[["B", "C", "D"]]
....: .rolling(window=4)
....: .cov(df[["A", "B", "C"]], pairwise=True)
....: )
....:
In [69]: covs
Out[69]:
B C D
2020-01-01 A NaN NaN NaN
B NaN NaN NaN
C NaN NaN NaN
2020-01-02 A NaN NaN NaN
B NaN NaN NaN
... ... ... ...
2020-01-09 B 0.342006 0.230190 0.052849
C 0.230190 1.575251 0.082901
2020-01-10 A -0.333945 0.006871 -0.655514
B 0.649711 0.430860 0.469271
C 0.430860 0.829721 0.055300
[30 rows x 3 columns]
```## 加权窗口
`.rolling`中的`win_type`参数生成了常用于滤波和频谱估计的加权窗口。`win_type`必须是一个对应于[scipy.signal 窗口函数](https://docs.scipy.org/doc/scipy/reference/signal.windows.html#module-scipy.signal.windows)的字符串。必须安装 Scipy 才能使用这些窗口,并且必须在聚合函数中指定 Scipy 窗口方法所需的补充参数。
```py
In [70]: s = pd.Series(range(10))
In [71]: s.rolling(window=5).mean()
Out[71]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
对于所有支持的聚合函数,请参阅加权窗口函数。## 扩展窗口
扩展窗口产生一个聚合统计量的值,其中包含截至该时间点的所有可用数据。由于这些计算是滚动统计的特例,因此在 pandas 中实现了以下两个调用是等效的:
In [74]: df = pd.DataFrame(range(5))
In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]:
0
0 0.0
1 0.5
2 1.0
3 1.5
4 2.0
In [76]: df.expanding(min_periods=1).mean()
Out[76]:
0
0 0.0
1 0.5
2 1.0
3 1.5
4 2.0
对于所有支持的聚合函数,请参阅扩展窗口函数。## 指数加权窗口
指数加权窗口类似于扩展窗口,但是每个先前点相对于当前点按指数方式加权。
一般来说,加权移动平均值计算如下
[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},]
其中(x_t)是输入,(y_t)是结果,(w_i)是权重。
对于所有支持的聚合函数,请参阅指数加权窗口函数。
EW 函数支持指数权重的两个变体。默认情况下,adjust=True
使用权重(w_i = (1 - \alpha)^i),得到
[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)² + ... + (1 - \alpha)^t}]
当指定adjust=False
时,移动平均值计算如下
[]
这等价于使用权重
[]
注意
有时这些方程以(\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)的几何级数,我们有
[]
这与上面的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)或半衰期:
[]
必须精确指定跨度、质心、半衰期和alpha中的一个来使用 EW 函数:
-
跨度对应于通常称为“N 天 EW 移动平均”的概念。
-
质心具有更具物理意义的解释,可以按照跨度来考虑:(c = (s - 1) / 2)。
-
半衰期是指数权重减少到一半所需的时间段。
-
Alpha直接指定平滑因子。
您还可以指定halflife
,以时间间隔可转换的单位来指定观察值衰减到一半所需的时间,同时指定一系列times
。
In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
In [78]: df
Out[78]:
B
0 0.0
1 1.0
2 2.0
3 NaN
4 4.0
In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]
In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]:
B
0 0.000000
1 0.585786
2 1.523889
3 1.523889
4 3.233686
以下公式用于计算具有时间输入向量的指数加权均值:
[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},]
指数加权窗口也有一个ignore_na
参数,用于确定中间空值如何影响权重的计算。当ignore_na=False
(默认)时,权重是基于绝对位置计算的,因此中间的空值会影响结果。当ignore_na=True
时,通过忽略中间的空值来计算权重。例如,假设adjust=True
,如果ignore_na=False
,则3, NaN, 5
的加权平均值将被计算为
[\frac{(1-\alpha)² \cdot 3 + 1 \cdot 5}{(1-\alpha)² + 1}.]
当ignore_na=True
时,加权平均值将被计算为
[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.]
var()
、std()
和cov()
函数有一个bias
参数,指定结果是否应包含有偏或无偏的统计数据。例如,如果bias=True
,则ewmvar(x)
被计算为ewmvar(x) = ewma(x**2) - ewma(x)**2
;而如果bias=False
(默认),有偏方差统计数据将通过去偏因子进行缩放
[\frac{\left(\sum_{i=0}^t w_i\right)²}{\left(\sum_{i=0}^t w_i\right)² - \sum_{i=0}^t w_i²}.]
(对于(w_i = 1),这将减少到通常的(N / (N - 1))因子,其中(N = t + 1)。)有关更多详细信息,请参阅维基百科上的加权样本方差。 ## 概述
pandas 支持 4 种类型的窗口操作:
-
滚动窗口:对数值进行通用的固定或可变滑动窗口。
-
加权窗口:由
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 起) |
如上所述,一些操作支持基于时间偏移量指定窗口:
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
In [13]: online_ewm = df.head(2).ewm(0.5).online()
In [14]: online_ewm.mean()
Out[14]:
0 1 2
0 1.00 2.00 0.60
1 1.75 2.75 0.45
In [15]: online_ewm.mean(update=df.tail(1))
Out[15]:
0 1 2
3 3.307692 4.307692 0.623077
所有窗口操作都支持min_periods
参数,该参数规定窗口必须具有的非np.nan
值的最小数量;否则,结果值为np.nan
。对于基于时间的窗口,默认值为 1,对于固定窗口,默认值为window
。
In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])
In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]:
0 NaN
1 1.0
2 3.0
3 3.0
4 2.0
5 3.0
dtype: float64
In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]:
0 NaN
1 NaN
2 3.0
3 3.0
4 NaN
5 NaN
dtype: float64
# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
dtype: float64
此外,所有窗口操作都支持aggregate
方法,用于返回应用于窗口的多个聚合的结果。
In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})
In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]:
A B
sum mean std sum mean std
0 0.0 0.0 NaN 10.0 10.0 NaN
1 1.0 0.5 0.707107 21.0 10.5 0.707107
2 3.0 1.0 1.000000 33.0 11.0 1.000000
3 6.0 1.5 1.290994 46.0 11.5 1.290994
4 10.0 2.0 1.581139 60.0 12.0 1.581139
滚动窗口
通用滚动窗口支持将窗口指定为固定数量的观测值或基于偏移量的可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。
In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']
In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))
In [24]: s
Out[24]:
2020-01-01 0
2020-01-03 1
2020-01-04 2
2020-01-05 3
2020-01-29 4
dtype: int64
# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]:
2020-01-01 NaN
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 7.0
dtype: float64
# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]:
2020-01-01 0.0
2020-01-03 1.0
2020-01-04 3.0
2020-01-05 5.0
2020-01-29 4.0
dtype: float64
有关所有支持的聚合函数,请参见滚动窗口函数。
居中窗口
默认情况下,标签设置为窗口的右边缘,但可以使用center
关键字将标签设置为中心。
In [27]: s = pd.Series(range(10))
In [28]: s.rolling(window=5).mean()
Out[28]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [29]: s.rolling(window=5, center=True).mean()
Out[29]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
5 5.0
6 6.0
7 7.0
8 NaN
9 NaN
dtype: float64
这也可以应用于类似日期时间的索引。
1.3.0 版本中的新功能。
In [30]: df = pd.DataFrame(
....: {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
....: )
....:
In [31]: df
Out[31]:
A
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
In [32]: df.rolling("2D", center=False).mean()
Out[32]:
A
2020-01-01 0.0
2020-01-02 0.5
2020-01-03 1.5
2020-01-04 2.5
2020-01-05 3.5
In [33]: df.rolling("2D", center=True).mean()
Out[33]:
A
2020-01-01 0.5
2020-01-02 1.5
2020-01-03 2.5
2020-01-04 3.5
2020-01-05 4.0
``` ### 滚动窗口端点
可以使用`closed`参数指定在滚动窗口计算中包含间隔端点:
| 值 | 行为 |
| --- | --- |
| `'right'` | 关闭右端点 |
| `'left'` | 关闭左端点 |
| `'both'` | 关闭两个端点 |
| `'neither'` | 开放端点 |
例如,正确地开放右端点在许多需要确保当前信息不会影响过去信息的问题中非常有用。这允许滚动窗口计算统计数据“直到那个时间点”,但不包括那个时间点。
```py
In [34]: df = pd.DataFrame(
....: {"x": 1},
....: index=[
....: pd.Timestamp("20130101 09:00:01"),
....: pd.Timestamp("20130101 09:00:02"),
....: pd.Timestamp("20130101 09:00:03"),
....: pd.Timestamp("20130101 09:00:04"),
....: pd.Timestamp("20130101 09:00:06"),
....: ],
....: )
....:
In [35]: df["right"] = df.rolling("2s", closed="right").x.sum() # default
In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()
In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()
In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()
In [39]: df
Out[39]:
x right both left neither
2013-01-01 09:00:01 1 1.0 1.0 NaN NaN
2013-01-01 09:00:02 1 2.0 2.0 1.0 1.0
2013-01-01 09:00:03 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:04 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:06 1 1.0 2.0 1.0 NaN
``` ### 自定义窗口滚动
除了接受整数或偏移作为`window`参数外,`rolling`还接受一个`BaseIndexer`子类,允许用户定义计算窗口边界的自定义方法。`BaseIndexer`子类需要定义一个`get_window_bounds`方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,`num_values`、`min_periods`、`center`、`closed`和`step`将自动传递给`get_window_bounds`,定义的方法必须始终接受这些参数。
例如,如果我们有以下`DataFrame`
```py
In [40]: use_expanding = [True, False, True, False, True]
In [41]: use_expanding
Out[41]: [True, False, True, False, True]
In [42]: df = pd.DataFrame({"values": range(5)})
In [43]: df
Out[43]:
values
0 0
1 1
2 2
3 3
4 4
如果我们想要使用一个扩展窗口,其中use_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
我们还可以通过使用切片、应用滚动聚合,然后翻转结果来实现,如下面的示例所示:
In [57]: df = pd.DataFrame(
....: data=[
....: [pd.Timestamp("2018-01-01 00:00:00"), 100],
....: [pd.Timestamp("2018-01-01 00:00:01"), 101],
....: [pd.Timestamp("2018-01-01 00:00:03"), 103],
....: [pd.Timestamp("2018-01-01 00:00:04"), 111],
....: ],
....: columns=["time", "value"],
....: ).set_index("time")
....:
In [58]: df
Out[58]:
value
time
2018-01-01 00:00:00 100
2018-01-01 00:00:01 101
2018-01-01 00:00:03 103
2018-01-01 00:00:04 111
In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]
In [60]: reversed_df
Out[60]:
value
time
2018-01-01 00:00:00 201.0
2018-01-01 00:00:01 101.0
2018-01-01 00:00:03 214.0
2018-01-01 00:00:04 111.0
``` ### 滚动应用
`apply()` 函数接受额外的 `func` 参数并执行通用的滚动计算。`func` 参数应该是一个从 ndarray 输入产生单个值的函数。`raw` 指定窗口是作为 `Series` 对象 (`raw=False`) 还是 ndarray 对象 (`raw=True`)。
```py
In [61]: def mad(x):
....: return np.fabs(x - x.mean()).mean()
....:
In [62]: s = pd.Series(range(10))
In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]:
0 NaN
1 NaN
2 NaN
3 1.0
4 1.0
5 1.0
6 1.0
7 1.0
8 1.0
9 1.0
dtype: float64
``` ### Numba 引擎
此外,如果安装了 Numba 作为可选依赖项,`apply()` 可以利用 [Numba](https://numba.pydata.org/)。通过指定 `engine='numba'` 和 `engine_kwargs` 参数(`raw` 也必须设置为 `True`),可以使用 Numba 执行 apply 聚合。参见使用 Numba 提升性能以获取参数的一般用法和性能考虑。
Numba 将应用于可能的两个例程:
1. 如果 `func` 是标准 Python 函数,则引擎将[JIT](https://numba.pydata.org/numba-doc/latest/user/overview.html)传入的函数。`func` 也可以是一个已经 JIT 的函数,此时引擎将不会再次 JIT 函数。
1. 引擎将 JIT 应用于将 apply 函数应用于每个窗口的循环。
`engine_kwargs` 参数是一个关键字参数字典,将传递给 [numba.jit 装饰器](https://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#numba.jit)。这些关键字参数将应用于*传入的函数*(如果是标准 Python 函数)和应用于每个窗口的 apply 循环。
新版本 1.3.0 中新增功能。
`mean`、`median`、`max`、`min` 和 `sum` 也支持 `engine` 和 `engine_kwargs` 参数。### 二进制窗口函数
`cov()` 和 `corr()` 可以计算关于两个 `Series` 或任何 `DataFrame`/`Series` 或 `DataFrame`/`DataFrame` 的移动窗口统计信息。在每种情况下的行为如下:
+ 两个 `Series`:计算配对的统计信息。
+ `DataFrame`/`Series`:使用传入的 Series 计算 DataFrame 的每一列的统计信息,从而返回一个 DataFrame。
+ `DataFrame`/`DataFrame`:默认情况下,计算匹配列名的统计量,返回一个 DataFrame。如果传递了关键字参数`pairwise=True`,则为每对列计算统计量,返回一个具有`MultiIndex`的`DataFrame`,其值是相关日期(请参见下一节)。
例如:
```py
In [64]: df = pd.DataFrame(
....: np.random.randn(10, 4),
....: index=pd.date_range("2020-01-01", periods=10),
....: columns=["A", "B", "C", "D"],
....: )
....:
In [65]: df = df.cumsum()
In [66]: df2 = df[:4]
In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]:
A B C D
2020-01-01 NaN NaN NaN NaN
2020-01-02 -1.0 1.0 -1.0 1.0
2020-01-03 1.0 1.0 1.0 -1.0
2020-01-04 -1.0 1.0 1.0 -1.0
``` ### 计算滚动成对协方差和相关性
在金融数据分析和其他领域,通常会为一组时间序列计算协方差和相关性矩阵。通常还对移动窗口协方差和相关性矩阵感兴趣。可以通过传递`pairwise`关键字参数来实现这一点,在`DataFrame`输入的情况下,将产生一个具有`index`为相关日期的多重索引的`DataFrame`。在单个 DataFrame 参数的情况下,甚至可以省略`pairwise`参数:
注意
缺失值将被忽略,并且每个条目都是使用成对完整观察值计算的。
假设缺失数据是随机缺失的,这将导致协方差矩阵的估计是无偏的。然而,对于许多应用程序来说,这种估计可能不可接受,因为估计的协方差矩阵不能保证是正半定的。这可能导致估计的相关性具有绝对值大于一,和/或一个不可逆的协方差矩阵。更多细节请参见[协方差矩阵的估计](https://en.wikipedia.org/w/index.php?title=Estimation_of_covariance_matrices)。
```py
In [68]: covs = (
....: df[["B", "C", "D"]]
....: .rolling(window=4)
....: .cov(df[["A", "B", "C"]], pairwise=True)
....: )
....:
In [69]: covs
Out[69]:
B C D
2020-01-01 A NaN NaN NaN
B NaN NaN NaN
C NaN NaN NaN
2020-01-02 A NaN NaN NaN
B NaN NaN NaN
... ... ... ...
2020-01-09 B 0.342006 0.230190 0.052849
C 0.230190 1.575251 0.082901
2020-01-10 A -0.333945 0.006871 -0.655514
B 0.649711 0.430860 0.469271
C 0.430860 0.829721 0.055300
[30 rows x 3 columns]
``` ### 居中窗口
默认情况下,标签被设置在窗口的右边缘,但是有一个`center`关键字可用,因此标签可以设置在中心位置。
```py
In [27]: s = pd.Series(range(10))
In [28]: s.rolling(window=5).mean()
Out[28]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [29]: s.rolling(window=5, center=True).mean()
Out[29]:
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
5 5.0
6 6.0
7 7.0
8 NaN
9 NaN
dtype: float64
这也可以应用于类似日期时间的索引。
版本 1.3.0 中的新功能。
In [30]: df = pd.DataFrame(
....: {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
....: )
....:
In [31]: df
Out[31]:
A
2020-01-01 0
2020-01-02 1
2020-01-03 2
2020-01-04 3
2020-01-05 4
In [32]: df.rolling("2D", center=False).mean()
Out[32]:
A
2020-01-01 0.0
2020-01-02 0.5
2020-01-03 1.5
2020-01-04 2.5
2020-01-05 3.5
In [33]: df.rolling("2D", center=True).mean()
Out[33]:
A
2020-01-01 0.5
2020-01-02 1.5
2020-01-03 2.5
2020-01-04 3.5
2020-01-05 4.0
滚动窗口端点
在滚动窗口计算中包含区间端点可以通过closed
参数指定:
值 | 行为 |
---|---|
'right' |
关闭右端点 |
'left' |
关闭左端点 |
'both' |
关闭两个端点 |
'neither' |
开放端点 |
例如,将右端点保持开放在许多需要确保没有来自当前信息到过去信息的污染的问题中是有用的。这允许滚动窗口计算统计量“直到那个时间点”,但不包括那个时间点。
In [34]: df = pd.DataFrame(
....: {"x": 1},
....: index=[
....: pd.Timestamp("20130101 09:00:01"),
....: pd.Timestamp("20130101 09:00:02"),
....: pd.Timestamp("20130101 09:00:03"),
....: pd.Timestamp("20130101 09:00:04"),
....: pd.Timestamp("20130101 09:00:06"),
....: ],
....: )
....:
In [35]: df["right"] = df.rolling("2s", closed="right").x.sum() # default
In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()
In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()
In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()
In [39]: df
Out[39]:
x right both left neither
2013-01-01 09:00:01 1 1.0 1.0 NaN NaN
2013-01-01 09:00:02 1 2.0 2.0 1.0 1.0
2013-01-01 09:00:03 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:04 1 2.0 3.0 2.0 1.0
2013-01-01 09:00:06 1 1.0 2.0 1.0 NaN
自定义窗口滚动
除了接受整数或偏移作为window
参数外,rolling
还接受一个BaseIndexer
子类,允许用户定义用于计算窗口边界的自定义方法。BaseIndexer
子类将需要定义一个get_window_bounds
方法,返回两个数组的元组,第一个是窗口的起始索引,第二个是窗口的结束索引。此外,num_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
通过使用切片、应用滚动聚合,然后翻转结果,我们也可以实现这一点,如下例所示:
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
,其值是相关日期(请参阅下一节)。
例如:
In [64]: df = pd.DataFrame(
....: np.random.randn(10, 4),
....: index=pd.date_range("2020-01-01", periods=10),
....: columns=["A", "B", "C", "D"],
....: )
....:
In [65]: df = df.cumsum()
In [66]: df2 = df[:4]
In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]:
A B C D
2020-01-01 NaN NaN NaN NaN
2020-01-02 -1.0 1.0 -1.0 1.0
2020-01-03 1.0 1.0 1.0 -1.0
2020-01-04 -1.0 1.0 1.0 -1.0
计算滚动成对协方差和相关性
在金融数据分析和其他领域,通常会为一组时间序列计算协方差和相关矩阵。通常还会对移动窗口协方差和相关矩阵感兴趣。这可以通过传递pairwise
关键字参数来实现,对于DataFrame
输入,将产生一个多索引的DataFrame
,其index
是相关日期。对于单个 DataFrame 参数的情况,甚至可以省略pairwise
参数:
注意
忽略缺失值,并使用成对完整观测值计算每个条目。
假设缺失数据是随机缺失的,这将导致对协方差矩阵的估计是无偏的。然而,对于许多应用程序来说,这种估计可能是不可接受的,因为估计的协方差矩阵不能保证是半正定的。这可能导致估计的相关性具有绝对值大于一的情况,和/或一个不可逆的协方差矩阵。更多详情请参见协方差矩阵的估计。
In [68]: covs = (
....: df[["B", "C", "D"]]
....: .rolling(window=4)
....: .cov(df[["A", "B", "C"]], pairwise=True)
....: )
....:
In [69]: covs
Out[69]:
B C D
2020-01-01 A NaN NaN NaN
B NaN NaN NaN
C NaN NaN NaN
2020-01-02 A NaN NaN NaN
B NaN NaN NaN
... ... ... ...
2020-01-09 B 0.342006 0.230190 0.052849
C 0.230190 1.575251 0.082901
2020-01-10 A -0.333945 0.006871 -0.655514
B 0.649711 0.430860 0.469271
C 0.430860 0.829721 0.055300
[30 rows x 3 columns]
加权窗口
.rolling
中的win_type
参数生成了在滤波和频谱估计中常用的加权窗口。win_type
必须是一个字符串,对应于scipy.signal 窗口函数。必须安装 Scipy 才能使用这些窗口,并且必须在聚合函数中指定 Scipy 窗口方法所需的补充参数。
In [70]: s = pd.Series(range(10))
In [71]: s.rolling(window=5).mean()
Out[71]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]:
0 NaN
1 NaN
2 NaN
3 NaN
4 2.0
5 3.0
6 4.0
7 5.0
8 6.0
9 7.0
dtype: float64
对于所有支持的聚合函数,请参见加权窗口函数。
扩展窗口
扩展窗口提供了一个聚合统计量的值,其中包含截至该时间点的所有可用数据。由于这些计算是滚动统计的一个特例,因此在 pandas 中实现了以下两种调用是等效的:
In [74]: df = pd.DataFrame(range(5))
In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]:
0
0 0.0
1 0.5
2 1.0
3 1.5
4 2.0
In [76]: df.expanding(min_periods=1).mean()
Out[76]:
0
0 0.0
1 0.5
2 1.0
3 1.5
4 2.0
对于所有支持的聚合函数,请参见扩展窗口函数。
指数加权窗口
指数加权窗口类似于扩展窗口,但是每个先前点相对于当前点被指数加权降低。
一般来说,加权移动平均值计算如下
[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},]
其中(x_t)是输入,(y_t)是结果,(w_i)是权重。
对于所有支持的聚合函数,请参见指数加权窗口函数。
EW 函数支持指数权重的两个变体。默认情况下,adjust=True
使用权重(w_i = (1 - \alpha)^i),得到
[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)² x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)² + ... + (1 - \alpha)^t}]
当指定adjust=False
时,移动平均值计算如下
[]
这等价于使用权重
[]
注意
这些方程有时以(\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),我们有
[]
这与上面的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)或半衰期:
[]
必须向 EW 函数精确指定跨度、质心、半衰期和alpha中的一个:
-
跨度对应于通常称为“N 天 EW 移动平均”的内容。
-
质心具有更物理的解释,可以��虑为跨度:(c = (s - 1) / 2)。
-
半衰期是指数权重减少到一半所需的时间段。
-
Alpha直接指定平滑因子。
您还可以指定halflife
,以时间间隔可转换的单位来指定观察值衰减到一半所需的时间,同时指定一系列times
。
In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
In [78]: df
Out[78]:
B
0 0.0
1 1.0
2 2.0
3 NaN
4 4.0
In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]
In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]:
B
0 0.000000
1 0.585786
2 1.523889
3 1.523889
4 3.233686
以下公式用于计算具有时间输入向量的指数加权均值:
[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},]
ExponentialMovingWindow 还有一个ignore_na
参数,用于确定中间空值如何影响权重的计算。当ignore_na=False
(默认值)时,权重是基于绝对位置计算的,因此中间空值会影响结果。当ignore_na=True
时,权重是通过忽略中间空值计算的。例如,假设adjust=True
,如果ignore_na=False
,则3, NaN, 5
的加权平均值将被计算为
[\frac{(1-\alpha)² \cdot 3 + 1 \cdot 5}{(1-\alpha)² + 1}.]
若ignore_na=True
,则加权平均值将被计算为
[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.]
var()
、std()
和cov()
函数有一个bias
参数,指定结果是否应包含有偏或无偏统计数据。例如,如果bias=True
,则ewmvar(x)
被计算为ewmvar(x) = ewma(x**2) - ewma(x)**2
;而如果bias=False
(默认值),有偏方差统计数据将通过去偏因子进行缩放。
[\frac{\left(\sum_{i=0}^t w_i\right)²}{\left(\sum_{i=0}^t w_i\right)² - \sum_{i=0}^t w_i²}.]
(对于(w_i = 1),这将缩减为通常的(N / (N - 1))因子,其中(N = t + 1)。请参阅维基百科上的加权样本方差以获取更多详细信息。
时间序列/日期功能
pandas 包含了广泛的功能和特性,用于处理各个领域的时间序列数据。使用 NumPy 的datetime64
和timedelta64
数据类型,pandas 已经整合了许多其他 Python��(如scikits.timeseries
)的功能,并为操作时间序列数据创造了大量新功能。
例如,pandas 支持:
从各种来源和格式解析时间序列信息
In [1]: import datetime
In [2]: dti = pd.to_datetime(
...: ["1/1/2018", np.datetime64("2018-01-01"), datetime.datetime(2018, 1, 1)]
...: )
...:
In [3]: dti
Out[3]: DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None)
生成固定频率日期和时间跨度的序列
In [4]: dti = pd.date_range("2018-01-01", periods=3, freq="h")
In [5]: dti
Out[5]:
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00',
'2018-01-01 02:00:00'],
dtype='datetime64[ns]', freq='h')
操纵和转换带有时区信息的日期时间
In [6]: dti = dti.tz_localize("UTC")
In [7]: dti
Out[7]:
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 01:00:00+00:00',
'2018-01-01 02:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='h')
In [8]: dti.tz_convert("US/Pacific")
Out[8]:
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 17:00:00-08:00',
'2017-12-31 18:00:00-08:00'],
dtype='datetime64[ns, US/Pacific]', freq='h')
对时间序列重新采样或转换为特定频率
In [9]: idx = pd.date_range("2018-01-01", periods=5, freq="h")
In [10]: ts = pd.Series(range(len(idx)), index=idx)
In [11]: ts
Out[11]:
2018-01-01 00:00:00 0
2018-01-01 01:00:00 1
2018-01-01 02:00:00 2
2018-01-01 03:00:00 3
2018-01-01 04:00:00 4
Freq: h, dtype: int64
In [12]: ts.resample("2h").mean()
Out[12]:
2018-01-01 00:00:00 0.5
2018-01-01 02:00:00 2.5
2018-01-01 04:00:00 4.0
Freq: 2h, dtype: float64
使用绝对或相对时间增量进行日期和时间算术运算
In [13]: friday = pd.Timestamp("2018-01-05")
In [14]: friday.day_name()
Out[14]: 'Friday'
# Add 1 day
In [15]: saturday = friday + pd.Timedelta("1 day")
In [16]: saturday.day_name()
Out[16]: 'Saturday'
# Add 1 business day (Friday --> Monday)
In [17]: monday = friday + pd.offsets.BDay()
In [18]: monday.day_name()
Out[18]: 'Monday'
pandas 提供了一套相对紧凑和自包含的工具,用于执行上述任务以及更多任务。
概述
pandas 涵盖了 4 个与时间相关的概念:
-
日期时间:具有时区支持的特定日期和时间。类似于标准库中的
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
表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出。
例如:
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’以将索引的频率设置为创建时推断的频率:
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]
你只需传递需要组装的列。
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 秒)。
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 年左右:
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')
部分字符串索引
可以将日期和解析为时间戳的字符串作为索引参数传递:
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428
In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]:
2011-12-30 0.56702
Freq: BME, dtype: float64
In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]:
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
为了方便访问更长的时间序列,您也可以将年份或年份和月份作为字符串传入:
In [103]: ts["2011"]
Out[103]:
2011-01-31 0.119209
2011-02-28 -1.044236
2011-03-31 -0.861849
2011-04-29 -2.104569
2011-05-31 -0.494929
2011-06-30 1.071804
2011-07-29 0.721555
2011-08-31 -0.706771
2011-09-30 -1.039575
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
In [104]: ts["2011-6"]
Out[104]:
2011-06-30 1.071804
Freq: BME, dtype: float64
这种类型的切片也适用于具有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]
这从月份的第一个时间开始,并包括该月份的最后日期和时间:
In [108]: dft["2013-1":"2013-2"]
Out[108]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个包含最后一天所有时间的停止时间:
In [109]: dft["2013-1":"2013-2-28"]
Out[109]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个精确的停止时间(与上述不同):
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
我们在包含的端点上停止,因为它是索引的一部分:
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]:
A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00 0.941451
2013-01-15 00:02:00 1.559365
2013-01-15 00:03:00 1.034374
2013-01-15 00:04:00 -1.480656
... ...
2013-01-15 12:26:00 0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00 0.066510
2013-01-15 12:30:00 -0.003945
[751 rows x 1 columns]
DatetimeIndex
部分字符串索引也适用于具有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 偏移量。
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
In [119]: df
Out[119]:
0
2019-01-01 00:00:00-08:00 0
In [120]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[120]:
0
2019-01-01 00:00:00-08:00 0
``` ### 切片 vs. 精确匹配
使用作为索引参数的相同字符串,根据索引的分辨率,可以将其视为切片或精确匹配。如果字符串比索引不准确,则将其视为切片,否则视为精确匹配。
考虑一个具有分钟分辨率索引的`Series`对象:
```py
In [121]: series_minute = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
.....: ),
.....: )
.....:
In [122]: series_minute.index.resolution
Out[122]: 'minute'
不到一分钟的时间戳字符串会给出一个Series
对象。
In [123]: series_minute["2011-12-31 23"]
Out[123]:
2011-12-31 23:59:00 1
dtype: int64
具有分钟分辨率(或更精确)的时间戳字符串会给出一个标量,即它不会被转换为切片。
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1
In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1
如果索引分辨率为秒,则具有分钟精度时间戳会给出一个Series
。
In [126]: series_second = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
.....: ),
.....: )
.....:
In [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
如果时间戳字符串被视为切片,则也可以使用.loc[]
对DataFrame
进行索引。
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
警告
然而,如果将字符串视为精确匹配,DataFrame
的[]
中的选择将是按列而不是按行进行的,请参见 Indexing Basics。例如,dft_minute['2011-12-31 23:59']
会引发KeyError
,因为'2012-12-31 23:59'
与索引具有相同的分辨率,并且没有这样的列名:
为了始终有明确的选择,无论行是被视为切片还是单个选择,请使用.loc
。
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
还要注意,DatetimeIndex
的分辨率不能低于天。
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
精确索引
如前一节所讨论的,使用部分字符串索引 DatetimeIndex
取决于周期的“准确性”,换句话说,间隔相对于索引分辨率的具体性。相比之下,使用 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]
没有默认值。
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')
让我们映射到工作日名称:
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)
In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]:
2013-04-30 Tue
2013-05-02 Thu
2013-05-05 Sun
2013-05-06 Mon
2013-05-07 Tue
Freq: C, dtype: object
节假日日历可用于提供节假日列表。有关更多信息,请参阅节假日日历部分。
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')
可以按照通常的方式定义尊重某个节假日日历的月度偏移。
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)
In [201]: dt + bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')
# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]:
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
注意
频率字符串‘C’用于指示使用 CustomBusinessDay DateOffset,重要的是要注意,由于 CustomBusinessDay 是一个参数化类型,CustomBusinessDay 的实例可能不同,这在‘C’频率字符串中是不可检测的。因此,用户需要确保在用户应用程序中一致使用‘C’频率字符串。 ### 营业时间
BusinessHour
类提供了在BusinessDay
上的营业时间表示,允许使用特定的开始和结束时间。
默认情况下,BusinessHour
使用 9:00 - 17:00 作为营业时间。添加BusinessHour
将按小时频率递增Timestamp
。如果目标Timestamp
超出营业时间,则移至下一个营业时间,然后递增。如果结果超出营业时间结束,则剩余的小时将添加到下一个营业日。
In [203]: bh = pd.offsets.BusinessHour()
In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>
# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4
In [206]: pd.Timestamp("2014-08-01 10:00") + bh
Out[206]: Timestamp('2014-08-01 11:00:00')
# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [207]: pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')
# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')
# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')
# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')
# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')
你还可以通过关键字指定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
。
结合别名
正如我们之前所见,大多数函数中别名和偏移实例是可互换的:
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
您可以组合日和日内偏移:
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]:
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140min')
In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]:
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010us')
锚定偏移
对于一些频率,您可以指定一个锚定后缀:
别名 | 描述 |
---|---|
W-SUN | 每周频率(星期日)。与‘W’相同 |
W-MON | 每周频率(星期一) |
W-TUE | 每周频率(星期二) |
W-WED | 每周频率(星期三) |
W-THU | 每周频率(星期四) |
W-FRI | 每周频率(星期五) |
W-SAT | 每周频率(星期六) |
(B)Q(E)(S)-DEC | 季度频率,年底在十二月。与‘QE’相同 |
(B)Q(E)(S)-JAN | 季度频率,年底在一月 |
(B)Q(E)(S)-FEB | 季度频率,年底在二月 |
(B)Q(E)(S)-MAR | 季度频率,年底在三月 |
(B)Q(E)(S)-APR | 季度频率,年底在四月 |
(B)Q(E)(S)-MAY | 季度频率,年底在五月 |
(B)Q(E)(S)-JUN | 季度频率,年底在六月 |
(B)Q(E)(S)-JUL | 季度频率,年底在七月 |
(B)Q(E)(S)-AUG | 季度频率,年底在八月 |
(B)Q(E)(S)-SEP | 季度频率,年底在九月 |
(B)Q(E)(S)-OCT | 季度频率,年底在十月 |
(B)Q(E)(S)-NOV | 季度频率,年底在十一月 |
(B)Y(E)(S)-DEC | 每年频率,锚定在十二月底。与‘YE’相同 |
(B)Y(E)(S)-JAN | 每年频率,锚定在一月底 |
(B)Y(E)(S)-FEB | 每年频率,锚定在二月底 |
(B)Y(E)(S)-MAR | 每年频率,锚定在三月底 |
(B)Y(E)(S)-APR | 每年频率,锚定在四月底 |
(B)Y(E)(S)-MAY | 每年频率,锚定在五月底 |
(B)Y(E)(S)-JUN | 每年频率,锚定在六月底 |
(B)Y(E)(S)-JUL | 每年频率,锚定在七月底 |
(B)Y(E)(S)-AUG | 每年频率,锚定在八月底 |
(B)Y(E)(S)-SEP | 每年频率,锚定在九月底 |
(B)Y(E)(S)-OCT | 每年频率,锚定在十月底 |
(B)Y(E)(S)-NOV | 每年频率,锚定在十一月底 |
这些可以作为date_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 | 将周六和周日移至下周一 |
假期和假日日历的定义示例:
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 来覆盖。
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 文档。
基础知识
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
注意星期日的值如何被拉回到前一个星期五。要获得星期日的值被推到星期一的行为,请改用
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)
如果我们想要重新采样到系列的完整范围:
In [310]: ts.resample("3min").sum()
Out[310]:
2014-01-01 00:00:00 0
2014-01-01 00:03:00 0
2014-01-01 00:06:00 0
2014-01-01 00:09:00 0
2014-01-01 00:12:00 0
..
2014-04-09 23:48:00 0
2014-04-09 23:51:00 0
2014-04-09 23:54:00 0
2014-04-09 23:57:00 0
2014-04-10 00:00:00 99
Freq: 3min, Length: 47521, dtype: int64
相反,我们可以只重新采样那些我们有点的组,如下所示:
In [311]: from functools import partial
In [312]: from pandas.tseries.frequencies import to_offset
In [313]: def round(t, freq):
.....: freq = to_offset(freq)
.....: td = pd.Timedelta(freq)
.....: return pd.Timestamp((t.value // td.value) * td.value)
.....:
In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]:
2014-01-01 0
2014-01-02 1
2014-01-03 2
2014-01-04 3
2014-01-05 4
..
2014-04-06 95
2014-04-07 96
2014-04-08 97
2014-04-09 98
2014-04-10 99
Length: 100, dtype: int64
聚合
resample()
方法返回一个pandas.api.typing.Resampler
实例。类似于聚合 API、分组 API 和窗口 API,Resampler
可以选择性地重新采样。
对DataFrame
进行重新采样,默认情况下将对所有列使用相同的函数。
In [315]: df = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: index=pd.date_range("1/1/2012", freq="s", periods=1000),
.....: columns=["A", "B", "C"],
.....: )
.....:
In [316]: r = df.resample("3min")
In [317]: r.mean()
Out[317]:
A B C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00 0.056909 0.146731 -0.024320
2012-01-01 00:06:00 -0.058837 0.047046 -0.052021
2012-01-01 00:09:00 0.063123 -0.026158 -0.066533
2012-01-01 00:12:00 0.186340 -0.003144 0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
我们可以使用标准的 getitem 选择特定列或列。
In [318]: r["A"].mean()
Out[318]:
2012-01-01 00:00:00 -0.033823
2012-01-01 00:03:00 0.056909
2012-01-01 00:06:00 -0.058837
2012-01-01 00:09:00 0.063123
2012-01-01 00:12:00 0.186340
2012-01-01 00:15:00 -0.085954
Freq: 3min, Name: A, dtype: float64
In [319]: r[["A", "B"]].mean()
Out[319]:
A B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00 0.056909 0.146731
2012-01-01 00:06:00 -0.058837 0.047046
2012-01-01 00:09:00 0.063123 -0.026158
2012-01-01 00:12:00 0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287
您可以传递一个函数列表或字典来进行聚合,输出一个DataFrame
:
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
在重新采样的DataFrame
上,您可以传递一个函数列表以应用于每列,这将产生一个带有分层索引的聚合结果:
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
通过将字典传递给aggregate
,您可以对DataFrame
的列应用不同的聚合:
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:
In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
此外,您还可以为每列单独指定多个聚合函数。
In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]:
A B
sum std mean std
2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294
2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597
2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309
2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953
2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025
2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
如果一个DataFrame
没有日期时间索引,而你想要根据帧中的日期时间列进行重新采样,可以传递给on
关键字。
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
同样,如果您希望按照MultiIndex
的日期时间级别重新采样,则可以将其名称或位置传递给level
关键字。
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
``` ### 通过组进行迭代
有了`Resampler`对象,通过分组数据进行迭代非常自然,并且类似于[`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"):
```py
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="\n\n")
.....:
Group: 2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00 0
2017-01-01 00:30:00 1
2017-01-01 00:31:00 2
dtype: int64
Group: 2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00 3
dtype: int64
Group: 2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)
Group: 2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00 4
2017-01-01 03:05:00 5
dtype: int64
查看通过组进行迭代或Resampler.__iter__
获取更多信息。### 使用origin
或offset
来调整箱子的起始点
分组的箱子根据时间序列起始点的当天开始时间进行调整。这适用于是天数的倍数(如30D
)或能够均匀分割一天的频率(如90s
或1min
)。这可能会导致某些不符合此标准的频率出现不一致。要更改此行为,可以使用参数origin
指定一个固定的 Timestamp。
例如:
In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
In [333]: middle = "2000-10-02 00:00:00"
In [334]: rng = pd.date_range(start, end, freq="7min")
In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
In [336]: ts
Out[336]:
2000-10-01 23:30:00 0
2000-10-01 23:37:00 3
2000-10-01 23:44:00 6
2000-10-01 23:51:00 9
2000-10-01 23:58:00 12
2000-10-02 00:05:00 15
2000-10-02 00:12:00 18
2000-10-02 00:19:00 21
2000-10-02 00:26:00 24
Freq: 7min, dtype: int64
在这里,我们可以看到,当使用origin
的默认值('start_day'
)时,根据时间序列的起始点,'2000-10-02 00:00:00'
之后的结果并不相同:
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
在这里,我们可以看到,将origin
设置为'epoch'
时,根据时间序列的起始点,'2000-10-02 00:00:00'
之后的结果是相同的:
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
如果需要,可以使用自定义时间戳作为origin
:
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
如果需要,您可以使用offset
Timedelta 调整箱子,该 Timedelta 将添加到默认的origin
中。对于这个时间序列,这两个示例是等效的:
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
注意最后一个示例中对origin
使用'start'
而不是origin
。在这种情况下,origin
将被设置为时间序列的第一个值。
向后重新采样
版本 1.3.0 中的新功能。
有时,我们需要调整箱子的开始而不是结束,以便使用给定的freq
进行向后重新采样。向后重新采样默认将closed
设置为'right'
,因为最后一个值应被视为最后一个箱子的边缘点。
我们可以将origin
设置为'end'
。特定Timestamp
索引的值表示从当前Timestamp
减去freq
到当前Timestamp
的右闭区间的重新采样结果。
In [345]: ts.resample('17min', origin='end').sum()
Out[345]:
2000-10-01 23:35:00 0
2000-10-01 23:52:00 18
2000-10-02 00:09:00 27
2000-10-02 00:26:00 63
Freq: 17min, dtype: int64
此外,与'start_day'
选项相反,支持end_day
。这将把origin
设置为最大Timestamp
的午夜。
In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]:
2000-10-01 23:38:00 3
2000-10-01 23:55:00 15
2000-10-02 00:12:00 45
2000-10-02 00:29:00 45
Freq: 17min, dtype: int64
以上结果使用2000-10-02 00:29:00
作为最后一个箱子的右边缘,因为以下计算。
In [347]: ceil_mid = rng.max().ceil('D')
In [348]: freq = pd.offsets.Minute(17)
In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)
In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00')
``` ## 时间跨度表示
在 pandas 中,时间的常规间隔由`Period`对象表示,而`Period`对象的序列被收集在`PeriodIndex`中,可以使用便利函数`period_range`创建。
### 期间
`Period`表示一段时间(例如,一天,一个月,一个季度等)。您可以通过使用频率别名来指定`freq`关键字来指定跨度。因为`freq`表示`Period`的跨度,所以不能像“-3D”那样是负数。
```py
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')
In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')
In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')
In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h')
从期间中添加和减去整数会按照其自身频率移动期间。不允许在具有不同freq
(跨度)的Period
之间进行算术运算。
In [355]: p = pd.Period("2012", freq="Y-DEC")
In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')
In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')
In [358]: p = pd.Period("2012-01", freq="2M")
In [359]: p + 2
Out[359]: Period('2012-05', '2M')
In [360]: p - 1
Out[360]: Period('2011-11', '2M')
In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False
如果Period
频率是每天或更高(D
,h
,min
,s
,ms
,us
和ns
),则可以添加offsets
和类似于timedelta
的内容,如果结果具有相同的频率,则可以添加。否则,将引发ValueError
。
In [362]: p = pd.Period("2014-07-01 09:00", freq="h")
In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')
In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')
In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()
File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()
ValueError: Cannot losslessly convert units
The above exception was the direct cause of the following exception:
IncompatibleFrequency Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)
File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
IncompatibleFrequency: Input cannot be converted to Period(freq=h)
如果Period
具有其他频率,则只能添加相同的offsets
。否则,将引发ValueError
。
In [367]: p = pd.Period("2014-07", freq="M")
In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)
File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()
File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()
IncompatibleFrequency: Input has different freq=3M from Period(freq=M)
具有相同频率的Period
实例之间的差异将返回它们之间的频率单位数:
In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12>
PeriodIndex 和 period_range
Period
对象的常规序列可以收集在PeriodIndex
中,可以使用period_range
便利函数构建:
In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")
In [372]: prng
Out[372]:
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
'2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
'2012-01'],
dtype='period[M]')
PeriodIndex
构造函数也可以直接使用:
In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
传递乘以的频率会输出一个具有乘以跨度的Period
序列。
In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')
如果start
或end
是Period
对象,则它们将用作与PeriodIndex
构造函数的频率匹配的锚定端点。
In [375]: pd.period_range(
.....: start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
.....: )
.....:
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')
就像DatetimeIndex
一样,PeriodIndex
也可以用于索引 pandas 对象:
In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)
In [377]: ps
Out[377]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
PeriodIndex
支持与Period
相同规则的加法和减法。
In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")
In [379]: idx
Out[379]:
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
'2014-07-01 12:00', '2014-07-01 13:00'],
dtype='period[h]')
In [380]: idx + pd.offsets.Hour(2)
Out[380]:
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
'2014-07-01 14:00', '2014-07-01 15:00'],
dtype='period[h]')
In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")
In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')
In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')
PeriodIndex
有自己的名为period
的 dtype,请参考 Period Dtypes。
期间 dtype
PeriodIndex
具有自定义的period
dtype。这是类似于时区感知 dtype(datetime64[ns, tz]
)的 pandas 扩展 dtype。
period
dtype 保存freq
属性,并且用period[freq]
表示,如period[D]
或period[M]
,使用频率字符串。
In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")
In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')
In [386]: pi.dtype
Out[386]: period[M]
period
dtype 可以在.astype(...)
中使用。它允许更改PeriodIndex
的freq
,如.asfreq()
,并将DatetimeIndex
转换为PeriodIndex
,如to_period()
:
# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')
# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')
# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)
In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')
In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
PeriodIndex 部分字符串索引
PeriodIndex 现在支持具有非单调索引的部分字符串切片。
您可以像DatetimeIndex
一样向Series
和DataFrame
传递日期和字符串,具有PeriodIndex
,有关详细信息,请参考 DatetimeIndex 部分字符串索引。
In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507
In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]:
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]:
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
传递表示低于PeriodIndex
的频率的字符串将返回部分切片数据。
In [395]: ps["2011"]
Out[395]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
In [396]: dfp = pd.DataFrame(
.....: np.random.randn(600, 1),
.....: columns=["A"],
.....: index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
.....: )
.....:
In [397]: dfp
Out[397]:
A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
... ...
2013-01-01 18:55 0.522460
2013-01-01 18:56 0.118710
2013-01-01 18:57 0.167517
2013-01-01 18:58 0.922883
2013-01-01 18:59 1.721104
[600 rows x 1 columns]
In [398]: dfp.loc["2013-01-01 10h"]
Out[398]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59 0.802298
[60 rows x 1 columns]
与DatetimeIndex
一样,结果将包括端点。下面的示例从 10:00 开始切片数据到 11:59。
In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56 1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58 0.578798
2013-01-01 11:59 -0.685496
[120 rows x 1 columns]
使用 PeriodIndex 进行频率转换和重采样
Period
和PeriodIndex
的频率可以通过asfreq
方法进行转换。让我们从 2011 财政年度开始,截至 12 月:
In [400]: p = pd.Period("2011", freq="Y-DEC")
In [401]: p
Out[401]: Period('2011', 'Y-DEC')
我们可以将其转换为月度频率。使用how
参数,我们可以指定是返回起始月份还是结束月份:
In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')
In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M')
提供了‘s’和‘e’的简写形式以方便使用:
In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')
In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M')
转换为“超期”(例如,年度频率是季度频率的超期)将自动返回包含输入期间的超期:
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
请注意,由于我们转换为年度频率,年终在十一月,因此 2011 年 12 月的月度期间实际上在 2012 年 Y-NOV 期间。
具有锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年第一季度可能从 2010 年开始,或者在 2011 年的几个月内开始。通过锚定频率,pandas 适用于所有季度频率 Q-JAN
到 Q-DEC
。
Q-DEC
定义常规日历季度:
In [408]: p = pd.Period("2012Q1", freq="Q-DEC")
In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')
In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D')
Q-MAR
定义了年度财政年度结束于三月:
In [411]: p = pd.Period("2011Q4", freq="Q-MAR")
In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')
In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D')
``` ## 转换表示方式
时间戳数据可以使用 `to_period` 转换为 PeriodIndex 数据,反之亦然使用 `to_timestamp`:
```py
In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")
In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [416]: ts
Out[416]:
2012-01-31 1.931253
2012-02-29 -0.184594
2012-03-31 0.249656
2012-04-30 -0.978151
2012-05-31 -0.873389
Freq: ME, dtype: float64
In [417]: ps = ts.to_period()
In [418]: ps
Out[418]:
2012-01 1.931253
2012-02 -0.184594
2012-03 0.249656
2012-04 -0.978151
2012-05 -0.873389
Freq: M, dtype: float64
In [419]: ps.to_timestamp()
Out[419]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
请记住,‘s’ 和 ‘e’ 可以用于返回期间开始或结束的时间戳:
In [420]: ps.to_timestamp("D", how="s")
Out[420]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
在期间和时间戳之间进行转换可以使用一些方便的算术函数。在以下示例中,我们将将年终在十一月的季度频率转换为季度结束后一个月的月底上午 9 点:
In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")
In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)
In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9
In [424]: ts.head()
Out[424]:
1990-03-01 09:00 -0.109291
1990-06-01 09:00 -0.637235
1990-09-01 09:00 -1.735925
1990-12-01 09:00 2.096946
1991-03-01 09:00 -1.039926
Freq: h, dtype: float64
``` ## 表示超出范围的时间段
如果您的数据超出了 `Timestamp` 的范围,请参阅时间戳限制,然后您可以使用 `PeriodIndex` 和/或 `Periods` 的 `Series` 进行计算。
```py
In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")
In [426]: span
Out[426]:
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
'1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
'1215-01-09', '1215-01-10',
...
'1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
'1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
'1380-12-31', '1381-01-01'],
dtype='period[D]', length=60632)
要从基于 int64
的 YYYYMMDD 表示形式转换。
In [427]: s = pd.Series([20121231, 20141130, 99991231])
In [428]: s
Out[428]:
0 20121231
1 20141130
2 99991231
dtype: int64
In [429]: def conv(x):
.....: return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
.....:
In [430]: s.apply(conv)
Out[430]:
0 2012-12-31
1 2014-11-30
2 9999-12-31
dtype: period[D]
In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D')
这些可以轻松转换为 PeriodIndex
:
In [432]: span = pd.PeriodIndex(s.apply(conv))
In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]')
``` ## 时区处理
pandas 提供了丰富的支持,使用 `pytz` 和 `dateutil` 库或标准库中的 [`datetime.timezone`](https://docs.python.org/3/library/datetime.html#datetime.timezone "(在 Python v3.12 中)") 对象,可以处理不同时区的时间戳。
### 处理时区
默认情况下,pandas 对象不考虑时区:
```py
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")
In [435]: rng.tz is None
Out[435]: True
要将这些日期本地化到时区(为一个无时区日期分配特定的时区),您可以使用 tz_localize
方法或 date_range()
中的 tz
关键字参数,Timestamp
或 DatetimeIndex
。您可以传递 pytz
或 dateutil
时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回 pytz
时区对象。要返回 dateutil
时区对象,请在字符串之前添加 dateutil/
。
-
在
pytz
中,您可以使用from pytz import common_timezones, all_timezones
找到常见(以及不太常见)的时区列表。 -
dateutil
使用操作系统时区,因此没有固定的列表可用。对于常见时区,名称与pytz
相同。
In [436]: import dateutil
# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")
In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")
In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')
# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=dateutil.tz.tzutc(),
.....: )
.....:
In [443]: rng_utc.tz
Out[443]: tzutc()
# datetime.timezone
In [444]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=datetime.timezone.utc,
.....: )
.....:
In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc
请注意,UTC
时区在dateutil
中是一个特殊情况,应该显式构造为dateutil.tz.tzutc
的实例。您也可以首先显式构造其他时区对象。
In [446]: import pytz
# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")
In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)
In [450]: rng_pytz.tz == tz_pytz
Out[450]: True
# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")
In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)
In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True
要将一个时区感知的 pandas 对象从一个时区转换到另一个时区,您可以使用tz_convert
方法。
In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]:
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
'2012-03-07 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
注意
当使用pytz
时区时,DatetimeIndex
将构造一个不同的时区对象,而对于相同的时区输入,Timestamp
将构造一个不同的时区对象。一个DatetimeIndex
可以保存一组具有不同 UTC 偏移的Timestamp
对象,而不能简洁地由一个pytz
时区实例表示,而一个Timestamp
代表一个具有特定 UTC 偏移的时间点。
In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")
In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")
In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
警告
要谨慎处理库之间的转换。对于一些时区,pytz
和dateutil
对时区的定义不同。这对于不寻常的时区比‘标准’时区如US/Eastern
更有问题。
警告
请注意,跨时间区库版本的时区定义可能不被视为相等。这可能会在使用一个版本本地化的存储数据并在不同版本上操作时出现问题。请参见这里如何处理这种情况。
警告
对于pytz
时区,直接将一个时区对象传递给datetime.datetime
构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))
)。相反,需要使用pytz
时区对象上的localize
方法对日期时间进行本地化。
警告
请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自的政府更改。
警告
如果您使用的日期超过 2038-01-18,由于底层库中当前存在的年 2038 问题导致的缺陷,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,DST 转换将会被应用。
例如,对于两个处于英国夏令时的日期(通常为 GMT+1),以下断言都为真:
In [459]: d_2037 = "2037-03-31T010101"
In [460]: d_2038 = "2038-03-31T010101"
In [461]: DST = "Europe/London"
In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")
In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")
在幕后,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndex
或Timestamp
的值将被本地化到时区。然而,具有相同 UTC 值的时间戳即使在不同时区中仍被视为相等:
In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")
In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")
In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')
In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')
In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True
不同时区中的Series
之间的操作将产生 UTC Series
,将数据对齐到 UTC 时间戳上:
In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))
In [470]: eastern = ts_utc.tz_convert("US/Eastern")
In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")
In [472]: result = eastern + berlin
In [473]: result
Out[473]:
2013-01-01 00:00:00+00:00 0
2013-01-02 00:00:00+00:00 2
2013-01-03 00:00:00+00:00 4
Freq: D, dtype: int64
In [474]: result.index
Out[474]:
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
'2013-01-03 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='D')
要删除时区信息,请使用tz_localize(None)
或tz_convert(None)
。tz_localize(None)
将删除时区,得到本地时间表示。tz_convert(None)
将在转换为 UTC 时间后删除时区。
In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")
In [476]: didx
Out[476]:
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
'2014-08-01 11:00:00-04:00'],
dtype='datetime64[ns, US/Eastern]', freq='h')
In [477]: didx.tz_localize(None)
Out[477]:
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
'2014-08-01 11:00:00'],
dtype='datetime64[ns]', freq=None)
In [478]: didx.tz_convert(None)
Out[478]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq='h')
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq=None)
Fold
对于模糊时间,pandas 支持显式指定仅关键字 fold 参数。由于夏令时,当从夏季时间转换到冬季时间时,一个挂钟时间可能发生两次;fold 描述 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime
(有关详细信息,请参阅datetime 文档)或从Timestamp
构造或从组件构造(见下文)。仅支持dateutil
时区(请参阅dateutil 文档以了解处理模糊日期时间的dateutil
方法),因为pytz
时区不支持 fold(请参阅pytz 文档以了解pytz
如何处理模糊日期时间的详细信息)。要使用pytz
本地化模糊日期时间,请使用Timestamp.tz_localize()
。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议在本地化模糊日期时间时依赖于Timestamp.tz_localize()
。
In [480]: pd.Timestamp(
.....: datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
.....: tz="dateutil/Europe/London",
.....: fold=0,
.....: )
.....:
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')
In [481]: pd.Timestamp(
.....: year=2019,
.....: month=10,
.....: day=27,
.....: hour=1,
.....: minute=30,
.....: tz="dateutil/Europe/London",
.....: fold=1,
.....: )
.....:
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')
``` ### 本地化时的模糊时间
`tz_localize`可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时导致某些时间在一天内发生两次(“时钟回拨”)。以下选项可用:
+ `'raise'`:引发`pytz.AmbiguousTimeError`(默认行为)
+ `'infer'`:尝试根据时间戳的单调性确定正确的偏移量
+ `'NaT'`:用`NaT`替换模糊时间
+ `bool`:`True`表示 DST 时间,`False`表示非 DST 时间。支持用于时间序列的`bool`值的类似数组。
```py
In [482]: rng_hourly = pd.DatetimeIndex(
.....: ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
.....: )
.....:
这将失败,因为存在模糊的时间('11/06/2011 01:00'
)
In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument
通过指定以下内容来处理这些模糊的时间。
In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
'2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
``` ### 本地化不存在的时间
DST 转换也可能会将当地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前调整”)。可以通过`nonexistent`参数控制具有不存在时间的时间序列的本地化行为。可用的选项如下:
+ `'raise'`:引发`pytz.NonExistentTimeError`(默认行为)
+ `'NaT'`:用`NaT`替换不存在的时间
+ `'shift_forward'`:将不存在的时间向前移动到最近的真实时间
+ `'shift_backward'`:将不存在的时间向后移动到最近的真实时间
+ timedelta 对象:通过 timedelta 持续时间移动不存在的时间
```py
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")
# 2:30 is a nonexistent time
本地化不存在的时间将默认引发错误。
In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
NonExistentTimeError: 2015-03-29 02:30:00
将不存在的时间转换为NaT
或移动时间。
In [489]: dti
Out[489]:
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
'2015-03-29 04:30:00'],
dtype='datetime64[ns]', freq='h')
In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]:
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]:
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
'2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]:
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]:
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
``` ### 时区系列操作
具有**naive**值的`Series`以`datetime64[ns]`的 dtype 表示。
```py
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))
In [495]: s_naive
Out[495]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
dtype: datetime64[ns]
具有aware值的Series
以datetime64[ns, tz]
的 dtype 表示,其中tz
是时区
In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))
In [497]: s_aware
Out[497]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
这两个Series
的时区信息可以通过.dt
访问器进行操作,参见 dt 访问器部分。
例如,将 naive 时间戳本地化和转换为时区感知。
In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]:
0 2012-12-31 19:00:00-05:00
1 2013-01-01 19:00:00-05:00
2 2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]
时间区域信息也可以使用astype
方法进行操作。该方法可以在不同的时区感知 dtype 之间进行转换。
# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]:
0 2013-01-01 06:00:00+01:00
1 2013-01-02 06:00:00+01:00
2 2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]
注意
在Series
上使用Series.to_numpy()
,返回数据的 NumPy 数组。 NumPy 当前不支持时区(即使在本地时区打印!),因此对于时区感知数据,将返回时间戳的对象数组:
In [500]: s_naive.to_numpy()
Out[500]:
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
'2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')
In [501]: s_aware.to_numpy()
Out[501]:
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
dtype=object)
通过转换为时间戳的对象数组,它保留了时区信息。例如,当转换回 Series 时:
In [502]: pd.Series(s_aware.to_numpy())
Out[502]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
但是,如果您想要一个实际的 NumPydatetime64[ns]
数组(其值已转换为 UTC),而不是对象数组,您可以指定dtype
参数:
In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]:
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
'2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')
``` ## 概述
pandas 捕获了 4 个通用的与时间相关的概念:
1. 日期时间:具有时区支持的特定日期和时间。类似于标准库中的`datetime.datetime`。
1. 时间增量:绝对时间持续时间。类似于标准库中的`datetime.timedelta`。
1. 时间跨度:由时间点及其关联频率定义的时间跨度。
1. 日期偏移量:一种尊重日历算术的相对时间持续。类似于`dateutil`包中的`dateutil.relativedelta.relativedelta`。
| 概念 | 标量类 | 数组类 | pandas 数据类型 | 主要创建方法 |
| --- | --- | --- | --- | --- |
| 日期时间 | `Timestamp` | `DatetimeIndex` | `datetime64[ns]`或`datetime64[ns, tz]` | `to_datetime`或`date_range` |
| 时间增量 | `Timedelta` | `TimedeltaIndex` | `timedelta64[ns]` | `to_timedelta`或`timedelta_range` |
| 时间跨度 | `Period` | `PeriodIndex` | `period[freq]` | `Period`或`period_range` |
| 日期偏移量 | `DateOffset` | `None` | `None` | `DateOffset` |
对于时间序列数据,习惯上将时间分量表示为`Series`或`DataFrame`的索引,以便可以针对时间元素进行操作。
```py
In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]:
2000-01-01 0
2000-01-02 1
2000-01-03 2
Freq: D, dtype: int64
然而,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
时间戳与时间跨度
时间戳数据是与时间点关联值的最基本类型的时间序列数据。对于 pandas 对象,这意味着使用时间点。
In [28]: import datetime
In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')
In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')
In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00')
然而,在许多情况下,将变量的变化与时间跨度关联起来更自然。由Period
表示的跨度可以明确指定,也可以从日期时间字符串格式中推断出来。
例如:
In [32]: pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')
In [33]: pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D')
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”以设置索引的频率为创建时的推断频率:
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]
您可以只传递您需要组装的列。
In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]:
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
pd.to_datetime
会查找列名中 datetime 组件的标准标识,包括:
-
必需的:
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)
纪元时间戳
pandas 支持将整数或浮点数纪元时间转换为Timestamp
和DatetimeIndex
。默认单位是纳秒,因为Timestamp
对象在内部存储时是以纳秒为单位的。然而,纪元时间通常以另一个单位存储,可以指定。这些是从origin
参数指定的起始点计算出来的。
In [59]: pd.to_datetime(
....: [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
....: )
....:
Out[59]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
In [60]: pd.to_datetime(
....: [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
....: unit="ms",
....: )
....:
Out[60]:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
注意
unit
参数不使用与上述讨论的format
参数相同的字符串)。 可用单位在pandas.to_datetime()
的文档中列出。
使用tz
参数指定了 epoch 时间戳的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 时间将四舍五入到最近的纳秒。
警告
将 float 型 epoch 时间转换可能导致不准确和意外的结果。Python floats在十进制中有约 15 位数字精度。在从浮点数到高精度Timestamp
的转换过程中进行舍入是不可避免的。实现精确精度的唯一方法是使用固定宽度的类型(例如 int64)。
In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)
In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912')
另请参阅
使用 origin 参数 ### 从时间戳到 epoch
要反转上述操作,即从Timestamp
转换为‘unix’ epoch:
In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
In [66]: stamps
Out[66]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
我们减去纪元(1970 年 1 月 1 日 UTC 午夜),然后进行“单位”(1 秒)的地板除法。
In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
``` ### 使用`origin`参数
使用`origin`参数,可以指定一个替代创建`DatetimeIndex`的起始点。例如,要使用 1960-01-01 作为起始日期:
```py
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)
默认设置为origin='unix'
,默认为1970-01-01 00:00:00
。通常称为“unix 纪元”或 POSIX 时间。
In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)
``` ### 提供 format 参数
除了必需的 datetime 字符串之外,还可以传递一个`format`参数以确保特定的解析。这也可能显著加快转换速度。
```py
In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')
In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00')
有关在指定format
选项时可用选择的更多信息,请参阅 Python datetime 文档。
从多个 DataFrame 列中组装 datetime
你还可以传递一个整数或字符串列的DataFrame
以组装成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]
你只需要传递你需要组装的列。
In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]:
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
pd.to_datetime
查找列名中 datetime 组件的标准设计,包括:
-
必需:
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 支持将整数或浮点时代转换为Timestamp
和DatetimeIndex
。默认单位为纳秒,因为这是Timestamp
对象在内部存储的方式。但是,时代通常以另一个可以指定的unit
存储。这些是从由origin
参数指定的起始点计算得出的。
In [59]: pd.to_datetime(
....: [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
....: )
....:
Out[59]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
In [60]: pd.to_datetime(
....: [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
....: unit="ms",
....: )
....:
Out[60]:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
注意
unit
参数不使用与上面讨论的format
参数相同的字符串。可以在pandas.to_datetime()
的文档中找到可用的单位。
使用指定了tz
参数的时代时间戳构造Timestamp
或DatetimeIndex
将引发 ValueError。如果您在另一个时区的壁钟时间中有时代,您可以将时代读取为时区不可知的时间戳,然后本地化到适当的时区:
In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')
In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)
注意
时代时间将四舍五入到最接近的纳秒。
警告
浮点时代转换可能导致不准确和意外的结果。 Python 浮点数 在十进制中具有约 15 位数字精度。在从浮点数转换为高精度Timestamp
时进行四舍五入是不可避免的。实现精确精度的唯一方法是使用固定宽度的类型(例如 int64)。
In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)
In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912')
另请参阅
使用起始参数
从时间戳到时代
要反转上述操作,即从Timestamp
转换为‘unix’时代:
In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
In [66]: stamps
Out[66]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
我们减去时代(1970 年 1 月 1 日 UTC 的午夜),然后除以“unit”(1 秒)。
In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
使用 origin
参数
使用origin
参数,可以为创建DatetimeIndex
指定替代起始点。例如,要使用 1960-01-01 作为起始日期:
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)
默认设置为origin='unix'
,默认为1970-01-01 00:00:00
。通常称为“unix 时代”或 POSIX 时间。
In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)
生成时间戳范围
要生成带有时间戳的索引,您可以使用DatetimeIndex
或Index
构造函数,并传递一个日期时间对象列表:
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
这样的便利函数可以利用各种频率别名:
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')
另请参阅
自定义工作日 ### 自定义频率范围
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 年:
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
等)快速访问日期字段。 -
snap
等正规化函数和非常快速的asof
逻辑。
DatetimeIndex
对象具有常规 Index
对象的所有基本功能,以及一系列用于简化频率处理的高级时间序列特定方法。
另请参阅
重新索引方法
注意
虽然 pandas 不强制要求您具有排序的日期索引,但如果日期未排序,则其中一些方法可能会出现意外或不正确的行为。
DatetimeIndex
可以像常规索引一样使用,并提供所有智能功能,如选择、切片等。
In [95]: rng = pd.date_range(start, end, freq="BME")
In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [97]: ts.index
Out[97]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
'2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
dtype='datetime64[ns]', freq='BME')
In [98]: ts[:5].index
Out[98]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31'],
dtype='datetime64[ns]', freq='BME')
In [99]: ts[::2].index
Out[99]:
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
'2011-09-30', '2011-11-30'],
dtype='datetime64[ns]', freq='2BME')
部分字符串索引
可以将日期和解析为时间戳的字符串作为索引参数传递:
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428
In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]:
2011-12-30 0.56702
Freq: BME, dtype: float64
In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]:
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
为了方便访问更长的时间序列,也可以将年份或年份和月份作为字符串传递:
In [103]: ts["2011"]
Out[103]:
2011-01-31 0.119209
2011-02-28 -1.044236
2011-03-31 -0.861849
2011-04-29 -2.104569
2011-05-31 -0.494929
2011-06-30 1.071804
2011-07-29 0.721555
2011-08-31 -0.706771
2011-09-30 -1.039575
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
In [104]: ts["2011-6"]
Out[104]:
2011-06-30 1.071804
Freq: BME, dtype: float64
这种切片方式也适用于具有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]
这从月初开始,包括月底的日期和时间:
In [108]: dft["2013-1":"2013-2"]
Out[108]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个包括最后一天所有时间的停止时间:
In [109]: dft["2013-1":"2013-2-28"]
Out[109]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个精确的停止时间(与上述不同):
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
我们在包含的端点上停止,因为它是索引的一部分:
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]:
A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00 0.941451
2013-01-15 00:02:00 1.559365
2013-01-15 00:03:00 1.034374
2013-01-15 00:04:00 -1.480656
... ...
2013-01-15 12:26:00 0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00 0.066510
2013-01-15 12:30:00 -0.003945
[751 rows x 1 columns]
DatetimeIndex
部分字符串索引也适用于具有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 偏移。
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
In [119]: df
Out[119]:
0
2019-01-01 00:00:00-08:00 0
In [120]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[120]:
0
2019-01-01 00:00:00-08:00 0
``` ### 切片 vs. 精确匹配
使用作为索引参数的相同字符串,根据索引的分辨率,可以将其视为切片或精确匹配。如果字符串比索引不准确,则将其视为切片,否则视为精确匹配。
考虑一个具有分钟分辨率索引的`Series`对象:
```py
In [121]: series_minute = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
.....: ),
.....: )
.....:
In [122]: series_minute.index.resolution
Out[122]: 'minute'
比分钟精度低的时间戳字符串会给出一个Series
对象。
In [123]: series_minute["2011-12-31 23"]
Out[123]:
2011-12-31 23:59:00 1
dtype: int64
具有分钟分辨率(或更精确)的时间戳字符串会给出一个标量,即不会转换为切片。
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1
In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1
如果索引分辨率为秒,则具有分钟精度的时间戳会给出一个Series
。
In [126]: series_second = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
.....: ),
.....: )
.....:
In [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
如果将时间戳字符串视为切片,它也可以用于使用.loc[]
索引DataFrame
。
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
警告
但是,如果将字符串视为精确匹配,DataFrame
的[]
中的选择将按列而不是按行进行,参见索引基础知识。例如,dft_minute['2011-12-31 23:59']
将引发KeyError
,因为'2012-12-31 23:59'
的分辨率与索引相同,没有这样的列名:
为了始终具有明确的选择,无论行是作为切片还是单个选择,都使用.loc
。
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
还要注意,DatetimeIndex
的分辨率不能比天更精确。
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
精确索引
如前一节所讨论的,使用部分字符串索引DatetimeIndex
取决于周期的“准确性”,换句话说,与索引的分辨率相比间隔的具体性。相比之下,使用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]
没有默认值。
In [136]: dft[
.....: datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
.....: 2013, 2, 28, 10, 12, 0
.....: )
.....: ]
.....:
Out[136]:
A
2013-01-01 10:12:00 0.565375
2013-01-01 10:13:00 0.068184
2013-01-01 10:14:00 0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00 0.931536
... ...
2013-02-28 10:08:00 0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00 0.139348
2013-02-28 10:11:00 0.085288
2013-02-28 10:12:00 0.950146
[83521 rows x 1 columns]
截断和花式索引
提供了一个类似于切片的 truncate()
方便函数。请注意,truncate
假设 DatetimeIndex
中未指定日期组件的任何部分的值为 0,而切片则返回任何部分匹配的日期:
In [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")
In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)
In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
Freq: W-SUN, dtype: float64
In [140]: ts2["2011-11":"2011-12"]
Out[140]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
2011-12-04 0.046611
2011-12-11 0.059478
2011-12-18 -0.286539
2011-12-25 0.841669
Freq: W-SUN, dtype: float64
即使是破坏了 DatetimeIndex
频率规律的复杂花式索引也会导致一个 DatetimeIndex
,尽管频率会丢失:
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)
部分字符串索引
可以将日期和解析为时间戳的字符串作为索引参数传递:
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428
In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]:
2011-12-30 0.56702
Freq: BME, dtype: float64
In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]:
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
为了方便访问更长的时间序列,你也可以将年份或年份和月份作为字符串传递:
In [103]: ts["2011"]
Out[103]:
2011-01-31 0.119209
2011-02-28 -1.044236
2011-03-31 -0.861849
2011-04-29 -2.104569
2011-05-31 -0.494929
2011-06-30 1.071804
2011-07-29 0.721555
2011-08-31 -0.706771
2011-09-30 -1.039575
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
In [104]: ts["2011-6"]
Out[104]:
2011-06-30 1.071804
Freq: BME, dtype: float64
这种类型的切片也适用于具有 DatetimeIndex
的 DataFrame
。由于部分字符串选择是一种标签切片形式,因此端点将被包括在内。这将包括在包含日期的匹配时间:
警告
使用单个字符串通过 getitem(例如 frame[dtstring]
)对 DataFrame
行进行索引在 pandas 1.2.0 中已弃用(因为它存在将行索引与列选择混淆的歧义),并将在将来的版本中删除。使用 .loc
相当于(例如 frame.loc[dtstring]
)仍然受支持。
In [105]: dft = pd.DataFrame(
.....: np.random.randn(100000, 1),
.....: columns=["A"],
.....: index=pd.date_range("20130101", periods=100000, freq="min"),
.....: )
.....:
In [106]: dft
Out[106]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
In [107]: dft.loc["2013"]
Out[107]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
这从月份的第一天开始,并包括月份的最后日期和时间:
In [108]: dft["2013-1":"2013-2"]
Out[108]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个包含最后一天所有时间的停止时间:
In [109]: dft["2013-1":"2013-2-28"]
Out[109]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
这指定了一个确切的停止时间(与上述不同):
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
我们在包含的结束点停止,因为它是索引的一部分:
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]:
A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00 0.941451
2013-01-15 00:02:00 1.559365
2013-01-15 00:03:00 1.034374
2013-01-15 00:04:00 -1.480656
... ...
2013-01-15 12:26:00 0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00 0.066510
2013-01-15 12:30:00 -0.003945
[751 rows x 1 columns]
DatetimeIndex
部分字符串索引也适用于具有 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 偏移量。
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
In [119]: df
Out[119]:
0
2019-01-01 00:00:00-08:00 0
In [120]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[120]:
0
2019-01-01 00:00:00-08:00 0
切片 vs. 精确匹配
使用相同的字符串作为索引参数时,根据索引的分辨率,它可以被视为切片或精确匹配。如果字符串的精度低于索引,则将其视为切片,否则视为精确匹配。
考虑一个具有分钟分辨率索引的 Series
对象:
In [121]: series_minute = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
.....: ),
.....: )
.....:
In [122]: series_minute.index.resolution
Out[122]: 'minute'
低于分钟的时间戳字符串给出一个 Series
对象。
In [123]: series_minute["2011-12-31 23"]
Out[123]:
2011-12-31 23:59:00 1
dtype: int64
具有分钟分辨率(或更高)的时间戳字符串会给出一个标量,即不会转换为切片。
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1
In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1
如果索引分辨率是秒,那么分钟精度的时间戳将给出一个 Series
。
In [126]: series_second = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
.....: ),
.....: )
.....:
In [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
如果时间戳字符串被视为切片,则也可以使用 .loc[]
索引 DataFrame
。
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
警告
但是,如果字符串被视为精确匹配,则 DataFrame
的 []
中的选择将以列而不是行为基础,参见 索引基础知识。例如,dft_minute['2011-12-31 23:59']
将引发 KeyError
,因为 '2012-12-31 23:59'
的分辨率与索引相同,并且没有具有这样名称的列:
为了始终有明确的选择,无论行是被视为切片还是单个选择,都可以使用 .loc
。
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
还要注意,DatetimeIndex
的分辨率不能比日更不精确。
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
精确索引
如前所述,使用部分字符串索引DatetimeIndex
取决于“精确度”(即间隔相对于索引分辨率的特定程度)。相比之下,使用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]
没有默认值。
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
等时间戳集合中访问。
属性 | 描述 |
---|---|
year | 日期时间的年份 |
month | 日期时间的月份 |
day | 日期时间的天数 |
hour | 日期时间的小时数 |
minute | 日期时间的分钟数 |
second | 日期时间的秒数 |
microsecond | 日期时间的微秒数 |
nanosecond | 日期时间的纳秒数 |
date | 返回 datetime.date(不包含时区信息) |
time | 返回 datetime.time(不包含时区信息) |
timetz | 返回带有时区信息的本地时间 datetime.time |
dayofyear | 年份中的日序数 |
day_of_year | 年份中的日序数 |
weekofyear | 年份中的周序数 |
week | 年份中的周序数 |
dayofweek | 一周中的星期几,星期一=0,星期日=6 的编号 |
day_of_week | 一周中的星期几,星期一=0,星期日=6 的编号 |
weekday | 一周中的星期几,星期一=0,星期日=6 的编号 |
quarter | 日期的季度:1 表示 1 月至 3 月,2 表示 4 月至 6 月,依此类推 |
days_in_month | 日期所在月份的天数 |
is_month_start | 逻辑值,指示是否月份的第一天(由频率定义) |
is_month_end | 逻辑值,指示是否月份的最后一天(由频率定义) |
is_quarter_start | 逻辑值,指示是否季度的第一天(由频率定义) |
is_quarter_end | 逻辑值,指示是否季度的最后一天(由频率定义) |
is_year_start | 逻辑值,指示是否年份的第一天(由频率定义) |
is_year_end | 逻辑值,指示是否年份的最后一天(由频率定义) |
is_leap_year | 逻辑值,指示日期是否属于闰年 |
此外,如果您有一个具有日期时间值的 Series
,则可以通过 .dt
访问器访问这些属性,详细信息请参见 .dt 访问器 部分。
您可以从 ISO 8601 标准获得 ISO 年份的年、周和日组件:
In [142]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
In [143]: idx.isocalendar()
Out[143]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
In [144]: idx.to_series().dt.isocalendar()
Out[144]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
DateOffset 对象
在前面的例子中,频率字符串(例如 'D'
)用于指定定义的频率:
-
当使用
date_range()
时,DatetimeIndex
中的日期时间间隔。 -
Period
或PeriodIndex
的频率。
这些频率字符串映射到一个 DateOffset
对象及其子类。DateOffset
类似于 Timedelta
,表示一段时间的持续时间,但遵循特定的日历持续时间规则。例如,Timedelta
的一天始终会将 datetimes
增加 24 小时,而 DateOffset
的一天将会将 datetimes
增加到次日的同一时间,无论一天是否由于夏令时而表示为 23、24 或 25 小时。但是,所有表示小时或更小单位(Hour
、Minute
、Second
、Milli
、Micro
、Nano
)的 DateOffset
子类都像 Timedelta
一样遵循绝对时间。
基本的 DateOffset
行为类似于 dateutil.relativedelta
(relativedelta 文档)会按照指定的日历持续时间移动日期时间。可以使用算术运算符(+
)执行移位。
# This particular day contains a day light savings time transition
In [145]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")
# Respects absolute time
In [146]: ts + pd.Timedelta(days=1)
Out[146]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')
# Respects calendar time
In [147]: ts + pd.DateOffset(days=1)
Out[147]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
In [148]: friday = pd.Timestamp("2018-01-05")
In [149]: friday.day_name()
Out[149]: 'Friday'
# Add 2 business days (Friday --> Tuesday)
In [150]: two_business_days = 2 * pd.offsets.BDay()
In [151]: friday + two_business_days
Out[151]: Timestamp('2018-01-09 00:00:00')
In [152]: (friday + two_business_days).day_name()
Out[152]: 'Tuesday'
大多数 DateOffsets
都有关联的频率字符串或偏移别名,可以传递到 freq
关键字参数中。下面列出了可用的日期偏移量及其关联的频率字符串:
日期偏移量 | 频率字符串 | 描述 |
---|---|---|
DateOffset |
无 | 通用偏移类,默认为绝对 24 小时 |
BDay 或 BusinessDay |
'B' |
工作日(周日至周五) |
CDay or CustomBusinessDay |
'C' |
自定义工作日 |
Week |
'W' |
一周,可选择以一周中的某一天为锚点 |
WeekOfMonth |
'WOM' |
每月第 x 周的第 y 天 |
LastWeekOfMonth |
'LWOM' |
每月最后一周的第 x 天 |
MonthEnd |
'ME' |
日历月末 |
MonthBegin |
'MS' |
日历月开始 |
BMonthEnd or BusinessMonthEnd |
'BME' |
工作月末 |
BMonthBegin or BusinessMonthBegin |
'BMS' |
工作月开始 |
CBMonthEnd or CustomBusinessMonthEnd |
'CBME' |
自定义工作月末 |
CBMonthBegin or CustomBusinessMonthBegin |
'CBMS' |
自定义工作月开始 |
SemiMonthEnd |
'SME' |
每月 15 日(或其他日子)和日历月末 |
SemiMonthBegin |
'SMS' |
15 号(或其他日期)和月初 |
QuarterEnd |
'QE' |
季度末 |
QuarterBegin |
'QS' |
季度初 |
BQuarterEnd |
'BQE |
业务季度末 |
BQuarterBegin |
'BQS' |
业务季度初 |
FY5253Quarter |
'REQ' |
零售(又称 52-53 周)季度 |
YearEnd |
'YE' |
年末 |
YearBegin |
'YS' or 'BYS' |
年初 |
BYearEnd |
'BYE' |
业务年末 |
BYearBegin |
'BYS' |
业务年初 |
FY5253 |
'RE' |
零售(又称 52-53 周)年 |
Easter |
None | 复活节假期 |
BusinessHour |
'bh' |
工作小时 |
CustomBusinessHour |
'cbh' |
自定义工作小时 |
Day |
'D' |
一天 |
Hour |
'h' |
一小时 |
Minute |
'min' |
一分钟 |
Second |
's' |
一秒钟 |
Milli |
'ms' |
一毫秒 |
Micro |
'us' |
一微秒 |
Nano |
'ns' |
一纳秒 |
DateOffsets
另外还有rollforward()
和rollback()
方法,用于将日期向前或向后移动到相对于偏移量的有效日期。例如,商业偏移将周末(星期六和星期日)落在的日期向前推到星期一,因为商业偏移是在工作日上操作的。
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')
让我们映射到星期几的名称:
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)
In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]:
2013-04-30 Tue
2013-05-02 Thu
2013-05-05 Sun
2013-05-06 Mon
2013-05-07 Tue
Freq: C, dtype: object
假日日历可用于提供假日列表。有关更多信息,请参阅假日日历部分。
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')
可以像通常一样定义尊重某个假日日历的月度偏移量。
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)
In [201]: dt + bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')
# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]:
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
注
频率字符串‘C’用于指示使用 CustomBusinessDay DateOffset,需要注意的是,由于 CustomBusinessDay 是一个带参数的类型,CustomBusinessDay 的实例可能不同,这不能从‘C’频率字符串中检测出来。因此,用户需要确保在用户的应用程序中一致使用‘C’频率字符串。### 营业时间
BusinessHour
类提供了在BusinessDay
上表示营业时间的方式,允许使用特定的开始和结束时间。
默认情况下,BusinessHour
使用 9:00 - 17:00 作为营业时间。添加BusinessHour
将按小时频率递增Timestamp
。如果目标Timestamp
超出营业时间,移动到下一个营业时间然后递增。如果结果超出营业时间结束,剩余的小时将添加到下一个营业日。
In [203]: bh = pd.offsets.BusinessHour()
In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>
# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4
In [206]: pd.Timestamp("2014-08-01 10:00") + bh
Out[206]: Timestamp('2014-08-01 11:00:00')
# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [207]: pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')
# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')
# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')
# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')
# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')
您还可以通过关键字指定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')
``` ### 偏移别名
给出了一些常见时间序列频率的字符串别名。我们将这些别名称为*偏移别名*。
| Alias | 描述 |
| --- | --- |
| B | 营业日频率 |
| C | 自定义营业日频率 |
| D | 日历日频率 |
| W | 每周频率 |
| ME | 月末频率 |
| SME | 半月末频率(15 号和月末) |
| BME | 营业月末频率 |
| CBME | 自定义营业月末频率 |
| MS | 月初频率 |
| SMS | 半月初频率(1 号和 15 号) |
| BMS | 营业月初频率 |
| CBMS | 自定义业务月开始频率 |
| QE | 季度结束频率 |
| BQE | 业务季度结束频率 |
| QS | 季度开始频率 |
| BQS | 业务季度开始频率 |
| YE | 年结束频率 |
| BYE | 营业年结束频率 |
| YS | 年开始频率 |
| BYS | 营业年开始频率 |
| h | 每小时频率 |
| bh | 营业时间频率 |
| cbh | 自定义营业小时频率 |
| min | 每分钟频率 |
| s | 每秒频率 |
| ms | 毫秒 |
| us | 微秒 |
| ns | 纳秒 |
自版本 2.2.0 开始已弃用:别名`H`,`BH`,`CBH`,`T`,`S`,`L`,`U`和`N`已弃用,转而使用别名`h`,`bh`,`cbh`,`min`,`s`,`ms`,`us`和`ns`。
注意
> 当使用上述偏移别名时,应注意诸如`date_range()`、`bdate_range()`等函数将仅返回在`start_date`和`end_date`定义的区间内的时间戳。如果`start_date`不对应频率,则返回的时间戳将从下一个有效时间戳开始,对于`end_date`,返回的时间戳将停止在上一个有效时间戳处。
例如,对于偏移`MS`,如果`start_date`不是月份的第一天,则返回的时间戳将从下个月的第一天开始。如果`end_date`不是月份的第一天,则最后返回的时间戳将是对应月份的第一天。
```py
In [235]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")
In [236]: dates_lst_1
Out[236]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
In [237]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")
In [238]: dates_lst_2
Out[238]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
在上面的例子中,我们可以看到date_range()
和bdate_range()
仅返回在start_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
。
组合别名
正如我们之前所见,别名和偏移实例在大多数函数中是可以互换的:
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
您可以组合日和日内偏移量:
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]:
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140min')
In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]:
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010us')
锚定的偏移
对于某些频率,您可以指定锚定后缀:
别名 | 描述 |
---|---|
W-SUN | 每周频率(星期日)。与‘W’相同 |
W-MON | 每周频率(星期一) |
W-TUE | 每周频率(星期二) |
W-WED | 每周频率(星期三) |
W-THU | 每周频率(星期四) |
W-FRI | 每周频率(星期五) |
W-SAT | 每周频率(星期六) |
(B)Q(E)(S)-DEC | 季度频率,年份以十二月结束。与‘QE’相同 |
(B)Q(E)(S)-JAN | 季度频率,年份以一月结束 |
(B)Q(E)(S)-FEB | 季度频率,年份以二月结束 |
(B)Q(E)(S)-MAR | 季度频率,年份以三月结束 |
(B)Q(E)(S)-APR | 季度频率,年份以四月结束 |
(B)Q(E)(S)-MAY | 季度频率,年份以五月结束 |
(B)Q(E)(S)-JUN | 季度频率,年份以六月结束 |
(B)Q(E)(S)-JUL | 季度频率,年份以七月结束 |
(B)Q(E)(S)-AUG | 季度频率,年份以八月结束 |
(B)Q(E)(S)-SEP | 季度频率,年份以九月结束 |
(B)Q(E)(S)-OCT | 季度频率,年份以十月结束 |
(B)Q(E)(S)-NOV | 季度频率,年份以十一月结束 |
(B)Y(E)(S)-DEC | 每年频率,锚定在十二月底。与‘YE’相同 |
(B)Y(E)(S)-JAN | 每年频率,锚定在一月底 |
(B)Y(E)(S)-FEB | 每年频率,锚定在二月底 |
(B)Y(E)(S)-MAR | 每年频率,锚定在三月底 |
(B)Y(E)(S)-APR | 每年频率,锚定在四月底 |
(B)Y(E)(S)-MAY | 每年频率,锚定在五月底 |
(B)Y(E)(S)-JUN | 每年频率,锚定在六月底 |
(B)Y(E)(S)-JUL | 每年频率,锚定在七月底 |
(B)Y(E)(S)-AUG | 每年频率,锚定在八月底 |
(B)Y(E)(S)-SEP | 每年频率,锚定在九月底 |
(B)Y(E)(S)-OCT | 每年频率,锚定在十月底 |
(B)Y(E)(S)-NOV | 每年频率,锚定在十一月底 |
这些可以用作date_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 日)的假期,观察规则确定了如果假期落在周末或其他非观察日时,该假期何时被观察。定义的观察规则有:
Rule | Description |
---|---|
nearest_workday | 将周六移至周五,周日移至周一 |
sunday_to_monday | 将周日移至下一个周一 |
next_monday_or_tuesday | 将周六移至周一,周日/周一移至周二 |
previous_friday | 将周六和周日移至上一个星期五” |
next_monday | 将周六和周日移至下一个周一 |
假期和假期日历如何定义的示例:
In [259]: from pandas.tseries.holiday import (
.....: Holiday,
.....: USMemorialDay,
.....: AbstractHolidayCalendar,
.....: nearest_workday,
.....: MO,
.....: )
.....:
In [260]: class ExampleCalendar(AbstractHolidayCalendar):
.....: rules = [
.....: USMemorialDay,
.....: Holiday("July 4th", month=7, day=4, observance=nearest_workday),
.....: Holiday(
.....: "Columbus Day",
.....: month=10,
.....: day=1,
.....: offset=pd.DateOffset(weekday=MO(2)),
.....: ),
.....: ]
.....:
In [261]: cal = ExampleCalendar()
In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
提示:
weekday=MO(2) 等同于 2 * Week(weekday=2)
使用此日历,创建索引或进行偏移算术会跳过周末和假期(例如,阵亡将士纪念日/7 月 4 日)。例如,以下定义了使用ExampleCalendar
创建自定义工作日偏移。与任何其他偏移一样,它可以用于创建DatetimeIndex
或添加到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 来覆盖这些日期。
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)
In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)
In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
每个日历类都可以通过名称使用get_calendar
函数访问,该函数返回一个假期类实例。任何导入的日历类都将自动通过此函数可用。此外,HolidayCalendarFactory
提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay
In [275]: cal = get_calendar("ExampleCalendar")
In [276]: cal.rules
Out[276]:
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)
In [278]: new_cal.rules
Out[278]:
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO(+1)>),
Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
参数化偏移
创建时,某些偏移可以“参数化”以产生不同的行为。例如,用于生成每周数据的Week
偏移接受一个weekday
参数,这将导致生成的日期始终落在一周的特定某一天:
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)
In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)
In [169]: d + pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')
In [170]: d + pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')
In [171]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4
In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00')
normalize
选项将对加法和减法产生影响。
In [173]: d + pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')
In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00')
另一个示例是使用特定结束月份对YearEnd
进行参数化:
In [175]: d + pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')
In [176]: d + pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00')
使用Series
/ DatetimeIndex
进行偏移
偏移可以与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
类,可用于创建考虑到本地节假日和本地周末惯例的定制工作日日历。
作为一个有趣的例子,让我们看一下埃及,那里观察到周五至周六的周末。
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"
# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
.....: "2012-05-01",
.....: datetime.datetime(2013, 5, 1),
.....: np.datetime64("2014-05-01"),
.....: ]
.....:
In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
.....: holidays=holidays,
.....: weekmask=weekmask_egypt,
.....: )
.....:
In [191]: dt = datetime.datetime(2013, 4, 30)
In [192]: dt + 2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00')
让我们映射到工作日名称:
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)
In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]:
2013-04-30 Tue
2013-05-02 Thu
2013-05-05 Sun
2013-05-06 Mon
2013-05-07 Tue
Freq: C, dtype: object
节假日日历可用于提供节假日列表。有关更多信息,请参阅节假日日历部分。
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')
可以以通常的方式定义尊重某个节假日日历的月度偏移量。
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)
In [201]: dt + bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')
# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]:
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
注意
频率字符串 ‘C’ 用于指示使用 CustomBusinessDay DateOffset,重要的是要注意,由于 CustomBusinessDay 是一个参数化类型,CustomBusinessDay 的实例可能会有所不同,这在 ‘C’ 频率字符串中是不可检测到的。因此,用户需要确保在用户应用程序中一致使用 ‘C’ 频率字符串。
营业时间
BusinessHour
类在 BusinessDay
上提供了营业时间的表示,允许使用特定的开始和结束时间。
默认情况下,BusinessHour
使用 9:00 - 17:00 作为营业时间。添加 BusinessHour
将按小时频率递增 Timestamp
。如果目标 Timestamp
超出营业时间,则移到下一个营业时间然后递增。如果结果超出营业时间结束,则剩余时间添加到下一个营业日。
In [203]: bh = pd.offsets.BusinessHour()
In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>
# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4
In [206]: pd.Timestamp("2014-08-01 10:00") + bh
Out[206]: Timestamp('2014-08-01 11:00:00')
# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [207]: pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')
# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')
# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')
# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')
# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')
您还可以通过关键字指定 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
不是某个月的第一天,则最后返回的时间戳将是对应月份的第一天。
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
。
合并别名
正如我们之前所看到的,别名和偏移实例在大多数函数中是可互换的:
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
您可以组合一起日和日内偏移:
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]:
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140min')
In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]:
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010us')
锚定偏移
对于一些频率,您可以指定一个锚定后缀:
别名 | 描述 |
---|---|
W-SUN | 每周频率(周日)。与‘W’相同 |
W-MON | 每周频率(周一) |
W-TUE | 每周频率(周二) |
W-WED | 每周频率(周三) |
W-THU | 每周频率(周四) |
W-FRI | 每周频率(周五) |
W-SAT | 每周频率(周六) |
(B)Q(E)(S)-DEC | 季度频率,年底在十二月。与‘QE’相同 |
(B)Q(E)(S)-JAN | 季度频率,年底在一月 |
(B)Q(E)(S)-FEB | 季度频率,年底在二月 |
(B)Q(E)(S)-MAR | 季度频率,年底在三月 |
(B)Q(E)(S)-APR | 季度频率,年底在四月 |
(B)Q(E)(S)-MAY | 季度频率,年底在五月 |
(B)Q(E)(S)-JUN | 季度频率,年底在六月 |
(B)Q(E)(S)-JUL | 季度频率,年底在七月 |
(B)Q(E)(S)-AUG | 季度频率,年底在八月 |
(B)Q(E)(S)-SEP | 季度频率,年底在九月 |
(B)Q(E)(S)-OCT | 季度频率,年底在十月 |
(B)Q(E)(S)-NOV | 季度频率,年底在十一月 |
(B)Y(E)(S)-DEC | 年度频率,锚定在十二月底。与‘YE’相同 |
(B)Y(E)(S)-JAN | 年度频率,锚定在一月底 |
(B)Y(E)(S)-FEB | 年度频率,锚定在二月底 |
(B)Y(E)(S)-MAR | 年度频率,锚定在三月底 |
(B)Y(E)(S)-APR | 年度频率,锚定在四月底 |
(B)Y(E)(S)-MAY | 年度频率,锚定在五月底 |
(B)Y(E)(S)-JUN | 年度频率,锚定在六月底 |
(B)Y(E)(S)-JUL | 年度频率,锚定在七月底 |
(B)Y(E)(S)-AUG | 年度频率,8 月底锚定 |
(B)Y(E)(S)-SEP | 年度频率,9 月底锚定 |
(B)Y(E)(S)-OCT | 年度频率,10 月底锚定 |
(B)Y(E)(S)-NOV | 年度频率,11 月底锚定 |
这些可以作为date_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 日),一个遵守规则确定了如果假期落在周末或其他非观察日时如何观察。定义的遵守规则有:
Rule | Description |
---|---|
nearest_workday | 将周六移动到周五,周日移动到周一 |
sunday_to_monday | 将周日移动到下一个周一 |
next_monday_or_tuesday | 将周六移动到周一,周日/周一移动到周二 |
previous_friday | 将周六和周日移动到上一个星期五” |
next_monday | 将周六和周日移动到下一个周一 |
定义假期和假日日历的示例:
In [259]: from pandas.tseries.holiday import (
.....: Holiday,
.....: USMemorialDay,
.....: AbstractHolidayCalendar,
.....: nearest_workday,
.....: MO,
.....: )
.....:
In [260]: class ExampleCalendar(AbstractHolidayCalendar):
.....: rules = [
.....: USMemorialDay,
.....: Holiday("July 4th", month=7, day=4, observance=nearest_workday),
.....: Holiday(
.....: "Columbus Day",
.....: month=10,
.....: day=1,
.....: offset=pd.DateOffset(weekday=MO(2)),
.....: ),
.....: ]
.....:
In [261]: cal = ExampleCalendar()
In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
提示:
weekday=MO(2) 等同于 2 * Week(weekday=2)
使用此日历,创建索引或进行偏移算术时会跳过周末和假期(例如,阵亡将士纪念日/7 月 4 日)。例如,以下定义了使用ExampleCalendar
的自定义工作日偏移量。与任何其他偏移量一样,它可以用于创建DatetimeIndex
或添加到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 来覆盖。
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)
In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)
In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
每个日历类都可以通过名称使用get_calendar
函数访问,该函数返回一个节假日类实例。任何导入的日历类都将自动通过此函数可用。此外,HolidayCalendarFactory
提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay
In [275]: cal = get_calendar("ExampleCalendar")
In [276]: cal.rules
Out[276]:
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)
In [278]: new_cal.rules
Out[278]:
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO(+1)>),
Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff27fdb0b80>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
与时间序列相关的实例方法
Shifting / lagging
有时可能需要将时间序列中的值向前或向后移动。用于此操作的方法是shift()
,可用于所有 pandas 对象。
In [279]: ts = pd.Series(range(len(rng)), index=rng)
In [280]: ts = ts[:5]
In [281]: ts.shift(1)
Out[281]:
2012-01-01 NaN
2012-01-02 0.0
2012-01-03 1.0
Freq: D, dtype: float64
shift
方法接受一个freq
参数,可以接受一个DateOffset
类或其他类似timedelta
的对象,也可以是一个 offset alias。
当指定freq
时,shift
方法会更改索引中的所有日期,而不是更改数据和索引的对齐:
In [282]: ts.shift(5, freq="D")
Out[282]:
2012-01-06 0
2012-01-07 1
2012-01-08 2
Freq: D, dtype: int64
In [283]: ts.shift(5, freq=pd.offsets.BDay())
Out[283]:
2012-01-06 0
2012-01-09 1
2012-01-10 2
dtype: int64
In [284]: ts.shift(5, freq="BME")
Out[284]:
2012-05-31 0
2012-05-31 1
2012-05-31 2
dtype: int64
请注意,当指定freq
时,前导条目不再是 NaN,因为数据没有被重新对齐。
频率转换
更改频率的主要函数是asfreq()
方法。对于DatetimeIndex
,这基本上只是reindex()
的一个薄包装,它生成一个date_range
并调用reindex
。
In [285]: dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())
In [286]: ts = pd.Series(np.random.randn(3), index=dr)
In [287]: ts
Out[287]:
2010-01-01 1.494522
2010-01-06 -0.778425
2010-01-11 -0.253355
Freq: 3B, dtype: float64
In [288]: ts.asfreq(pd.offsets.BDay())
Out[288]:
2010-01-01 1.494522
2010-01-04 NaN
2010-01-05 NaN
2010-01-06 -0.778425
2010-01-07 NaN
2010-01-08 NaN
2010-01-11 -0.253355
Freq: B, dtype: float64
asfreq
提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。
In [289]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]:
2010-01-01 1.494522
2010-01-04 1.494522
2010-01-05 1.494522
2010-01-06 -0.778425
2010-01-07 -0.778425
2010-01-08 -0.778425
2010-01-11 -0.253355
Freq: B, dtype: float64
向前/向后填充
与asfreq
和reindex
相关的是fillna()
,该方法在 missing data section 中有文档记录。
转换为 Python 日期时间
DatetimeIndex
可以使用to_pydatetime
方法转换为 Python 本机datetime.datetime
对象的数组。
Shifting / lagging
有时可能需要将时间序列中的值向前或向后移动。用于此操作的方法是shift()
,可用于所有 pandas 对象。
In [279]: ts = pd.Series(range(len(rng)), index=rng)
In [280]: ts = ts[:5]
In [281]: ts.shift(1)
Out[281]:
2012-01-01 NaN
2012-01-02 0.0
2012-01-03 1.0
Freq: D, dtype: float64
shift
方法接受一个freq
参数,可以接受一个DateOffset
类或其他类似timedelta
的对象,也可以是一个 offset alias。
当指定freq
时,shift
方法会更改索引中的所有日期,而不是更改数据和索引的对齐:
In [282]: ts.shift(5, freq="D")
Out[282]:
2012-01-06 0
2012-01-07 1
2012-01-08 2
Freq: D, dtype: int64
In [283]: ts.shift(5, freq=pd.offsets.BDay())
Out[283]:
2012-01-06 0
2012-01-09 1
2012-01-10 2
dtype: int64
In [284]: ts.shift(5, freq="BME")
Out[284]:
2012-05-31 0
2012-05-31 1
2012-05-31 2
dtype: int64
请注意,当指定freq
时,前导条目不再是 NaN,因为数据没有被重新对齐。
频率转换
更改频率的主要函数是 asfreq()
方法。对于 DatetimeIndex
,这基本上只是一个薄的但方便的包装器,围绕 reindex()
生成一个 date_range
并调用 reindex
。
In [285]: dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())
In [286]: ts = pd.Series(np.random.randn(3), index=dr)
In [287]: ts
Out[287]:
2010-01-01 1.494522
2010-01-06 -0.778425
2010-01-11 -0.253355
Freq: 3B, dtype: float64
In [288]: ts.asfreq(pd.offsets.BDay())
Out[288]:
2010-01-01 1.494522
2010-01-04 NaN
2010-01-05 NaN
2010-01-06 -0.778425
2010-01-07 NaN
2010-01-08 NaN
2010-01-11 -0.253355
Freq: B, dtype: float64
asfreq
提供了进一步的便利,因此您可以为频率转换后可能出现的任何间隙指定插值方法。
In [289]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]:
2010-01-01 1.494522
2010-01-04 1.494522
2010-01-05 1.494522
2010-01-06 -0.778425
2010-01-07 -0.778425
2010-01-08 -0.778425
2010-01-11 -0.253355
Freq: B, dtype: float64
向前/向后填充
与 asfreq
和 reindex
相关的是 fillna()
,该方法在 缺失数据部分有详细说明。
转换为 Python 日期时间
DatetimeIndex
可以使用 to_pydatetime
方法转换为 Python 原生的 datetime.datetime
对象数组。
重采样
pandas 在频率转换期间执行重采样操作(例如,将秒数据转换为 5 分钟数据)具有简单、强大和高效的功能。这在金融应用中非常常见,但不限于此。
resample()
是基于时间的分组,后跟对每个组的减少方法。查看一些 示例 以了解一些高级策略。
resample()
方法可以直接从 DataFrameGroupBy
对象中使用,请参阅 groupby 文档。
基础知识
In [290]: rng = pd.date_range("1/1/2012", periods=100, freq="s")
In [291]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
In [292]: ts.resample("5Min").sum()
Out[292]:
2012-01-01 25103
Freq: 5min, dtype: int64
resample
函数非常灵活,允许您指定许多不同的参数来控制频率转换和重采样操作。
通过 GroupBy 可用的任何内置方法都可以作为返回对象的方法使用,包括 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
注意星期日的值被拉回到上一个星期五。要使星期日的值推到星期一,可以使用以下方法:
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
是基于时间的 groupby,以下是一种有效地仅重新采样不全为NaN
的组的方法。
In [308]: rng = pd.date_range("2014-1-1", periods=100, freq="D") + pd.Timedelta("1s")
In [309]: ts = pd.Series(range(100), index=rng)
如果我们想要重新采样到系列的完整范围:
In [310]: ts.resample("3min").sum()
Out[310]:
2014-01-01 00:00:00 0
2014-01-01 00:03:00 0
2014-01-01 00:06:00 0
2014-01-01 00:09:00 0
2014-01-01 00:12:00 0
..
2014-04-09 23:48:00 0
2014-04-09 23:51:00 0
2014-04-09 23:54:00 0
2014-04-09 23:57:00 0
2014-04-10 00:00:00 99
Freq: 3min, Length: 47521, dtype: int64
相反,我们可以仅重新采样那些具有点的组,如下所示:
In [311]: from functools import partial
In [312]: from pandas.tseries.frequencies import to_offset
In [313]: def round(t, freq):
.....: freq = to_offset(freq)
.....: td = pd.Timedelta(freq)
.....: return pd.Timestamp((t.value // td.value) * td.value)
.....:
In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]:
2014-01-01 0
2014-01-02 1
2014-01-03 2
2014-01-04 3
2014-01-05 4
..
2014-04-06 95
2014-04-07 96
2014-04-08 97
2014-04-09 98
2014-04-10 99
Length: 100, dtype: int64
聚合
resample()
方法返回一个pandas.api.typing.Resampler
实例。类似于聚合 API、groupby API 和 window API,Resampler
可以被选择性地重新采样。
对于DataFrame
进行重新采样,默认情况下将对所有列执行相同的函数。
In [315]: df = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: index=pd.date_range("1/1/2012", freq="s", periods=1000),
.....: columns=["A", "B", "C"],
.....: )
.....:
In [316]: r = df.resample("3min")
In [317]: r.mean()
Out[317]:
A B C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00 0.056909 0.146731 -0.024320
2012-01-01 00:06:00 -0.058837 0.047046 -0.052021
2012-01-01 00:09:00 0.063123 -0.026158 -0.066533
2012-01-01 00:12:00 0.186340 -0.003144 0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
通过标准的 getitem 方法,我们可以选择特定的列或多列。
In [318]: r["A"].mean()
Out[318]:
2012-01-01 00:00:00 -0.033823
2012-01-01 00:03:00 0.056909
2012-01-01 00:06:00 -0.058837
2012-01-01 00:09:00 0.063123
2012-01-01 00:12:00 0.186340
2012-01-01 00:15:00 -0.085954
Freq: 3min, Name: A, dtype: float64
In [319]: r[["A", "B"]].mean()
Out[319]:
A B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00 0.056909 0.146731
2012-01-01 00:06:00 -0.058837 0.047046
2012-01-01 00:09:00 0.063123 -0.026158
2012-01-01 00:12:00 0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287
您可以传递一个函数列表或字典进行聚合,输出一个DataFrame
:
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
在重新采样的DataFrame
上,您可以传递一个函数列表以应用于每列���从而产生具有分层索引的聚合结果:
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
通过将字典传递给aggregate
,您可以对DataFrame
的列应用不同的聚合:
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:
In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
此外,您还可以为每列单独指定多个聚合函数。
In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]:
A B
sum std mean std
2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294
2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597
2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309
2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953
2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025
2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
如果一个DataFrame
没有 datetimelike 索引,而是您想要基于帧中的 datetimelike 列进行重新采样,可以传递给on
关键字。
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
同样,如果您希望按照MultiIndex
的 datetimelike 级别重新采样,可以将其名称或位置传递给level
关键字。
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
``` ### 遍历组
有了`Resampler`对象,通过分组数据进行迭代非常自然,并且类似于[`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"):
```py
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="\n\n")
.....:
Group: 2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00 0
2017-01-01 00:30:00 1
2017-01-01 00:31:00 2
dtype: int64
Group: 2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00 3
dtype: int64
Group: 2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)
Group: 2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00 4
2017-01-01 03:05:00 5
dtype: int64
查看 遍历分组 或 Resampler.__iter__
了解更多。### 使用 origin
或 offset
调整箱的开始
分组的箱根据时间序列起始点的日期开始进行调整。这在频率是天的倍数(如 30D
)或者能够均匀分割一天的频率(如 90s
或 1min
)时效果很好。这可能会导致一些不符合此标准的频率出现不一致。要更改此行为,您可以使用参数 origin
指定一个固定的时间戳。
例如:
In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
In [333]: middle = "2000-10-02 00:00:00"
In [334]: rng = pd.date_range(start, end, freq="7min")
In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
In [336]: ts
Out[336]:
2000-10-01 23:30:00 0
2000-10-01 23:37:00 3
2000-10-01 23:44:00 6
2000-10-01 23:51:00 9
2000-10-01 23:58:00 12
2000-10-02 00:05:00 15
2000-10-02 00:12:00 18
2000-10-02 00:19:00 21
2000-10-02 00:26:00 24
Freq: 7min, dtype: int64
在使用默认值 ('start_day'
) 的 origin
时,我们可以看到,根据时间序列的开始,'2000-10-02 00:00:00'
之后的结果是不相同的:
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
在将 origin
设置为 'epoch'
时,我们可以看到,根据时间序列的开始,'2000-10-02 00:00:00'
之后的结果是相同的:
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
如果需要,您可以使用自定义时间戳来设置 origin
:
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
如果需要,您可以通过添加到默认 origin
的 offset
时间增量来调整箱。对于这个时间序列,这两个示例是等效的:
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
请注意在最后一个示例中使用 'start'
作为 origin
。在这种情况下,origin
将设置为时间序列的第一个值。
向后重采样
版本 1.3.0 中的新功能。
与调整箱的开始不同,有时我们需要固定箱的结束,以便使用给定的 freq
进行向后重采样。向后重采样默认将 closed
设置为 'right'
,因为最后一个值应被视为最后一个箱的边界点。
我们可以将 origin
设置为 'end'
。特定 Timestamp
索引的值表示从当前 Timestamp
减去 freq
到当前 Timestamp
的右闭区间的重采样结果。
In [345]: ts.resample('17min', origin='end').sum()
Out[345]:
2000-10-01 23:35:00 0
2000-10-01 23:52:00 18
2000-10-02 00:09:00 27
2000-10-02 00:26:00 63
Freq: 17min, dtype: int64
此外,与 'start_day'
选项相反,支持 end_day
。这将将原点设置为最大 Timestamp
的午夜。
In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]:
2000-10-01 23:38:00 3
2000-10-01 23:55:00 15
2000-10-02 00:12:00 45
2000-10-02 00:29:00 45
Freq: 17min, dtype: int64
由于以下计算,上述结果使用 2000-10-02 00:29:00
作为最后一个箱的右边缘。
In [347]: ceil_mid = rng.max().ceil('D')
In [348]: freq = pd.offsets.Minute(17)
In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)
In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00')
基础知识
In [290]: rng = pd.date_range("1/1/2012", periods=100, freq="s")
In [291]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
In [292]: ts.resample("5Min").sum()
Out[292]:
2012-01-01 25103
Freq: 5min, dtype: int64
resample
函数非常灵活,允许您指定许多不同的参数来控制频率转换和重采样操作。
通过 GroupBy 可用的任何内置方法都可以作为返回对象的方法使用,包括 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’ 的 label
和 closed
是相同的,除了 ‘ME’、‘YE’、‘QE’、‘BME’、‘BYE’、‘BQE’ 和 ‘W’,它们的默认值都是 ‘right’。
这可能会意外地导致向前查看,其中稍后时间的值被拉回到先前的时间,如下例所示,使用BusinessDay
频率:
In [300]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()
In [301]: s.iloc[2] = pd.NaT
In [302]: s.dt.day_name()
Out[302]:
2000-01-01 Saturday
2000-01-02 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: D, dtype: object
# default: label='left', closed='left'
In [303]: s.resample("B").last().dt.day_name()
Out[303]:
1999-12-31 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: B, dtype: object
注意星期日的值被拉回到前一个星期五。要使星期日的值推到星期一,可以使用以下方式
In [304]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[304]:
2000-01-03 Sunday
2000-01-04 Tuesday
2000-01-05 Wednesday
2000-01-06 NaN
Freq: B, dtype: object
axis
参数可以设置为 0 或 1,允许您对DataFrame
的指定轴进行重新采样。
kind
可以设置为'timestamp'或'period',以将生成的索引转换为时间戳和时间跨度表示。默认情况下,resample
保留输入表示。
当重新采样周期数据时,可以将convention
设置为'start'或'end'(下面有详细信息)。它指定了如何将低频周期转换为高频周期。
上采样
对于上采样,您可以指定一种上采样方式和limit
参数,以插值填补创建的间隙:
# from secondly to every 250 milliseconds
In [305]: ts[:2].resample("250ms").asfreq()
Out[305]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 NaN
2012-01-01 00:00:00.500 NaN
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
In [306]: ts[:2].resample("250ms").ffill()
Out[306]:
2012-01-01 00:00:00.000 308
2012-01-01 00:00:00.250 308
2012-01-01 00:00:00.500 308
2012-01-01 00:00:00.750 308
2012-01-01 00:00:01.000 204
Freq: 250ms, dtype: int64
In [307]: ts[:2].resample("250ms").ffill(limit=2)
Out[307]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 308.0
2012-01-01 00:00:00.500 308.0
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
稀疏重新采样
稀疏时间序列是指您要重新采样的时间点相对于您要重新采样的时间量要少得多。简单地对稀疏系列进行上采样可能会产生大量中间值。当您不想使用填充这些值的方法时,例如fill_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)
如果我们想要重新采样到系列的完整范围:
In [310]: ts.resample("3min").sum()
Out[310]:
2014-01-01 00:00:00 0
2014-01-01 00:03:00 0
2014-01-01 00:06:00 0
2014-01-01 00:09:00 0
2014-01-01 00:12:00 0
..
2014-04-09 23:48:00 0
2014-04-09 23:51:00 0
2014-04-09 23:54:00 0
2014-04-09 23:57:00 0
2014-04-10 00:00:00 99
Freq: 3min, Length: 47521, dtype: int64
相反,我们可以只重新采样那些具有数据点的组,如下所示:
In [311]: from functools import partial
In [312]: from pandas.tseries.frequencies import to_offset
In [313]: def round(t, freq):
.....: freq = to_offset(freq)
.....: td = pd.Timedelta(freq)
.....: return pd.Timestamp((t.value // td.value) * td.value)
.....:
In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]:
2014-01-01 0
2014-01-02 1
2014-01-03 2
2014-01-04 3
2014-01-05 4
..
2014-04-06 95
2014-04-07 96
2014-04-08 97
2014-04-09 98
2014-04-10 99
Length: 100, dtype: int64
聚合
resample()
方法返回一个pandas.api.typing.Resampler
实例。类似于聚合 API、分组 API 和窗口 API,Resampler
可以选择性地重新采样。
对DataFrame
进行重新采样时,默认情况下将对所有列使用相同的函数。
In [315]: df = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: index=pd.date_range("1/1/2012", freq="s", periods=1000),
.....: columns=["A", "B", "C"],
.....: )
.....:
In [316]: r = df.resample("3min")
In [317]: r.mean()
Out[317]:
A B C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00 0.056909 0.146731 -0.024320
2012-01-01 00:06:00 -0.058837 0.047046 -0.052021
2012-01-01 00:09:00 0.063123 -0.026158 -0.066533
2012-01-01 00:12:00 0.186340 -0.003144 0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
我们可以使用标准的 getitem 选择特定列或列。
In [318]: r["A"].mean()
Out[318]:
2012-01-01 00:00:00 -0.033823
2012-01-01 00:03:00 0.056909
2012-01-01 00:06:00 -0.058837
2012-01-01 00:09:00 0.063123
2012-01-01 00:12:00 0.186340
2012-01-01 00:15:00 -0.085954
Freq: 3min, Name: A, dtype: float64
In [319]: r[["A", "B"]].mean()
Out[319]:
A B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00 0.056909 0.146731
2012-01-01 00:06:00 -0.058837 0.047046
2012-01-01 00:09:00 0.063123 -0.026158
2012-01-01 00:12:00 0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287
您可以传递要进行聚合的函数列表或字典,输出一个DataFrame
:
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
在重新采样的DataFrame
上,您可以传递要应用于每列的函数列表,这将产生具有分层索引的聚合结果:
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
通过将字典传递给aggregate
,您可以对DataFrame
的列应用不同的聚合方式:
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
函数名称也可以是字符串。为了使字符串有效,必须在重新采样对象上实现它:
In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
此外,您还可以为每列单独指定多个聚合函数。
In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]:
A B
sum std mean std
2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294
2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597
2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309
2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953
2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025
2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
如果DataFrame
没有日期时间索引,而是您想要基于帧中的日期时间列进行重新采样,则可以将其传递给on
关键字。
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
类似地,如果您希望按照MultiIndex
的 datetimelike 级别重新采样,可以将其名称或位置传递给level
关键字。
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
遍历分组
拥有Resampler
对象后,通过分组数据进行迭代非常自然,并且类似于itertools.groupby()
的功能:
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="\n\n")
.....:
Group: 2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00 0
2017-01-01 00:30:00 1
2017-01-01 00:31:00 2
dtype: int64
Group: 2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00 3
dtype: int64
Group: 2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)
Group: 2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00 4
2017-01-01 03:05:00 5
dtype: int64
查看遍历分组或Resampler.__iter__
获取更多信息。
使用origin
或offset
调整 bins 的开始
分组的 bins 根据时间序列起始点的当天开始进行调整。这适用于是天数的倍数(如30D
)或者能够均匀分割一天的频率(如90s
或1min
)。这可能会导致一些不符合这些标准的频率出现不一致。要更改此行为,可以使用参数origin
指定一个固定的 Timestamp。
例如:
In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
In [333]: middle = "2000-10-02 00:00:00"
In [334]: rng = pd.date_range(start, end, freq="7min")
In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
In [336]: ts
Out[336]:
2000-10-01 23:30:00 0
2000-10-01 23:37:00 3
2000-10-01 23:44:00 6
2000-10-01 23:51:00 9
2000-10-01 23:58:00 12
2000-10-02 00:05:00 15
2000-10-02 00:12:00 18
2000-10-02 00:19:00 21
2000-10-02 00:26:00 24
Freq: 7min, dtype: int64
在这里我们可以看到,当使用具有默认值'start_day'
的origin
时,'2000-10-02 00:00:00'
之后的结果取决于时间序列的开始:
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
在这里我们可以看到,当将origin
设置为'epoch'
时,'2000-10-02 00:00:00'
之后的结果取决于时间序列的开始:
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
如果需要,可以使用自定义时间戳作为origin
:
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
如果需要,您可以通过添加到默认origin
的offset
Timedelta 来调整 bins。对于这个时间序列,这两个示例是等效的:
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
注意在最后一个示例中将origin
设置为'start'
的用法。在这种情况下,origin
将被设置为时间序列的第一个值。
向后重新采样
版本 1.3.0 中新增。
有时,我们需要固定 bins 的结束而不是调整 bins 的开始,以便使用给定的freq
进行向后重新采样。向后重新采样默认将closed
设置为'right'
,因为最后一个值应被视为最后一个 bin 的边界点。
我们可以将origin
设置为'end'
。特定Timestamp
索引的值代表从当前Timestamp
减去freq
到当前Timestamp
的右闭区间的重新采样结果。
In [345]: ts.resample('17min', origin='end').sum()
Out[345]:
2000-10-01 23:35:00 0
2000-10-01 23:52:00 18
2000-10-02 00:09:00 27
2000-10-02 00:26:00 63
Freq: 17min, dtype: int64
此外,与'start_day'
选项相比,支持end_day
。这将将原点设置为最大Timestamp
的午夜。
In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]:
2000-10-01 23:38:00 3
2000-10-01 23:55:00 15
2000-10-02 00:12:00 45
2000-10-02 00:29:00 45
Freq: 17min, dtype: int64
以上结果使用2000-10-02 00:29:00
作为最后一个 bin 的右边缘,因为以下计算。
In [347]: ceil_mid = rng.max().ceil('D')
In [348]: freq = pd.offsets.Minute(17)
In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)
In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00')
时间跨度表示
在 pandas 中,Period
对象表示正规时间间隔,而Period
对象的序列则被收集在PeriodIndex
中,可以使用便利函数period_range
创建。
时期
Period
表示一段时间(例如,一天,一个月,一个季度等)。您可以通过使用频率别名来指定freq
关键字来指定跨度。因为freq
表示Period
的跨度,所以不能像“-3D”那样是负数。
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')
In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')
In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')
In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h')
从周期中添加和减去整数会按照其自身频率移动周期。不允许在具有不同freq
(跨度)的Period
之间进行算术运算。
In [355]: p = pd.Period("2012", freq="Y-DEC")
In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')
In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')
In [358]: p = pd.Period("2012-01", freq="2M")
In [359]: p + 2
Out[359]: Period('2012-05', '2M')
In [360]: p - 1
Out[360]: Period('2011-11', '2M')
In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False
如果Period
频率为每天或更高(D
,h
,min
,s
,ms
,us
和ns
),则如果结果可以具有相同的频率,则可以添加offsets
和类似于timedelta
的内容。否则,将引发ValueError
。
In [362]: p = pd.Period("2014-07-01 09:00", freq="h")
In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')
In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')
In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()
File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()
ValueError: Cannot losslessly convert units
The above exception was the direct cause of the following exception:
IncompatibleFrequency Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)
File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
IncompatibleFrequency: Input cannot be converted to Period(freq=h)
如果Period
具有其他频率,只能添加相同的offsets
。否则,将引发ValueError
。
In [367]: p = pd.Period("2014-07", freq="M")
In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)
File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()
File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()
IncompatibleFrequency: Input has different freq=3M from Period(freq=M)
具有相同频率的Period
实例之间的差异将返回它们之间的频率单位数:
In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12>
PeriodIndex 和 period_range
Period
对象的常规序列可以收集在PeriodIndex
中,可以使用period_range
便捷函数构建:
In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")
In [372]: prng
Out[372]:
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
'2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
'2012-01'],
dtype='period[M]')
PeriodIndex
构造函数也可以直接使用:
In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
传递乘以频率的输出将返回一个具有乘以跨度的Period
序列。
In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')
如果start
或end
是Period
对象,则它们将用作与PeriodIndex
构造函数频率匹配的锚定端点。
In [375]: pd.period_range(
.....: start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
.....: )
.....:
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')
就像DatetimeIndex
一样,PeriodIndex
也可以用于索引 pandas 对象:
In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)
In [377]: ps
Out[377]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
PeriodIndex
支持与Period
相同规则的加法和减法。
In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")
In [379]: idx
Out[379]:
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
'2014-07-01 12:00', '2014-07-01 13:00'],
dtype='period[h]')
In [380]: idx + pd.offsets.Hour(2)
Out[380]:
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
'2014-07-01 14:00', '2014-07-01 15:00'],
dtype='period[h]')
In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")
In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')
In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')
PeriodIndex
有自己的名为period
的 dtype,请参考 Period Dtypes。
Period dtypes
PeriodIndex
具有自定义的period
dtype。这是类似于时区感知 dtype(datetime64[ns, tz]
)的 pandas 扩展 dtype。
period
dtype 保存freq
属性,并且用period[freq]
表示,如period[D]
或period[M]
,使用频率字符串。
In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")
In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')
In [386]: pi.dtype
Out[386]: period[M]
period
dtype 可以在.astype(...)
中使用。它允许您像.asfreq()
一样更改PeriodIndex
的freq
,并将DatetimeIndex
转换为PeriodIndex
,如to_period()
:
# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')
# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')
# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)
In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')
In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
PeriodIndex 部分字符串索引
PeriodIndex 现在支持具有非单调索引的部分字符串切片。
您可以像DatetimeIndex
一样向Series
和DataFrame
传递日期和字符串,使用PeriodIndex
,有关详细信息,请参考 DatetimeIndex 部分字符串索引。
In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507
In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]:
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]:
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
传递表示低于PeriodIndex
频率的字符串将返回部分切片数据。
In [395]: ps["2011"]
Out[395]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
In [396]: dfp = pd.DataFrame(
.....: np.random.randn(600, 1),
.....: columns=["A"],
.....: index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
.....: )
.....:
In [397]: dfp
Out[397]:
A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
... ...
2013-01-01 18:55 0.522460
2013-01-01 18:56 0.118710
2013-01-01 18:57 0.167517
2013-01-01 18:58 0.922883
2013-01-01 18:59 1.721104
[600 rows x 1 columns]
In [398]: dfp.loc["2013-01-01 10h"]
Out[398]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59 0.802298
[60 rows x 1 columns]
与DatetimeIndex
一样,结果中将包括端点。下面的示例从 10:00 开始切片数据到 11:59。
In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56 1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58 0.578798
2013-01-01 11:59 -0.685496
[120 rows x 1 columns]
使用 PeriodIndex 进行频率转换和重采样
可以通过asfreq
方法将Period
和PeriodIndex
的频率转换。让我们从 2011 财政年度开始,截至 12 月:
In [400]: p = pd.Period("2011", freq="Y-DEC")
In [401]: p
Out[401]: Period('2011', 'Y-DEC')
我们可以将其转换为月度频率。使用how
参数,我们可以指定返回起始还是结束月份:
In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')
In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M')
提供了‘s’和‘e’的简写形式以方便使用:
In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')
In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M')
转换为“超级周期”(例如,年度频率是季度频率的超级周期)将自动返回包含输入周期的超级周期:
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
请注意,由于我们转换为以十一月结束的年度频率,因此 2011 年 12 月的月份实际上属于 2012 年的 Y-NOV 期间。
使用锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年的第一季度可能在 2010 年开始,也可能在 2011 年的几个月内开始。通过锚定频率,pandas 可以处理所有季度频率Q-JAN
到Q-DEC
。
Q-DEC
定义了常规的日历季度:
In [408]: p = pd.Period("2012Q1", freq="Q-DEC")
In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')
In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D')
Q-MAR
定义了财政年度截止于三月:
In [411]: p = pd.Period("2011Q4", freq="Q-MAR")
In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')
In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D')
Period
Period
表示一段时间(例如一天、一个月、一个季度等)。您可以通过使用类似下面的频率别名的freq
关键字来指定这段时间。因为freq
表示Period
的时间跨度,所以它不能像“-3D”那样是负数。
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')
In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')
In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')
In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h')
从期间中加减整数会按照其自身频率移动期间。不允许在具有不同freq
(时间跨度)的Period
之间进行算术运算。
In [355]: p = pd.Period("2012", freq="Y-DEC")
In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')
In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')
In [358]: p = pd.Period("2012-01", freq="2M")
In [359]: p + 2
Out[359]: Period('2012-05', '2M')
In [360]: p - 1
Out[360]: Period('2011-11', '2M')
In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False
如果Period
的频率是每天或更高(D
、h
、min
、s
、ms
、us
和ns
),则可以添加offsets
和类似于timedelta
的内容,如果结果具有相同的频率,则可以添加。否则,将引发ValueError
。
In [362]: p = pd.Period("2014-07-01 09:00", freq="h")
In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')
In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')
In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()
File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()
ValueError: Cannot losslessly convert units
The above exception was the direct cause of the following exception:
IncompatibleFrequency Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)
File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
IncompatibleFrequency: Input cannot be converted to Period(freq=h)
如果Period
具有其他频率,则只能添加相同的offsets
。否则,将引发ValueError
。
In [367]: p = pd.Period("2014-07", freq="M")
In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)
File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()
File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()
IncompatibleFrequency: Input has different freq=3M from Period(freq=M)
使用相同频率的Period
实例之间的差异将返回它们之间的频率单位数:
In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12>
PeriodIndex 和 period_range
Period
对象的常规序列可以收集在PeriodIndex
中,可以使用period_range
便利函数构建:
In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")
In [372]: prng
Out[372]:
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
'2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
'2012-01'],
dtype='period[M]')
PeriodIndex
构造函数也可以直接使用:
In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
传递乘以的频率会输出一个具有乘以时间跨度的Period
序列。
In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')
如果start
或end
是Period
对象,则它们将被用作与PeriodIndex
构造函数的频率匹配的锚定端点。
In [375]: pd.period_range(
.....: start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
.....: )
.....:
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')
就像DatetimeIndex
一样,PeriodIndex
也可以用于索引 pandas 对象:
In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)
In [377]: ps
Out[377]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
PeriodIndex
支持与Period
相同的加法和减法规则。
In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")
In [379]: idx
Out[379]:
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
'2014-07-01 12:00', '2014-07-01 13:00'],
dtype='period[h]')
In [380]: idx + pd.offsets.Hour(2)
Out[380]:
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
'2014-07-01 14:00', '2014-07-01 15:00'],
dtype='period[h]')
In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")
In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')
In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')
PeriodIndex
有自己的名为period
的数据类型,请参考 Period 数据类型。
Period 数据类型
PeriodIndex
具有自定义的period
数据类型。这是一种类似于时区感知数据类型(datetime64[ns, tz]
)的 pandas 扩展数据类型。
period
数据类型保存了freq
属性,并用period[freq]
表示,例如period[D]
或period[M]
,使用频率字符串。
In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")
In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')
In [386]: pi.dtype
Out[386]: period[M]
period
数据类型可以在.astype(...)
中使用。它允许您像.asfreq()
那样更改PeriodIndex
的freq
,并像to_period()
那样将DatetimeIndex
转换为PeriodIndex
:
# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')
# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')
# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)
In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')
In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
PeriodIndex 部分字符串索引
PeriodIndex 现在支持对非单调索引进行部分字符串切片。
您可以像DatetimeIndex
一样向Series
和DataFrame
传递日期和字符串,使用PeriodIndex
。有关详细信息,请参考 DatetimeIndex 部分字符串索引。
In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507
In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]:
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]:
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
传递表示低于PeriodIndex
的频率的字符串将返回部分切片数据。
In [395]: ps["2011"]
Out[395]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
In [396]: dfp = pd.DataFrame(
.....: np.random.randn(600, 1),
.....: columns=["A"],
.....: index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
.....: )
.....:
In [397]: dfp
Out[397]:
A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
... ...
2013-01-01 18:55 0.522460
2013-01-01 18:56 0.118710
2013-01-01 18:57 0.167517
2013-01-01 18:58 0.922883
2013-01-01 18:59 1.721104
[600 rows x 1 columns]
In [398]: dfp.loc["2013-01-01 10h"]
Out[398]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59 0.802298
[60 rows x 1 columns]
与DatetimeIndex
一样,结果将包括端点。下面的示例从 10:00 开始切片数据到 11:59。
In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56 1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58 0.578798
2013-01-01 11:59 -0.685496
[120 rows x 1 columns]
使用PeriodIndex
进行频率转换和重采样
Period
和PeriodIndex
的频率可以通过asfreq
方法进行转换。让我们从 2011 财政年度开始,截至 12 月:
In [400]: p = pd.Period("2011", freq="Y-DEC")
In [401]: p
Out[401]: Period('2011', 'Y-DEC')
我们可以将其转换为月度频率。使用how
参数,我们可以指定返回起始月份还是结束月份:
In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')
In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M')
提供了‘s’和‘e’的简写以方便使用:
In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')
In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M')
转换为“超期”(例如,年度频率是季度频率的超期)将自动返回包含输入期间的超期:
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
请注意,由于我们转换为以 11 月结束的年度频率,因此 2011 年 12 月的月度期间实际上在 2012 年 Y-NOV 期间。
具有锚定频率的期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度定义为其财政年度开始和结束的月份。因此,2011 年第一季度可能从 2010 年开始,或者在 2011 年的几个月内开始。通过锚定频率,pandas 适用于所有季度频率Q-JAN
到Q-DEC
。
Q-DEC
定义了常规日历季度:
In [408]: p = pd.Period("2012Q1", freq="Q-DEC")
In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')
In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D')
Q-MAR
定义了以 3 月结束的财政年度:
In [411]: p = pd.Period("2011Q4", freq="Q-MAR")
In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')
In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D')
在不同表示之间转换
时间戳数据可以使用to_period
转换为 PeriodIndex 数据,反之亦然使用to_timestamp
:
In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")
In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [416]: ts
Out[416]:
2012-01-31 1.931253
2012-02-29 -0.184594
2012-03-31 0.249656
2012-04-30 -0.978151
2012-05-31 -0.873389
Freq: ME, dtype: float64
In [417]: ps = ts.to_period()
In [418]: ps
Out[418]:
2012-01 1.931253
2012-02 -0.184594
2012-03 0.249656
2012-04 -0.978151
2012-05 -0.873389
Freq: M, dtype: float64
In [419]: ps.to_timestamp()
Out[419]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
请记住,‘s’和‘e’可用于返回期间的开始或结束时间戳:
In [420]: ps.to_timestamp("D", how="s")
Out[420]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
在周期和时间戳之间进行转换可以使用一些方便的算术函数。在下面的示例中,我们将以 11 月结束的年度季度频率转换为季度结束后月底的上午 9 点:
In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")
In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)
In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9
In [424]: ts.head()
Out[424]:
1990-03-01 09:00 -0.109291
1990-06-01 09:00 -0.637235
1990-09-01 09:00 -1.735925
1990-12-01 09:00 2.096946
1991-03-01 09:00 -1.039926
Freq: h, dtype: float64
表示超出范围的跨度
如果您的数据超出了Timestamp
的范围,请参阅 Timestamp 限制,那么您可以使用PeriodIndex
和/或Periods
的Series
进行计算。
In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")
In [426]: span
Out[426]:
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
'1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
'1215-01-09', '1215-01-10',
...
'1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
'1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
'1380-12-31', '1381-01-01'],
dtype='period[D]', length=60632)
转换为基于int64
的 YYYYMMDD 表示。
In [427]: s = pd.Series([20121231, 20141130, 99991231])
In [428]: s
Out[428]:
0 20121231
1 20141130
2 99991231
dtype: int64
In [429]: def conv(x):
.....: return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
.....:
In [430]: s.apply(conv)
Out[430]:
0 2012-12-31
1 2014-11-30
2 9999-12-31
dtype: period[D]
In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D')
这些可以轻松转换为PeriodIndex
:
In [432]: span = pd.PeriodIndex(s.apply(conv))
In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]')
时区处理
pandas 提供了丰富的支持,可以使用pytz
和dateutil
库或标准库中的datetime.timezone
对象来处理不同时区的时间戳。
处理时区
默认情况下,pandas 对象不考虑时区:
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")
In [435]: rng.tz is None
Out[435]: True
要将这些日期本地化到特定时区(为一个无时区日期分配特定时区),您可以使用 tz_localize
方法或 date_range()
、Timestamp
或 DatetimeIndex
中的 tz
关键字参数。您可以传递 pytz
或 dateutil
时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回 pytz
时区对象。要返回 dateutil
时区对象,请在字符串前附加 dateutil/
。
-
在
pytz
中,您可以使用from pytz import common_timezones, all_timezones
找到常见(和不太常见)时区列表。 -
dateutil
使用操作系统的时区,因此没有固定的可用列表。对于常见时区,名称与pytz
相同。
In [436]: import dateutil
# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")
In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")
In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')
# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=dateutil.tz.tzutc(),
.....: )
.....:
In [443]: rng_utc.tz
Out[443]: tzutc()
# datetime.timezone
In [444]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=datetime.timezone.utc,
.....: )
.....:
In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc
请注意,UTC
时区在 dateutil
中是一个特殊情况,应明确构造为 dateutil.tz.tzutc
的实例。您也可以首先明确构造其他时区对象。
In [446]: import pytz
# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")
In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)
In [450]: rng_pytz.tz == tz_pytz
Out[450]: True
# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")
In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)
In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True
要将一个时区感知的 pandas 对象从一个时区转换为另一个时区,您可以使用 tz_convert
方法。
In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]:
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
'2012-03-07 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
注意
当使用 pytz
时区时,DatetimeIndex
会为相同的时区输入构造一个不同的时区对象,而 Timestamp
则不同。DatetimeIndex
可以保存可能具有不同 UTC 偏移的一组 Timestamp
对象,并且不能用一个 pytz
时区实例简洁地表示,而一个 Timestamp
代表具有特定 UTC 偏移的一个时间点。
In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")
In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")
In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
警告
要注意在库之间进行转换。对于一些时区,pytz
和 dateutil
对时区的定义不同。这对于不寻常的时区比‘标准’时区(如 US/Eastern
)更成问题。
警告
请注意,跨版本的时区库可能不被视为相等。这可能会在使用一个版本本地化的存储数据与使用不同版本操作时出现问题。请参阅此处了解如何处理这种情况。
警告
对于 pytz
时区,直接将时区对象传递给 datetime.datetime
构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))
)。相反,需要使用 pytz
时区对象上的 localize
方法对日期时间进行本地化。
警告
请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自政府更改。
警告
如果您使用的日期超过 2038-01-18,由于底层库中当前存在的由 2038 年问题引起的缺陷,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,夏令时转换将会被应用。
例如,对于处于英国夏令时的两个日期(因此通常为 GMT+1),以下断言都为真:
In [459]: d_2037 = "2037-03-31T010101"
In [460]: d_2038 = "2038-03-31T010101"
In [461]: DST = "Europe/London"
In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")
In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")
在底层,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndex
或Timestamp
的值将其字段(天、小时、分钟等)本地化到时区。然而,具有相同 UTC 值的时间戳即使位于不同时区仍被视为相等:
In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")
In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")
In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')
In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')
In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True
在不同时区之间的Series
操作将产生 UTC Series
,将数据对齐到 UTC 时间戳上:
In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))
In [470]: eastern = ts_utc.tz_convert("US/Eastern")
In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")
In [472]: result = eastern + berlin
In [473]: result
Out[473]:
2013-01-01 00:00:00+00:00 0
2013-01-02 00:00:00+00:00 2
2013-01-03 00:00:00+00:00 4
Freq: D, dtype: int64
In [474]: result.index
Out[474]:
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
'2013-01-03 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='D')
要删除时区信息,请使用tz_localize(None)
或tz_convert(None)
。tz_localize(None)
将删除时区,得到本地时间表示。tz_convert(None)
将在转换为 UTC 时间后删除时区。
In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")
In [476]: didx
Out[476]:
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
'2014-08-01 11:00:00-04:00'],
dtype='datetime64[ns, US/Eastern]', freq='h')
In [477]: didx.tz_localize(None)
Out[477]:
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
'2014-08-01 11:00:00'],
dtype='datetime64[ns]', freq=None)
In [478]: didx.tz_convert(None)
Out[478]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq='h')
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq=None)
折叠
对于模糊时间,pandas 支持显式指定仅关键字参数 fold。由于夏令时,从夏季时间到冬季时间转换时,一个挂钟时间可能会出现两次;fold 描述了 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime
构造(有关详细信息,请参阅datetime 文档)或从Timestamp
构造或从组件构造(见下文)。仅支持dateutil
时区(请参阅dateutil 文档以了解处理模糊日期时间的dateutil
方法)因为pytz
时区不支持 fold(请参阅pytz 文档以了解pytz
如何处理模糊日期时间的详细信息)。要使用pytz
本地化模糊日期时间,请使用Timestamp.tz_localize()
。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议依赖于Timestamp.tz_localize()
来本地化模糊日期时间。
In [480]: pd.Timestamp(
.....: datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
.....: tz="dateutil/Europe/London",
.....: fold=0,
.....: )
.....:
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')
In [481]: pd.Timestamp(
.....: year=2019,
.....: month=10,
.....: day=27,
.....: hour=1,
.....: minute=30,
.....: tz="dateutil/Europe/London",
.....: fold=1,
.....: )
.....:
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')
``` ### 本地化时的模糊时间
`tz_localize`可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时(DST)导致某些时间在一天内发生两次(“时钟回拨”)。以下选项可用:
+ `'raise'`: 抛出`pytz.AmbiguousTimeError`(默认行为)
+ `'infer'`: 尝试根据时间戳的单调性确定正确的偏移量
+ `'NaT'`: 用`NaT`替换模糊时间
+ `bool`: `True`表示夏令时,`False`表示非夏令时。支持类似数组的`bool`值序列。
```py
In [482]: rng_hourly = pd.DatetimeIndex(
.....: ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
.....: )
.....:
由于存在模糊时间('11/06/2011 01:00'
),这将失败。
In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument
通过指定以下内容来处理这些模糊时间。
In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
'2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
``` ### 本地化时不存在的时间
夏令时转换也可能使本地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前跳转”)。通过`nonexistent`参数可以控制本地化具有不存在时间的时间序列的行为。以下选项可用:
+ `'raise'`: 抛出`pytz.NonExistentTimeError`(默认行为)
+ `'NaT'`: 用`NaT`替换不存在的时间
+ `'shift_forward'`: 将不存在的时间向前移动到最接近的真实时间
+ `'shift_backward'`: 将不存在的时间向后移动到最接近的真实时间
+ timedelta 对象:通过时间间隔持续时间将不存在的时间向后移动
```py
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")
# 2:30 is a nonexistent time
默认情况下,本地化不存在的时间会引发错误。
In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
NonExistentTimeError: 2015-03-29 02:30:00
将不存在的时间转换为NaT
或移动时间。
In [489]: dti
Out[489]:
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
'2015-03-29 04:30:00'],
dtype='datetime64[ns]', freq='h')
In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]:
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]:
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
'2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]:
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]:
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
``` ### 时区 Series 操作
具有时区**naive**值的`Series`表示为`datetime64[ns]`的 dtype。
```py
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))
In [495]: s_naive
Out[495]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
dtype: datetime64[ns]
具有时区aware值的Series
表示为datetime64[ns, tz]
的 dtype,其中tz
是时区
In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))
In [497]: s_aware
Out[497]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
这两个Series
的时区信息可以通过.dt
访问器进行操作,参见 dt 访问器部分。
例如,将 naive 时间戳本地化并转换为时区感知。
In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]:
0 2012-12-31 19:00:00-05:00
1 2013-01-01 19:00:00-05:00
2 2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]
也可以使用astype
方法来操作时区信息。此方法可以在不同的时区感知 dtype 之间转换。
# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]:
0 2013-01-01 06:00:00+01:00
1 2013-01-02 06:00:00+01:00
2 2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]
注意
使用Series.to_numpy()
在Series
上,返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管在本地时区打印!),因此对于时区感知数据,返回时间戳的对象数组:
In [500]: s_naive.to_numpy()
Out[500]:
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
'2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')
In [501]: s_aware.to_numpy()
Out[501]:
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
dtype=object)
通过转换为时间戳的对象数组,它保留了时区信息。例如,当转换回 Series 时:
In [502]: pd.Series(s_aware.to_numpy())
Out[502]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
但是,如果您想要一个实际的 NumPydatetime64[ns]
数组(值转换为 UTC)而不是对象数组,可以指定dtype
参数:
In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]:
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
'2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')
处理时区
默认情况下,pandas 对象不具有时区感知:
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")
In [435]: rng.tz is None
Out[435]: True
要将这些日期本地化到时区(为 naive 日期分配特定时区),可以使用tz_localize
方法或date_range()
、Timestamp
或DatetimeIndex
中的tz
关键字参数。您可以传递pytz
或dateutil
时区对象或 Olson 时区数据库字符串。Olson 时区字符串将默认返回pytz
时区对象。要返回dateutil
时区对象,请在字符串之前附加dateutil/
。
-
在
pytz
中,您可以使用from pytz import common_timezones, all_timezones
找到常见(以及不太常见)时区的列表。 -
dateutil
使用操作系统时区,因此没有固定的可用列表。对于常见时区,名称与pytz
相同。
In [436]: import dateutil
# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")
In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")
In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')
# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=dateutil.tz.tzutc(),
.....: )
.....:
In [443]: rng_utc.tz
Out[443]: tzutc()
# datetime.timezone
In [444]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=datetime.timezone.utc,
.....: )
.....:
In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc
请注意,UTC
时区是dateutil
中的特殊情况,应明确构造为dateutil.tz.tzutc
的实例。您也可以首先明确构造其他时区对象。
In [446]: import pytz
# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")
In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)
In [450]: rng_pytz.tz == tz_pytz
Out[450]: True
# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")
In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)
In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True
要将一个时区感知的 pandas 对象从一个时区转换为另一个时区,可以使用tz_convert
方法。
In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]:
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
'2012-03-07 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
注意
使用pytz
时区时,DatetimeIndex
将为相同时区输入构造一个不同的时区对象,而Timestamp
将为具有特定 UTC 偏移的一个时间点。一个DatetimeIndex
可以保存一组具有不同 UTC 偏移的Timestamp
对象,无法用一个pytz
时区实例简洁地表示,而一个Timestamp
代表一个具有特定 UTC 偏移的时间点。
In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")
In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")
In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
警告
谨慎处理不同库之间的转换。对于一些时区,pytz
和 dateutil
对时区的定义可能不同。这对于不寻常的时区而言可能是一个问题,而对于像US/Eastern
这样的‘标准’时区则不太会出现这个问题。
警告
请注意,跨版本的时区库可能不会被视为相等。当使用一个版本本地化存储数据并使用另一个版本进行操作时,可能会出现问题。查看这里以了解如何处理这种情况。
警告
对于pytz
时区,直接将时区对象传递给datetime.datetime
构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))
)。相反,需要使用pytz
时区对象上的localize
方法对日期进行本地化。
警告
请注意,对于未来的时间,任何时区库都无法保证正确的时区(和 UTC)之间的转换,因为时区与 UTC 的偏移可能会被各自的政府更改。
警告
如果你使用的日期超过了 2038-01-18,由于当前底层库存在的年 2038 问题,时区感知日期的夏令时(DST)调整将不会被应用。如果底层库被修复,那么夏令时转换将会被应用。
例如,对于处于英国夏令时的两个日期(通常为 GMT+1),以下两个断言都会评估为真:
In [459]: d_2037 = "2037-03-31T010101"
In [460]: d_2038 = "2038-03-31T010101"
In [461]: DST = "Europe/London"
In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")
In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")
在幕后,所有时间戳都以 UTC 存储。来自时区感知的DatetimeIndex
或Timestamp
的值将其字段(日期、小时、分钟等)本地化到时区。然而,即使处于不同时区,具有相同 UTC 值的时间戳仍被视为相等:
In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")
In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")
In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')
In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')
In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True
在不同时区之间的Series
操作将产生 UTC Series
,将数据对齐到 UTC 时间戳上:
In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))
In [470]: eastern = ts_utc.tz_convert("US/Eastern")
In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")
In [472]: result = eastern + berlin
In [473]: result
Out[473]:
2013-01-01 00:00:00+00:00 0
2013-01-02 00:00:00+00:00 2
2013-01-03 00:00:00+00:00 4
Freq: D, dtype: int64
In [474]: result.index
Out[474]:
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
'2013-01-03 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='D')
要删除时区信息,请使用tz_localize(None)
或tz_convert(None)
。tz_localize(None)
将删除时区,产生本地时间表示。tz_convert(None)
将在转换为 UTC 时间后删除时区。
In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")
In [476]: didx
Out[476]:
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
'2014-08-01 11:00:00-04:00'],
dtype='datetime64[ns, US/Eastern]', freq='h')
In [477]: didx.tz_localize(None)
Out[477]:
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
'2014-08-01 11:00:00'],
dtype='datetime64[ns]', freq=None)
In [478]: didx.tz_convert(None)
Out[478]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq='h')
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq=None)
折叠
对于模糊时间,pandas 支持显式指定仅关键字的 fold 参数。由于夏令时,当从夏季转换到冬季时间时,一个挂钟时间可能会发生两次;fold 描述了 datetime-like 是否对应于挂钟第一次(0)或第二次(1)命中模糊时间。仅支持从 naive datetime.datetime
(有关详细信息,请参见datetime 文档)或从Timestamp
构建,或从组件构建(见下文)。仅支持dateutil
时区(有关处理模糊日期时间的dateutil
方法,请参见dateutil 文档)因为pytz
时区不支持 fold(有关pytz
处理模糊日期时间的详细信息,请参见pytz 文档)。要使用pytz
本地化模糊日期时间,请使用Timestamp.tz_localize()
。一般来说,如果需要直接控制处理模糊日期时间的方式,我们建议依赖于Timestamp.tz_localize()
。
In [480]: pd.Timestamp(
.....: datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
.....: tz="dateutil/Europe/London",
.....: fold=0,
.....: )
.....:
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')
In [481]: pd.Timestamp(
.....: year=2019,
.....: month=10,
.....: day=27,
.....: hour=1,
.....: minute=30,
.....: tz="dateutil/Europe/London",
.....: fold=1,
.....: )
.....:
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')
本地化时的模糊时间
tz_localize
可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时(DST)导致一天内的某些时间发生两次(“时钟回拨”)。有以下选项可用:
-
'raise'
: 引发pytz.AmbiguousTimeError
(默认行为) -
'infer'
: 尝试根据时间戳的单调性确定正确的偏移量 -
'NaT'
: 用NaT
替换模糊时间 -
bool
:True
表示 DST 时间,False
表示非 DST 时间。支持bool
值的数组形式用于一系列时间。
In [482]: rng_hourly = pd.DatetimeIndex(
.....: ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
.....: )
.....:
这将失败,因为存在模糊时间('11/06/2011 01:00'
)
In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument
通过指定以下方式处理这些模糊时间。
In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
'2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
本地化时的不存在时间
DST 转换也可能会将本地时间向前调整 1 小时,从而创建不存在的本地时间(“时钟向前跳转”)。通过nonexistent
参数可以控制本地化具有不存在时间的时间序列的行为。以下选项可用:
-
'raise'
:引发pytz.NonExistentTimeError
(默认行为) -
'NaT'
:用NaT
替换不存在的时间 -
'shift_forward'
:将不存在的时间向前移动到最接近的真实时间 -
'shift_backward'
:将不存在的时间向后移动到最接近的真实时间 -
timedelta 对象:通过时间增量将不存在的时间移动
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")
# 2:30 is a nonexistent time
默认情况下,对不存在的时间进行本地化将引发错误。
In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
NonExistentTimeError: 2015-03-29 02:30:00
将不存在的时间转换为NaT
或者移动时间。
In [489]: dti
Out[489]:
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
'2015-03-29 04:30:00'],
dtype='datetime64[ns]', freq='h')
In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]:
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]:
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
'2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]:
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]:
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
时区 Series 操作
具有时区naive值的Series
表示为datetime64[ns]
类型。
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))
In [495]: s_naive
Out[495]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
dtype: datetime64[ns]
具有时区aware值的Series
表示为datetime64[ns, tz]
类型,其中tz
是时区
In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))
In [497]: s_aware
Out[497]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
这两个Series
的时区信息可以通过.dt
访问器进行操作,参见 dt 访问器部分。
例如,将一个朴素时间戳本地化并转换为时区感知时间。
In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]:
0 2012-12-31 19:00:00-05:00
1 2013-01-01 19:00:00-05:00
2 2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]
也可以使用astype
方法来操作时区信息。该方法可以在不同的时区感知数据类型之间进行转换。
# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]:
0 2013-01-01 06:00:00+01:00
1 2013-01-02 06:00:00+01:00
2 2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]
注意
在Series
上使用Series.to_numpy()
,返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管它在本地时区打印!),因此对于时区感知数据,返回 Timestamps 对象数组:
In [500]: s_naive.to_numpy()
Out[500]:
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
'2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')
In [501]: s_aware.to_numpy()
Out[501]:
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
dtype=object)
通过将时间戳转换为 Timestamps 对象数组,可以保留时区信息。例如,当转换回 Series 时:
In [502]: pd.Series(s_aware.to_numpy())
Out[502]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
但是,如果您想要一个实际的 NumPy datetime64[ns]
数组(将值转换为 UTC)而不是对象数组,可以指定dtype
参数:
In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]:
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
'2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2023-06-26 Bert Pytorch 源码分析:五、模型架构简图
2023-06-26 Bert Pytorch 源码分析:四、编解码器
2023-06-26 Bert Pytorch 源码分析:三、Transformer块
2020-06-26 PythonGuru 中文系列教程·翻译完成