Matplotlib darker hsv colormap - matplotlib

I'm using the HSV colormap from matplotlib to plot some vector fields. Is there a way to darken or make smoother the HSV colours so they look more like this
than my original plot colours, which are too bright:

Introduction
Assuming you're trying to plot a pcolor image like this:
import numpy as np
import matplotlib.pyplot as plt
y, x = np.mgrid[slice(-3, 3 + 0.05, 0.05),
slice(-3, 3 + 0.15, 0.15)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
# x and y are bounds, so z should be the value *inside* those bounds.
# Therefore, remove the last value from the z array.
z = z[:-1, :-1]
fig = plt.figure(1)
fig.clf()
ax = plt.gca()
pcol = ax.pcolormesh(x, y, z, cmap=plt.get_cmap('hsv'), )
plt.colorbar(pcol)
ax.set_xlim([-3, 3])
ax.set_ylim([-3, 3])
Your image will be:
Methods
I've written an alternate implementation of the MPL cookbook cmap_map function that modifies colormaps. In addition to support for kwargs and pep8 compliance, this version handles discontinuities in a colormap:
import numpy as np
from matplotlib.colors import LinearSegmentedColormap as lsc
def cmap_map(function, cmap, name='colormap_mod', N=None, gamma=None):
"""
Modify a colormap using `function` which must operate on 3-element
arrays of [r, g, b] values.
You may specify the number of colors, `N`, and the opacity, `gamma`,
value of the returned colormap. These values default to the ones in
the input `cmap`.
You may also specify a `name` for the colormap, so that it can be
loaded using plt.get_cmap(name).
"""
if N is None:
N = cmap.N
if gamma is None:
gamma = cmap._gamma
cdict = cmap._segmentdata
# Cast the steps into lists:
step_dict = {key: map(lambda x: x[0], cdict[key]) for key in cdict}
# Now get the unique steps (first column of the arrays):
step_list = np.unique(sum(step_dict.values(), []))
# 'y0', 'y1' are as defined in LinearSegmentedColormap docstring:
y0 = cmap(step_list)[:, :3]
y1 = y0.copy()[:, :3]
# Go back to catch the discontinuities, and place them into y0, y1
for iclr, key in enumerate(['red', 'green', 'blue']):
for istp, step in enumerate(step_list):
try:
ind = step_dict[key].index(step)
except ValueError:
# This step is not in this color
continue
y0[istp, iclr] = cdict[key][ind][1]
y1[istp, iclr] = cdict[key][ind][2]
# Map the colors to their new values:
y0 = np.array(map(function, y0))
y1 = np.array(map(function, y1))
# Build the new colormap (overwriting step_dict):
for iclr, clr in enumerate(['red', 'green', 'blue']):
step_dict[clr] = np.vstack((step_list, y0[:, iclr], y1[:, iclr])).T
return lsc(name, step_dict, N=N, gamma=gamma)
Implementation
To use it, simply define a function that will modify your RGB colors as you like (values from 0 to 1) and supply it as input to cmap_map. To get colors close to the ones in the images you provided, for example, you could define:
def darken(x, ):
return x * 0.8
dark_hsv = cmap_map(darken, plt.get_cmap('hsv'))
And then modify the call to pcolormesh:
pcol = ax.pcolormesh(x, y, z, cmap=dark_hsv)
If you only wanted to darken the greens in the image, you could do (now all in one line):
pcol = ax.pcolormesh(x, y, z,
cmap=cmap_map(lambda x: x * [1, 0.7, 1],
plt.get_cmap('hsv'))
)

Related

How to plot a 3D function with colors given spacing 2D input

Let's assume I have 3 arrays defined as:
v1=np.linspace(1,100)
v2=np.linspace(1,100)
v3=np.linspace(1,100)   
Then I have a function that takes those 3 values and gives me the desired output, let's assume it is like:
f = (v1 + v2*10)/v3
I want to plot that function on a 3D plot with axis v1,v2,v3 and color it's surface depending on its value.
More than the best way to plot it, I was also interested in how to scroll all the values in the in vectors and build the function point by point.
I have been trying with for loops inside other for loops but I am always getting one error.
MANY THANKS
I tried this but i'm always getting a line instead of a surface
import mpl_toolkits.mplot3d.axes3d as axes3d
import sympy
from sympy import symbols, Function
# Parameters I use in the function
L = 132
alpha = 45*math.pi/180
beta = 0
s,t = symbols('s,t')
z = Function('z')(s,t)
figure = plt.figure(figsize=(8,8))
ax = figure.add_subplot(1, 1, 1, projection='3d')
# experiment with various range of data in x and y
x1 = np.linspace(-40,-40,100)
y1 = np.linspace(-40,40,100)
x,y = np.meshgrid(x1,y1)
# My function Z
c1=math.cos(beta)**2
c2=math.cos(alpha)**2
s1=math.sin(alpha)**2
den = math.sqrt((c1*c2)+s1)
z=L*((math.cos(beta)/den)-1)+(s*(math.sin(alpha)))+(t*(1-math.cos(alpha)))
ax.plot_surface(x,y,z,cmap='rainbow')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
In this example I'm going to show you how to achieve your goal. Specifically, I use Numpy because it supports vectorized operations, hence I avoid for loops.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
import matplotlib.cm as cm
# Parameters I use in the function
L = 132
alpha = 45*np.pi/180
beta = 0
figure = plt.figure()
ax = figure.add_subplot(1, 1, 1, projection='3d')
# experiment with various range of data in x and y
x1 = np.linspace(-40,40,100)
y1 = np.linspace(-40,40,100)
x,y = np.meshgrid(x1,y1)
# My function Z
c1=np.cos(beta)**2
c2=np.cos(alpha)**2
s1=np.sin(alpha)**2
den = np.sqrt((c1*c2)+s1)
z=L*((np.cos(beta)/den)-1)+(x*(np.sin(alpha)))+(y*(1-np.cos(alpha)))
# compute the color values according to some other function
color_values = np.sqrt(x**2 + y**2 + z**2)
# normalize color values between 0 and 1
norm = Normalize(vmin=color_values.min(), vmax=color_values.max())
norm_color_values = norm(color_values)
# chose a colormap and create colors starting from the normalized values
cmap = cm.rainbow
colors = cmap(norm_color_values)
surf = ax.plot_surface(x,y,z,facecolors=colors)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# add a colorbar
figure.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), label="radius")
plt.show()

