I am trying to plot the energy consumption profile of an electric vehicle. I am using the elevation profile vs the horizontal distance the vehicle runs along a path. I want to add a second x-axis on top of the plot to represent by each chunk of distance, what the energy consumption value was at that precise location.
This is what I have so far, but it's not precisely what I need:
I know this should be fairly simple as it is only adding a second x-axis that matches with the primary x-axis, but I have wasted an entire day trying to figure out unsuccessfully :(
Any insights will be greatly appreciated.
Code:
fig, ax1 = plt.subplots()
elevation_distance_np = elevation_distance.to_numpy()
plt.plot(elevation_distance_np[:,0], elevation_distance_np[:,1], color = 'blue')
plt.grid(True)
plt.xlabel("Distancia recorrida")
plt.ylabel("Elevación de distancia recorrrida")
axes2 = ax1.twiny()
axes2.set_xticks(suma_kWh_np[::mth.ceil(len(suma_kWh_np)/8)])
plt.title("Elevación vs Distancia Recorrida")
plt.show()
This is a not so trivial endeavor, as these questions show, so don't feel frustrated for not getting this on your own.
Disclaimer: this is not the most elegant solution, but it works. I made a toy example where the conversion from one axis to the other is obtained by dividing the main by 8.5. Also, I replotted your data on this secondary axis, to set the values of its own X axis to something sensible, then removed this extra line.
x = np.linspace(0, 140) # Some x values, similar to your range
# Caps them to a minimum of 0
y = np.clip(x * (-1) + 100, a_min=0, a_max=100)
# Creates something similar to your data
elevation_distance_np = np.hstack((x[:, np.newaxis], y[:, np.newaxis]))
# I guessed some transform. If you don't have a formula,
# you'll need to interpolate between known values, probably.
suma_kWh_np = x / 8.5
fig, ax1 = plt.subplots()
# Changed to explicit notation, so we don't go back and forth between them
ax1.plot(elevation_distance_np[:,0], elevation_distance_np[:,1], color = 'blue')
ax1.grid(True)
ax1.set_xlabel("Distancia recorrida")
ax1.set_ylabel("Elevación de distancia recorrrida")
ax2 = ax1.twiny()
# Added a copy of your line, but which will be removed later
extra_line = ax2.plot(suma_kWh_np, elevation_distance_np[:,1], color = 'r')
# Now, we get the x ticks and transform them to kWh.
# Here, I had to remove the first and last points ([1:-1])
# because ax1.get_xticks() returned a range from -20 to 160,
ax2.set_xticks(ax1.get_xticks()[1:-1] / 8.5)
ax1.set_title("Elevación vs Distancia Recorrida")
ax2.lines.pop() # We remove the temporary line right before plotting
plt.show()
Here's the result.
Related
I want to plot two different functions in the same figure. However I want them to use different scales on their x-axis.
One scale shoudl just show the values of x and the others will have to show seconds in the end.
Right now I have this
k=5
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_xlabel(r"values of x") #adds description to scale on bottom
ax2 = ax1.twiny() #adds the seconds scale on top
x = np.arange(0.1, 1.5, 0.1) #values of x for function are in range
y = k*(np.power(x,(k-1))) * np.exp(-(np.power(x,(k-1)))) #that is the function I want to draw
ax1.plot(x,y) #draw function
tx = x
ty = x*7
ax2.plot(x,x*7)
ax2.set_xlabel(r"time in seconds")
ax2.set_xlim(1484) #set limit of time
ax2.invert_xaxis() #invert it so that it works like we want to
ax1.set_xlim(0.1,1.4) #set limit for the x axis so that it doesn't skale on its own.
plt.show()
I am sorry but I could not properly insert the code.
The ax2 function is right now just a dummy. I just want to be able to see it and also in the end change the scale of the ax2 to my time frame.
Any help would be greatly appreciated!
I am not sure your code doesn't work :-p
Your dummy function for ax2 is not good enough, I replaced it with ax2.plot(x*1000,x*50) to be able to see it.
And I do the plotting after the rescaling :
k=5
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_xlabel(r"values of x") #adds description to scale on bottom
ax2 = ax1.twiny() #adds the seconds scale on top
x = np.arange(0.1, 1.5, 0.1) #values of x for function are in range
y = k*(np.power(x,(k-1))) * np.exp(-(np.power(x,(k-1)))) #that is the function I want to draw
ax1.plot(x,y) #draw function
tx = x
ty = x*7
ax2.set_xlabel(r"time in seconds")
ax2.set_xlim(1484) #set limit of time
ax2.invert_xaxis() #invert it so that it works like we want to
ax2.plot(x*1000,x*50)
ax1.set_xlim(0.1,1.4) #set limit for the x axis so that it doesn't skale on its own.
plt.show()
Which gives :
The second plot is hidden behind the left Y axis. You will be able to see it if you use a thicker line and/or markers:
ax2.plot(x,x*7, '-o', lw=5)
You could as well change the x limits of ax2 but you went out of your way to make it as it is so I guess it is exactly as you want it to be.
I'm writing a pythonic script for a coastal engineering application which should output, amongst other things, a figure with two subplots.
The problem is that I would like to shade a section of both subplots using plt.axvspan() but for some reason it only shades one of them.
Please find below an excerpt of the section of the code where I set up the plots as well as the figure that it's currently outputting (link after code).
Thanks for your help, and sorry if this is a rookie question (but it just happens that I am indeed a rookie in Python... and programming in general) but I couldn't find an answer for this anywhere else.
Feel free to add any comments to the code.
# PLOTTING
# now we generate a figure with the bathymetry vs required m50 and another figure with bathy vs Hs
#1. Generate plots
fig = plt.figure() # Generate Figure
ax = fig.add_subplot(211) # add the first plot to the figure.
depth = ax.plot(results[:,0],results[:,1]*-1,label="Depth [mDMD]") #plot the first set of data onto the first set of axis.
ax2 = ax.twinx() # generate a secondary vertical axis with the same horizontal axis as the first
m50 = ax2.plot(results[:,0],results[:,6],"r",label="M50 [kg]") # plot the second set of data onto the second vertical axis
ax3 = fig.add_subplot(212) # generate the second subplot
hs = ax3.plot(results[:,0],results[:,2],"g",label="Hs(m)")
#Now we want to find where breaking starts to occur so we shade it on the plot.
xBreakingDistance = results[numpy.argmax(breakingIndex),0]
# and now we plot a box from the origin to the depth of breaking.
plt.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) # this box is called a span in matplotlib (also works for axhspan)
# and then we write BREAKING ZONE in the box we just created
yLimits = ax.get_ylim() # first we get the range of y being plotted
yMiddle = (float(yLimits[1])-float(yLimits[0])) / 2 + yLimits[0] # then we calculate the middle value in y (to center the text)
xMiddle = xBreakingDistance / 2 # and then the middle value in x (to center the text)
#now we write BREAKING ZONE in the center of the box.
ax.text(xMiddle,yMiddle,"BREAKING ZONE",fontweight="bold",rotation=90,verticalalignment="center",horizontalalignment="center")
#FIGURE FORMATTING
ax.set_xlabel("Distance [m]") # define x label
ax.set_ylabel("Depth [mDMD]") # define y label on the first vertical axis (ax)
ax2.set_ylabel("M50 [kg]") # define y label on the second vertical axis (ax2)
ax.grid() # show grid
ax3.set_xlabel("Distance[m]") #define x label
ax3.set_ylabel("Hs[m]") # define y label
ax3.grid()
plt.tight_layout() # minimize subplot labels overlapping
# generating a label on a plot with 2 vertical axis is not very intuitive. Normally we would just write ax.label(loc=0)
combined_plots = depth+m50 #first we need to combine the plots in a vector
combined_labels = [i.get_label() for i in combined_plots] # and then we combine the labels
ax.legend(combined_plots,combined_labels,loc=0) # and finally we plot the combined_labels of the combined_plots
plt.savefig("Required M50(kg) along the trench.png",dpi=1000)
plt.close(fig)
Output Figure:
By just calling plt.axvspan, you are telling matplotlib to create the axvspan on the currently active axes (i.e. in this case, the last one you created, ax3)
You need to plot the axvspan on both of the axes you would like for it to appear on. In this case, ax and ax3.
So, you could do:
ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
ax3.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
or in one line:
[this_ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) for this_ax in [ax,ax3]]
It's difficult to analyze your code and not being able to reproduce it. I advise you to build a minimal example. In any case notice that you are calling "plt.axvspan(" which is general call to the library.
You need to specifically state that you want this in both "ax" and "ax2" (i think).
Also if you need more control consider using Patches (I don't know axvspan):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
fig1.savefig('rect1.png', dpi=90, bbox_inches='tight')
See that call to "ax1" in the example? Just make something similar to yours. Or just add axvspan to each of your plots.
I'm trying to make a 1D heatmap for a gene (see ref 1 in pastebin for example). I've gotten close to what I'm looking for with contourf, but I haven't been able to figure out how to get exactly what I'm looking for. Basically, I'm want to utilize a colormap that has 10 discrete colors, and the cutoffs for the different colors correspond to the percentiles of the data (so the top 10% of the data points are red, the next 10% are orange, etc).
I don't have enough reputation to post more than two links or any images, so you can also see my output images from the code below, as well as the other pages I've looked at to try and solve this question, at http://pastebin.com/jAkxyQsK.
The actual data points are in a list at http://pastebin.com/3TrkkpZ0. You can try with random integers, but the difference between linear scaling and percentile-scaling likely won't be clear unless your data is skewed like mine.
data = [] #actually a list of ~450 floats
x = []
nd = np.array(data)
x = np.empty([2, nd.shape[0]])
x[:,:] = nd
fig = plt.figure(figsize = (11, 8.5))
ax = fig.add_subplot(111)
Now, here are my experiments:
mind, maxd, sprd = min(data), max(data), max(data)-min(data)
levels = [(lambda n: mind + (n*sprd)/10)(n) for n in range(0,11,1)]
hm = plt.contourf(x, levels = levels, cmap = "rainbow")
cbar = fig.colorbar(hm, ax = ax)
plt.show()
[Figure 1 on pastebin]
This is mostly what I want to see: the colorbar is discretized and the plot looks fine, but the colorbar is spaced linearly between the max and the min of the data, which is not what I want. Attempt two:
levels = np.percentile(data, [z for z in range (0,110,10)])
hm = plt.contourf(x, levels = levels, cmap = "rainbow")
cbar = fig.colorbar(hm, ax = ax)
plt.show()
[Figure 2 on pastebin]
This is also close; the colorbar is divided up by the values of the percentiles (or at least the tick values indicate that), but for some reason it's no longer utilizing the full range of the colormap and I have no idea why.
I also tried implementing the function described in references 2 and 3 with pcolor, but I couldn't figure out how to make them work with my data instead of a scatter plot and the results were not as close as I could get with contourf, so I stopped pursuing them. If the answer is already in one of the links I've looked at but I couldn't understand it, then a 'plain English' translation would be super helpful.
I cannot tell why the colormap does not use the full range of colors in your example, but it seems that the following is closer to the result you want (i.e. it does span a larger range of colors with the quantile levels).
...
hm = plt.contourf(x, levels = levels, cmap = "rainbow", vmax = levels[-2])
...
You can also try a 'weighted' value for the max colormap level.
...
hm = plt.contourf(x, levels = levels, cmap = "rainbow", vmax = 0.3 * levels[-1] + 0.7 * levels[-2])
...
I'm creating a figure with multiple subplots. One of these subplots is giving me some trouble, as none of the axes corners or centers are free (or can be freed up) for placing the legend. What I'd like to do is to have the legend placed somewhere in between the 'upper left' and 'center left' locations, while keeping the padding between it and the y-axis equal to the legends in the other subplots (that are placed using one of the predefined legend location keywords).
I know I can specify a custom position by using loc=(x,y), but then I can't figure out how to get the padding between the legend and the y-axis to be equal to that used by the other legends. Would it be possible to somehow use the borderaxespad property of the first legend? Though I'm not succeeding at getting that to work.
Any suggestions would be most welcome!
Edit: Here is a (very simplified) illustration of the problem:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, sharex=False, sharey=False)
ax[0].axhline(y=1, label='one')
ax[0].axhline(y=2, label='two')
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
ax[1].axhline(y=1, label='one')
ax[1].axhline(y=2, label='two')
ax[1].axhline(y=3, label='three')
ax[1].set_ylim([0.8,3.2])
ax[1].legend(loc=2)
plt.show()
What I'd like is that the legend in the right plot is moved down somewhat so it no longer overlaps with the line.
As a last resort I could change the axis limits, but I would very much like to avoid that.
I saw the answer you posted and tried it out. The problem however is that it is also depended on the figure size.
Here's a new try:
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(0, 10, 10000)
y = numpy.cos(x) + 2.
x_value = .014 #Offset by eye
y_value = .55
fig, ax = plt.subplots(1, 2, sharex = False, sharey = False)
fig.set_size_inches(50,30)
ax[0].plot(x, y, label = "cos")
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
line1 ,= ax[1].plot(x,y)
ax[1].set_ylim([0.8,3.2])
axbox = ax[1].get_position()
fig.legend([line1], ["cos"], loc = (axbox.x0 + x_value, axbox.y0 + y_value))
plt.show()
So what I am now doing is basically getting the coordinates from the subplot. I then create the legend based on the dimensions of the entire figure. Hence, the figure size does not change anything to the legend positioning anymore.
With the values for x_value and y_value the legend can be positioned in the subplot. x_value has been eyeballed for a good correspondence with the "normal" legend. This value can be changed at your desire. y_value determines the height of the legend.
Good luck!
After spending way too much time on this, I've come up with the following satisfactory solution (the Transformations Tutorial definitely helped):
bapad = plt.rcParams['legend.borderaxespad']
fontsize = plt.rcParams['font.size']
axline = plt.rcParams['axes.linewidth'] #need this, otherwise the result will be off by a few pixels
pad_points = bapad*fontsize + axline #padding is defined in relative to font size
pad_inches = pad_points/72.0 #convert from points to inches
pad_pixels = pad_inches*fig.dpi #convert from inches to pixels using the figure's dpi
Then, I found that both of the following work and give the same value for the padding:
# Define inverse transform, transforms display coordinates (pixels) to axes coordinates
inv = ax[1].transAxes.inverted()
# Inverse transform two points on the display and find the relative distance
pad_axes = inv.transform((pad_pixels, 0)) - inv.transform((0,0))
pad_xaxis = pad_axes[0]
or
# Find how may pixels there are on the x-axis
x_pixels = ax[1].transAxes.transform((1,0)) - ax[1].transAxes.transform((0,0))
# Compute the ratio between the pixel offset and the total amount of pixels
pad_xaxis = pad_pixels/x_pixels[0]
And then set the legend with:
ax[1].legend(loc=(pad_xaxis,0.6))
Plot:
I have a hopefully simple question. When using the python hexbin plot option on some spatial data (Ra, and Dec are x and y) I also want to see the marginals on the side. Happily there is a simple option 'marginals = True'....
Unhappily, as you can see below... the x-axis marginals are visibly offset from the hexagon produced image. I have tried adjusting parameters but the marginals on the x-axis always appear offset to the image (and there never seems to be a problem in y), any ideas would be appreciated. Please see the code and image below, Thanks in advance!
fig5=plt.figure(5)
ax=fig5.add_subplot(111)
imageh=plt.hexbin(Radeg[CoreL],
Decdeg[CoreL],
extent=[np.min(Radeg[CoreL]), np.max(Radeg[CoreL]), np.min(Decdeg[CoreL]), np.max(Decdeg[CoreL])],
alpha=0.7,
gridsize=20,
marginals=True,
vmin=5,
vmax=105,
cmap=get_cmap("jet"),
mincnt=5)
ax.axis([305,275,-40,-25])
cbar=plt.colorbar(imageh,extend='max')
cbar.set_label(r'$\mathrm{Counts}$',fontsize=18)
ax.set_xlabel(r'$\mathrm{RA}$',fontsize=20)
ax.set_ylabel(r'$\mathrm{DEC}$',fontsize=18)
plt.show()
-- per request that I add data to test with..... my data is rather lengthy and unwieldily, but here is a standalone version that illustrates the problem. This is a altered version from 'Hooked' who posted in regard to a different hexbin question.
def generate_data(n):
"""Make random, correlated x & y arrays"""
points = np.random.multivariate_normal(mean=(0,0),
cov=[[0.4,9],[9,10]],size=int(n))
return points
if __name__ =='__main__':
color_map = plt.cm.Spectral_r
n = 1e4
points = generate_data(n)
xbnds = np.array([-20.0,20.0])
ybnds = np.array([-20.0,20.0])
extent = [xbnds[0],xbnds[1],ybnds[0],ybnds[1]]
fig=plt.figure(figsize=(10,9))
ax = fig.add_subplot(111)
x, y = points.T
image = plt.hexbin(x,y,cmap=color_map,gridsize=20,marginals=True,extent=extent,mincnt=1,bins='log')
ax.set_xlim(xbnds)
ax.set_ylim(ybnds)
plt.grid(True)
cb = plt.colorbar(image,spacing='uniform',extend='max')
plt.show()
This code gives a similar image, but this time the marginals are offset in the x and y direction, the integral should be just in one direction, over the other variable, i.e. rows and columns. In theory I would expect a marginal on the side for every row and column I have data in.