Создание устройств для клиентов и мошенников. Создание id онлайн мерчантов¶

  • ноутбуки лучше просматривать на Github pages, т.к. при просмотре прямо в репозитории могут быть проблемы с отображением, например, обрезка вывода с широкими датафреймами. Если в адресной строке браузера есть iaroslav-dzh.github.io, то вы уже на Github pages.
    Ссылки:
    • Ссылка на этот ноутбук
    • Ссылка на страницу генератора где есть все ноутбуки

Информация о ноутбуке

  • датафреймы с устройствами будут включать id клиента, уникальный id устройств(а) и платформу устройств(а) - Windows, Android и т.д.
  • у клиента может быть одно или более устройств
  • онлайн мерчанты это просто уникальные id мерчантов не пересекающиеся с оффлайн мерчантами
In [5]:
import pandas as pd
import numpy as np
import os
import pyarrow
from data_generator.utils import load_configs
In [2]:
np.set_printoptions(suppress=True)
pd.set_option('display.max_columns', None)
In [3]:
os.chdir("..")
os.getcwd()
Out[3]:
'C:\\Users\\iaros\\My_documents\\Education\\projects\\fraud_detection_01'
In [6]:
# Базовые конфиги
base_cfg = load_configs("./config/base.yaml")

# Пути к файлам
data_paths = base_cfg["data_paths"]

Загрузка данных:

  • оффлайн мерчантов
  • клиентов
In [10]:
offline_merchants = pd.read_parquet(data_paths["base"]["offline_merchants"])
clients = pd.read_parquet(data_paths["clients"]["clients"])

Генерация id для онлайн мерчантов¶

Просто, чтобы было чем заполнить поле merchant_id для онлайн покупок

In [11]:
offline_merch_max_id = int(offline_merchants.merchant_id.max())
online_merchant_ids = pd.Series([i for i in range(offline_merch_max_id + 1, offline_merch_max_id + 201)])
online_merchant_ids
Out[11]:
0      6777
1      6778
2      6779
3      6780
4      6781
       ... 
195    6972
196    6973
197    6974
198    6975
199    6976
Length: 200, dtype: int64

Выгрузка online_merchant_ids в файл¶

In [12]:
online_merchant_ids.to_csv(data_paths["base"]["online_merchant_ids"], index=False)



Генерация device-ов¶

  • для клиентов и для потенциальных мошенников

1. Девайсы клиентов¶

In [13]:
# проценты использования платформ с мая 2024 по май 2025 по данным https://gs.statcounter.com/os-market-share
# Сделаем это весами, для случайного назначения клиентам их платформ(ы)

platforms_mobile= pd.DataFrame({"platform":["Android", "iOS"], \
                              "weight":[16, 3], "type":["mobile","mobile"]})
platforms_mobile["weight"] = platforms_mobile.weight.div(platforms_mobile.weight.sum())
platforms_mobile
Out[13]:
platform weight type
0 Android 0.842105 mobile
1 iOS 0.157895 mobile
In [14]:
platforms_desktop = pd.DataFrame({"platform":["Windows", "macOS", "Linux"], \
                              "weight":[26, 8, 4], "type":["desktop","desktop","desktop"]})
platforms_desktop["weight"] = platforms_desktop.weight.div(platforms_desktop.weight.sum())
platforms_desktop
Out[14]:
platform weight type
0 Windows 0.684211 desktop
1 macOS 0.210526 desktop
2 Linux 0.105263 desktop
In [15]:
client_devices = clients[["client_id"]].copy()
In [16]:
client_devices["platform"] = "temp_value"
client_devices["device_id"] = 0
In [17]:
# добавим еще по записи на каждого клиента. Т.к. максимальное количество девайсов - 2.
# Потом просто у кого не будет второго девайса, те записи будут удалены

client_devices = pd.concat([client_devices, client_devices], ignore_index=True)
In [18]:
client_devices.shape
Out[18]:
(10738, 3)
In [19]:
# назначение платформ клиентам. 
# Клиент может иметь 1 или 2 платформы: мобильную и ПК или какую-то одну из них. 
# Это, конечно же упрощение. Но для демонтсрации антифрод-правил связанных с девайсами, думаю достаточно.


# id для девайса. Это будет своего рода уникальность этого девайса среди других, своего рода фингерпринт.
device_id = 1

for row in clients.itertuples():
    client_id = row.client_id
    
    # если случайное float число меньше или равно 0.8, то у клиента будет два девайса.
    # Т.е. примерно 80% клиентов будут с двумя девайсами
    device_rand_value = np.random.uniform(0,1)
    
    if device_rand_value <= 0.8:
        client_subset = client_devices.loc[client_devices.client_id == client_id]
        i = 0
        for row_2 in client_subset.itertuples():
            if i == 0:
                sample_mobile = platforms_mobile["platform"].sample(n=1, weights=platforms_mobile.weight).iloc[0]
                client_devices.loc[row_2.Index, ["platform", "device_id"]] = (sample_mobile, device_id)
                device_id += 1
                i += 1
            else:
                sample_desktop = platforms_desktop["platform"].sample(n=1, weights=platforms_desktop.weight).iloc[0]
                client_devices.loc[row_2.Index, ["platform", "device_id"]] = (sample_desktop, device_id)
                device_id += 1
                

            
    # ~15% будут иметь только мобильный девайс
    elif device_rand_value > 0.8 and device_rand_value < 0.95:
        # берем только первую строчку из client_devices для клиента т.к. девайс будет один
        client_subset = client_devices.loc[client_devices.client_id == client_id].head(1)
        sample_mobile = platforms_mobile["platform"].sample(n=1, weights=platforms_mobile.weight).iloc[0]
        client_devices.loc[client_subset.index, ["platform", "device_id"]] = (sample_mobile, device_id)
        device_id += 1

    # и оставшиеся 5% - только ПК.
    else:
        # берем только первую строчку из client_devices для клиента т.к. девайс будет один
        client_subset = client_devices.loc[client_devices.client_id == client_id].head(1)
        sample_desktop = platforms_desktop["platform"].sample(n=1, weights=platforms_desktop.weight).iloc[0]
        client_devices.loc[client_subset.index, ["platform", "device_id"]] = (sample_desktop, device_id)
        device_id += 1