Surface Plot of a function B(x,y,z)

I have to plot a surface plot which has axes x,y,z and a colormap set by a function of x,y,z [B(x,y,z)].
I have the coordinate arrays:
x=np.arange(-100,100,1)
y=np.arange(-100,100,1)
z=np.arange(-100,100,1)
Moreover, my to-be-colormap function B(x,y,z) is a 3D array, whose B(x,y,z)[i] elements are the (x,y) coordinates at z.
I have tried something like:
Z,X,Y=np.meshgrid(z,x,y) # Z is the first one since B(x,y,z)[i] are the (x,y) coordinates at z.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
img = ax.scatter(Z, X, Y, c=B(x,y,z), cmap=plt.hot())
fig.colorbar(img)
plt.show()
However, it unsurprisingly plots dots, which is not what I want. Rather, I need a surface plot.
The figure I have obtained:
The kind of figure I want:
where the colors are determined by B(x,y,z) for my case.
You have to:
use plot_surface to create a surface plot.
your function B(x, y, z) will be used to compute the color parameter, a number assigned to each face of the surface.
the color parameter must be normalized between 0, 1. We use matplotlib's Normalize to achieve that.
then, you create the colors by applying the colormap to the normalized color parameter.
finally, you create the plot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import Normalize
t = np.linspace(0, 2*np.pi)
p = np.linspace(0, 2*np.pi)
t, p = np.meshgrid(t, p)
r1, r2 = 1, 3
x = (r2 + r1 * np.cos(t)) * np.cos(p)
y = (r2 + r1 * np.cos(t)) * np.sin(p)
z = r1 * np.sin(t)
color_param = np.sin(x / 2) * np.cos(y) + z
cmap = cm.jet
norm = Normalize(vmin=color_param.min(), vmax=color_param.max())
norm_color_param = norm(color_param)
colors = cmap(norm_color_param)
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.plot_surface(x, y, z, facecolors=colors)
ax.set_zlim(-4, 4)
plt.show()

How do I plot a contour from a table of values?

