Время прочтения: 5 мин.

Занимаясь поиском доступных банкоматов в г. Москва, случайно обнаружил, что не у всех банкоматов указаны ближайшие станции метрополитена (далее – метро).

Родилась идея с помощью инструментов машинного обучения (ML) создать модель поиска ближайшей станции метро для банкоматов, у которых она не указана.

Для выгрузки списка банкоматов был использован язык программирования Python и библиотека request. Каждый банкомат был выгружен в виде строки:

coordinates latitude;coordinates longitude;fullAddress;subwayStations
55.758824;37.752139;г. Москва, ш. Энтузиастов, д. 31стр39;[['Шоссе Энтузиастов', 'ЖЕЛ', '50'], ['Шоссе Энтузиастов', 'КРЖ', '230']];

Полученный датасет выглядит следующим образом:

df.head()

Для лучшей обрабатываемости преобразуем сначала «адрес», используя команды dataframe. str.replace и re.sub:

import re
df_address = df["fullAddress"].str.split(",", expand=True)
df_address_1 = df_address.drop(columns=[0,3,4])
df_address_1[2] = df_address_1[2].str.replace('(^\D+)|(\W+$)', '') 
for i in range(len(df_address_1[2])):
    if str(df_address_1[2][i]).isdigit() == False:
        try:
            df_address_1[2][i] = re.sub(r'(\d+$)', '', df_address_1[2][i])
        except:
            pass
df_address_1[2] = df_address_1[2].str.replace('(\D+$)', '') 

Теперь очистим столбец станций метро:

df_metro = df["subwayStations"].str.split(",", expand=True)
df_metro = df_metro[0]
df_metro = df_metro.str.replace("'", "") 
df_metro= df_metro.str.replace("[", "") 
df_metro = df_metro.str.replace("]", "") 
df_metro.head()

Удаляя «исходные» столбцы ‘fullAddress’, ‘subwayStations’, получаем датасет в более удобном виде:

Пустые ячейки метро заменим на «0». Очистим выборку от строк с пустыми значениями и закодируем категориальные признаки.

df.loc[df_2['subway'] == '', 'subway'] = '0'
df.dropna(inplace=True)
from sklearn import preprocessing
result = df.copy()
encoders = {}
for column in result.columns:
    # если тип столбца - object, то нужно его закодировать
    if result.dtypes[column] == np.object: 
       # для колонки column создаем кодировщик
       encoders[column] = preprocessing.LabelEncoder()
       # применяем кодировщик к столбцу и перезаписываем столбец
       result[column] = encoders[column].fit_transform(result[column])    
encoded_data = result # содержит закодированные кат. признаки 
encoded_data.head()

Выделим данные по метро, где нет значений в отдельный датафрейм, и удалим из основного Dataset.

encoded_data_metro =  encoded_data.loc[encoded_data['subway'] == 0]
encoded_data = encoded_data.drop(encoded_data.loc[encoded_data['subway'] == 0].index)

Построим матрицу корреляций, проверим – имеются ли данные с единичной линейной зависимостью.

encoded_data_corr = encoded_data.corr()
plt.subplots(figsize=(12, 10))
sns.heatmap(encoded_data.corr(), square = True, annot=True)
plt.show()

Видим, что признаки не коллинеарны. Значит все признаки могут быть использованы для создания модели.

Библиотеки, которые были использованы для создания разных моделей классификации ML: CatBoost Classifier, Gradient Boosting Classifier, Hist Gradient Boosting Classifier, XGB Classifier, LGBM Classifier.

# выбросим колонку, которую будем предсказывать
X, y = encoded_data[encoded_data.columns[0:4]].values, encoded_data[encoded_data.columns[4]].values

разделим на train и test

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

Создадим модели с помощью CatBoostClassifier, Gradient Boosting Classifier, XGB Classifier, LGBM Classifier (полный код по ссылке ). Для сравнения моделей напишем функцию и посмотрим где находится рассчитанная станция метро:

def proverka_model(model, name, X_search, t):
    y_search = model.predict([X_search])
    print(y_search)
    try:
        y_search = int(y_search[0])
        df_r = encoded_data.loc[encoded_data['subway'] == y_search]
    except:
        df_r = encoded_data.loc[encoded_data['subway'] == y_search[0][0]]
    n_metro = encoded_data_subway.loc[encoded_data_subway['subway'] == df_r['subway'].iloc[0]] 
    name_metro = df_2.loc[df_2['number']  == n_metro['number'].iloc[0]]
    print(f"По расчету '{name}' ближайшая станция метро '{name_metro['subway'].iloc[0]}' для банкомата расположенного на широте и долготе: {X_search[0]}, {X_search[1]}, время формирования модели {round(t,2)}") 
proverka_model(model_class, "CatBoost", X_search,time_model_class)
proverka_model(model_gb_class, "GradientBoosting", X_search, time_gb_class)
proverka_model(model_XGBC, "xgboost", X_search, time_XGBC)
proverka_model(model_LGBMC, "lightgbm", X_search, time_LGBMC)
proverka_model(gs_rfr, "Random_Forest", X_search, time_gs_rf)

[[10]]

По расчету ‘CatBoost’ ближайшая станция метро ‘Бабушкинская’ для банкомата, расположенного на широте и долготе: 55.880299, 37.694748, время формирования модели 905.05

[10]

По расчету ‘Gradient Boosting’ ближайшая станция метро ‘Бабушкинская’ для банкомата, расположенного на широте и долготе: 55.880299, 37.694748, время формирования модели 6534.01

[10]

По расчету ‘xgboost’ ближайшая станция метро ‘Бабушкинская’ для банкомата, расположенного на широте и долготе: 55.880299, 37.694748, время формирования модели 480.57

[10]

По расчету ‘lightgbm’ ближайшая станция метро ‘Бабушкинская’ для банкомата, расположенного на широте и долготе: 55.880299, 37.694748, время формирования модели 383.23

[20.35307612]

По расчету ‘Random_Forest’ ближайшая станция метро ‘Бибирево’ для банкомата, расположенного на широте и долготе: 55.880299, 37.694748, время формирования модели 45.45

Все модели справились с указанной задачей – по времени лучшая ‘Random_Forest’, по точности: ‘xgboost’, ‘CatBoost’, ‘GradientBoosting’, а по времени и точности ‘lightgbm’. Но есть и недостатки: на карте видно, что имеется еще одна станция метро  — «Медведково», но так как в обучающем датасете  ее не было, то предсказать эту станцию не представляется возможным (файл с данными расположен по ссылке). Таким образом,  с помощью инструментов ML можно создать модель, которая поможет нам находить близлежащую станцию метро от нашего банкомата.