应用 returns 单个系列的组特定函数

Applying group-specific function that returns a single series

我正在尝试为以下场景找出一个有效的 split/apply/combine 方案。考虑下面定义的 pandas 数据框 demoAll

import datetime
import pandas as pd


demoA = pd.DataFrame({'date':[datetime.date(2010,1,1), datetime.date(2010,1,2), datetime.date(2010,1,3)],
                     'ticker':['A', 'A', 'A'],
                     'x1':[10,20,30],
                     'close':[120, 133, 129]}).set_index('date', drop=True)
demoB = pd.DataFrame({'date':[datetime.date(2010,1,1), datetime.date(2010,1,2), datetime.date(2010,1,3)],
                     'ticker':['B', 'B', 'B'],
                     'x1':[18,11,45],
                     'close':[50, 49, 51]}).set_index('date', drop=True)
demoAll = pd.concat([demoA, demoB])
print(demoAll)

结果是:

           ticker  x1  close
date                        
2010-01-01      A  10    120
2010-01-02      A  20    133
2010-01-03      A  30    129
2010-01-01      B  18     50
2010-01-02      B  11     49
2010-01-03      B  45     51

我还有一个代码到模型对象的字典映射

ticker2model = {'A':model_A, 'B':model_B,...}

其中每个模型都有一个 self.predict(df) 方法接收整个数据帧和 returns 一系列相同长度的数据。

我现在想创建一个与这些预测相对应的新列 demoAll['predictions']。 cleanest/most-efficient 这样做的方法是什么?需要注意的几点:

  1. demoAll 是代码特定数据帧的串联,每个数据帧仅按日期索引。因此 demoAll 的索引不是唯一的。 (但是,date/ticker 的组合是唯一的。)

  2. 我的想法是做类似下面示例的事情,但是 运行 遇到了索引、数据类型强制转换和慢 运行 时间等问题。真实数据集相当大(包括行和列)。

    demoAll['predictions'] = demoAll.groupby('ticker').apply(
                               lambda x: ticker2model[x.name].predict(x)
                             )
    

我可能误解了您通过模型进行预测的内容,但如果我理解正确,我会执行以下操作:

  1. 预分配 demoAll
  2. predictions
  3. 遍历代码和过滤器的唯一值 demoAll
  4. 过滤掉代码行
  5. 使用过滤后的 df 预测结果
  6. 将结果保存在 demoAll['predictions']
  7. 中的正确位置

使用您的代码的示例:

# get non 'ticker' columns
non_ticker_cols = [col for col in demoAll.columns if col is not 'ticker']
# get unique set of tickers 
tickers = demoAll.ticker.unique()
# create and prepopulate the predictions column
demoAll['predictions'] = 0

for ticker in tickers:
    # get boolean Series to filter the Dataframes by.
    filter_by_ticker = demoAll.ticker == ticker
    # filter, predict and allocate 
    demoAll.loc[filter_by_ticker, 'predictions'] = ticker2model[
        ticker].predict(
        demoAll.loc[filter_by_ticker,
                    non_ticker_cols]
    )        

输出如下:

         ticker x1  close   predictions
date                
2010-01-01  A   10  120 10.0
2010-01-02  A   20  133 10.0
2010-01-03  A   30  129 10.0
2010-01-01  B   18  50  100.0
2010-01-02  B   11  49  100.0
2010-01-03  B   45  51  100.0

与使用应用的比较

我们可以按行使用应用程序,但正如您提到的,它会变慢。我将比较两者以了解加速。

设置

我将使用 sklearn 中的 DummyRegressor 来允许我调用 predict 方法并创建您在问题中提到的字典。

model_a = DummyRegressor(strategy='mean')
model_b = DummyRegressor(strategy='median')

model_a.fit([[10,14]], y=np.array([10]))
model_b.fit([[200,200]], [100])
ticker2model = {'A':model_a, 'B':model_b}

将两者定义为函数

def predict_by_ticker_filter(df, model_dict):
    # get non 'ticker' columns
    non_ticker_cols = [col for col in df.columns if col is not 'ticker']
    # get unique set of tickers 
    tickers = df.ticker.unique()
    # create and prepopulate the predictions column
    df['predictions'] = 0

    for ticker in tickers:
        # get boolean Series to filter the Dataframes by.
        filter_by_ticker = df.ticker==ticker
        # filter, predict and allocate 
        df.loc[filter_by_ticker,'predictions'] = model_dict[ticker].predict(
            df.loc[filter_by_ticker,
                   non_ticker_cols]
        )
    return df

def model_apply_by_row(df_row, model_dict):
    # includes some conversions to list to allow the predict method to run
    return model_dict[df_row['ticker']].predict([df_row[['x1','close']].tolist()])[0]

性能 我在函数调用中使用 timeit 得到以下结果

你的例子demoAll:

model_apply_by_row

%timeit demoAll.apply(model_apply_by_row,model_dict=ticker2model, axis=1)

3.78 ms ± 227 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

predict_by_ticker_filter

%timeit predict_by_ticker_filter(demoAll, ticker2model)

6.24 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

demoAll 的大小增加到 (606, 3):

model_apply_by_row

%timeit demoAll.apply(model_apply_by_row,model_dict=ticker2model, axis=1)

320 ms ± 28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

predict_by_ticker_filter

%timeit predict_by_ticker_filter(demoAll, ticker2model)

6.1 ms ± 512 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

demoAll 的大小增加到 (6006, 3):

model_apply_by_row

%timeit demoAll.apply(model_apply_by_row,model_dict=ticker2model, axis=1)

3.15 s ± 371 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

predict_by_ticker_filter

%timeit predict_by_ticker_filter(demoAll, ticker2model)

9.1 ms ± 767 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)