Python:为什么使用 asfreq() 会删除我的最后一行数据?

Python: Why using asfreq() is dropping my last row of data?

我不明白为什么当我使用 pandas asfreq() 方法时它会删除我的最后一行数据。

data = rental_2012_09_to_2020_06.copy()

data.drop(columns='postcode', inplace=True)

data.columns = ['Quarter_End', 'Rental_Median']

data.index = pd.to_datetime(data['Quarter_End'])

data = data.asfreq(freq='Q', method='ffill')

data.drop(columns='Quarter_End', inplace=True)

data.info()
data.head()

结果运行 以上代码:

结果 运行 注释 asfreq() 行:

有什么想法吗?我阅读了文档,但没有提供有关此行为的详细信息。谢谢!

您按季度频率对 time-series 数据帧进行了重新采样:

data = data.asfreq(freq='Q', method='ffill')

根据pandas documentationQ频率别名代表“四分之一结束频率”。原始数据框中的第一个条目是 2012 年 9 月 1 日,最后一个条目是 2020 年 6 月 1 日。

我猜测 asfreq 会重新采样时间范围,以便重新采样的范围落在原始范围内(但据我所知,这没有记录)。由于 2012 年 1 月 9 日之后的第一季度末是 2012 年 9 月 30 日,而 2020 年 1 月 6 日之前的最后一个季度末是 2020 年 3 月 31 日,因此得出的日期范围是 2012 年 9 月 30 日到 2003 年 3 月 31 日/2020(包括两者),每季度一次,产生 31 个样本。这恰好比原始数据帧少一个样本这一事实仅仅是巧合(从某种意义上说,它取决于原始日期时间范围)。

编辑: 通过深入研究 pandas 源代码,我发现了这种行为的确切位置 defined/documented。在 asfreq 的调用图中,有一个生成器 generate_range(具体来说,pandas.core.arrays.datetimes.generate_range,从 v1.1.2 开始),它是在一个范围内定义日期时间值的核心功能[start, end] 值之间有一定的时间偏移(即频率)。

def generate_range(start=None, end=None, periods=None, offset=BDay()): ...

其文档字符串指定:

Notes
-----
* [...]
* If both start and end are specified, the returned dates will
satisfy start <= date <= end.

回复评论

我认为您可能误解了 asfreq 的作用。它不是简单地将值移动到落在频率边界上的最近时间;相反,它创建了一个全新的系列,使用来自原始数据的数据,就好像它是在特定时间戳(由频率指定)采样的一样。也就是说,您是 resampling 您的数据。

尝试从调用中删除 method='ffil' 参数:您会看到新系列将全部为 NaNs(除了时间戳与原始时间戳重合的情况series)—这是因为它不知道样本的值在未知时间戳应该是多少。

import numpy as np
import pandas as pd
import datetime as dtm

index = pd.DatetimeIndex(data=[
    dtm.datetime(2019, 12, 1),
    dtm.datetime(2020, 2, 17),
    dtm.datetime(2020, 3, 31),
    dtm.datetime(2020, 6, 1),
])

series = pd.Series([1, 2, 3, 4], index=index)
>>> series
2019-12-01    1
2020-02-17    2
2020-03-31    3
2020-06-01    4
dtype: int64

>>> series.asfreq('Q')
2019-12-31    NaN
2020-03-31    3.0
Freq: Q-DEC, dtype: float64

如果您考虑一下,这就非常有意义:如果原始数据中没有记录该日期的值,pandas 应该如何知道 2019-12-31 上的值?

当然,拥有一个充满 NaN 的系列并不是很有用,因此我们需要找到一种方法来 推断 从可用数据中丢失的数据.这就是 method='ffil'(复制最后一个可用值)或 method='bfil'(复制下一个可用值)等填充方法发挥作用的地方。

现在,回到您的问题:没有 2020-06-30 记录的唯一原因是上面 generate_range 的文档字符串指定的内容:

[...] the returned dates will satisfy start <= date <= end

即,转换后的日期时间范围将始终落在 原始范围内,而 2020-06-30 落在 范围之外(因为原始范围的 end 是 2020-06-01).

同样,考虑到我们刚才讨论的内容,这也是有道理的:通常您想要推断重采样序列中的缺失值(使用原始序列中的值),并且它总是更容易(也更安全) interpolate(即猜测值在两个其他值之间的时间步长内是什么),而不是 extrapolate(即猜测原始范围之外的值,无论是在开始之前还是在结束之后),而前者只有在将新的、重新采样的范围保持在原始范围内的情况下才能保证——更不用说 'ffil''bfil' 都需要这个条件为了正常工作(你不能 forward-fill 如果它出现在 start 之前的第一个值,你不能 backwards-fill 如果它出现在 end 之后的最后一个值)。

如果这不是您想要的行为,而您只是想简单地移动时间步长,则您必须完全执行其他操作。可以给索引加一个偏移量,例如:

index = pd.date_range(
    dtm.datetime(2019, 9, 1), 
    dtm.datetime(2020, 6, 1), 
    freq=pd.DateOffset(months=3)
)

series = pd.Series([1, 2, 3, 4], index=index)
>>> index
DatetimeIndex(['2019-09-01', '2019-12-01', '2020-03-01', '2020-06-01'], dtype='datetime64[ns]', freq='<DateOffset: months=3>')

>>> index + pd.tseries.offsets.MonthEnd()
DatetimeIndex(['2019-09-30', '2019-12-31', '2020-03-31', '2020-06-30'], dtype='datetime64[ns]', freq='<DateOffset: months=3>')