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

Трансформация отдельных предикторов

Начнем с преобразования значений отдельных предикторов. В качестве примера используем рассмотренные ранее данные GermanCredit по кредитоспособности клиентов одного из немецких банков:

library(caret)
data(GermanCredit)
 
summary(GermanCredit) 
# для экономии места вывод результа предыдущей команды сокращен
 
    Duration        Amount      InstallmentRatePercentage ResidenceDuration
 Min.   : 4.0   Min.   :  250   Min.   :1.000             Min.   :1.000    
 1st Qu.:12.0   1st Qu.: 1366   1st Qu.:2.000             1st Qu.:2.000    
 Median :18.0   Median : 2320   Median :3.000             Median :3.000    
 Mean   :20.9   Mean   : 3271   Mean   :2.973             Mean   :2.845    
 3rd Qu.:24.0   3rd Qu.: 3972   3rd Qu.:4.000             3rd Qu.:4.000    
 Max.   :72.0   Max.   :18424   Max.   :4.000             Max.   :4.000    
      Age        NumberExistingCredits NumberPeopleMaintenance   Telephone    
 Min.   :19.00   Min.   :1.000         Min.   :1.000           Min.   :0.000  
 1st Qu.:27.00   1st Qu.:1.000         1st Qu.:1.000           1st Qu.:0.000  
 Median :33.00   Median :1.000         Median :1.000           Median :1.000  
 Mean   :35.55   Mean   :1.407         Mean   :1.155           Mean   :0.596  
 3rd Qu.:42.00   3rd Qu.:2.000         3rd Qu.:1.000           3rd Qu.:1.000  
 Max.   :75.00   Max.   :4.000         Max.   :2.000           Max.   :1.000  
 
...

Стандартизация

В таблицу GermanCredit входят переменные, измеренные на разных шкалах. Так, многие из них являются индикаторными переменными (т.е. отражают наличие или отсутствие того или иного признака у клиента в виде значений 1 и 0). Размер кредита Amount измеряется в немецких марках, возраст клиента Age - в годах, и т.д. Как следствие, размах значений некоторых переменных также существенно разнится. Например, индикаторные переменные по определению варьируют от 0 до 1, тогда как размер кредита изменяется от 250 до 18424 марок. Для приведения всех переменных к одинаковым единицам измерения служит стандартизация. Данная операция заключается в вычитании из исходного значения \(x_i\) некоторой переменной \(X\) соответствующего среднего значения \(\bar{x}\) ("центрирование", или "centering") и последующем делении полученной разницы на стандартное отклонение этой переменной \(\sigma_{x}\) ("нормализация", или "scaling"):

