将行分组到一个新的 Pandas DataFrame 中,每组一行

Posted

技术标签:

【中文标题】将行分组到一个新的 Pandas DataFrame 中,每组一行【英文标题】:Grouping rows into a new Pandas DataFrame with one row per group 【发布时间】:2022-01-21 02:50:32 【问题描述】:

我有以下数据框:

from datetime import datetime as dt
import numpy as np
import pandas as pd 

inputs = 
    'indicator':[69.88, 85.05, 50.19, 71.08, 44.83, 36.32, 29.42, 44.47, 34.71, 37.91, 32.78, 35.85, 38.98, 23.16, 73.22, 77.77, 49.22, 59.1, 83.38, 88.5, 47.78],
    'short_trade':[0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0],
    'pnl':[-0.0, -0.0, 0.05, -0.06, 0.05, 0.0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, -0.0, 0.0, -0.0, -0.0, 0.01, -0.0, -0.0, -0.01, 0.03]



_idx = pd.date_range('2018-08-10','2018-09-09',freq='D').to_series()
_idx = _idx[_idx.dt.dayofweek < 5]

data = pd.DataFrame(inputs, index = _idx)

我的目标是创建一个新的 DataFrame,如下面的屏幕截图所示。在short_trade != 0pnl != 0 时进行分组(相同)。

新 DataFrame (trade_n) 的第一列只是每个不同交易的 ID。新列 pnl 是初始 DataFrame 中每个组的总和。最后,duration (D) 是每笔交易持续的天数。

我找到了一种解决方法,循环遍历 DataFrame 并检查每一行,但我很确定使用 pandas/numpy 有一个更有效的解决方案。

【问题讨论】:

您选择的逻辑是什么?因为您首先选择 3 行,然后选择 1,然后选择 2 您要选择 pnl != 0 的行吗?加上之前的 0 是的,或者 short_trade != 0,都是一样的。将编辑Q,谢谢指出 在您的预期输出中,第二行的pnl0.01 - 不应该是0.005,因为这是0.000.01 的平均值,或者应该是它被夹在0.01 @richardec 这只是short_trade == 1 时我会得到的价格(回报)的pct_change()。稍后我使用该列进行cumsum(),然后获得总 PnL。我没有在此处发布该部分,因为我想让它尽可能简单(出于同样的目的,我还对 pnlindicator 数字进行了四舍五入) 【参考方案1】:

试试这个:

s = df \
    .groupby((df['short_trade'].astype(bool) | df['short_trade'].shift(1)).diff().cumsum()) \
    .apply(lambda x: [x.shape[0] - 1, x['pnl'].tolist()]) \
    [::2] \
    .reset_index(drop=True) \
    .tolist()
    
df = pd.DataFrame(s, columns=['duration (D)', 'pnl']) \
    .reset_index() \
    .rename('index': 'trade_n', axis=1)

输出:

>>> df
   trade_n  duration (D)                        pnl
0        0             3  [-0.0, 0.05, -0.06, 0.05]
1        1             1               [-0.0, 0.01]
2        2             2        [-0.0, -0.01, 0.03]

【讨论】:

应该过滤您在pnl 中的列表,并且您必须只保留只有pnl 不为0 的值并对其应用总和。 @Corralien,我将pnl 作为列表留下,因为 OP 在 cmets 中说输出 df 中 pnl 的值来自 pct_change,所以我怀疑它可能是最好只保留原始值。【参考方案2】:

IIUC:

m = df.short_trade.ne(0) | df.pnl.ne(0)
g = mask.eq(True) & mask.shift().eq(False)

out = df.assign(trade_n=g.cumsum().sub(1)[m]).groupby('trade_n') \
        .agg(**'pnl': ('pnl', lambda x: sum(x[x.ne(0)])),
                'duration (D)': ('short_trade', lambda x: len(x.ne(0)))) \
        .reset_index().astype('trade_n': int)

输出:

>>> out
   trade_n   pnl  duration (D)
0        0  0.04             4
1        1  0.01             2
2        2  0.02             3

【讨论】:

以上是关于将行分组到一个新的 Pandas DataFrame 中,每组一行的主要内容,如果未能解决你的问题,请参考以下文章

pandas一些基本操作(DataFram和Series)_1

pandas一些基本操作(DataFram和Series)_2

pandas一些基本操作(DataFram和Series)_3

pandas一些基本操作(DataFram和Series)_4

python Pandas - 将行附加到数据帧

将行附加到 pandas DataFrame 而不制作新副本