I'm trying to plot a set of points relative to 1; I can do this on Excel but I'm struggling to do this in Python. The hardest part for me is to keep it into log scale for the Y-axis. If I have values
0.15 0.7 1.3 0.5 1.7,
How can I use matplotlib to achieve the same effect as shown below (done by Excel)? The best I've come up with so far is to subtract 1 from each value to actually center it around 0, but this ends up messing up the scaling.
This is what I want:
This is a failed attempt I get from Python:
Another failed attempt at Python gives me this; even though the Y-axis is in the log-range, notice that everything starts from the bottom, when I really want the values to be going up/down with respect to the centre, or 1, on the Y-axis
Does this work for you:
# our imports
import numpy as np
import matplotlib.pyplot as plt
# define the sample size and draw a random sample
N = 5
ind = np.arange(N)
sample = np.random.uniform(low=-1, high=1, size=(N))
# initialize our bar width and the subplot
width = 0.35
fig, ax = plt.subplots()
# plot our indexes, sample using a blue color
rects1 = ax.bar(ind, sample, width, color='blue')
# Set our axes labels, title, tick marks, and then our x ticks.
ax.set_ylabel('Scores')
ax.set_title('Scores Example')
ax.set_xticks(ind + width)
ax.set_xticklabels(('E1', 'E2', 'E3', 'E4', 'E5'))
# Create a horizontal line at the origin
ax.axhline(y=0, color='black')
# Show our plot, do whatever
plt.show()
More examples and references can be found here and here.
You can also use hex codes and set no outline for the bar graphs, and give some spacing for the actual plot, along with adding log axes:
Here is the final code to give a representation similar to your own:
# our imports
import numpy as np
import matplotlib.pyplot as plt
# define the sample size and draw a random sample
N = 5
ind = np.arange(N)
sample = 10 ** np.random.uniform(low=-2, high=2, size=(N))
# initialize our bar width and the subplot
width = 0.35
fig, ax = plt.subplots()
# plot our indexes, sample using a hex color and a 0 linewidth to get rid
# of the plot edges
rects1 = ax.bar(ind, sample, width, color='#50A6C2', linewidth=0)
# Set our axes labels, title, tick marks, and then our x ticks.
ax.set_ylabel('Scores')
ax.set_title('Scores Example')
ax.set_xticks(ind + width)
ax.set_xticklabels(('E1', 'E2', 'E3', 'E4', 'E5'))
# Create a horizontal line at the origin
ax.axhline(y=1, color='black')
# Set our limits
ax.set_xlim(-1, 5)
ax.set_yscale('log')
ax.set_ylim(0.01, 100)
# Show our plot, do whatever
plt.show()
Related
I am trying to create a heatmap by putting gridlines to some particular positions which I have done. Suppose, I tried to make gridlines in positions 358 and 589 in a matrix of length 640,640. After that, I wanted to change the label from 358 to a defined value of 999 and 589 to a specified value of 1023. However, I cannot change the x and y labels in the center position of two gridlines. For example, I have tried the following:
data = np.random.rand(640, 640)
fig, ax = plt.subplots()
im = ax.imshow(data,cmap='coolwarm')
ax.set_xticks([358,589])
ax.set_yticks([358,589])
ax.set_xticklabels([999,1023])
ax.set_yticklabels([999,1023])
ax.grid(which='major',color='black',linestyle='--',linewidth=1,alpha=0.5)
plt.show()
That create a image as follows:
Heatmap with customized labelling
But I want the labeling in the middle of two gridlines instead of the gridline positions. How can that be done?
By default, both the tick labels and the grid lines are decided via the major ticks. To change this, you could use the minor ticks to position the grid lines and the major ticks for the tick labels:
from matplotlib import pyplot as plt
import numpy as np
data = np.random.randn(640, 640).cumsum(axis=0).cumsum(axis=1)
fig, ax = plt.subplots()
im = ax.imshow(data, cmap='coolwarm')
positions = np.array([358, 589])
ax.set_xticks(positions, minor=True)
ax.set_yticks(positions, minor=True)
borders = np.append(0, positions)
mids = (borders[:-1] + borders[1:]) / 2
ax.set_xticks(mids, [999, 1023], minor=False)
ax.set_yticks(mids, [999, 1023], minor=False)
ax.grid(which='minor', color='black', linestyle='--', linewidth=1, alpha=0.9)
plt.show()
I could find a way to set a figure size with dpi
px = 1/plt.rcParams['figure.dpi']
fig = plt.figure(figsize=(1580*px, 25*px))
(reference: https://matplotlib.org/stable/gallery/subplots_axes_and_figures/figure_size_units.html)
fig = plt.figure(figsize=(1580*px, 25*px))
plt.plot(xx, y[0], label='min')
plt.plot(xx, y[1], label='max')
plt.yticks(y_ticks, y_tick_labels)
plt.ylim(top=y_max)
plt.legend()
However, how do you set the plot size?
I want my plot or graph to be full of (1580px, 25px)
but if I set the figure size and plot graphs using the above code, then the graph does not fit the figure (1580px, 25px). Even worse, labels or ticks are not shown well in the figure like below.
I want my graph size to be the above white space size( for example, 1580px, 25px) and then draw ticks and labels outside the white space (then figure size should be bigger than the given plot size). But I couldn't find a way to set the plot size. I could only find a way to set the figure size.
import matplotlib.pyplot as plt
import numpy as np
def axes_with_pixels(width, height, margin=0.2):
px = 1/plt.rcParams['figure.dpi']
fig_width, fig_height = np.array([width, height]) / (1 - 2 * margin)
fig, ax = plt.subplots(figsize=(fig_width*px, fig_height*px))
fig.subplots_adjust(left=margin, right=1-margin,
bottom=margin, top=1-margin)
return fig, ax
fig, ax = axes_with_pixels(580, 80) # Specify the Axes size in pixels
X = np.linspace(0, 10, 10)
Y0 = np.sin(X)
Y1 = np.cos(X)
plt.plot(X, Y0, label='min')
plt.plot(X, Y1, label='max')
plt.legend()
As you can see, the Axes (plot area) is exactly 580 * 80 pixels. (Note, the shown width of 581 pixels is due to the offset of the right edge.)
However, axes_with_pixels can be only used to set a single Axes with a specified pixels. If you want a figure to have multiple Axes with some specified pixels, then you have to consider wspace and hspace in subplots_adjust to get the figure size.
I would like a representation consisting of a scatter plot and 2 histograms on the right and below the scatter plot
create. I have the following requirements:
1.) In the scatter plot, the apect ratio is equal so that the circle does not look like an ellipse.
2.) In the graphic, the subplots should be exactly as wide or high as the axes of the scatter plot.
This also works to a limited extent. However, I can't make the lower histogram as wide as the x axis of the scatter plot. How do I do that?
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import random
#create some demo data
x = [random.uniform(-2.0, 2.0) for i in range(100)]
y = [random.uniform(-2.0, 2.0) for i in range(100)]
#create figure
fig = plt.figure()
gs = gridspec.GridSpec(2, 2, width_ratios = [3, 1], height_ratios = [3, 1])
ax = plt.subplot(gs[0])
# Axis labels
plt.xlabel('pos error X [mm]')
plt.ylabel('pos error Y [mm]')
ax.grid(True)
ax.axhline(color="#000000")
ax.axvline(color="#000000")
ax.set_aspect('equal')
radius = 1.0
xc = radius*np.cos(np.linspace(0,np.pi*2))
yc = radius*np.sin(np.linspace(0,np.pi*2))
plt.plot(xc, yc, "k")
ax.scatter(x,y)
hist_x = plt.subplot(gs[1],sharey=ax)
hist_y = plt.subplot(gs[2],sharex=ax)
plt.tight_layout() #needed. without no xlabel visible
plt.show()
what i want is:
Many thanks for your help!
The easiest (but not necessarily most elegant) solution is to manually position the lower histogram after applying the tight layout:
ax_pos = ax.get_position()
hist_y_pos = hist_y.get_position()
hist_y.set_position((ax_pos.x0, hist_y_pos.y0, ax_pos.width, hist_y_pos.height))
This output was produced by matplotlib version 3.4.3. For your example output, you're obviously using a different version, as I get a much wider lower histogram than you.
(I retained the histogram names as in your example although I guess the lower one should be hist_x instead of hist_y).
I have some plots with a lot of information and lines, so sometimes I tend to put the legend outside the plot itself using bbox_to_anchor. I also prefer to have a title of the plot, but this will positionally coincide with the legend in that case. The following example below is just an illustration of the problem.
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
r = 1 + np.sin(4 * np.pi * t)
q = 1 + np.sin(6 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s, label='S')
ax.plot(t, r, label='R')
ax.plot(t, q, label='Q')
leg = ax.legend(loc=3, ncol=3, bbox_to_anchor=(.0, 1.02, 1., .102), borderaxespad=0., mode='expand')
ax.set_title('SIMPLE PLOT', y=1.1)
plt.show()
To avoid this, I set some kind of y-value (e.g. y=1.1). I would like to automate this process because I keep updating the same plot with new data, so the legend grows in size, and I need to adjust the position of the title accordingly.
Is there a way to automate this process?
Is there a function in Python that is able to read the height of the legend so that this can be used to adjust the title position?
The height of the legend is determined at draw time. You can get it after having drawn the figure via legend.get_window_extent(). The resulting bounding box is in units of pixels. In order to find the offset of the title, you will need to subtract the upper limit of the legend from the upper limit of the axes. So you need to get the axes position in pixels as well.
The title can be offset either in figure coordinates (y=1.1) or points (pad=20). I would suggest to use points here, to make it independent of the size of the axes. So you can calculate the difference in upper positions, convert from pixels to points (i.e. distance [pixels] * ppi / dpi) and add some constant offset in points (because usually you would not want the title to sit exactly on the border of the legend). Then use that number as pad.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(constrained_layout=True)
ax.plot([1,2,3], np.random.rand(3,5), label='Label')
leg = ax.legend(loc="lower center", ncol=3, bbox_to_anchor=(.0, 1.02, 1., 1.02),
borderaxespad=0, mode='expand')
fig.canvas.draw()
leg_box = leg.get_window_extent()
ax_box = ax.get_position().transformed(fig.transFigure)
pad = (leg_box.y1 - ax_box.y1)*72./fig.dpi + 6
ax.set_title('SIMPLE PLOT', pad=pad)
plt.show()
Note that here I also used constrained_layout to have the title not cropped by the figure boundaries.
I am trying to plot a scatterplot matrix based on the code written by Joe Kington: Is there a function to make scatterplot matrices in matplotlib?
Some people already helped me: Thank you again (especially J.K.).
I am having a last problem: I cannot rotate the ticks of some axis for which numbers overlap (bottom left):
I would like to try to have them vertical but I cannot do it.... Here is my code:
import itertools
import numpy as np
import pylab as plot
import scipy
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import axis
import math
from matplotlib import rc
import os
import platform
def main():
FigSize=8.89
FontSize=8
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
fig = scatterplot_matrix(data, ['mpg', 'disp', 'drat', 'wt'], FigSize, FontSize,
linestyle='none', marker='o', color='black', mfc='none', markersize=3,)
fig.suptitle('Simple Scatterplot Matrix')
plt.savefig('Plots/ScatterplotMatrix/ScatterplotMatrix2.pdf',format='pdf', dpi=1000, transparent=True, bbox_inches='tight')
plt.show()
def scatterplot_matrix(data, names, FigSize, FontSize, **kwargs):
"""Plots a scatterplot matrix of subplots. Each row of "data" is plotted
against other rows, resulting in a nrows by nrows grid of subplots with the
diagonal subplots labeled with "names". Additional keyword arguments are
passed on to matplotlib's "plot" command. Returns the matplotlib figure
object containg the subplot grid."""
legend=['(kPa)','\%','\%','\%']
numvars, numdata = data.shape
fig, axes = plt.subplots(nrows=numvars, ncols=numvars, figsize=(FigSize/2.54,FigSize/2.54))
fig.subplots_adjust(hspace=0.05, wspace=0.05)
sub_labelx_top=[2,4]
sub_labelx_bottom=[13,15]
sub_labely_left=[5,13]
sub_labely_right=[4,12]
for i, ax in enumerate(axes.flat, start=1):
# Hide all ticks and labels
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.xaxis.set_major_locator(MaxNLocator(prune='both',nbins=4))
ax.yaxis.set_major_locator(MaxNLocator(prune='both',nbins=4)) #http://matplotlib.org/api/ticker_api.html#matplotlib.ticker.MaxNLocator
# Set up ticks only on one side for the "edge" subplots...
if ax.is_first_col():
ax.yaxis.set_ticks_position('left')
ax.tick_params(direction='out')
ax.yaxis.set_tick_params(labelsize=0.75*FontSize)
if i in sub_labely_left:
ax.yaxis.set_label_position('left')
ax.set_ylabel('(\%)',fontsize=0.75*FontSize)
if ax.is_last_col():
ax.yaxis.set_ticks_position('right')
ax.tick_params(direction='out')
ax.yaxis.set_tick_params(labelsize=0.75*FontSize)
if i in sub_labely_right:
ax.yaxis.set_label_position('right')
if i==4:
ax.set_ylabel('(kPa)',fontsize=0.75*FontSize)
else:
ax.set_ylabel('(\%)',fontsize=0.75*FontSize)
if ax.is_first_row():
ax.xaxis.set_ticks_position('top')
ax.tick_params(direction='out')
ax.xaxis.set_tick_params(labelsize=0.75*FontSize)
if i in sub_labelx_top:
ax.xaxis.set_label_position('top')
ax.set_xlabel('(\%)',fontsize=0.75*FontSize)
if ax.is_last_row():
ax.xaxis.set_ticks_position('bottom')
ax.tick_params(direction='out')
ax.xaxis.set_tick_params(labelsize=0.75*FontSize)
if i in sub_labelx_bottom:
ax.xaxis.set_label_position('bottom')
if i==13:
ax.set_xlabel('(kPa)',fontsize=0.75*FontSize)
else:
ax.set_xlabel('(\%)',fontsize=0.75*FontSize)
# Plot the data.
for i, j in zip(*np.triu_indices_from(axes, k=1)):
for x, y in [(i,j), (j,i)]:
axes[x,y].plot(data[y], data[x], **kwargs)
# Label the diagonal subplots...
for i, label in enumerate(names):
axes[i,i].annotate(label, (0.5, 0.5), xycoords='axes fraction',
ha='center', va='center',fontsize=FontSize)
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
return fig
main()
My second question is more for the 'fun': how can I make the subplots perfectly squares?
I apologize to Joe Kington; I know my code is way less elegant than his... I just started few weeks ago. If you have any suggestions to improve mine, for example to make it more dynamic, I am very interesting.
You can rotate the xtick labels using setp.
from matplotlib.artist import setp
Then after you set the x tick positions for the top row and left column of subplot call:
setp(ax.get_xticklabels(), rotation=90)
To make the size of the subplots equal, you can fig.subplots_adjust to set the area of all the subplots to a square. Something like this:
gridSize = 0.6
leftBound = 0.5 - gridSize/2
bottomBound = 0.1
rightBound = leftBound + gridSize
topBound = bottomBound + gridSize
fig.subplots_adjust(hspace=0.05, wspace=0.05, left=leftBound,
bottom=bottomBound, right=rightBound, top=topBound)
If the figure size isn't square, you'll need to change the shape of the grid accordingly. Alternately, you could add each subplot axes individually with fig.add_axes. That will allow you to set the size directly but you'll also have to set the location.
Don't use bbox_inches='tight' to save the figure or you'll lose the title with these setting. You can save like this:
plt.savefig('ScatterplotMatrix.pdf',format='pdf', dpi=1000, transparent=True)
The resulting graph looks like this: