I need to create a Python script that plots a list of (sorted) value as a vertical bar plot. I'd like to plot all the values and save it as a long vertical plot, so that both the yticks labels and bars are clearly visible. That is, I'd like a "long" verticalplot. The number of elements in the list varies (e.g. from 500 to 1000), so the use of figsize does not help as I don't know how long that should be.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
# Example data
n = 500
y_pos = np.arange(n)
performance = 3 + 10 * np.random.rand(n)
ax.barh(y_pos, np.sort(performance), align='center', color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels([str(x) for x in y_pos])
ax.set_xlabel('X')
How can I modify the script so that I can stretch the figure vertically and make it readable?
Change the figsize depending on the number of data values. Also, manage the y-axis limit accordingly.
The following works perfectly:
n = 500
fig, ax = plt.subplots(figsize=(5,n//5)) # Changing figsize depending upon data
# Example data
y_pos = np.arange(n)
performance = 3 + 10 * np.random.rand(n)
ax.barh(y_pos, np.sort(performance), align='center', color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels([str(x) for x in y_pos])
ax.set_xlabel('X')
ax.set_ylim(0, n) # Manage y-axis properly
Given below is the output picture for n=200
Related
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.
Currently, I have the first y axis (probability) of my subplots aligned. However, I am attempting to get the secondary y axis (sample size) of the subplots aligned. I've tried to simply set the y-axis limit, but this solution isn't very generalizable.
Here is my code:
attacks = 5
crit_rate = .5
idealdata = fullMatrix(attacks, crit_rate)
crit_rate = ("crit_%.0f" % (crit_rate*100))
actualdata = trueDataM(attacks, crit_rate)
[enter image description here][1]
fig, axs = plt.subplots(attacks+1, sharex=True, sharey=True)
axs2 = [ax.twinx() for ax in axs]
fig.text(0.5, 0.04, 'State', ha='center')
fig.text(0.04, 0.5, 'Probability', va='center', rotation='vertical')
fig.text(.95, .5, 'Sample Size', va='center', rotation='vertical')
fig.text(.45, .9, 'Ideal vs. Actual Critical Strike Rate', va='center')
cmap = plt.get_cmap('rainbow')
samplesize = datasample(attacks, 'crit_50')
fig.set_size_inches(18.5, 10.5)
for i in range(attacks+1):
axs[i].plot(idealdata[i], color=cmap(i/attacks), marker='o', lw=3)
axs[i].plot(actualdata[i], 'gray', marker='o', lw=3, ls='--')
axs2[i].bar(range(len(samplesize[i])), samplesize[i], width=.1, color=cmap(i/attacks), alpha = .6)
plt.show()
https://i.stack.imgur.com/HKJlE.png
Without data to confirm my assumptions it's hard to tell if this will be correct.
You are not making any attempt to scale the left y-axes so that data must all have the same range. To ensure the right y-axes all have the same scale/limits you need to determine the range (max and min) of the (all) data being plotted on those axes then apply that to all of them.
It isn't clear whether samplesize is a Numpy ndarray or a lists of lists, I'm also assuming that it is a 2-d structure with range(attacks+1) rows. Since you are making bar charts on the second y-axes you only need to find the largest height in all the data.
# for a list of lists
biggest = max(max(row) for row in samplesize)
# or
biggest = max(map(max,samplesize))
# for an ndarray
biggest = samplesize.max()
Then apply that scale to all the right y-axes before they are shown
for ax in axs2:
ax.set_ylim(top=biggest)
If you determine biggest prior to the plot loop you can just add a line to that loop:
for i in range(attacks+1):
...
axs2[i].set_ylim(top=biggest)
You'll find plenty of related SO Q&A'a searching with the terms: matplotlib subplots same y scale, matplotlib subplots y axis limits or something similar.
Here is a toy example:
from matplotlib import pyplot as plt
import numpy as np
lines = np.random.randint(0,200,(5,10))
bars = [np.random.randint(0,np.random.randint(0,10000),10) for _ in (0,0,0,0,0,)]
fig, axs = plt.subplots(lines.shape[0], sharex=True, sharey=True)
axs2 = [ax.twinx() for ax in axs]
#xs = np.arange(lines.shape[1])
xs = np.arange(1,11)
biggest = max(map(max,bars))
for ax,ax2,line,row in zip(axs,axs2,lines,bars):
bars = ax2.bar(xs,row)
ax.plot(line)
ax2.set_ylim(top=biggest)
plt.show()
plt.close()
I would like to achieve two objectives with matplotlib:
Dynamically update a scatter plot
Slowly make the points that were plotted at previous iterations more transparent.
Currently, I am able to achieve the opposite goal using colormaps. That is, I can plot points over time but the recent points look more transparent.
Is it possible to get a 'fading' effect on matplotlib using cmap or other tools? Thanks! My code is below:
def plotter_fader(iterations = 100, stay_open = True):
# Set up plot
fig, ax = plt.subplots()
x_data = []
y_data = []
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
t_vals = np.linspace(0,1, iterations)
cmap = (0.09803921568627451, 0.09803921568627451, 0.09803921568627451, .05)
for t in t_vals:
# Get intermediate points
intermediate = (1-t)*A + t*B
new_xvals, new_yvals = ... #Get these through some process
x_vals.extend(new_xvals)
y_vals.extend(new_yvals)
# Put new values in your plot
plt.plot(x_vals, y_vals, '.', color = cmap)
# Recompute plot limits
ax.relim()
# Set title and wait a little bit for 'smoothness'
ax.set_xlabel('X Axis', size = 12)
ax.set_ylabel('Y Axis', size = 12)
ax.set_title('Time: %0.3f' %t)
ax.autoscale_view()
fig.canvas.draw()
time.sleep(0.005)
# Stay open after plotting ends
while stay_open:
pass
Just as usual with a scatter plot you may define an array of values and a colormap that maps those values to colors. You can change this array in each iteration to make older points have a different value.
In the following we take a value of 0 as transparent and a value of 1 as dark blue and create a colormap with those colors.
In each iteration old values are multiplied by a number smaller than one, new values are set to have a value of 1.
Running the animation will hence produce fading points.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.colors import LinearSegmentedColormap
fig, ax = plt.subplots()
ax.set_xlabel('X Axis', size = 12)
ax.set_ylabel('Y Axis', size = 12)
ax.axis([0,1,0,1])
x_vals = []
y_vals = []
intensity = []
iterations = 100
t_vals = np.linspace(0,1, iterations)
colors = [[0,0,1,0],[0,0,1,0.5],[0,0.2,0.4,1]]
cmap = LinearSegmentedColormap.from_list("", colors)
scatter = ax.scatter(x_vals,y_vals, c=[], cmap=cmap, vmin=0,vmax=1)
def get_new_vals():
n = np.random.randint(1,5)
x = np.random.rand(n)
y = np.random.rand(n)
return list(x), list(y)
def update(t):
global x_vals, y_vals, intensity
# Get intermediate points
new_xvals, new_yvals = get_new_vals()
x_vals.extend(new_xvals)
y_vals.extend(new_yvals)
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals,y_vals])
#calculate new color values
intensity = np.concatenate((np.array(intensity)*0.96, np.ones(len(new_xvals))))
scatter.set_array(intensity)
# Set title
ax.set_title('Time: %0.3f' %t)
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals,interval=50)
plt.show()
Is this what you are looking for ?
from matplotlib import pyplot as plt
# Creates new axis.
plt.axis([0, 10, 0, 1])
# Allows interactive plotting
plt.ion()
# Transparency
alpha = 1
# Plotting first point outside of loop because more convenient for example
point = plt.scatter(0.5, 0.5, alpha=alpha)
for i in range(10):
# As the loop goes on, increase transparency, remove the current point,
# and plots another one, more transparent.
alpha -= 0.1
point.remove()
point = plt.scatter(5, .5, alpha=alpha, color='r')
plt.pause(0.05)
while True:
plt.pause(0.05)
I just can't get the subplots to work when adding the residuals with add_axes. It works well without residuals and I can add residuals to just one plot. This is an example of what I'm doing:
First, just to give you an idea of what I'm ploting, (t, y) is the data I want to plot, fit is the fit to the data, and diff is the difference between fit and data.
t, s, fit = [], [], []
diff = []
for i in range(12):
t.append(x / y[i])
s.append(np.linspace(0, 1, num=100, endpoint=True))
fit.append(UnivariateSpline(t[i], y, er, s=5e20))
diff.append(fit[i](t[i]) - y)
And this is the figure:
fig = plt.figure()
for i in range(12):
plt.subplot(4,3,i+1)
fig.add_axes((0.,0.3,0.7,0.9))
plt.plot(s[i], fit[i](s[i]), 'r-') # this is the fit
plt.errorbar(t[i], y, er, fmt='.k',ms=6) # this is the data
plt.axis([0,1, 190, 360])
fig.add_axes((0.,0.,0.7,0.3))
plot(t[i],diff[i],'or') # this are the residuals
plt.axis([0,1, 190, 360])
So as you can see I'm generating 12 subplots, which works just fine until I add the fig.add_axes to separate each subplot between data+fit and the residuals, but what I get is one messy plot on top of the subplots (figure has been shrunken to see the subplots under):
And what I want is 12 subplots where each one looks like this:
Usually plt.subplot(..) and fig.add_axes(..) are complementary. This means that both commands create an axes inside the figure.
Their usage however would be a bit different. To create 12 subplots with subplot you would do
for i in range(12):
plt.subplot(4,3,i+1)
plt.plot(x[i],y[i])
To create 12 subplots with add_axes you would need to do something like this
for i in range(12):
ax = fig.add_axes([.1+(i%3)*0.8/3, 0.7-(i//3)*0.8/4, 0.2,.18])
ax.plot(x[i],y[i])
where the positions of the axes need to be passed to add_axes.
Both work fine. But combining them is not straight forward, as the subplots are positionned according to a grid, while using add_axes you would need to already know the grid positions.
So I would suggest starting from scratch. A reasonable and clean approach to create subplots is to use plt.subplots().
fig, axes = plt.subplots(nrows=4, ncols=3)
for i, ax in enumerate(axes.flatten()):
ax.plot(x[i],y[i])
Each subplot can be divided into 2 by using an axes divider (make_axes_locatable)
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
ax2 = divider.append_axes("bottom", size=size, pad=pad)
ax.figure.add_axes(ax2)
So looping over the axes and doing the above for every axes allows to get the desired grid.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.rcParams["font.size"] = 8
x = np.linspace(0,2*np.pi)
amp = lambda x, phase: np.sin(x-phase)
p = lambda x, m, n: m+x**(n)
fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(8,6), sharey=True, sharex=True)
def createplot(ax, x, m, n, size="20%", pad=0):
divider = make_axes_locatable(ax)
ax2 = divider.append_axes("bottom", size=size, pad=pad)
ax.figure.add_axes(ax2)
ax.plot(x, amp(x, p(x,m,n)))
ax2.plot(x, p(x,m,n), color="crimson")
ax.set_xticks([])
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
phase = i*np.pi/2
createplot(axes[i,j], x, i*np.pi/2, j/2.,size="36%")
plt.tight_layout()
plt.show()
I have a matplotlib figure that am using embedded into a pyQT GUI, therefore I need to recycle the figure to display several resuls.
When I display a matrix using the spy function, I get what I spect:
However when I clear the figure and plot a series I get this:
Instead of this:
Which I get if I plot the series without displaying a matrix before.
So a script to reproduce the issue is:
from matplotlib.pyplot import figure, show
import numpy
fig = figure()
ax = fig.add_subplot(111)
mat = numpy.random.randn(20, 20)
# display the matrix
ax.spy(mat, markersize=5)
x = numpy.linspace(0, 1, 100)
y = x**2 + x - 5
ax.clear()
ax.plot(x, y)
I have also tried
ax.relim() # make sure all the data fits
ax.autoscale() # auto-scale
But it doesn't do anything noticeable.
plt.spy will automatically set the aspect ratio of the axes to 'equal' in order to ensure that the sparsity plot for a square matrix looks square. If the x-axis scale of your series is much larger than that of the y-axis, an equal aspect ratio will result in a very long and thin line plot.
To switch back to the 'default' mode where the aspect ratio is determined automatically you can call ax.set_aspect('auto'):
from matplotlib.pyplot import figure, show
import numpy
fig = figure()
ax = fig.add_subplot(111)
mat = numpy.random.randn(20, 20)
# display the matrix
ax.spy(mat, markersize=5)
x = numpy.linspace(0, 1, 100)
y = x**2 + x - 5
ax.clear()
ax.set_aspect('auto')
ax.plot(x, y)