13 августа 2015

Подготовка данных для создания предсказательных моделей: преобразование качественных переменных в количественные



Рисунок заимствован из Wiki
Эта статья завершает обзор основных методов подготовки данных для создания предсказательных моделей и посвящена преобразованию качественных предикторов (= категориальных, или факторных переменных) в количественные. Многие статистические методы требуют, чтобы все входные данные были представлены количественными переменными. Однако обычны ситуации, когда в состав данных входят не только количественные, но и качественные предикторы (пол, социальный статус, должность, почтовый индекс, город, наличие заболевания и т.п.). Распространенным подходом для преобразования таких переменных в количественные является создание индикаторных, или фиктивных переменных (англ. "dummy variables"). Мы уже сталкивались с такими переменными при рассмотрении линейных моделей, подгоняемых при помощи базовой функции lm(). Эта и подобные ей функции (например, glm()) выполняют преобразование качественных предикторов в индикаторные "на лету", не требуя от пользователя никаких дополнительных действий. Однако при работе со многими методами машинного обучения пользователь должен сначала самостоятельно преобразовать качественные переменные в индикаторные и добавить их в таблицу с данными. Рассмотрим, как это можно сделать при помощи пакета caret.



Для создания таблиц данных с индикаторными переменными в пакете caret имеется функция dummyVars(), работа которой контролируется следующими аргументами:
  • formula - формула R, задающая необходимую структуру итоговой таблицы;
  • data - таблица данных;
  • sep - необязательный параметр, который определяет символ, используемый в именах производных индикаторных переменных для разделения названия исходной качественной переменной и ее уровней (см. пример ниже);
  • levelsOnly - логическое значение (TRUE/FALSE), позволяющее включить/отключить добавление имени исходной качественной переменной в имена производных индикаторных переменных;
  • fullRank - логическое значение (TRUE/FALSE), определяющее ранг итоговой матрицы с нулями и единицами, которые кодируют уровни того или иного качественного предиктора; при fullRank = TRUE (подход, автоматически реализуемый функциями вроде lm(), glm() и др.) матрица будет иметь полный ранг (т.е. ее корреляция между ее столбцами будет равна нулю; по умолчанию fullRank = FALSE).
Рассмотрим работу функции dummyVars() на примере данных hellung из пакета ISwR. Таблица hellung содержит данные о численности conc (экз./мл) и диаметре diameter особей ресничных инфузорий Tetrahymena, которые выращивались в питательной среде без (glucose = 2) и с добавлением (glucose = 1) глюкозы. Цель эксперимента заключалась в установлении влияния глюкозы на размер инфузорий с учетом того, что этот размер может быть также связан с численностью клеток в культуре (примеры применения ковариационного анализа к этим данным можно найти в книгах Dalgaard 2008 и  Мастицкий, Шитиков 2015).

data(hellung, package = "ISwR")
hellung$glucose = as.factor(hellung$glucose)
 
head(hellung)
  glucose   conc diameter
1       1 631000     21.2
2       1 592000     21.5
3       1 563000     21.3
4       1 475000     21.0
5       1 461000     21.5
6       1 416000     21.3

Для удобства переименуем уровни переменной glucose:

levels(hellung$glucose) = c("yes", "no")
 
head(hellung)
  glucose   conc diameter
1     yes 631000     21.2
2     yes 592000     21.5
3     yes 563000     21.3
4     yes 475000     21.0
5     yes 461000     21.5
6     yes 416000     21.3

Наша задача заключается в преобразовании переменной-фактора glucose в набор индикаторных переменных в соответствии с ее уровнями. Для большинства статистических методов число индикаторных переменных должно составлять \(K - 1\), где \(K\) - это число уровней в исходной качественной переменной (т.е. матрица, кодирующая индикаторные переменные, должна иметь полный ранг). В нашем случае переменная glucose имеет только два уровня (yes/no) и поэтому ее достаточно будет представить только в виде одной индикаторной переменной (т.е \(K - 1 = 2 - 1 = 1\)). Воспользуемся функцией dummyVars() для создания новой таблицы данных с соответствующей индикаторной переменной:

newvar = dummyVars(~ glucose + conc + diameter,
                   fullRank = TRUE, data = hellung)
 
newvar
 
Dummy Variable Object
 
