2025, Dec 03 03:01

Как построить теплокарту из polars в Plotly без pandas

Показываем, как строить теплокарты из tidy‑данных polars в Plotly без конвертации в pandas: вариант с px.imshow и graph_objects, советы по оси y и ориентации.

Тепловые карты естественно подходят для tidy‑данных, но на этапе построения графика часто приходится делать пивот и переключаться между библиотеками. Если у вас уже есть tidy/длинный DataFrame в polars и вы хотите нарисовать теплокарту без захода в pandas, есть два аккуратных варианта: передать в Plotly Express широкую таблицу из polars или отдать tidy‑столбцы напрямую в Plotly graph_objects. Оба подхода избавляют от кругового пути через pandas.

Постановка задачи

Предположим, вы начинаете с tidy‑DataFrame в polars и строите теплокарту, сначала делая пивот в широкую форму и конвертацию в pandas перед отрисовкой. Это работает, но шагов больше, чем нужно.

import plotly.express as px
import polars as pl

long_pl = pl.DataFrame(
    {
        "x": [10, 10, 10, 20, 20, 20, 30, 30, 30],
        "y": [3, 4, 5, 3, 4, 5, 3, 4, 5],
        "value": [5, 8, 2, 4, 10, 14, 10, 8, 9],
    }
)
print(long_pl)

wide_pd = (
    long_pl.pivot(index="x", on="y", values="value").to_pandas().set_index("x")
)
print(wide_pd)

chart = px.imshow(wide_pd)
chart.show()

Что происходит под капотом

Когда вы передаёте Plotly объект DataFrame, он воспринимает индекс pandas как ось y. В polars у DataFrame нет такого понятия индекса, поэтому нужно явно указать, что использовать в качестве подписей по y. Это единственное принципиальное отличие, и его легко учесть без конвертации в pandas.

Если бы Plotly действительно умел работать с данными polars нативно, я бы ожидал, что он справится с tidy‑DataFrame, то есть без необходимости пивота.

Ожидание оправдывается: можно либо передать в Plotly Express широкую таблицу из polars и явно указать y, либо отдать plotly.graph_objects tidy‑столбцы напрямую и позволить ему построить сетку.

Решение: широкая таблица polars напрямую в Plotly Express

Остаёмся в polars: делаем пивот в широкую форму в памяти и говорим Plotly, что брать для y. Из матрицы Z убираем столбец с метками.

import plotly.express as px

wide_pl = long_pl.pivot(index="x", on="y", values="value")
heat1 = px.imshow(wide_pl.drop("x"), y=wide_pl["x"])  # подписи по оси y берутся из прежнего индекса
heat1.show()

Получится та же визуальная раскладка, что и в варианте с pandas, только без конвертаций.

Решение: tidy‑DataFrame polars с plotly.graph_objects

Можно строить график прямо из tidy‑столбцов. Подход повторяет то, как это делается с pandas в tidy‑формате.

import plotly.graph_objects as go

heat2 = go.Figure(
    go.Heatmap(
        x=long_pl["y"],
        y=long_pl["x"],
        z=long_pl["value"],
    )
)
# Выравниваем ориентацию под вариант с широкой таблицей
heat2.update_layout(yaxis_autorange="reversed")
heat2.show()

Альтернатива: tidy‑построение через Altair

Если вам ближе Altair, в polars есть пространство имён .plot, принимающее tidy‑данные. Результат — сопоставимая теплокарта.

import altair as alt

(
    long_pl.plot.rect(
        x="y:O",
        y="x:O",
        # используем палитру в духе Plotly; простое color="value:Q" тоже подойдёт
        color=alt.Color("value:Q", scale=alt.Scale(scheme="plasma")),
    )
    .properties(width=500, height=400)
)

Зачем это важно

Оставаясь в polars от начала до конца, вы упрощаете конвейер и избегаете межбиблиотечных преобразований. Plotly может рисовать напрямую из данных polars — будь то широкая матрица с явными подписями по y или tidy‑столбцы через graph_objects. Это помогает сохранить tidy‑подход на всём пути и держит код визуализации рядом с шагами преобразования данных.

Выводы

Если вы уже сделали пивот в polars, передайте px.imshow широкую таблицу polars, исключите из Z столбец‑метку и явно укажите y. Если хотите остаться целиком в tidy‑виде, создайте go.Heatmap, подставив x, y и z из соответствующих столбцов polars, и инвертируйте ось y для той же ориентации. Оба варианта обходятся без конвертации в pandas и делают этап визуализации компактным.