Опубликовано KGP Talkie3 октября 2020 г.

Выброс — это точка данных, которая значительно отличается от остальных данных. «Выброс — это наблюдение, которое настолько отличается от других наблюдений, что вызывает подозрение, что оно было произведено другим механизмом.

Следует ли удалять выбросы?

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

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

Какие модели машинного обучения чувствительны к выбросам?

Некоторые модели машинного обучения более чувствительны к выбросам, чем другие. Например, AdaBoost может рассматривать выбросы как «сложные» случаи и придавать выбросам огромные веса, создавая таким образом модель с плохим обобщением.

Линейные модели, в частности линейная регрессия, также могут быть чувствительны к выбросам.

Деревья решений, как правило, игнорируют наличие выбросов при создании ветвей своих деревьев. Как правило, деревья принимают решения, спрашивая, соответствует ли переменная x ›= определенному значению, и, следовательно, выброс будет падать с каждой стороны ветви, но он будет рассматриваться одинаково с остальными значениями, независимо от его величины.

Недавняя исследовательская статья предполагает, что нейронные сети также могут быть чувствительны к выбросам, если количество выбросов велико и отклонение также велико. Я бы сказал, что если количество выбросов велико (> 15%, как предлагается в статье), то они больше не выбросы, а скорее справедливое представление этой переменной.

Как можно выявить выбросы?

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

В этом блоге мы сосредоточимся на выявлении выбросов, вызванных механической ошибкой или ошибкой измерения. Мы увидим, как идентифицировать эти выбросы.

Анализ экстремальных значений

Наиболее простой формой обнаружения выбросов является Анализ экстремальных значений одномерных данных. Ключом к этому методу является определение статистических хвостов базового распределения переменной, а затем поиск значений, которые находятся в самом конце хвостов.

Если переменная имеет нормальное распределение (по Гауссу), то значения, лежащие за пределами среднего плюс-минус 3 стандартного отклонения переменной, считаются выбросами.

  • выбросы = среднее +/- 3 * станд.

Если распределение переменной асимметрично, общий подход заключается в расчете квантилей, а затем межквантильного диапазона (IQR) следующим образом:

  • IQR = 75-й квантиль — 25-й квантиль

Выброс будет находиться за пределами следующих верхних и нижних границ:

  • Верхняя граница = 75-й квантиль + (IQR * 1,5)
  • Нижняя граница = 25-й квантиль — (IQR * 1,5)

или для крайних случаев:

  • Верхняя граница = 75-й квантиль + (IQR * 3)
  • Нижняя граница = 25-й квантиль — (IQR * 3)

В этом блоге

Мы будем:

  • Определите выбросы, используя полный анализ случаев в нормально распределенных переменных.
  • Выявите выбросы, используя полный анализ случая в искаженных переменных.

Используемые наборы данных:

В этой демонстрации мы будем использовать наборы данных House Prices и Titanic. Мы также будем использовать набор данных о ценах на жилье в Бостоне от Scikit-learn.

Давайте начнем!

Мы начнем с импорта необходимых библиотек.

# to read the dataset into a dataframe and perform operations on it
import pandas as pd
# to perform basic array operations
import numpy as np
# for plotting and visualization
import matplotlib.pyplot as plt
import seaborn as sns
# for Q-Q plots
import scipy.stats as stats
# boston house dataset for the demo
from sklearn.datasets import load_boston

Сначала мы загрузим набор данных о ценах на жилье в Бостоне из sklearn. load_boston() загружает и возвращает набор данных о ценах на жилье в Бостоне. Мы увидим подробное описание набора данных. DESCR дает полное описание набора данных.

from sklearn.datasets import load_boston
print(load_boston().DESCR)

.. _boston_dataset:
Boston house prices dataset
---------------------------
**Data Set Characteristics:**  
    :Number of Instances: 506 
    :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.
    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000's
    :Missing Attribute Values: None
    :Creator: Harrison, D. and Rubinfeld, D.L.
This is a copy of UCI ML housing dataset.
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/

This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.
The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980.   N.B. Various transformations are used in the table on
pages 244-261 of the latter.
The Boston house-price data has been used in many machine learning papers that address regression
problems.   
     
.. topic:: References
   - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
   - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

Теперь мы создадим pandas dataframe, который будет включать независимые переменные. В этой демонстрации мы будем использовать только 3 переменные RM, LSTAT и CRIM.

