Matplotlib: add twin y axis without using its values in the plots - python

This is to clarify the question title. Say you have four lists of integers, with which you want to produce a scatter plot:
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
You also have a function, for simplicity f(x)=1/x, that applies to all lists, so that:
from __future__ import division
a1=[1/i for i in a]
b1=[1/i for i in b]
c1=[1/i for i in c]
d1=[1/i for i in d]
My question: how to add a second y axis, knowing that the values returned by the function range from 0.06 to 1.0, without using any of the a1, b1, c1, d1 lists in the scatter plots?
What I am saying is: if you produce the following scatter plots in the traditional way, how can you then add the second y axis based on the values of a1, b1, c1, d1, without having any series using them in the plot itself?
import matplotlib.pyplot as plt
plt.scatter(a,b,c='red',label='reds')
plt.scatter(c,d,c='blue',label='blues')
plt.legend(loc='best')
This is the scatter without the second y axis:
And this is a made up version of the same one, including the second y axis discussed so far:
NB: This question is different from this, in that I am not trying to plot with different scales. I only want to add a second axis with the relevant values.

To make sure the numbers on the new axis are in the corresponding position to their inverses:
import matplotlib.pylab as plt
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(a,b,c='red',label='reds')
ax.scatter(c,d,c='blue',label='blues')
ax.legend(loc='best')
ax.set_ylabel('Y')
# make shared y axis
axi = ax.twinx()
# set limits for shared axis
axi.set_ylim(ax.get_ylim())
# set ticks for shared axis
inverse_ticks = []
label_format = '%.3f'
for tick in ax.get_yticks():
if tick != 0:
tick = 1/tick
inverse_ticks.append(label_format % (tick,))
axi.set_yticklabels(inverse_ticks)
axi.set_ylabel('1/Y')
fig.tight_layout()
fig.show()
And you can also do it for the X axis:
# make shared x axis
xaxi = ax.twiny()
# set limits for shared axis
xaxi.set_xlim(ax.get_xlim())
# set ticks for shared axis
inverse_ticks = []
label_format = '%.3f'
for tick in ax.get_xticks():
if tick != 0:
tick = 1/tick
inverse_ticks.append(label_format % (tick,))
xaxi.set_xticklabels(inverse_ticks)
xaxi.set_xlabel('1/X')

Just make shared y axis and set desired limits and ticks for new axis like here:
import matplotlib.pylab as plt
import numpy as np
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
plt.scatter(a,b,c='red',label='reds')
plt.scatter(c,d,c='blue',label='blues')
plt.legend(loc='best')
ax = plt.gca()
# make shared y axis
ax2 = ax.twinx()
# set limits for shared axis
ax2.set_ylim([0,1])
# set ticks for shared axis
plt.yticks(np.arange(0.06, 1, 0.14))
plt.show()

Related

Multiple plots on common x axis in Matplotlib with common y-axis labeling