Formula: ~glucose + conc + diameter
3 variables, 1 factors
Variables and levels will be separated by '.'
A full rank encoding is used

Важно отметить, что созданный нами объект newvar не является новой таблицей данных. Подобно тому, как это работает в случае с функцией preProcess(), результатом работы dummyVars() является создание объекта, в котором хранится информация о способе преобразования данных, содержащих соответствующие переменные. При выводе содержимого объекта newvar на экран, мы видим формулу, определяющую структуру новой таблицы данных, общее число переменных и число качественных переменных среди них (3 variables, 1 factors), узнаём, каким символом будут разделяться имена исходных качественных переменных и названия их уровней в именах индикаторных переменных (здесь - точкой: Variables and levels will be separated by '.'), а также видим, что для кодирования индикаторных переменных будет применяться метод "полный ранг" (A full rank encoding is used). Для выполнения преобразования данных как такового необходимо подать объект newvar на функцию predict():

transformedData = predict(newvar, newdata = hellung)
 
transformedData
 
  glucose.no   conc diameter
1          0 631000     21.2
2          0 592000     21.5
3          0 563000     21.3
4          0 475000     21.0
5          0 461000     21.5
6          0 416000     21.3
...
 
46          1 27000     23.6
47          1 24000     23.5
48          1 22000     23.3
49          1 14000     24.4
50          1 13000     24.3
51          1 11000     24.2

Новая таблица transformedData, как и раньше, содержит переменные conc и diameter, а также индикаторную переменную glucose.no. Переменная glucose.no представлена двумя значениями - 0 для обозначения наличия глюкозы и 1 для обозначения ее отсутствия в питательной среде. Вместо имени glucose.no мы можем получить только название соответствующего уровня переменной glucose - такой подход (включается аргументом levelsOnly = TRUE) полезен при работе с качественными переменными, чьи уровни имеют вполне описательные названия (например, названия городов), что делает повтор имени исходной переменной ненужным:

newvar = dummyVars(~ glucose + conc + diameter,
                   fullRank = TRUE, levelsOnly = TRUE,
                   data = hellung)
 
transformedData = predict(newvar, newdata = hellung)
 
head(transformedData)
no   conc diameter
1  0 631000     21.2
2  0 592000     21.5
3  0 563000     21.3
4  0 475000     21.0
5  0 461000     21.5
6  0 416000     21.3

Хотя использование метода "полный ранг" при кодировании индикаторных переменных является стандартным в R и необходимо для многих статистических методов, ряд методов (например, деревья принятия решений, кластерный анализ) все же могут сработать лучше при включении всех \(K\) индикаторных переменных в новую таблицу данных. Для реализации этого подхода достаточно воспользоваться аргументом fullRank = FALSE:

newvar = dummyVars(~ glucose + conc + diameter,
                    fullRank = FALSE, levelsOnly = TRUE,
                    data = hellung)
 
transformedData = predict(newvar, newdata = hellung)
 
head(transformedData)
  yes no   conc diameter
1   1  0 631000     21.2
2   1  0 592000     21.5
3   1  0 563000     21.3
4   1  0 475000     21.0
5   1  0 461000     21.5
6   1  0 416000     21.3

Заметьте, что теперь таблица transformedData включает две индикаторные переменные - yes и no.

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

13 комментариев :

Анонимный комментирует...

Отличная статья. Спасибо!
Скажите, в чём смысл в новой таблице transformedData, переименовывать переменную glucose в индикаторную переменную glucose.no?

Sergey Mastitsky комментирует...

Не совсем понял вопрос. Зачем создавать индикаторные переменные? Об этом, собственно, вся статья. Зачем присваивать новое имя? В данном конкретном примере, смысла в переименовании, дейтсвительно нет (оно происходит автоматически), посольку у переменной glucose есть только два уровня. Но представьте ситуацию, когда у переменной есть много уровней (их может быть несколько тысяч) - в таких случаях автоматическое переименование окажется более чем кстати, потому как вручную ничего делать не придется.

Ирина Голощапова комментирует...

