#python #pandas #lambda #cython
#python #pandas #лямбда #cython
Вопрос:
У меня есть два фрейма данных, df1 (5 миллионов строк) и df2 (всего около 150 строк). Я хотел выполнить поиск df2 и обновить df1 на основе данных в df2. В принципе, у меня есть несколько условий, которые необходимо применить к df1 в виде фрейма данных df1. Установите флаг, если условия в df2 соответствуют условиям в df1.
data1 = {'type':['1','2','3'],
'code':['A','B','C'],
'type_2':['A1', 'B1', 'C1'],
'num1': ['101','102', '103'],
'num2': ['','',''],
'p_cd':['AA', 'BB', 'CC']
}
df1 = pd.DataFrame(data1)
data2 = {'type':['1','2'],
'code':['A','B'],
'type_2':['', 'B1'],
'num1': ['','102'],
'num2': ['',''],
'custom_expression':["p_cd=='AA'", ''],
'delete_flag':['Y', 'Y']
}
df2 = pd.DataFrame(data2)
df_cols = df1.columns.tolist()
def delete_flag(row_from_outlier):
for index, row_from_delete in df2.iterrows():
if row_from_delete['custom_expression'].strip():
for col in df_cols:
if row_from_delete['custom_expression'].find(col) != -1:
print(row_from_delete['custom_expression'])
cust_exp = row_from_delete['custom_expression'].replace(col, "row_from_outlier['" col "']" )
else:
cust_exp = '1==1'
print(cust_exp)
if (
( (not row_from_delete['type'].strip() ) or (row_from_delete['type'] ==row_from_outlier['type'] ) )
and ( (not row_from_delete['code'].strip() ) or (row_from_delete['code'] == row_from_outlier['code'] ) )
and ( (not row_from_delete['type_2'].strip()) or (row_from_delete['type_2'] == row_from_outlier['type_2'] ) )
and ( (not row_from_delete['num1'].strip() ) or (row_from_delete['num1'] == row_from_outlier['num1'] ) )
and ( (not row_from_delete['num2'].strip() ) or (row_from_delete['num2'] == row_from_outlier['num2'] ) )
and eval(cust_exp)
) :
del_flg = row_from_delete['delete_flag']
if not 'del_flg' in locals():
del_flg = 'N/A'
return del_flg
df1['delete_flag'] = df1.apply(lambda row:delete_flag(row), axis=1)
Приведенный выше код отлично работает при тестировании для небольшого набора данных, но недостаточно быстро для обработки 5 миллионов записей.
Нашел эту рекомендацию Cython, Numba и pandas.eval(), которая может сработать, но я был новичком в Python и не знал, как преобразовать приведенный выше код для использования CYTHON
Комментарии:
1. пожалуйста, опубликуйте ожидаемый результат. iterrows и apply combo будут медленнее, чем векторизованные альтернативы
Ответ №1:
Пара наблюдений, которые могут помочь вам ускорить работу.
Вы делаете много вещей повторно:
Удаление внутри delete_flag
:
Просто сделайте это только один раз, вне функции, в начале:
for col in df1.columns:
df1[col] = df1[col].str.strip()
for col in df2.columns:
df2[col] = df2[col].str.strip()
Построение cust_exp
:
Эта часть функции
...
for index, row_from_delete in df2.iterrows():
if row_from_delete['custom_expression'].strip():
for col in df_cols:
if row_from_delete['custom_expression'].find(col) != -1:
cust_exp = row_from_delete['custom_expression'].replace(col, "row_from_outlier['" col "']" )
else:
cust_exp = '1==1'
...
фактически не зависит от аргумента функции! row_from_outlier
Таким образом, вы cust_exp
снова и снова создаете одинаковые s. Это означает, что вы должны сделать это только один раз, вне функции. Для этого вы можете попробовать
df2['cust_exp'] = df2.custom_expression.str.replace(
'|'.join(str(col) for col in df1.columns),
lambda m: 'row_from_outlier["' m.group(0) '"]',
regex=True
)
df2.cust_exp[df2.cust_exp == ''] = 'True'
который добавляет результат в df2
( print(df2.cust_exp)
):
0 row_from_outlier["p_cd"]=='AA'
1 True
Для дальнейшего «дециклирования» функции вы можете выполнить следующее:
from functools import reduce
from operator import and_
def delete_flag(row_from_outlier):
scope = locals()
ser = reduce(and_, [df2[col].isin(['', row_from_outlier[col]])
for col in df2.columns[:5]]
[df2.cust_exp.apply(lambda s: eval(s, scope))])
if ser.any():
return df2.delete_flag[ser].to_list()[-1]
return 'N/A'
Здесь я использовал это
( (not row_from_delete['type'].strip() )
or (row_from_delete['type'] == row_from_outlier['type'] ) )
эквивалентно
row_from_delete['type'].isin(['', row_from_outlier['type']])
и что операция может выполняться по столбцам
df2['type'].isin(['', row_from_outlier['type']])
И and/amp;
-aggreation столбцов выполняется reduce(and_, ...)
.
И последний, но не менее важный вопрос: мне кажется, что в вашем коде есть потенциал, который del_flg
назначается более одного раза, и используется только последний? Это кажется странным. Предназначено ли это?
Комментарии:
1. Спасибо за ваш ответ @Timus. Я обновил код, как было предложено, и выполнил тестовый запуск на выборке данных, но все равно не увидел улучшения производительности. Потребовалось 15,5 секунд, чтобы обработать 5 тыс. записей в df1 с 2 записями в df2.
2. @SuryaKiran Спасибо за отзыв. Я бы не ожидал большой разницы только с 2 строками в df2.