boston_dataset = load_boston()
boston = pd.DataFrame(boston_dataset.data,
                      columns=boston_dataset.feature_names)[[
                          'RM', 'LSTAT', 'CRIM'
                      ]]

boston.head()

Теперь мы загрузим две переменные Age и Fare из набора данных titanic. У двух переменных отсутствуют значения, которые мы удалим для этой демонстрации с помощью dropna().

titanic = pd.read_csv('https://raw.githubusercontent.com/laxmimerit/All-CSV-ML-Data-Files-Download/master/titanic.csv',
                      usecols=['Age', 'Fare'])
titanic.dropna(subset=['Age', 'Fare'], inplace=True)
titanic.head()

Определить распределение переменных

В нормально распределенных переменных выбросы — это те значения, которые лежат за пределами среднего плюс-минус 3 стандартного отклонения. Однако, если переменные искажены, мы находим выбросы, используя межквантильный диапазон. Чтобы решить, какой метод использовать для обнаружения выбросов, нам сначала нужно знать распределение переменной.

Мы можем использовать гистограммы и графики Q-Q, чтобы определить, нормально ли распределена переменная. Мы также можем использовать диаграммы для прямой визуализации выбросов. Блочные диаграммы — это стандартный способ отображения распределения переменной с использованием первого квартиля, медианы, третьего квартиля и усов.

Глядя на диаграмму, вы можете легко определить:

  • Медиана, указанная линией в рамке.
  • Межквантильный диапазон (IQR), сама коробка.
  • Квантиль: 25-й (Q1) — нижний, а 75-й (Q3) — верхний конец поля.
  • Усы, доходящие до:- top whisker: Q3 + 1.5 x IQR - bottom whisker: Q1 -1.5 x IQR

Любое значение, находящееся за пределами усов, считается выбросом. Давайте посмотрим на примеры ниже.

Мы создадим функцию diagnostic_plots(), которая строит гистограмму, график Q-Q и блочную диаграмму для указанной переменной данного фрейма данных.

def diagnostic_plots(df, variable):
    # function takes a dataframe (df) and
    # the variable of interest as arguments
    # define figure size
    plt.figure(figsize=(16, 4))
    # histogram
    plt.subplot(1, 3, 1)
    sns.distplot(df[variable], bins=30)
    plt.title('Histogram')
    # Q-Q plot
    plt.subplot(1, 3, 2)
    stats.probplot(df[variable], dist="norm", plot=plt)
    plt.ylabel('RM quantiles')
    # boxplot
    plt.subplot(1, 3, 3)
    sns.boxplot(y=df[variable])
    plt.title('Boxplot')
    plt.show()

Нормально распределенные переменные

Начнем с переменной RM из набора данных домов в Бостоне. RM — среднее количество комнат в жилом доме.

diagnostic_plots(boston, 'RM')

Из гистограммы и графика Q-Q мы видим, что переменная RM довольно хорошо аппроксимирует распределение Гаусса. На блочной диаграмме мы видим, что переменная может иметь выбросы, поскольку за пределами усов на обоих хвостах распределения находится много точек.

Теперь давайте проверим переменную Age из титанического набора данных. Это относится к возрасту пассажиров на борту.

diagnostic_plots(titanic, 'Age')

Из гистограммы и графика Q-Q мы видим, что переменная довольно хорошо аппроксимирует распределение Гаусса. Наблюдается отклонение распределения в сторону меньших значений возраста. На блочной диаграмме мы видим, что переменная может иметь выбросы, поскольку за пределами усов в правом конце распределения находится много точек (верхние усы на блочной диаграмме).

Искаженные переменные

Теперь мы построим переменную LSTAT из набора данных домов в Бостоне. Это дает % более низкий статус населения.

diagnostic_plots(boston, 'LSTAT')

LSTAT нормально не распределяется, скошено хвостом вправо. Согласно диаграмме, в правом конце распределения переменной есть несколько выбросов.

Давайте посмотрим на распределение переменной CRIM из набора данных домов в Бостоне. CRIM - уровень преступности на душу населения в городе.

diagnostic_plots(boston, 'CRIM')

CRIM сильно перекошен, с хвостом вправо. Судя по диаграмме, в правом конце распределения также довольно много выбросов.

Теперь о переменной Fare из титанического набора данных. Fare — цена билета, уплаченная пассажирами.

