Forcing a colormap to be square - python

I am working on Python with the matplotlib library and I have a problem with color maps dimension.
I have some target variable target that depends on two variables x and y - i.e. my data target is a matrix with the variable x being rows and y columns. I want to represent target in a color map with respect to x and y. The problem is that I have more values for x than for y, hence if I plot a color map I get a rectangle - a rather ugly one because I have many more values for x than for y.
I would rather have rectangular "pixels" in the color map and a square map, rather than square "pixels" but a rectangular color map - or at least I would like to compare the two visualizations.
My question is: how can I force the color map to be square?
This is my current code - the cmap variable simply allows me to define my custom color scale:
import matplotlib.pyplot as plt
import matplotlib.pyplot as clr
target = ...
cmap = clr.LinearSegmentedColormap.from_list('custom blue',
['#DCE6F1','#244162'],
N=128)
plt.matshow(target, cmap=cmap)
plt.colorbar(cmap='custom blue')

What's your matplotlib version?
If it's newer than 1.1.0 then you can try this:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(np.random.rand(32, 64), cmap='rainbow', aspect=2)
plt.show()
This gives you rectangular pixels with square figure shape.
You should replace np.random.rand(32, 64) by your data, and define the aspect ratio you like. Also, please refer to this post. There are other solutions you might be interested in.

Andreas's answer works on my computer. You might see a slightly different result that with your code because imshow() will by default interpolate the image, therefore the picture will be different compared to matshow(). But the aspect parameter works on both. So either feed it to matshow(), or disable the interpolation in imshow():
fig = plt.figure()
ax = fig.add_subplot(111)
ax.matshow(np.random.rand(32, 64), cmap='rainbow', aspect=2)
plt.show()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(np.random.rand(32, 64), cmap='rainbow', aspect=2, interpolation="None")
plt.show()

Related

Make all data points of a matplotlib plot homogeneously transparent

I'd like to plot two scatter plots into the same Axes and turn the upper one's data points transparent such that the other plot shines through. However, I want the whole upper plot to have a homogeneous transparency level, such that superimposed markers of the upper plot do not add up their opacity as they would do if I simply set alpha=0.5.
In other words, I'd like both scatter plots to be rendered first and being set to one constant transparency level. Technically this should be possible for both raster and vector graphics (as SVG supports layer transparency, afaik), but either would be fine for me.
Here is some example code that displays what I do not want to achieve. ;)
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax = fig.gca()
s1 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="b", edgecolors="none")
s2 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="g", edgecolors="none")
s2.set_alpha(0.5) # sadly the same as setting `alpha=0.5`
fig.show() # or display(fig)
I'd like the green markers around (2,2) to not be darker where they superimpose, for example. Is this possible with matplotlib?
Thanks for your time! :)
After searching some more, I found related questions and two solutions, of which at least one kind of works for me:
As I hoped one can render one layer and then afterwards blend them together like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax1 = fig.gca()
s1 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#3355ff", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
img1 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
ax1.clear()
s2 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#11aa44", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
img2 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
fig.clf()
plt.imshow(np.minimum(img1,img2))
plt.subplots_adjust(0, 0, 1, 1)
plt.axis("off")
plt.show()
I may have to come up with better methods than just taking the np.minimum of both layers to keep more color options (and probably save the axes and labels to a third layer).
I did not try mplcairo as suggested in the other linked answer as it has too many dependencies for my use case (my solution should be portable).
I am still happy for additional input. :)

Change colour scheme label to log scale without changing the axis in matplotlib

