Определение сегментов клиентов на основе транзакций, совершенных ими за определенный период, с помощью Python

#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. К счастью, есть гораздо более короткое решение:

  1. Когда вы читаете свой файл, преобразуйте Transaction_Date в datetime, например, передав parse_dates=[‘Transaction_Date’] в read_excel .

  2. Определите функцию подсчета количества дат в группе (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 дней.

  3. Определите функцию, подсчитывающую оба столбца, содержащие количество предыдущих транзакций, для текущей группы дат транзакций:

     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')))})
      
  4. Сгенерируйте результат:

     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)