Search code examples
pythonmatplotlibbar-chartsubplot

Frame and text issues for Subplots in matplotlib


I am trying to get text next to the bars in this horizontal bar plot: [1]: https://i.sstatic.net/jgcvj.png

However, the "Fullerton" name seems to be inside the red plot, but I want to move it outside it. The issue is that I would have to move ALL the text from each year the same way, and that would make some names super far right. Is there a way to manually move "Fullerton"?

  1. Additionally, I would like to be able to control which parts of the "frame" I want included. I used the code axes[1].set(frame_on=False) but that removed EVERYTHING. I would like to have an x-axis line included.

  2. Also, I would like to have the year numbers to be in the middle of the y-ticks.

  3. Lastly, I am not sure how my subplots do not list every single year value (its from 2000 to 2020). I would like it to do so, but they are only every 5 years. I have tried many methods such as set_yticks but that does not seem to work.

My code is below:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime as dt
import time
import itertools
from sklearn.linear_model import Ridge
import seaborn.apionly as sns
import numpy as np; np.random.seed(1)

Years = np.array([2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020])

S_scores = np.array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, -18, 35, 36, 37, 38, 39])

A_scores = np.array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60])



A_presidents = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U"]
S_presidents = ["AA", "BB", "CC", "DD", "EE", "FF", "GG", "HH", "II", "JJ", "KK", "LL", "MM", "NN", "OO", "PP", "QQ", "RR", "SS", "TT", "UU"]


regression_A = pd.DataFrame({
    "Years": Years,
    "A_scores": A_scores
})

lr = Ridge()
lr.fit(regression_A[['A_scores']], regression_A['Years'])
Ridge(alpha=1.0, copy_X=True, fit_intercept=True, max_iter=None,
   normalize=False, random_state=None, solver='auto', tol=0.001)

regression_S = pd.DataFrame({
    "Years": Years,
    "S_scores": S_scores
})

lr = Ridge()
lr.fit(regression_S[['S_scores']], regression_S['Years'])
Ridge(alpha=1.0, copy_X=True, fit_intercept=True, max_iter=None,
   normalize=False, random_state=None, solver='auto', tol=0.001)


fig, axes = plt.subplots(nrows=1, ncols=2, sharey=True, sharex=False, figsize= (8,8))


A = axes[0].barh(Years, A_scores, align='center', color=np.where(A_scores < 0, 'red', 'green'))
axes[0].set_xlim(-40, 80)
axes[0].plot(regression_A["A_scores"], lr.coef_*regression_A['A_scores']+lr.intercept_, color='orange')
axes[0].plot(lr.coef_*regression_A['Years']+lr.intercept_, regression_A["Years"], color='purple')
axes[0].set_xlabel("A Score")
axes[0].set(frame_on=False)  # Remove all the framing
axes[0].yaxis.set_ticks_position('right')
axes[0].vlines  

S = axes[1].barh(Years, S_scores, align='center', color=np.where(S_scores < 0, 'red', 'green'))
axes[1].set_xlim(-40, 80)
axes[1].set_ylabel("Years")
axes[1].yaxis.set_label_position("right")

axes[1].set_xlabel("S Score")
axes[1].set(frame_on=False)  # Remove all the framing


axes[1].plot(regression_S["S_scores"], lr.coef_*regression_S['S_scores']+lr.intercept_, color='orange')
axes[1].plot(lr.coef_*regression_S['Years']+lr.intercept_, regression_S["Years"], color='purple')


rects2 = A.patches

for rect, label in zip(A, A_presidents):
    width = rect.get_width() + 2
    label_y_pos = rect.get_y() + (rect.get_height() / 2)
    axes[0].text(width, label_y_pos, s=label, color='blue')#, ha="right", va="top")


rects1 = S.patches

for rect, label in zip(S, S_presidents):
    width = rect.get_width() + 2
    label_y_pos = rect.get_y() + rect.get_height() / 2
    axes[1].text(width, label_y_pos, s=label, color='blue')#, ha="right", va="top")
   