diagnostic_plots(titanic, 'Fare')

Fare также сильно перекошено и показывает некоторые необычные значения в правом конце своего распределения.

Теперь мы определим выбросы, используя среднее значение и стандартное отклонение для переменных RM и Age из наборов данных Boston и Titanic соответственно. Затем мы будем использовать межквантильный диапазон, чтобы определить выбросы для переменных LSTAT, CRIM и Fare из наборов данных Boston и Titanic.

Обнаружение выбросов для нормально распределенных переменных

Мы создадим функцию find_normal_boundaries(), которая будет находить верхнюю и нижнюю границы для нормально распределенных переменных.

def find_normal_boundaries(df, variable):
    # calculate the boundaries outside which lie the outliers for a Gaussian distribution
    upper_boundary = df[variable].mean() + 3 * df[variable].std()
    lower_boundary = df[variable].mean() - 3 * df[variable].std()
    return upper_boundary, lower_boundary

Теперь мы вычислим границы для RM, используя созданную выше функцию.

upper_boundary, lower_boundary = find_normal_boundaries(boston, 'RM')
print(upper_boundary, lower_boundary)
(8.392485817597757, 4.176782957105816)

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

print('Total number of houses: {}'.format(len(boston)))
print('Houses with more than 8.4 rooms (right end outliers): {}'.format(
    len(boston[boston['RM'] > upper_boundary])))
print('Houses with less than 4.2 rooms (left end outliers: {}'.format(
    len(boston[boston['RM'] < lower_boundary])))
print()
print('% right end outliers: {}'.format(
    len(boston[boston['RM'] > upper_boundary]) / len(boston)))
print('% left end outliers: {}'.format(
    len(boston[boston['RM'] < lower_boundary]) / len(boston)))
