Построить тепловую карту (как и почти любую визуализацию данных) в великолепной plotly.express невероятно просто:

px.density_heatmap(df, x='x', y='y', z='z', marginal_y='histogram').show()

plotly-express-density-heatmap

Но иногда хочется разбить данные на децили/квартили/квинтили. Как например, в нашем случае - по оси Y.

К сожалению, “из коробки” plotly.express не справится.

Если Вы сделаете вот так:

df.y_q = pandas.qcut(df.y, q=10)

px.density_heatmap(df, x='x', y='y_q', z='z', marginal_y='histogram').show()

То ничего не выйдет, plotly будет падать с ошибкой TypeError: Object of type Interval is not JSON serializable

Да, можно потанцевать с бубном и привести Interval объекты к строке, и у Вас даже получится нарисовать density_heatmap - но они будут сортироваться как строки и будут перепутаны с точки зрения значений y.

px.density_heatmap(df, x='x', y=df.y_q.astype(str), z='z', marginal_y='histogram').show()

plotly-express-density-heatmap

Можно явно передать plotly порядок для y - и это будет работать, но код будет выглядеть так:

df['y_q'] = pandas.qcut(df.y, q=10)

order = [str(c) for c in reversed(df.y_q.cat.categories)]

px.density_heatmap(df, x='x', y=df.y_q.astype(str), z='z', \
  marginal_y='histogram', category_orders={"y": order}).show()

plotly-express-density-heatmap

Из плюсов - на оси Y обозначены понятные интервалы значений y для каждого дециля.

Есть решение по-проще и по-красивее:

df.y_q, labels = pandas.qcut(df.y, q=10, labels=False, retbins=True)

px.density_heatmap(df, x='x', y='y_q', z='z', marginal_y='histogram').show()

Теперь df.y_q (как и ось Y на графике) - это порядковый номер дециля, обычный int.

Границы интервалов лежат в numpy.array labels, это 11 float чисел.

plotly-express-density-heatmap

Красиво! И за 2 читаемые строчки!

Что еще почитать?

Обзор и доки plotly.express

Наш телеграм канал