I have a table that has 2 features (x,y) - and a vector with the same length that contains their corresponding values (z).
I'm trying to use matplotlib to print this as a 2D plot but I am get an error:
TypeError: Input z must be at least a (2, 2) shaped array, but has shape (5797, 1)
Is there any way to solve this? (since I am trying to use 1d arrays instead of 2d arrays)
The relevant code:
x, y = train_features[:,0], train_features[:,1]
z = train_predictions.detach()
print(x.size())
print(y.size())
print(z.size())
plt.figure()
CS = plt.contour(x, y, z)
CS = plt.contourf(x, y, z)
plt.clabel(CS, fontsize=8, colors='black')
cbar = plt.colorbar(CS)
The prints that result from the prints commands:
torch.Size([5797])
torch.Size([5797])
torch.Size([5797, 1])
EDIT:
I tried to implement this with a second method:
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
npts = 200
ngridx = 100
ngridy = 200
x = train_features[:,0]
y = train_features[:,1]
z = train_predictions.detach().squeeze()
fig, ax1 = plt.subplots()
# -----------------------
# Interpolation on a grid
# -----------------------
# A contour plot of irregularly spaced data coordinates
# via interpolation on a grid.
# Create grid values first.
xi = np.linspace(1, 10, ngridx)
yi = np.linspace(1, 10, ngridy)
# Perform linear interpolation of the data (x,y)
# on a grid defined by (xi,yi)
triang = tri.Triangulation(x, y)
interpolator = tri.LinearTriInterpolator(triang, z)
Xi, Yi = np.meshgrid(xi, yi)
zi = interpolator(Xi, Yi)
ax1.contour(xi, yi, zi, levels=100, linewidths=0.5, colors='k')
cntr1 = ax1.contourf(xi, yi, zi, levels=14, cmap="RdBu_r")
fig.colorbar(cntr1, ax=ax1)
ax1.plot(x, y, 'ko', ms=3)
ax1.set_title('grid and contour (%d points, %d grid points)' %
(npts, ngridx * ngridy))
But the resulting image was the following:
even though z's values are:
tensor([-0.2434, -0.2155, -0.1900, ..., 64.7516, 65.2064, 65.6612])

Matplotlib: different scale on negative side of the axis

