Create a stacked graph ** that corresponds to both the positive and negative directions as shown below.

Created and confirmed to work with Google Colab.
python     version 3.6.9
numpy      version 1.18.5
pandas     version 1.1.2
matplotlib version 3.2.2
Allows Japanese to be used in matplotlib graphs.
python
!pip install japanize_matplotlib
Load the library.
python
%reset -f
import sys
import pandas as pd
import numpy as np
import japanize_matplotlib
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patheffects as pe 
pv = '.'.join([ str(v) for v in sys.version_info[:3] ])
print(f'python     version {pv}')
print(f'numpy      version {np.__version__}')
print(f'pandas     version {pd.__version__}')
print(f'matplotlib version {matplotlib.__version__}')
Prepare sample data.
python
df = pd.DataFrame()
df['Classification']=['Middle school students','High school student','College student']
df['Agree']=[10,20,30]
df['If anything, I agree']=[45,50,45]
df['If anything opposite']=[30,20,20]
df['Opposition']=[15,10,5]
display(df)

For this data, "agree" and "somewhat agree" are piled up on the left side, and "somewhat disagree" and "disagree" are piled up on the right side.
python
def draw(df, y_column,x_columns,colors,x_range):
  left_columns ,right_columns = x_columns
  left_colors,right_colors = colors
  #Create a 1-by-2 graph
  fig,ax = plt.subplots(nrows=1, ncols=2, figsize=(6,3), facecolor='white',sharey='row',dpi=150)
  #Erase the border
  for a in ax:
    for x in ['top','bottom','left','right']:
      a.spines[x].set_visible(False) 
      a.tick_params(axis='y',left=False) 
  #Graph on the left
  acc = np.zeros(len(df))
  for colum,color in reversed(list(zip(left_columns ,left_colors))):
    s = df[colum]
    ax[0].barh(df[y_column],s,left=acc,color=color,label=colum)
    for i in range(len(df)):
      t = ax[0].text(acc[i]+s[i]/2,i,f'{s[i]}%', ha='center',va='center')
      t.set_path_effects([pe.Stroke(linewidth=3, foreground='white'), pe.Normal()])
    acc+=s
  #Graph on the right
  acc = np.zeros(len(df))
  for colum,color in zip(right_columns,right_colors):
    s = df[colum]
    ax[1].barh(df[y_column],s,left=acc,color=color,label=colum)
    for i in range(len(df)):
      t = ax[1].text(acc[i]+s[i]/2,i,f'{s[i]}%', ha='center',va='center')
      t.set_path_effects([pe.Stroke(linewidth=3, foreground='white'), pe.Normal()])
    acc+=s
  #Usage Guide
  ha,la = ax[0].get_legend_handles_labels()
  ax[0].legend(reversed(ha),reversed(la),bbox_to_anchor=(0.95, 1.05), loc='lower right',ncol=len(left_columns), borderaxespad=0,frameon=False)
  ax[1].legend(bbox_to_anchor=(0.05, 1.05), loc='lower left',ncol=len(right_columns), borderaxespad=0,frameon=False)
  #Draw a line in the center
  ax[1].axvline(x=0,ymin=0,ymax=1,clip_on=False,color='black',lw=1)
  ax[0].set_xlim(x_range[0],0) #The graph on the left flips the X axis
  ax[1].set_xlim(0,x_range[1])
  fig.subplots_adjust(wspace=0.0) #Set the distance between the left and right graphs to zero
  plt.savefig('test.png')
  plt.show()
#Create a graph with sample data
draw(df,'Classification',
     [['Agree','どちらかと言えばAgree'],['If anything opposite','Opposition']],
     [['tab:blue','tab:cyan'],['tab:pink','tab:red']],[80,80])

Recommended Posts