Я использую 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
См. здесь и здесь. Вы хотите изменить эти 1 и 0 в столбцах feat на логические значения с помощью astype(bool), и тогда вы почти у цели.
Это тот самый pandasapply о котором ты говоришь?






Можешь попробовать:
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, что вполне может потребоваться для других данных в столбцах навыков.
Другое возможное решение, примерно в 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) небезопасен?
@e-motta ты можешь сделать это еще быстрее ;)
@ Анри, чтобы имитировать условие в твоем вопросе: ... if row['scored'] == 0 else None. Чтобы добиться того же результата, я устанавливаю для всех столбцов 'feat...' значение 0 когда scored == 1 перед созданием столбца 'reason'. Обратите внимание, что это не влияет на исходные данные в df. Если это не является обязательным требованием, вы можете снять маску.
Вы можете улучшить подход @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
Это очень убедительный ответ, вау.
Вы можете предварительно фильтровать столбцы (так что вам не нужно делать это для каждой строки — результат будет тот же), что позволит избавиться от некоторых циклов. также row[col] имеет сложность n log n, если у вас много столбцов, которые также могут иметь значение. Я бы объединил 0 и 1 в сводный столбец, а затем повторял бы каждую строку, каждую запись в списке предварительно отфильтрованных столбцов. Это можно было бы делать даже параллельно.