Menu

Факторы в R: категориальные данные, уровни и типичные ловушки

Факторы — это способ хранения категориальных данных в R: целочисленные коды, надевшие текстовые метки. Как их создавать, упорядочивать, задавать базовый уровень и не попасть в ловушку преобразования фактора в число.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Что такое фактор на самом деле

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

Вывод показывает метки, а затем строку Levels: со списком меню. as.integer() обнажает механику: коды возвращаются как 3 1 3 2, потому что каждое значение на самом деле является индексом в таблице уровней, а уровни по умолчанию сортируются по алфавиту (large, medium, small), а не в порядке появления в данных. Так что small — код 3, а large — код 1, что не совпадает ни с чьей интуицией. Запомните этот алфавитный порядок по умолчанию; он лежит в основе двух ловушек ниже.

Зачем нужна эта двухслойная конструкция вместо обычных строк? Потому что она нужна статистике. Модель не может умножить «small» на коэффициент — зато может закодировать три известных уровня фиктивными переменными в столбцы из нулей и единиц. Функции вроде lm(), glm(), table() и механика, стоящая за дисперсионным анализом, опираются на факторы, чтобы понять, что переменная категориальная, каков полный набор категорий (включая отсутствующие в данных) и какая категория базовая. Обычные символьные векторы ничего этого не несут.

Создание факторов: levels, labels и table()

По умолчанию factor() берёт найденные различные значения и сортирует их по алфавиту в уровни. Вам часто захочется контролировать и набор, и порядок — передайте levels =:

Теперь уровни идут в естественном порядке размеров, и table() — однострочный подсчёт частот, которым вы будете постоянно пользоваться с факторами, — тоже сообщает количества в этом порядке. nlevels() считает категории.

Ещё две вещи, которые даёт levels =. Значения в данных, отсутствующие в вашем списке уровней, становятся NA (это хорошо: опечатки всплывают вместо того, чтобы стать отдельной категорией). А уровни с нулевым числом наблюдений всё равно существуют, поэтому сводка по ответам опроса покажет «категорически не согласен: 0», а не сделает вид, что такого варианта не было.

labels = переименовывает уровни в момент создания, что удобно, когда сырые данные используют коды:

А as.factor(x) — быстрый конвертер для существующего вектора, когда настройки по умолчанию устраивают.

Упорядоченные факторы для порядковых данных

Обычные факторы считают категории неупорядоченными: «red» не меньше «blue». Но у некоторых категориальных шкал есть настоящее ранжирование: низкий/средний/высокий, не согласен/нейтрально/согласен. Объявите это через ordered = TRUE:

Теперь вывод показывает Levels: low < medium < high, и операторы сравнения работают: можно спросить, превышает ли одна оценка другую, или отфильтровать всё на уровне «medium» и выше — на неупорядоченном факторе оба действия дали бы ошибки (точнее, предупреждения и NA). Упорядоченные факторы также меняют то, как модели кодируют переменную (полиномиальные контрасты вместо фиктивных переменных), — и обычно это как раз то, что нужно для порядковых предикторов.

Используйте ordered = TRUE только когда ранжирование реально. Кодирование обычных групп как упорядоченных меняет вывод модели так, что его легко неверно прочитать.

Базовый уровень и relevel()

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

relevel() продвигает уровень на первое место:

До: control оказался первым лишь по алфавитной удаче. После relevel(..., ref = "control") он первый намеренно. В линейной регрессии с этим предиктором коэффициент treatment теперь отвечает на вопрос «чем treatment отличается от control?» — тот вопрос, который вы на самом деле задавали. Всякий раз, когда категориальные коэффициенты модели выглядят непонятно, сначала проверьте базовый уровень.

(Для полного переупорядочивания, а не только первой позиции, снова передайте factor() полный вектор levels =.)

Классическая ловушка: преобразование фактора в число

Иногда числа приходят факторами — обычно из-за столбца CSV, содержавшего затесавшееся нечисловое значение. Обратное преобразование выглядит очевидным и запоминающимся образом идёт не так:

as.numeric(f) возвращает 2 1 3. Не 20, 10, 30 — коды уровней. Уровни сортируются по алфавиту в "10", "20", "30", поэтому "20" — это уровень 2 и преобразуется в... 2. Ни ошибки, ни предупреждения — просто правдоподобные маленькие целые числа тихо заменяют ваши данные. Из-за этого отзывали опубликованные исследования.

Правильный путь идёт через символьный тип: as.numeric(as.character(f)) сначала восстанавливает метки как текст, затем разбирает текст как числа — 20 10 30. Впечатайте эту идиому в память: фактор в число всегда через as.character().

droplevels() и история stringsAsFactors

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

После отсеивания large table() всё равно его показывает — с нулём наблюдений. Иногда это ровно то, что нужно (вы хотите видеть пустую категорию). Когда нет — группы с нулём наблюдений загромождают графики и могут ломать стратифицированный анализ — droplevels() отбрасывает уровни без наблюдений.

Историческая заметка, которая понадобится при чтении старого кода или ответов на Stack Overflow: до R 4.0 (2020) data.frame() и read.csv() автоматически превращали каждый символьный столбец в фактор — stringsAsFactors = TRUE был вариантом по умолчанию. Десятилетие учебников усеяно обходными приёмами для факторов, которых никто не просил. Начиная с R 4.0 по умолчанию FALSE: строки остаются строками, а факторы вы создаёте намеренно там, где переменная в вашем датафрейме действительно категориальная. Это и есть правильная привычка — явные факторы, осознанно, с выбранными вами уровнями.

Что вы уносите с собой

  • Фактор = целочисленные коды + метки уровней; именно он сообщает статистическим функциям, что переменная категориальная.
  • Управляйте набором и порядком категорий через levels =, переименовывайте через labels =, считайте через table().
  • ordered = TRUE включает сравнения для действительно порядковых шкал.
  • Первый уровень — база модели; задавайте его намеренно через relevel(f, ref = ...).
  • Никогда не делайте as.numeric(f) напрямую — всегда as.numeric(as.character(f)).
  • droplevels() очищает неиспользуемые уровни после выборки; начиная с R 4.0 строки остаются строками, пока вы сами не создадите факторы.

Дальше: датафреймы — там, где факторы, числа и текст живут вместе как столбцы одной таблицы.

Часто задаваемые вопросы

Что такое фактор в R?

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

Как преобразовать фактор в число в R?

Через символьный тип: as.numeric(as.character(f)). Прямой вызов as.numeric(f) возвращает внутренние коды уровней (1, 2, 3, ...), а не значения, которые показывают метки, — так что фактор, отображающий «20», может вернуться как 2. Это один из самых распространённых тихих багов в R.

Что делает relevel() в R?

Она перемещает выбранный уровень на первую позицию: relevel(group, ref = "control"). Первый уровень — базовая (референсная) категория, с которой функции моделей вроде lm() и glm() сравнивают все остальные уровни, поэтому осознанный её выбор делает коэффициенты регрессии осмысленными.

Почему мой фактор всё ещё показывает удалённые уровни?

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

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