Seaborn Align twinx and x Axis - python

I am trying to align X axis with its twin but I'm not finding a way to do it.
Here is my code
# Initialize the figure
plt.figure(figsize=(16, 10))
# Adding a title
plt.title(f'Client Retention Quarters: Monthly Cohorts', fontsize = 14)
# Creating the heatmap
sns.heatmap(retention, annot = True,vmin = 0, vmax =30,cmap="flare", fmt='g')
plt.ylabel('Cohort Quarter')
plt.xlabel('')
plt.yticks( rotation='360')
#Twinx
ax2 = plt.twiny()
ax2.set_xticks(range(0,len(x2)))
ax2.set_xticklabels(labels=x2)
ax2.spines['top'].set_position(('axes', -0.10))
plt.show()
And here is the output:
I want to align the percentages with the x ticks.
Is it possible?

You can use the below updated code. See if this works. Note that I have used random data for retention and x2. Basically, the main change it to get the xlim()s for both axes and then adjust it (see lambda f) so that the ticks align. Finally use set_major_locator() to fix the points. Hope this is what you are looking for...
retention = np.random.rand(10, 12) ##My random data
# Initialize the figure
plt.figure(figsize=(16, 10))
# Adding a title
plt.title(f'Client Retention Quarters: Monthly Cohorts', fontsize = 14)
# Creating the heatmap
ax=sns.heatmap(retention, annot = True,vmin = 0, vmax =30,cmap="flare", fmt='g') ## Note I am assigning to ax
plt.ylabel('Cohort Quarter')
plt.xlabel('')
plt.yticks( rotation='360')
x2 = np.around(np.linspace(1, 25, 12),2)
#Twinx
ax2 = ax.twiny()
#ax2.set_xticks(range(0,len(x2))) ## Commented as not required
#ax2.set_xticklabels(labels=x2) ## Commented as not required
## New code here ##
import matplotlib.ticker
l = ax.get_xlim()
l2 = ax2.get_xlim()
f = lambda y : l2[0]+(y-l[0])/(l[1]-l[0])*(l2[1]-l2[0]) ##Add delta to each tick
ticks = f(ax.get_xticks())
ax2.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(ticks)) ##Set the ticks
ax2.spines['top'].set_position(('axes', -0.10))
plt.show()

Related

Generate multi-line plot using cmap from pandas DataFrame with discrete legends for each line series

