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

Сведения о реализации

CategoricalArray состоит из двух полей.

  • refs: целочисленный массив, хранящий позицию уровня категории в поле levels в CategoricalPool для каждого элемента CategoricalArray; 0 обозначает отсутствующее значение (только для CategoricalArray{Union{T, Missing}}).

  • pool: объект CategoricalPool, поддерживающий уровни массива.

Тип CategoricalPool{V,R,C} отслеживает уровни типа V и связывает их с целочисленным справочным кодом типа R (для внутреннего использования). Он предлагает методы для добавления новых уровней, а также для получения целочисленного индекса, соответствующего уровню, и наоборот. Упорядочены ли значения CategoricalArray или нет, определяется полем ordered пула.

Обратите внимание, что уровни CategoricalPool являются полуизменяемыми: разрешается только добавлять новые уровни, но никогда — удалять или изменять порядок существующих. Благодаря этому существующие объекты CategoricalValue остаются действительными и всегда указывают на тот же уровень, на котором они были созданы. Поэтому CategoricalArray создают новый пул каждый раз, когда некоторые из их уровней удаляются или упорядочиваются. Это происходит при вызове функции levels!, а также при присвоении CategoricalValue с помощью функций setindex!, push!, append!, copy! или copyto! (поскольку новые уровни могут быть добавлены впереди, чтобы сохранить относительный порядок исходного и конечного уровней). В этом случае требуется обновить все справочные коды, чтобы они указывали на новый пул. Однако будет невозможно сравнивать существующие упорядоченные объекты CategoricalValue со значениями из массива с помощью < и >.

Параметры типа CategoricalArray{T, N, R <: Integer, V, C, U} несколько сложны.

  • T является типом элементов массива без оболочек CategoricalValue. Если T >: Missing, массив поддерживает пропущенные значения.

  • N представляет количество измерений массива.

  • R является ссылочным типом, типом элемента поля refs. Он позволяет оптимизировать использование памяти в зависимости от количества уровней (т. е. CategoricalArray с менее чем 256 уровнями можно использовать R = UInt8).

  • V является типом уровней, он равен T для массивов, не поддерживающих пропущенные значения. Для массивов, поддерживающих пропущенные значения, T = Union{V, Missing}

  • C — это тип категориальных значений, то есть объектов, возвращаемых при индексации неотсутствующих элементов CategoricalArray. Он всегда равен CategoricalValue{V, R} и присутствует только по техническим причинам (чтобы нарушить рекурсивную зависимость между CategoricalArray и CategoricalValue).

  • U может быть либо Union{} для массивов, которые не поддерживают отсутствующие значения, либо Missing для тех, которые их поддерживают.

При построении можно указать только T, N и R. Последние три параметра выбираются автоматически, но они необходимы для определения типа. В частности, U позволяет выразить, что CategoricalArray{T, N} наследуется от AbstractArray{Union{C, U}, N} (что эквивалентно AbstractArray{C, N} для массивов, не поддерживающих отсутствующие значения, и AbstractArray{Union{C, Missing}, N} для тех, которые их поддерживают).

Тип CategoricalPool предназначен для ограничения необходимости прохода по всем элементам вектора, как для чтения, так и для записи. Именно поэтому неиспользуемые уровни не отбрасываются автоматически (это заставило бы проверять все элементы при каждом изменении или вести таблицу подсчетов), а только при вызове droplevels!. levels — это (очень быстрая) операция O(1), поскольку она просто возвращает (упорядоченный) вектор уровней, не обращаясь к данным вообще.

Скалярные операции между объектами CategoricalValue или между объектами CategoricalValue и CategoricalArray обычно требуют проверки того, равны ли пулы или является ли один из них супермножеством другого. Чтобы эти операции были эффективными, CategoricalPool хранит указатель на последний встретившийся равный пул в поле equalto и указатель на последний встретившийся пул строгих супермножеств в поле subsetof. Хэш уровней вычисляется при первой необходимости и хранится в поле hash. Эти оптимизации означают, что при циклическом проходе по значениям в массиве за сравнение пулов придется платить только один раз.