I have written the following minimal Python code in order to plot various functions of x on the same X-axis.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from cycler import cycler
cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
xlabel='$X$'; ylabel='$Y$'
### Set tick features
plt.tick_params(axis='both',which='major',width=2,length=10,labelsize=18)
plt.tick_params(axis='both',which='minor',width=2,length=5)
#plt.set_axis_bgcolor('grey') # Doesn't work if I uncomment!
lines = ["-","--","-.",":"]
Nlayer=4
f, axarr = plt.subplots(Nlayer, sharex=True)
for a in range(1,Nlayer+1):
X = np.linspace(0,10,100)
Y = X**a
index = a-1 + np.int((a-1)/Nlayer)
axarr[a-1].plot(X, Y, linewidth=2.0+index, color=cycle[a], linestyle = lines[index], label='Layer = {}'.format(a))
axarr[a-1].legend(loc='upper right', prop={'size':6})
#plt.legend()
# Axes labels
plt.xlabel(xlabel, fontsize=20)
plt.ylabel(ylabel, fontsize=20)
plt.show()
However, the plots don't join together on the X-axis and I failed to get a common Y-axis label. It actually labels for the last plot (see attached figure). I also get a blank plot additionally which I couldn't get rid of.
I am using Python3.
The following code will produce the expected output :
without blank plot which was created because of the two plt.tick_params calls before creating the actual fig
with the gridspec_kw argument of subplots that allows you to control the space between rows and cols of subplots environment in order to join the different layer plots
with unique and centered common ylabel using fig.text with relative positioning and rotation argument (same thing is done to xlabel to get an homogeneous final result). One may note that, it can also be done by repositioning the ylabel with ax.yaxis.set_label_coords() after an usual call like ax.set_ylabel().
import numpy as np
import matplotlib.pyplot as plt
cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
xlabel='$X$'; ylabel='$Y$'
lines = ["-","--","-.",":"]
Nlayer = 4
fig, axarr = plt.subplots(Nlayer, sharex='col',gridspec_kw={'hspace': 0, 'wspace': 0})
X = np.linspace(0,10,100)
for i,ax in enumerate(axarr):
Y = X**(i+1)
ax.plot(X, Y, linewidth=2.0+i, color=cycle[i], linestyle = lines[i], label='Layer = {}'.format(i+1))
ax.legend(loc='upper right', prop={'size':6})
with axes labels, first option :
fig.text(0.5, 0.01, xlabel, va='center')
fig.text(0.01, 0.5, ylabel, va='center', rotation='vertical')
or alternatively :
# ax is here, the one of the last Nlayer plotted, i.e. Nlayer=4
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
# change y positioning to be in the horizontal center of all Nlayer, i.e. dynamically Nlayer/2
ax.yaxis.set_label_coords(-0.1,Nlayer/2)
which gives :
I also simplified your for loop by using enumerate to have an automatic counter i when looping over axarr.

matplotlib set_major_formatter creating 2 plots

This excerpt from my code changes the value of the y axis labels from exponential to millions. Problem is it creates 2 figures. The first one is an x and y axis with no plot (and the scale of the x axis is used for the y axis as well), and then the 2nd figure is exactly what I want. It is a double bar graph.
I am guessing it has something to do with using f.plot.bar instead of plt.bar but I am not sure. I just want to get rid of the first figure than all will be well.
from matplotlib.ticker import FuncFormatter
def millions(x, pos):
'The two args are the value and tick position'
return '%1.1fM' % (x*1e-6)
formatter = FuncFormatter(millions)
fig, ax = plt.subplots()
ax = tempg.plot.bar(y=['Republican2016Votes', 'Democrat2016Votes'], rot=0,
color = ['DarkRed','Blue'])
ax.yaxis.set_major_formatter(formatter)
plt.show()

Setting font size in matplotlib plot [duplicate]

