Построение линии на вторичной оси с помощью столбчатой диаграммы с накоплением — matplotlib

#python #pandas #matplotlib

#python #панды #matplotlib

Вопрос:

Далее строится столбчатая диаграмма с накоплением, разделенная на 4 подзаголовка. Четыре подзаголовка вызываются из Area . Значения вызываются из Result . Этот столбец содержит 0 и 1. Я хочу построить общее количество этих значений для каждой отдельной комбинации в Group .

Это работает нормально, но я надеюсь использовать вторичную ось для отображения нормализованных значений в виде линейного графика. В частности, процентное соотношение единиц по сравнению с 0. На данный момент мне просто нужно подсчитать количество 0's и 1's в виде столбчатой диаграммы. Я надеюсь построить процент 1's использования вторичной оси y.

 import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({
    'Result' :[0,1,1,1,0,1,1,0,1,0,1,1,1,1,0,1],
    'Group' :[-2,-1,1,0,0,-1,-1,0,1,-1,0,1,-1,1,0,1],        
    'Area' :['North','East','South','West','North','East','South','West','North','East','South','West','North','East','South','West'],        
         })

total = df['Result'].sum()

def custom_stacked_barplot(t, sub_df, ax):

    plot_df = pd.crosstab(index = sub_df['Group'], 
                          columns = sub_df['Result'], 
                          values = sub_df['Result'], 
                          aggfunc = ['count',(lambda x: sum(x)/total*100)],
                          )

    p = plot_df.plot(kind = "bar", y = 'count',stacked = True, ax = ax, rot = 0, width = 0.6, legend = False)

    ax2=ax.twinx()

    #plot norm line
    #r = plot_df.plot(y = '<lambda>', ax = ax2, legend = False, zorder = 2, color = 'black')

    return p

g_dfs = df.groupby(['Area'])


fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(8,12))

for ax, (i,g) in zip(axes.ravel(), sorted(g_dfs)):
    custom_stacked_barplot(i, g, ax)

plt.legend(bbox_to_anchor=(1.129, 2.56))

plt.show()
  

предполагаемый вывод df для построения графика:

        count          perc           
Result     0    1        0          
Group                                
-1       1.0  2.0      0.66
 1       0.0  1.0      1.0
       count          perc           
Result     0    1        0         
Group                               
-2       1.0  0.0      0.0  
-1       0.0  1.0      1.0  
 0       1.0  0.0      0.0  
 1       0.0  1.0      1.0  
       count          perc           
Result     0    1        0         
Group                               
-1       0.0  1.0      1.0  
 0       1.0  1.0      0.5  
 1       0.0  1.0      1.0  
       count          perc            
Result     0    1        0          
Group                                
0        1.0  1.0      0.5   
1        0.0  2.0      1.0  
  

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

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

1. Я добавил новый ответ, но я не совсем уверен, что правильно понял вопрос. Дайте мне знать, если это не то, что вы искали, и я отредактирую это!

Ответ №1:

попробуйте использовать twinx()

 import matplotlib.pyplot as plt

df = pd.DataFrame({
    'Result' :[0,1,1,1,0,1,1,0,1,0,1,1,1,1,0,1],
    'Group' :[-2,-1,1,0,0,-1,-1,0,1,-1,0,1,-1,1,0,1],        
    'Area' :['North','East','South','West','North','East','South','West','North','East','South','West','North','East','South','West'],        
        })

total = df['Result'].sum()



def custom_stacked_barplot(t, sub_df, ax):

    plot_df = pd.crosstab(index = sub_df['Group'], 
                          columns=sub_df['Result'], 
                          values=sub_df['Result'], 
                          aggfunc = ['count',(lambda x: sum(x)/total*100)])
    print(plot_df)

    p = plot_df.plot(kind="bar",y='count',stacked=True, ax = ax, rot = 0, width = 0.6, legend = False)
    
    ax2=ax.twinx()
    r = plot_df.plot(kind="bar",y='<lambda>', stacked=True, ax = ax2, rot = 0, width = 0.6, legend = False)


    return p,r

g_dfs = df.groupby(['Area'])

fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(8,12))

for ax, (i,g) in zip(axes.ravel(), sorted(g_dfs)):
    custom_stacked_barplot(i, g, ax)

plt.legend(bbox_to_anchor=(1.129, 2.56))

plt.show()
# save the plot as a file
fig.savefig('two_different_y_axis_for_single_python_plot_with_twinx.jpg',
            format='jpeg',
            dpi=100,
            bbox_inches='tight')


plt.show()
  

Результат выглядит примерно так :
введите описание изображения здесь

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

1. Спасибо, но я получаю сообщение об ошибке в перекрестной таблице TypeError: crosstab() got an unexpected keyword argument 'margin_name'

2. Спасибо. Может ли вторичный быть линейным графиком вместо диаграммы с накоплением?

Ответ №2:

Хорошо, итак, я тоже попробовал это:

 import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

