#python #python-3.x #pandas #python-datetime
#python #python-3.x #pandas #python-datetime
Вопрос:
Для целей сегментации клиентов я хочу проанализировать, сколько транзакций совершил клиент за предыдущие 10 дней и 20 дней на основе заданной таблицы записей транзакций с датой. В этой таблице последние 2 столбца объединяются с помощью следующего кода.
Но я не удовлетворен этим кодом, пожалуйста, предложите мне улучшение.
import pandas as pd
df4 = pd.read_excel(path)
# Since A and B two customers are there, two separate dataframe created
df4A = df4[df4['Customer_ID'] == 'A']
df4B = df4[df4['Customer_ID'] == 'B']
from datetime import date
from dateutil.relativedelta import relativedelta
txn_prior_10days = []
for i in range(len(df4)):
current_date = df4.iloc[i,2]
prior_10days_date = current_date - relativedelta(days=10)
if df4.iloc[i,1] == 'A':
No_of_txn = ((df4A['Transaction_Date'] >= prior_10days_date) amp; (df4A['Transaction_Date'] < current_date)).sum()
txn_prior_10days.append(No_of_txn)
elif df4.iloc[i,1] == 'B':
No_of_txn = ((df4B['Transaction_Date'] >= prior_10days_date) amp; (df4B['Transaction_Date'] < current_date)).sum()
txn_prior_10days.append(No_of_txn)
txn_prior_20days = []
for i in range(len(df4)):
current_date = df4.iloc[i,2]
prior_20days_date = current_date - relativedelta(days=20)
if df4.iloc[i,1] == 'A':
no_of_txn = ((df4A['Transaction_Date'] >= prior_20days_date) amp; (df4A['Transaction_Date'] < current_date)).sum()
txn_prior_20days.append(no_of_txn)
elif df4.iloc[i,1] == 'B':
no_of_txn = ((df4B['Transaction_Date'] >= prior_20days_date) amp; (df4B['Transaction_Date'] < current_date)).sum()
txn_prior_20days.append(no_of_txn)
df4['txn_prior_10days'] = txn_prior_10days
df4['txn_prior_20days'] = txn_prior_20days
df4
Ответ №1:
Ваш код было бы очень сложно написать, если бы у вас было, например, 10 разных идентификаторов Customer_ID. К счастью, есть гораздо более короткое решение:
-
Когда вы читаете свой файл, преобразуйте Transaction_Date в datetime, например, передав parse_dates=[‘Transaction_Date’] в read_excel .
-
Определите функцию подсчета количества дат в группе (gr) в диапазоне от tDlt (Timedelta) до 1 дня до текущей даты (dd):
def cntPrevTr(dd, gr, tDtl): return gr.between(dd - tDtl, dd - pd.Timedelta(1, 'D')).sum()
Он будет применен дважды к каждому члену текущей группы
по идентификатору Customer_ID (фактически только к столбцу Transaction_Date),
один раз с tDtl == 10 дней и второй раз с tDlt == 20 дней. -
Определите функцию, подсчитывающую оба столбца, содержащие количество предыдущих транзакций, для текущей группы дат транзакций:
def priorTx(td): return pd.DataFrame({ 'tx10' : td.apply(cntPrevTr, args=(td, pd.Timedelta(10, 'D'))), 'tx20' : td.apply(cntPrevTr, args=(td, pd.Timedelta(20, 'D')))})
-
Сгенерируйте результат:
df[['txn_prior_10days', 'txn_prior_20days']] = df.groupby('Customer_ID') .Transaction_Date.apply(priorTx)
Приведенный выше код:
- группирует df по Customer_ID,
- извлекает из текущей группы только столбец Transaction_Date,
- применяет к нему функцию priorTx,
- сохраняет результат в 2 целевых столбцах.
Результат для немного сокращенного Transaction_ID выглядит следующим образом:
Transaction_ID Customer_ID Transaction_Date txn_prior_10days txn_prior_20days
0 912410 A 2019-01-01 0 0
1 912341 A 2019-01-03 1 1
2 312415 A 2019-01-09 2 2
3 432513 A 2019-01-12 2 3
4 357912 A 2019-01-19 2 4
5 912411 B 2019-01-06 0 0
6 912342 B 2019-01-11 1 1
7 312416 B 2019-01-13 2 2
8 432514 B 2019-01-20 2 3
9 357913 B 2019-01-21 3 4
Вы не можете использовать скользящие вычисления, потому что:
- скользящее окно простирается вперед от текущей строки, но вы хотите подсчитать предыдущие транзакции,
- скользящие вычисления включают текущую строку, тогда как вы хотите ее исключить.
Вот почему я придумал вышеупомянутое решение (всего 8 строк кода).
Подробно о том, как работает мое решение
Чтобы просмотреть все детали, создайте тестовый фрейм данных следующим образом:
import io
txt = '''
Transaction_ID Customer_ID Transaction_Date
912410 A 2019-01-01
912341 A 2019-01-03
312415 A 2019-01-09
432513 A 2019-01-12
357912 A 2019-01-19
912411 B 2019-01-06
912342 B 2019-01-11
312416 B 2019-01-13
432514 B 2019-01-20
357913 B 2019-01-21'''
df = pd.read_fwf(io.StringIO(txt), skiprows=1,
widths=[15, 12, 16], parse_dates=[2])
Выполните groupby, но пока извлеките только group с ключом ‘A’:
gr = df.groupby('Customer_ID')
grp = gr.get_group('A')
Он содержит:
Transaction_ID Customer_ID Transaction_Date
0 912410 A 2019-01-01
1 912341 A 2019-01-03
2 312415 A 2019-01-09
3 432513 A 2019-01-12
4 357912 A 2019-01-19
Давайте начнем с самого подробного вопроса, как работает cntPrevTr.
Извлеките одну из дат из grp:
dd = grp.iloc[2,2]
Он содержит временную метку(‘2019-01-09 00:00:00’).
Чтобы протестировать пример вызова cntPrevTr для этой даты, выполните:
cntPrevTr(dd, grp.Transaction_Date, pd.Timedelta(10, 'D'))
т.е. Вы хотите проверить, сколько предыдущих транзакций выполнил этот клиент
до этой даты, но не ранее, чем за 10 дней назад.
Результат таков 2
.
Чтобы увидеть, как вычисляется весь первый столбец, выполните:
td = grp.Transaction_Date
td.apply(cntPrevTr, args=(td, pd.Timedelta(10, 'D')))
Результатом является:
0 0
1 1
2 2
3 2
4 2
Name: Transaction_Date, dtype: int64
Левый столбец — это индекс, а правый — значения, возвращаемые
из вызова cntPrevTr для каждой даты.
И последнее, что нужно показать, как генерируется результат для всей группы. Выполнить:
priorTx(grp.Transaction_Date)
Результат (фрейм данных):
tx10 tx20
0 0 0
1 1 1
2 2 2
3 2 3
4 2 4
Та же процедура выполняется для всех других групп, затем
все частичные результаты объединяются (по вертикали), и последним
шагом является сохранение обоих столбцов всего фрейма данных в
соответствующих столбцах df.
Комментарии:
1. Спасибо Valdi_BO, за ответ… ваш код крошечный и сложный. поскольку у меня нет опыта работы в программировании, я не могу понять, как обе функции (cntPrevTr amp; priorTx) принимают входные аргументы и в каком формате. не могли бы вы, пожалуйста, объяснить пошаговые входные аргументы функции?
2. Спасибо Valdi_Bo за подробное решение и идею groupby. Я полностью понимаю ваш код. и обновите мой старый вместительный код, используя циклы и groupby. Вот мой код…
def prior_days_trans2(DF, days): trans_prior_count = [] for group_i in DF.groupby('Customer_ID').Transaction_Date: for j in group_i[1]: trans = group_i[1].between(j - pd.Timedelta(days=days), j - pd.Timedelta(days=1)).sum() trans_prior_count.append(trans) DF['trans_prior_' str(days) 'days'] = trans_prior_count return DF prior_days_trans2(df2, 10)