У меня есть фрейм данных:
one N th
0 A 5 1
1 Z 17 0
2 A 16 0
3 B 9 1
4 B 17 0
5 B 117 1
6 XC 35 1
7 C 85 0
8 Ce 965 1
Я ищу способ продолжать чередовать 0101 в третьем столбце, не удваивая 0 или 1. Итак, я хочу удалить строку с минимальным значением в случае, если у меня есть два повторяющихся 0 в столбце th и максимальное значение, если у меня есть повторяющаяся 1.
Моя база состоит из 1000000 строк.
Я ожидаю, что у меня будет такой фрейм данных:
one N th
0 A 5 1
1 Z 17 0
3 B 9 1
4 B 17 0
6 XC 35 1
7 C 85 0
8 Ce 965 1
Как это сделать быстрее всего. Я имею в виду векторизованный способ. Мои попытки без результата.
Вы забыли опубликовать свою попытку решить хотя бы часть этой проблемы.
Если вы удаляете дубликат с минимальными значениями, не следует ли удалить строку 6, а не 5?
Хорошо, понятно, вы берете мин/макс в зависимости от 0/1
«Я хочу удалить строку с минимальным значением в случае, если у меня есть два повторяющихся 0 в столбце th и максимальное значение, если у меня есть повторяющаяся 1». Я понимаю, что это означает: для дубликатов с 0 удалите строку с минимальным значением для N (т.е. индексом 2); для дубликатов с 1 удалите строку с максимальным значением для N (т. е. индексом 5).
@Энди, можешь ли ты получить больше двух последовательных 0 или 1?
не было бы очень легко просто создать переменную. А затем в цикле установите его на первый, а затем сравните со следующим. Если оно одинаково и равно 0, удалите наименьшее значение; если 1, удалить самое высокое. Если они разные, установите переменную на следующую и продолжите цикл.
Для выполнения работы лучше использовать фрейм данных. Но если бы вы действительно захотели, вы могли бы создать цикл. Я добавлю это как решение, но это определенно не лучший способ сделать это.
df['group'] = (df['th'] != df['th'].shift(1)).cumsum()
df['max'] = df.groupby('group')['N'].transform('max')
df2 = df.loc[df['N'] == df['max']][['one', 'N', 'th']]
Одна версия вкладыша, если вы не хотите создавать промежуточные столбцы:
df.loc[df['N'] == df.groupby((df['th'] != df['th'].shift(1)).cumsum())['N'].transform('max')]
groupby.idxmax
Вы можете поменять знак, если «th» равен 1
(чтобы получить максимум вместо минимума), затем настроить собственный группер (с diff или сдвигом + cumsum ) и выполнить groupby .idxmax, чтобы выбрать строки для сохранения:
out = df.loc[df['N'].mul(df['th'].map({0: 1, 1: -1}))
.groupby(df['th'].ne(df['th'].shift()).cumsum())
.idxmax()]
Вариант с другим методом замены знака и вычисления группы:
out = df.loc[df['N'].mask(df['th'].eq(1), -df['N'])
.groupby(df['th'].diff().ne(0).cumsum())
.idxmax()]
Выход:
one N th
0 A 5 1
1 Z 17 0
3 B 9 1
4 B 17 0
6 XC 35 1
7 C 85 0
8 Ce 965 1
Промежуточные продукты:
one N th swap group max
0 A 5 1 -5 1 X
1 Z 17 0 17 2 X
2 A 16 0 16 2
3 B 9 1 -9 3 X
4 B 17 0 17 4 X
5 B 117 1 -117 5
6 XC 35 1 -35 5 X
7 C 85 0 85 6 X
8 Ce 965 1 -965 7 X
Приведенный выше код работает для произвольного количества последовательных 0 или 1. Если вы знаете, что у вас есть только два последовательных индекса, вы также можете использовать логическое индексирование, которое должно быть значительно быстрее:
# has the value higher precedence than the next?
D = df['N'].mask(df['th'].eq(1), -df['N']).diff()
# is the th different from the previous?
G = df['th'].ne(df['th'].shift(fill_value=-1))
# rule for the bottom row
m1 = D.gt(0) | G
# rule for the top row
# same rule as above but shifted up
# D is inverted
# comparison is not strict in case of equality
m2 = ( D.le(0).shift(-1, fill_value=True)
| G.shift(-1, fill_value=True)
)
# keep rows of interest
out = df.loc[m1&m2]
Выход:
one N th
0 A 5 1
1 Z 17 0
3 B 9 1
4 B 17 0
6 XC 35 1
7 C 85 0
8 Ce 965 1
Промежуточные продукты:
one N th D G m1 m2 m1&m2
0 A 5 1 NaN True True True True
1 Z 17 0 22.0 True True True True
2 A 16 0 -1.0 False False True False
3 B 9 1 -25.0 True True True True
4 B 17 0 26.0 True True True True
5 B 117 1 -134.0 True True False False
6 XC 35 1 82.0 False True True True
7 C 85 0 120.0 True True True True
8 Ce 965 1 -1050.0 True True True True
Более сложный пример с равными значениями:
one N th D G m1 m2 m1&m2
0 A 5 1 NaN True True True True
1 Z 17 0 22.0 True True True True
2 A 16 0 -1.0 False False True False
3 B 9 1 -25.0 True True True True
4 B 17 0 26.0 True True True True
5 B 117 1 -134.0 True True False False
6 XC 35 1 82.0 False True True True
7 C 85 0 120.0 True True True True
8 Ce 965 1 -1050.0 True True True True
9 u 123 0 1088.0 True True True True # because of D.le(0)
10 v 123 0 0.0 False False True False # because or D.gt(0)
Примечание. в случае равенства можно выбрать первую/вторую строку или обе или ничего, в зависимости от используемого оператора (D.le(0)
, D.lt(0)
, D.gt(0)
, D.ge(0)
).
Несмотря на то, что использование логической маски ограничено максимум двумя последовательными «th», подход с логической маской работает примерно в 4–5 раз быстрее. Рассчитано на 1 миллион строк:
# groupby + idxmax
96.4 ms ± 6.64 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# boolean masks
22.2 ms ± 1.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Добавление из-за одного из комментариев. Что касается итеративного способа, описанного ниже, это не тот метод, который вам хотелось бы использовать, поскольку он не использует Pandas. Добавляю его для полноты, так как по сравнению с другими решениями оно менее лаконично.
data = [
[0, 'A', 5, 1],
[1, 'Z', 17, 0],
[2, 'A', 16, 0],
[3, 'B', 9, 1],
[4, 'B', 17, 0],
[5, 'B', 117, 1],
[6, 'XC', 35, 1],
[7, 'C', 85, 0],
[8, 'Ce', 965, 1]
]
df = pd.DataFrame(data, columns=['id', 'one', 'N', 'th'])
def ensure_alternating_th(df):
while True:
repeats_found = False
idx_to_remove = []
for idx in range(1, len(df)):
# check for repeated values in 'th' column
if df.at[idx, 'th'] == df.at[idx - 1, 'th']:
repeats_found = True
if df.at[idx, 'th'] == 0:
# Drop row with minimum 'N' where 'th' == 0
min_row_idx = df.iloc[[idx - 1, idx]]['N'].idxmin()
elif df.at[idx, 'th'] == 1:
# Drop row with maximum 'N' where 'th' == 1
max_row_idx = df.iloc[[idx - 1, idx]]['N'].idxmax()
idx_to_remove.append(min_row_idx if df.at[idx, 'th'] == 0 else max_row_idx)
if not repeats_found:
break
# remove identified rows and reset index
df = df.drop(idx_to_remove).reset_index(drop=True)
return df
df_cleaned = ensure_alternating_th(df)
"""
# Returns
id one N th
0 0 A 5 1
1 1 Z 17 0
2 3 B 9 1
3 4 B 17 0
4 6 XC 35 1
5 7 C 85 0
6 8 Ce 965 1
"""
Можете ли вы отредактировать вопрос и добавить ожидаемый результат для этого фрейма данных?