\[ {x}'_{i} = \frac{x_i - \bar{x}}{\sigma_{x}} \]

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

Преобразование Бокса-Кокса

Помимо того, что многие переменные в таблице GermanCredit выражаются в разных единицах, некоторые из них имеют также явно выраженные асимметричные распределения (например, Amount и Age), что может представлять проблему для некоторых статистических методов. Часто решить эту проблему позволяют такие простые преобразования исходных значений, как извлечение квадратного корня, логарифмирование или расчет обратных значений. В качестве популярной альтернативы можно также использовать т.н. степенное преобразование Бокса-Кокса ("Box-Cox transformation"), которое в общем виде выражается следующим образом:

\( {x}'_{i} = \frac{x_{i}^{\lambda} - 1}{\lambda} \) для \( \lambda \neq 0\) и
\( {x}'_{i} = \log(x) \) для \( \lambda = 0\).

Можно заметить, что преобразование Бокса-Кокса в действительности представляет собой целое семейство преобразований. Так, при \(\lambda = 2\) исходные значения трансформируемой переменной будут возведены в квадрат, при \(\lambda = 0.5\) будет извлечен квадратный корень, а при \(\lambda = -1\) будут рассчитаны обратные значения. Параметр \(\lambda\) может принимать произвольные положительные или отрицательные значения. Оптимальное значение этого параметра для конкретной переменной обычно находят при помощи метода максимального правдоподобия (подробный пример того, как это сделать в R, можно найти в разд. 4.5 книги Мастицкий, Шитиков, 2015). Одной из функций, позволяющих оценить оптимальное значение \(\lambda\) по данным, является функция BoxCoxTrans() из пакета caret. Применим ее, например, к переменной Amount из таблицы GermanCredit:

( AmountTrans = BoxCoxTrans(GermanCredit$Amount) )
 
Box-Cox Transformation
 
1000 data points used to estimate Lambda
 
Input data summary:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    250    1366    2320    3271    3972   18420 
 
Largest/Smallest: 73.7 
Sample Skewness: 1.94 
 
Estimated Lambda: -0.1 
With fudge factor, Lambda = 0 will be used for transformations

Из приведенного результата видно, что найденное программой оптимальное значение \(\lambda\)  составило -0.1 (Estimate Lambda: -0.1). Однако с учетом определенной поправочной константы ("fudge factor"), учитывающей случайный характер выборки, итоговое значение \(\lambda\) составило 0 (см. строку With fudge factor, Lambda = 0 will be used for transformations), что соответствует логарифмированию (см. выше). Для применения преобразования на основе \(\lambda = 0\) к значениям Amount необходимо воспользоваться функцией predict(), подав на нее объект AmountTrans и переменную с исходными значениями:

head(predict(AmountTrans, GermanCredit$Amount))
[1] 7.063904 8.691315 7.647786 8.972337 8.490849 9.11107

Результат этого преобразования представлен графически на рисунке ниже:


Одновременная трансформация нескольких предикторов

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

Метод пространственных знаков

Если ожидается, что тот или иной метод построения предсказательной модели чувствителен к наличию многомерных выбросов, исходные данные можно преобразовать при помощи метода пространственных знаков ("spatial sign"; Serneels et al. 2006). С математической точки зрения, этот метод проецирует значения предикторов на поверхность многомерной сферы, в результате чего отдельные наблюдения становятся равноудаленными от центра этой сферы:

\[ {x}'_{ij} = \frac{x_{ij}}{\sum_{j=1}^{P} {x}^2_{ij} } \]

В приведенной формуле \(P\) - это число предикторов. Поскольку знаменатель выражает квадрат расстояния до центра распределения предикторов, перед применением этой формулы важно выполнить стандартизацию всех предикторов (этим будет достигнут примерно одинаковый их вклад в величину квадрата расстояния). Кроме того, важно понимать, что это преобразование по методу пространственных знаков применяется одновременно к группе предикторов. Поэтому удаление каких-либо предикторов из анализа после такого преобразования приведет к утрате его смысла.

Метод главных компонент

Метод главных компонент ("principle components analysis", PCA) является одним из наиболее популярных методов снижения размерности исходного набора данных. Необходимость снижения размерности задачи может быть обусловлена, например, чрезмерно большим количеством предикторов по сравнению с количеством наблюдений или большим количеством высоко коррелирующих предикторов, несущих сходную информацию. PCA сводится к нахождению ограниченного числа ортогональных (=некоррелирующих) линейных комбинаций предикторов, которые "объясняют" достаточно высокую долю дисперсии в исходных данных (например, 80% или 95%). Такие линейные комбинации называются главными компонентами и могут быть использованы вместо исходных предикторов. Например, j-тую главную компоненту можно записать следующим образом:

\[ PC_{j} = (a_{j1}X_1) + (a_{j2}X_2) + \dots + (a_{jP}X_P),\]

где P - число предикторов в исходном наборе данных.

Реализация разных методов трансформации при помощи функции preProcess()

В состав пакета caret входит функция preProcess(), которая позволяет одновременно применить к данным любые комбинации рассмотренных выше, а также некоторых других методов трансформации. Ее основными аргументами являются следующие:
  • x - таблица или матрица с данными (все переменные должны быть количественными);
  • method - метод трансформации; возможные значения: "BoxCox", "YeoJohnson", "expoTrans", "center", "scale", "range", "knnImpute", "bagImpute", "medianImpute", "pca", "ica" и "spatialSign";
  • thresh - кумулятивная доля дисперсии исходных, которая должна содержаться в главных компонентах (по умолчанию 0.95, т.е. 95%);
  • pcaComp - максимальное число главных компонент (целое число), которое следует оставить после трансформации данных для дальнейшей работы (по умолчанию этот параметр имеет значение NULL, т.е. он выключен, и оптимальное число главных компонент выбирается на основе thresh).
С дополнительными аргументами можно ознакомиться в справочном файле функции, доступном по команде ?preProcess.

Применим функцию preProcess() к таблице GermanCredit, предварительно удалив предикторы с околонулевой дисперсией и разбив данные на обучающую (80%) и контрольную (=проверочную, 20%) выборки.

# Сохраним переменную-отклик в виде отдельного объекта
# и удалим ее из таблицы с предикторами:
Class = GermanCredit$Class
GermanCredit$Class = NULL
 
# Удалим предикторы с околонулевой дисперсией:
nz = nearZeroVar(GermanCredit)
GermanCredit = GermanCredit[, -nz]
 
# Разобъем исходные данные случайным образом на
# обучающую и контрольную выборки:
set.seed(202)
index = sample(1:nrow(GermanCredit), nrow(GermanCredit)*0.8, replace = F)
 
trSet = GermanCredit[index, ] # обучающая выборка
testSet = GermanCredit[-index, ] # проверочная выборка
 
# Применим preProcess() к обучающей выборке и выполним
преобразование Бокса-Кокса и 
# стандартизацию исходных переменных ("center" и "scale"):
 
( trans = preProcess(trSet, method = c("BoxCox", "center", "scale")) )
 
Call:
preProcess.default(x = trSet, method = c("BoxCox", "center", "scale"))
 
Created from 800 samples and 48 variables
Pre-processing: Box-Cox transformation, centered, scaled 
 
Lambda estimates for Box-Cox transformation:
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
-2.00000 -0.55000  0.00000 -0.01667  0.85000  1.50000       42 


Функция preProcess() приводит к созданию объекта одноименного класса. При выводе содержимого этого объекта на экран, мы узнаем, что он был создан на основе 800 наблюдений и 48 переменных (Created from 800 samples and 48 variables), к которым были применены преобразование Бокса-Кокса и стандартизация (Pre-processing: Box-Cox transformation, centered, scaled). Далее мы увидим описательные статистики для параметра \(\lambda\) и узнаем, что преобразование Бокса-Кокса не удалось применить к 42 предикторам (см. Lambda estimates for Box-Cox transformation). Последнее обстоятельство обусловлено тем, что эти 42 переменные являются индикаторными, в связи с чем применение к ним преобразования Бокса-Кокса попросту не имеет смысла.

Важно понимать, что применив функцию preProcess() к обучающим данным, мы просто оценили параметры, необходимые для выполнения соответствующих преобразований (в частности, значения \(\lambda\), средние значения и стандартные отклонения каждой переменной), но данные пока еще остались неизменными. Для применения преобразований с учетом этих оцененных параметров необходимо воспользоваться функцией predict():

trSetTrans = predict(trans, trSet)
summary(trSetTrans)

Такой подход очень удобен, поскольку однажды создав объект preProcess, мы можем применить его к любым данным, содержащим те же предикторы, что и обучающая выборка, включая данные из контрольной выборки и новые данные, к которым в последующем будет применяться предсказательная модель. Применим, например, преобразования, хранящиеся в объекте trans к нашей контрольной выборке:

testSetTrans = predict(trans, testSet)
summary(testSetTrans)


7 Комментарии

Анонимный написал(а)…
Спасибо за блог и книгу, много полезного там для себя открыл.
Интересует вопрос.C R я на "Вы",к сожалению.
Хотел бы спросить, может есть задачник какой нибудь по R? Потому что обычно, все книги по R могут служить скорее справочниками, нежели учебниками.
Sergey Mastitsky написал(а)…
Как таковые задачники мне не известны. Однако во многих книгах по R можно встретить задания для самостоятельного решения после каждой главы. Не зная Ваших инттересов, сложно порекомендовать что-то конкретное. Из книг общей тематики обязательна к изучению следующая: http://www-bcf.usc.edu/~gareth/ISL/
Кроме того, обычно логика книг по R такова, что они строятся на конкретных примерах, которые читатель может легко модифицировать для собственных нужд. Соответственно, Вам нужно найти хорошую книгую по интересующей Вас теме и пробовать, пробовать, пробовать...
Евгений написал(а)…
Почему "предсказательная" модель, а не "прогнозирующая"?:)
а так полезно и хорошо!!
Sergey Mastitsky написал(а)…
От слова "predictive". Как мне кажется, термин "предсказательный" является более общим, охватывая как модели, которые предсказывают будущее (прогнозируют), так и модели, предсказывающие некоторый отклик просто для нового набора об'ектов
Unknown написал(а)…
Подскажите, пожалуйста, про Box-Cox трансформацию. Никогда ее не понимал. Как ее интерпретируют и зачем используют? разве не практичнее использовать логарифмы или их приросты?
Sergey Mastitsky написал(а)…
Виталий, больше чем написано в статье, о преобразовании Бокса-Кокса сказать особенно нечего. Это - универсальное семейство преобразований. Логарифмическая трансформация является частным случаем преобразования Бокса-Кокса. Подробнее можно почитать в книге, на которую приведена ссылка в статье, или здесь, например: http://bit.ly/1QZYuXa
Edward написал(а)…
Сергей, у меня вопрос по обратной трансформации: если мы нашу зависимую переменную трансформируем ВоксКокс, шкалируем и т.п., то как прогнозное значение вернуть в изначальное измерение?
Ну понятно, что если у нас логарифмирование было переменной, то мы просто экспонируем, но если у нас было несколько разных итераций?
Новые Старые