Вопрос или проблема
У меня есть следующий датафрейм:
Я хотел бы добавить колонку со значением, равным 1, если flag
равен 0, и поэтапно добавлять 1 в последующих строках, пока не встретится следующий 0 (как показано в примере ниже).
Мне удалось сгенерировать последовательность, но код работает очень медленно, поэтому есть ли более быстрый способ сгенерировать последовательность?
Я не знаю, как вы проверяли это в первый раз, вот моя логика. Она предполагает, что первый элемент в flag равен 0!
df = pd.DataFrame({'memberid': [1]*11,
'flag': [0,0,1,1,0,1,0,0,0,1,1],
})
df['seq'] = ""
for i in range(0, len(df)):
df.loc[i, 'seq'] = 1 if df.loc[i, 'flag'] == 0 else df.loc[i - 1, 'seq'] + 1
print(df)
Другой вариант с использованием lambda:
df = pd.DataFrame({'memberid': [1] * 11,
'flag': [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1]
})
def f(flag):
global previous_seq
previous_seq = 1 if flag == 0 else previous_seq + 1
return previous_seq
previous_seq = 0
df['seq'] = df[['flag']].apply(lambda x: f(*x), axis=1)
print(df)
Я не уверен, быстрее ли это, чем первый способ….
Используйте эту одну строку кода, которая работает сверхбыстро, поскольку просто использует словарь с функцией update(), которая занимает O(1) времени, а лямбда-функция в общей сложности занимает O(N) времени, так что O(n) * O(1) = O(N)
b = {}
b[0] = 1
df['flags'].map(lambda x : [b.update({0 : 1}) , x+1][1] if x < 1 else (lambda : ([x+b[0] , b.update({0 : b[0]+1})][0]))())
Это сработает супербыстро.
Я пробовал это 3 способами, перебирая флаги (считая непрерывные 1) и это было самым быстрым для того же датафрейма (достаточно большим, чтобы пренебречь небольшими вариациями во времени при повторных попытках).
Я поддерживаю два списка (один для флагов, другой для последовательности) и переменную-счетчик.
Мы проходим через флаги и добавляем соответствующую последовательность в seq_list. Как вы описали, мы отслеживаем счет, если продолжаем видеть 1, и сбрасываем до 1, если видим 0 в flags_list.
После завершения мы добавляем seq_list как колонку в датафрейм.
seq_list = []
counter = 0
flag_list = list(df['flag'])
for flag in flag_list:
if(flag == 0):
counter = 1
seq_list.append(counter)
else:
counter += 1
seq_list.append(counter)
df['seq'] = seq_list
Другие варианты, которые я пробовал, включают
- Прямое перебирание каждого элемента флага в датафрейме (используя .loc) и добавление в seq_list, который добавляется как колонка, но это заняло 16 раз больше времени, чем метод выше.
seq_list = []
counter = 0
for i in range(df.shape[0]):
if(df.loc[i,'flag'] == 0):
counter = 1
seq_list.append(counter)
else:
counter += 1
seq_list.append(counter)
df['seq'] = seq_list
- Прямое перебирание каждого элемента флага в датафрейме (используя .loc) и немедленное изменение датафрейма (снова используя .loc), но это было еще медленнее и заняло от 5 до 10 раз больше времени, чем предыдущий метод.
counter = 1
df['seq'] = 0
for i in range(df.shape[0]):
if(df.loc[i,'flag'] == 0):
counter = 1
df.loc[i,'seq'] = counter
else:
counter += 1
df.loc[i,'seq'] = counter
Ответ или решение
Чтобы сгенерировать последовательность на основе значений в столбце flag
в Python с использованием библиотеки pandas, мы можем рассмотреть несколько подходов к решению этой задачи, каждый из которых имеет свои плюсы и минусы касаемо скорости выполнения и читаемости кода. Рассмотрим основные шаги, как это можно сделать наиболее эффективно.
Постановка задачи
У нас есть DataFrame, содержащий столбец flag
, где значения могут быть 0 или 1. Наша цель — добавить новый столбец, который будет равен 1, если flag
равен 0, и будет инкрементально увеличиваться на 1 в следующих строках, до тех пор, пока не встретится следующий 0.
Эффективное решение
Одним из самых эффективных подходов является использование метода numpy
или метод последовательного обхода с хранением состояния. В отличие от использования .apply()
или for
циклов, которые требуют большого количества обращений к индексам, мы можем использовать простой цикл, чтобы обновить значения.
Вот пример кода, который эффективно решает поставленную задачу:
import pandas as pd
# Создать DataFrame
df = pd.DataFrame({'memberid': [1] * 11,
'flag': [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1]})
# Инициализация переменной для подсчета
counter = 0
seq_list = []
# Итерирование по значениям столбца 'flag'
for flag in df['flag']:
if flag == 0:
counter = 1 # Сбросить счетчик на 1, если встречаем 0
else:
counter += 1 # Увеличить счетчик, если 1
seq_list.append(counter)
# Добавить новый столбец к DataFrame
df['seq'] = seq_list
print(df)
Объяснение работы кода
- Инициализация: Мы начинаем с создания списков и переменной
counter
, которая будет отслеживать текущую последовательность. - Итерация: Проходим по каждому значению в столбце
flag
. Если значение — 0, мы сбрасываем счетчик, иначе увеличиваем его на 1. - Сбор результатов: Добавляем значение счетчика в список, который затем используется для добавления нового столбца в DataFrame.
Альтернативное решение с использованием Numpy
Если ваш DataFrame очень большой, вы также можете воспользоваться библиотекой numpy
, которая предоставляет функции для обработки массивов гораздо быстрее, чем работа с обычными списками.
import pandas as pd
import numpy as np
# Создать DataFrame
df = pd.DataFrame({'memberid': [1] * 11,
'flag': [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1]})
# Использовать numpy для создания последовательности
seq_array = np.zeros(len(df), dtype=int)
counter = 0
for i in range(len(df)):
if df.iloc[i]['flag'] == 0:
counter = 1
else:
counter += 1
seq_array[i] = counter
# Добавить новый столбец к DataFrame
df['seq'] = seq_array
print(df)
Заключение
Оба предложенных метода являются эффективными, но с точки зрения производительности и чистоты кода, использование прямого обхода с хранением состояния является оптимальным (O(N)). Метод с использованием numpy
также показывает хорошую производительность, особенно на больших наборах данных. Выбор подхода зависит от ваших требований по читаемости и среде выполнения.
Эти решения помогут вам увидеть, как можно эффективно работать с DataFrame и обрабатывать последовательности на основе условий в других столбцах.