Je suis un débutant en Python et possède le cadre de données Pandas suivant: j’essaie d’écrire du code qui renseigne la colonne ‘signal’ comme ci-dessous:
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 FALSE FALSE 1
21 FALSE TRUE
22 FALSE FALSE
23 FALSE FALSE
Ma version pseudo-code
suivrait les étapes suivantes
Idées bienvenues sur les moyens de remplir rapidement la colonne ‘signal’ si possible (en utilisant la vectorisation?) - il s’agit d’un sous-ensemble d’une grande base de données comportant des dizaines de milliers de lignes, et il s’agit d’une des nombreuses images analysées en séquence.
Merci d'avance!
Tu peux faire
# 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
ce qui va vous donner exactement le résultat que vous avez posté.
Inspiré par la réponse de @ jezrael , j'ai créé une version légèrement plus performante de ce qui précède tout en essayant de la garder aussi nette que possible:
# 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])
Pour améliorer les performances, utilisez la solution 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)
Performance:
#[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)
Vous pouvez également utiliser numpy pour le décalage, le code @Dark est simplifié:
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)
MODIFIER:
Vous pouvez également vérifier Est-ce que iterrows a des problèmes de performances? pour l'ordre de priorité général pour l'exécution de diverses opérations sur des pandas.
Voici une approche avec des opérations booléennes complètes qui est une approche vectorisée et qui sera rapide.
Étape 1 : Si long_exit_flag est True, le retour est Np.nan sinon, appliquez or
entre long_entry_flag
et long_exit_flag
.
df['d'] = np.where(df['long_exit_flag'], np.nan, df['long_entry_flag'] | df['long_exit_flag'])
Étape 2 : C'est maintenant l'état dans lequel les deux colonnes sont false
. Nous devons l'ignorer et remplacer les valeurs par l'état précédent. Ce qui peut être fait en utilisant where
et select
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
#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
si la température est positive, le signal doit être égal à 1. Si la température est négative, le signal doit être vide. (Je suis un peu coincé ici)