Matplotlib polar histogram has shifted bins - python

I am using Matplotlib to create a polar histogram. The correct data for the histogram, in radians is below:
The alignment is [0,0.78) radians (aka 0 to 45 degrees) [0.78,...) (45 to 90 degrees) etc.
However, when plotting it with the polar plot, the bin is now centred on 0 rather than starting at 0. Yet the histogram count is the same.
If it was actually (-22.5 degrees, 22.5 degrees), then the histogram distribution would be different. Therefore it seems like the polar plot axis label is incorrect - that is, the 0 degrees label should actually be 22.5 degrees (or alternatively the 0 degrees label should be shifted 22.5 degrees clockwise).
Is there any way anyone knows how to achieve this?
Relevant Code:
Histogram
bins = np.linspace(-np.pi, np.pi, bins_number + 1)
n, _, _ = plt.hist(angles, bins) # Create histogram
plt.show()
Note, angles is a list of angles in radians
Polar
plt.clf()
width = 2 * np.pi / bins_number
ax = plt.subplot(1, 1, 1, projection='polar')
bars = ax.bar(bins[:bins_number], n, width=width, bottom=0.0)
for bar in bars:
bar.set_alpha(0.5)
plt.show()
Complete Code
import numpy as np
import matplotlib.pyplot as plt
import csv
with open('circ2.csv', 'r') as f:
reader=csv.reader(f)
angles=[] # Initialise empty list
next(reader) # Skip header line
for row in reader:
angle = float(row[1]) # Angle is in the second column of the row
angles.append(angle)
bins_number = 8 # the [-180, 180) interval will be subdivided into this
bins = np.linspace(-np.pi, np.pi, bins_number + 1)
n, _, _ = plt.hist(angles, bins) # Create histogram
plt.clf()
width = 2 * np.pi / bins_number
ax = plt.subplot(1, 1, 1, projection='polar')
bars = ax.bar(bins[:bins_number], n, width=width, bottom=0.0)
for bar in bars:
bar.set_alpha(0.5)
plt.show()
Thanks

Solved by adding align='edge' in the bar plot. That is:
bars = ax.bar(bins[:bins_number], n, width=width, bottom=0.0, align='edge')
Thanks to ImportanceOfBeingErnest

Related

Custom Histogram Normalization in matplotlib

I am trying to make a normalized histogram in matplotlib, however I want it normalized such that the total area will be 1000. Is there a way to do this?
I know to get it normalized to 1, you just have to include density=True,stacked=True in the argument of plt.hist(). An equivalent solution would be to do this and multiply the height of each column by 1000, if that would be more doable than changing what the histogram is normalized to.
Thank you very much in advance!
The following approach uses np.histogram to calculate the counts for each histogram bin. Using 1000 / total_count / bin_width as normalization factor, the total area will be 1000. On the contrary, to get the sum of all bar heights to be 1000, a factor of 1000 / total_count would be needed.
plt.bar is used to display the end result.
The example code calculates the same combined histogram with density=True, to compare it with the new histogram summing to 1000.
import matplotlib.pyplot as plt
import numpy as np
data = [np.random.randn(100) * 5 + 10, np.random.randn(300) * 4 + 14, np.random.randn(100) * 3 + 17]
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 4))
ax1.hist(data, stacked=True, density=True)
ax1.set_title('Histogram with density=True')
xmin = min([min(d) for d in data])
xmax = max([max(d) for d in data])
bins = np.linspace(xmin, xmax, 11)
bin_width = bins[1] - bins[0]
counts = [np.histogram(d, bins=bins)[0] for d in data]
total_count = sum([sum(c) for c in counts])
# factor = 1000 / total_count # to sum to 1000
factor = 1000 / total_count / bin_width # for an area of 1000
thousands = [c * factor for c in counts]
bottom = 0
for t in thousands:
ax2.bar(bins[:-1], t, bottom=bottom, width=bin_width, align='edge')
bottom += t
ax2.set_title('Histogram with total area of 1000')
plt.show()
An easy way to do this is to set up a second y-axis whose tick labels are the original multiplied by 1000, then hide the original axis' ticks:
import matplotlib.pyplot as plt
import numpy as np
data = [np.random.randn(5000)]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
#hist returns a tuple that contains a list of y values at its 0 index:
y,_,_ = ax1.hist(data, density=True, bins=10, edgecolor = 'black')
#find max y value of histogram and multiply by 1000:
max_y = np.round(y.max(),1)*1000
#set up the second y-axis ticks as increments of max_y:
ax2.set_ylim(0,max_y)
ax2.set_yticks(np.linspace(0, max_y, 9))
#hide original y-axis ticks:
ax1.axes.yaxis.set_ticks([])
plt.show()

