Создание устройств для клиентов и мошенников. Создание 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)