Background
I am trying to show three variables on a single plot. I have connected the three points using lines of different colours based on some other variables. This is shown here
Problem
What I want to do is to have a different scale on the negative x-axis. This would help me in providing positive x_ticks, different axis label and also clear and uncluttered representation of the lines on left side of the image
Question
How to have a different positive x-axis starting from 0 towards negative direction?
Have xticks based on data plotted in that direction
Have a separate xlabel for this new axis
Additional information
I have checked other questions regarding inclusion of multiple axes e.g. this and this. However, these questions did not serve the purpose.
Code Used
font_size = 20
plt.rcParams.update({'font.size': font_size})
fig = plt.figure()
ax = fig.add_subplot(111)
#read my_data from file or create it
for case in my_data:
#Iterating over my_data
if condition1 == True:
local_linestyle = '-'
local_color = 'r'
local_line_alpha = 0.6
elif condition2 == 1:
local_linestyle = '-'
local_color = 'b'
local_line_alpha = 0.6
else:
local_linestyle = '--'
local_color = 'g'
local_line_alpha = 0.6
datapoint = [case[0], case[1], case[2]]
plt.plot(datapoint[0], 0, color=local_color)
plt.plot(-datapoint[2], 0, color=local_color)
plt.plot(0, datapoint[1], color=local_color)
plt.plot([datapoint[0], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.plot([-datapoint[2], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.show()
exit()
You can define a custom scale, where values below zero are scaled differently than those above zero.
import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FuncFormatter
class AsymScale(mscale.ScaleBase):
name = 'asym'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.a = kwargs.get("a", 1)
def get_transform(self):
return self.AsymTrans(self.a)
def set_default_locators_and_formatters(self, axis):
# possibly, set a different locator and formatter here.
fmt = lambda x,pos: "{}".format(np.abs(x))
axis.set_major_formatter(FuncFormatter(fmt))
class AsymTrans(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, a):
mtransforms.Transform.__init__(self)
self.a = a
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x*self.a
def inverted(self):
return AsymScale.InvertedAsymTrans(self.a)
class InvertedAsymTrans(AsymTrans):
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x/self.a
def inverted(self):
return AsymScale.AsymTrans(self.a)
Using this you would provide a scale parameter a that scales the negative part of the axes.
# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(AsymScale)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([-2, 0, 5], [0,1,0])
ax.set_xscale("asym", a=2)
ax.annotate("negative axis", xy=(.25,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
ax.annotate("positive axis", xy=(.75,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
plt.show()
The question is not very clear about what xticks and labels are desired, so I left that out for now.
Here's how to get what you want. This solution uses two twined axes object to get different scaling to the left and right of the origin, and then hides all the evidence:
import matplotlib.pyplot as plt
import matplotlib as mpl
from numbers import Number
tickkwargs = {m+k:False for k in ('bottom','top','left','right') for m in ('','label')}
p = np.zeros((10, 3, 2))
p[:,0,0] -= np.arange(10)*.1 + .5
p[:,1,1] += np.repeat(np.arange(5), 2)*.1 + .3
p[:,2,0] += np.arange(10)*.5 + 2
fig = plt.figure(figsize=(8,6))
host = fig.add_subplot(111)
par = host.twiny()
host.set_xlim(-6, 6)
par.set_xlim(-1, 1)
for ps in p:
# mask the points with negative x values
ppos = ps[ps[:,0] >= 0].T
host.plot(*ppos)
# mask the points with positive x values
pneg = ps[ps[:,0] <= 0].T
par.plot(*pneg)
# hide all possible ticks/notation text that could be set by the second x axis
par.tick_params(axis="both", **tickkwargs)
par.xaxis.get_offset_text().set_visible(False)
# fix the x tick labels so they're all positive
host.set_xticklabels(np.abs(host.get_xticks()))
fig.show()
Output:
Here's what the set of points p I used in the code above look like when plotted normally:
fig = plt.figure(figsize=(8,6))
ax = fig.gca()
for ps in p:
ax.plot(*ps.T)
fig.show()
Output:
The method of deriving a class of mscale.ScaleBase as shown in other answers may be too complicated for your purpose.
You can pass two scale transform functions to set_xscale or set_yscale, something like the following.
def get_scale(a=1): # a is the scale of your negative axis
def forward(x):
x = (x >= 0) * x + (x < 0) * x * a
return x
def inverse(x):
x = (x >= 0) * x + (x < 0) * x / a
return x
return forward, inverse
fig, ax = plt.subplots()
forward, inverse = get_scale(a=3)
ax.set_xscale('function', functions=(forward, inverse)) # this is for setting x axis
# do plotting
More examples can be found in this doc.

getting matplotlib radar plot with pandas

I am trying to go a step further by creating a radar plot like this question states. I using the same source code that the previous question was using, except I'm trying to implement this using pandas dataframe and pivot tables.
import numpy as np
import pandas as pd
from StringIO import StringIO
import matplotlib.pyplot as plt
from matplotlib.projections.polar import PolarAxes
from matplotlib.projections import register_projection
def radar_factory(num_vars, frame='circle'):
"""Create a radar chart with `num_vars` axes."""
# calculate evenly-spaced axis angles
theta = 2 * np.pi * np.linspace(0, 1 - 1. / num_vars, num_vars)
# rotate theta such that the first axis is at the top
theta += np.pi / 2
def draw_poly_frame(self, x0, y0, r):
# TODO: use transforms to convert (x, y) to (r, theta)
verts = [(r * np.cos(t) + x0, r * np.sin(t) + y0) for t in theta]
return plt.Polygon(verts, closed=True, edgecolor='k')
def draw_circle_frame(self, x0, y0, r):
return plt.Circle((x0, y0), r)
frame_dict = {'polygon': draw_poly_frame, 'circle': draw_circle_frame}
if frame not in frame_dict:
raise ValueError, 'unknown value for `frame`: %s' % frame
class RadarAxes(PolarAxes):
"""Class for creating a radar chart (a.k.a. a spider or star chart)
http://en.wikipedia.org/wiki/Radar_chart
"""
name = 'radar'
# use 1 line segment to connect specified points
RESOLUTION = 1
# define draw_frame method
draw_frame = frame_dict[frame]
def fill(self, *args, **kwargs):
"""Override fill so that line is closed by default"""
closed = kwargs.pop('closed', True)
return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
def plot(self, *args, **kwargs):
"""Override plot so that line is closed by default"""
lines = super(RadarAxes, self).plot(*args, **kwargs)
for line in lines:
self._close_line(line)
def _close_line(self, line):
x, y = line.get_data()
# FIXME: markers at x[0], y[0] get doubled-up
if x[0] != x[-1]:
x = np.concatenate((x, [x[0]]))
y = np.concatenate((y, [y[0]]))
line.set_data(x, y)
def set_varlabels(self, labels):
self.set_thetagrids(theta * 180 / np.pi, labels)
def _gen_axes_patch(self):
x0, y0 = (0.5, 0.5)
r = 0.5
return self.draw_frame(x0, y0, r)
register_projection(RadarAxes)
return theta
def day_radar_plot(df):
fig = plt.figure(figsize=(6,6))
#adjust spacing around the subplots
fig.subplots_adjust(wspace=0.25,hspace=0.20,top=0.85,bottom=0.05)
ldo,rup = 0.1,0.8 #leftdown and right up normalized
ax = fig.add_axes([ldo,ldo,rup,rup],polar=True)
N = len(df['Group1'].unique())
theta = radar_factory(N)
polar_df = pd.DataFrame(df.groupby([df['Group1'],df['Type'],df['Vote']]).size())
polar_df.columns = ['Count']
radii = polar_df['Count'].get_values()
names = polar_df.index.get_values()
#get the number of unique colors needed
num_colors_needed = len(names)
#Create the list of unique colors needed for red and blue shades
Rcolors = []
Gcolors = []
for i in range(num_colors_needed):
ri=1-(float(i)/float(num_colors_needed))
gi=0.
bi=0.
Rcolors.append((ri,gi,bi))
for i in range(num_colors_needed):
ri=0.
gi=1-(float(i)/float(num_colors_needed))
bi=0.
Gcolors.append((ri,gi,bi))
from_x = np.linspace(0,0.95,num_colors_needed)
to_x = from_x + 0.05
i = 0
for d,f,R,G in zip(radii,polar_df.index,Rcolors,Gcolors):
i = i+1
if f[2].lower() == 'no':
ax.plot(theta,d,color=R)
ax.fill(theta,d,facecolor=R,alpha=0.25)
#this is where I think i have the issue
ax.axvspan(from_x[i],to_x[i],color=R)
elif f[2].lower() == 'yes':
ax.plot(theta,d,color=G)
ax.fill(theta,d,facecolor=G,alpha=0.25)
#this is where I think i have the issue
ax.axvspan(from_x[i],to_x[i],color=G)
plt.show()
So, let's say I have this StringIO that has a list of Group1 voting either yes or no and they are from a numbered type..these numbers are arbitrary in labeling but just as an example..
fakefile = StringIO("""\
Group1,Type,Vote
James,7,YES\nRachael,7,YES\nChris,2,YES\nRachael,9,NO
Chris,2,YES\nChris,7,NO\nRachael,9,NO\nJames,2,NO
James,7,NO\nJames,9,YES\nRachael,9,NO
Chris,2,YES\nChris,2,YES\nRachael,7,NO
Rachael,7,YES\nJames,9,YES\nJames,9,NO
Rachael,2,NO\nChris,2,YES\nRachael,7,YES
Rachael,9,NO\nChris,9,NO\nJames,7,NO
James,2,YES\nChris,2,NO\nRachael,9,YES
Rachael,9,YES\nRachael,2,NO\nChris,7,YES
James,7,YES\nChris,9,NO\nRachael,9,NO\n
Chris,9,YES
""")
record = pd.read_csv(fakefile, header=0)
day_radar_plot(record)
The error I get is Value Error: x and y must have same first dimension.
As I indicated in my script, I thought I had a solution for it but apparently I'm going by it the wrong way. Does anyone have any advice or guidance?
Since I'm completely lost in what you are trying to do, I will simply provide a solution on how to draw a radar chart from the given data.
It will answer the question how often have people voted Yes or No.
import pandas as pd
import numpy as np
from StringIO import StringIO
import matplotlib.pyplot as plt
fakefile = StringIO("""\
Group1,Type,Vote
James,7,YES\nRachael,7,YES\nChris,2,YES\nRachael,9,NO
Chris,2,YES\nChris,7,NO\nRachael,9,NO\nJames,2,NO
James,7,NO\nJames,9,YES\nRachael,9,NO
Chris,2,YES\nChris,2,YES\nRachael,7,NO
Rachael,7,YES\nJames,9,YES\nJames,9,NO
Rachael,2,NO\nChris,2,YES\nRachael,7,YES
Rachael,9,NO\nChris,9,NO\nJames,7,NO
James,2,YES\nChris,2,NO\nRachael,9,YES
Rachael,9,YES\nRachael,2,NO\nChris,7,YES
James,7,YES\nChris,9,NO\nRachael,9,NO\n
Chris,9,YES""")
df = pd.read_csv(fakefile, header=0)
df["cnt"] = np.ones(len(df))
pt = pd.pivot_table(df, values='cnt', index=['Group1'],
columns=['Vote'], aggfunc=np.sum)
fig = plt.figure()
ax = fig.add_subplot(111, projection="polar")
theta = np.arange(len(pt))/float(len(pt))*2.*np.pi
l1, = ax.plot(theta, pt["YES"], color="C2", marker="o", label="YES")
l2, = ax.plot(theta, pt["NO"], color="C3", marker="o", label="NO")
def _closeline(line):
x, y = line.get_data()
x = np.concatenate((x, [x[0]]))
y = np.concatenate((y, [y[0]]))
line.set_data(x, y)
[_closeline(l) for l in [l1,l2]]
ax.set_xticks(theta)
ax.set_xticklabels(pt.index)
plt.legend()
plt.title("How often have people votes Yes or No?")
plt.show()