
Эта статья будет полезна DS специалистам, и тем, кто хоть когда-нибудь сталкивался с такой проблемой, как выбросы в данных или OOD (out of distribution), и ищет пути решения проблем, возникающих из-за них.
Появление идеи
Машинное обучение я начал изучать основательно в декабре 2024 года, где то в августе 2025 у меня возник вопрос: «А как правильно работать с выбросами, если они не являются ошибкой?». Удаление выбросов — это просто игнорирование проблемы, которое потом повлечет за собой падение качества модели в проде, оставление выбросов без внимания могут создавать сложности в достижении необходимого уровня качества, либо потребуется использовать более сложные алгоритмы.
Потратив около часа на дискуссию с ИИ чат-ботом, как стоит поступить, пришли к выводу, что на данном этапе есть такой способ, как clipping, его суть в том, чтобы просто заменять значения, которые превышают определенный порог, конкретным значением.
Примечание: вы меня спросите, а как же логарифмирование, преобразование box-cox и т. д.? А я отвечу, а что если в распределении выбросы редки или распределение не имеет длинного хвоста, тогда мы можем потерять ценную информацию в данных.
Метод clipping’a довольно топорный поэтому он мне не очень понравился, а услышав знакомое слово (я увлекаюсь звукозаписью более 10 лет) я понимаю, что это же один из методов компрессии сигнала, где по сути идет та же борьба с пиками в сигнале, которые так же сильно били аналоговое оборудование.
Суть компрессии в том, чтобы значения сигнала, которые превышают определенный порог уменьшать в выбранном соотношении, а тот сигнал, который ниже этого порога, оставлять без изменений.

И тут я понял, что нужно делать компрессор для данных.
MVP или простая реализация алгоритма
Я начал с простой реализации алгоритма в обычной функции, где старался подобрать наиболее эффективный подход и его математическое выражение, где основным требованием был получить алгоритм, который:
Легко интерпретировать;
Легко настраиваемый;
Интуитивно понятный.
Спустя несколько итераций, и нескольких работ, которые проходили ревью на Яндекс Практикуме, где я обучаюсь и смог отработать порядок работы с этой функцией, понять ее минусы и плюсы, найти понимание, как вообще понять, что твоя компрессия действительно полезна для данного признака.
Я получил такой код:
# Импорты
import pandas as pd
import numpy as np
# Функция для компрессии данных
def compress(data, threshold, coef, method='power', dry=0):
data = np.asarray(data, dtype=np.float64)
mask = data > threshold
compressed = data.copy()
if method == 'power':
compressed[mask] = threshold + np.power(data[mask] - threshold, coef)
else:
compressed[mask] = threshold + np.log1p(data[mask] - threshold) * coef
return dry * data + (1 - dry) * compressedПример применения функции до и после:


Из плюсов:
Помимо линейных моделей, данный подход положительно показывал и на моделях основанных, на деревьях решений, как принято устойчивы к выбросам, но тут работает другой эффект, признаки между собой начинают лучше взаимодействовать, что дает некий прирост в качестве модели.


Оценить поможет тебе компрессирование признака или нет — mutual_information на обучающей выборке, и с помощью такого рейтинга признаков самостоятельно отобрать оптимальный набор признаков.

Все вроде хорошо, теперь к минусам:
Было дико неудобно ее использовать, и стояли следующие вопросы:
А что, если выбросы с обеих сторон распределения значений?
А что, если нужно использовать разные виды компрессии из каждых сторон?
А что если в распределении есть, как положительные, так и отрицательные значение?
С кучей этих не решенных вопросов я жил спокойно до момента разговора с человеком из индустрии, который активно уже работает в DS/ML, где он мне рассказал, что вообще существует такая проблема, как OOD, из-за которой нужно переучивать модель, пристально мониторить ее прогнозы, чтобы в какой то момент, когда отвалился какой-то датчик на оборудовании модель не стала прогнозировать откровенную чушь.
И тут я понял — пора, пора решать те вопросы, которые нависли над моей функцией, и которая в целом способна помочь хотя бы частично решить вопрос OOD, а не только выбросы в распределении.
Реализация кастомного трансформера
Почему кастомный трансформер? Я хотел, чтобы данный инструмент можно было бы легко внедрить в пайплайны, а именно, чтобы он был полностью совместим с популярно библиотекой sklearn, и ни на каком этапе не возникало утечки данных (тут речь про определение порога срабатывали самого компрессора).
Тут я не буду расписывать все технические моменты, как писать класс для sklearn, а расскажу, как я решил вопросы, которые стояли перед этой задачей.
Решение оказалось довольное тривиальное и одно сразу на все вопросы, а именно, раз мы компрессируем значения только с одной стороны, тогда нам нужно просто последовательно обработать сигнал, с начала с одной стороны, потом с другой, просто меняя знак у значений и прибавляя к распределению полученное минимальное значение, так мы себе гарантировано получаем нужную сторону распределения и то, что все значения положительные. А потом после преобразований возвращаем распределение на место, откуда взяли. И теперь нам только остается в нужный момент подать нужный аргумент, для определения соответствующего метода компрессии.
Но трудности ждали впереди, основной проблемой была валидация входных признаков, и гибкость применения самого трансформера. Одно дело, когда я сам понимаю что как работает и могу не вставлять отрицательные степени, так как окончательно предугадать результат не смогу, другое дело когда это могут сделать из-за опечатки или просто решив поэкспериментировать, что будет. Поэтому пришлось прописать огромное полотно проверок задаваемых параметров. Далее хотелось бы сохранить гибкость, которая есть во всех инструментах sklearn, чтобы он лаконично вписывался к остальным и не возникало трудностей, поэтому необходимо было продумать логику, как трансформер будет интерпретировать входные параметры, которые можно подать скляром, списком или списком списков (нужно для обработки обеих сторон признака).
Заключение
На этом я хочу закончить, моей целью было рассказать, что теперь есть такой инструмент, что я готов выслушать ваше мнение и буду рад фидбеку. Ссылка на репозитория ниже, документацию можно будет найти в README репозитория.
