web-dev-qa-db-fra.com

Utiliser les colonnes 1 et 2 pour remplir la colonne 3

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

  1. Recherchez la colonne [‘long_entry_flag’] jusqu’à ce que la condition d’entrée soit vraie (jour 3 initialement)
  2. Ensuite, nous entrons ‘1’ dans la colonne [‘signal’] tous les jours jusqu’à ce que la condition de sortie soit vraie [‘long_exit_flag’] == True le jour 8
  3. Ensuite, nous regardons la colonne [‘long_entry_flag’] pour attendre la prochaine condition d’entrée (survient le jour 10).
  4. Et encore nous entrons ‘1’ dans la colonne [‘signal’] tous les jours jusqu’à ce que la condition de sortie soit vraie (jour 14)
  5. etc

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!

10
Baz

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])
7
ayorgo

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.

5
jezrael

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
3
Dark
#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)

0
IsaKyodo