Цель состоит в том, чтобы заполнить значения только между двумя значениями (start и end) уникальными номерами (позже будут использоваться в groupby), обратите внимание, как значения между end и start по-прежнему None в желаемом выводе:

enter image description here

Код:

>>> df = pd.DataFrame(
       dict(
           flag=[None, 'start', None, None, 'end', 'start', 'end', None, 'start', None,'end',None],
       )
    )

>>> df 
     flag
0    None
1   start
2    None
3    None
4     end
5   start
6     end
7    None
8   start
9    None
10    end
11   None

1
user16993219 21 Янв 2022 в 14:42

4 ответа

Лучший ответ

Обычно подобные проблемы решаются путем возни с cumsum и shift.

Основная идея этого решения состоит в том, чтобы идентифицировать строки, в которых количество видимых «начал» превышает количество «концов», видимых на единицу.

Единственное предположение, которое я сделал, состоит в том, что 'start' и 'end' чередуются, начиная с 'start'.

>>> values = df['flag'].eq('start').cumsum()
>>> where = values.sub(1).eq(df['flag'].eq('end').cumsum().shift(1).fillna(0))
>>> df['flag_periods'] = df['flag'].mask(where, values)
>>> df 
     flag flag_periods
0    None         None
1   start            1
2    None            1
3    None            1
4     end            1
5   start            2
6     end            2
7    None         None
8   start            3
9    None            3
10    end            3
11   None         None

Визуализация:

>>> df['values'] = df.eq('start').cumsum()
>>> df['end_cumsum'] = df['flag'].eq('end').cumsum()
>>> df['end_cumsum_s1'] = df['end_cumsum'].shift(1).fillna(0)
>>> df['values-1'] = df['values'].sub(1)
>>> df['where'] = df['values-1'].eq(df['end_cumsum_s1'])
>>> df 
     flag  values  end_cumsum  end_cumsum_s1  values-1  where
0    None       0           0            0.0        -1  False
1   start       1           0            0.0         0   True
2    None       1           0            0.0         0   True
3    None       1           0            0.0         0   True
4     end       1           1            0.0         0   True
5   start       2           1            1.0         1   True
6     end       2           2            1.0         1   True
7    None       2           2            2.0         1  False
8   start       3           2            2.0         2   True
9    None       3           2            2.0         2   True
10    end       3           3            2.0         2   True
11   None       3           3            3.0         2  False

Редактировать: добавлено .fillna(0) для учета кадров данных, где первое значение в столбце 'flag' равно 'start'.

1
timgeb 21 Янв 2022 в 16:13
Спасибо, это то, что я хотел, что-то без петель. Кроме того, взгляните на ответ, который я опубликовал, это еще один способ сделать это с помощью цепочки. Но я отметил ваш ответ выбранным, потому что он имеет визуализацию.
 – 
user16993219
21 Янв 2022 в 18:55

Один из вариантов — найти индекс всех строк 'start' и 'end', перебрать список со значениями None и заменить значения в правильной позиции.

df = pd.DataFrame(
       dict(
           flag=[None, 'start', None, None, 'end', 'start', 'end', None, 'start', None,'end',None],
       )
    )

start_index = df[df['flag']=='start'].index
end_index = df[df['flag']=='end'].index

values = [None]*df.shape[0]
for i, (s, e) in enumerate(zip(start_index, end_index),1):
    for j in range(s,e+1):
        values[j]=i
df["flag_persiods"]=values
>>> df
     flag  flag_persiods
0    None            NaN
1   start            1.0
2    None            1.0
3    None            1.0
4     end            1.0
5   start            2.0
6     end            2.0
7    None            NaN
8   start            3.0
9    None            3.0
10    end            3.0
11   None            NaN
0
mosc9575 21 Янв 2022 в 14:58

Кажется, это работает для меня

# start by creating a col containing only None values 
df['flag_periods'] = None
#set the count to 0 
count = 0
#use end as False or True to know when a flag periods ends or starts 
end = True 
#loop over the df using indexes to fill the 'flag_periods' column wit the period
for index in df.index :     
    if df.at[index, 'flag'] == 'start':
        end = False 
        count += 1 
        df.at[index, 'flag_periods'] = count
    elif df.at[index, 'flag'] == 'end':
        df.at[index, 'flag_periods'] = count
        end = True 
    else : 
        if end == False :
            df.at[index, 'flag_periods'] = count
        else : 
            df.at[index, 'flag_periods'] = None
            
df
0
Gregory 21 Янв 2022 в 15:47

Мне удалось сделать это без циклов следующим образом:

res = df.assign(
    flag_bool=lambda x: x.flag.replace({'start': True, 'end': False}).ffill(),
    flag_periods=lambda x: np.where(
        x.flag_bool | x.flag_bool.shift(1), (x.flag == 'start').cumsum(), None
    ),
).drop(columns=['flag_bool'])

Результат вывода:

>>> res
    flag    flag_periods
0   None    None
1   start   1
2   None    1
3   None    1
4   end 1
5   start   2
6   end 2
7   None    None
8   start   3
9   None    3
10  end 3
11  None    None

0
user16993219 21 Янв 2022 в 18:54