Extending colorbar to include out of range data

I was trying to make a Polar heatmap using the following code.
# Plotting the polar plot
from matplotlib.colorbar import ColorbarBase
from matplotlib.colors import LogNorm
import matplotlib.pyplot as plt
cmap = obspy_sequential
# Have defined the variables to be used for pointing to the coordinates
# baz is angular, slow is radial, abs_power is the value at every co-ordinate
# Choose number of fractions in plot (desirably 360 degree/N is an integer!)
N = 72
N2 = 30
abins = np.arange(N + 1) * 360. / N
sbins = np.linspace(0, 3, N2 + 1)
# Sum rel power in bins given by abins and sbins
hist, baz_edges, sl_edges = \
np.histogram2d(baz, slow, bins=[abins, sbins], weights=abs_power)
# Transform to radian
baz_edges = np.radians(baz_edges)
# Add polar and colorbar axes
fig = plt.figure(figsize=(8, 8))
cax = fig.add_axes([0.85, 0.2, 0.05, 0.5])
ax = fig.add_axes([0.10, 0.1, 0.70, 0.7], polar=True)
ax.set_theta_direction(-1)
ax.set_theta_zero_location("N")
dh = abs(sl_edges[1] - sl_edges[0])
dw = abs(baz_edges[1] - baz_edges[0])
# Circle through backazimuth
for i, row in enumerate(hist):
bars = ax.bar((i * dw) * np.ones(N2),
height=dh * np.ones(N2),
width=dw, bottom=dh * np.arange(N2),color=cmap(row / hist.max()))
ax.set_xticks(np.linspace(0, 2 * np.pi, 10, endpoint=False))
ax.set_yticklabels(velocity)
ax.set_ylim(0, 3)
[i.set_color('white') for i in ax.get_yticklabels()]
ColorbarBase(cax, cmap=cmap,
norm=LogNorm(vmin=hist.min(),vmax=hist.max()))
plt.show()
I am creating multiple plots like this and thus I need to extend the range of the colorbar beyond the maximum of the abs_power data range.
I tried changing the vmax and vmin to the maximum-minimum target numbers I want, but it plots out the exact same plot every single time. The maximum value on the colorbar keeps changing but the plot does not change. Why is this happening?
Here is how it looks,
Here the actual maximum power is way lesser than the maximum specified in the colorbar. Still a bright yellow spot is visible.
PS : I get this same plot for any vmax,vmin values I provide.
Changing the colorbar doesn't have an effect on the main plot. You'd need to change the formula used in color=cmap(row / hist.max()) to change the barplot. The 'norm' is just meant for this task. The norm maps the range of numbers to the interval [0, 1]. Every value that is mapped to a value higher than 1 (i.e. a value higher than hist.max() in the example), gets assigned the highest color.
To have the colorbar reflect the correct information, you'd need the same cmap and same norm for both the plot and the colorbar:
my_norm = LogNorm(vmin=hist.min(),vmax=hist.max())
for i, row in enumerate(hist):
bars = ax.bar((i * dw) * np.ones(N2),
height=dh * np.ones(N2),
width=dw, bottom=dh * np.arange(N2),color=cmap(my_norm(row)))
and
ColorbarBase(cax, cmap=cmap, norm=my_norm)
On the other hand, if you don't want the yellow color to show up, you could try something like my_norm = LogNorm(vmin=hist.min(), vmax=hist.max()*100) in the code above.
Instead of creating the colorbar via ColorbarBase, it can help to use a standard plt.colorbar(), but with a ScalarMappable that indicates the color map and the norm used. In case of a LogNorm this will show the ticks in log format.
from matplotlib.cm import ScalarMappable
plt.colorbar(ScalarMappable(cmap=cmap, norm=my_norm), ax=ax, cax=cax)

scatterplot and combined polar histogram in matplotlib

