2025, Oct 18 17:15
Как задать порядок категорий в plotnine после unpivot в polars
Почему после unpivot в polars в plotnine сбивается порядок категорий и как исправить: задаем limits в scale_x_discrete, сохраняем порядок Category до unpivot.
Неверный порядок дискретных категорий способен испортить даже предельно понятный график. При преобразовании данных в polars и построении диаграммы в plotnine частая ловушка — перепутанные метки в столбце Category. На столбчатой диаграмме, как ниже, группа с меткой 5-10 должна стоять на третьем месте, но это не так. Решение простое: достаточно явно задать порядок дискретной оси.
Повтор проблемы: столбчатая диаграмма polars + plotnine с перепутанными категориями
import polars as po
import polars.selectors as sl
from plotnine import ggplot, geom_bar, aes, scale_fill_manual, coord_flip
frame_wide = po.read_csv('https://raw.githubusercontent.com/Brinkhuis/Zorguitgaven/refs/heads/master/zorguitgaven.csv')
frame_long = frame_wide.unpivot(sl.numeric(), index='Category')
(
    ggplot() +
    geom_bar(
        data=frame_long.filter(po.col('variable').is_in(['Mannen 2040', 'Vrouwen 2040'])),
        mapping=aes(x='Category', y='value', fill='variable'),
        stat='identity'
    ) +
    scale_fill_manual(values=['#007BC7', '#CA005D']) +
    coord_flip()
)
График строится, но порядок Category оказывается другим, и 5-10 не попадает на своё место.
Почему сбивается порядок
После unpivot в столбце Category одни и те же метки повторяются для каждого «расплавленного» числового столбца. Графический слой видит несколько вхождений одной категории и выбирает порядок, который не совпадает с желаемой последовательностью. Чтобы это исправить, оси нужен явный порядок.
Прямое решение в plotnine: задать дискретные limits
Самый простой способ зафиксировать порядок по оси x — передать упорядоченный список категорий в scale_x_discrete. Этот список должен отражать ожидаемую последовательность. Быстрый практичный вариант — взять первый блок значений Category из «длинной» таблицы и отсечь его до нужной длины.
import polars as po
import polars.selectors as sl
from plotnine import ggplot, geom_bar, aes, scale_fill_manual, scale_x_discrete, coord_flip
wide_tbl = po.read_csv('https://raw.githubusercontent.com/Brinkhuis/Zorguitgaven/refs/heads/master/zorguitgaven.csv')
long_tbl = wide_tbl.unpivot(sl.numeric(), index='Category')
(
    ggplot() +
    geom_bar(
        data=long_tbl.filter(po.col('variable').is_in(['Mannen 2040', 'Vrouwen 2040'])),
        mapping=aes(x='Category', y='value', fill='variable'),
        stat='identity'
    ) +
    scale_x_discrete(limits=long_tbl['Category'].to_list()[:21]) +
    scale_fill_manual(values=['#007BC7', '#CA005D']) +
    coord_flip()
)
Такой срез помогает обойти дубликаты Category, появившиеся после преобразования.
Более аккуратный подход: зафиксировать порядок категорий до unpivot
Чтобы обойтись без срезов, сначала сохраните уникальную последовательность Category из «широких» данных, затем выполните unpivot и передайте этот список в limits шкалы. Так порядок остаётся однозначным и привязан к исходным данным.
import polars as po
import polars.selectors as sl
from plotnine import ggplot, geom_bar, aes, scale_fill_manual, scale_x_discrete, coord_flip
source_df = po.read_csv('https://raw.githubusercontent.com/Brinkhuis/Zorguitgaven/refs/heads/master/zorguitgaven.csv')
cat_order = source_df['Category']
reshaped_df = source_df.unpivot(sl.numeric(), index='Category')
(
    ggplot() +
    geom_bar(
        data=reshaped_df.filter(po.col('variable').is_in(['Mannen 2040', 'Vrouwen 2040'])),
        mapping=aes(x='Category', y='value', fill='variable'),
        stat='identity'
    ) +
    scale_x_discrete(limits=cat_order) +
    scale_fill_manual(values=['#007BC7', '#CA005D']) +
    coord_flip()
)
При условии, что в исходном датафрейме порядок уже правильный. Иначе придётся отсортировать по какому‑то критерию. Например, по первому числу.
Почему это важно
Порядок дискретных категорий несёт смысл. В возрастных группах, когортах или упорядоченных «корзинах» перемешанная ось искажает интерпретацию, усложняет сравнения и подталкивает к неверным выводам. После преобразования дубликаты могут скрывать естественную последовательность, если явно не указать слою визуализации, как раскладывать категории. Контроль дискретной шкалы помогает избежать тихой переупорядочивки и сохраняет ваши столбчатые диаграммы верными источнику.
Вывод
При работе с polars и plotnine операции преобразования, такие как unpivot, дублируют метки категорий и приводят к неожиданному порядку на дискретных осях. Надёжный способ сохранить задуманный порядок — передать явные limits в scale_x_discrete. Если взять столбец Category до unpivot, вы получите чистую уникальную последовательность; передав её в шкалу, вы добьётесь ровно того отображения, которое задумано.