df = pd.DataFrame({
    'Result' :[0,1,1,1,0,1,1,0,1,0,1,1,1,1,0,1],
    'Group' :[-2,-1,1,0,0,-1,-1,0,1,-1,0,1,-1,1,0,1],        
    'Area' :['North','East','South','West','North','East','South','West','North','East','South','West','North','East','South','West'],        
         })

## iterate over unique areas 
unique_areas = df['Area'].unique()

fig, axes = plt.subplots(nrows=len(unique_areas), ncols=1, figsize=(8,12))
twin_axes=[]

for i,key in enumerate(unique_areas):
    # print(f"== {key} ==") #<- uncomment this line to debug
    
    ## first, filter the df by 'Area'
    area_df = df[(df['Area']==key)]
    
    ## and do the crosstab:
    ct_df = pd.crosstab(index=area_df['Group'],
                        columns=area_df['Result'],
                       )
    ## to add the 'count' label you wanted to the dataframe multiindex:
    ct_df = pd.concat({'count': ct_df}, names=['type'],axis=1)
    
    ## now iterate over the unique 'Groups' in the index ...
    for ix in ct_df.index:
        sub_df = ct_df.loc[ix,'count']
        
        ## ... and calculate the contribution of each Result
        #      which is equal to '1' (ct_df.loc[ix,1])
        #      in the total for this group (ct_df.loc[ix].sum())
        ct_df.loc[ix,'perc'] = sub_df.loc[1]/sub_df.sum()

    # print(ct_df) #<- uncomment this line to debug
    
    ## add your stacked bar plot
    bar = ct_df.plot(kind = "bar", y = 'count',stacked = True, ax = axes[i], rot = 0, width = 0.6, legend = False)
    
    ## keep the twin_axes in a separate list
    twin_axes.append(axes[i].twinx())
    
    ## generate the "correct" x values that match the bar plot locations 
    #  (i.e. use [0,1,2,3] instead of [-2,-1,0,1] )
    xs=np.arange(0,len(ct_df),1)
    
    ## and plot the percentages as a function this new x range as a black line:
    twin_axes[i].plot(xs,ct_df['perc'],zorder=2,color='black')

    ## optional:    
    #  using these 'xs' you could also e.g. add some labels for the contained groups:
    for x in xs:
        twin_axes[i].text(x,1.15,ct_df.index[x],color="b")
    #  make some nice changes to the formatting of the plots
    for a in [twin_axes]:
        # a[i].set_xlim(-1,4)
        a[i].set_ylim(0,1.1)
    
plt.show()         
  

В основном, вместо того, чтобы пытаться использовать pd.crosstab для выполнения всего, я бы предложил выполнить несколько быстрых и простых циклов for по уникальным областям, чтобы получить желаемую структуру df.

Каждый зависящий от группы фрейм данных теперь выглядит так, как вы хотели:

 type   count    perc
Result     0  1     
Group               
-2         1  0  0.0
-1         0  1  1.0
 0         1  0  0.0
 1         0  1  1.0
type   count         perc
Result     0  1          
Group                    
-1         1  2  0.666667
 1         0  1  1.000000
type   count    perc
Result     0  1     
Group               
-1         0  1  1.0
 0         1  1  0.5
 1         0  1  1.0
type   count    perc
Result     0  1     
Group               
0          1  1  0.5
1          0  2  1.0
  

И график теперь выглядит так:

Объединенный столбчатый и линейный график

Ответ №3:

Редактировать:

 def create_plot(ax, x, y1, y2, y3):
    ax1 = ax
    ax2 = ax1.twinx()

    ax1.bar(x, y1)
    ax1.bar(x, y2, bottom=y1)

    ax2.plot(x, y3, c="C3")

fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(8,12))
for ax in axes:
    create_plot(ax, (1,2,3,4), (1,2,3,4), (7,5,3,1), (1,4,2,3))
plt.show()
  

Четыре вложенных графика, расположенных вертикально, с перекрывающимися столбчатыми и линейными графиками


(Старый пост ниже)

Делает что-то вроде

 def create_plot(x, y1, y2, y3):

    fig = plt.figure()
    ax1 = fig.gca()
    ax2 = ax1.twinx()


    ax1.bar(x, y1)
    ax1.bar(x, y2, bottom=y1)

    ax2.plot(x, y3, c="C3")
    return fig

fig = create_plot((1,2,3,4), (1,2,3,4), (7,5,3,1), (1,4,2,3))
plt.show()
  

соответствует тому, что вам нужно? Это дает мне:

График, созданный с помощью приведенного выше кода. Столбчатый график с наложением линейного графика

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

1. Спасибо. Это правильный вывод, но мне нужен для подзаголовков. По одному для каждого элемента в Group

2. Я обновил код. Я предполагаю, что есть способ сделать все это с помощью метода построения фрейма данных pandas, и я могу изучить это, если хотите, но в целом я считаю, что проще использовать matplotlib напрямую.

3. Спасибо. Это хорошо. Я могу обновить входные данные с помощью этого.