Multiple step histograms in matplotlib - python

Dear python/matplotlib community,
I am having an issue within matplotlib: I can't seem to plot multiple overlaid histograms in the same plot space using the following:
binsize = 0.05
min_x_data_sey, max_x_data_sey = np.min(logOII_OIII_sey), np.max(logOII_OIII_sey)
num_x_bins_sey = np.floor((max_x_data_sey - min_x_data_sey) / binsize)
min_x_data_comp, max_x_data_comp = np.min(logOII_OIII_comp), np.max(logOII_OIII_comp)
num_x_bins_comp = np.floor((max_x_data_comp - min_x_data_comp) / binsize)
min_x_data_sf, max_x_data_sf = np.min(logOII_OIII_sf), np.max(logOII_OIII_sf)
num_x_bins_sf = np.floor((max_x_data_sf - min_x_data_sf) / binsize)
axScatter_farright = fig.add_subplot(gs_right[0,0])
axScatter_farright.tick_params(axis='both', which='major', labelsize=10)
axScatter_farright.tick_params(axis='both', which='minor', labelsize=10)
axScatter_farright.set_ylabel(r'$\mathrm{N}$', fontsize='medium')
axScatter_farright.set_xlim(-1.5, 1.0)
axScatter_farright.set_xlabel(r'$\mathrm{log([OII]/[OIII])}$', fontsize='medium')
axScatter_farright.hist(logOII_OIII_sey, num_x_bins_sey, ec='0.3', fc='none', histtype='step')
axScatter_farright.hist(logOII_OIII_comp, num_x_bins_comp, ec='0.3', fc='none', histtype='step')
axScatter_farright.hist(logOII_OIII_sf, num_x_bins_sf, ec='0.3', fc='none', histtype='step')
It seems like the axes class can not handle multiple histograms? Please correct me if and/or where I have gone wrong.
My overall plot is a 1 row, 3 column plotting space. I would like to use grid spec to give the plots a good layout.
This is what my plot looks like thus far:
This is what I want the histogram portion of the figure to look like in terms of the step type histogram overlays (with legend):
I have the datasets as three different tuple type arrays generated from a csv file. i.e., using x, y = np.genfromtext(datafile.csv)
If anyone is able to explain how this could be done I would be very appreciative.