I have too many ticks on my graph and they are running into each other.
How can I reduce the number of ticks?
For example, I have ticks:
1E-6, 1E-5, 1E-4, ... 1E6, 1E7
And I only want:
1E-5, 1E-3, ... 1E5, 1E7
I've tried playing with the LogLocator, but I haven't been able to figure this out.
Alternatively, if you want to simply set the number of ticks while allowing matplotlib to position them (currently only with MaxNLocator), there is pyplot.locator_params,
pyplot.locator_params(nbins=4)
You can specify specific axis in this method as mentioned below, default is both:
# To specify the number of ticks on both or any single axes
pyplot.locator_params(axis='y', nbins=6)
pyplot.locator_params(axis='x', nbins=10)
To solve the issue of customisation and appearance of the ticks, see the Tick Locators guide on the matplotlib website
ax.xaxis.set_major_locator(plt.MaxNLocator(3))
would set the total number of ticks in the x-axis to 3, and evenly distribute them across the axis.
There is also a nice tutorial about this
If somebody still gets this page in search results:
fig, ax = plt.subplots()
plt.plot(...)
every_nth = 4
for n, label in enumerate(ax.xaxis.get_ticklabels()):
if n % every_nth != 0:
label.set_visible(False)
There's a set_ticks() function for axis objects.
in case somebody still needs it, and since nothing
here really worked for me, i came up with a very
simple way that keeps the appearance of the
generated plot "as is" while fixing the number
of ticks to exactly N:
import numpy as np
import matplotlib.pyplot as plt
f, ax = plt.subplots()
ax.plot(range(100))
ymin, ymax = ax.get_ylim()
ax.set_yticks(np.round(np.linspace(ymin, ymax, N), 2))
The solution #raphael gave is straightforward and quite helpful.
Still, the displayed tick labels will not be values sampled from the original distribution but from the indexes of the array returned by np.linspace(ymin, ymax, N).
To display N values evenly spaced from your original tick labels, use the set_yticklabels() method. Here is a snippet for the y axis, with integer labels:
import numpy as np
import matplotlib.pyplot as plt
ax = plt.gca()
ymin, ymax = ax.get_ylim()
custom_ticks = np.linspace(ymin, ymax, N, dtype=int)
ax.set_yticks(custom_ticks)
ax.set_yticklabels(custom_ticks)
If you need one tick every N=3 ticks :
N = 3 # 1 tick every 3
xticks_pos, xticks_labels = plt.xticks() # get all axis ticks
myticks = [j for i,j in enumerate(xticks_pos) if not i%N] # index of selected ticks
newlabels = [label for i,label in enumerate(xticks_labels) if not i%N]
or with fig,ax = plt.subplots() :
N = 3 # 1 tick every 3
xticks_pos = ax.get_xticks()
xticks_labels = ax.get_xticklabels()
myticks = [j for i,j in enumerate(xticks_pos) if not i%N] # index of selected ticks
newlabels = [label for i,label in enumerate(xticks_labels) if not i%N]
(obviously you can adjust the offset with (i+offset)%N).
Note that you can get uneven ticks if you wish, e.g. myticks = [1, 3, 8].
Then you can use
plt.gca().set_xticks(myticks) # set new X axis ticks
or if you want to replace labels as well
plt.xticks(myticks, newlabels) # set new X axis ticks and labels
Beware that axis limits must be set after the axis ticks.
Finally, you may wish to draw only an arbitrary set of ticks :
mylabels = ['03/2018', '09/2019', '10/2020']
plt.draw() # needed to populate xticks with actual labels
xticks_pos, xticks_labels = plt.xticks() # get all axis ticks
myticks = [i for i,j in enumerate(b) if j.get_text() in mylabels]
plt.xticks(myticks, mylabels)
(assuming mylabels is ordered ; if it is not, then sort myticks and reorder it).
xticks function auto iterates with range function
start_number = 0
end_number = len(data you have)
step_number = how many skips to make from strat to end
rotation = 90 degrees tilt will help with long ticks
plt.xticks(range(start_number,end_number,step_number),rotation=90)
if you want 10 ticks:
for y axis: ax.set_yticks(ax.get_yticks()[::len(ax.get_yticks())//10])
for x axis: ax.set_xticks(ax.get_xticks()[::len(ax.get_xticks())//10])
this simply gets your ticks and chooses every 10th of the list and sets it back to your ticks. you can change the number of ticks as you wish.
When a log scale is used the number of major ticks can be fixed with the following command
import matplotlib.pyplot as plt
....
plt.locator_params(numticks=12)
plt.show()
The value set to numticks determines the number of axis ticks to be displayed.
Credits to #bgamari's post for introducing the locator_params() function, but the nticks parameter throws an error when a log scale is used.

MatPlotLib share y across entire figure

Here is an example of a plot I am generating using Pandas and MatPlotLib.
Please note that even though I stated sharey = True in the code, the y-Axis is only shared across each row.This isn't much help to me, as I need to compare all plots against each other.
How can I use just one axis for the entire plot? I'd also ideally want that axis repeated for each plot.
Thank you!
for field in chosenFields:
for dataID in dataIDs:
fig = plt.figure()
subplots = [fig.add_subplot(rows, cols, subplot) for
subplot in range(1, len(fileNames) + 1)]
for subplot, plot, fileName in zip(subplots, plots, fileNames):
graphData = Build_Graphs.prepareOutputGraph(plot[0],
field,
dataID,
batchName,
segmentName)
haveLegend = True if len(graphData.columns) < 12 else False
subplt = graphData.plot(ax = subplot,
legend = haveLegend,
title = fileName,
sharey = True)
Build_Graphs.labelGraph(subplt, field, dataID, batchName, segmentName)
plt.get_current_fig_manager().window.showMaximized()
writeOutput(outputDirectory, field, dataID, graphData)
plt.show()
In order to get the same axis range repeated for each plot, you can get_ylim from all existing and use global min/max to set all the axes,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Get global minimum and maximum y values accross all axis
ygmin = 0.; ygmax = 0.
for sp in subplots.reshape(-1):
ymin, ymax = sp.get_ylim()
ygmin = min(ygmin,ymin)
ygmax = max(ygmax,ymax)
#Set same axis for all subplots
[sp.set_ylim((ygmin,ygmax)) for sp in subplots.reshape(-1)]
plt.show()
As suggested by paulH, this can also be done with sharey=True as part of plt.subplots. However, the y axis is hidden for anything but the first axis by default, so you need to tell matplotlib to show these again,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3,sharey=True)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Show axis on all subplots
[plt.setp(sp.get_yticklabels(), visible=True) for sp in subplots.reshape(-1)]
plt.show()
You can also specify sharey="col" or sharey="row" to share axes alone the column or row respectively.

x axis with duplicate values (loading profile) plot in matplotlib

i have load profile data where x axis is load profile such that for multiple same values of x (constant load) i have different values for y.
till now in excel i used to line plot y and right click graph->selec data->change hoizontal axis data by providing it range o x axis data and that used to give me the graph
the problem i have is when i try to give
plot(x,y), matplotlib plots y for unique vals of x ie it neglects out all the remaining value of for same value of x.
and when i plot with plot(y) i get sequence numbers on x axis
i tried xticks([0,5,10,15]) for checking out but couldn't get the required result.
my question is
is it possible to plot a graph in a similar fashion as of excel
the other alternative i could think of was plotting plot(y and plot (x) with same horizontal axis it atleast gives a pictorial idea but is there any means to do it the excel way??
From your description, it sounds to me like you want to use the "scatter" plotting command instead of the "plot" plotting command. This will allow the use of redundant x-values. Sample code:
import numpy as np
import matplotlib.pyplot as plt
# Generate some data that has non-unique x-values
x1 = np.linspace(1,50)
y1 = x1**2
y2 = 2*x1
x3 = np.append(x1,x1)
y3 = np.append(y1,y2)
# Now plot it using the scatter command
# Note that some of the abbreviations that work with plot,
# such as 'ro' for red circles don't work with scatter
plt.scatter(x3,y3,color='red',marker='o')
As I mentioned in the comments, some of the handy "plot" shortcuts don't work with "scatter" so you may want to check the documentation: http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.scatter
If you want to plot y-values for a given x-values, you need to get the index which has same x-values. If you are working with numpy then you can try
import pylab as plt
import numpy as np
x=np.array([1]*5+[2]*5+[3]*5)
y=np.array([1,2,3,4,5]*3)
idx=(x==1) # Get the index where x-values are 1
plt.plot(y[idx],'o-')
plt.show()
If you are working with lists you can get the index by
# Get the index where x-values are 1
idx=[i for i, j in enumerate(x) if j == 1]
just answering own question,found this around when i had posted this question years back :)
def plotter(y1,y2,y1name,y2name):
averageY1=float(sum(y1)/len(y1))
averageY2=float(sum(y2)/len(y2))
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(y1,'b-',linewidth=2.0)
ax1.set_xlabel("SNo")
# Make the y2-axis label and tick labels match the line color.
ax1.set_ylabel(y1name, color='b')
for tl in ax1.get_yticklabels():
tl.set_color('b')
ax1.axis([0,len(y2),0,max(y1)+50])
ax2 = ax1.twinx()
ax2.plot(y2, 'r-')
ax2.axis([0,len(y2),0,max(y2)+50])
ax2.set_ylabel(y2name, color='r')
for tl in ax2.get_yticklabels():
tl.set_color('r')
plt.title(y1name + " vs " + y2name)
#plt.fill_between(y2,1,y1)
plt.grid(True,linestyle='-',color='0.75')
plt.savefig(y1name+"VS"+y2name+".png",dpi=200)
You can use
import numpy as np
import matplotlib.pyplot as plt
x = np.array([1, 1, 1, 2, 2, 2])
y = np.array([1, 2, 1, 5, 6, 7])
fig, ax = plt.subplots()
ax.plot(np.arange(len(x)), y)
ax.set_xticklabels(x)
plt.show()

Categories

Resources