Группа панд кончает с условием

#python #pandas

Вопрос:

У меня есть следующий df:

 df = pd.DataFrame({"values":[1,5,7,3,0,9,8,8,7,5,8,1,0,0,0,0,2,5],"signal":['L_exit',None,None,'R_entry','R_exit',None,'L_entry','L_exit',None,'R_entry','R_exit','R_entry','R_exit','L_entry','L_exit','L_entry','R_exit',None]}) df   values signal 0 1 L_exit 1 5 None 2 7 None 3 3 R_entry 4 0 R_exit 5 9 None 6 8 L_entry 7 8 L_exit 8 7 None 9 5 R_entry 10 8 R_exit 11 1 R_entry 12 0 R_exit 13 0 L_entry 14 0 L_exit 15 0 L_entry 16 2 R_exit 17 5 None  

Моя цель-добавить столбец tx, подобный этому:

 values signal num 0 1 L_exit nan 1 5 None nan 2 7 None nan 3 3 R_entry 1.00 4 0 R_exit 1.00 5 9 None 1.00 6 8 L_entry 1.00 7 8 L_exit 1.00 8 7 None nan 9 5 R_entry 2.00 10 8 R_exit 2.00 11 1 R_entry 2.00 12 0 R_exit 2.00 13 0 L_entry 2.00 14 0 L_exit 2.00 15 0 L_entry nan 16 2 R_exit nan 17 5 None nan  

Бизнес-логика: когда есть сигнал R_entry , мы группируем tx до тех пор, пока не появится L_exit (если есть еще один R_entry — игнорируйте его)

визуализация введите описание изображения здесь

Что я пробовал?

 g = ( df['signal'].eq('R_entry') | df_tx['signal'].eq('L_exit') ).cumsum()  df['tx'] = g.where(df['signal'].eq('R_entry')).groupby(g).ffill()   

проблема в том, что он увеличивается каждый раз, когда у него есть «R_entry»

Комментарии:

1. Я должен сказать, что это один из тех редких вопросов, о которых действительно приятно думать 😉

Ответ №1:

Вы можете сначала создать маску, чтобы получить непрерывные R_entries вплоть до L_exit.

Затем получите первое значение R_entry для каждой группы (по сравнению со следующим значением) и примените a cumsum .

 # keep only 'R_entry'/'L_exit' and get groups mask = df['signal'].where(df['signal'].isin(['R_entry', 'L_exit'])).ffill().eq('R_entry') # get groups and extend to next value (the L_exit) df['num'] = (mask.ne(mask.shift())amp;mask).cumsum().where(mask).ffill(limit=1)  

выход:

 values signal num 0 1 L_exit NaN 1 5 None NaN 2 7 None NaN 3 3 R_entry 1.0 4 0 R_exit 1.0 5 9 None 1.0 6 8 L_entry 1.0 7 8 L_exit 1.0 8 7 None NaN 9 5 R_entry 2.0 10 8 R_exit 2.0 11 1 R_entry 2.0 12 0 R_exit 2.0 13 0 L_entry 2.0 14 0 L_exit 2.0 15 0 L_entry NaN 16 2 R_exit NaN 17 5 None NaN  

разбивая, как это работает

Вот промежуточные шаги:

 df['isin ffill'] = df['signal'].where(df['signal'].isin(['R_entry', 'L_exit'])).ffill() df['mask'] = df['isin ffill'].eq('R_entry') df['first_of_group'] = (mask.ne(mask.shift())amp;mask) df['cumsum'] = df['first_of_group'].cumsum().where(mask) df['num'] = df['cumsum'].ffill(limit=1)  
 values signal isin ffill mask first_of_group cumsum num 0 1 L_exit L_exit False False NaN NaN 1 5 None L_exit False False NaN NaN 2 7 None L_exit False False NaN NaN 3 3 R_entry R_entry True True 1.0 1.0 4 0 R_exit R_entry True False 1.0 1.0 5 9 None R_entry True False 1.0 1.0 6 8 L_entry R_entry True False 1.0 1.0 7 8 L_exit L_exit False False NaN 1.0 8 7 None L_exit False False NaN NaN 9 5 R_entry R_entry True True 2.0 2.0 10 8 R_exit R_entry True False 2.0 2.0 11 1 R_entry R_entry True False 2.0 2.0 12 0 R_exit R_entry True False 2.0 2.0 13 0 L_entry R_entry True False 2.0 2.0 14 0 L_exit L_exit False False NaN 2.0 15 0 L_entry L_exit False False NaN NaN 16 2 R_exit L_exit False False NaN NaN 17 5 None L_exit False False NaN NaN  

Комментарии:

1. Мое первое впечатление: ВАУ! в 2 строках кода. Большое вам спасибо за разработку и решение. А Обожаю это!

Ответ №2:

Давайте попробуем (надеюсь, объяснимо):

 signals = df['signal']  after_entry = signals.where(signals.eq('R_entry')).ffill().eq('R_entry') before_exit = signals.where(signals.eq('L_exit')).bfill().eq('L_exit')  valids = after_entry amp; before_exit   blocks = signals.where(valids).ffill()[::-1].eq('L_exit').cumsum()[::-1] valid_blocks = (blocks.groupby(blocks).transform('size') > 2) valid_entries = valid_blocks amp; (~blocks.duplicated())  df.loc[valid_blocks, 'num'] = valid_entries.cumsum()  

Выход:

 values signal num 0 1 L_exit NaN 1 5 None NaN 2 7 None NaN 3 3 R_entry 1.0 4 0 R_exit 1.0 5 9 None 1.0 6 8 L_entry 1.0 7 8 L_exit 1.0 8 7 None NaN 9 5 R_entry 2.0 10 8 R_exit 2.0 11 1 R_entry 2.0 12 0 R_exit 2.0 13 0 L_entry 2.0 14 0 L_exit 2.0 15 0 L_entry NaN 16 2 R_exit NaN 17 5 None NaN