I try to plot two polar plots in one figure. See code below:
fig = super(PlotWindPowerDensity, self).get_figure()
rect = [0.1, 0.1, 0.8, 0.8]
ax = WindSpeedDirectionAxes(fig, rect)
self.values_dict = collections.OrderedDict(sorted(self.values_dict.items()))
values = self.values_dict.items()
di, wpd = zip(*values)
wpd = np.array(wpd).astype(np.double)
wpdmask = np.isfinite(wpd)
theta = self.radar_factory(int(len(wpd)))
# spider plot
ax.plot(theta[wpdmask], wpd[wpdmask], color = 'b', alpha = 0.5)
ax.fill(theta[wpdmask], wpd[wpdmask], facecolor = 'b', alpha = 0.5)
# bar plot
ax.plot_bar(table=self.table, sectors=self.sectors, speedbins=self.wpdbins, option='wind_power_density', colorfn=get_sequential_colors)
fig.add_axes(ax)
return fig
The length of the bar is the data base (how many sampling points for this sector). The colors of the bars show the frequency of certain value bins (eg. 2.5-5 m/s) in the correspondent sector (blue: low, red: high). The blue spider plot shows the mean value for each sector.
In the shown figure, the values of each plot are similar, but this is rare. I need to assign the second plot to another axis and show this axis in another direction.
EDIT:
After the nice answer of Joe, i get the result of the figure.
That's almost everything i wanted to achieve. But there are some points i wasn't able to figure out.
The plot is made for dynamicly changing data bases. Therefore i need a dynamic way to get the same location of the circles. Till now I solve it with:
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(0, end, end / len(ax.yaxis.get_ticklocs())))
means: for second axis i alter the ticks in order to fit the ticklocs to the one's of first axis.
In most cases i get some decimal places, but i don't want that, because it corrupts the clearness of the plot. Is there a way to solve this problem more smartly?
The ytics (the radial one's) range from 0 to the next-to-last circle. How can i achieve that the values range from the first circle to the very last (the border)? The same like for the first axis.
So, as I understand it, you want to display data with very different magnitudes on the same polar plot. Basically you're asking how to do something similar to twinx for polar axes.
As an example to illustrate the problem, it would be nice to display the green series on the plot below at a different scale than the blue series, while keeping them on the same polar axes for easy comparison.:
import numpy as np
import matplotlib.pyplot as plt
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
However, ax.twinx() doesn't work for polar plots.
It is possible to work around this, but it's not very straight-forward. Here's an example:
import numpy as np
import matplotlib.pyplot as plt
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Ensure that original axes tick labels are on top of plots in twinned axes
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
main()
That does what we want, but it looks fairly bad at first. One improvement would be to the tick labels to correspond to what we're plotting:
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
However, we still have the double-grids, which are rather confusing. One easy way around this is to manually set the r-limits (and/or r-ticks) such that the grids will fall on top of each other. Alternately, you could write a custom locator to do this automatically. Let's stick with the simple approach here:
ax.set_rlim([0, 5])
ax2.set_rlim([0, 1])
Caveat: Because shared axes don't work for polar plots, the implmentation I have above will have problems with anything that changes the position of the original axes. For example, adding a colorbar to the figure will cause all sorts of problems. It's possible to work around this, but I've left that part out. If you need it, let me know, and I'll add an example.
At any rate, here's the full, stand-alone code to generate the final figure:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1977)
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
ax.set_ylim([0, 5])
ax2.set_ylim([0, 1])
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Bit of a hack to ensure that the original axes tick labels are on top of
# whatever is plotted in the twinned axes. Tick labels will be drawn twice.
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
if __name__ == '__main__':
main()
Just to add onto #JoeKington 's (great) answer, I found that the "hack to ensure that the original axes tick labels are on top of whatever is plotted in the twinned axes" didn't work for me so as an alternative I've used:
from matplotlib.ticker import MaxNLocator
#Match the tick point locations by setting the same number of ticks in the
# 2nd axis as the first
ax2.yaxis.set_major_locator(MaxNLocator(nbins=len(ax1.get_yticks())))
#Set the last tick as the plot limit
ax2.set_ylim(0, ax2.get_yticks()[-1])
#Remove the tick label at zero
ax2.yaxis.get_major_ticks()[0].label1.set_visible(False)
Related
I have written my code to create a scatter plot with a color bar on the right. But the color bar does not look right, in the sense that the color is too light to be mapped to the actual color used in the plot. I am not sure what is missing or wrong here. But I am hoping to get something similar to what's shown here: https://medium.com/#juliansteam/what-bert-topic-modelling-reveal-about-the-2021-unrest-in-south-africa-d0d15629a9b4 (about in the middle of the page)
df = .... # data loading
df["topic"] = topics
# Plot parameters
top_n = topn
fontsize = 15
# some data preparation
to_plot = df.copy()
to_plot[df.topic >= top_n] = -1
outliers = to_plot.loc[to_plot.topic == -1]
non_outliers = to_plot.loc[to_plot.topic != -1]
#the actual plot
fig, ax = plt.subplots(figsize=(15, 15))
scatter_outliers = ax.scatter(outliers['x'], outliers['y'], color="#E0E0E0", s=1, alpha=.3)
scatter = ax.scatter(non_outliers['x'], non_outliers['y'], c=non_outliers['topic'], s=1, alpha=.3, cmap='hsv_r')
ax.text(0.99, 0.01, f"BERTopic - Top {top_n} topics", transform=ax.transAxes, horizontalalignment="right", color="black")
plt.xticks([], [])
plt.yticks([], [])
plt.colorbar(scatter)
plt.savefig(outfile+"_1.png", format='png', dpi=300)
plt.clf()
plt.close()
As you can see, an example plot looks like this. The color bar is created, but compared to that shown in the link above, the color is very light and does not seem to map to those on the scatter plot. Any suggestions?
The colorbar uses the given alpha=.3. In the scatterplot, many dots with the same color are superimposed, causing them to look brighter than a single dot.
One way to tackle this, is to create a ScalarMappable object to be used by the colorbar, taking the colormap and the norm of the scatter plot (but not its alpha). Note that simply changing the alpha of the scatter object (scatter.set_alpha(1)) would also change the plot itself.
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
import numpy as np
x = np.random.normal(np.repeat(np.random.uniform(0, 20, 10), 1000))
y = np.random.normal(np.repeat(np.random.uniform(0, 10, 10), 1000))
c = np.repeat(np.arange(10), 1000)
scatter = plt.scatter(x, y, c=c, cmap='hsv_r', alpha=.3, s=3)
plt.colorbar(ScalarMappable(cmap=scatter.get_cmap(), norm=scatter.norm))
plt.tight_layout()
plt.show()
I'm trying to create a plot with two Y axes (left and right) for the same data, that is, one is a scaled version of the other. I would like also to preserve the tick positions and grid positions, so the grid will match the ticks at both sides.
I'm trying to do this by plotting twice the same data, one as-is and the other scaled, but they are not coincident.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(17, 27, 0.1)
y1 = 0.05 * x + 100
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y1/max(y1), 'g-')
ax1.set_xlabel('X data')
ax1.set_ylabel('Y data', color='g')
ax2.set_ylabel('Y data normalized', color='b')
plt.grid()
plt.show()
Any help will be appreciated.
Not sure if you can achieve this without getting ugly-looking numbers on your normalized axis. But if that doesn't bother you, try adding this to your code:
ax2.set_ylim([ax1.get_ylim()[0]/max(y1),ax1.get_ylim()[1]/max(y1)])
ax2.set_yticks(ax1.get_yticks()/max(y1))
Probably not the most elegant solution, but it scales your axis limits and tick positions similarly to what you do with the data itself so the grid matches both axes.
Here's some code that does scatter plot of a number of different series using matplotlib and then adds the line y=x:
import numpy as np, matplotlib.pyplot as plt, matplotlib.cm as cm, pylab
nseries = 10
colors = cm.rainbow(np.linspace(0, 1, nseries))
all_x = []
all_y = []
for i in range(nseries):
x = np.random.random(12)+i/10.0
y = np.random.random(12)+i/5.0
plt.scatter(x, y, color=colors[i])
all_x.extend(x)
all_y.extend(y)
# Could I somehow do the next part (add identity_line) if I haven't been keeping track of all the x and y values I've seen?
identity_line = np.linspace(max(min(all_x), min(all_y)),
min(max(all_x), max(all_y)))
plt.plot(identity_line, identity_line, color="black", linestyle="dashed", linewidth=3.0)
plt.show()
In order to achieve this I've had to keep track of all the x and y values that went into the scatter plot so that I know where identity_line should start and end. Is there a way I can get y=x to show up even if I don't have a list of all the points that I plotted? I would think that something in matplotlib can give me a list of all the points after the fact, but I haven't been able to figure out how to get that list.
You don't need to know anything about your data per se. You can get away with what your matplotlib Axes object will tell you about the data.
See below:
import numpy as np
import matplotlib.pyplot as plt
# random data
N = 37
x = np.random.normal(loc=3.5, scale=1.25, size=N)
y = np.random.normal(loc=3.4, scale=1.5, size=N)
c = x**2 + y**2
# now sort it just to make it look like it's related
x.sort()
y.sort()
fig, ax = plt.subplots()
ax.scatter(x, y, s=25, c=c, cmap=plt.cm.coolwarm, zorder=10)
Here's the good part:
lims = [
np.min([ax.get_xlim(), ax.get_ylim()]), # min of both axes
np.max([ax.get_xlim(), ax.get_ylim()]), # max of both axes
]
# now plot both limits against eachother
ax.plot(lims, lims, 'k-', alpha=0.75, zorder=0)
ax.set_aspect('equal')
ax.set_xlim(lims)
ax.set_ylim(lims)
fig.savefig('/Users/paul/Desktop/so.png', dpi=300)
Et voilĂ
In one line:
ax.plot([0,1],[0,1], transform=ax.transAxes)
No need to modify the xlim or ylim.
Starting with matplotlib 3.3 this has been made very simple with the axline method which only needs a point and a slope. To plot x=y:
ax.axline((0, 0), slope=1)
You don't need to look at your data to use this because the point you specify (i.e. here (0,0)) doesn't actually need to be in your data or plotting range.
If you set scalex and scaley to False, it saves a bit of bookkeeping. This is what I have been using lately to overlay y=x:
xpoints = ypoints = plt.xlim()
plt.plot(xpoints, ypoints, linestyle='--', color='k', lw=3, scalex=False, scaley=False)
or if you've got an axis:
xpoints = ypoints = ax.get_xlim()
ax.plot(xpoints, ypoints, linestyle='--', color='k', lw=3, scalex=False, scaley=False)
Of course, this won't give you a square aspect ratio. If you care about that, go with Paul H's solution.
In my plot, a secondary x axis is used to display the value of another variable for some data. Now, the original axis is log scaled. Unfortunaltely, the twinned axis puts the ticks (and the labels) referring to the linear scale of the original axis and not as intended to the log scale. How can this be overcome?
Here the code example that should put the ticks of the twinned axis in the same (absolute axes) position as the ones for the original axis:
def conv(x):
"""some conversion function"""
# ...
return x2
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
# get the location of the ticks of ax
axlocs,axlabels = plt.xticks()
# twin axis and set limits as in ax
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
#Set the ticks, should be set referring to the log scale of ax, but are set referring to the linear scale
ax2.set_xticks(axlocs)
# put the converted labels
ax2.set_xticklabels(map(conv,axlocs))
An alternative way would be (the ticks are then not set in the same position, but that doesn't matter):
from matplotlib.ticker import FuncFormatter
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.xaxis.set_major_formatter(FuncFormatter(lambda x,pos:conv(x)))
Both approaches work well as long as no log scale is used.
Perhaps there exists an easy fix. Is there something I missed in the documentation?
As a workaround, I tried to obtain the ax.transAxes coordinates of the ticks of ax and put the ticks at the very same position in ax2. But there does not exist something like
ax2.set_xticks(axlocs,transform=ax2.transAxes)
TypeError: set_xticks() got an unexpected keyword argument 'transform'
This has been asked a while ago, but I stumbled over it with the same question.
I eventually managed to solve the problem by introducing a logscaled (semilogx) transparent (alpha=0) dummy plot.
Example:
import numpy as np
import matplotlib.pyplot as plt
def conversion_func(x): # some arbitrary transformation function
return 2 * x**0.5 # from x to z
x = np.logspace(0, 5, 100)
y = np.sin(np.log(x))
fig = plt.figure()
ax = plt.gca()
ax.semilogx(x, y, 'k')
ax.set_xlim(x[0], x[-1]) # this is important in order that limits of both axes match
ax.set_ylabel("$y$")
ax.set_xlabel("$x$", color='C0')
ax.tick_params(axis='x', which='both', colors='C0')
ax.axvline(100, c='C0', lw=3)
ticks_x = np.logspace(0, 5, 5 + 1) # must span limits of first axis with clever spacing
ticks_z = conversion_func(ticks_x)
ax2 = ax.twiny() # get the twin axis
ax2.semilogx(ticks_z, np.ones_like(ticks_z), alpha=0) # transparent dummy plot
ax2.set_xlim(ticks_z[0], ticks_z[-1])
ax2.set_xlabel("$z \equiv f(x)$", color='C1')
ax2.xaxis.label.set_color('C1')
ax2.tick_params(axis='x', which='both', colors='C1')
ax2.axvline(20, ls='--', c='C1', lw=3) # z=20 indeed matches x=100 as desired
fig.show()
In the above example the vertical lines demonstrate that first and second axis are indeed shifted to one another as wanted. x = 100 gets shifted to z = 2*x**0.5 = 20. The colours are just to clarify which vertical line goes with which axis.
Don't need to cover them, just Eliminate the ticks!
d= [7,9,14,17,35,70];
j= [100,80,50,40,20,10];
plt.figure()
plt.xscale('log')
plt.plot(freq, freq*spec) #plot some spectrum
ax1 = plt.gca() #define my first axis
ax1.yaxis.set_ticks_position('both')
ax1.tick_params(axis='y',which='both',direction='in');
ax1.tick_params(axis='x',which='both',direction='in');
ax2 = ax1.twiny() #generates second axis (top)
ax2.set_xlim(ax1.get_xlim()); #same limits
plt.xscale('log') #make it log
ax2.set_xticks(freq[d]); #my own 'major' ticks OVERLAPS!!!
ax2.set_xticklabels(j); #change labels
ax2.tick_params(axis='x',which='major',direction='in');
ax2.tick_params(axis='x',which='minor',top=False); #REMOVE 'MINOR' TICKS
ax2.grid()
I think you can fix your issue by calling ax2.set_xscale('log').
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.semilogx(np.logspace(1.0, 5.0, 20), np.random.random([20]))
new_tick_locations = np.array([10., 100., 1000., 1.0e4])
def tick_function(X):
V = X / 1000.
return ["%.3f" % z for z in V]
ax2 = ax.twiny()
ax2.set_xscale('log')
ax2.set_xlim(ax.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $X/1000$")
I've got a Matplotlib graph with two y axes, created like:
ax1 = fig.add_subplot(111)
ax1.grid(True, color='gray')
ax1.plot(xdata, ydata1, 'b', linewidth=0.5)
ax2 = ax1.twinx()
ax2.plot(xdata, ydata2, 'g', linewidth=0.5)
I need grid lines but I want them to apply to both y axes not just the left one. The scales of each axes will differ. What I get is grid lines that only match the values on the left hand axes.
Can Matplotlib figure this out for me or do I have to do it myself?
Edit: Don't think I was completely clear, I want the major ticks on both y axes to be aligned but the scales and ranges are potentially quite different making it tricky to setup the mins and maxes manually to achieve this. I am hoping that matplotlib will be able to do this "tricky" bit for me. Thanks
EDIT
Consider this simple example:
from pylab import *
# some random values
xdata = arange(0.0, 2.0, 0.01)
ydata1 = sin(2*pi*xdata)
ydata2 = 5*cos(2*pi*xdata) + randn(len(xdata))
# number of ticks on the y-axis
numSteps = 9;
# plot
figure()
subplot(121)
plot(xdata, ydata1, 'b')
yticks( linspace(ylim()[0],ylim()[1],numSteps) )
grid()
subplot(122)
plot(xdata, ydata2, 'g')
yticks( linspace(ylim()[0],ylim()[1],numSteps) )
grid()
show()