pandas range_date极其缓慢地降低了功能

时间:2017-10-13 11:16:23

标签: python pandas

我已经提供了一个样本数据集,并希望从原始样本数据集中选择多个样本,例如1000个样本块,每个样本块包含来自原始样本数据的500个数据点。我在python中编写了这个小函数:

import timeit
import pandas as pd
import numpy as np
sample_data = np.random.randn(10000, 15)
index = pd.date_range("20000101", periods=10000, freq='B')
sample_data_df = pd.DataFrame(sample_data, index=index)
def f(n, sample_data_df, f):
    s = (1+sample_data_df).resample(f, axis=0)
    r = s.prod()-1
    out = r.sample(n, replace=True)
    # out_index = pd.date_range(start=sample_data_df.index[0],
    #                              periods=len(out.index),
    #                              freq=f)
    # out.index = output_index
    return out


start_time = timeit.default_timer()
N = 1000
a = [f(500, sample_data_df, 'BM') for i in range(N)]
elapsed = timeit.default_timer() - start_time
print(elapsed)

如果我运行此代码,则需要35.8964748383秒。但是,我想在每个块上附加一个索引,我将取消注释函数中的行,即

def f(n, sample_data_df, f):
        s = (1+sample_data_df).resample(f, axis=0)
        r = s.prod()-1
        out = r.sample(n, replace=True)
        out_index = pd.date_range(start=sample_data_df.index[0],
                                  periods=len(out.index),
                                  freq=f)
        out.index = output_index
        return out

现在该功能需要 72.2418179512 。疯了吧。如果需要在每个输出中都有这样的索引,我怎样才能加快这个速度?我知道生成一次索引并将其随后附加到每个输出。但是,我想在其他情况下使用该函数,以便在索引的分配在函数内完成时将非常感激。

此外,除了索引还有其他来源可以提高速度吗?因为即使没有索引35.8964748383也是很长时间。

1 个答案:

答案 0 :(得分:3)

编辑:

  • 添加了创建新日期索引的时间
  • 添加了缓存函数以创建新索引

问题不在于重新采样或索引的速度,如果我们看一下时机:

%timeit (1+sample_data_df).resample('BM', axis=0).prod()-1
21.7 ms ± 170 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit pd.date_range(start="20000101", periods=500, freq='BM')
21.4 ms ± 272 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
考虑到我们正在对150'000元素进行重新采样和减少,22毫秒对我来说似乎并不坏。

你的问题来自1000,在你的情况下没有必要(因为你做的完全相同)。 如果要在函数中保留重新采样,可以执行的操作是缓存重新采样的结果。不幸的是,缓存函数结果(lru_cache)的标准方法是无法处理可变对象(如dfs,lists ......)。所以我的解决方案是将重新采样包装在一个函数中,该函数创建哈希并使用哈希作为参数调用实际函数:

from functools import lru_cache
class Sampler():
  def __init__(self, df):
    self.df = df

  def get_resampled_sample(self, n, freq):
    resampled = self._wraper_resample_prod(freq)
    return resampled.sample(n, replace=True)

  def _wraper_resample_prod(self, freq):
    hash_df = hash(self.df.values.tobytes())
    return self._resample_prod(hash_df, freq)

  @lru_cache(maxsize=1)  
  def _resample_prod(self, hash_df, freq):
    return (self.df+1).resample(freq, axis=0).prod()-1

现在,只要不更改df值的哈希值,就会缓存重新采样的结果。这意味着我们可以更快地采样。

%timeit [sampler.get_resampled_sample(500, 'BM') for i in range(1000)]
881 ms ± 10.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

您可以对索引执行相同的操作,但是在这里您不需要创建自定义哈希,因为pd.date_range的所有参数都是不可变对象。

class Sampler():
  def __init__(self, df):
    self.df = df

  def update_df(self, df):
    self.df = df

  def get_resampled_sample(self, n, freq):
    resampled = self._wraper_resample_prod(freq)
    df = resampled.sample(n, replace=True)
    df.index = self._create_date_range(self.df.index[0], n, freq)
    return df

  def _wraper_resample_prod(self, freq):
    hash_df = hash(self.df.values.tobytes())
    return self._resample_prod(hash_df, freq)

  @lru_cache(maxsize=1)  
  def _resample_prod(self, hash_df, freq):
    return (self.df+1).resample(freq, axis=0).prod()-1

  @lru_cache(maxsize=1)
  def _create_date_range(self, start, periods, freq):
    return pd.date_range(start=start, periods=periods, freq=freq)

时序:

%timeit [sampler.get_resampled_sample(500, 'BM') for i in range(1000)]
1.11 s ± 43.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)