Масштабируемый подход вместо применения в Python

Я использую apply для зацикливания строк и получения имен столбцов feat1, feat2 или feat3, если они равны 1, а оценка равна 0. Затем имена столбцов вставляются в новую функцию, называемую причиной.

Это решение не масштабируется для большего набора данных. Я ищу более быстрый подход. Как мне это сделать?

df = pd.DataFrame({'ID':[1,2,3],
             'feat1_tax':[1,0,0],
             'feat2_move':[1,0,0],
             'feat3_coffee': [0,1,0],
             'scored':[0,0,1]})

def get_not_scored_reason(row):
    exclusions_list = [col for col in df.columns if col.startswith('feat')]
    reasons = [col for col in exclusions_list if row[col] == 1]
    return ', '.join(reasons) if reasons else None

df['reason'] = df.apply(lambda row: get_not_scored_reason(row) if row['scored'] == 0 else None, axis=1)

print(df)
   ID  feat1_tax  feat2_move  feat3_coffee  scored      reason
0   1          1           1             0       0  feat1_tax, feat2_move
1   2          0           0             1       0           feat3_coffee
2   3          0           0             0       1                   None

Вы можете предварительно фильтровать столбцы (так что вам не нужно делать это для каждой строки — результат будет тот же), что позволит избавиться от некоторых циклов. также row[col] имеет сложность n log n, если у вас много столбцов, которые также могут иметь значение. Я бы объединил 0 и 1 в сводный столбец, а затем повторял бы каждую строку, каждую запись в списке предварительно отфильтрованных столбцов. Это можно было бы делать даже параллельно.

Ashalynd 18.06.2024 23:39

См. здесь и здесь. Вы хотите изменить эти 1 и 0 в столбцах feat на логические значения с помощью astype(bool), и тогда вы почти у цели.

ouroboros1 18.06.2024 23:45

Это тот самый pandasapply о котором ты говоришь?

hpaulj 19.06.2024 03:04
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
3
126
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Можешь попробовать:

columns = df.filter(regex=r"^feat")
df["reason"] = (columns * columns.columns).agg(
    lambda x: ", ".join(x[x.ne("")]) or None, axis=1
)

print(df)

Распечатки:

   ID  feat1_tax  feat2_move  feat3_coffee  scored                 reason
0   1          1           1             0       0  feat1_tax, feat2_move
1   2          0           0             1       0           feat3_coffee
2   3          0           0             0       1                   None

OP также требует None для df['reason'] если df['scored'] != 0, что вполне может потребоваться для других данных в столбцах навыков.

user19077881 18.06.2024 23:48
Ответ принят как подходящий

Другое возможное решение, примерно в 8 раз быстрее, чем у @Andrej Kesely, по приблизительным оценкам:

feat_columns = df.filter(regex=r"^feat").columns
reasons = df.mask(df["scored"] != 0, 0)[feat_columns].to_numpy()
df["reason"] = np.array(
    [", ".join(feat_columns[row == 1]) if np.any(row == 1) else None for row in reasons]
)
   ID  feat1_tax  feat2_move  feat3_coffee  scored                 reason
0   1          1           1             0       0  feat1_tax, feat2_move
1   2          0           0             1       0           feat3_coffee
2   3          0           0             0       1                   None

Большое спасибо. Почему df.mask(df["scored"] != 0, 0) небезопасен?

Henri 19.06.2024 07:41

@e-motta ты можешь сделать это еще быстрее ;)

mozway 19.06.2024 09:03

@ Анри, чтобы имитировать условие в твоем вопросе: ... if row['scored'] == 0 else None. Чтобы добиться того же результата, я устанавливаю для всех столбцов 'feat...' значение 0 когда scored == 1 перед созданием столбца 'reason'. Обратите внимание, что это не влияет на исходные данные в df. Если это не является обязательным требованием, вы можете снять маску.

e-motta 19.06.2024 10:41

Вы можете улучшить подход @e-motta (в 4 раза быстрее), используя zip в понимании списка и избегая numpy.any (замените оператором or):

feat = df.filter(regex=r'^feat')

df['reason'] = [', '.join(feat.columns[row == 1]) or None if s else None
                for row, s in zip(feat.to_numpy(), df['scored'].eq(0))]

Если количество столбцов «подвигов» разумно, произведение с точкой будет еще быстрее:

feat = df.filter(regex=r'^feat')
s = (feat@(feat.columns+', ')).str[:-2]
df['reason'] = s.mask(s.eq('')|df['scored'].ne(0), None)

Выход:

   ID  feat1_tax  feat2_move  feat3_coffee  scored                 reason
0   1          1           1             0       0  feat1_tax, feat2_move
1   2          0           0             1       0           feat3_coffee
2   3          0           0             0       1                   None

тайминги

Использование 3 столбцов «подвига» и переменного количества строк:

Используя 10 тыс. строк и переменное количество столбцов «подвигов», подход скалярного произведения начинает работать медленнее, чем понимание выше ~ 200 столбцов «подвигов»:

Таким образом, вы можете представить гибридный подход, который выбирает лучший метод на основе количества столбцов «подвигов»:

def auto_reason(df, thresh=200):
    feat = df.filter(regex=r"^feat")
    if feat.shape[1] > thresh:
        df['reason'] = [', '.join(feat.columns[row == 1]) or None if s else None
                for row, s in zip(feat.to_numpy(), df['scored'].eq(0))]
    else:
        s = (feat@(feat.columns+', ')).str[:-2]
        df['reason'] = s.mask(s.eq('')|df['scored'].ne(0), None)

auto_reason(df)

Относительно понимания списка:

Отличный и информативный ответ, как всегда. +1

e-motta 19.06.2024 10:38

Это очень убедительный ответ, вау.

Cow 19.06.2024 13:44

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