###TRENDLINE CODE###
import statsmodels.api as sm

resA = stats.theilslopes(Years, A_scores, 0.90)
lsq_resA = stats.linregress(A_scores, Years)
resS = stats.theilslopes(Years, S_scores, 0.90)
lsq_resS = stats.linregress(S_scores, Years)

axes[0].plot(A_scores, resA[1] + resA[0] * A_scores, '--', color="black", label="Thiel-Sen Regression Line")`

axes[1].plot(S_scores, resS[1] + resS[0] * S_scores, '--', color="black", label="Thiel-Sen Regression Line")

plt.tight_layout()


S_presidents is just an list with the names for each year. S_scores is also just a list with the score associated for each year.

Thank you!


Solution

  • Modification points

    1. Placement of string annotations If the value is negative and the position information is set to 0, it will be placed on the right side; if 0 is too close to the y-axis, it can be changed arbitrarily by changing the value.
    2. Graph frame control Set the frame control to lower only.
    3. Center the year tick marks Adjust the spacing of the subplots manually, and turn off the automatic adjustment of tight_layout().
    4. Set y-axis year scale from 2010 to 2020 Create a scale interval for the y-axis and set the notation for the ticks.
    fig, axes = plt.subplots(nrows=1, ncols=2, sharey=True, sharex=False, figsize= (8,8))
    fig.subplots_adjust(wspace=0.18) # update
    
    A = axes[0].barh(Years, A_scores, align='center', color=np.where(A_scores < 0, 'red', 'green'))
    axes[0].set_xlim(-40, 80)
    axes[0].plot(regression_A["A_scores"], lr.coef_*regression_A['A_scores']+lr.intercept_, color='orange')
    axes[0].plot(lr.coef_*regression_A['Years']+lr.intercept_, regression_A["Years"], color='purple')
    axes[0].set_xlabel("A Score")
    #axes[0].set(frame_on=False)  # Remove all the framing
    axes[0].yaxis.set_ticks_position('right')
    axes[0].vlines  
    axes[0].spines['top'].set_visible(False) # update
    axes[0].spines['left'].set_visible(False) # update
    axes[0].spines['right'].set_visible(False) # update
    axes[0].set_yticks(np.arange(2000,2021,1)) # update
    axes[0].set_yticklabels(np.arange(2000,2021,1)) # update
    
    S = axes[1].barh(Years, S_scores, align='center', color=np.where(S_scores < 0, 'red', 'green'))
    axes[1].set_xlim(-40, 80)
    axes[1].set_ylabel("Years")
    axes[1].yaxis.set_label_position("right")
    
    axes[1].set_xlabel("S Score")
    #axes[1].set(frame_on=False)  # Remove all the framing
    axes[1].spines['top'].set_visible(False) # update
    axes[1].spines['left'].set_visible(False) # update
    axes[1].spines['right'].set_visible(False) # update
    
    axes[1].plot(regression_S["S_scores"], lr.coef_*regression_S['S_scores']+lr.intercept_, color='orange')
    axes[1].plot(lr.coef_*regression_S['Years']+lr.intercept_, regression_S["Years"], color='purple')
    
    rects2 = A.patches
    
    for rect, label in zip(A, A_presidents):
        width = rect.get_width() + 2
        label_y_pos = rect.get_y() + (rect.get_height() / 2)
        axes[0].text(width, label_y_pos, s=label, color='blue')#, ha="right", va="top")
    
    
    rects1 = S.patches
    
    for rect, label in zip(S, S_presidents):
        width = rect.get_width() + 2
        if width < 0: # update
            width = 0 # update
        label_y_pos = rect.get_y() + rect.get_height() / 2
        axes[1].text(width, label_y_pos, s=label, color='blue')#, ha="right", va="top")
        
    #plt.tight_layout() # update
    
    plt.show()
    

    enter image description here