What you're doing should work perfectly. Is it possible that only one of the distributions is in the x-range of -1.5 to 1 that you've set a couple of lines before? (i.e. Try removing the manual set_xlim statement and see if the other distributions show up.)
As a quick, stand-alone example to demonstrate that things should work:
import numpy as np
import matplotlib.pyplot as plt
num = 1000
d1 = np.random.normal(-1, 1, num)
d2 = np.random.normal(1, 1, num)
d3 = np.random.normal(0, 3, num)
fig, ax = plt.subplots()
ax.hist(d1, 50, ec='red', fc='none', lw=1.5, histtype='step', label='Dist A')
ax.hist(d2, 50, ec='green', fc='none', lw=1.5, histtype='step', label='Dist B')
ax.hist(d3, 100, ec='blue', fc='none', lw=1.5, histtype='step', label='Dist C')
ax.legend(loc='upper left')
plt.show()
(If you want the legend to show lines instead of boxes, you'll need use a proxy artist. I can add an example if you'd like. That's outside the scope of this question, though.)

Related

Having trouble to display the graph of a function

Given a function f(x), how to ?
Generate the graph of the function and its derivative function on a figure with one bottom and top subplots that share the x-axis.
Both axes take their form with an input array with values from min to max
that are spaced by 0.5.
Each subplot must have a title
The top subplot must have the ylabel, whereas the bottom subplot must have both xlabel and ylabel
Each subplot shot have different styles of lines (colors, thickness, ...) and x ticks must also be in a different style(specifically the fontsize and rotation).
I did this :
#the function is f(x) while the derivative is df(x)
x = np.arange(min, max, 0.5) #scale
figure, (top, bottom) = plt.subplots(2, sharex=True, figsize = [5.0, 7.0])
top.plot(x, f(x), 'b', linewidth = 5)
top.set_title(Function f(x))
top.ylabel('f(x)')
figure.set_xticks(colors = 'r', fontsize = 12, rotation = 30)
bottom.plot(x, df(x), 'g-', linewidth = 8)
bottom.set_title('Derivative function of f(x)')
bottom.xlabel('x')
bottom.ylabel('df(x)')
plt.show(figure)
But it does not work. How could I fix everything ?
Edited for generalization
This seems to do what you need:
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return np.sin(x) - x*np.cos(x) #The derivative is equal to x
x = np.arange(-5.0, 5.0, 0.05)
figure, (top, bottom) = plt.subplots(2, sharex=True, figsize = [5.0, 7.0])
top.plot(x, f(x), 'b', linewidth = 5)
top.set_title('Function f(x) = sin(x) - x*cos(x)')
top.set_ylabel('f(x)')
plt.tick_params(axis='x', colors = 'r', labelsize = 12, rotation = 30)
bottom.plot(x, x, 'g-', linewidth = 8)
bottom.set_title('Derivative function of f(x)')
bottom.set_xlabel('x')
bottom.set_ylabel('df(x)')
bottom.tick_params(axis='x', colors = 'r')
plt.show()
When something goes wrong, it really helps to read Python's error messages. If you don't understand the message, you can google it, which often leads to a useful StackOverflow answer.
For example, the message "AttributeError: 'AxesSubplot' object has no attribute 'ylabel'" leads to this post. In this case, the correct function name is .set_ylabel(). The difference between the "Object Oriented Interface" and the "old" "plt" interface, can be a bit confusing (it is due to historic reasons). It helps to give the "axes" returned by plt.subplots() a name such as ax_top to more easily figure out how example code maps to your situation.
To set the color, rotation etc. of the ticks, you can use ax.tick_params(axis='x', rotation=30, labelsize=12, labelcolor='red'). This can be a bit tricky, the only way to "know" is to search for it on StackOverflow.
There are many different line styles. They can be set via the linestyle=... keyword. The most common ones also can be part of the code in the third parameter to plot(), e.g. ax.plot(x, y, 'b--') would use a blue dashed line style. (See also the introductory tutorial.)
Also note that plt.show() doesn't get the figure as parameter.
import matplotlib.pyplot as plt
import numpy as np
def f(x):
return np.sin(x) - x * np.cos(x) # The derivative is equal to x
x = np.arange(-5.0, 5.0, 0.05)
figure, (ax_top, ax_bottom) = plt.subplots(2, sharex=True, figsize=[5.0, 7.0])
ax_top.plot(x, f(x), 'b', linewidth=1)
ax_top.set_title('Function f(x) = sin(x) - x*cos(x)')
ax_top.set_ylabel('f(x)')
ax_bottom.tick_params(axis='x', rotation=30, labelsize=12, labelcolor='red')
ax_bottom.plot(x, x, 'g--', linewidth=2)
ax_bottom.set_title('Derivative function of f(x)')
ax_bottom.set_xlabel('x')
ax_bottom.set_ylabel('df(x)')
plt.tight_layout() # fits the axes and text nicely into the figure
plt.show()

Polar plot - Put one grid line in bold

I am trying to make use the polar plot projection to make a radar chart. I would like to know how to put only one grid line in bold (while the others should remain standard).
For my specific case, I would like to highlight the gridline associated to the ytick "0".
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
#Variables
sespi = pd.read_csv("country_progress.csv")
labels = sespi.country
progress = sespi.progress
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#Concatenation to close the plots
progress=np.concatenate((progress,[progress[0]]))
angles=np.concatenate((angles,[angles[0]]))
#Polar plot
fig=plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, progress, '.--', linewidth=1, c="g")
#ax.fill(angles, progress, alpha=0.25)
ax.set_thetagrids(angles * 180/np.pi, labels)
ax.set_yticklabels([-200,-150,-100,-50,0,50,100,150,200])
#ax.set_title()
ax.grid(True)
plt.show()
The gridlines of a plot are Line2D objects. Therefore you can't make it bold. What you can do (as shown, in part, in the other answer) is to increase the linewidth and change the colour but rather than plot a new line you can do this to the specified gridline.
You first need to find the index of the y tick labels which you want to change:
y_tick_labels = [-100,-10,0,10]
ind = y_tick_labels.index(0) # find index of value 0
You can then get a list of the gridlines using gridlines = ax.yaxis.get_gridlines(). Then use the index you found previously on this list to change the properties of the correct gridline.
Using the example from the gallery as a basis, a full example is shown below:
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.set_rmax(2)
ax.set_rticks([0.5, 1, 1.5, 2]) # less radial ticks
ax.set_rlabel_position(-22.5) # get radial labels away from plotted line
ax.grid(True)
y_tick_labels = [-100, -10, 0, 10]
ax.set_yticklabels(y_tick_labels)
ind = y_tick_labels.index(0) # find index of value 0
gridlines = ax.yaxis.get_gridlines()
gridlines[ind].set_color("k")
gridlines[ind].set_linewidth(2.5)
plt.show()
Which gives:
It is just a trick, but I guess you could just plot a circle and change its linewidth and color to whatever could be bold for you.
For example:
import matplotlib.pyplot as plt
import numpy as np
Yline = 0
Npoints = 300
angles = np.linspace(0,360,Npoints)*np.pi/180
line = 0*angles + Yline
ax = plt.subplot(111, projection='polar')
plt.plot(angles, line, color = 'k', linewidth = 3)
plt.ylim([-1,1])
plt.grid(True)
plt.show()
In this piece of code, I plot a line using plt.plot between any point of the two vectors angles and line. The former is actually all the angles between 0 and 2*np.pi. The latter is constant, and equal to the 'height' you want to plot that line Yline.
I suggest you try to decrease and increase Npoints while having a look to the documentaion of np.linspace() in order to understand your problem with the roundness of the circle.

