Мой первый open-source продукт
pip install nona
В реальных наборах данных пропущенные значения создают проблему для дальнейшей обработки. Большую ценность имеет подстановка или заполнение отсутствующих значений. К сожалению, стандартные «ленивые» методы, такие как простое использование медианы столбца или среднего значения, не всегда работают должным образом.
В 2021-ом году ко мне пришла идея создания алгоритма на основе методов машинного обучения с прогнозированием по каждому столбцу с пропусками. Данную идею я воплотил сначала схематично на бумаге.
Суть алгоритма заключается в заполнении пропусков различными методами машинного обучения. Циклом проходим по всем столбцам, если столбец с пропущенными значениями — останавливаемся и делаем этот столбец — target. Предыдущие столбцы делим на X_train, X_test. X_test будет соответствовать пропущенным значениям. Значения Y_train берем из столбца (на котором остановились в цикле) в которых нет пропусков. Обучаем X_train и y_train на выбранном нами методе, например гребневой регрессии (Ridge regression). Делаем предсказания на X_test, заполняем пропущенные значения в столбце предсказанными значениями. Так делаем по всем столбцам с пропусками.
Данный алгоритм я регулярно использовал на практике для заполнения пропущенных значений.
В 2023-м году решил на его основе написать библиотеку для Python и провести сравнение с иными методами заполнения пропусков.
Преимущества метода:
Простое и быстрое заполнение пропущенных значений.
Кастомизация используемых методов машинного обучения.
Высокая точность прогнозирования.
Установка
Исходный код в настоящее время размещен на GitHub по адресу: GitHub — AbdualimovTP/nona: библиотека для заполнения пропущенных значений с использованием методов искусственного интеллекта
Двоичные установщики для последней выпущенной версии доступны в каталоге пакетов Python (PyPI)
# PyPI
pip install nona
Зависимости
Быстрый старт
" Из коробки" используем гребневую регрессию, чтобы заполнить пробелы в столбцах где необходимо решить задачу регрессии, и RandomForestClassifier для задачи классификации.
# Загружаем библиотеку
from nona.nona import nona
# Подготовьте ваш датасет, только численные значения в датасете
# Заполняем пропущенные значения
nona(YOUR_DATA)
Повышение точности алгоритма
Вы можете вставить в функцию иные методы машинного обучения. Они должны поддерживать простую реализацию fit и predict.
Параметры алгоритма:
data: подготовленный набор данных
algreg: Алгоритм регрессии для прогнозирования отсутствующих значений в столбцах
algclass: Алгоритм классификации для прогнозирования пропущенных значений в столбцах
# Загружаем библиотеку
from nona.nona import nona
# Подготовьте ваш датасет, только численные значения в датасете
# Заполняем пропущенные значения
nona(data=YOUR_DATA, algreg=make_pipeline(StandardScaler(with_mean=False), Ridge(alpha=0.1)), algclass=RandomForestClassifier(max_depth=2, random_state=0))
Сравнение с иными методами заполнения пропусков
Сравниваемые методы:
Baseline - заполнение средним по столбцу.
KNN - заполнение пропущенных значений с использованием k-ближайших соседей. Отсутствующие значения каждой выборки заполняются с использованием среднего значения ближайших соседей n_neighbors, найденных в обучающем наборе.
MICE - Использование класса IterativeImputer sklearn, который моделирует каждую функцию с отсутствующими значениями как функцию других функций и использует эту оценку для заполнения. Он делает это в итерированном циклическом режиме: на каждом шаге столбец признаков обозначается как выход y, а другие столбцы признаков обрабатываются как входы X. Регрессор подходит для (X, y) для известного y. Затем регрессор используется для предсказания пропущенных значений y. Это делается для каждого признака итеративно, а затем повторяется для раундов заполнения max_iter. Возвращаются результаты финального раунда заполнения.
MissForest - алгоритм заполнения данных на основе машинного обучения, который работает на основе алгоритма Random Forest.
NoNA - мой алгоритм "постолбцового" заполнения пропусков при помощи различных методов машинного обучения.
Для работы я взял Framingham heart study dataset доступный на Kaggle.
На данном датасете мы будем моделировать пропуски в объёме 10%, 20%, 30%, 40%, 50%, 70%, 90% пропущенных значений. Заполнять их описанными методами (каждым по отдельности). И сравнивать результаты с истинными значениями. На выходе получим среднеквадратичную ошибку (RMSE).
for i in [0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.9]:
# we create a random matrix the size of a dataset, randomly fill it with gaps and zeros
randomMatrixNA = np.random.choice([0,np.NaN], (data.shape[0], data.shape[1]), p=[1-i, i])
# fill dataset with missing values
dataWithNA = data + randomMatrixNA
# create datasets with filled gaps
# fill in the middle
Baseline_1_mean = dataWithNA.fillna(dataWithNA.mean())
print(f'Baseline_MEAN, {i*100}, RMSE:' , np.round(mean_squared_error(data, Baseline_1_mean, squared=False), 2))
dataFrameRMSE.loc['Baseline_MEAN'][f'{int(i*100)}%'] = np.round(mean_squared_error(data, Baseline_1_mean, squared=False), 2)
# KNN
imputer = KNNImputer(n_neighbors=15)
KNN = imputer.fit_transform(dataWithNA)
dataFrameRMSE.loc['KNN'][f'{int(i*100)}%'] = np.round(mean_squared_error(data, KNN, squared=False), 2)
print(f'KNN, {i*100}, RMSE:' , np.round(mean_squared_error(data, KNN, squared=False), 2))
# MICE
mice = IterativeImputer(max_iter=10, random_state=0)
MICE = mice.fit_transform(dataWithNA)
dataFrameRMSE.loc['MICE'][f'{int(i*100)}%'] = np.round(mean_squared_error(data, MICE, squared=False), 2)
print(f'MICE, {i*100}, RMSE:' , np.round(mean_squared_error(data, MICE, squared=False), 2))
# MISSFOREST
missforest = MissForest(random_state=0, verbose=0)
MISSFOREST = missforest.fit_transform(dataWithNA)
dataFrameRMSE.loc['MISSFOREST'][f'{int(i*100)}%'] = np.round(mean_squared_error(data, MISSFOREST, squared=False), 2)
print(f'MISSFOREST, {i*100}, RMSE:' , np.round(mean_squared_error(data, MISSFOREST, squared=False), 2))
# nona_Base
dataWithNA_NonaBase = dataWithNA.copy(deep=True)
nona(dataWithNA_NonaBase)
dataFrameRMSE.loc['NONA'][f'{int(i*100)}%'] = np.round(mean_squared_error(data, dataWithNA_NonaBase, squared=False), 2)
print(f'NONA, {i*100}, RMSE:' , np.round(mean_squared_error(data, dataWithNA_NonaBase, squared=False), 2))
Результаты
10% |
20% |
30% |
40% |
50% |
70% |
90% |
|
Baseline - MEAN |
2.67 |
3.8 |
4.7 |
5.66 |
6.4 |
7.4 |
8.43 |
KNN |
2.48 |
3.7 |
4.57 |
5.55 |
6.35 |
7.47 |
8.49 |
MICE |
2.12 |
3.17 |
4.59 |
5.41 |
5.94 |
7.33 |
8.61 |
MISSFOREST |
2.26 |
3.36 |
4.31 |
5.33 |
6.15 |
8.06 |
9.85 |
NONA |
2.24 |
3.35 |
4.28 |
5.16 |
5.83 |
7.12 |
8.43 |
Неплохие результаты для работы алгоритма "из коробки".
На 30%, 40%, 50%, 70%, 90% алгоритм NONA показал лучшую RMSE на данном датасете со смоделированными пропусками. На 10%, 20% второе место, первое за MICE.
В дальнейшем планирую проверить точность заполнения на иных датасетах. Также вижу возможности для улучшения качества прогнозирования. Планирую реализовать в следующих версиях библиотеки.
Комментарии (15)
Refridgerator
10.01.2023 07:33+1Решал на работе похожую задачу связанную с отслеживанием вагонов. Естественно, без машинного обучения — это смерть человека можно списать на неизлечимую и неизвестную науке болезнь, а на производстве такое не прокатит. Да и неоткуда было взяться статистике для биг дата.
В математике задача восстановления пропущенных данных — это задача интерполяции, для которой существует множество уже готовых, детерминированных и предсказуемых решений. Одна только проблема — во всё это надо вникать, а вникать в математику не всем интересно, а врачам наверно и тем более.
Discivery
10.01.2023 08:04На мой взгляд, заполнение пропусков в данных - вредная идея. Это может быть полезно только в случае малых выборок, да и то при условия простых структур данных.
koresh_builder
10.01.2023 08:38+1Вредная или полезная- вопрос применения. Для ориентировочных прикидок, когда учитывается риск неполных исходных данных, имхо, вполне полезная. Или когда непринятие решения, к примеру, дороже, чем принятие неверного.
Discivery
10.01.2023 10:34+1У меня сложилось впечатление, что на мой комментарий ответил ИИ - набор абсолютно пустых бессмысленных фраз )
dvbondarev
10.01.2023 08:05Проше пардону, но как можно сочинить то, чего нет, и выдавать это за реальные данные? Не есть ли это случай так называемого подгона под красивый ответ?
economist75
10.01.2023 17:12+1Сочинять аналитикам приходится постоянно. Реальные данные всегда настолько большие, что:
пустоты в них есть всегда, стоит лишь начать искать. Выбрасывать строки с пустотами нельзя: часто это важные строки, перед или после которых что-то существенное стряслось. Пример: "Сначала вышел из строя спидометр, а потом они врезались в столб и все погибли".
заполненные пустоты выглядят "просто красиво", а заполненные ML-методами - еще и правдоподобны. Статья описывает библиотеку, имеющую все шансы "досочинить" пустоты полагаясь на другие данные. В примере со спидометром: если есть данные тахометра, микрофона, датчика вибрации итд - данные спидометра можно сочинить в верном направлении и даже близко к истине
сами данные никому не показывают, а ML-модель, обученная на частично ML-досочиненных данных - выглядит точно также непонятно, как "черный ящик"
раз истинных данных все равно не существует - можно сочинять смело.
к красивому ответу часто аналитика толкают силком. Вот где настоящая беда. Там и пустоты не помешают.
Refridgerator
10.01.2023 11:08Вот у вас в сравнении упоминается KNN - заполнение пропущенных значений с использованием k-ближайших соседей. И сразу вопросы: а данные-то для этого уже отсортированы? Если да, то по какому критерию и почему он признан наилучшим? А значение k взято с потолка или есть обоснование? А веса где — по умолчанию, все единицы? Так это наихудший случай. А метод наименьших квадратов где, почему среди всех линейных методов вы взяли наименее подходящий?
OBIEESupport
Как любопытно! Интересно, а среднее по больнице, если не известны этимологии болезней, ваша система сгенерить сможет? Допустим, медсестры забыли поставить градусники, а в корпусе лежит 500 неходячих больных, у которых температура - один из показателей качества лечения? А без температуры больных робот-разносильщик лекарств не дает разнести лекарства.
vassabi
при том что
(плачет)
я думаю что сможет сгенерить.
главное только чтобы потом не сделали стрелочником автора библиотеки :)
OBIEESupport
Если бы вы только знали, насколько физика каждого реального процесса тонка, когда в нее встраивают KPI! Как программисты начинают разворачивать циклы, делать CASE по UNICODE? Больница - это еще простой пример. Есть интереснее. Допустим: забыли у вагонов проверить пары при выведении их на главный ход Транссиба. Через сколько времени есть вероятность схода вагона, если допустимая скорость на 1000 км трассы не превышает 25 км в час, а предельный допуск расхождения пути на холоде превышен в два раза?
vassabi
японская бензопила и сибирские лесорубы? https://4tob.ru/anekdots/2457 :)
OBIEESupport
Это как раз корректный пример экспериментального определения уровня вязкости инструментальной стали в условиях избытка пиломатериалов и достаточного количества времени на починку.
economist75
Сможет, опираясь на предшествующую динамику измерений. Но если у Васи Пупкина ни разу не измерили температуру (весь ряд данных - <NA>) - то ML, не имея точки >U< (36.7), по идее, не должен ничего сочинять.
В том и сила ML (и вообще всей статистики), что они не просто могут что-то утверждать, но и всегда говорят о степени своей уверенности, вероятности, доверительном интервале итд. Другое дело что в эти метрики редко заглядывают, но в случае с пациентами - шансы выше.