I have a dataframe such as below and I am trying to use the example plot code (given below) to generate a similar style line series plot for my dataframe.
df = pd.DataFrame({'x': np.linspace(0, 10, 100),
'run0_Y': np.sin(np.linspace(0, 10, 100)),
'run1_Y': np.cos(np.linspace(0, 10, 100)),
'run2_Y': np.cos(np.linspace(0, 10, 100)),
'run3_Y': np.arctan(np.linspace(0, 10, 100))
})
I would like to generate a plot like below (see code) but I want to
replace the colorbar with my dataframe headings ['run0_Y' ...
'run3_Y'] as legends for each color.
'run0_Y' and 'run1_Y' belongs to the same color but differentiated
with solid line '-k' and dashed line '--k'
I am stuck as to how to plot the line series from my dataframe and
associate each dataframe column to its column header in the colorbar
as legend.
Example plotting code:
import numpy as np
import matplotlib.pyplot as plt
# Use the spectral colormap for examples
cmap = plt.cm.Spectral
# Generate some fake data
N = 100
nlines = 10
x = np.linspace(-np.pi, np.pi, N)
print('x: \n', x)
y = np.linspace(-np.pi, np.pi, nlines)
print('y: \n', y)
# Use np.newaxis to create [N,1] and [1,Nlines] x and y arrays
# Then broadcasting to generate Z with shape [N,Nlines]
z = np.sin(x[:,np.newaxis] + y[np.newaxis,:]/4)
print('z \n', z)
# Use 0-1 values to generate the colors with the linspace method
line_colors = cmap(np.linspace(0,1,nlines))
# We have to generate our own axis to put the colorbar in
# otherwise it "steals" space from the current axis. Please
# let me know if anyone has found another way around this,
# because the custom axes generation is the only way I've
# figured out.
from matplotlib.gridspec import GridSpec
# fig = plt.figure(figsize = (12,6))
# nrows = 2
# gs = GridSpec(nrows,2,width_ratios=[50,1])
# ax = [plt.subplot(gs[i,0]) for i in range(nrows)]
# cbax1 = plt.subplot(gs[1,1])
# # First, plot lines w/ legend
# a = ax[0]
# a.set_title('Labeling with a legend')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=3,label='{:4.1f}'.format(y[i]))
# leg = a.legend(loc='center left', bbox_to_anchor=(1, 0.5), ncol=2)
# leg.set_title('Y')
# # Next, plot with colorbar
# a = ax[1]
# a.set_title('Labeling with a "continuous" colorbar')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=3,label='{:3.1f}'.format(y[i]))
# # Generate fake ScalarMappable for colorbar
# sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=y[0],vmax=y[-1]))
# sm.set_array([]) # You have to set a dummy-array for this to work...
# cbar = plt.colorbar(sm, cax=cbax1)
# cbar.set_label('Y')
# cbar.set_ticks(y)
# cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# # Moves colorbar closer to main axis by adjusting width-spacing between subplot axes.
# fig.subplots_adjust(wspace=0.05, hspace=0.4)
# # Set axis limits
# for a in ax:
# a.set_xlim(-np.pi, np.pi)
fig = plt.figure(figsize = (12,6))
nrows = 1
gs = GridSpec(nrows,2,width_ratios=[50,1])
ax = [plt.subplot(gs[i,0]) for i in range(nrows)]
cbax = [plt.subplot(gs[i,1]) for i in range(nrows)]
# We'll use the same fake ScalarMappable and colormap for each example
from matplotlib.colors import ListedColormap
cmap2 = ListedColormap(line_colors)
sm = plt.cm.ScalarMappable(cmap=cmap2,
norm=plt.Normalize(vmin=y[0],vmax=y[-1]))
sm.set_array([])
# # Discrete colorbar with default spacing
# a = ax[0]
# a.set_title('Labeling with a discrete colorbar')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=2,label='{:4.1}'.format(y[i]))
# cbar = plt.colorbar(sm, cax=cbax[0])
# cbar.set_label('Y')
# cbar.set_ticks(y)
# cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# Discrete colorbar with centered ticks
# a = ax[1]
a = ax[0]
a.set_title('Labeling with a discrete colorbar & centered labels')
for i in range(nlines):
a.plot(x, z[:,i], c=line_colors[i],lw=2,label='{:4.1}'.format(y[i]))
# Generate custom bounds so that ticks are centered
dy = y[1]-y[0]
ybounds = np.linspace(y[0]-dy/2., y[-1]+dy/2., nlines+1)
cbar = plt.colorbar(sm, cax=cbax[0], boundaries=ybounds)
cbar.set_label('Y')
cbar.set_ticks(y)
cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# Set axis limits
for a in ax:
a.set_xlim(-np.pi, np.pi)
# Moves colorbar closer to main axis by adjusting width-spacing between subplot axes.
fig.subplots_adjust(wspace=0.05, hspace=0.4)
plt.show()
source: https://pyhogs.github.io/colormap-examples.html

What is wrong with my multiple line graph plotting?