Plotting a dashed line on a log-log plot using Python's matplotlib

I'm currently trying to place a horizontal dashed line through my log-log graph using the below code. K2H_HUBp[:,1] and DivR are two [1x6000] arrays. The variable ones is a [1x6000] array full of ones.
The point of this plot is showing how the radii of "potatoes" compares with "sweet potatoes". Hence if they were the same, all of the data points should fall on this y = 1 line.
plt.scatter(K2H_HUBp[:,1],DivR,s=2.5,alpha=0.15,c = '#A9A9A9')
plt.loglog(K2H_HUBp[:,1], ones, '--',dashes=(1, 1),linewidth=0.9,c='#3C323C')
plt.ylim((0.1,10))
plt.xlim((0.35,12))
ax = plt.gca()
ax.tick_params(which = 'both', direction = 'in',right='on',top='on')
ax.set_xscale('log')
ax.set_yscale('log')
plt.ylabel("Radius (Potatos/Sweet Potatos)")
plt.xlabel("Radius (Potatos)")
I'd like the ones line to be equally dashed through the plot. I have the problem of getting this graph here where the lines aren't equally spaced out.
I'm looking for the graph to be very similar to this one (yes this is a linear graph and I'm working with a log-log graph)
I've tried modifying the dashes() parameters with no luck.
Thanks in advance for your guidance. :)
You can either plot it with an other loglog-plot or with a standard plot. Is this code giving you what you're after?
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(2, 1)
x = np.linspace(0.01, 10, 100)
y = x**5
ax1.loglog(x, y, '.')
ax1.plot([x[0], x[-1]], [y[0], y[-1]], '--', label='with plot')
ax1.legend()
ax2.loglog(x, y, '.')
ax2.loglog([x[0], x[-1]], [y[0], y[-1]], '--', label='with loglog')
ax2.legend()
fig.show()
# plt.show()
So it turns out Pyplot has a nifty function called hlines. This function just draws a horizontal line using the following arguments:
matplotlib.pyplot.hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, data=None, **kwargs)
In my case i've now completely removed the code:
plt.loglog(K2H_HUBp[:,1], ones, '--',dashes=(1, 1),linewidth=0.9,c='#3C323C')
and have replaced it with:
plt.hlines(1, 0.001, 20, linestyles='dashed',linewidth=0.9,colors='#3C323C')
plotting a y = 1 line from x 0.001 to x 20. This then gives me my desired result being this graph.
Thanks for all your guidance and I hope this helps someone else in the future!

Creating Matplotlib Graph with common line across subplots in Python

