Создание категориальной или сгруппированной гистограммы с линейным графиком вторичной оси

#python #pandas #matplotlib #plot #seaborn

#python #pandas #matplotlib #график #seaborn

Вопрос:

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

Я использовал оба, matplotlib и seaborn, и, хотя я могу использовать один или другой (разные цветные полосы / линии для каждой смены), как только я включаю другой, либо один исчезает, либо происходят другие аномалии, как показано только в одной точке графика. Я просмотрел все, и есть решения для представления одной серии данных на обоих типах диаграмм, но ни одно из них не относится к нескольким категориям или не сгруппировано для обоих.

Пример данных:

 report_date wh_id   shift   Head_Count  UTL_R
3/17/19     55  A   72  25%
3/18/19     55  A   71  10%
3/19/19     55  A   76  20%
3/20/19     55  A   59  33%
3/21/19     55  A   65  10%
3/22/19     55  A   54  20%
3/23/19     55  A   66  14%
3/17/19     55  1   11  10%
3/17/19     55  2   27  13%
3/17/19     55  3   18  25%
3/18/19     55  1   23  100%
3/18/19     55  2   16  25%
3/18/19     55  3   12  50%
3/19/19     55  1   28  10%
3/19/19     55  2   23  50%
3/19/19     55  3   14  33%
3/20/19     55  1   29  25%
3/20/19     55  2   29  25%
3/20/19     55  3   10  50%
3/21/19     55  1   17  20%
3/21/19     55  2   29  14%
3/21/19     55  3   30  17%
3/22/19     55  1   12  14%
3/22/19     55  2   10  100%
3/22/19     55  3   17  14%
3/23/19     55  1   16  10%
3/23/19     55  2   11  100%
3/23/19     55  3   13  10%
  
 tm_daily_df = pd.read_csv('fg_TM_Daily.csv')
tm_daily_df = tm_daily_df.set_index('report_date')
fig2, ax2 = plt.subplots(figsize=(12,8))
ax3 = ax2.twinx()
group_obj = tm_daily_df.groupby('shift')
g = group_obj['Head_Count'].plot(kind='bar', x='report_date',  y='Head_Count',ax=ax2,stacked=False,alpha = .2)
g = group_obj['UTL_R'].plot(kind='line',x='report_date', y='UTL_R', ax=ax3,marker='d', markersize=12)
plt.legend(tm_daily_df['shift'].unique())
  

Этот код приблизил меня к тому, что я смог получить. Обратите внимание, что даже с stacked = False они все еще сложены. Я изменил настройку на True, и ничего не изменилось.

Все, что мне нужно, это чтобы столбцы были рядом друг с другом с одинаковой цветовой схемой, представляющей сдвиг

График:

График

Комментарии:

1. Спасибо за публикацию на SO и добро пожаловать на сайт. Чтобы убедиться, что я понимаю ваш вопрос, вы пытаетесь построить head_count как столбчатую диаграмму, где каждый стах представляет сдвиг, и UTL_R как линейный график, где каждая строка представляет сдвиг?

2. Спасибо за приветствие! Я хотел, чтобы численность персонала не была загружена, так как я хочу сравнить численность персонала в каждой смене каждый день (A для всего сайта / всего количества смен). Каждый день необходимо иметь 4 столбца рядом друг с другом, но, глядя на то, насколько чисто это выглядит, я бы не возражал, чтобы они были сложены (но не целые значения друг над другом, вместо этого больше похоже на то, что вы видите на картинке, каждый столбец находится друг над другом, начиная с y = 0), если вы можете различать каждый сдвиг в стеке, и если они имеют одинаковые цвета, назначенные сдвигам на линейном графике.

Ответ №1:

Вот два решения (сложенные и неупакованные). На основе ваших вопросов мы будем:

  • постройте Head_Count по левой оси y и UTL_R по правой оси y.
  • report_date будет нашей осью x
  • shift будет представлять оттенок нашего графика.

В сложенной версии используется pandas функция построения графиков по умолчанию, а в неупакованной версии — seaborn .

Редактировать
По вашему запросу я добавил 100%-ный сложенный график. Хотя это не совсем то, что вы спросили в комментарии, тип графика, который вы спросили, может создать некоторую путаницу при чтении (значения основаны на верхней строке стека или ширине стека). Альтернативным решением может быть использование 100%-ного сложенного графика.

Сложенный

 import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

dfg = df.set_index(['report_date', 'shift']).sort_index(level=[0,1])

fig, ax = plt.subplots(figsize=(12,6))

ax2  = ax.twinx()

dfg['Head_Count'].unstack().plot.bar(stacked=True, ax=ax, alpha=0.6)
dfg['UTL_R'].unstack().plot(kind='line', ax=ax2, marker='o', legend=None)

ax.set_title('My Graph')
plt.show()
  

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

Уложено 100%

 import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

dfg = df.set_index(['report_date', 'shift']).sort_index(level=[0,1])

# Create `Head_Count_Pct` column
for date in dfg.index.get_level_values('report_date').unique():
    for shift in dfg.loc[date, :].index.get_level_values('shift').unique():
        dfg.loc[(date, shift), 'Head_Count_Pct'] = dfg.loc[(date, shift), 'Head_Count'].sum() / dfg.loc[(date, 'A'), 'Head_Count'].sum()

fig, ax = plt.subplots(figsize=(12,6))

ax2  = ax.twinx()
pal = sns.color_palette("Set1")