I am attempting to produce a plot like this which combines a cartesian scatter plot and a polar histogram. (Radial lines optional)
A similar solution (by Nicolas Legrand) exists for looking at differences in x and y (code here), but we need to look at ratios (i.e. x/y).
More specifically, this is useful when we want to look at the relative risk measure which is the ratio of two probabilities.
The scatter plot on it's own is obviously not a problem, but the polar histogram is more advanced.
The most promising lead I have found is this central example from the matplotlib gallery here
I have attempted to do this, but have run up against the limits of my matplotlib skills. Any efforts moving towards this goal would be great.
I'm sure that others will have better suggestions, but one method that gets something like you want (without the need for extra axes artists) is to use a polar projection with a scatter and bar chart together. Something like
import matplotlib.pyplot as plt
import numpy as np
x = np.random.uniform(size=100)
y = np.random.uniform(size=100)
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi, bins=np.linspace(0, np.pi/2, 21), density=True)
colors = plt.cm.Spectral(h / h.max())
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, r, marker='.')
ax.bar(b[:-1], h, width=b[1:] - b[:-1],
align='edge', bottom=np.max(r) + 0.2, color=colors)
# Cut off at 90 degrees
ax.set_thetamax(90)
# Set the r grid to cover the scatter plot
ax.set_rgrids([0, 0.5, 1])
# Let's put a line at 1 assuming we want a ratio of some sort
ax.set_thetagrids([45], [1])
which will give
It is missing axes labels and some beautification, but it might be a place to start. I hope it is helpful.
You can use two axes on top of each other:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.8,.8], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-1)
ax2.set_thetamax(90)
plt.show()
Ok. Thanks to the answer from Nicolas, and the answer from tomjn I have a working solution :)
import numpy as np
import matplotlib.pyplot as plt
# Scatter data
n = 50
x = 0.3 + np.random.randn(n)*0.1
y = 0.4 + np.random.randn(n)*0.02
def radial_corner_plot(x, y, n_hist_bins=51):
"""Scatter plot with radial histogram of x/y ratios"""
# Axis setup
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.6,.6], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-20)
ax2.set_thetamax(90)
# define useful constant
offset_in_radians = np.pi/4
def rotate_hist_axis(ax):
"""rotate so that 0 degrees is pointing up and right"""
ax.set_theta_offset(offset_in_radians)
ax.set_thetamin(-45)
ax.set_thetamax(45)
return ax
# Convert scatter data to histogram data
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi,
bins=np.linspace(0, np.pi/2, n_hist_bins),
density=True)
# SCATTER PLOT -------------------------------------------------------
ax1.scatter(x,y)
ax1.set(xlim=[0, 1], ylim=[0, 1], xlabel="x", ylabel="y")
ax1.spines['right'].set_visible(False)
ax1.spines['top'].set_visible(False)
# HISTOGRAM ----------------------------------------------------------
ax2 = rotate_hist_axis(ax2)
# rotation of axis requires rotation in bin positions
b = b - offset_in_radians
# plot the histogram
bars = ax2.bar(b[:-1], h, width=b[1:] - b[:-1], align='edge')
def update_hist_ticks(ax, desired_ratios):
"""Update tick positions and corresponding tick labels"""
x = np.ones(len(desired_ratios))
y = 1/desired_ratios
phi = np.arctan2(y,x) - offset_in_radians
# define ticklabels
xticklabels = [str(round(float(label), 2)) for label in desired_ratios]
# apply updates
ax2.set(xticks=phi, xticklabels=xticklabels)
return ax
ax2 = update_hist_ticks(ax2, np.array([1/8, 1/4, 1/2, 1, 2, 4, 8]))
# just have radial grid lines
ax2.grid(which="major", axis="y")
# remove bin count labels
ax2.set_yticks([])
return (fig, [ax1, ax2])
fig, ax = radial_corner_plot(x, y)
Thanks for the pointers!

Set radial axis on Matplotlib polar plots

I'm plotting an azimuth-elevation curve on a polar plot where the elevation is the radial component. By default, Matplotlib plots the radial value from 0 in the center to 90 on the perimeter. I want to reverse that so 90 degrees is at the center. I tried setting the limits with a call to ax.set_ylim(90,0) but this results in a LinAlgError exception being thrown. ax is the axes object obtained from a call to add_axes.
Can this be done and, if so, what must I do?
Edit: Here is what I'm using now. The basic plotting code was taken from one of the Matplotlib examples
# radar green, solid grid lines
rc('grid', color='#316931', linewidth=1, linestyle='-')
rc('xtick', labelsize=10)
rc('ytick', labelsize=10)
# force square figure and square axes looks better for polar, IMO
width, height = matplotlib.rcParams['figure.figsize']
size = min(width, height)
# make a square figure
fig = figure(figsize=(size, size))
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], projection='polar', axisbg='#d5de9c')
# Adjust radius so it goes 90 at the center to 0 at the perimeter (doesn't work)
#ax.set_ylim(90, 0)
# Rotate plot so 0 degrees is due north, 180 is due south
ax.set_theta_zero_location("N")
obs.date = datetime.datetime.utcnow()
az,el = azel_calc(obs, ephem.Sun())
ax.plot(az, el, color='#ee8d18', lw=3)
obs.date = datetime.datetime.utcnow()
az,el = azel_calc(obs, ephem.Moon())
ax.plot(az, el, color='#bf7033', lw=3)
ax.set_rmax(90.)
grid(True)
ax.set_title("Solar Az-El Plot", fontsize=10)
show()
The plot that results from this is
I managed to put he radial axis inverted. I had to remap the radius, in order to match the new axis:
fig = figure()
ax = fig.add_subplot(1, 1, 1, polar=True)
def mapr(r):
"""Remap the radial axis."""
return 90 - r
r = np.arange(0, 90, 0.01)
theta = 2 * np.pi * r / 90
ax.plot(theta, mapr(r))
ax.set_yticks(range(0, 90, 10)) # Define the yticks
ax.set_yticklabels(map(str, range(90, 0, -10))) # Change the labels
Note that is just a hack, the axis is still with the 0 in the center and 90 in the perimeter. You will have to use the mapping function for all the variables that you are plotting.