I am quite new to python programming. I have a script with me that plots out a heat map using matplotlib. Range of X-axis value = (-180 to +180) and Y-axis value =(0 to 180). The 2D heatmap colours areas in Rainbow according to the number of points occuring in a specified area in the x-y graph (defined by the 'bin' (see below)).
In this case, x = values_Rot and y = values_Tilt (see below for code).
As of now, this script colours the 2D-heatmap in the linear scale. How do I change this script such that it colours the heatmap in the log scale? Please note that I only want to change the heatmap colouring scheme to log-scale, i.e. only the number of points in a specified area. The x and y-axis stay the same in linear scale (not in logscale).
A portion of the code is here.
rot_number = get_header_number(headers, AngleRot)
tilt_number = get_header_number(headers, AngleTilt)
psi_number = get_header_number(headers, AnglePsi)
values_Rot = []
values_Tilt = []
values_Psi = []
for line in data:
try:
values_Rot.append(float(line.split()[rot_number]))
values_Tilt.append(float(line.split()[tilt_number]))
values_Psi.append(float(line.split()[psi_number]))
except:
print ('This line didnt work, it may just be a blank space. The line is:' + line)
# Change the values here if you want to plot something else, such as psi.
# You can also change how the data is binned here.
plt.hist2d(values_Rot, values_Tilt, bins=25,)
plt.colorbar()
plt.show()
plt.savefig('name_of_output.png')
You can use a LogNorm for the colors, using plt.hist2d(...., norm=LogNorm()). Here is a comparison.
To have the ticks in base 2, the developers suggest adding the base to the LogLocator and the LogFormatter. As in this case the LogFormatter seems to write the numbers with one decimal (.0), a StrMethodFormatter can be used to show the number without decimals. Depending on the range of numbers, sometimes the minor ticks (shorter marker lines) also get a string, which can be suppressed assigning a NullFormatter for the minor colorbar ticks.
Note that base 2 and base 10 define exactly the same color transformation. The position and the labels of the ticks are different. The example below creates two colorbars to demonstrate the different look.
import matplotlib.pyplot as plt
from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
import numpy as np
from copy import copy
# create some toy data for a standalone example
values_Rot = np.random.randn(100, 10).cumsum(axis=1).ravel()
values_Tilt = np.random.randn(100, 10).cumsum(axis=1).ravel()
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15, 4))
cmap = copy(plt.get_cmap('hot'))
cmap.set_bad(cmap(0))
_, _, _, img1 = ax1.hist2d(values_Rot, values_Tilt, bins=40, cmap='hot')
ax1.set_title('Linear norm for the colors')
fig.colorbar(img1, ax=ax1)
_, _, _, img2 = ax2.hist2d(values_Rot, values_Tilt, bins=40, cmap=cmap, norm=LogNorm())
ax2.set_title('Logarithmic norm for the colors')
fig.colorbar(img2, ax=ax2) # default log 10 colorbar
cbar2 = fig.colorbar(img2, ax=ax2) # log 2 colorbar
cbar2.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar2.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar2.ax.yaxis.set_minor_formatter(NullFormatter())
plt.show()
Note that log(0) is minus infinity. Therefore, the zero values in the left plot (darkest color) are left empty (white background) on the plot with the logarithmic color values. If you just want to use the lowest color for these zeros, you need to set a 'bad' color. In order not the change a standard colormap, the latest matplotlib versions wants you to first make a copy of the colormap.
PS: When calling plt.savefig() it is important to call it before plt.show() because plt.show() clears the plot.
Also, try to avoid the 'jet' colormap, as it has a bright yellow region which is not at the extreme. It may look nice, but can be very misleading. This blog article contains a thorough explanation. The matplotlib documentation contains an overview of available colormaps.
Note that to compare two plots, plt.subplots() needs to be used, and instead of plt.hist2d, ax.hist2d is needed (see this post). Also, with two colorbars, the elements on which the colorbars are based need to be given as parameter. A minimal change to your code would look like:
from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
from matplotlib import pyplot as plt
from copy import copy
# ...
# reading the data as before
cmap = copy(plt.get_cmap('magma'))
cmap.set_bad(cmap(0))
plt.hist2d(values_Rot, values_Tilt, bins=25, cmap=cmap, norm=LogNorm())
cbar = plt.colorbar()
cbar.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar.ax.yaxis.set_minor_formatter(NullFormatter())
plt.savefig('name_of_output.png') # needs to be called prior to plt.show()
plt.show()

maintaining consistent matplotlib axes limits

I'm trying to produce a series of figures showing geometric shapes of different sizes (one shape in each figure) but consistent, equal-spacing axes across each figure. I can't seem to get axis('equal') to play nice with set_xlim in matplotlib.
Here's the closest I've come so far:
pts0 = np.array([[13,34], [5,1], [ 0,0], [7,36], [13,34]], dtype=np.uint8)
pts1 = np.array([[10,82], [119,64], [149,63], [136,0], [82,14], [81,18],
[26,34], [3,29], [0,34], [10,82]], dtype=np.uint8)
shapes = [pts0,pts1]
for i in range(2):
pts = shapes[i]
fig = plt.figure()
ax1 = fig.add_subplot(111)
plotShape = patches.Polygon(pts, True, fill=True)
p = PatchCollection([plotShape], cmap=cm.Greens)
color = [99]
p.set_clim([0, 100])
p.set_array(np.array(color))
ax1.add_collection(p)
ax1.axis('equal')
ax1.set_xlim(-5,200)
ax1.set_ylim(-5,200)
ax1.set_title('pts'+str(i))
plt.show()
In my system, this results in two figures with the same axes, but neither one of them shows y=0 or the lower portion of the shape. If I remove the line ax1.set_ylim(-5,200), then figure "pts1" looks correct, but the limits of figure "pts0" are such that the shape doesn't show up at all.
My ideal situation is to "anchor" the lower-left corner of the figures at (-5,-5), define xlim as 200, and allow the scaling of the x axis and the value of ymax to "float" as the figure windows are resized, but right now I'd be happy just to consistently get the shapes inside the figures.
Any help would be greatly appreciated!
You can define one of your axes independently first and then when you define the second axis use the sharex or sharey arguments
new_ax = fig.add_axes([<bounds>], sharex=old_ax)

Using color scales as axes in matplotlib

