Problems rotating xtick labels when using twinx - python

I have problems with the rotation of my X-axis, I have tried to do the rotation the output plot without errors, but I do not have the results.
# Import Data
#df = pd.read_csv("https://github.com/selva86/datasets/raw/master/economics.csv")
x = total_test["Dia"].values[:]; y1 = total_test["Confirmados"].values[:]; y2 = total_test["Fallecidos"].values[:]
# Plot Line1 (Left Y Axis)
fig, ax1 = plt.subplots(1,1,figsize=(10,8), dpi= 200)
ax1.plot(x, y1,'g^', color='tab:red')
# Plot Line2 (Right Y Axis)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
ax2.plot(x, y2,'bs', color='tab:blue')
# Just Decorations!! -------------------
# ax1 (left y axis)
ax1.set_xlabel('Dias', fontsize=10)
ax1.set_ylabel('Personas Confirmadas', color='tab:red', fontsize=20)
ax1.tick_params(axis='y', rotation=0, labelcolor='tab:red' )
# ax2 (right Y axis)
ax2.set_ylabel("Personas Fallecidas", color='tab:blue', fontsize=20)
ax2.tick_params(axis='y', rotation=0, labelcolor='tab:blue')
ax2.set_title("Personas Confirmadas y Fallecidas por Covid-19 Peru", fontsize=15)
#ax2.set_xticks(x)
ax2.set_xticklabels(x[::],fontsize=10,rotation=90)
plt.show()

Any commands for the xaxis need to occur before ax2.
Verify date is in a datetime format and set as the index.
import pandas as pd
import matplotlib.pyplot as plt
# read data
df = pd.read_csv("https://github.com/selva86/datasets/raw/master/economics.csv")
# verify the date column is a datetime format and set as index
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
#plot
# create figure
fig, ax1 = plt.subplots(1, 1, figsize=(10,8))
# 1st plot
ax1.plot(df['pop'], color='tab:red')
# set xticks rotation before creating ax2
plt.xticks(rotation=90)
# 2nd plot (Right Y Axis)
ax2 = ax1.twinx() # create the 'twin' axis on the right
ax2.plot(df['unemploy'], color='tab:blue')
plt.show()
Plot directly with pandas.DataFrame.plot
# load data
df = pd.read_csv("https://github.com/selva86/datasets/raw/master/economics.csv", parse_dates=True, index_col=[0])
# plot and rotate the tick labels with rot= in the first plot call
ax = df.plot(y='pop', color='tab:red', figsize=(10,8), rot=90)
ax2 = ax.twinx()
df.plot(y='unemploy', color='tab:blue', ax=ax2)
ax2.legend(loc='upper right')

Related

Combine Binned barplot with lineplot

I'd like to represent two datasets on the same plot, one as a line as one as a binned barplot. I can do each individually:
tobar = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
tobar["bins"] = pd.qcut(tobar.index, 20)
bp = sns.barplot(data=tobar, x="bins", y="value")
toline = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
lp = sns.lineplot(data=toline, x=toline.index, y="value")
But when I try to combine them, of course the x axis gets messed up:
fig, ax = plt.subplots()
ax2 = ax.twinx()
bp = sns.barplot(data=tobar, x="bins", y="value", ax=ax)
lp = sns.lineplot(data=toline, x=toline.index, y="value", ax=ax2)
bp.set(xlabel=None)
I also can't seem to get rid of the bin labels.
How can I get these two informations on the one plot?
This answer explains why it's better to plot the bars with matplotlib.axes.Axes.bar instead of sns.barplot or pandas.DataFrame.bar.
In short, the xtick locations correspond to the actual numeric value of the label, whereas the xticks for seaborn and pandas are 0 indexed, and don't correspond to the numeric value.
This answer shows how to add bar labels.
ax2 = ax.twinx() can be used for the line plot if needed
Works the same if the line plot is different data.
Tested in python 3.11, pandas 1.5.2, matplotlib 3.6.2, seaborn 0.12.1
Imports and DataFrame
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
# test data
np.random.seed(2022)
df = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
# create the bins
df["bins"] = pd.qcut(df.index, 20)
# add a column for the mid point of the interval
df['mid'] = df.bins.apply(lambda row: row.mid.round().astype(int))
# pivot the dataframe to calculate the mean of each interval
pt = df.pivot_table(index='mid', values='value', aggfunc='mean').reset_index()
Plot 1
# create the figure
fig, ax = plt.subplots(figsize=(30, 7))
# add a horizontal line at y=0
ax.axhline(0, color='black')
# add the bar plot
ax.bar(data=pt, x='mid', height='value', width=4, alpha=0.5)
# set the labels on the xticks - if desired
ax.set_xticks(ticks=pt.mid, labels=pt.mid)
# add the intervals as labels on the bars - if desired
ax.bar_label(ax.containers[0], labels=df.bins.unique(), weight='bold')
# add the line plot
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')
Plot 2
fig, ax = plt.subplots(figsize=(30, 7))
ax.axhline(0, color='black')
ax.bar(data=pt, x='mid', height='value', width=4, alpha=0.5)
ax.set_xticks(ticks=pt.mid, labels=df.bins.unique(), rotation=45)
ax.bar_label(ax.containers[0], weight='bold')
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')
Plot 3
The bar width is the width of the interval
fig, ax = plt.subplots(figsize=(30, 7))
ax.axhline(0, color='black')
ax.bar(data=pt, x='mid', height='value', width=50, alpha=0.5, ec='k')
ax.set_xticks(ticks=pt.mid, labels=df.bins.unique(), rotation=45)
ax.bar_label(ax.containers[0], weight='bold')
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')

