2025, Oct 19 02:31

Один столбец на строку в Altair при повторе категорий X

Поясняем, почему Altair объединяет столбцы при повторе категорий по X, и показываем решение: уникальный ключ по оси X плюс labelExpr для исходных подписей.

Altair группирует строки, относящиеся к одной и той же категории по оси X. Если значение поля повторяется, его значения по оси Y суммируются, и в итоге столбцов становится меньше, чем строк. Когда в исходных данных дубликаты осознанны и вам нужен один столбец на строку, потребуется уникальный ключ для канала x — при этом на оси должны оставаться исходные подписи.

Воспроизведение проблемы

Набор данных содержит четыре строки, при этом два элемента имеют одинаковое значение в поле a. Прямое кодирование Altair сворачивает их в три столбца, потому что обе строки с B складываются.

data_tbl = pd.DataFrame({
    'a': ['A', 'B', 'C', 'B'],
    'b': [10, 20, 30, 15]
})

alt.Chart(data_tbl).mark_bar().encode(
    x='a',
    y='b'
)

Для сравнения, построение с pandas рисует по одному столбцу на строку, поскольку рассматривает каждую запись независимо, даже когда подписи повторяются.

data_tbl.plot(kind='bar', x='a', y='b')

Почему столбцов меньше

В такой конфигурации строки с одинаковым значением a объединяются, поэтому две строки с B складываются в один столбец со значением 35. В результате получается три столбца для A, B и C, а не четыре, соответствующие числу строк.

Наивный обходной путь и его ограничения

Использование индекса DataFrame в качестве канала x дает четыре столбца, но подписи превращаются в числовые индексы, и исходные значения a на оси теряются.

alt.Chart(data_tbl.reset_index()).mark_bar().encode(
    x='index:O',
    y='b'
)

Это сохраняет количество столбцов, но лишает нужных категориальных меток.

Аккуратное решение: уникальные ключи по x и исходные подписи

Сформируйте уникальный ключ по x, объединив индекс строки со значением a, а на оси показывайте только часть a. Так вы сохраните соответствие «одна строка — один столбец» и оставите на оси X ожидаемый текст.

alt.Chart(data_tbl.reset_index()).transform_calculate(
    x_token=alt.datum['a'] + '_' + alt.datum['index']
).mark_bar().encode(
    alt.X('x_token:N', title='A', axis=alt.Axis(labelExpr='split(datum.value, "_")[0]')),
    alt.Y('b')
)

Преобразование создает составной ключ, уникальный для каждой строки. Выражение labelExpr на оси разделяет ключ по символу подчеркивания и показывает только левую часть, тем самым возвращая на ось исходные значения a и не допуская агрегации.

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

Когда вы осознанно используете повторяющиеся категории для разных записей, их тихое объединение меняет и число элементов на графике, и высоту столбцов. Гарантируя соответствие «строка ↔ отметка», вы получаете визуализацию, которая отражает исходные данные без нежелательной консолидации. Одновременно сохранение исходных подписей поддерживает читаемость и соответствует смыслу данных.

Выводы

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

Статья основана на вопросе с StackOverflow от MiscBits и ответе от kgoodrick.