Как сохранить предыдущее значение, чтобы найти строку, большую его?

Это мой 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

Логика:

Я объясняю это построчно. Для строки 0x равен 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)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
97
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

обновленный ответ

Если предыдущий конец должен быть распространен, то логика не может быть векторизована. Однако это можно сделать намного быстрее, чем 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 27.06.2024 17:28

@AmirX Понятно, тогда логику нельзя векторизовать, но можно сделать ее эффективной с помощью numba

mozway 27.06.2024 18:35

Вы можете использовать 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

Спасибо. Я предоставил дополнительные кадры данных. Это решение для них не работает по той же логике

AmirX 27.06.2024 18:00

Приведенные выше ответы должны работать, но в случае, если вы хотите использовать что-то на основе 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
'''

Другие вопросы по теме