Вопрос или проблема
Предположим, у нас есть данные в таких экземплярах:
[
[15, 20, ("банан","яблоко","огурец"), ...],
[91, 12, ("апельсин","банан"), ...],
...
]
Мне интересно, как я могу закодировать третий элемент этих данных. Для множественных значений признаков мы могли бы использовать OneHotEncoder из sklearn, но, насколько я смог выяснить, он не может обрабатывать входные данные разной длины.
Вот что я попробовал:
X = [[15, 20, ("банан","яблоко","огурец")], [91, 12, ("апельсин","банан")]]
ct = ColumnTransformer(
[
("genre_encoder", OneHotEncoder(), [2])
],
remainder="passthrough"
)
print(ct.fit_transform(X))
Это выведет только
[[1.0 0.0 15 20]
[0.0 1.0 91 12]]
как и ожидалось, потому что кортежи обрабатываются как возможные значения, с которыми можно представить этот признак.
Мы не можем встроить наши признаки напрямую (например, [15, 12, "банан", "яблоко", "огурец"]
), потому что
- мы не знаем, сколько экземпляров этого признака у нас будет (два? три?)
- каждая позиция будет интерпретироваться как собственный признак, и таким образом, если у нас есть
банан
в первом номинальном слоте в одной точке данных и во втором во втором номинальном слоте, они не будут относиться к одному и тому же “пулу значений”, который может содержать признак
Пример:
X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
enc = OneHotEncoder()
print(enc.fit_transform(X).toarray())
[[1. 0. 1. 0. 1.]
[0. 1. 0. 1. 1.]]
Это представление содержит 5 слотов вместо 4, потому что первый слот интерпретируется как использование банан
или апельсин
, второй как яблоко
или банан
, а последний имеет только опцию огурец
.
(Это также не решит проблему с разным количеством значений признаков на точку данных. И замена пустых значений на None
тоже не решает проблему, потому что тогда None
сталкивается с этой позиционной неоднозначностью.)
Есть идеи, как закодировать эти “Мульти-Мульти-“признаки, которые могут иметь несколько значений и состоять из переменного количества элементов? Заранее спасибо!
Я думаю, вы можете преобразовать это в задачу предварительной обработки текста и затем использовать CountVectorizer. Вы в основном создаете “документы”, связывая все слова в ваших сырых данных, а затем используете CountVectorizer на этих документах.
from sklearn.feature_extraction.text import CountVectorizer
X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
# Создаем документы
X_ = [' '.join(x) for x in X]
enc = CountVectorizer()
print(enc.fit_transform(X_).toarray())
Возвращает
[[1 1 1 0]
[0 1 1 1]]
что имеет 4 разных значения, как вы и ожидали.
Я верю, что ответ @David Masip можно еще больше улучшить, предоставив пользовательский анализатор для CountVectorizer.
Объединение всех признаков в одну строку имеет некоторые проблемы:
- Не будет работать хорошо с признаками, содержащими пробелы
- Не будет работать хорошо с признаками, содержащими акценты, стоп-слова и т. д. из-за токенизации CountVectorizer
К счастью, CountVectorizer теперь предлагает способ предоставить пользовательский токенизатор через аргумент analyzer
. Так что мы могли бы продолжать передавать списки значений в качестве входных данных и использовать пользовательский анализатор, чтобы пропустить эти списки без токенизации:
from sklearn.feature_extraction.text import CountVectorizer
X = [["банан","яблоко","огурец"], ["апельсин","банан", "огурец"]]
enc = CountVectorizer(analyzer=lambda lst: lst)
print(enc.fit_transform(X).toarray())
Это дает тот же результат
[[1 1 1 0]
[0 1 1 1]]
избегая вышеупомянутых проблем и также экономя ресурсы ЦП/памяти, не объединяя и не повторно токенизируя признаки.
Ответ или решение
Многофункциональный One-Hot-Encoder с переменным числом значений признаков
В процессе работы с многомерными данными нередко возникает необходимость в кодировании категориальных признаков, которые могут иметь переменное количество значений для каждого экземпляра. В данной статье мы рассмотрим подход к решению этой задачи с использованием инструмента CountVectorizer
из библиотеки scikit-learn
, который позволят правильно обработать такие данные.
Исходные данные
Рассмотрим массив данных, где каждый экземпляр состоит из различных признаков, в том числе категориальных:
X = [
[15, 20, ("banana", "apple", "cucumber")],
[91, 12, ("orange", "banana")]
]
В данном примере третий элемент каждого подмассива содержит кортежи, которые представляют произвольное число значений.
Исходная проблема
Стандартный метод кодирования категориальных признаков с помощью OneHotEncoder
из scikit-learn
не может обрабатывать входные данные, имеющие разную длину. В результате, если использовать приведенный выше подход, получится следующее:
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
X = [[15, 20, ("banana", "apple", "cucumber")], [91, 12, ("orange", "banana")]]
ct = ColumnTransformer(
[("genre_encoder", OneHotEncoder(), [2])],
remainder="passthrough"
)
print(ct.fit_transform(X))
Вывод будет:
[[1.0 0.0 15 20]
[0.0 1.0 91 12]]
К сожалению, OneHotEncoder
воспринимает кортежи как множество отдельных признаков, что делает невозможным объединение значений в единую кодировку.
Решение с использованием CountVectorizer
Одним из элегантных решений может быть использование CountVectorizer
, который способен справляться с текстовой информацией. Мы создали «документы», чтобы обработать списки значений, представленных для каждого экземпляра:
from sklearn.feature_extraction.text import CountVectorizer
set_of_values = [
["banana", "apple", "cucumber"],
["orange", "banana", "cucumber"]
]
# Объединяем значения в строку
X_ = [' '.join(values) for values in set_of_values]
enc = CountVectorizer()
print(enc.fit_transform(X_).toarray())
Результат будет следующим:
[[1 1 1 0]
[0 1 1 1]]
Таким образом, CountVectorizer
создает кодировку, корректно определяя общее множество значений без разделения по позициям.
Улучшение с использованием кастомного токенизатора
Поскольку использование CountVectorizer
с объединением значений может привести к нежелательным эффектам, связанным с токенизацией (например, некорректная обработка пробелов или акцентов), мы можем сделать шаг дальше и задать собственный токенизатор:
from sklearn.feature_extraction.text import CountVectorizer
set_of_values = [
["banana", "apple", "cucumber"],
["orange", "banana", "cucumber"]
]
enc = CountVectorizer(analyzer=lambda lst: lst)
print(enc.fit_transform(set_of_values).toarray())
Этот подход позволяет избежать перечисленных проблем, так как мы передаем списки значений напрямую и обрабатываем их без необходимости объединения, что экономит ресурсы.
Заключение
Использование CountVectorizer
и кастомного токенизатора позволяет эффективно кодировать многозначные категориальные признаки с переменным количеством значений без потери структуры данных. Этот подход обеспечивает более гибкое представление ваших данных для последующей обработки и анализа.
Подводя итог, ни один из используемых методов не является универсальным, однако представленный способ позволяет добиться желаемого результата максимально простым и эффективным способом, а также преодолеть ограничения стандартных инструментов, таких как OneHotEncoder
.