【问题标题】:How to quickly subset many dataframes?如何快速子集许多数据帧?
【发布时间】:2021-02-19 09:22:35
【问题描述】:

我有 180 个DataFrame 对象,每个对象有 3130 行,内存大约 300KB。 索引为DatetimeIndex,工作日从2000-01-03到2011-12-31:

from datetime import datetime
import pandas as pd
freq = pd.tseries.offsets.BDay()

index = pd.date_range(datetime(2000,1,3), datetime(2011,12,31), freq=freq)

df = pd.DataFrame(index=index)
df['A'] = 1000.0
df['B'] = 2000.0
df['C'] = 3000.0
df['D'] = 4000.0
df['E'] = 5000.0
df['F'] = True
df['G'] = 1.0
df['H'] = 100.0

我利用 numpy/pandas 矢量化对所有数据进行预处理,然后我必须每天循环遍历数据帧。为了防止“前瞻性偏差”的可能性并从未来获取数据,我必须确保每天我只返回我的数据帧的一个子集,直到那个数据点。我解释一下:如果我正在处理的当前数据点是datetime(2010,5,15),我需要从datetime(2000,1,3)datetime(2010,5,15) 的数据。您应该无法访问比datetime(2010,5,15) 更新的数据。有了这个子集,我将进行其他无法矢量化的计算,因为它们是路径相关的。

我像这样修改了我的原始循环:

def get_data(datapoint):
    return df.loc[:datapoint]
    
calendar = df.index

for datapoint in calendar:
    x = get_data(datapoint)   

这种代码非常慢。提高速度的最佳选择是什么? 如果我不尝试防止前瞻偏差,我的生产代码大约需要 3 分钟才能运行,但风险太大。使用这样的代码需要 13 分钟,这是不可接受的。

%%timeit

一个稍快的选项是使用iloc 而不是loc,但它仍然很慢:

def get_data2(datapoint):
    idx = df.index.get_loc(datapoint)
    return df.iloc[:idx]

for datapoint in calendar:
    x = get_data(datapoint)  

每个循环 371 毫秒 ± 23.2 毫秒(平均值 ± 标准偏差,7 次运行,每个循环 1 个)

for datapoint in calendar:
    x = get_data2(datapoint)

每个循环 327 毫秒 ± 7.05 毫秒(平均值 ± 标准偏差,7 次运行,每个循环 1 个)

原始代码并未试图防止前瞻偏差的可能性,而是在为每个数据点调用时简单地返回整个DataFrame。在这个例子中要快 100 倍,实际代码要快 4 倍。

def get_data_no_check():
    return df

for datapoint in calendar:
    x = get_data_no_check() 

每个循环 2.87 ms ± 89.8 µs(平均值 ± 标准偏差,7 次运行,每次 100 个循环)

【问题讨论】:

  • 如果您有预设的数据点值,为什么不直接使用df.loc[:datapoint]?为什么必须遍历数据框的所有索引?循环遍历所有索引并每次定位到该索引,这实际上不会对任何内容进行子集化。这有用吗?
  • 这就是运行 3 分钟的代码所做的,但它是有风险的。您可能会感到疲倦并为 datapoint 输入错误的值,您将从未来获取数据,这是我试图阻止的
  • 您不能先对日期进行排序以始终确保当前日期之后的日期都在当前日期之后吗? df.sort_values(by='date',ascending=True)
  • 索引已经排序
  • 我认为您可以设计出一个日期的方法,只需将其称为数据点,即一个工作日,并验证为过去或现在的日期,而不是未来的日期。然后只需使用您的 df 切片来获取您想要的日期范围。

标签: python pandas dataframe numpy


【解决方案1】:

看看这是否适合你:

datapoint_range = pd.date_range(datetime(2000,1,3), datetime.now(), freq=freq)
datapoint = datapoint_range[-1]

逻辑是:将结束日期替换为今天,以确保不是将来的日期。然后获取范围的最后日期。

然后使用您的df.loc[:datapoint] 获取您想要的范围。

【讨论】:

  • 在我的示例中,结束日期是 2011-12-31。使用 datetime.now() 已经是“未来”...
【解决方案2】:

我是这样解决的:首先我预处​​理DataFrame 中的所有数据以利用pandas 矢量化,然后将其转换为dict of dict 并对其进行迭代,以防止出现“前瞻偏差”的可能性。由于数据已经过预处理,我可以避免DataFrame 开销。生产代码处理速度的提升让我无语:从30多分钟降到40秒!

# Convert the DataFrame into a dict of dict

for s, data in self._data.items():
    self._data[s] = data.to_dict(orient='index')

【讨论】:

    猜你喜欢
    • 2019-09-01
    • 2013-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-02
    • 1970-01-01
    • 2016-05-16
    • 1970-01-01
    相关资源
    最近更新 更多