Цель состоит в том, чтобы заполнить значения только между двумя значениями (start
и end
) уникальными номерами (позже будут использоваться в groupby
), обратите внимание, как значения между end
и start
по-прежнему None
в желаемом выводе:
Код:
>>> 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
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'
.
Один из вариантов — найти индекс всех строк '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
Кажется, это работает для меня
# 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
Мне удалось сделать это без циклов следующим образом:
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
Похожие вопросы
Новые вопросы
python
Python - это многопарадигмальный, динамически типизированный, многоцелевой язык программирования. Он разработан для быстрого изучения, понимания и использования, а также для обеспечения чистого и единообразного синтаксиса. Обратите внимание, что Python 2 официально не поддерживается с 01.01.2020. Тем не менее, для вопросов о Python, связанных с версией, добавьте тег [python-2.7] или [python-3.x]. При использовании варианта Python (например, Jython, PyPy) или библиотеки (например, Pandas и NumPy) включите его в теги.