У меня есть следующий код для большинства голосов за данные в фреймворке данных:
def vote(df, systems):
test = df.drop_duplicates(subset=['begin', 'end', 'case', 'system'])
n = int(len(systems)/2)
data = []
for row in test.itertuples():
# get all matches
fx = test.loc[(test.begin == row.begin) & (test.end == row.end) & (test.case == row.case)]
fx = fx.loc[fx.system.isin(systems)]
# keep if in a majority of systems
if len(set(fx.system.tolist())) > n:
data.append(fx)
out = pd.concat(data, axis=0, ignore_index=True)
out = out.drop_duplicates(subset=['begin', 'end', 'case'])
return out[['begin', 'end', 'case']]
Данные выглядят так:
systems = ['A', 'B', 'C', 'D', 'E']
df = begin,end,system,case
0,9,A,0365
10,14,A,0365
10,14,B,0365
10,14,C,0365
28,37,A,0366
38,42,A,0366
38,42,B,0366
53,69,C,0366
56,60,B,0366
56,60,C,0366
56,69,D,0366
64,69,E,0366
83,86,B,0367
Ожидаемый результат должен быть:
out = begin,end,case
10,14,0365
56,69,0366
IOW, при желании элементы begin, end, case
появляются в большинстве систем, мы накапливаем их и возвращаем как фрейм данных.
Алгоритм работает отлично, но, поскольку в нем сотни тысяч строк, его обработка занимает довольно много времени.
Одна оптимизация, которую я могу придумать, но не знаю, как ее реализовать, находится в itertuples
итерация: если для первого экземпляра набора фильтров begin, end, case
есть совпадения в
fx = test.loc[(test.begin == row.begin) & (test.end == row.end) & (test.case == df.case) & (fx.system.isin(systems))]
тогда было бы полезно не перебирать другие строки в itertuples
итерация, совпадающая с этим фильтром. Например, для первого экземпляра 10,14,A,0365
нет необходимости проверять следующие две строки, поскольку они уже были оценены. Однако, поскольку итерация уже исправлена, я не могу обойтись без них.
1 ответ
Это можно решить с помощью простого groupby
операция, которая может сказать вам, как часто появляется каждая комбинация. Тогда вам просто нужно сравнить это с вашим n
и отфильтруйте данные:
key = ["begin", "end", "case"]
n = len(systems) // 2
mask = df.groupby(key)["system"].count() > n
df.set_index(key)[mask]
.reset_index()
.drop(columns="system")
.drop_duplicates()
# begin end case
# 0 10 14 0365
Это выводит то же самое, что и ваш код на моей машине, но не то же самое, что и пример вывода, который вы указали в своем вопросе (что я считаю неправильным).
Обратите внимание, что я использовал целочисленное деление //
вместо плавающего деления /
и вручную переводить в int
.
В общем, если вы обнаружите, что используете itertuples
в pandas
, вам следует очень хорошо подумать, если вы не можете достичь своей цели другим способом, поскольку это один из самых медленных способов сделать что-либо со всеми строками фрейма данных.
Спасибо за подтверждение! То же самое решение осенило меня сегодня утром. (Я исправил пример вывода: я неправильно прочитал для него конечное значение!)
— horcle_buzz