Как мне написать эффективную функцию для вычисления среднего остатка на момент закрытия для разных учетных записей за определенный период времени

#python #pandas #function #statistics

#python #pandas #функция #Статистика

Вопрос:

Я работаю в финансовой организации. В нашей таблице транзакций мы отслеживаем остатки клиентов только тогда, когда они совершают транзакции. Например, если клиент открыл счет 1 октября с 200 долларами, а затем снял 50 долларов 8 октября, у него будет всего две записи в таблице транзакций: одна для 2020/10/01, а другая для 2020/10/8. Теперь в центре внимания этого вопроса находятся остатки на момент закрытия. Следуя этой аналогии, если мы используем сегодняшний день в качестве даты отсечения, вы согласитесь, что у клиента был бы баланс закрытия в размере 200 долларов США в течение 7 дней (2020/10/8 — 2020/10/1) и 150 долларов США в течение оставшихся 29 дней.

Теперь я не уверен, как написать эту функцию. Я сталкивался с ошибками, и я был бы признателен, если кто-нибудь сможет помочь мне с кодом python и соответствующими комментариями, чтобы это стало для меня полезным опытом обучения.

Это образец набора данных, который у меня есть:

 sample_df = pd.DataFrame({'ID': [15, 16, 15, 15, 16, 17, 17, 16],
                         'Calendar_Date': ['2020-10-10', '2020-10-12', '2020-10-12', '2020-10-22', '2020-10-28', '2020-10-30', '2020-11-03', '2020-11-04'] ,
                         'Closing_Balance': [10000, 3000, 6000, 5100, 14500, 25000, 13000, 9000]}) 
  

и это результат, который я ожидаю:

 result_df = pd.DataFrame({'ID':[15, 16, 17],
                         'Total_Days': [26, 24, 6],
                         'Average_Account_Balance': [5823.08, 6375.00, 19000]})
  

Для ясности: вот как я пришел к результату_df:

Когда ID = 15, Total_Days = (2 10 15) = 27; Average_Account_Balance Средний баланс счета = ((10000 * 2) (6000 * 10) (5100 * 15))/27 = 156500/27 = 5796.3

когда ID = 16, Total_Days = (16 7 2) = 25; Average_Account_Balance Средний баланс счета = ((3000 * 16) (14500 * 7) (9000 * 2))/25 = 167500/25 = 6700.00

когда ID = 17, Total_Days = (4 3) = 7;
Average_Account_Balance = ((25000 * 4) (13000 * 3))/7 = 139000/7 = 19857.14

Мне нужно, чтобы решение было эффективным с точки зрения вычислений, потому что вы можете догадаться, сколько транзакций у нас в нашей БД. Пожалуйста, не стесняйтесь задавать дополнительные вопросы, если вам не ясно, что здесь указано или подразумевается. Спасибо!

Ответ №1:

Вы можете разбить эту проблему на несколько шагов. Во-первых, нам нужно создать несколько новых столбцов во фрейме данных:

  1. Найдите количество дней с каждой даты до даты окончания (сегодня в вашем примере).
  2. Внутри каждой группы "ID" найдите разницу между предыдущими вычисляемыми столбцами, чтобы получить количество дней между транзакциями. Затем мы используем fillna метод для заполнения оставшихся различий в датах (например, используя diff , мы получаем разницу между строками, но мы упускаем разницу между самой последней датой в пределах "ID" и сегодняшней датой). Это создает нам правильный "days between transaction" столбец
  3. Вычислите столбец взвешенного баланса: просто умножьте "Closing_Balance" by the newly created столбец «дни между транзакциями»
 sample_df["days_from_today"] = (pd.to_datetime("11/06/2020").normalize() - sample_df["Calendar_Date"]).dt.days

sample_df["days_between_transactions"] = (sample_df.groupby("ID")["days_from_today"]
                                          .diff(-1)
                                          .fillna(sample_df["days_from_today"])
                                          .astype(int))

sample_df["weighted_balance"] = sample_df["Closing_Balance"] * sample_df["days_between_transactions"]

print(sample_df)
   ID Calendar_Date  Closing_Balance  days_from_today  days_between_transactions  weighted_balance
0  15    2020-10-10            10000               27                          2             20000
1  16    2020-10-12             3000               25                         16             48000
2  15    2020-10-12             6000               25                         10             60000
3  15    2020-10-22             5100               15                         15             76500
4  16    2020-10-28            14500                9                          7            101500
5  17    2020-10-30            25000                7                          4            100000
6  17    2020-11-03            13000                3                          3             39000
7  16    2020-11-04             9000                2                          2             18000
  

Теперь, когда мы создали наши дополнительные столбцы, мы можем выполнить groupby -> aggregation операцию для получения sum нашего "weighted_balance" столбца и разделить его на max из "days_from_today" для каждого уникального "ID"

 aggregated_df = sample_df.groupby("ID").agg(
    weighted_total_account_balance=("weighted_balance", "sum"), 
    total_days=("days_from_today", "max")
)

aggregated_df["average_account_balance"] = aggregated_df["weighted_total_account_balance"] / aggregated_df["total_days"]

print(aggregated_df)
    weighted_total_account_balance  total_days  average_account_balance
ID                                                                     
15                          156500          27              5796.296296
16                          167500          25              6700.000000
17                          139000           7             19857.142857
  

Я заметил, что в наших результатах есть небольшие расхождения, я полагаю, это может быть связано с различиями в наших часовых поясах (сегодня для меня 11/6/2020, не уверен, какое время / день для вас), поэтому наши «total_days» могут отличаться.

Кроме того, если ваши данные очень большие, я бы рекомендовал использовать DataFrame.eval для выполнения арифметических операций.

Комментарии:

1. Привет, Кэмерон, спасибо тебе за это. У нас есть та же дата, которая также равна 6.11.2020, но я думаю, что вы принимаете во внимание сегодняшний день, чего не должно быть, поскольку мы не знаем, совершит ли кто-либо из клиентов транзакцию сегодня. Предполагается, что это ситуация типа T-1 или D-1.

2. Привет @Cameron Riddell, я был так утомлен, когда делал свои вычисления, и это повлияло на мои цифры. Да, ваше решение сработало. Большое вам спасибо! Я собираюсь отредактировать свой вопрос, чтобы он никого не вводил в заблуждение в будущем.

3. Потрясающе! Поскольку вам показалось, что это сработало, не могли бы вы выбрать этот ответ как правильный, чтобы другие, у кого есть подобный вопрос, могли быстро найти решение?

4. Riddwell, ГОТОВО!