For an upcoming assignment I am require to make a series of diagrams that have two graphs that have a line going across from one graph to the other, colouring an area below that line on the other graph.
As shown in this rough drawing:
This is what I currently have:
From this code:
from matplotlib import pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.plot([0,1,2,3,4,5,6,7,8,9,10], [1,1,1,1,1,0,0,0,0,0,0], '-b')
ax1.plot([0,1,2,3,4,5,6,7,8,9,10], [0,0,0,0,0,1,1,1,1,1,1], '-r')
ax1.set_ylim([0, 1.2])
ax2 = fig.add_subplot(122)
ax2.plot([0,5,10,15,20,25,30,35,40], [1,1,1,1,0,0,0,0,0], '-b')
ax2.plot([0,5,10,15,20,25,30,35,40], [0,0,0,0,1,1,1,1,1], '-r')
ax2.set_ylim([0, 1.2])
plt.show()
Obviously this only generates the two graphs and I have yet been unable to add the line across the two graphs.
I really want to be able to do this with Matplotlib in python with the ability to change the value (45 in the example case) and the coloured area change automatically.
Thanks!
There are three steps: 1st, find the intercept point between green and blue lines in the left panel. 2nd, find the intercept point between the red and lines in the right panel. 3rd, fill the area between. These steps involves np.interp scipy.interpolat scipy.optimize and plt.fill_between, which you should look up.
from matplotlib import pyplot as plt
import numpy as np
import scipy.interpolate as spinp
import scipy.optimize as spop
fig = plt.figure(figsize=(16,4))
ax1 = fig.add_subplot(121)
x = [0,10,20,30,40,50,60,70,80,90,100]
yr = [1,1,1,1,1,0,0,0,0,0,0]
yg = [0,0,0,0,0,1,1,1,1,1,1]
turn_pt = np.interp(45, x, yr) #change 45 to whatever.
yb = [0.,turn_pt,turn_pt]
ax1.plot(x, yr, '-r')
ax1.plot(x, yg, '-g')
xb = [45, 45, 200]
ll = plt.plot(xb,yb, '-b')
ll[0].set_clip_on(False)
plt.axis([0,100,0,1.2])
#the above three lines to draw the line out of the box.
ax2 = fig.add_subplot(122)
yr = [1,1,1,1,0,0,0,0,0]
yg = [0,0,0,0,1,1,1,1,1]
x = [0,5,10,15,20,25,30,35,40]
brk_pt_f = lambda X, V: (spinp.interp1d(x, yr)(X)-V)**2
brk_pt = spop.fmin(brk_pt_f, 17., args=(turn_pt,), disp=0) #17. is you inital guess,
#the above two lines solve for the intersection between the blue line and the red line
zero_pt = 20.
start_pt= 0.
xb = np.hstack((start_pt, brk_pt, zero_pt))
yb = [turn_pt,turn_pt,0]
ax2.plot(x, yr, '-r')
ax2.plot(x, yg, '-g')
ax2.plot(xb, yb, '-b')
ax2.hlines(turn_pt,0, 40, 'b', alpha=0.)
ax2.fill_between(xb, yb, 0, alpha=0.4)
ax2.set_ylim([0, 1.2])
ax2.set_xlim([0, 40])
There are a few solutions to get rid of the top x-axis and the right y-axis, please search older SO posts.
And finally, welcome to SO.

Extending a line segment in matplotlib

Is there a function in matplotlib similar to MATLAB's line extensions?
I am basically looking for a way to extend a line segment to a plot. My current plot looks like this.
After looking at another question and applying the formula, I was able to get it to here, but it still looks messy.
Does anyone have the magic formula here?
Have a go to write your own as I don't think this exists in matplotlib. This is a start, you could improve by adding the semiinfinite etc
import matplotlib.pylab as plt
import numpy as np
def extended(ax, x, y, **args):
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x_ext = np.linspace(xlim[0], xlim[1], 100)
p = np.polyfit(x, y , deg=1)
y_ext = np.poly1d(p)(x_ext)
ax.plot(x_ext, y_ext, **args)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
return ax
ax = plt.subplot(111)
ax.scatter(np.linspace(0, 1, 100), np.random.random(100))
x_short = np.linspace(0.2, 0.7)
y_short = 0.2* x_short
ax = extended(ax, x_short, y_short, color="r", lw=2, label="extended")
ax.plot(x_short, y_short, color="g", lw=4, label="short")
ax.legend()
plt.show()
I just realised you have some red dots on your plots, are those important? Anyway the main point I think you solution so far is missing is to set the plot limits to those that existed before otherwise, as you have found, they get extended.
New in matplotlib 3.3
There is now an axline method to easily extend arbitrary lines:
Adds an infinitely long straight line. The line can be defined either by two points xy1 and xy2
plt.axline(xy1=(0, 1), xy2=(1, 0.5), color='r')
or defined by one point xy1 and a slope.
plt.axline(xy1=(0, 1), slope=-0.5, color='r')
Sample data for reference:
import numpy as np
import matplotlib.pyplot as plt
x, y = np.random.default_rng(123).random((2, 100)) * 2 - 1
m, b = -0.5, 1
plt.scatter(x, y, c=np.where(y > m*x + b, 'r', 'k'))

Categories