Как вывести на барплот значение, которое сильно меняет свой порядок от начала до конца временного ряда

Требуется, чтобы на большм диапазоне данных было видно начальные бары. Т.е. условно, значение растет от 10 до 1 млн на нескольких временных точках по x. Как сделать, чтобы первые бары графика были хорошо читаемы (не сливались с нулем) и при этом выводилось их фактическое значение по оси y.?

Нужно получить что-то вроде такого графика, только укрупнить столбцы в начале. Чтобы они не сливались в линию. Плюс в 1-2-3 барах есть отрицательные значения, которые должно быть хорошо видно ниже линии 0.

x = ['2022','2023','2024','2025','2026','2027','2028','2029','2030']
y = [45, 3792, 33708, 251604, 605088, 919392, 1233696, 1548000, 1862304]
y2 = [-7168, -13044, -10928, 77554, 348029, 598680, 849331, 1099982, 1351329]

введите сюда описание изображения

Как примерно должны быть расположены бары


Ответы (4 шт):

Автор решения: SergFSM

если данные меняются постепенно на всем протяжении временного интервала, то должно помочь использование логарифмических или кастомных шкал. примеры смотрите здесь.

ну а если это просто отдельные выбросы, которые вы хотите тоже показать, то есть еще вариант с разорванной шкалой. пример здесь.

UPD

еще, как вариант, можно разбить данные на несколько интервалов и для каждого задать свою шкалу. примерно вот так (хотя это равносильно тому, чтобы построить несколько отдельных графиков):

import pandas as pd
import matplotlib.pyplot as plt

fruits = ['apple', 'pear', 'melon', 'blueberry', 'cherry', 'orange', 'mango', 'tomato', 'peach']
counts = [-1, 3, 4, 40, 30, 55, 1000, 1500, 1800]
s = pd.Series(counts,index=fruits)

fig, ax = plt.subplots(figsize=(15,5))

twin1 = ax.twinx()
twin2 = ax.twinx()
twin2.spines.right.set_position(("axes", 1.05))

b1 = ax.bar(s[s<10].index, s[s<10], color='b', label='low')
b2 = twin1.bar(s[(10<=s)&(s<100)].index, s[(10<=s)&(s<100)], color='g', label='midle')
b3 = twin2.bar(s[s>=100].index, s[s>=100], color='r', label='high')

ax.bar_label(b1, padding=-20, color='w')
twin1.bar_label(b2, padding=-20, color='w')
twin2.bar_label(b3, padding=-20, color='w')

ax.set_xlabel("fruits")
ax.set_ylabel("low_total")
twin1.set_ylabel("mid_total")
twin2.set_ylabel("hi_total")

ax.yaxis.label.set_color('b')
twin1.yaxis.label.set_color('g')
twin2.yaxis.label.set_color('r')

ax.tick_params(axis='y', colors='b', size=4, width=1.5)
twin1.tick_params(axis='y', colors='g', size=4, width=1.5)
twin2.tick_params(axis='y', colors='r', size=4, width=1.5)
ax.tick_params(axis='x', size=4, width=1.5)

ax.legend(handles=[b1, b2, b3], loc='upper left')
plt.show()

введите сюда описание изображения

UPD2

если применить этот подход к вашим данным, то можно получить что-то вроде такого: введите сюда описание изображения

но как по мне, этот график не только сильно перегружен, но и совсем не наглядный

→ Ссылка
Автор решения: splash58

самая первая ссылка в том ответе дает вот такой результат. мне кажется, это примерно то, что вы хотите. метки можете по оси можете сделать другие

df = pd.DataFrame({'x': x, 'y': y, 'y2': y2})
fig, ax = plt.subplots()
ax.bar(df.x, df.y - df.y2, bottom=df.y2)
ax.set_yscale('function', functions=(lambda x: np.sign(x)*np.abs(x)**(1/2), lambda x: np.sign(x)*np.abs(x)**2))
ax.grid(True)
plt.show()

введите сюда описание изображения

→ Ссылка
Автор решения: user532719
    import plotly.graph_objects as go

years = ['2022','2023','2024','2025','2026','2027','2028','2029','2030']

fig = go.Figure()
fig.add_trace(go.Bar(x=years,
                y=[45, 3792, 33708, 251604, 605088, 919392, 1233696, 1548000, 1862304],
                name='Gross',
                marker_color='rgb(55, 83, 109)'
                ))
fig.add_trace(go.Bar(x=years,
                y=[-7168, -13044, -10928, 77554, 348029, 598680, 849331, 1099982, 1351329],
                name='Revenue',
                marker_color='rgb(26, 118, 255)'
                ))

fig.update_layout(
    title='График дохода',
    xaxis_tickfont_size=14,
    yaxis=dict(
        title='График дохода',
        titlefont_size=16,
        tickfont_size=14,
    ),
    legend=dict(
        x=0,
        y=1.0,
        bgcolor='rgba(255, 255, 255, 0)',
        bordercolor='rgba(255, 255, 255, 0)'
    ),
    barmode='group',
    bargap=0.15, # gap between bars of adjacent location coordinates.
    bargroupgap=0.1 # gap between bars of the same location coordinate.
)
fig.update_layout(yaxis_range=[-14000,20000])
fig.show()

введите сюда описание изображения

Получается похоже на желаемый вариант, если ограничивать значения оси Y (собственно сокращать диапазон данных).

→ Ссылка
Автор решения: CrazyElf

В matplotlib вот достаточно выбрать правильную шкалу (симметричную логарифмическую) чтобы получить что-то наподобие нужного вам:

import matplotlib.pylab as plt

x = ['2022','2023','2024','2025','2026','2027','2028','2029','2030']
y = [45, 3792, 33708, 251604, 605088, 919392, 1233696, 1548000, 1862304]
y2 = [-7168, -13044, -10928, 77554, 348029, 598680, 849331, 1099982, 1351329]

plt.bar(x, y, color='b')
plt.bar(x, y2, color='r')
plt.yscale('symlog')

введите сюда описание изображения

Более "человеческую" шкалу можно сделать так:

from matplotlib import ticker

fig, ax = plt.subplots()
...
plt.yscale('symlog')
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%d"))

введите сюда описание изображения

→ Ссылка