I am attempting to plot multiple line graphs in a graph table itself. However, I run into an error that mentioned:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Not only this happened but my legend tables of the 3 lines don't merge together and my X-axis does not show the months but random numbers from my dataframe. Here is my code and graph result to look through.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_excel (r'C:\Users\admin\Desktop\Question Folder\Sales of top 30 customers.xlsx')
#Refine and adjust the dataframe for suitable manipulation
df = df.drop('Unnamed: 0', axis = 1)
df = df.iloc[2: , :]
row_detail = df.head(1).values.tolist()
row_detail = row_detail[0]
a = df.iloc[-3:, :].values.tolist()
a = a[0]
df.columns = row_detail
df = df.iloc[1:, :]
print(df) # This is for checking purpose
# This creates a dataframe needed for the practice
df1 = df.iloc[:3]
# This is to plot a line graph from df1
df_chosen = df1
a = 0
# Turning data row of a customer into a list
data_row_1 = df_chosen.iloc[a].values.tolist()
data_row_2 = df_chosen.iloc[a + 1].values.tolist()
data_row_3 = df_chosen.iloc[a + 2].values.tolist()
date = data_row_1[1:]
cus_1 = data_row_1[0]
cus_2 = data_row_2[0]
cus_3 = data_row_3[0]
y1 = data_row_1[1:]
y2 = data_row_2[1:]
y3 = data_row_3[1:]
x = np.arange(len(date)) # the label locations
width = 0.60 # the width of the bars
fig, ax = plt.subplots()
# Increase size of plot in jupyter
plt.rcParams["figure.figsize"] = (20,15)
plt.rcParams.update({'font.size':25})
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_xlabel('Months', fontsize=30)
ax.set_ylabel('Sales', fontsize=30)
ax.set_title('Monthly Sales from ' + cus_1 +", " + cus_2+ " and " + cus_3, fontsize=30)
ax.set_xticks(x, date)
ax.set_ylim(bottom = 0, top = 1000)
legend1 = plt.legend(())
ax.legend(loc='best', fontsize=30)
plt.grid(True)
# set up the 1st line graph
ax.plot(x, y1, "r", label = cus_1, marker='x')
#ax.set_yticks(
ax.grid(True) # turn on grid #1
ax.set_ylim(bottom = 0, top = 1000)
ax.legend(loc='upper left', fontsize=25)
ax2 = ax.twinx()
ax2.plot(x, y2, "b", label= cus_2, marker='x')
ax2.set_yticks([])
ax2.grid(False) # turn off grid #2
ax2.set_ylim(bottom = 0, top = 10000)
ax2.legend(loc='upper left', fontsize=25)
ax3 = ax2.twinx()
ax3.plot(x, y3, "g", label= cus_3, marker='x')
ax3.set_yticks([])
ax3.grid(False) # turn off grid #2
ax3.set_ylim(bottom = 0, top = 10000)
ax3.legend(loc='upper left', fontsize=25)
I just need to understand and know the solutions for the following:
Why is the X-axis not showing the months' names?
Why is the 3 separate legend tables not connected together?
How do I avoid the 'No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.' error warning?
Hope to receive a favorable reply soon. :)
Edit notice: Here is the dataframe used for this problem:

Colorbar line plot of multiple lines according to a third variable value

Dataset: I have a series (n = 30) of X (wavelength) and Y (reflectance) data, each associated with a unique value Z (age). Z values are stored as a separate ordered list.
Goal: I am trying to create a series of line plots which display each of the 30 datasets together, where each line is appropriately colored according their Z value (age). I am hoping for weighted colorization depending on the Z value, and an associated colorbar() or similar.
Attempts: I tried manipulating rcParams to do this by iterating through a color-scheme per plot [i], but the colors are not weighted properly to the Z value. See example figure. I think my issue is similar to this question here.
I feel like this shouldn't be so hard and that I am missing something obvious!
#plot
target_x = nm_names
target_y = data_plot
target_names = ages
N = len(target_y) # number of objects to plot i.e. color cycle count
plt.rcParams["figure.figsize"] = [16,7] # fig size
plt.rcParams["axes.prop_cycle"] = plt.cycler("color", plt.cm.PiYG(np.linspace(0,1,N))) # colors to cycle through, choose default like 'viridis' or 'PiYG'
fig, ax = plt.subplots()
for i in range(N):
ax.plot(target_x, target_y.iloc[i], label = target_names[i]) # for i in range of objects, plot x,y
#axes
plt.xticks(fontsize = 10, rotation=70, size = 8)
ax.xaxis.set_major_locator(ticker.MultipleLocator(50))
plt.xlabel('Wavelength (nm)', fontsize = 14)
plt.yticks(fontsize = 12)
plt.ylabel('Normalized Relative Reflectance', fontsize = 13)
plt.title("Spectral Profile", size = 14)
plt.title
plt.xlim(375,2500)
# legend location
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.1,
box.width, box.height * .9])
ax.legend(loc='lower left', bbox_to_anchor=(1, 0),
fancybox=True, shadow=True, ncol=1, title = 'Age (ky)') # Put a legend below current axis
plt.rcdefaults() # reset global plt parameters, IMPORTANT!
plt.show()
My plot, where 'age' is the 'Z' value

Add a Right Yticks To a Plot

