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.
Related
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)
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!
This question already has answers here:
Shifted colorbar matplotlib
(1 answer)
Defining the midpoint of a colormap in matplotlib
(10 answers)
Closed 5 years ago.
For my current project I need a heat map. The heat map needs a scalable color palette, because the values are interesting only in a small range. That means, even if I have values from 0 to 1, interesting is only the part between 0.6 and 0.9; so I would like to scale the heat map colors accordingly, plus show the scale next to the chart.
In Matplotlib I had no way of setting the mid point of a color palette except for overloading the original class, like shown here in the matplotlib guide.
This is exactly what I need, but without the disadvantages of the unclean data structure in Matplotlib.
So I tried Bokeh.
In five minutes I achieved more than with Matplotlib in an hour, however, I got stuck when I wanted to show the color scale next to the heatmap and when I wanted to change the scale of the color palette.
So, here are my questions:
How can I scale the color palette in Bokeh or Matplotlib?
Is there a way to display the annotated color bar next to the heatmap?
import pandas
scores_df = pd.DataFrame(myScores, index=c_range, columns=gamma_range)
import bkcharts
from bokeh.palettes import Inferno256
hm = bkcharts.HeatMap(scores_df, palette=Inferno256)
# here: how to insert a color bar?
# here: how to correctly scale the inferno256 palette?
hm.ylabel = "C"
hm.xlabel = "gamma"
bkcharts.output_file('heatmap.html')
Following Aarons tips, i now implemented it as follows:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from bokeh.palettes import Inferno256
def print_scores(scores, gamma_range, C_range):
# load a color map
# find other colormaps here
# https://docs.bokeh.org/en/latest/docs/reference/palettes.html
cmap = colors.ListedColormap(Inferno256, len(Inferno256))
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
# adjust lower, midlle and upper bound of the colormap
cmin = np.percentile(scores, 10)
cmid = np.percentile(scores, 75)
cmax = np.percentile(scores, 99)
bounds = np.append(np.linspace(cmin, cmid), np.linspace(cmid, cmax))
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=len(Inferno256))
pcm = ax.pcolormesh(np.log10(gamma_range),
np.log10(C_range),
scores,
norm=norm,
cmap=cmap)
fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')
plt.show()
ImportanceOfBeingErnest correctly pointed out that my first comment wasn't entirely clear (or accurately worded)..
Most plotting functions in mpl have a kwarg: norm= this denotes a class (subclass of mpl.colors.Normalize) that will map your array of data to the values [0 - 1] for the purpose of mapping to the colormap, but not actually impact the numerical values of the data. There are several built in subclasses, and you can also create your own. For this application, I would probably utilize BoundaryNorm. This class maps N-1 evenly spaced colors to the space between N discreet boundaries.
I have modified the example slightly to better fit your application:
#adaptation of https://matplotlib.org/users/colormapnorms.html#discrete-bounds
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.mlab import bivariate_normal
#example data
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
Z1 = Z1/0.03
'''
BoundaryNorm: For this one you provide the boundaries for your colors,
and the Norm puts the first color in between the first pair, the
second color between the second pair, etc.
'''
fig, ax = plt.subplots(3, 1, figsize=(8, 8))
ax = ax.flatten()
# even bounds gives a contour-like effect
bounds = np.linspace(-1, 1)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[0].pcolormesh(X, Y, Z1,
norm=norm,
cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical')
# clipped bounds emphasize particular region of data:
bounds = np.linspace(-.2, .5)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[1].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
# now if we want 0 to be white still, we must have 0 in the middle of our array
bounds = np.append(np.linspace(-.2, 0), np.linspace(0, .5))
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[2].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
fig.show()
I have the following plot:
I would like to make the x-axis ticks more readable by rotating the ticks by ~40 degrees. So from:
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='90', fontsize=16)
To:
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='40', fontsize=16)
When I do this, though, I get some crazy spacing issues:
(ignore the change in color...)
What's causing this problem? How can I fix it? Here's a minimum working example:
import matplotlib.pyplot as plt
import numpy as np
# Z is your data set
N = 100
height = df_100.shape[0]
width = df_100.shape[1]
# Z = np.random.random((100, 29))
# G is a NxNx3 matrix
G = np.zeros((height,width,3))
# Where we set the RGB for each pixel
G[Z>0.5] = [1, 1, 1]
G[Z<0.5] = [0.25, 0.25, 0.25]
fig, ax = plt.subplots(figsize=(20, 10))
ax.imshow(G, interpolation='none')
ax.set_aspect('auto')
ax.grid(None)
ax.xaxis.tick_top()
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='45', fontsize=16)
plt.yticks([0, df_100.shape[0] - 1], [1, df_100.shape[0]], fontsize=20)
plt.tight_layout()
plt.show()
If xticklabels are of the same length, you won't have this kind of problem. But given different length of labels, you can encounter this kind of problem. Because the default rotation is from the center of the xlabel string. So you can try to set the rotation anchor properly from
['right', 'center', 'left'].
ha = 'left' # or 'right'. Experiment with it.
ax.set_xticks(x) # set tick location
ax.set_xticklabels(xlabels, rotation=40, ha=ha) # rotate the labels with proper anchoring.
I was wondering, is it possible to offset the start of the radial axis or move it outside of the graph.
This is what I'm hoping to achieve:
And this is what I have for now.
I have read the documentation and different topics on SO, but I couldn't find anything helpful. Does that mean that it is not even possible if it is not mentioned anywhere.
Thank you in advance.
EDIT (added snippet of a code used to create the plot):
ax = fig.add_subplot(111, projection='polar')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.plot(X,lines[li]*yScalingFactor,label=linelabels[li],color=color,linestyle=ls)
To offset the start of the radial axis:
EDIT: As of Matplotlib 2.2.3 there's a new Axes method called set_rorigin which does exactly that. You call it with the theoretical radial coordinate of the origin. So if you call ax.set_ylim(0, 2) and ax.set_rorigin(-1), the radius of the center circle will be a third of the radius of the plot.
A quick and dirty workaround for Matplotlib < 2.2.3 is to set the lower radial axis limit to a negative value and hide the inner part of the plot behind a circle:
import numpy as np
import matplotlib.pyplot as plt
CIRCLE_RES = 36 # resolution of circle inside
def offset_radial_axis(ax):
x_circle = np.linspace(0, 2*np.pi, CIRCLE_RES)
y_circle = np.zeros_like(x_circle)
ax.fill(x_circle, y_circle, fc='white', ec='black', zorder=2) # circle
ax.set_rmin(-1) # needs to be after ax.fill. No idea why.
ax.set_rticks([tick for tick in ax.get_yticks() if tick >= 0])
# or set the ticks manually (simple)
# or define a custom TickLocator (very flexible)
# or leave out this line if the ticks are fully behind the circle
To add a scale outside the plot:
You can add an extra axes object in the upper half of the other axes and use its yaxis:
X_OFFSET = 0 # to control how far the scale is from the plot (axes coordinates)
def add_scale(ax):
# add extra axes for the scale
rect = ax.get_position()
rect = (rect.xmin-X_OFFSET, rect.ymin+rect.height/2, # x, y
rect.width, rect.height/2) # width, height
scale_ax = ax.figure.add_axes(rect)
# hide most elements of the new axes
for loc in ['right', 'top', 'bottom']:
scale_ax.spines[loc].set_visible(False)
scale_ax.tick_params(bottom=False, labelbottom=False)
scale_ax.patch.set_visible(False) # hide white background
# adjust the scale
scale_ax.spines['left'].set_bounds(*ax.get_ylim())
# scale_ax.spines['left'].set_bounds(0, ax.get_rmax()) # mpl < 2.2.3
scale_ax.set_yticks(ax.get_yticks())
scale_ax.set_ylim(ax.get_rorigin(), ax.get_rmax())
# scale_ax.set_ylim(ax.get_ylim()) # Matplotlib < 2.2.3
Putting it all together:
(The example is taken from the Matplotlib polar plot demo)
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.plot(theta, r)
ax.grid(True)
ax.set_rorigin(-1)
# offset_radial_axis(ax) # Matplotlib < 2.2.3
add_scale(ax)
ax.set_title("A line plot on an offset polar axis", va='bottom')
plt.show()
I am not sure if the polar plot can be adjusted like that. But here is a work-around, based on the last example given here: Floating Axes.
I have included explanatory comments in the code, if you copy/paste it, it should run as-is:
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, \
MaxNLocator, DictFormatter
import numpy as np
import matplotlib.pyplot as plt
# generate 100 random data points
# order the theta coordinates
# theta between 0 and 2*pi
theta = np.random.rand(100)*2.*np.pi
theta = np.sort(theta)
# "radius" between 0 and a max value of 40,000
# as roughly in your example
# normalize the r coordinates and offset by 1 (will be clear later)
MAX_R = 40000.
radius = np.random.rand(100)*MAX_R
radius = radius/np.max(radius) + 1.
# initialize figure:
fig = plt.figure()
# set up polar axis
tr = PolarAxes.PolarTransform()
# define angle ticks around the circumference:
angle_ticks = [(0, r"$0$"),
(.25*np.pi, r"$\frac{1}{4}\pi$"),
(.5*np.pi, r"$\frac{1}{2}\pi$"),
(.75*np.pi, r"$\frac{3}{4}\pi$"),
(1.*np.pi, r"$\pi$"),
(1.25*np.pi, r"$\frac{5}{4}\pi$"),
(1.5*np.pi, r"$\frac{3}{2}\pi$"),
(1.75*np.pi, r"$\frac{7}{4}\pi$")]
# set up ticks and spacing around the circle
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
# set up grid spacing along the 'radius'
radius_ticks = [(1., '0.0'),
(1.5, '%i' % (MAX_R/2.)),
(2.0, '%i' % (MAX_R))]
grid_locator2 = FixedLocator([v for v, s in radius_ticks])
tick_formatter2 = DictFormatter(dict(radius_ticks))
# set up axis:
# tr: the polar axis setup
# extremes: theta max, theta min, r max, r min
# the grid for the theta axis
# the grid for the r axis
# the tick formatting for the theta axis
# the tick formatting for the r axis
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=(2.*np.pi, 0, 2, 1),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2)
ax1 = floating_axes.FloatingSubplot(fig, 111, grid_helper=grid_helper)
fig.add_subplot(ax1)
# create a parasite axes whose transData in RA, cz
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch # for aux_ax to have a clip path as in ax
ax1.patch.zorder=0.9 # but this has a side effect that the patch is
# drawn twice, and possibly over some other
# artists. So, we decrease the zorder a bit to
# prevent this.
# plot your data:
aux_ax.plot(theta, radius)
plt.show()
This will generate the following plot:
You'd have to tweak the axis labels to meet your demands.
I scaled the data because otherwise the same issue as with your plot would have occurred - the inner, empty circle would have been scaled to a dot. You might try the scaling with your polar plot and just put custom labels on the radial axis to achieve a similar effect.