Генерация легальных транзакций¶
- ноутбуки лучше просматривать на Github pages, т.к. при просмотре прямо в репозитории могут быть проблемы с отображением, например, обрезка вывода с широкими датафреймами. Если в адресной строке браузера есть
iaroslav-dzh.github.io
, то вы уже на Github pages.
Ссылки:
Информация о ноутбуке
- В этом ноутбуке демонстрация основных функций и классов относящихся к генерации легальных транзакций:
- генерация времени, генерация одной транзакции, многих транзакций, запись транзакций в файл
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
import geopandas as gpd
from scipy.stats import truncnorm, norm
import pyarrow
import yaml
from data_generator.utils import load_configs, create_txns_df
from data_generator.general_time import *
from data_generator.legit.time.utils import log_check_min_time
np.set_printoptions(suppress=True)
pd.set_option('display.max_columns', None)
os.chdir("..")
os.getcwd()
'C:\\Users\\iaros\\My_documents\\Education\\projects\\fraud_detection_01'
# Базовые конфиги
base_cfg = load_configs("./config/base.yaml")
# Настройки легальных транзакций
legit_cfg = load_configs("./config/legit.yaml")
# Настройки времени
time_cfg = load_configs("./config/time.yaml")
# Пути к файлам
data_paths = base_cfg["data_paths"]
Создание конфиг класса с конфигами и данными для генерации¶
1. Конструктор конфиг класса LegitConfigBuilder
¶
- модуль
data_generator.legit.build.config
. Ссылка на исходный код в Github - Принимает на вход словари с данными из конфиг файлов для создания объекта
- Метод
build_cfg()
создает объект конфиг класса легальных транзакций с данными и конфигами для генерации легальных транзакций, например:- датафреймами с данными - timestamp-ы для семплирования времени, данные выбранных клиентов, мерчанты; путями к директориям для записи файлов
Демонстрация¶
from data_generator.legit.build.config import LegitConfigBuilder
from data_generator.runner.utils import make_dir_for_run
# Нужно создать директорию в которой хранятся файлы целого запуска генератора всех транзакций
# Т.к. билдеру необходимо знать этот путь
run_dir = make_dir_for_run(base_cfg)
builder = LegitConfigBuilder(base_cfg, legit_cfg, time_cfg, run_dir) # передаем конфиги и путь
configs = builder.build_cfg() # Создаем объект конфиг класса
Примеры атрибутов созданного конфиг класса
# Семпл клиентов под генерацию легальных транзакций
configs.clients.head(3)
client_id | birth_date | sex | region | city | lat | lon | city_id | home_ip | |
---|---|---|---|---|---|---|---|---|---|
0 | 2269 | 1988-11-03 | female | Кемеровская | Кемерово | 55.390972 | 86.046786 | 48 | 2.60.8.101 |
1 | 2529 | 1964-06-15 | male | Новосибирская | Новосибирск | 55.028102 | 82.921058 | 70 | 2.60.9.97 |
2 | 4768 | 1985-03-28 | female | Москва | Москва | 55.753879 | 37.620373 | 1 | 2.60.17.157 |
# Диапазон timestamp-ов под генерацию случайного времени
configs.timestamps.head(3)
timestamp | hour | unix_time | |
---|---|---|---|
0 | 2025-01-01 00:00:00 | 0 | 1735689600 |
1 | 2025-01-01 00:01:00 | 0 | 1735689660 |
2 | 2025-01-01 00:02:00 | 0 | 1735689720 |
1. Функция check_min_interval_from_near_txn
¶
- модуль
data_generator.legit.time.time
. Ссылка на исходный код в Github - проверка что сгенерированное время создаваемой транзакции не ближе по времени к другим транзакциям чем выставлено в минимальных интервалах в конфиг файле
legit.yaml
- подразумевается вызов функции когда есть предыдущие транзакции
Основная логика функции
- получает на вход семплированное ранее время для создаваемой транзакции - и другие аргументы
- проверяет есть ли среди уже созданных транзакций такие, которые по времени ближе допутимого - мин. допустимые интервалы зависят от того онлайн или оффлайн создаваемая транзакция и от онлайн/оффлайн статуса уже созданных транзакций. Для разных отношений онлайн статусов, разные мин. интервалы.
- Если есть любые транзакции, которые ближе допустимого по времени, то проверяем онлайн статус последней по времени транзакции и в зависимости от отношения онлайн статусов генерируемой транзакции и последней создаем случайную дельту времени в соответствии с установленными мин. и макс. лимитами. Например: отношение онлайн-онлайн, создать дельту от 6 до 30 минут; оффлайн-онлайн создать дельту от 30 до 60 минут и т.п.
- Затем эту дельту прибавляем ко времени последней транзакции. Это и будет время текущей транзакции.
- Если нет транзакций близких по времени меньше допустимого, то просто возвращаем семплированное время
Демонстрация¶
from data_generator.legit.time.time import check_min_interval_from_near_txn
from data_generator.legit.build.config import LegitConfigBuilder
# Создадим пустой датафрейм под транзакции с ограниченными колонками
trans_time_test = create_txns_df(base_cfg["txns_df"]).loc[:, ['client_id', 'txn_time', 'unix_time','online', 'is_fraud',]]
print("ready for tests")
ready for tests
Кейс¶
- Текущая транзакция онлайн
- Ближайшая к ней транзакция - онлайн. Она ближе допустимой
online_time_diff
- Последняя транзакция - оффлайн.
Ожидается:
- детект недопустимого минимального интервала с ближайшей транзакцией
- создание нового времени через создание дельты времени с учетом того что текущая транзакция онлайн, а последняя оффлайн и прибавление этой дельты ко времени последней транзакции
Какие мин. интервалы выставлены в конфигах для легальных транзакций
Возьмем только несколько примеров:
online_time_diff
мин. разница между онлайн транзonline_ceil
макс. разница от последней онлайн транз. если текущая онлайн.general_diff
мин. разница между онлайн и оффлайн транз.general_ceil
макс. разница от последней транз. если online статусы разные (оффлайн-онлайн, онлайн-оффлайн)
min_intervals = legit_cfg["time"]["min_intervals"]
online_time_diff = min_intervals["online_time_diff"]
online_ceil = min_intervals["online_ceil"]
general_diff = min_intervals["general_diff"]
general_ceil = min_intervals["general_ceil"]
print(f"""Время в минутах
online_time_diff: {online_time_diff}
online_ceil: {online_ceil}
general_diff: {general_diff}
general_ceil: {general_ceil}""")
Время в минутах online_time_diff: 6 online_ceil: 60 general_diff: 30 general_ceil: 90
# Условно семплированное время создаваемой транзакции, которое нуждается в проверке
timstamp_check_min = pd.to_datetime("2025-01-31 08:19:00", format="%Y-%m-%d %H:%M:%S")
timstamp_check_min_unix = pd_timestamp_to_unix(timstamp_check_min)
timestamp_sample_check_min = pd.DataFrame([{"timestamp":timstamp_check_min, "unix_time":timstamp_check_min_unix}])
# Время ближайшей транзакции. 5 минут разницы. Допустимая разница 6 минут и более
nearest_time = pd.to_datetime("2025-01-31 08:14:00", format="%Y-%m-%d %H:%M:%S")
nearest_unix = pd_timestamp_to_unix(nearest_time)
print(f"nearest: {nearest_time}, {nearest_unix}")
# Время последней транзакции
last_time = pd.to_datetime("2025-01-31 09::00", format="%Y-%m-%d %H:%M:%S")
last_unix = pd_timestamp_to_unix(last_time)
print(f"last: {last_time}, {last_unix}")
timestamp_sample_check_min
nearest: 2025-01-31 08:14:00, 1738311240 last: 2025-01-31 09:28:00, 1738315680
timestamp | unix_time | |
---|---|---|
0 | 2025-01-31 08:19:00 | 1738311540 |
Заполняем датафрейм
- с условно уже сущействующими ближайшей и последней транзакциями
- указываем
client_id
, время и онлайн статус и какая это транзакция в контексте демонстрации - Выставить online флаги для ближайшей и последней в соответствии с кейсом: True и False
trans_time_test.loc[1, ["client_id", "txn_time","unix_time", "online", "txn_type"]] = 28, nearest_time, nearest_unix, True, "closest"
trans_time_test.loc[2, ["client_id", "txn_time","unix_time", "online", "txn_type"]] = 28, last_time, last_unix, False, "last"
trans_time_test
client_id | txn_time | unix_time | online | is_fraud | txn_type | |
---|---|---|---|---|---|---|
1 | 28.0 | 2025-01-31 08:14:00 | 1.738311e+09 | True | NaN | closest |
2 | 28.0 | 2025-01-31 09:28:00 | 1.738316e+09 | False | NaN | last |
Запуск функции
# Выставить в функции аргумент online в соответсвии с тест-кейсом
# True - создаваемая транзакция - онлайн. False - оффлайн
txn_time, txn_unix = check_min_interval_from_near_txn(client_txns=trans_time_test, timestamp_sample=timestamp_sample_check_min, \
online=True, round_clock=True, configs=configs)
# Запись сгенерированной транзакции в датафрейм: время, online флаг
trans_time_test.loc[3, ["client_id", "txn_time","unix_time", "online", "txn_type"]] = 28, txn_time, txn_unix, True, "current"
trans_time_test = trans_time_test.sort_values("txn_time")
# Расчет времени между транзакциями в минутах
trans_time_test["abs_time_proximity"] = trans_time_test.unix_time.sub(trans_time_test.unix_time.shift(1)).div(60)
trans_time_test
client_id | txn_time | unix_time | online | is_fraud | txn_type | abs_time_proximity | |
---|---|---|---|---|---|---|---|
1 | 28.0 | 2025-01-31 08:14:00 | 1.738311e+09 | True | NaN | closest | NaN |
2 | 28.0 | 2025-01-31 09:28:00 | 1.738316e+09 | False | NaN | last | 74.000000 |
3 | 28.0 | 2025-01-31 10:22:04 | 1.738319e+09 | True | NaN | current | 54.066667 |
Как видно выше, текущая транзакция получила новое время на основе времени последней транзакции. Ко времени последней был прибавлено 54 минуты что в рамках границ для случайной разницы во времени между онлайн и оффлайн транзакциями - 30-90 минут.
Основная логика функции
- проверяет есть ли созданные ранее транзакции
- если нет ни одной то просто семплирует время датафрейма с timestamp-ами и возвращает его
- если есть хотя бы одна транзакция, то семплирует время и проверяет его на нарушение минимальных интервалов времени между ним и уже имеющимися транзакциями через функцию
check_min_interval_from_near_txn
. Если нарушены интервалы, то создается другое время на основании времени последней транзакции, тоже черезcheck_min_interval_from_near_txn
и возвращается как результат.
def get_legit_txn_time(trans_df, time_weights, configs, round_clock, online=None):
"""
Генерация времени для легальной транзакции
------------------------------------------
trans_df: pd.DataFrame. Транзакции текущего клиента. Откуда брать информацию по предыдущим транзакциям клиента
time_weights: pd.DataFrame. Веса часов в периоде времени
configs: LegitCfg. Конфиги и данные для генерации легальных транзакций.
round_clock: bool. Круглосуточная или дневная категория.
online: bool. Онлайн или оффлайн покупка. True or False
-------------------------------------------
Возвращает время для генерируемой транзакции в виде pd.Timestamp и в виде unix времени
"""
timestamps = configs.timestamps
timestamps_1st = configs.timestamps_1st
# Время последней транзакции клиента. pd.Timestamp и unix в секундах
last_txn_time = trans_df.txn_time.max()
# Если нет никакой предыдущей транзакции т.е. нет последнего времени совсем
if last_txn_time is pd.NaT:
# время транзакции в виде timestamp и unix time.
return sample_time_for_trans(timestamps=timestamps_1st, time_weights=time_weights)
# Если есть предыдущая транзакция
# берем случайный час передав веса часов для соответсвующейго временного паттерна
txn_hour = time_weights.hours.sample(n=1, weights=time_weights.proportion, replace=True).iloc[0]
# фильтруем по этому часу timestamp-ы и семплируем timestamp уже с равной вероятностью
# Дальше будем обрабатывать этот timestamp в некоторых случаях
timestamps_subset = timestamps.loc[timestamps.hour == txn_hour]
timestamp_sample = timestamps_subset.sample(n=1, replace=True)
# check_min_interval_from_near_txn проверит ближайшие к timestamp_sample по времени транзакции
# в соответствии с установленными интервалами и если время до ближайшей транзакции меньше
# допустимогшо, то создаст другой timestamp. сли интервал допустимый, то вернет исходный timestamp
txn_time, txn_unix = check_min_interval_from_near_txn(client_txns=trans_df, timestamp_sample=timestamp_sample, \
online=online, round_clock=round_clock, configs=configs)
return txn_time, txn_unix
1. Функция генератор локации и мерчанта транзакции get_txn_location_and_merchant
¶
- модуль
data_generator.legit.txndata
. Ссылка на исходный код в Github
Возвращает:
- id мерчанта, координаты транзакции, ip адрес транзакции если это применимо и город транзакции
Основная логика
В зависимости от online флага текущей транзакции генерирует данные немного по-разному
- онлайн покупка: семплирование id онлайн мерчанта, координаты просто координаты города клиента, ip просто ip клиента, город - город клиента
- оффлайн покупка семплирует данные оффлайн мерчанта и оттуда берет: id мерчанта, его координаты как координаты транзакции
Демонстрация¶
# Импорт самой функции
from data_generator.legit.txndata import get_txn_location_and_merchant
# Возьмем нужные данные из конфиг класса созданного ранее
offline_merchants = configs.offline_merchants
clients = configs.clients
# namedtuple с информацией об одном клиенте для примера
for row in clients.iloc[[0]].itertuples():
one_client_info = row
one_client_info
Pandas(Index=0, client_id=2269, birth_date=Timestamp('1988-11-03 00:00:00'), sex='female', region='Кемеровская', city='Кемерово', lat=55.3909721, lon=86.0467864, city_id=48, home_ip='2.60.8.101')
# Фильтруем оффлайн мерчантов по городу клиента т.к. на вход подается отфильтрованный датафрейм
offline_merchants_test_one_txn = offline_merchants[offline_merchants["city"] == one_client_info.city]
offline_merchants_test_one_txn.head(2)
city | city_id | category | merchant_id | merchant_lat | merchant_lon | |
---|---|---|---|---|---|---|
319 | Кемерово | 48 | gas_transport | 320.0 | 55.288288 | 86.073445 |
320 | Кемерово | 48 | grocery_pos | 321.0 | 55.305180 | 86.101067 |
# Вызов get_txn_location_and_merchant - оффлайн покупка
get_txn_location_and_merchant(online=False, merchants_df=offline_merchants_test_one_txn, category_name="gas_transport", \
client_info=one_client_info, configs=configs)
# Получаем offline merchant id, его широта и долгота, значение для колонки ip, город транзакции
(np.float64(4555.0), np.float64(55.388418458753), np.float64(86.168305358768), 'not applicable', 'Кемерово')
# Вызов get_txn_location_and_merchant - онлайн покупка
get_txn_location_and_merchant(online=True, merchants_df=offline_merchants_test_one_txn, category_name="gas_transport", \
client_info=one_client_info, configs=configs)
# Получаем online merchant id, широта и долгота города клиента(координаты транзакции), ip клиента, город клиента(город транзакции)
(np.int64(6858), 55.3909721, 86.0467864, '2.60.8.101', 'Кемерово')
Основные действия функции
- Создать случайную сумму транзакции по нормальному распределению в соот-вии с характеристиками категории покупки
- получить данные мерчанта, локации транзакции и ip от
get_txn_location_and_merchant
- Определить типа распределения времени к которому относится категория: круглосуточная оффлайн, онлайн, оффлайн дневная и получить время от
get_legit_txn_time
- Если это онлайн то семплировать id девайса клиента
- Задать канал транзакции в зависимости от онлайн статуса
- Задать значения для статичных полей, которые неизменны для легальных транзакций: is_fraud, txn_type (всегда purchase), rule и др.
- Собрать все значения в словарь через функцию
build_transaction
и вернуть их
def generate_one_legit_txn(client_info, client_trans_df, client_device_ids, category, \
merchants_df, configs):
"""
Генерация одной легальной транзакции покупки для клиента.
------------------------------------------------
client_info: namedtuple, полученная в результате итерации с помощью
.itertuples() через датафрейм с информацией о клиентах.
client_trans_df: pd.DataFrame. Транзакции клиента.
client_device_ids: pd.Series. id девайсов клиента.
category: pd.DataFrame. Одна запись с категорией и её характеристиками.
merchants_df: pd.DataFrame. Оффлайн мерчанты заранее отфильтрованные по
городу клиента т.к. это легальные транзакции.
configs: LegitCfg. Конфиги и данные для генерации легальных транзакций.
"""
all_time_weights = configs.all_time_weights
client_id = client_info.client_id
category_name = category["category"].iloc[0]
round_clock = category["round_clock"].iloc[0]
online = category["online"].iloc[0]
# средняя сумма для этой категории
amt_mean = category["avg_amt"].iloc[0]
# стандартное отклонение сумм для этой категории
amt_std = category["amt_std"].iloc[0]
# случайно сгенерированная сумма транзакции, но не менее 1
amount = max(1, round(np.random.normal(amt_mean, amt_std), 2))
amount = amt_rounding(amount=amount, rate=0.6) # Случайное целочисленное округление
# 1. Offline_24h_Legit - круглосуточные оффлайн покупки
if not online and round_clock:
weights_key = "Offline_24h_Legit"
channel = "POS"
device_id = np.nan
# 2. Online_Legit - Онлайн покупки
elif online:
weights_key = "Online_Legit"
# локация клиента по IP. Т.к. это не фрод. Просто записываем координаты города клиента
channel = "ecom"
device_id = client_device_ids.sample(n=1).iloc[0]
# 3. Offline_Day_Legit - Оффлайн покупки. Дневные категории.
elif not online and not round_clock:
weights_key = "Offline_Day_Legit"
channel = "POS"
device_id = np.nan
# Генерация мерчанта, координат транзакции. И если это онлайн, то IP адреса с которого сделана транзакция
merchant_id, trans_lat, trans_lon, trans_ip, trans_city = \
get_txn_location_and_merchant(online=online, merchants_df=merchants_df, \
category_name=category_name, client_info=client_info, \
configs=configs)
time_weights = all_time_weights[weights_key]["weights"]
# Генерация времени транзакции
txn_time, txn_unix = get_legit_txn_time(trans_df=client_trans_df, time_weights=time_weights, \
configs=configs, round_clock=round_clock, online=online)
# Статичные значения для данной функции.
status = "approved"
txn_type = "purchase"
is_fraud = False
is_suspicious = False
account = np.nan
rule = "not applicable"
# Возвращаем словарь со всеми данными сгенерированной транзакции
return build_transaction(client_id=client_id, txn_time=txn_time, txn_unix=txn_unix, amount=amount, txn_type=txn_type, \
channel=channel, category_name=category_name, online=online, merchant_id=merchant_id, \
trans_city=trans_city, trans_lat=trans_lat, trans_lon=trans_lon, trans_ip=trans_ip, \
device_id=device_id, account=account, is_fraud=is_fraud, is_suspicious=is_suspicious, \
status=status, rule=rule)
Демонстрация¶
# Импорт функций которые используются в generate_one_legit_txn
from data_generator.utils import build_transaction, amt_rounding
from data_generator.legit.time.time import get_legit_txn_time
from data_generator.legit.txndata import get_txn_location_and_merchant
client_devices = configs.client_devices
for row in clients.iloc[[0]].itertuples():
one_client_info = row
one_client_info
Pandas(Index=0, client_id=2269, birth_date=Timestamp('1988-11-03 00:00:00'), sex='female', region='Кемеровская', city='Кемерово', lat=55.3909721, lon=86.0467864, city_id=48, home_ip='2.60.8.101')
# id устройств которые принадлежат клиенту
device_id_demo = client_devices.loc[client_devices.client_id == one_client_info.client_id, "device_id"]
device_id_demo
2148 3848 7068 3849 Name: device_id, dtype: int64
# Датафрейм под запись транзакций
client_trans_df = create_txns_df(base_cfg["txns_df"])
client_trans_df
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule |
---|
Онлайн пример
# выберем одну категорию и возьмем запись с ее характеристиками
category_demo_on_txn = cat_stats_full.query("category == 'shopping_net'")
category_demo_on_txn
category | avg_amt | amt_std | cat_count | online | share | fraud_count | fraud_share | round_clock | |
---|---|---|---|---|---|---|---|---|---|
5 | shopping_net | 1252.224798 | 3558.296372 | 41779 | True | 0.07518 | 506 | 0.012111 | False |
one_txn_demo = generate_one_legit_txn(client_info=one_client_info, client_trans_df=client_trans_df, \
client_device_ids=device_id_demo, category=category_demo_on_txn, \
merchants_df=offline_merchants_test_one_txn, configs=configs)
pd.DataFrame([one_txn_demo])
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2269 | 2025-01-06 14:54:00 | 1736175240 | 3293.61 | purchase | ecom | shopping_net | True | 6923 | Кемерово | 55.390972 | 86.046786 | 2.60.8.101 | 3849 | NaN | False | False | approved | not applicable |
Оффлайн пример
# выберем одну категорию и возьмем запись с ее характеристиками
category_demo_on_txn2 = cat_stats_full.query("category == 'grocery_pos'")
category_demo_on_txn2
category | avg_amt | amt_std | cat_count | online | share | fraud_count | fraud_share | round_clock | |
---|---|---|---|---|---|---|---|---|---|
1 | grocery_pos | 1738.279905 | 773.284951 | 52553 | False | 0.094568 | 485 | 0.009229 | False |
one_txn_demo2 = generate_one_legit_txn(client_info=one_client_info, client_trans_df=client_trans_df, \
client_device_ids=device_id_demo, category=category_demo_on_txn2, \
merchants_df=offline_merchants_test_one_txn, configs=configs)
pd.DataFrame([one_txn_demo2])
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2269 | 2025-01-01 18:18:00 | 1735755480 | 921.0 | purchase | POS | grocery_pos | False | 1168.0 | Кемерово | 55.410074 | 86.060446 | not applicable | NaN | NaN | False | False | approved | not applicable |
Основные функции класса
- демонстрации работы не будет, просто опишу его функционал
- Создает поддиректорию
chunks
в директории текущей генерации легальных транзакций - Пишет транзакции в файлы чанками в эту поддиректорию. Размер чанков определяется в конфигах
legit.yaml
- Читает все созданные чанки и собирает в единый датафрейм
- Записывает этот датафрейм в две директории: текущего запуска генератора транзакций и последнего запуска генератора
Подразумевается встраивание recorder-а в функцию генерации множества легальных транзакций. Сам по себе он не определяет логику когда писать/читать транзакции.
Основная логика работы
- Итерирование через семпл клиентов хранящихся в
configs.clients
- Генерация случайного числа транзакций для каждого клиента. Число транзакций берется из обрезанного норм. распределения. Параметры распределения указаны в
legit.yaml
- Запись создаваемых транзакций чанками
- В конце чтение чанков и сборка единого датафрейма, запись этого датафрейма в файл
def gen_multiple_legit_txns(configs, txn_recorder, ignore_index=True):
"""
Генерирует несколько транзакций для каждого клиента ориентируясь
на существующие транзакции если они есть.
Количество на клиента берется по нормальному распределению с
указанными средним и стандартным отклонением.
Ограничение забито в функцию gen_trans_number_norm: от 1 до 120 транзакций.
---------------------------------------------------
configs: LegitCfg. Конфиги и данные для генерации легальных транзакций.
txn_recorder: LegitTxnsRecorder.
ignore_index: bool. Сбросить ли индекс при конкатенации датафреймов
в финальный датафрейм с транзакциями всех клиентов
"""
clients_df = configs.clients
trans_df = configs.transactions
client_devices = configs.client_devices
offline_merchants = configs.offline_merchants
categories = configs.categories
avg_txn_num = configs.txn_num["avg_txn_num"]
txn_num_std = configs.txn_num["txn_num_std"]
low_bound = configs.txn_num["low_bound"]
up_bound = configs.txn_num["up_bound"]
# Сюда будем собирать сгенрированные транзакции клиента в виде словарей.
client_txns = txn_recorder.client_txns
for client_info in clients_df.itertuples():
txn_recorder.clients_counter += 1
# случайное кол-во транзакций на клиента взятое из нормального распределения с мин. и макс. лимитами
txns_num = gen_trans_number_norm(avg_num=avg_txn_num, num_std=txn_num_std, low_bound=low_bound, \
up_bound=up_bound)
merchants_from_city = offline_merchants[offline_merchants["city"] == client_info.city]
client_transactions = trans_df.loc[trans_df.client_id == client_info.client_id]
# id девайсов клиента для онлайн транзакций
client_device_ids = client_devices.loc[client_devices.client_id == client_info.client_id, "device_id"]
for _ in range(txns_num):
# семплирование категории для транзакции
category = categories.sample(1, replace=True, weights=categories.share)
# генерация одной транзакции
one_txn = generate_one_legit_txn(client_info=client_info, client_trans_df=client_transactions, \
category=category, client_device_ids=client_device_ids, \
merchants_df=merchants_from_city, configs=configs)
# Запись транз-ции в список транз-ций текущего клиента.
client_txns.append(one_txn)
txn_recorder.txns_counter += 1 # счетчик всех транз-ций
# Управление записью транзакций чанками в файлы.
txn_recorder.record_chunk(txn=one_txn, txns_num=txns_num)
# Добавляем созданную транзакцию к транзакциям клиента, т.к. иногда
# при генерации других транзакций нужно знать уже созданные транзакции
one_txn_df = pd.DataFrame([one_txn])
client_transactions = pd.concat([client_transactions, one_txn_df], ignore_index=ignore_index)
client_txns.clear() # Конец генерации на клиента. Чистим список для текущего кл-та
# Сборка цельного датафрейма из чанков записанных в файлы. Датафрейм сохраняется
# в txn_recorder.all_txns.
txn_recorder.build_from_chunks()
# Запись собранного датафрейма в два файла в разные директории: data/generated/lastest/
# И data/generated/history/<своя_папка_с_датой_временем>
txn_recorder.write_built_data()
Демонстрация¶
# Импорт зависимостей
from data_generator.legit.recorder import LegitTxnsRecorder
from data_generator.utils import gen_trans_number_norm
# Нужно создать объект LegitTxnsRecorder для передачи в gen_multiple_legit_txns
# Это надо делать заново если повторяем генерацию в этом ноутбуке т.к. нужно очистить некоторые атрибуты
# txn_recorder, в которых остаются некоторые данные после предыдущего запуска
txn_recorder = LegitTxnsRecorder(configs=configs)
# Путь к директории которую мы создали в самом начале
# В этой директории в папке legit будут храниться созданные легальные транзакции
configs.run_dir
WindowsPath('data/generated/history/generation_run_2025-07-25_121029')
Если запуск повторный с теми же конфигами то в директории указанной в configs.run_dir
открыть папку legit
и удалить ее содержимое, т.к. это содержимое предыдущего запуска. Но это только если мы используем те же конфиги где указан тот же путь до директории прошлого запуска configs.run_dir
. В целом варианте подразумевается что конфиг создается каждый запуск генератора и каждый раз это другая директория, но в тестовых запусках нужно это учесть если повторяем запуск с тем же configs
# Непосредственно генерация транзакций.
gen_multiple_legit_txns(configs=configs, txn_recorder=txn_recorder)
# gen_multiple_legit_txns не возвращает результат. Прочитаем его из файла с транзакциями
# Также датафрейм со всеми созданными транзакциями можно взять из txn_recorder.all_txns
# Соберем полный путь к файлу
data_storage = legit_cfg["data_storage"] # конфиги названий поддиректорий и файлов
leg_dir = data_storage["folder_name"] # название поддиректории легальных транз.
leg_file = data_storage["files"]["txns"] # название файла с легальными транз.
path_to_leg_txns = os.path.join(run_dir, leg_dir, leg_file)
# Чтение датафрейма
multi_leg_txn_demo = pd.read_parquet(path_to_leg_txns)
# Итоговый датафрейм с легальными транзакциями. Уже отсортирован по времени
multi_leg_txn_demo.head(5)
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 13556 | 2025-01-01 01:16:00 | 1735694160 | 615.46 | purchase | ecom | shopping_net | True | 6828.0 | Курск | 51.730339 | 36.192645 | 2.60.20.218 | 9613.0 | NaN | False | False | approved | not applicable |
1 | 3733 | 2025-01-01 01:50:00 | 1735696200 | 955.55 | purchase | ecom | grocery_net | True | 6787.0 | Москва | 55.753879 | 37.620373 | 2.60.13.206 | 6350.0 | NaN | False | False | approved | not applicable |
2 | 3289 | 2025-01-01 02:49:00 | 1735699740 | 1746.00 | purchase | ecom | shopping_net | True | 6782.0 | Калининград | 54.707322 | 20.507246 | 2.60.12.44 | 5588.0 | NaN | False | False | approved | not applicable |
3 | 3769 | 2025-01-01 04:02:00 | 1735704120 | 204.63 | purchase | ecom | grocery_net | True | 6820.0 | Екатеринбург | 56.838633 | 60.605489 | 2.60.13.241 | 6419.0 | NaN | False | False | approved | not applicable |
4 | 1245 | 2025-01-01 04:23:00 | 1735705380 | 1.00 | purchase | ecom | shopping_net | True | 6862.0 | Нижний Новгород | 56.324209 | 44.005395 | 2.60.4.161 | 2105.0 | NaN | False | False | approved | not applicable |
# Сколько транзакций сгенерировалось
multi_leg_txn_demo.shape
(5045, 19)
Оркестрация генерации легальных транзакций¶
Функции класса
- Собирает все что нужно для генерации легальных транзакций воедино
- Запускает полную генерацию легальных транзакций
При создании объекта класса нужно просто передать загруженные конфиги из yaml
файлов и путь к созданной директории текущего запуска генератора (создается функцией make_dir_for_run
из модуля data_generator.runner.utils
)
class LegitRunner:
"""
Запуск генератора легальных транзакций.
----------
Атрибуты:
----------
cfg_builder: LegitConfigBuilder.
configs: LegitCfg. Конфиги и данные для генерации легальных транзакций.
txn_recorder: LegitTxnsRecorder. Запись легальных транзакций в файл.
text: str. Текст для вставки в спиннер.
"""
def __init__(self, base_cfg, legit_cfg, time_cfg, run_dir):
"""
base_cfg: dict. Конфиги из base.yaml
legit_cfg: dict. Конфиги из legit.yaml
time_cfg: dict. Конфиги из time.yaml
run_dir: str. Название директории для хранения сгенерированных
данных текущей генерации.
"""
self.cfg_builder = LegitConfigBuilder(base_cfg=base_cfg, legit_cfg=legit_cfg, \
time_cfg=time_cfg, run_dir=run_dir)
self.configs = self.cfg_builder.build_cfg()
self.txn_recorder = LegitTxnsRecorder(configs=self.configs)
self.text = "Legit txns generation"
@spinner_decorator
def run(self):
"""
Запуск генератора.
"""
configs = self.configs
txn_recorder = self.txn_recorder
gen_multiple_legit_txns(configs=configs, txn_recorder=txn_recorder)