In [20]:
client_devices.head()
Out[20]:
client_id platform device_id
0 1 Android 1
1 2 Windows 3
2 3 Android 4
3 4 Android 6
4 5 Android 8
In [21]:
# клиенты с одним девайсом

one_device_clients = client_devices.query("platform == 'temp_value'").client_id
one_device_clients.shape[0] /  client_devices.client_id.nunique()
Out[21]:
0.1991059787669957
In [22]:
# клиенты только с мобильным девайсом

mobile_only_clients = client_devices[(client_devices.client_id.isin(one_device_clients)) \
                                   & (client_devices.platform.isin(platforms_mobile.platform))]
mobile_only_clients.shape[0] /  client_devices.client_id.nunique()
Out[22]:
0.14937604768113244
In [23]:
# клиенты только с десктопным девайсом

desktop_only_clients = client_devices[(client_devices.client_id.isin(one_device_clients)) \
                                   & (client_devices.platform.isin(platforms_desktop.platform))]
desktop_only_clients.shape[0] /  client_devices.client_id.nunique()
Out[23]:
0.04972993108586329
In [24]:
client_devices = client_devices.query("platform != 'temp_value'").reset_index(drop=True).copy()
In [25]:
# доли платформ

client_devices.platform.value_counts(normalize=True)
Out[25]:
platform
Android    0.443583
Windows    0.323094
macOS      0.100941
iOS        0.084083
Linux      0.048299
Name: proportion, dtype: float64
In [26]:
# проверка что все device_id уникальны

if client_devices.device_id.nunique() != client_devices.shape[0]:
    raise ValueError(f"""Device ids are not unique!
Devices count: {client_devices.shape[0]}
Unique device ids: {client_devices.device_id.nunique()}""")

else:
    print("All device ids are unique")
All device ids are unique

Выгрузка клиентских девайсов в файл¶

In [27]:
client_devices.to_csv(data_paths["base"]["client_devices"], index=False)

2. Девайсы мошенников¶

In [28]:
# назначим вероятности для девайсов мошенников

platforms_fraud = pd.DataFrame({"platform":["Windows", "macOS", "Linux","Android", "iOS"], \
                              "weight":[60, 8, 2, 25, 5], "type":["desktop","desktop","desktop","mobile","mobile"]})
platforms_fraud["weight"] = platforms_fraud.weight.div(platforms_fraud.weight.sum())
platforms_fraud
Out[28]:
platform weight type
0 Windows 0.60 desktop
1 macOS 0.08 desktop
2 Linux 0.02 desktop
3 Android 0.25 mobile
4 iOS 0.05 mobile
In [29]:
# стартовый device_id для фрод девайсов

fraud_device_id = client_devices.device_id.max() + 1
In [30]:
clients_count = clients.shape[0]
clients_count
Out[30]:
5369
In [31]:
# количество девайсов для фрода. Прикидывая что будет сгенерировано около 1 миллиона транзакций и примерно 1% из них будут фродом
# при этом около 0.5% будут онлайн. Т.е. будет фигурировать посторонний девайс. Конечно, возможно, мошеннический девайс будет повторяться
# в общем, создадим 5500 уникальных девайсов

fraud_dev_count = 5500
In [32]:
# Начнем генерацию фрод девайсов с создания массива их id, которые отличаются от клиентских

fraud_devices = pd.DataFrame({"device_id":list(range(fraud_device_id, fraud_device_id + fraud_dev_count))})
In [33]:
fraud_devices["platform"] = "temp_value"
In [34]:
client_devices.device_id.max()
Out[34]:
np.int64(9669)
In [35]:
# первые и последние 5 записей
fraud_devices.iloc[np.r_[0:5,-5:0]]
Out[35]:
device_id platform
0 9670 temp_value
1 9671 temp_value
2 9672 temp_value
3 9673 temp_value
4 9674 temp_value
5495 15165 temp_value
5496 15166 temp_value
5497 15167 temp_value
5498 15168 temp_value
5499 15169 temp_value
In [36]:
# сэмплируем платфрому для каждого device_id

for row in fraud_devices.itertuples():
    sample_platform = platforms_fraud.platform.sample(n=1, weights=platforms_fraud.weight).iloc[0]
    fraud_devices.loc[row.Index, "platform"] = sample_platform
In [37]:
fraud_devices.head()
Out[37]:
device_id platform
0 9670 Windows
1 9671 Windows
2 9672 Windows
3 9673 Windows
4 9674 Windows
In [38]:
assert fraud_devices.device_id.nunique() == fraud_devices.shape[0], "There are duplicated device ids for fraud"
In [39]:
# проверим пропорции полученного результата

fraud_devices_result = fraud_devices.platform.value_counts(normalize=True).reset_index().round({"proportion":2})
platforms_fraud.merge(fraud_devices_result, on="platform")
Out[39]:
platform weight type proportion
0 Windows 0.60 desktop 0.59
1 macOS 0.08 desktop 0.08
2 Linux 0.02 desktop 0.02
3 Android 0.25 mobile 0.25
4 iOS 0.05 mobile 0.05

Выгрузка девайсов мошенников в csv¶

In [40]:
fraud_devices.to_csv(data_paths["base_fraud"]["fraud_devices"], index=False)