I'm trying to animate several text objects in matplotlib at the same time. I have a 3-D numpy array (arrow_data) of data which stores the index of a unicode arrow which I need to plot (text for all unicode arrows are stored in a list arrows). The first and second dimension of this np.array indicate the location on a grid where this arrow needs to be plotted and the 3rd dimension is the 'time' dimension over which I need to update the plot (ie the arrows change through time over the 3d dimension of the array).
Below is the code I have for animating these arrows through 'time' using a loop, but I don't see how I can make a collection of text objects, as I can with other matplotlib functions like scatter, and then collectively (and efficiently) update the set_text property of each of them. The loop over the 'time' variable is fine but I'd prefer to not use a loop over the grid of text objects if possible.
Any thoughts on how to do this without the double nested loop?
Thanks.
from matplotlib import pyplot as plt
import numpy as np
import time
import matplotlib
matplotlib.interactive(True)
arrows = [u'\u2190', u'\u2196', u'\u2191', u'\u2197', u'\u2192', u'\u2198', u'\u2193', u'\u2199']
rest_time = 0.25
steps = 20
fig, ax = plt.subplots(1, 1)
ax.plot([0,4],[0,4], c = 'white')
objs = [[[] for i in range(4)] for j in range(4)]
for i in range(4):
for j in range(4):
objs[i][j] = ax.text(i+0.5,j+0.5, arrows[i], ha = 'center', va = 'center')
fig.canvas.draw()
arrow_data = np.random.randint(0,len(arrows), (4,4,steps))
for t in range(arrow_data.shape[2]):
for i in range(4):
for j in range(4):
objs[i][j].set_text(arrows[arrow_data[i,j,t]])
fig.canvas.draw()
time.sleep(rest_time)
Related
I am trying to create a corner plot for an upcoming paper, but I'm running into difficulty. I am creating an N x N array of subplots (currently, N = 6) and then deleting a bit over half of them. The issue is that the figure doesn't seem to resize itself after I delete the extraneous subplots, so when I later add a legend using a dummy subplot, it exists in the area where a full row and column of deleted subplots were, thus enlarging the figure. I've been working on this for several hours now and haven't found a solution. Here is the MWE:
import matplotlib.pyplot as plt
%matplotlib notebook
n_char = 8
# Set up the main figure.
fig, ax = plt.subplots(n_char, n_char, figsize=(n_char, n_char))
# Get rid of the axis labels unless it's on the left-most column or bottom-most row.
for i in range(0, n_char):
# For each row, loop over each column.
for j in range(0, n_char):
# If the plot isn't in the bottom-most row, get rid of the x-axis tick labels.
if i != n_char - 1:
ax[i, j].set_xticklabels([])
# If the plot isn't in the left-most column, get rid of the y-axis tick labels.
if j != 0:
ax[i, j].set_yticklabels([])
# Remove the plots that are repetitive or boring (plotting against the same characteristic).
for i in range(0, n_char):
# For each row, loop over each column.
for j in range(0, n_char):
# Delete the offending axes.
if j >= i:
ax[i, j].remove()
# Set the spacing between the plots to a much smaller value.
fig.subplots_adjust(hspace=0.00, wspace=0.00)
# Create a big plot for the legend. Have the frame hidden.
fig.add_subplot(111, frameon=False, xticks=[], yticks=[], xticklabels=[], yticklabels=[])
# Create some dummy data to serve as the source of the legend.
plt.scatter([10], [10], color="k", s=5, zorder=2, label="Targets")
# Set the x-axis limits such that the dummy data point is invisible.
fig.gca().set_xlim(-1, 1)
# Add the legend to the plot. Have it located in the upper right.
plt.legend(scatterpoints=1, loc="upper right", fontsize=5)
# Save the final plot.
fig.savefig("./../Code Output/Other Plots/Corner_Plot_Test.png", bbox_inches="tight", dpi=500)
I have looked at many different questions here on Stack Overflow. The two most promising candidates was this one, but I found the solution wasn't quite workable due to the large number of plots (and, to be frank, I didn't fully understand the solution). I thought that the first answer in this one might also work, as I thought it was a sizing issue (i.e. the figure wasn't resizing, so creating a new subplot was creating one the size of the original figure), but all it did was resize the entire figure, so that didn't work either.
To help, I will also include an image. I took the output of the code above and edited it to show what I want:
I should add that if I don't add a subplot, the output is as I expected (i.e. it's the proper size), so the issue comes in when adding the subplot, i.e. the line fig.add_subplot(111, frameon=False, xticks=[], yticks=[], xticklabels=[], yticklabels=[]).
The use of GridSpec may help.
GridSpec is used to specify array of axes to plot. You can set widths for columns and heights for rows as ratios in the option. The unneeded row should have very small height ratio, while unneeded column very small width ratio.
Here is the runnable code and output plot:-
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
#import numpy as np
fig = plt.figure(figsize=(8, 8))
nn = 6
# will create gridspec of 6 rows, 6 columns
# 1st row will occupy v small heights
# last column will occupy v small widths
sm = 0.01 # the v small width/height
wh = (1.-sm)/(nn-1.) # useful width/height
gs = gridspec.GridSpec(nn, nn, width_ratios=[*[wh]*(nn-1), sm], \
height_ratios= [sm, *[wh]*(nn-1)])
cols, rows = nn, nn
ax = [[0 for i in range(cols)] for j in range(rows)]
for ea in range(nn):
for eb in range(nn):
ax[ea][eb] = fig.add_subplot(gs[ea, eb])
ax[ea][eb].set_xticklabels([])
ax[ea][eb].set_yticklabels([])
if eb>=ea:
ax[ea][eb].remove()
# plot data on some axes
# note that axes on the first row (index=0) are gone
ax[2][0].plot([2,5,3,7])
ax[4][2].plot([2,3,7])
# make legend in upper-right axes (GridSpec's first row, last column)
# first index: 0
# second index: nn-1
rx, cx = 0, nn-1
ax[rx][cx] = fig.add_subplot(gs[rx,cx])
hdl = ax[rx][cx].scatter([10], [10], color="k", s=5, zorder=2, label="Targets")
ax[rx][cx].set_axis_off()
#ax[rx][cx].set_visible(True) # already True
ax[rx][cx].set_xticklabels([])
ax[rx][cx].set_yticklabels([])
# plot legend
plt.legend(bbox_to_anchor=(1.0, 1.0), loc='upper right', borderaxespad=0.)
fig.subplots_adjust(hspace=0.00, wspace=0.00)
plt.show
I am using the matplotlib.pylot module to generate thousands of figures that all deal with a value called "Total Vertical Depth(TVD)". The data that these values come from are all negative numbers but the industry standard is to display them as positive (I.E. distance from zero / absolute value). My y axis is used to display the numbers and of course uses the actual value (negative) to label the axis ticks. I do not want to change the values, but am wondering how to access the text elements and just remove the negative symbols from each value(shown in red circles on the image).
Several iterations of code after diving into the matplotlib documentation has gotten me to the following code, but I am still getting an error.
locs, labels = plt.yticks()
newLabels = []
for lbl in labels:
newLabels.append((lbl[0], lbl[1], str(float(str(lbl[2])) * -1)))
plt.yticks(locs, newLabels)
It appears that some of the strings in the "labels" list are empty and therefore the cast isn't working correctly, but I don't understand how it has any empty values if the yticks() method is retrieving the current tick configuration.
#SiHA points out that if we change the data then the order of labels on the y-axis will be reversed. So we can use a ticker formatter to just change the labels without changing the data as shown in the example below:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
#ticker.FuncFormatter
def major_formatter(x, pos):
label = str(-x) if x < 0 else str(x)
return label
y = np.linspace(-3000,-1000,2001)
fig, ax = plt.subplots()
ax.plot(y)
ax.yaxis.set_major_formatter(major_formatter)
plt.show()
This gives me the following plot, notice the order of y-axis labels.
Edit:
based on the Amit's great answer, here's the solution if you want to edit the data instead of the tick formatter:
import matplotlib.pyplot as plt
import numpy as np
y = np.linspace(-3000,-1000,2001)
fig, ax = plt.subplots()
ax.plot(-y) # invert y-values of the data
ax.invert_yaxis() # invert the axis so that larger values are displayed at the bottom
plt.show()
In the following I use scatter and an own ListedColormap to plot some coloured data points. In addition the corresponding colorbar is also plotted.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap, BoundaryNorm
from numpy import arange
fig, ax = plt.subplots()
my_cm = ListedColormap(['#a71b1b','#94258f','#ea99e6','#ec9510','#ece43b','#a3f8ff','#2586df','#035e0d'])
bounds=range(8)
norm = BoundaryNorm(bounds, my_cm.N)
data = [1,2,1,3,0,5,3,4]
ret = ax.scatter(range(my_cm.N), [1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50)
cbar = fig.colorbar(ret, ax=ax, boundaries=arange(-0.5,8,1), ticks=bounds, norm=norm)
cbar.ax.tick_params(axis='both', which='both',length=0)
If my data is not covering each value of the boundary interval, the colorbar does not show all colours (like in the added figure). If data would be set to range(8), I get a dot of each colour and the colorbar also shows all colours.
How can I force the colorbar to show all defined colours even if data does not contain all boundary values?
You need to manually set vminand vmax in your call to ax.scatter:
ret = ax.scatter(range(my_cm.N), [1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50, vmin=0, vmax=7)
resulting in
If my data is not covering each value of the boundary interval, the colorbar does not show all colours (like in the added figure).
If either vminor vmax are `None the color limits are set via the method
autoscale_None, and the minimum and maximum of your data are therefore used.
So using your code it is actually not necessary for showing all colors in the colorbar that every value of the boundary interval is covered, only the minimum and maximum need to be included.
Using e.g. data = [0,0,0,0,0,0,0,7] results in the following:
When looking for something else, I found another solution to that problem: colorbar-for-matplotlib-plot-surface-command.
In that case, I do not need to set vmin and vmax and it is also working in cases if the arrays/lists of points to plot are empty. Instead a ScalarMappable is defined and provided to colorbar instead of the scatterinstance.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap, BoundaryNorm
import matplotlib.cm as cm
from numpy import arange
fig, ax = plt.subplots()
my_cm = ListedColormap(['#a71b1b','#94258f','#ea99e6','#ec9510','#ece43b','#a3f8ff','#2586df','#035e0d'])
bounds=range(8)
norm = BoundaryNorm(bounds, my_cm.N)
mappable = cm.ScalarMappable(cmap=my_cm)
mappable.set_array(bounds)
data = [] # also x and y can be []
ax.scatter(x=range(my_cm.N), y=[1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50)
cbar = fig.colorbar(mappable, ax=ax, boundaries=arange(-0.5,8,1), ticks=bounds, norm=norm)
cbar.ax.tick_params(axis='both', which='both',length=0)
I am trying to create a scatter plot of measurements where the x labels are WIFI channels. By default matplotlib is spacing the labels in proportion to their numerical value. However, I would like them to be spaced uniformly over the scatter plot. Is that possible?
This is basically what my plot code currently looks like:
- where chanPoints is a list of frequencies and measurements is a list of measurements.
plt.scatter(chanPoints,measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(Tchan,rotation = 90)
plt.title('EVM for 5G Channels by Site')
plt.show()
Numpy
You may use numpy to create an array which maps the unique items within chanPoints to numbers 0,1,2.... You can then give each of those numbers the corresponding label.
import matplotlib.pyplot as plt
import numpy as np
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
unique, index = np.unique(chanPoints, return_inverse=True)
plt.scatter(index, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(range(len(unique)), unique)
plt.title('EVM for 5G Channels by Site')
plt.show()
Seaborn
If you're happy to use seaborn, this can save a lot of manual work. Seaborn is specialized for plotting categorical data. The chanPoints would be interpreted as categories on the x axis, and have the same spacing between them, if you were e.g. using a swarmplot. If several points would then overlap, they are plotted next to each other, which may be an advantage as it allows to see the number of measurement for that channel.
import matplotlib.pyplot as plt
import seaborn.apionly as sns
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
sns.swarmplot(chanPoints, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.title('EVM for 5G Channels by Site')
plt.show()
Replace chanPoints with an index.
index = numpy.searchsorted(Tchan, chanPoints)
plt.scatter(index, measurements)
Then build your xticks with the corresponding lables.
ticks = range(len(Tchan))
plt.xticks(ticks, labels=Tchan, rotation = 90)
I am trying to plot 2 y-variables (primary and secondary y-axis) against one one x-variable (axis) in Matplotlib (scatterplot), with Python 2.7.
I first create 2 x-axes [twinx()]. Then I use a for loop to plot the primary y-axis scatter plot - I have to use a loop in order to iterate through the columns of a Pandas dataframe which are the x and y-variables. Then I would like to use a second for loop to add a secondary y-axis scatter plot. Finally, I would like to add a legend consisting of primary and secondary y-axis variable names.
However, when I follow these steps, I am having some problems:
s
1. the colors of the legend symbols are not correct
2. the colors of the symbols in the 2 scatter plots are the same so it looks like there
is no secondary y-axis
3. the x and y-tick labels are not picking up the correct font
Here is the code that I have:
import numpy as np
import matplotlib.pyplot as plt
import pylab as pl
import matplotlib.cm as cm
from matplotlib.ticker import MultipleLocator, FormatStrFormatter, FuncFormatter, AutoMinorLocator
from matplotlib import cm
import pandas as pd
from matplotlib.font_manager import FontProperties
# Loading data from *.csv file:
x_var = pd.DataFrame(np.random.rand(10,5),columns=['chan','top','lk_207','robt_sh','me_sh'])
y_var = pd.DataFrame(np.random.rand(10,5),columns=['values','count','chko','54_lib','941_dat'])
y_var_sec = pd.DataFrame(np.random.rand(10,5),columns=['header_max','two','bottom_7739','max_gain','low_ext'])
# List of primary and secondary x and y-variable names:
x_var_names = x_var.columns.tolist() #primary x variable list of names
y_var_names = y_var.columns.tolist() #primary y variable list of names
y_var_sec_names = y_var_sec.columns.tolist() #secondary y variable list of names
# Matplotlib plotting begins:
fig = plt.figure(1)
fig.set_facecolor('white')
ax = fig.add_subplot(111)
ax2=ax.twinx()
for j in range(0,len(x_var_names)): #Generate plot on primary y-axis
ax.scatter(x_var[x_var_names[j]], y_var[y_var_names[j]], color=cm.jet(1.*j/len(x_var)), label=x_var_names[j])
ax.legend(y_var_names+y_var_sec_names, loc = 1, scatterpoints = 1)
# for label in ax2.get_xticklabels(): #NOT WOKRING
# label.set_fontproperties(axis_tick_font)
title_font = {'fontname':'Times New Roman', 'size':'28', 'color':'black', 'weight':'bold','verticalalignment':'bottom'}
axis_font = {'fontname':'Constantia', 'size':'26'}
axis_tick_font = FontProperties(family='Times New Roman', style='normal', size=20, weight='normal', stretch='normal')
legend_fontsize = 20
ax.set_title(ax.get_title())
ax.grid(False)
ax.set_xlabel('Both X here_bottom',**axis_font)
ax.xaxis.set_label_position('bottom')
ax.set_ylabel('Primary Y here_left',**axis_font)
ax.xaxis.set_minor_locator(AutoMinorLocator(5))
ax.yaxis.set_minor_locator(AutoMinorLocator(5))
ax.tick_params(which='minor', length=5, width = 1)
ax.tick_params(direction='in')
ax.tick_params(which='major', width=1)
ax.tick_params(length=10)
ax2.set_ylabel('Secondary Y here_right',**axis_font)
ax2.yaxis.set_minor_locator(AutoMinorLocator(5))
ax2.tick_params(which='minor', length=5, width = 1)
ax2.tick_params(direction='in')
ax2.tick_params(which='major', width=1)
ax2.tick_params(length=10)
plt.show()
To dd the secondary y-axis plot, I would need to use ax2 in the scatter() code but I do not know how to get the colors to increment from the last primary axis color. I want the primary y-axis colors to be different from the secondary y-axis colors.
How can I plot the x-axis on the right (from inside the loop) and get the correct different colors from the primary y-axis?
How can I fix specify the font properties for the primary secondary axes' tickmarks?