Гистограмма Альтаира с барами переменной ширины? - PullRequest
4 голосов
/ 06 января 2020

Я пытаюсь использовать Altair в Python, чтобы создать гистограмму, где столбцы имеют различную ширину в зависимости от данных в столбце исходного кадра данных. Конечная цель - получить такой график:

A bar chart with bars of variable width

Высота столбцов соответствует предельной стоимости каждой энергетической технологии ( в виде столбца в исходном фрейме данных). Ширина полосы соответствует мощности каждой энерготехнологии (также приведена в виде столбцов в исходном кадре данных). Цвета являются порядковыми данными также из исходного кадра данных. Бары сортируются в порядке возрастания предельных затрат. (Подобный сюжет в энергетической отрасли называется «стеком генерации»). Это легко сделать в matplotlib, как показано в коде ниже:

import matplotlib.pyplot as plt 

# Make fake dataset
height = [3, 12, 5, 18, 45]
bars = ('A', 'B', 'C', 'D', 'E')

# Choose the width of each bar and their positions
width = [0.1,0.2,3,1.5,0.3]
y_pos = [0,0.3,2,4.5,5.5]

# Make the plot
plt.bar(y_pos, height, width=width)
plt.xticks(y_pos, bars)
plt.show()

(код из https://python-graph-gallery.com/5-control-width-and-space-in-barplots/)

Но есть ли способ сделать это с Альтаиром? Я хотел бы сделать это с Altair, чтобы я мог по-прежнему получать другие замечательные функции Altair, такие как всплывающая подсказка, селекторы / привязки, поскольку у меня есть много других данных, которые я хочу показать рядом с гистограммой.

Первые 20 строк моих исходных данных выглядят так:

enter image description here

(не в точности соответствует диаграмме, показанной выше).

1 Ответ

4 голосов
/ 06 января 2020

В Altair способ сделать это - использовать метку rect и явно строить свои бары. Вот пример, который имитирует ваши данные:

import altair as alt
import pandas as pd
import numpy as np

np.random.seed(0)

df = pd.DataFrame({
    'MarginalCost': 100 * np.random.rand(30),
    'Capacity': 10 * np.random.rand(30),
    'Technology': np.random.choice(['SOLAR', 'THERMAL', 'WIND', 'GAS'], 30)
})

df = df.sort_values('MarginalCost')
df['x1'] = df['Capacity'].cumsum()
df['x0'] = df['x1'].shift(fill_value=0)

alt.Chart(df).mark_rect().encode(
    x=alt.X('x0:Q', title='Capacity'),
    x2='x1',
    y=alt.Y('MarginalCost:Q', title='Marginal Cost'),
    color='Technology:N',
    tooltip=["Technology", "Capacity", "MarginalCost"]
)

enter image description here

Чтобы получить тот же результат без предварительной обработки данных, вы можете использовать синтаксис преобразования Altair для преобразования :

df = pd.DataFrame({
    'MarginalCost': 100 * np.random.rand(30),
    'Capacity': 10 * np.random.rand(30),
    'Technology': np.random.choice(['SOLAR', 'THERMAL', 'WIND', 'GAS'], 30)
})

alt.Chart(df).transform_window(
    x1='sum(Capacity)',
    sort=[alt.SortField('MarginalCost')]
).transform_calculate(
    x0='datum.x1 - datum.Capacity'
).mark_rect().encode(
    x=alt.X('x0:Q', title='Capacity'),
    x2='x1',
    y=alt.Y('MarginalCost:Q', title='Marginal Cost'),
    color='Technology:N',
    tooltip=["Technology", "Capacity", "MarginalCost"]
)
...