Half or quarter polar plots in Matplotlib?

I am trying to make a polar plot that goes 180 degrees instead of 360 in Matplotlib similar to http://www.mathworks.com/matlabcentral/fileexchange/27230-half-polar-coordinates-figure-plot-function-halfpolar in MATLAB. Any ideas?
The following works in matplotlib 2.1 or higher. There is also an example on the matplotlib page.
You may use a usual polar plot, ax = fig.add_subplot(111, polar=True) and confine the theta range. For a half polar plot
ax.set_thetamin(0)
ax.set_thetamax(180)
or for a quarter polar plot
ax.set_thetamin(0)
ax.set_thetamax(90)
Complete example:
import matplotlib.pyplot as plt
import numpy as np
theta = np.linspace(0,np.pi)
r = np.sin(theta)
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r, c=r, s=10, cmap='hsv', alpha=0.75)
ax.set_thetamin(0)
ax.set_thetamax(180)
plt.show()
The example code in official matplotlib documentation may obscure things a little bit if someone just needs a simple quarter of half plot.
I wrote a code snippet that may help someone who is not that familiar with AxisArtists here.
"""
Reference:
1. https://gist.github.com/ycopin/3342888
2. http://matplotlib.org/mpl_toolkits/axes_grid/users/overview.html#axisartist
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.floating_axes import GridHelperCurveLinear, FloatingSubplot
import mpl_toolkits.axisartist.grid_finder as gf
def generate_polar_axes():
polar_trans = PolarAxes.PolarTransform()
# Setup the axis, here we map angles in degrees to angles in radius
phi_degree = np.arange(0, 90, 10)
tlocs = phi_degree * np.pi / 180
gl1 = gf.FixedLocator(tlocs) # Positions
tf1 = gf.DictFormatter(dict(zip(tlocs, map(str, phi_degree))))
# Standard deviation axis extent
radius_min = 0
radius_max = 1
# Set up the axes range in the parameter "extremes"
ghelper = GridHelperCurveLinear(polar_trans, extremes=(0, np.pi / 2, # 1st quadrant
radius_min, radius_max),
grid_locator1=gl1,
tick_formatter1=tf1,
)
figure = plt.figure()
floating_ax = FloatingSubplot(figure, 111, grid_helper=ghelper)
figure.add_subplot(floating_ax)
# Adjust axes
floating_ax.axis["top"].set_axis_direction("bottom") # "Angle axis"
floating_ax.axis["top"].toggle(ticklabels=True, label=True)
floating_ax.axis["top"].major_ticklabels.set_axis_direction("top")
floating_ax.axis["top"].label.set_axis_direction("top")
floating_ax.axis["top"].label.set_text("angle (deg)")
floating_ax.axis["left"].set_axis_direction("bottom") # "X axis"
floating_ax.axis["left"].label.set_text("radius")
floating_ax.axis["right"].set_axis_direction("top") # "Y axis"
floating_ax.axis["right"].toggle(ticklabels=True)
floating_ax.axis["right"].major_ticklabels.set_axis_direction("left")
floating_ax.axis["bottom"].set_visible(False) # Useless
# Contours along standard deviations
floating_ax.grid(True)
floating_ax.set_title("Quarter polar plot")
data_ax = floating_ax.get_aux_axes(polar_trans) # return the axes that can be plotted on
return figure, data_ax
if __name__ == "__main__":
# Plot data onto the defined polar axes
fig, ax = generate_polar_axes()
theta = np.random.rand(10) * np.pi / 2
radius = np.random.rand(10)
ax.scatter(theta, radius)
fig.savefig("test.png")

Categories