使用第一列和第二列填充第三列。

10
我是Python新手,有以下Pandas数据帧 - 我正在尝试编写代码来填充下面的“signal”列:
Days long_entry_flag long_exit_flag signal
1 FALSE TRUE
2 FALSE FALSE
3 TRUE FALSE 1
4 TRUE FALSE 1
5 FALSE FALSE 1
6 TRUE FALSE 1
7 TRUE FALSE 1
8 FALSE TRUE
9 FALSE TRUE
10 TRUE FALSE 1
11 TRUE FALSE 1
12 TRUE FALSE 1
13 FALSE FALSE 1
14 FALSE TRUE
15 FALSE FALSE
16 FALSE TRUE
17 TRUE FALSE 1
18 TRUE FALSE 1
19 FALSE FALSE 1
20 1
21
22
23

我的伪代码版本将采取以下步骤

  1. 查找 [‘long_entry_flag’] 列,直到出现 True 的入场条件(最初是第3天)
  2. 然后我们每天在 [‘signal’] 列中输入 '1' ,直到出现 True 的退场条件 [‘long_exit_flag’]==True (第8天)
  3. 接着回到 [‘long_entry_flag’] 列等待下一个入场条件(第10天出现)
  4. 再次每天在 [‘signal’] 列中输入 '1' ,直到出现 True 的退场条件(第14天)
  5. 等等。

有什么快速填充 ‘signal’ 列的方法吗?如果可能,可以使用矢量化技术吗?

这是一个数万行的大数据框的子集,并且它是正在顺序分析的许多数据框之一。

4个回答

7

您可以做以下操作

# Assuming we're starting from the "outside"
inside = False
for ix, row in df.iterrows():
    inside = (not row['long_exit_flag']
              if inside
              else row['long_entry_flag']
                  and not row['long_exit_flag']) # [True, True] case
    df.at[ix, 'signal'] = 1 if inside else np.nan

这将会给你与你发布的完全相同的输出。


@jezrael答案的启发,我创建了一个稍微更高效的版本,同时仍尽可能保持整洁:

# Same assumption of starting from the "outside"
df.at[0, 'signal'] = df.at[0, 'long_entry_flag']
for ix in df.index[1:]:
    df.at[ix, 'signal'] = (not df.at[ix, 'long_exit_flag']
                           if df.at[ix - 1, 'signal']
                           else df.at[ix, 'long_entry_flag']
                               and not df.at[ix, 'long_exit_flag']) # [True, True] case

# Adjust to match the requested output exactly
df['signal'] = df['signal'].replace([True, False], [1, np.nan])

我很感激你的建议,@jezrael。但是我的解决方案还有一个缺点,就是它需要一个状态。如果你有任何想法可以使它无状态,使用 apply(柯里化?)来保留状态或以向量化的方式实现,我将会第一个点赞。 - ayorgo
@ayorgo - 添加了解决方案。 - jezrael
@ayorgo,抱歉晚了一步编辑,但您的优雅解决方案完美地运行,除非在信号列中没有任何内容的同时有两个FALSE天 - 请参见第22-23天的附加行 - 在这种情况下,您的代码会在第23天的信号列中产生“1”,而实际上不应该如此 - 我是否错过了一个简单的修复方法?或者这现在是一个新问题? - Baz
@Baz,嗯,奇怪。在我的机器上可以运行。我的意思是,上面的两个解决方案都会在第23天产生“NaN”。 - ayorgo
然而,当两个信号都为“True”时,它的工作方式并不像我预期的那样,我刚刚修复了这个问题。 - ayorgo

5
为了提高性能,请使用 Numba 解决方案:
arr = df[['long_exit_flag','long_entry_flag']].values

@jit
def f(A):
    inside = False
    out = np.ones(len(A), dtype=float)
    for i in range(len(arr)):
        inside = not A[i, 0] if inside else A[i, 1]
        if not inside:
            out[i] = np.nan
    return out

df['signal'] = f(arr)

性能:

#[21000 rows x 5 columns]
df = pd.concat([df] * 1000, ignore_index=True)

In [189]: %%timeit
     ...: inside = False
     ...: for ix, row in df.iterrows():
     ...:     inside = not row['long_exit_flag'] if inside else row['long_entry_flag']
     ...:     df.at[ix, 'signal'] = 1 if inside else np.nan
     ...:
