Документация Engee

Категориальные данные

Зачастую столбцы во фрейме данных имеют небольшое количество уровней:

julia> v = ["Group A", "Group A", "Group A", "Group B", "Group B", "Group B"]
6-element Vector{String}:
 "Group A"
 "Group A"
 "Group A"
 "Group B"
 "Group B"
 "Group B"

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

Есть два стандартных типа, обеспечивающих сжатие уровней:

  • PooledVector из PooledArrays.jl;

  • CategoricalVector из CategoricalArrays.jl.

Различие между PooledVector и CategoricalVector в следующем.

  • Тип PooledVector предназначен для случаев, когда единственной целью является сжатие данных.

  • Тип CategoricalVector дополнительно обеспечивает полную поддержку работы с категориальными переменными, причем как с упорядоченными (номинальные переменные), так и с неупорядоченными (порядковые переменные) категориями, но ценой того, что допускаются элементы только типов AbstractString, AbstractChar и Number (возможно, в объединении с Missing).

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

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

Для разбора приведенных ниже примеров необходимо сначала установить пакет CategoricalArrays.jl.

julia> using CategoricalArrays

julia> cv = categorical(v)
6-element CategoricalArray{String,1,UInt32}:
 "Group A"
 "Group A"
 "Group A"
 "Group B"
 "Group B"
 "Group B"

Векторы CategoricalVectors поддерживают отсутствующие значения.

julia> cv = categorical(["Group A", missing, "Group A",
                         "Group B", "Group B", missing])
6-element CategoricalArray{Union{Missing, String},1,UInt32}:
 "Group A"
 missing
 "Group A"
 "Group B"
 "Group B"
 missing

Помимо эффективного представления повторяющихся данных, тип CategoricalArray позволяет эффективно в любой момент определять допустимые уровни переменной с помощью функции levels (имейте в виду, что уровни могут как использоваться, так и не использоваться на самом деле в данных):

julia> levels(cv)
2-element Vector{String}:
 "Group A"
 "Group B"

Функция levels! также позволяет изменять порядок отображения уровней, что может быть полезно при выводе данных или при работе с упорядоченными переменными.

julia> levels!(cv, ["Group B", "Group A"])
6-element CategoricalArray{Union{Missing, String},1,UInt32}:
 "Group A"
 missing
 "Group A"
 "Group B"
 "Group B"
 missing

julia> levels(cv)
2-element Vector{String}:
 "Group B"
 "Group A"

julia> sort(cv)
6-element CategoricalArray{Union{Missing, String},1,UInt32}:
 "Group B"
 "Group B"
 "Group A"
 "Group A"
 missing
 missing

По умолчанию вектор CategoricalVector может представлять разных уровней. Вызвав функцию compress, можно сократить занимаемую память:

julia> cv = compress(cv)
6-element CategoricalArray{Union{Missing, String},1,UInt8}:
 "Group A"
 missing
 "Group A"
 "Group B"
 "Group B"
 missing

Функция categorical принимает дополнительный именованный аргумент compress, значение true которого равносильно вызову функции compress для нового вектора:

julia> cv1 = categorical(["A", "B"], compress=true)
2-element CategoricalArray{String,1,UInt8}:
 "A"
 "B"

Если именованный аргумент ordered имеет значение true, полученный в итоге вектор CategoricalVector будет упорядоченным, то есть его уровни допускают проверку порядка (вместо выдачи ошибки):

julia> cv2 = categorical(["A", "B"], ordered=true)
2-element CategoricalArray{String,1,UInt32}:
 "A"
 "B"

julia> cv1[1] < cv1[2]
ERROR: ArgumentError: Unordered CategoricalValue objects cannot be tested for order using <. Use isless instead, or call the ordered! function on the parent array to change this

julia> cv2[1] < cv2[2]
true

Проверить, является ли вектор CategoricalVector упорядоченным, можно с помощью функции isordered. Изменить упорядоченный вектор на неупорядоченный и наоборот можно с помощью функции ordered!.

julia> isordered(cv1)
false

julia> ordered!(cv1, true)
2-element CategoricalArray{String,1,UInt8}:
 "A"
 "B"

julia> isordered(cv1)
true

julia> cv1[1] < cv1[2]
true