2025, Oct 18 12:16

Как получить бинарную индикаторную матрицу в Pandas без циклов

В Pandas показываем, как из строк целых чисел получить бинарную индикаторную матрицу без циклов: stack + one‑hot через get_dummies и транспонирование.

Преобразование строк из целых чисел в бинарную индикаторную матрицу на первый взгляд похоже на one‑hot‑кодирование, но цель здесь другая: для каждой исходной строки нам нужен отдельный бинарный столбец, который помечает позиции (значения индекса), присутствующие в этой строке, а в остальных местах остаются нули. Сложность в том, чтобы сделать это для множества строк сразу и без циклов.

Пример и наивная попытка

Рассмотрим простой DataFrame Pandas, где в каждой строке перечислены позиции (целые числа), которые нужно отметить:

import pandas as pd

data = pd.DataFrame({
    "A": [1, 2, 3],
    "B": [4, 5, 6],
    "C": [7, 8, 9],
}, index=[0, 1, 2])

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

src_df = data
out_grid = pd.DataFrame(range(src_df.max(axis=None) + 1)).isin(list(src_df.iloc[0]))

Этот подход обрабатывает только первую строку и не распространяется на все строки одновременно.

Что на самом деле происходит

Нужная нам структура проявляется, если сменить точку зрения. Если развернуть «широкую» таблицу в длинный столбец, можно one‑hot‑закодировать целочисленные значения, а затем агрегировать по исходным строкам. Так сохраняются позиции, и становится просто собрать бинарную индикаторную матрицу для каждой строки за один проход.

Векторизованное решение в Pandas

Вот компактный, полностью векторизованный конвейер, который выполняет преобразование от начала до конца:

result = pd.get_dummies(data.stack()).groupby(level=0).sum().T

Он выполняется в четыре шага. Сначала stack преобразует данные в длинный Series с многоуровневым индексом из исходного индекса строк и прежних названий столбцов. Затем get_dummies превращает целочисленные значения в one‑hot‑столбцы. Далее groupby(level=0).sum() агрегирует эти индикаторы по исходным строкам (первый уровень MultiIndex). Наконец, .T транспонирует таблицу так, что позиции становятся индексом строк, а исходные строки — столбцами.

Получившийся результат соответствует требуемой бинарной разметке:

   0  1  2
1  1  0  0
2  0  1  0
3  0  0  1
4  1  0  0
5  0  1  0
6  0  0  1
7  1  0  0
8  0  1  0
9  0  0  1

Заглянем под капот

Если хотите увидеть промежуточные объекты для ясности, разбейте решение на именованные шаги. Так проще проверить, как данные проходят через конвейер, не прибегая к циклам:

s_long = data.stack()
ohe = pd.get_dummies(s_long)
agg = ohe.groupby(level=0).sum()
final = agg.T

Длинный ряд, полученный с помощью stack, связывает исходный индекс строки с прежним именем столбца. get_dummies переводит целочисленные значения в индикаторные столбцы. Группировка по первому уровню индекса суммирует индикаторы в пределах каждой исходной строки. Транспонирование поворачивает таблицу в нужную ориентацию: позиции становятся индексом.

Почему это важно

Переформатирование, а затем one‑hot‑кодирование избавляют от кастомных циклов и построчной логики, оставаясь в рамках идиоматичного Pandas. Преобразование легко читать, удобно отлаживать и применяется к любому числу строк. Если вы думали о for‑цикле или .apply() по строкам, этот шаблон снимает такую необходимость. А числовой индекс делает операции предсказуемыми и понятными.

Вывод

Когда нужна бинарная индикаторная матрица, которая отмечает целочисленные позиции из каждой строки, разверните данные в длинный формат с помощью stack, выполните one‑hot‑кодирование через get_dummies, агрегируйте по исходным строкам и транспонируйте. Одна строка pd.get_dummies(data.stack()).groupby(level=0).sum().T решает задачу в векторизованном стиле и естественно масштабируется на весь DataFrame.

Статья основана на вопросе на StackOverflow от lane-h-rogers и ответе PaulS.