Seaborn / Matplotlib barplot with multiple y axis

I want to draw a barplot with 3 different y values which belong to RMSE, R2 and MAPE metrics.
My dataframe is;
DLscores = {"GRU":[293.7372606050454,0.961253983114077,86281.57826775634],
"LSTM":[285.9872902525968,0.9632715628933957,81788.73018602304],
"LSTM_Attention":[266.6285102384448,0.9680756432778241,71090.76247197246],
"TCN":[219.30770326715282,0.9784018398981137,48095.868712313546],
"Hybrid":[216.97781461699145,0.978858312741761,47079.372035965505]}
I am able to do this with linegraph. However when I change it to bar, they overlaps. My line plot code is;
# Create figure and axis #1
fig, ax1 = plt.subplots(figsize=(16,10))
# plot line chart on axis #1
p1, = ax1.plot(DLscores.columns, DLscores.iloc[1], color='blue')
ax1.set_ylabel('R2')
#ax1.set_ylim(0, 25)
#ax1.legend(['R2'], loc="upper left")
ax1.yaxis.label.set_color(p1.get_color())
ax1.yaxis.label.set_fontsize(14)
ax1.tick_params(axis='y', colors=p1.get_color(), labelsize=14)
# set up the 2nd axis
ax2 = ax1.twinx()
# plot bar chart on axis #2
p2, = ax2.plot(DLscores.columns, DLscores.iloc[0], color='green')
ax2.grid(False) # turn off grid #2
ax2.set_ylabel('RMSE')
#ax2.set_ylim(0, 90)
#ax2.legend(['RMSE'], loc="upper center")
ax2.yaxis.label.set_color(p2.get_color())
ax2.yaxis.label.set_fontsize(14)
ax2.tick_params(axis='y', colors=p2.get_color(), labelsize=14)
# set up the 3rd axis
ax3 = ax1.twinx()
# Offset the right spine of ax3. The ticks and label have already been placed on the right by twinx above.
ax3.spines.right.set_position(("axes", 1.2))
# Plot line chart on axis #3
p3, = ax3.plot(DLscores.columns, DLscores.iloc[2], color='red')
ax3.grid(False) # turn off grid #3
ax3.set_ylabel('MAPE')
#ax3.set_ylim(0, 8)
#ax3.legend(['MAPE'], loc="upper right")
ax3.yaxis.label.set_color(p3.get_color())
ax3.yaxis.label.set_fontsize(14)
ax3.tick_params(axis='y', colors=p3.get_color(), labelsize=14)
plt.show()
Output:
I also tried seaborn (I couldn't figure it out how can I merge it with "hue"), but code and result is in below:
# plot line chart on axis #1
ax1 = sns.barplot(
x=DLscores.index,
y=DLscores['RMSE'],
color='blue'
)
ax1.set_ylabel('RMSE')
#ax1.set_ylim(0, 8)
ax1.legend(['RMSE'], loc="upper left")
ax1.yaxis.label.set_color('blue')
ax1.yaxis.label.set_fontsize(14)
ax1.tick_params(axis='y', colors='blue', labelsize=14)
# set up the 2nd axis
ax2 = ax1.twinx()
# plot bar chart on axis #2
sns.barplot(
x=DLscores.index,
y=DLscores['R2'],
color='orange',
ax = ax2 # Pre-existing axes for the plot
)
ax2.grid(False) # turn off grid #2
ax2.set_ylabel('R2')
#ax2.set_ylim(0, 90)
ax2.legend(['R2'], loc="upper center")
ax2.yaxis.label.set_color('orange')
ax2.yaxis.label.set_fontsize(14)
ax2.tick_params(axis='y', colors='orange', labelsize=14)
# set up the 3rd axis
ax3 = ax1.twinx()
# Offset the right spine of ax3. The ticks and label have already been placed on the right by twinx above.
ax3.spines.right.set_position(("axes", 1.15))
# Plot line chart on axis #3
p3 = sns.barplot(
x=DLscores.index,
y=DLscores['MAPE'],
color='red',
ax = ax3 # Pre-existing axes for the plot
)
ax3.grid(False) # turn off grid #3
ax3.set_ylabel('MAPE')
#ax3.set_ylim(0, 8)
ax3.legend(['MAPE'], loc="upper right")
ax3.yaxis.label.set_color('red')
ax3.yaxis.label.set_fontsize(14)
ax3.tick_params(axis='y', colors='red', labelsize=14)
plt.show()
I assume the problem is clear.

Adjusting legend layout for multiple legends associated to one Python plot?

I am creating a Python plot from a dataframe with 3 y-axes. For each y-axis, there are multiple y-values I want to plot. All data sets for the y-axes are plotted against a shared Date x-axis.
The code looks as follows:
df = pd.read_excel (r'test.xlsx', sheet_name='test', engine='openpyxl')
fig, ax = plt.subplots()
ax3 = ax.twinx()
rspine = ax3.spines['right']
rspine.set_position(('axes', 1.15))
ax3.set_frame_on(True)
ax3.patch.set_visible(False)
fig.subplots_adjust(right=0.7)
ax.plot(df['Date'], df['Gas1'], label="Gas1", color='g')
ax.plot(df['Date'], df['Gas2'], label="Gas2", color='b')
ax.plot(df['Date'], df['Gas3'], label="Gas3", marker="o", markersize=2, color='r')
ax.set_xlabel("Date")
ax.set_ylabel("Gas Rate")
ax2 = ax.twinx()
ax2.plot(df['Date'], df['Water1'], label="Water1", color='k')
ax2.plot(df['Date'], df['Water2'], label="Water2", color='y')
ax2.set_ylabel("Water")
ax3.plot(df['Date'], df['Pressure1'], label="Pressure1")
ax3.plot(df['Date'], df['Pressure2'], label="Pressure2")
ax3.set_ylabel("Pressure")
ax.legend()
ax2.legend()
ax3.legend()
plt.show()
The problem I am having is that I want the legends to be outside of the plot, preferably on the right-hand side after the 2nd y-axis. Is this possible? Right now the legends are just overlayed on the plot and not fully visible. I have tried using bbox_to_anchor and loc functions but had no luck. Thank you!
ax.get_legend_handles_labels() collects all the legend handles and their labels. Combining those for each of the axes, a new legend can be created.
bbox_to_anchor= sets an anchor point for the legend, using axes coordinates. loc= needs to be set, to tell which point of the legend's box will get fixed by the anchor.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
df = pd.DataFrame({'Date': pd.date_range('20210401', periods=30, freq='D'),
'Gas1': np.random.randn(30).cumsum(),
'Gas2': np.random.randn(30).cumsum(),
'Gas3': np.random.randn(30).cumsum(),
'Water1': np.random.randn(30).cumsum(),
'Water2': np.random.randn(30).cumsum(),
'Pressure1': np.random.randn(30).cumsum(),
'Pressure2': np.random.randn(30).cumsum()})
fig, ax = plt.subplots()
ax3 = ax.twinx()
rspine = ax3.spines['right']
rspine.set_position(('axes', 1.15))
ax3.set_frame_on(True)
ax3.patch.set_visible(False)
fig.subplots_adjust(right=0.7)
ax.plot(df['Date'], df['Gas1'], label="Gas1", color='g')
ax.plot(df['Date'], df['Gas2'], label="Gas2", color='b')
ax.plot(df['Date'], df['Gas3'], label="Gas3", marker="o", markersize=2, color='r')
ax.set_ylabel("Gas Rate")
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
ax2 = ax.twinx()
ax2.plot(df['Date'], df['Water1'], label="Water1", color='k')
ax2.plot(df['Date'], df['Water2'], label="Water2", color='y')
ax2.set_ylabel("Water")
ax3.plot(df['Date'], df['Pressure1'], label="Pressure1")
ax3.plot(df['Date'], df['Pressure2'], label="Pressure2")
ax3.set_ylabel("Pressure")
handles1, labels1 = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
handles3, labels3 = ax3.get_legend_handles_labels()
ax.legend(handles=handles1 + handles2 + handles3,
labels=labels1 + labels2 + labels3,
bbox_to_anchor=(1.28, 1.02), loc='upper left')
plt.tight_layout()
plt.show()

Rotating 2nd y-axis tick labels in matplotlib

I need to rotate the 2nd y-axis ticklabel and add a label for this axis as well in the figure below
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.transforms as mtransforms
fig, ax1 = plt.subplots(constrained_layout=True)
x = [61,62,62,59,62,59,62,63,61,60,103,104,109,105,109,105,109,111,110,107]
y = [62,62,62,62,60,60,62,62,62,63,106,107,106,106,105,105,105,106,107,108]
ax1.plot(x,y,'b.')
x2 = [2.2,3.4,4.3,5.1,5.5,5.7]
y2 = [2.3,2.8,3.2,3.9,4.5,5.9]
ax2 = ax1.twinx().twiny()
ax2.tick_params(axis="y",labelrotation=90,direction='out',length=6, width=2, colors='r',grid_color='r', grid_alpha=0.5) #called tick_params before the plot and didn't work
ax2.plot(x2,y2,'r.')
ax2.set_xlim(0,10)
ax2.set_ylim(0,10)
ax2.set_yticklabels(['Label1', 'Label2', 'Label3'], rotation=90) #y ticklabels is not rotating
ax2.set_xlabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.set_ylabel('abc', rotation=0, fontsize=20, labelpad=20) #y label is not wroking
plt.yticks(rotation=90)
line = mlines.Line2D([0, 1], [0, 1], color='red')
transform = ax2.transAxes
line.set_transform(transform)
ax2.add_line(line)
plt.show()
This code produced the figure below
The problem is ax2.set_yticklabels and ax2.set_ylabel don't work.
I want to add a label to 2nd y-axis and rotate the tick label for that axis. Also, how to control the position of the tick mark at these axes, I want it to be at the same position of tick marks of 1st y-axis and 1st x-axis. So Label1 will shift up and 0 will shift right
Thanks
When you are instancing ax2 = ax1.twinx().twiny(), you can no longer modify the y axis. Instead, create two axes and modify accordingly. Modified code and the result is below.
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.transforms as mtransforms
fig, ax1 = plt.subplots(constrained_layout=True)
x = [61,62,62,59,62,59,62,63,61,60,103,104,109,105,109,105,109,111,110,107]
y = [62,62,62,62,60,60,62,62,62,63,106,107,106,106,105,105,105,106,107,108]
ax1.plot(x,y,'b.')
x2 = [2.2,3.4,4.3,5.1,5.5,5.7]
y2 = [2.3,2.8,3.2,3.9,4.5,5.9]
ax2 = ax1.twinx() # ax2 handles y
ax3 = ax2.twiny() # ax3 handles x
ax3.plot(x2,y2,'r.')
ax3.set_xlim(0,10)
ax2.set_ylim(0,10)
ax3.set_xlabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.set_ylabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.tick_params(axis="y",labelrotation=90,direction='out',length=6, width=2, colors='r',grid_color='r', grid_alpha=0.5)
ax2.set_yticklabels(['Label1', 'Label2', 'Label3'], rotation=-90)
plt.yticks(rotation=90)
line = mlines.Line2D([0, 1], [0, 1], color='red')
transform = ax2.transAxes
line.set_transform(transform)
ax2.add_line(line)
plt.show()
The resulting graph has all the y label/tick modifications.

matplotlib: How to remove ticks&tick values from secondary axis?

I have this code for a graph, and I do not want the values & ticks on the top and right axes.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
#Set axis labels
ax.set_xlabel('NEGATIVE')
ax.set_ylabel('HAPPY')
ax2 = ax.secondary_xaxis('top')
ax2.set_xlabel('POSITIVE')
ax2 = ax.secondary_yaxis('right')
ax2.set_ylabel('SAD')
#Remove ticks/values
ax.set_yticklabels([])
ax.set_xticklabels([])
ax.set_yticks([])
ax.set_xticks([])
ax2.set_yticklabels([])
ax2.set_xticklabels([])
ax2.set_yticks([])
ax2.set_xticks([])
#Show graph
plt.show()
it's showing it like this: image of graph
Use tick_params to manipulate the axis ticks and labels:
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
#Set axis labels
ax1.set_xlabel('NEGATIVE')
ax1.set_ylabel('HAPPY')
ax2 = ax1.secondary_xaxis('top')
ax2.set_xlabel('POSITIVE')
ax3 = ax1.secondary_yaxis('right')
ax3.set_ylabel('SAD')
#Remove ticks/values
for ax in (ax1, ax2, ax3):
ax.tick_params(left=False, labelleft=False, top=False, labeltop=False,
right=False, labelright=False, bottom=False, labelbottom=False)
#Show graph
plt.show()
A comment asked for how to only turn top and left ticks and labels off. This would be
for ax in (ax1, ax2, ax3):
ax.tick_params(top=False, labeltop=False, right=False, labelright=False)
Interesting why SecondaryAxis doesn't accept tick params, however let's use twinx and twiny:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
#Set axis labels
ax.set_xlabel('NEGATIVE')
ax.set_ylabel('HAPPY')
ax2x = ax.twiny()
ax2.set_yticks([])
ax2x.set_xlabel('POSITIVE')
ax2y = ax.twinx()
ax2y.set_ylabel('SAD')
ax2x.set_xticks([])
ax2y.set_yticks([])
#Show graph
plt.show()
Output:

Categories