История факторов в R
Сегодня разберем историю факторов в R, почему раньше при чтении таблиц аргумент
stringsAsFactors
по умолчанию был
TRUE
, и почему с версии R 4.0 это отменили, а также нужны ли факторы сейчас.
Моя история знакомства с факторами в R была такова: когда я только начинала осваивать R (это было в 2017 году), и использовала его для работы с табличками дифференциальной экспрессии генов, то мне сразу сказали, что при чтении таблицы надо использовать аргумент
stringsAsFactors = FALSE
, чтобы строки не превратились в факторы. Прозвучало это супер непонятно, но далее я обратила внимание, что если этого не делать, то после фильтрации таблицы в строковых переменных остаются какие-то уровни (levels), это было неудобно, поэтому я стала следить, чтобы всегда при чтении таблиц было указано
stringsAsFactors = FALSE
. И потом при работе уже с дисперсионным анализом поняла, зачем нужны были факторы, и как их использовать, но все равно они оставались чем-то загадочным, средним между строкой и числом, даже казались мне ближе к строке (но это не так, сейчас разберем почему).
Изначально R был создан и использовался в первую очередь для статистических задач, и поэтому превращение всех строк в факторы при чтении таблиц было оправданно, поскольку не числовая переменная в таблице скорее всего означала категории (пол, группа крови, наличие болезни и тп). Для дальнейшего статистического анализа, например, при создании моделей с помощью функций
lm()
,
glm()
, факторы были нужны для корректной работы этих функций.
Кроме этого, есть еще одна менее очевидная причина. Раньше хранение факторов занимало меньше места в памяти, чем хранение строк. Дело в том, что факторы во внутреннем представлении R – это integer, то есть целые числа, соответственно занимают около 4 байт памяти. При этом строки занимают больше памяти и при работе с категориальными переменными каждая строка таблицы занимала свое место в памяти (то есть если записано
disease
в 100 строках, то грубо говоря и памяти будет занято как место занимаемое строкой
disease
*
100
) [1].
Плюс сравнивать факторы было проще, чем строки, поскольку сравнение
чисел происходит за фиксированное время, в то время как для сравнения
строк необходимо было "проходить" каждую строку побайтно.
🖥 Все изменилось в обновлении R 2.6.0 в 2007 году, когда сделали хеширование CHARSXP, что означало, что теперь ВСЕ строки хешируются и хранятся в глобальном пуле строк, и теперь к строкам обращались по целочисленным указателям: не было нужды хранить каждую копию строки, достаточно было хранить только указатель.
Между прочим, это обновление было сделано благодаря проекту
Bioconductor, потому что разработчикам приходилось работать с большими строками, например, с последовательностями нуклеиновых кислот и белков, и без хеширования строк работа со старым типом была максимально неэффективной.
После этого выигрыш в хранении и сравнении строк в виде факторов перестал быть существенным, тем не менее факторы все еще необходимы в
lm
,
glm
и других подобных функциях, а также для упорядочивания категорий на графике.
Иллюстрация объема памяти, занимаемого на хранение строк (пример взят из книги
Advanced R):
banana <- "bananas bananas bananas"
obj_size(banana)
#> 136 B
obj_size(rep(banana, 100))
#> 928 B
Мы создали 100 копий строки, но при этом занимаемый объем увеличился всего в 6.8 раз!
При чтении таблиц средствами базового R аргумент
stringsAsFactors
был
TRUE
, и только в 2020 году с версии R 4.0 разработчики R это изменили на
FALSE
. Только в функциях из пакета
readr
строки не превращались в факторы по умолчанию. На мой взгляд, разработчики R довольно поздно изменили дефолтное поведение функций
read.table
и
read.csv
, но лучше уж поздно чем никогда, хотя я сейчас пользуюсь только
readr
-ом для загрузки таблиц (и
fread
, при необходимости).
Ссылки на источники:
1.
https://simplystatistics.org/posts/2015-07-24-stringsasfactors-an-unauthorized-biography/
2.
https://notstatschat.rbind.io/2015/07/25/stringsasfactors-sigh/
3.
https://notstatschat.rbind.io/2022/03/31/stringsasfactors-do-you-feel-lucky/
#R #history #baseR