I'm trying to create a visualization that varies color (specifically the H and V values of an HSV color scheme while keeping S constant), while representing the response of a given function to those colors.
Effectively, it's a heat map where the x and y axes are colors rather than numbers. Hunting through the matplotlib gallery I can find a lot of examples based on colorbars such as those found here, and here.
The colorbar implementation is close to what I'm looking for, with these important caveats:
I'm looking to align the colors as ticks on the main figure, rather than adding ticks to the colorbar itself. Principally this calls for making sure the plot and the colorbar are aligned, and I haven't found any way of actually guaranteeing this.
I'm trying to ensure that the color bar will display on the left of the figure (in place of the standard x-axis) rather than to the right.
The second point sounds trivial, but I haven't found any documented way of achieving it unfortunately.
Is there any way of creating a plot like this in matplotlib that would be considerably less effort than creating it from scratch in d3 or a similar lower-level visualization library?
I'm still not quite sure about it; but I'll give a try. Sorry if I misunderstood it.
Major thoughts are using GridSpec to solve your two requirements: aligning the "color axes" and put them beside the classic axes. The alignment should be correct because corresponding axes between ax_x/ax_y and the main ax are the same.
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
from matplotlib.gridspec import GridSpec
import numpy as np
# Create a spectrum sample
# Convert HSV to RGB so that matplotlib can plot;
# hsv_to_rgb assumes values to be in range [0, 1]
N = 0.001
v_y, h_x = np.mgrid[0:1:N, 0:1:N]
c = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), v_y], axis=2))
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
c_y = hsv_to_rgb(np.stack([np.zeros(h_x.shape), np.ones(h_x.shape), v_y], axis=2))
fig = plt.figure()
# Ratio to adjust width for "x axis" and "y axis"
fig_ratio = np.divide(*fig.get_size_inches())
gs = GridSpec(2, 2, wspace=0.0, hspace=0.0,
width_ratios=[1, 20], height_ratios=[20/fig_ratio, 1])
# Lower-left corner is ignored
ax_y = plt.subplot(gs[0])
ax = plt.subplot(gs[1])
ax_x = plt.subplot(gs[3])
# Image are stretched to fit the ax since numbers are hided or not important in this figure.
img = ax.imshow(c, aspect='auto', origin='lower')
# Colorbar on img won't give correct results since it is plot with raw RGB values
img_x = ax_x.imshow(c_x, aspect='auto', origin='lower')
img_y = ax_y.imshow(c_y, aspect='auto', origin='lower')
# Remove ticks and ticklabels
for ax in [ax_y, ax, ax_x]:
ax.tick_params(left=False, bottom=False,
labelleft=False, labelbottom=False)
plt.show()
Response to the comment:
To clarify, you're making three plots, and using imshow plots as axes by assigning them to quadrants of the grid?
Yes, it's a 2x2 grid and I ignored the lower-left one. The documentation might not be great but what I did is similar to this part.
And presumably if I wanted to add space between the axes here and the main plot I would increase wspace and hspace?
Yes, it is briefly demonstrated in this part of documentation. Besides, I adjusted it with width_ratios and height_ratios so that 3 parts of the figure are not the same size, like this.
Also, just to confirm, there is a fully black axis on the bottom of this image, and it's not a misalignment of the left axis.
The bottom is the colored x axis. It is black because I thought it corresponds to v=0. If you change
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
to
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.ones(v_y.shape)], axis=2))
You would get this figure, proving it's not misaligned:
If it's easier, you can also ignore the whole hsv thing, use a gray box or something as the central image.
I'm sorry but I'm really slow on this. I'm still having no idea what you want to show in the figure. So I don't know how to help. If you remove or comment out the line
img = ax.imshow(c, aspect='auto', origin='lower')
You got this:

changing size of a plot in a subplot figure

i create a figure with 4 subplots (2 x 2), where 3 of them are of the type imshow and the other is errorbar. Each imshow plots have in addition a colorbar at the right side of them. I would like to resize my 3rd plot, that the area of the graph would be exactly under the one above it (with out colorbar)
as example (this is what i now have):
How could i resize the 3rd plot?
Regards
To adjust the dimensions of an axes instance, you need to use the set_position() method. This applies to subplotAxes as well. To get the current position/dimensions of the axis, use the get_position() method, which returns a Bbox instance. For me, it's conceptually easier to just interact with the position, ie [left,bottom,right,top] limits. To access this information from a Bbox, the bounds property.
Here I apply these methods to something similar to your example above:
import matplotlib.pyplot as plt
import numpy as np
x,y = np.random.rand(2,10)
img = np.random.rand(10,10)
fig = plt.figure()
ax1 = fig.add_subplot(221)
im = ax1.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
ax2 = fig.add_subplot(222)
im = ax2.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
ax3 = fig.add_subplot(223)
ax3.plot(x,y)
ax3.axis([0,1,0,1])
ax4 = fig.add_subplot(224)
im = ax4.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
pos4 = ax4.get_position().bounds
pos1 = ax1.get_position().bounds
# set the x limits (left and right) to first axes limits
# set the y limits (bottom and top) to the last axes limits
newpos = [pos1[0],pos4[1],pos1[2],pos4[3]]
ax3.set_position(newpos)
plt.show()
You may feel that the two plots do not exactly look the same (in my rendering, the left or xmin position is not quite right), so feel free to adjust the position until you get the desired effect.

Categories

Resources