dfg[dfg.index.get_level_values('shift').isin(['1','2','3'])]['Head_Count_Pct'].unstack().plot.bar(stacked=True, ax=ax, alpha=0.5, color=pal)
dfg['UTL_R'].unstack().plot(kind='line', ax=ax2, marker='o', legend=None, color=pal)

ax.set_title('My Graph')
plt.show()
  

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

Распакованный

 import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

dfg = df.set_index(['report_date', 'shift']).sort_index(level=[0,1])

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

ax2  = ax.twinx()

sns.barplot(x=dfg.index.get_level_values('report_date'),
            y=dfg.Head_Count,
           hue=dfg.index.get_level_values('shift'), ax=ax, alpha=0.7)

sns.lineplot(x=dfg.index.get_level_values('report_date'),
            y=dfg.UTL_R,
           hue=dfg.index.get_level_values('shift'), ax=ax2, marker='o', legend=None)

ax.set_title('My Graph')
plt.show()
  

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


РЕДАКТИРОВАТЬ # 2

Вот график, который вы просили во второй раз (сложенный, но стек n 1 не начинается там, где заканчивается стек n).

Это немного сложнее, поскольку нам нужно сделать несколько вещей: — нам нужно вручную назначить наш цвет нашему shift в нашем df — после того, как мы назначим наши цвета, мы пройдемся по каждому диапазону дат и 1) отсортируем или Head_Count значения по убыванию (так, чтобы наш самый большой пакет был сзади, когда мы строим график), и 2) построим данные и назначим цвет каждому stacj — Затем мы можем создать нашу вторую ось y и построить наши UTL_R значения — Затем нам нужно назначить правильный цвет для наши легендарные этикетки

 import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

def assignColor(shift):
    if shift == 'A':
        return 'R'
    if shift == '1':
        return 'B'
    if shift == '2':
        return 'G'
    if shift == '3':
        return 'Y'

# map a color to a shift
df['color'] = df['shift'].apply(assignColor)

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

# plot our Head_Count values
for date in df.report_date.unique():
    d = df[df.report_date == date].sort_values(by='Head_Count', ascending=False)
    y = d.Head_Count.values
    x = date
    color = d.color
    b = plt.bar(x,y, color=color)

# Plot our UTL_R values
ax2 = ax.twinx()    

sns.lineplot(x=df.report_date, y=df.UTL_R, hue=df['shift'], marker='o', legend=None)

# Assign the color label color to our legend
leg = ax.legend(labels=df['shift'].unique(), loc=1)

legend_maping = dict()

for shift in df['shift'].unique():
    legend_maping[shift] = df[df['shift'] == shift].color.unique()[0]

i = 0
for leg_lab in leg.texts:
    leg.legendHandles[i].set_color(legend_maping[leg_lab.get_text()])
    i  = 1
  

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

Комментарии:

1. Эта неупакованная диаграмма — это именно то, что я хотел сделать, когда начинал. Итак, есть ли в любом случае, что столбцы могут быть «поверх» друг друга, но не сложены? В примере графического изображения, который я опубликовал, столбцы наложены друг на друга, но они не сложены. Так это выглядит намного чище. Возможно ли это?

2. @ricsilo, я не уверен, что этот тип графика будет наиболее репрезентативным для значения, поскольку читателю придется разбирать, является ли значение, представленное стеком, верхним стеком или шириной стека. Для вашей конкретной проблемы неупакованный график кажется наиболее подходящим. Хотя, если вы хотите пойти по сложенному маршруту, вы могли бы использовать 100% сложенный график.

3.Я делаю меньше, и значения в 100% сложенном графике не будут точными (поскольку A представляет 100% сдвига). Хотя, судя по вашим данным, это, похоже, не так (т. Е. 3/17/2019 A=72 , Но 1 2 3 = 58 . В любом случае, я добавил код для вас, чтобы показать, как вы можете его создать — если вам это когда-нибудь понадобится.

4. Итак, если это 100% с накоплением, есть ли способ, которым я могу умножить соотношение на значение A, чтобы вместо 0 -1 значения были 0-80 (или независимо от численности персонала)? И, хороший момент, но предоставленные мной данные были рандомизированы, чтобы не предоставлять личные данные. Я понимаю, что это не складывается, но реальные данные складываются….

5. Именно то, что я хотел! Я также искал решение, использующее c = df[‘color’].apply (лямбда x: цвета [x] путем сопоставления столбца цвета из shift и его применения.

Ответ №2:

Как насчет этого?

 tm_daily_df['UTL_R'] = tm_daily_df['UTL_R'].str.replace('%', '').astype('float') / 100
pivoted = tm_daily_df.pivot_table(values=['Head_Count', 'UTL_R'], 
                                  index='report_date', 
                                  columns='shift')
pivoted

#             Head_Count             UTL_R
# shift                1   2   3   A     1     2     3     A
# report_date
# 3/17/19             11  27  18  72  0.10  0.13  0.25  0.25
# 3/18/19             23  16  12  71  1.00  0.25  0.50  0.10
# 3/19/19             28  23  14  76  0.10  0.50  0.33  0.20
# 3/20/19             29  29  10  59  0.25  0.25  0.50  0.33
# 3/21/19             17  29  30  65  0.20  0.14  0.17  0.10
# 3/22/19             12  10  17  54  0.14  1.00  0.14  0.20
# 3/23/19             16  11  13  66  0.10  1.00  0.10  0.14

fig, ax = plt.subplots()
pivoted['Head_Count'].plot.bar(ax=ax)
pivoted['UTL_R'].plot.line(ax=ax, legend=False, secondary_y=True, marker='D')
ax.legend(loc='upper left', title='shift')