I am trying to make a plot of sort, this is my code and the output:
ticks = [3500, 5000]
labels = ["\u0332P", "P\u0305"]
plt.title("Nilai Premi Optimal \n dengan Batasan")
plt.xlabel("$\it{Bargaining Power}$ \u03BB")
plt.plot(xlamda, PsiBLamda, color = "red",linestyle='dashed',label = "$\u03C8_{B} (I^*(X))$")
plt.plot(xlamda, PsiSLamda, color = "blue",linestyle='dashed', label = "$\u03C8_{S} (I^*(X))$")
plt.legend(loc="upper left")
plt.plot(xlamda, PLamda, color = "black")
plt.xlim([0, 1])
plt.ylim([3500, 7000])
plt.show()
The plot output is correct, however I want to add a tick on the right y axis at the 5000 point with the label P. Here is an example:
How do I code that? Thank you
Check out secondary axes:
ticks = [3500, 5000]
labels = ["\u0332P", "P\u0305"]
fig, ax = plt.subplots() # need the axis object
plt.title("Nilai Premi Optimal \n dengan Batasan")
plt.xlabel("$\it{Bargaining Power}$ \u03BB")
plt.plot(xlamda, PsiBLamda, color = "red",linestyle='dashed',label = "$\u03C8_{B} (I^*(X))$")
plt.plot(xlamda, PsiSLamda, color = "blue",linestyle='dashed', label = "$\u03C8_{S} (I^*(X))$")
plt.legend(loc="upper left")
plt.plot(xlamda, PLamda, color = "black")
plt.xlim([0, 1])
plt.ylim([3500, 7000])
rightax = ax.secondary_yaxis('right') # create secondary axis on the right
rightax.set_yticks(ticks) # set tick locations
rightax.set_yticklabels(labels) # set tick labels
plt.show()

Plotting a plot with an additional y axis on the right and an additional x axis on the top, linked to the bottom one

I am trying to make a figure that has two plots, that share the same x axis on the bottom, one is linked to the left y axis, the other to the right y axis, and also have the top x-axis, which is a function of the bottom x-axis (current divided by area). Basically what I would like to have in the end is something like the attached figure on the left.
So far I can only make the plots with the left and right y axis, but I cannot find the right way to also include the top x-axis. I have run out of ideas, and I would like to request you help and suggestions on how to deal with this.
This is what I have tried so far:
# Open and Plot Data
fname = folder + r'/' +f
#print(fname)
vect = np.loadtxt(fname, delimiter=' ')
current = vect[:,0]
voltage = vect[:,1]
power = vect[:,2]
current_density=current/area1/1000 #in kA/cm^2
fig,ax1=plt.subplots()
ax1.plot(current,voltage)
#l = ax2.plot(current_density,voltage)
#l.set_visible(False)
#ax1.grid(True) #add a grid to the LIV
#ax2 = ax1.twinx()
ax2= ax1.twinx()
#ax2=ax1.twiny()
ax2.plot(current, power)
The right axis would be a twin axes, using the same x axis, but a different y axis as the original one.
The top axis would be a secondary axis, being linked to the original x axis by a functional dependence.
In total:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax_right = ax.twinx()
area = 0.226
density = lambda current: current / area / 1000
current = lambda density: density * area * 1000
ax_top = ax.secondary_xaxis("top", functions=(density, current))
ax.plot([0, 250, 565], [0,8,12], label="Voltage")
ax_right.plot([0, 300, 565], [0, 0.3, 40], label="Power", color="C3")
ax.set_xlabel("Current [mA]")
ax.set_ylabel("Voltage [V]")
ax_right.set_ylabel("Power [mW]", color="C3")
ax_top.set_xlabel("Density [kA/cm${}^2$]", color="C1")
ax_right.tick_params(axis="y", color="C3", labelcolor="C3")
ax_right.spines["right"].set_color("C3")
ax_top.set_color("C1")
ax.spines["top"].set_color("C1")
ax_right.spines["top"].set_color("C1")
plt.show()
IIUC, you can do something like this:
fig, axes = plt.subplots(1,2)
# set up clones
axes_cp = []
for i in range(2):
ax = fig.add_subplot(1,2,i+1)
ax.set_alpha(0)
ax.set_facecolor('none')
ax.xaxis.tick_top()
ax.yaxis.tick_right()
axes_cp.append(ax)
# plot
axes[0].plot([0,1], [0,1])
axes_cp[0].plot([0,100], [100,0], color='r')
x = np.linspace(0,1,100)
axes[1].plot(x, x**2)
axes_cp[1].plot(x, np.sqrt(x), color='r')
fig.tight_layout()
Output:

Categories