У меня есть фрейм данных под названием df, который похож на этот (за исключением того, что количество столбцов mat_deliv увеличивается до mat_deliv_8, есть несколько сотен клиентов и ряд других столбцов между Client_ID и mat_deliv_1 - здесь я упростил его).
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4
C1019876 xxx,yyy,zzz aaa,bbb,xxx xxx ddd
C1018765 yyy,zzz xxx xxx None
C1017654 yyy,xxx aaa,bbb ccc ddd
C1016543 aaa,bbb ccc None None
C1019876 yyy None None None
Я хочу создать новый столбец под названием xxx_count, который подсчитывает, сколько раз xxx появляется в mat_deliv_1, mat_deliv_2, mat_deliv_3 и mat_deliv_4. Значения должны выглядеть так:
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4 xxx_count
C1019876 xxx,yyy,zzz aaa,xxx,bbb xxx ddd 3
C1018765 yyy,zzz xxx xxx None 2
C1017654 yyy,xxx aaa,bbb ccc ddd 1
C1016543 aaa,bbb ccc None None 0
C1015432 yyy None None None 0
Я пробовал следующий код:
df = df.assign(xxx_count=df.loc[:, "mat_deliv_1":"mat_deliv_4"].\
apply(lambda col: col.str.count('xxx')).fillna(0).astype(int))
Но он не производит подсчет, а только двоичную переменную, где 0 = отсутствие xxx и 1 = присутствие xxx по крайней мере в одном из четырех столбцов mat_deliv.
NB: это дополнительный вопрос к заданному здесь: Создание столбца на основе наличия части строки в нескольких других столбцах






Попробуйте соединить их по горизонтали перед тем, как считать?
df['counts'] = (df.loc[:, "mat_deliv_1":"mat_deliv_4"]
.fillna('')
.agg(','.join, 1)
.str.count('xxx'))
df
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4 counts
0 C1019876 xxx,yyy,zzz aaa,bbb,xxx xxx ddd 3
1 C1018765 yyy,zzz xxx xxx NaN 2
2 C1017654 yyy,xxx aaa,bbb ccc ddd 1
3 C1016543 aaa,bbb ccc NaN NaN 0
4 C1019876 yyy NaN NaN NaN 0
Это будет работать, если "xxx" встречается только один раз в столбце. Если это происходит более одного раза, будет учитываться каждое появление.
Другой вариант включает stack:
df['counts'] = (
df.loc[:, "mat_deliv_1":"mat_deliv_4"].stack().str.count('xxx').sum(level=0))
df
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4 counts
0 C1019876 xxx,yyy,zzz aaa,bbb,xxx xxx ddd 3
1 C1018765 yyy,zzz xxx xxx NaN 2
2 C1017654 yyy,xxx aaa,bbb ccc ddd 1
3 C1016543 aaa,bbb ccc NaN NaN 0
4 C1019876 yyy NaN NaN NaN 0
Это можно легко изменить, чтобы подсчитать только первое появление, используя str.contains:
df['counts'] = (
df.loc[:, "mat_deliv_1":"mat_deliv_4"].stack().str.contains('xxx').sum(level=0))
Если "xxx" может быть подстрокой, сначала разделите, а затем подсчитайте:
df['counts'] = (df.loc[:, "mat_deliv_1":"mat_deliv_4"]
.stack()
.str.split(',', expand=True)
.eq('xxx')
.any(1) # change to `.sum(1)` to count all occurrences
.sum(level=0))
Для повышения производительности используйте понимание списка:
df['counts'] = [
','.join(x).count('xxx')
for x in df.loc[:, "mat_deliv_1":"mat_deliv_4"].fillna('').values
]
df
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4 counts
0 C1019876 xxx,yyy,zzz aaa,bbb,xxx xxx ddd 3
1 C1018765 yyy,zzz xxx xxx NaN 2
2 C1017654 yyy,xxx aaa,bbb ccc ddd 1
3 C1016543 aaa,bbb ccc NaN NaN 0
4 C1019876 yyy NaN NaN NaN 0
Почему цикл быстрее, чем при использовании методов str или apply? См. Для петель с пандами - когда мне это нужно?.
Следует отметить, что count будет включать соответствующую подстроку в большую строку, например Считаются и xxx, и xxxx. Если это нормально, тогда хорошо. Если нет, то нужно проверить равенство, например [sum(1 for word in ','.join(row).split(',') if word == 'xxx') for row in df.loc[:, "mat_deliv_1":"mat_deliv_4"].fillna('').values]
Использование str.findall
df.iloc[:,1:].apply(lambda x : x.str.findall('xxx')).sum(1).str.len()
Out[433]:
0 3
1 2
2 1
3 0
4 0
dtype: int64
Спасибо за ответ - однако после его запуска на моем df я получил сообщение об ошибке, что .str не может быть запущен для объекта dtype, поэтому я выбрал ответ ниже
Вы можете использовать разделение по ,, а затем использовать lambda в lambda. Преимущество этого решения в том, что вы не увидите неверных результатов, если xxx существует как подстрока yyy.
df['xxx_count'] = df.filter(like='mat_deliv').apply(lambda x: x.str.split(',')\
.apply(lambda x: 'xxx' in x)).sum(1)
print(df)
Client_ID mat_deliv_1 mat_deliv_2 mat_deliv_3 mat_deliv_4 xxx_count
0 C1019876 xxx,yyy,zzz aaa,bbb,xxx xxx ddd 3
1 C1018765 yyy,zzz xxx xxx None 2
2 C1017654 yyy,xxx aaa,bbb ccc ddd 1
3 C1016543 aaa,bbb ccc None None 0
4 C1019876 yyy None None None 0
Или, лучше, используйте функцию:
def sum_counts(series, value):
def finder(item, value):
return value in item
return series.str.split(',').apply(finder, value=value)
df['xxx_count'] = df.filter(like='mat_deliv').apply(sum_counts, value='xxx').sum(1)
"тогда используйте лямбду внутри лямбды" ... внутренне кричит
@coldspeed, ха-ха, тоже собирался обновиться с функцией! ... Сделанный.
Это сработало отлично - я выбрал последнее предложение, используя понимание списка. Большое спасибо за вашу помощь