【问题标题】:Plot line on secondary axis with stacked bar chart - matplotlib使用堆积条形图在辅助轴上绘制线 - matplotlib
【发布时间】:2020-12-24 07:45:36
【问题描述】:

下面绘制了一个堆积条形图,分为 4 个子图。从Area 调用四个子图。这些值是从Result 调用的。此列包含 0 和 1。我想为Group 中的每个不同组合绘制这些值的总数。

这很好用,但我希望使用辅助轴将标准化值显示为线图。具体来说,1 与 0 的百分比。目前,我只需要将0's1's 的总数计算为条形图。我希望使用辅助 y 轴绘制 1's 的百分比。

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  

【问题讨论】:

  • 我添加了一个新答案,但我不完全确定我完全理解了这个问题。如果不是您要找的,请告诉我,我会修改它!

标签: python pandas matplotlib


【解决方案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()

输出看起来像:

【讨论】:

  • 谢谢,但我在交叉表上遇到错误TypeError: crosstab() got an unexpected keyword argument 'margin_name'
  • 谢谢。次级图可以是折线图而不是堆积图吗?
【解决方案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 来做所有事情,而是在独特区域上做一些快速简单的循环,以获得你想要的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()
    

    满足你的需要?这给了我:

    【讨论】:

    • 谢谢。这是正确的输出,但我需要子图。 Group中的每个项目一个
    • 我更新了代码。我猜有一种方法可以通过 pandas 数据框绘图方法来做到这一点,如果你愿意,我可以研究一下,但总的来说,我发现直接使用 matplotlib 更容易。
    • 谢谢。这很好。我可以用这个更新输入数据。
    猜你喜欢
    • 2018-03-10
    • 2018-11-26
    • 1970-01-01
    • 2019-05-24
    • 2019-09-27
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多