1.58 s ± 9.45 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [190]: %%timeit
     ...: arr = df[['long_exit_flag','long_entry_flag']].values
     ...:
     ...: @jit
     ...: def f(A):
     ...:     inside = False
     ...:     out = np.ones(len(A), dtype=float)
     ...:     for i in range(len(arr)):
     ...:         inside = not A[i, 0] if inside else A[i, 1]
     ...:         if not inside:
     ...:             out[i] = np.nan
     ...:     return out
     ...:
     ...: df['signal'] = f(arr)
     ...:
171 ms ± 2.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [200]: %%timeit
     ...: df['d'] = np.where(~df['long_exit_flag'],df['long_entry_flag'] | df['long_exit_flag'],np.nan)
     ...:
     ...: df['new_select']= np.where(df['d']==0, np.select([df['d'].shift()==0, df['d'].shift()==1],[1,1], np.nan), df['d'])
     ...:
2.4 ms ± 561 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

您可以使用NumPy进行移位,@Dark code也在简化它:
In [222]: %%timeit
     ...: d = np.where(~df['long_exit_flag'].values,  df['long_entry_flag'].values | df['long_exit_flag'].values, np.nan)
     ...: shifted = np.insert(d[:-1], 0, np.nan)
     ...: m = (shifted==0) | (shifted==1)
     ...: df['signal1']= np.select([d!=0, m], [d, 1], np.nan)
     ...:
590 µs ± 35.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

您还可以查看Pandas中各种操作性能的一般优先级,了解iterrows是否存在性能问题。

1
啊,好的。我忘了可以简单地迭代索引。总是在寻找最简洁的东西。谢谢。 - ayorgo
@jezrael,请检查一下我的方法的时间:) - Bharath M Shetty
似乎没有名为“Dark”的用户。它指的是哪篇帖子? - Peter Mortensen

3
这里提供了一种完整的布尔运算向量化方法,可以快速完成操作。 步骤1
If long_exit_flag is True return Np.nan else apply `or` between `long_entry_flag` and `long_exit_flag`

df['d'] = np.where(df['long_exit_flag'], np.nan, df['long_entry_flag'] | df['long_exit_flag'])

第二步:现在两列都为false的状态。我们需要忽略它并用先前的状态替换这些值。可以使用whereselect来完成:

df['new_signal']= np.where(df['d']==0,
                  np.select([df['d'].shift()==0, df['d'].shift()==1],[1,1], np.nan),
                  df['d'])

    Days  long_entry_flag  long_exit_flag  signal    d  new_signal
0      1            False            True     NaN  NaN         NaN
1      2            False           False     NaN  0.0         NaN
2      3             True           False     1.0  1.0         1.0
3      4             True           False     1.0  1.0         1.0
4      5            False           False     1.0  0.0         1.0
5      6             True           False     1.0  1.0         1.0
6      7             True           False     1.0  1.0         1.0
7      8            False            True     NaN  NaN         NaN
8      9            False            True     NaN  NaN         NaN
9     10             True           False     1.0  1.0         1.0
10    11             True           False     1.0  1.0         1.0
11    12             True           False     1.0  1.0         1.0
12    13            False           False     1.0  0.0         1.0
13    14            False            True     NaN  NaN         NaN
14    15            False           False     NaN  0.0         NaN
15    16            False            True     NaN  NaN         NaN
16    17             True           False     1.0  1.0         1.0
17    18             True           False     1.0  1.0         1.0
18    19            False           False     1.0  0.0         1.0
19    20            False           False     1.0  0.0         1.0
20    21            False            True     NaN  NaN         NaN

1
不错的解决方案,我尝试使用numpy进行优化 - 检查了我的答案并更新了时间。 - jezrael
我已经点赞了先生。也许还有一种情况,这个解决方案仍然无法覆盖。仍然很好奇。 - Bharath M Shetty

0
#let the long_exit_flag equal to 0 when the exit is TRUE
df['long_exit_flag_r'] = ~df.long_exit_flag_r
df.temp = ''

for i in range(1, len(df.index)):
    df.temp[i] = (df.signal[i-1] + df.long_entry_flag[i])*df.long_exit_flag_r

如果温度为正,则信号应为1,如果温度为负,则信号应为空。(我有点卡在这里。)

"temp" 是什么意思?温度还是临时的? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接