Это мой DataFrame:
import pandas as pd
df = pd.DataFrame(
{
'start': [3, 11, 9, 19, 22],
'end': [10, 17, 10, 25, 30]
}
)
Ожидаемый результат — создание столбца x
:
start end x
0 3 10 10
1 11 17 17
2 9 10 NaN
3 19 25 25
4 22 30 NaN
Логика:
Я объясняю это построчно. Для строки 0
x
равен df.end.iloc[0]
. Теперь это значение x
необходимо сохранить до тех пор, пока в следующих строках и в столбце start
не будет найдено большее значение.
Итак, нужно сохранить 10, тогда процесс переходит к строке 1
. 11 > 10? Если да, то x
второй строки равно 17. Для следующей строки 9 > 17? Нет, значение NaN
.
Процесс переходит к следующей строке. Поскольку не обнаружено значений, превышающих 17, сохраняется 17. 19 > 17? Да, x
установлено на 25. И для последней строки, поскольку 22 < 25, выбирается NaN
.
Я предоставил дополнительные примеры с разными df
и желаемыми результатами:
df = pd.DataFrame({'start': [3, 20, 11, 19, 22],'end': [10, 17, 21, 25, 30]})
start end x
0 3 10 10.0
1 20 17 17.0
2 11 21 NaN
3 19 25 25.0
4 22 30 NaN
df = pd.DataFrame({'start': [3, 9, 11, 19, 22],'end': [10, 17, 21, 25, 30]})
start end x
0 3 10 10.0
1 9 17 NaN
2 11 21 21.0
3 19 25 NaN
4 22 30 30.0
df = pd.DataFrame({'start': [3, 11, 9, 19, 22],'end': [10, 17, 21, 25, 30]})
start end x
0 3 10 10.0
1 11 17 17.0
2 9 21 NaN
3 19 25 25.0
4 22 30 NaN
Это дает мне результат. Есть ли векторизованный способ сделать это?
l = []
for ind, row in df.iterrows():
if ind == 0:
x = row['end']
l.append(x)
continue
if row['start'] > x:
x = row['end']
l.append(x)
else:
l.append(np.NaN)
Если предыдущий конец должен быть распространен, то логика не может быть векторизована. Однако это можно сделать намного быстрее, чем iterrows
, используя numba:
from numba import jit
@jit(nopython=True)
def f(start, end):
prev_e = -np.inf
out = []
for s, e in zip(start, end):
if s>prev_e:
out.append(e)
prev_e = e
else:
out.append(None)
return out
df['x'] = f(df['start'].to_numpy(), df['end'].to_numpy())
Выход:
# example 1
start end x
0 3 10 10.0
1 11 17 17.0
2 9 10 NaN
3 19 25 25.0
4 22 30 NaN
# example 2
start end x
0 3 10 10.0
1 20 17 17.0
2 11 21 NaN
3 19 25 25.0
4 22 30 NaN
# example 3
start end x
0 3 10 10.0
1 9 17 NaN
2 11 21 21.0
3 19 25 NaN
4 22 30 30.0
# example 4
start end x
0 3 10 10.0
1 11 17 17.0
2 9 21 NaN
3 19 25 25.0
4 22 30 NaN
IIUC, вы можете использовать сдвиг для формирования логической маски и маску для скрытия недопустимых значений:
df['x'] = df['end'].mask(df['start'].le(df['end'].shift()))
Хитрость здесь в том, чтобы сравнить start <= end.shift
, что приведет к False
для первой строки из-за NaN. Если вы хотите исключить первую строку, вам следует использовать df['end'].where(df['start'].gt(df['end'].shift()))
.
Выход:
start end x
0 3 10 10.0
1 11 17 17.0
2 9 10 NaN
3 19 25 25.0
4 22 30 NaN
Промежуточные продукты:
start end x end.shift start<=end.shift
0 3 10 10.0 NaN False
1 11 17 17.0 10.0 False
2 9 10 NaN 17.0 True
3 19 25 25.0 10.0 False
4 22 30 NaN 25.0 True
@AmirX Понятно, тогда логику нельзя векторизовать, но можно сделать ее эффективной с помощью numba
Вы можете использовать pd.shift(), чтобы сдвинуть конечный столбец на 1, чтобы избежать повторения каждой строки по отдельности.
Сначала создайте пустой столбец 'x'
и назначьте первую запись первой записью столбца 'end'
, например:
df['x'] = np.nan
df.loc[0, 'x'] = df.loc[0, 'end']
start end x
0 3 10 10.0
1 11 17 NaN
2 9 10 NaN
3 19 25 NaN
4 22 30 NaN
Затем вы можете сдвинуть столбец 'end'
вперед на 1 и отфильтровать строки, в которых это сдвинутое значение меньше начального значения. Вы можете поместить это в .loc, чтобы присвоить значения 'end'
вашему столбцу 'x'
только тогда, когда это требование выполнено:
df.loc[(df['end'].shift(1) < df['start']), 'x'] = df['end']
Даю вам:
start end x
0 3 10 10.0
1 11 17 17.0
2 9 10 NaN
3 19 25 25.0
4 22 30 NaN
Спасибо. Я предоставил дополнительные кадры данных. Это решение для них не работает по той же логике
Приведенные выше ответы должны работать, но в случае, если вы хотите использовать что-то на основе iterrows:
x=df['end'].iloc[0]
final_values=[]
for index, row in df.iterrows():
if index==0:
final_values.append(x)
else:
if row['start']>x:
x=row['end']
final_values.append(x)
elif row['start']<=x:
final_values.append(np.nan)
Используйте нумбу.
import pandas as pd
import numpy as np
from numba import njit
@njit
def g(start, end):
x = np.full(len(start), np.nan)
last_valid_end = end[0]
x[0] = last_valid_end
for i in range(1, len(start)):
if start[i] > last_valid_end:
last_valid_end = end[i]
x[i] = last_valid_end
return x
def f(df):
start = df['start'].values
end = df['end'].values
x = g(start, end)
df['x'] = x
return df
dfs = [
pd.DataFrame({'start': [3, 20, 11, 19, 22], 'end': [10, 17, 21, 25, 30]}),
pd.DataFrame({'start': [3, 9, 11, 19, 22], 'end': [10, 17, 21, 25, 30]}),
pd.DataFrame({'start': [3, 11, 9, 19, 22], 'end': [10, 17, 21, 25, 30]}),
pd.DataFrame({'start': [3, 11, 9, 19, 22], 'end': [10, 17, 10, 25, 30]})
]
for df in dfs:
df = f(df)
print(df)
print()
'''
pydev debugger: starting (pid: 3988)
start end x
0 3 10 10.0
1 20 17 17.0
2 11 21 NaN
3 19 25 25.0
4 22 30 NaN
start end x
0 3 10 10.0
1 9 17 NaN
2 11 21 21.0
3 19 25 NaN
4 22 30 30.0
start end x
0 3 10 10.0
1 11 17 17.0
2 9 21 NaN
3 19 25 25.0
4 22 30 NaN
start end x
0 3 10 10.0
1 11 17 17.0
2 9 10 NaN
3 19 25 25.0
4 22 30 NaN
'''
Следующий метод работает только для одного набора данных.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'start': [3, 11, 9, 19, 22],
'end': [10, 17, 10, 25, 30]
})
print(df)
start = df.start.values
end = df.end.values
cumulative_max_end = np.maximum.accumulate(end)
df['cumulative_max_end'] = cumulative_max_end
npc = np.concatenate(([end[0]] ,cumulative_max_end[:-1]))
df['npc'] =npc
mask = start > npc
df['mask'] = mask
x = np.full_like(start,np.nan,dtype=np.float64)
x[mask] = end[mask]
x[0] = end[0]
df['res'] = x
print(df)
'''
start end cumulative_max_end npc mask res
0 3 10 10 10 False 10.0
1 11 17 17 10 True 17.0
2 9 10 17 17 False NaN
3 19 25 25 17 True 25.0
4 22 30 30 25 False NaN
'''
Спасибо. Я предоставил дополнительные кадры данных. Это решение для них не работает по той же логике