This question relates to #bgbg's question about how to visualize only the upper or lower triangle of a symmetric matrix in matplotlib. Using his code (shown at the end), we can generate a figure like this:
Now my question: how can we draw a dark border around just this set of blocks? I ask, because I want to plot two sets of correlation data and put them next to each other as an upper and lower triangle. We can then draw a dark border around each triangle independently, to separate out the two triangles and show they are different metrics. So, like this, but not confusing:
How to do it?
#Figure 1
import numpy as NP
from matplotlib import pyplot as PLT
from matplotlib import cm as CM
A = NP.random.randint(10, 100, 100).reshape(10, 10)
mask = NP.tri(A.shape[0], k=-1)
A = NP.ma.array(A, mask=mask) # mask out the lower triangle
fig = PLT.figure()
ax1 = fig.add_subplot(111)
cmap = CM.get_cmap('jet', 10) # jet doesn't have white color
cmap.set_bad('w') # default value is 'k'
ax1.imshow(A, interpolation="nearest", cmap=cmap)
ax1.grid(True)
axis('off')
#Figure 2
A = NP.random.randint(10, 100, 100).reshape(10, 10)
mask = NP.tri(A.shape[0], k=-1)
mask = NP.zeros_like(A)
mask[NP.arange(10), NP.arange(10)] = 1
A = NP.ma.array(A, mask=mask) # mask out the lower triangle
fig = PLT.figure()
ax1 = fig.add_subplot(111)
cmap = CM.get_cmap('jet', 10) # jet doesn't have white color
cmap.set_bad('w') # default value is 'k'
ax1.imshow(A, interpolation="nearest", cmap=cmap)
title("Correlation Data 1")
ylabel("Correlation Data 2")
yticks([])
xticks([])
You could draw a border using patches.Polygon:
import numpy as NP
from matplotlib import pyplot as PLT
import matplotlib.patches as patches
N = 10
A = NP.random.randint(10, 100, N * N).reshape(N, N)
mask = NP.tri(A.shape[0], k=-1)
mask = NP.zeros_like(A)
mask[NP.arange(N), NP.arange(N)] = 1
A = NP.ma.array(A, mask=mask) # mask out the lower triangle
fig, ax = PLT.subplots()
cmap = PLT.get_cmap('jet', 10) # jet doesn't have white color
cmap.set_bad('w') # default value is 'k'
ax.imshow(A, interpolation="nearest", cmap=cmap, extent=[0, N, 0, N])
line = ([(0, N - 1), (0, 0), (N - 1, 0)] +
[(N - 1 - i - j, i + 1) for i in range(N - 1) for j in (0, 1)])
lines = [line, [(N - x, N - y) for x, y in line]]
for line in lines:
path = patches.Polygon(line, facecolor='none', edgecolor='black',
linewidth=5, closed=True, joinstyle='round')
ax.add_patch(path)
ax.set_xlabel("Correlation Data 1")
ax.xaxis.set_label_position('top')
ax.set_ylabel("Correlation Data 2")
ax.set_yticks([])
ax.set_xticks([])
margin = 0.09
ax.set_xlim(-margin, N + margin)
ax.set_ylim(-margin, N + margin)
ax.set_frame_on(False)
PLT.show()
Related
So let's say I have a vector of numbers.
np.random.randn(5).round(2).tolist()
[2.05, -1.57, 1.07, 1.37, 0.32]
I want a draw a rectangle that shows this elements as numbers in a rectangle.
Something like this:
Is there an easy way to do this in matplotlib?
A bit convoluted but you could take advantage of seaborn.heatmap, creating a white colormap:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data = np.random.randn(5).round(2).tolist()
linewidth = 2
ax = sns.heatmap([data], annot=True, cmap=LinearSegmentedColormap.from_list('', ['w', 'w'], N=1),
linewidths=linewidth, linecolor='black', square=True,
cbar=False, xticklabels=False, yticklabels=False)
plt.tight_layout()
plt.show()
In this case, the external lines won't be as thick as the internal ones. If needed, this can be fixed with:
ax.axhline(y=0, color='black', lw=linewidth*2)
ax.axhline(y=1, color='black', lw=linewidth*2)
ax.axvline(x=0, color='black', lw=linewidth*2)
ax.axvline(x=len(data), color='black', lw=linewidth*2)
Edit: avoid these lines and add clip_on=False to sns.heatmap (thanks/credit #JohanC)
Output:
We can add rectangles , and annotate them in a for loop.
from matplotlib import pyplot as plt
import numpy as np
# Our numbers
nums = np.random.randn(5).round(2).tolist()
# rectangle_size
rectangle_size = 2
# We want rectangles look squared, you can change if you want
plt.rcParams["figure.figsize"] = [rectangle_size * len(nums), rectangle_size]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(len(nums)):
# We are adding rectangles
# You can change colors as you wish
plt.broken_barh([(rectangle_size * i, rectangle_size)], (0, rectangle_size), facecolors='white', edgecolor='black'
,linewidth = 1)
# We are calculating where to annotate numbers
cy = rectangle_size / 2.0
cx = rectangle_size * i + cy
# Annotation You can change color,font, etc ..
ax.annotate(str(nums[i]), (cx, cy), color='black', weight='bold', fontsize=20, ha='center', va='center')
# For squared look
plt.xlim([0, rectangle_size*len(nums)])
plt.ylim([0, rectangle_size])
# We dont want to show ticks
plt.axis('off')
plt.show()
One way using the Rectangle patch is:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
x = np.random.randn(5).round(2).tolist()
fig, ax = plt.subplots(figsize=(9, 2)) # make figure
dx = 0.15 # edge size of box
buf = dx / 10 # buffer around edges
# set x and y limits
ax.set_xlim([0 - buf, len(x) * dx + buf])
ax.set_ylim([0 - buf, dx + buf])
# set axes as equal and turn off axis lines
ax.set_aspect("equal")
ax.axis("off")
# draw plot
for i in range(len(x)):
# create rectangle with linewidth=4
rect = Rectangle((dx * i, 0), dx, dx, facecolor="none", edgecolor="black", lw=4)
ax.add_patch(rect)
# get text position
x0, y0 = dx * i + dx / 2, dx / 2
# add text
ax.text(
x0, y0, f"{x[i]}", color="black", ha="center", va="center", fontsize=28, fontweight="bold"
)
fig.tight_layout()
fig.show()
which gives:
I want to plot colored pie charts at specific positions without distorting their circular aspect ratio. I'm using Wedge patches because I could not find a better solution. Here is the code
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections
fig, axes = plt.subplots()
for i in range(20):
x = np.random.uniform(low=0, high=1, size=10).cumsum()
axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)
pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
color=c)
pies.append(wedge)
axes.add_collection(collections.PatchCollection(pies,
match_original=True))
plt.show()
How to preserve the aspect ratio of pie charts? Setting axes.set_aspect("equal") is NOT an option because it squeezes the plot completely when I have more data points.
I've been looking at how to draw circles and preserve the aspect ratio but the solution cannot be adopted here - I'm plotting Wedges/pie charts, not Circles.
I also looked at matplotlib transforms but couldn't find the answer there either.
I tried the same thing, and matplotlib really doesn't try to make this easy for you, but I found a solution that you should be able to use.
You need to separate the centers from the wedges and add them to the PatchCollection as offsets. Then you can apply different transforms to the offsets (transOffset) and shape (transform).
Notice that I have changed the r-value (radius). This value is no longer in data coordinates, so it should always be the same size, regardless of how much you zoom, but it is too small to be visible at i/10.
from matplotlib import patches, collections, transforms
offsets = []
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((0, 0), r=10, theta1=theta1, theta2=theta2,
color=c)
offsets.append((i, i))
pies.append(wedge)
coll = collections.PatchCollection(
pies, match_original=True, offsets=offsets,
transform=transforms.IdentityTransform(),
transOffset=axes.transData
)
It works fine for me when I set set_aspect('equal'):
Image is narrowed because y-range is longer than x-range I think.
If you'd set y_lim between 0 and a number lower than y_max, you'd see it better:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections
fig, axes = plt.subplots()
for i in range(20):
x = np.random.uniform(low=0, high=1, size=10).cumsum()
axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)
pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
color=c)
pies.append(wedge)
axes.add_collection(collections.PatchCollection(pies,
match_original=True))
axes.set_aspect('equal')
axes.set_ylim(0,7.5)
plt.show()
I have tried this and got the result as in the image:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax = ax1.twiny()
# Scatter plot of positive points, coloured blue (C0)
ax.scatter(np.argwhere(df['real'] > 0), df.loc[df['real'] > 0, 'real'], color='C2')
# Scatter plot of negative points, coloured red (C3)
ax.scatter(np.argwhere(df['real'] < 0), df.loc[df['real'] < 0, 'real'], color='C3')
# Scatter neutral values in grey (C7)
ax.scatter(np.argwhere(df['real'] == 0), df.loc[df['real'] == 0, 'real'], color='C7')
ax.set_ylim([df['real'].min(), df['real'].max()])
index = len(df.index)
ymin = df['prediction'].min()
ymax= df['prediction'].max()
ax1.imshow([np.arange(index),df['prediction']],cmap=cmap,
extent=(0,index-1,ymin, ymax), alpha=0.8)
plt.show()
Image:
I was expecting one output where the color is placed according to the figure. I am getting green color and no reds or greys.
I want to get the image or contours spread as the values are. How I can do that? See the following image, something similar:
Please let me know how I can achieve this. The data I used is here: t.csv
For a live version, have a look at Tensorflow Playground
There are essentially 2 tasks required in a solution like this:
Plot the heatmap as the background;
Plot the scatter data;
Output:
Source code:
import numpy as np
import matplotlib.pyplot as plt
###
# Plot heatmap in the background
###
# Setting up input values
x = np.arange(-6.0, 6.0, 0.1)
y = np.arange(-6.0, 6.0, 0.1)
X, Y = np.meshgrid(x, y)
# plot heatmap colorspace in the background
fig, ax = plt.subplots(nrows=1)
im = ax.imshow(X, cmap=plt.cm.get_cmap('RdBu'), extent=(-6, 6, -6, 6), interpolation='bilinear')
cax = fig.add_axes([0.21, 0.95, 0.6, 0.03]) # [left, bottom, width, height]
fig.colorbar(im, cax=cax, orientation='horizontal') # add colorbar at the top
###
# Plot data as scatter
###
# generate the points
num_samples = 150
theta = np.linspace(0, 2 * np.pi, num_samples)
# generate inner points
circle_r = 2
r = circle_r * np.random.rand(num_samples)
inner_x, inner_y = r * np.cos(theta), r * np.sin(theta)
# generate outter points
circle_r = 4
r = circle_r + np.random.rand(num_samples)
outter_x, outter_y = r * np.cos(theta), r * np.sin(theta)
# plot data
ax.scatter(inner_x, inner_y, s=30, marker='o', color='royalblue', edgecolors='white', linewidths=0.8)
ax.scatter(outter_x, outter_y, s=30, marker='o', color='crimson', edgecolors='white', linewidths=0.8)
ax.set_ylim([-6,6])
ax.set_xlim([-6,6])
plt.show()
To keep things simple, I kept the colorbar range (-6, 6) to match the data range.
I'm sure this code can be changed to suit your specific needs. Good luck!
Here is a possible solution.
A few notes and questions:
What are the 'prediction' values in your data file? They do not seem to correlate with the values in the 'real' column.
Why do you create a second axis? What is represented on the bottom X-axis in your plot? I removed the second axis and labelled the remaining axes (index and real).
When you slice a pandas DataFrame, the index comes with it. You don't need to create a separate index (argwhere and arange(index) in your code). I simplified the first part of the code, where scatterplots are produced.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
print(df)
fig = plt.figure()
ax = fig.add_subplot(111)
# Data limits
xmin = 0
xmax = df.shape[0]
ymin = df['real'].min()
ymax = df['real'].max()
# Scatter plots
gt0 = df.loc[df['real'] > 0, 'real']
lt0 = df.loc[df['real'] < 0, 'real']
eq0 = df.loc[df['real'] == 0, 'real']
ax.scatter(gt0.index, gt0.values, edgecolor='white', color='C2')
ax.scatter(lt0.index, lt0.values, edgecolor='white', color='C3')
ax.scatter(eq0.index, eq0.values, edgecolor='white', color='C7')
ax.set_ylim((ymin, ymax))
ax.set_xlabel('index')
ax.set_ylabel('real')
# We want 0 to be in the middle of the colourbar,
# because gray is defined as df['real'] == 0
if abs(ymax) > abs(ymin):
lim = abs(ymax)
else:
lim = abs(ymin)
# Create a gradient that runs from -lim to lim in N number of steps,
# where N is the number of colour steps in the cmap.
grad = np.arange(-lim, lim, 2*lim/cmap.N)
# Arrays plotted with imshow must be 2D arrays. In this case it will be
# 1 pixel wide and N pixels tall. Set the aspect ratio to auto so that
# each pixel is stretched out to the full width of the frame.
grad = np.expand_dims(grad, axis=1)
im = ax.imshow(grad, cmap=cmap, aspect='auto', alpha=1, origin='bottom',
extent=(xmin, xmax, -lim, lim))
fig.colorbar(im, label='real')
plt.show()
This gives the following result:
I am doing numerical simulations in python 3.6 and trying to inspect cross-sections of 2d imshow. I made the horizontal inspection and would like to have vertical, but got into some difficulties. The blue inspection lines correspond to 'bottom' (horizontal) and 'left' (vertical) subplots. Example code (I haven't been allowed to attach a matplotlib image):
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.pyplot as plt
import numpy as np
Array = np.random.rand(100, 100)
grid_points = 100
fig_mpl, ax = plt.subplots(figsize = (10, 10), facecolor = 'white')
line = ax.imshow(Array, cmap = 'hot')
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size = "5%", pad = 0.05)
caxb = divider.append_axes("bottom", size = "10%", pad = 0.05)
caxl = divider.append_axes("left", size = "10%", pad = 0.05)
bar = fig_mpl.colorbar(line, cax = cax, orientation = 'vertical')
ax.axhline(grid_points/2)
ax.axvline(grid_points/2)
X = np.linspace(0, grid_points - 1, grid_points)
projb, = caxb.plot(X, Array[int(grid_points/2)], color = 'red')
projl, = caxl.plot(X, Array[:, int(grid_points/2)], color = 'red')
caxb.set_ylim(-0.1*np.max(Array), 1.1*np.max(Array))
caxb.set_xlim(0, grid_points - 1)
caxl.set_xlim(-0.1*np.max(Array), 1.1*np.max(Array))
caxl.set_ylim(0, grid_points - 1)
ax.set_xticks([])
ax.set_yticks([])
caxb.set_xticks([])
caxl.set_yticks([])
caxb.set_yticks([np.min(Array), np.max(Array)])
caxl.set_xticks([np.min(Array), np.max(Array)])
caxb.yaxis.tick_right()
for tick in caxl.get_xticklabels():
tick.set_rotation(-90)
caxb.grid(color = 'black', marker = 8)
caxl.grid(color = 'black', marker = 8)
fig_mpl.subplots_adjust(wspace = 0)
fig_mpl.tight_layout()
I want projl to plot the cross-section of Array in vertical caxl.
Is there any proper way to do the thing?
Instead of
projl, = caxl.plot(X, Array[:, int(grid_points/2)], color = 'red')
you need
projl, = caxl.plot(Array[:, int(grid_points/2)], X, color = 'red')
because the amplitude should be shown along the horizontal (x-) axis and the grid index (X) along the vertical (y-) axis.
When I use fill_between The colored patches are slightly angled vertically so there is white space at the top of the y axis, whereas the colors are nicely merged at the bottom of the yaxis. Anyone know how to prevent this/understand what is causing this?
The plot is showing a 'weather window': when weather parameters are below a certain threshold the time period is 'operational' and at other times it is 'non operational'. The code to generate this plot is:
figure = plt.figure(figsize=(8, 3 * 3))
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0.3)
ax0 = plt.subplot(gs[0])
df1.plot() # pandas DataSeries
ax0.set_xlabel('')
ax1 = plt.subplot(gs[1])
df2.plot() # pandas DataSeries
ax1.set_xlabel('')
ax2 = plt.subplot(gs[2])
trans = mtransforms.blended_transform_factory(ax2.transData, ax2.transAxes)
ax2.plot(xtime, y, color = 'green', alpha = 0.5, lw = 0.01)
ax2.set_xlim(xtime[0], xtime[-1])
ax2.fill_between(xtime2, 0, 1, where = yop > 0, facecolor = 'green', alpha = 0.5, interpolate = True, transform = trans)
# yop is numpy array of 0's and 1's
ax2.fill_between(xtime2, 0, 1, where = ynonop > 0, facecolor = 'red', alpha = 0.5, interpolate = True, transform = trans)
# ynonop has 0's and 1's opposite to yop
The interpolate = True plays some role is removing the white spaces between points.
Here is simpler code to test the issue:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
ynonop = np.copy(yop)
# make 0's and 1's opposite to yop
ynonop[ynonop == 1] = 2
ynonop[ynonop == 0] = 1
ynonop[ynonop == 2] = 0
import matplotlib.transforms as mtransforms
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
ax.set_xlim(x[0], x[-1])
ax.fill_between(x, 0, 1, where=yop > 0, facecolor='green', alpha=0.5, interpolate = True, transform=trans)
ax.fill_between(x, 0, 1, where=ynonop > theta, facecolor='red', alpha=0.5, interpolate = True, transform=trans)
plt.show()
# plt.savefig('test.png', bbox_inches = 0)
To understand what is causing the white stripes, you may zoom into the plot.
Because fill_between fills between points that fulfil a certain condition, you get a sawtooth-like shape.
A possible solution might be to use a broken_barh plot. To this end one would need to rearange the data into a 2columns format of (position, width).
import matplotlib.pyplot as plt
import numpy as np
fig, (ax,ax2) = plt.subplots(nrows=2, sharex=True, sharey=True)
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
ynonop = np.copy(yop)
# make 0's and 1's opposite to yop
ynonop[ynonop == 1] = 2
ynonop[ynonop == 0] = 1
ynonop[ynonop == 2] = 0
trans = ax.get_xaxis_transform()
ax.set_xlim(x[0], x[-1])
ax.fill_between(x, 0, 1, where=yop > 0, facecolor='green',
alpha=0.5, interpolate = True, transform=trans)
ax.fill_between(x, 0, 1, where=ynonop > 0, facecolor='red',
alpha=0.5, interpolate = True, transform=trans)
trans2 = ax2.get_xaxis_transform()
xra = np.c_[x[:-1],np.diff(x)]
ax2.broken_barh(xra[yop[:-1] > 0,:], (0,1),
facecolors='green', alpha=0.5, transform=trans2)
ax2.broken_barh(xra[ynonop[:-1] > 0,:], (0,1),
facecolors='red', alpha=0.5, transform=trans2)
ax.set_title("fill_between")
ax2.set_title("broken_barh")
plt.show()
You can also do this using imshow
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as mcolors
import matplotlib.transforms as mtransforms
fig, ax = plt.subplots()
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
ax.set_xlim(x[0], x[-1])
lc = mcolors.ListedColormap(['r', 'g'], name='RWG')
ax.imshow(yop.reshape(1, -1),
extent=[0, len(yop), 0, 1],
transform=trans,
cmap=lc,
norm=mcolors.NoNorm(), alpha=.5)
ax.set_aspect('auto')
# debugging plotting
ax.step(x, yop, '.', where='post', linestyle='none')
ax.set_ylim([-.1, 1.1])
plt.show()
By tweaking x values in extent you can control exactly where the pixels fall in dataspace.