Total number of houses: 506
Houses with more than 8.4 rooms (right end outliers): 4
Houses with less than 4.2 rooms (left end outliers: 4
% right end outliers: 0.007905138339920948
% left end outliers: 0.007905138339920948

С помощью анализа экстремальных значений мы выявили выбросы на обоих концах распределения RM. Процент выбросов невелик (1,4% с учетом двух хвостов вместе), что логично, ведь мы находим именно выбросы. Это редкие значения, редкие случаи.

Давайте перейдем к Age в титаническом наборе данных.

# calculate boundaries for Age in the titanic
upper_boundary, lower_boundary = find_normal_boundaries(titanic, 'Age')
upper_boundary, lower_boundary
(73.27860964406095, -13.88037434994331)

Верхняя граница — 73 года, а это значит, что пассажиров старше 73 лет в «Титанике» было очень мало, если они вообще были. Нижняя граница отрицательная. Поскольку отрицательного возраста не существует, имеет смысл искать выбросы только с использованием верхней границы.

# lets look at the number and percentage of outliers
print('Total passengers: {}'.format(len(titanic)))
print('Passengers older than 73: {}'.format(
    len(titanic[titanic['Age'] > upper_boundary])))
print()
print('% of passengers older than 73: {}'.format(
    len(titanic[titanic['Age'] > upper_boundary]) / len(titanic)))
Total passengers: 714
Passengers older than 73: 2
% of passengers older than 73: 0.0028011204481792717

На борту «Титаника» было 2 пассажира старше 73 лет, что можно считать исключением, поскольку большинство населения было намного моложе.

Обнаружение выбросов для искаженных переменных

Мы создадим функцию find_skewed_boundaries(), чтобы найти верхнюю и нижнюю границы для асимметрично распределенных переменных. distance передается в качестве аргумента, дает нам возможность оценить IQR в 1,5 или 3 раза для расчета границ.

def find_skewed_boundaries(df, variable, distance):
    IQR = df[variable].quantile(0.75) - df[variable].quantile(0.25)
    lower_boundary = df[variable].quantile(0.25) - (IQR * distance)
    upper_boundary = df[variable].quantile(0.75) + (IQR * distance)
    return upper_boundary, lower_boundary

Мы будем искать выбросы в LSTAT из набора данных о домах в Бостоне, используя правило межквартильной близости IQR * 1,5, стандартную метрику.

upper_boundary, lower_boundary = find_skewed_boundaries(boston, 'LSTAT', 1.5)
upper_boundary, lower_boundary
(31.962500000000006, -8.057500000000005)

Теперь давайте посмотрим на количество и процент выбросов для LSTAT.

print('Total houses: {}'.format(len(boston)))
print('Houses with LSTAT bigger than 32: {}'.format(
    len(boston[boston['LSTAT'] > upper_boundary])))
print()
print('% of houses with LSTAT bigger than 32: {}'.format(
    len(boston[boston['LSTAT'] > upper_boundary])/len(boston)))
Total houses: 506
Houses with LSTAT bigger than 32: 7
% of houses with LSTAT bigger than 32: 0.01383399209486166

Верхняя граница показывает значение ~32. Нижняя граница отрицательна, однако переменная LSTAT не принимает отрицательных значений. Таким образом, для расчета выбросов для LSTAT мы будем использовать только верхнюю границу. Это совпадает с тем, что мы наблюдали на диаграмме ранее. Выбросы находятся только в правом хвосте LSTAT's распределения.

Мы наблюдаем 7 домов, 1,3% набора данных, с чрезвычайно высокими значениями LSTAT.

Теперь будем искать выбросы в CRIM, используя правило межквартильной близости IQR * 3, теперь ищем экстремально высокие значения.

upper_boundary, lower_boundary = find_skewed_boundaries(boston, 'CRIM', 3)
upper_boundary, lower_boundary
(14.462195000000001, -10.7030675)

давайте посмотрим на количество и процент выбросов для CRIM.

print('Total houses: {}'.format(len(boston)))
print('Houses with CRIM bigger than 14: {}'.format(
    len(boston[boston['CRIM'] > upper_boundary])))
print()
print('% of houses with CRIM bigger than 14s: {}'.format(
    len(boston[boston['CRIM'] > upper_boundary]) / len(boston)))
Total houses: 506
Houses with CRIM bigger than 14: 30
% of houses with CRIM bigger than 14s: 0.05928853754940711

Используя 3-кратный межквантильный диапазон для поиска выбросов, мы обнаружили, что ~ 6% домов имеют районы с необычно высоким уровнем преступности. Для CRIM также нижняя граница отрицательна, поэтому для вычисления выбросов имеет смысл использовать только верхнюю границу, так как переменная принимает только положительные значения. Это совпадает с тем, что мы наблюдали на CRIM's диаграмме ранее в этом блоге.

Наконец, мы идентифицируем выбросы в Fare из титанического набора данных. Мы снова будем искать экстремальные значения, используя IQR * 3.

upper_boundary, lower_boundary = find_skewed_boundaries(titanic, 'Fare', 3)
upper_boundary, lower_boundary
(109.35, -67.925)

Давайте посмотрим на количество и процент пассажиров, заплативших чрезвычайно высокие Fares.

print('Total passengers: {}'.format(len(titanic)))
print('Passengers who paid more than 117: {}'.format(
    len(titanic[titanic['Fare'] > upper_boundary])))
print()
print('% of passengers who paid more than 117: {}'.format(
    len(titanic[titanic['Fare'] > upper_boundary])/len(titanic)))
Total passengers: 714
Passengers who paid more than 117: 44
% of passengers who paid more than 117: 0.06162464985994398

Для Fare, как и для всех других переменных в этой тетради, у которых хвост справа, нижняя граница отрицательна. Поэтому мы использовали верхнюю границу для определения выбросов. Мы наблюдаем, что 6% значений набора данных попадают выше границы.

Многие алгоритмы машинного обучения чувствительны к диапазону и распределению значений атрибутов во входных данных. Выбросы во входных данных могут исказить и ввести в заблуждение процесс обучения алгоритмов машинного обучения, что приведет к увеличению времени обучения, менее точным моделям и, в конечном итоге, к худшим результатам. Даже до того, как будут подготовлены прогностические модели на обучающих данных, выбросы могут привести к вводящим в заблуждение представлениям и, в свою очередь, к вводящим в заблуждение интерпретациям собранных данных. Выбросы могут искажать суммарное распределение значений атрибутов в описательной статистике, такой как среднее значение и стандартное отклонение, а также в графиках, таких как гистограммы и диаграммы рассеяния, сжимая основную часть данных. С другой стороны, выбросы могут представлять собой примеры экземпляров данных, имеющих отношение к проблеме, таких как аномалии в случае обнаружения мошенничества и компьютерной безопасности.

Дальнейшее чтение

Если вы ищете новичков для продвинутых курсов по НЛП. Вы можете записаться на один из моих лучших курсов НЛП на Udemy.