Отличное описание, Сергей! Спасибо!
Поскольку недавно пришлось также погрузиться в эту тему, хотелось бы дополнить:
1) деревья принятия решений и ряд других популярных алгоритмов машинного обучения способны работать и с категориальными переменными напрямую. Однако преобразования в индикаторные переменные часто все-равно имеют смысл для адекватного сравнения качества предсказания таких моделей как lm/glm (работающих только с фиктивными переменными) и алгоритмов машинного обучения svm, trees, neural networks, etc. (которым все-равно, количественные или категориальные переменные им были поданы на входе)
2) Функция dummyVars работает также и с преобразованием порядковых переменных в количественные, разбирая их по полиномам разной степени. Что тоже очень удобно! :)
Хотя часто оказывается, что модели в среднем имеют более высокую предсказательную силу при использовании порядковых переменных как обычных индикаторных, чем при аккуратном преобразовании по полиному.

Olga комментирует...

Сергей, спасибо за интересный (как всегда) материал!

Мой вопрос о вашей книге. Можно ли ее купить в электронном виде? Я живу в Киеве, и доставка из России стоит столько же, сколько книга )) Спасибо!

Sergey Mastitsky комментирует...

Ольга, насколько я знаю, книгу можно купить и в Украине - см., например, http://www.bookzone.com.ua/Netshop/catalogue/catalogue_48870.html
Однако перед тем, как сделать заказ, я бы посоветовал уточнить у продавца, какое это издание. В первой партии типография, к сожалению, сработала очень некачественно и в итоге шрифт комментариев к коду и шрифт результатов вычислений читаются очень плохо. Недавно это было исправлено, и следующая партия книг (т.е. напечатанная после августа) должна быть получше.
Кроме того, Вы всегда можете скачать свободно распростряняемую "самодельную" версию 2014-го года: http://r-analytics.blogspot.de/2014/12/r.html
Бесплатная версия на >90% аналогична платной версии.

Olga комментирует...

Спасибо! Онлайн-версия с возможностью отблагодарить на Пейпеле — самое оно )

Анонимный комментирует...

Спасибо! Полезная статья. Удобнее кодировать, чем с помощью contrasts().
Вопрос вот какой. Иногда надо сделать обратное перекодирование из фиктивных переменных. Есть ли возможность сделать это штатными средствами? Сейчас приходится писать собственную функцию, но благо что фиктивных переменных у меня не так много бывает. Однако, как справедливо, заметили в комментариях может быть и несколько тысяч уровней.

Sergey Mastitsky комментирует...

Не совсем понимаю, зачем выполнять обратное преобразование, если можно сохранить исходную таблицу с данными и взять, все что нужно, из нее. В любом случае, свою функцию можно и не писать - Хэдли Уикхем уже сделал всю работу за нас - вам поможет функция gather() из пакета tidyr, см. примеры здесь: https://cran.r-project.org/web/packages/tidyr/vignettes/tidy-data.html

Анонимный комментирует...

Спасибо. С tidyr попробую. Странно, пакетом пользуюсь и в голову даже не пришло. Бывает.
А с точки зрения зачем. Навскидку два варианта. Первый, выходы нейронной сети по фиктивным переменным. После обучения - сравнение по тестовой выборке выходов модели с тестовыми данными. Потом организация выходов уже готового сигнала для модели. Вариант два - в случае conjoint-анализа (если руками делать) обратное преобразование требуется для отчетности, графиков и пр.
Еще раз спасибо за блог и интересные статьи!

Ирина Голощапова комментирует...

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

Sergey Mastitsky комментирует...

Ирина, жаль, что Вам прислали такую версию книги. Я не знаю, делают ли они обмен - свяжитесь, пожалуйста, с издательством: http://dmkpress.com/contacts/feedback/

Эдуард Бабушкин комментирует...

Сергей, не нашел в dummyVars метод смены контраста. Он вообще не предусмотрен? Поправьте, если ошибаюсь
Пока просто меняю уровни с помощью формулы factor() - и если поменять уровни фактора с помощью этой формулы, dummyVars меняет контрасты

Эдуард Бабушкин комментирует...

там есть еще одна беда, которую я не победил: он transformedData перекодирует как бы в numeric
как бы - потому что там не формируется датасет, но мы это понимаем по презентации решений.
например в дереве решений нам программа выдает ПолМ > 0,5 - так решения выдаются для numeric, хотя для integer форма записи была бы ПолМ = 1

Отправить комментарий