I saw this example on how to create a parallel coordinate plot: Parallel Coordinates:
This creates a nice Parallel Coordinates figure, but I would like to add this plot to an already existing figure in a subplot (there should be another plot next to it in the same plot).
For the already existing figure, the figure and axes are defined as:
fig = plt.figure(figsize=plt.figaspect(2.))
ax = fig.add_subplot(1,2,1)
For the Parallel Coordinates, they suggest:
fig, axes = plt.subplots(1, dims-1, sharey=False)
How can I reconcile both initializations of the figure and the ax(es)?
One option is to create all the axes using subplots then just shift the location of the one that you don't want to have wspace=0 as is done for the Parallel Coordinate plots:
import matplotlib.pylab as plt
dims = 4
fig, axes = plt.subplots(1, dims-1 + 1, sharey=False)
plt.subplots_adjust(wspace=0)
ax1 = axes[0]
pos = ax1.get_position()
ax1.set_position(pos.translated(tx = -0.1,ty=0))
I have added 1 to the number of columns creates (leaving it explicitly -1+1) and set wspace=0 which draws all the plots adjacent to one another with no space inbetween. Take the left most axes and get the position which is a Bbox. This is nice as it gives you the ability to translate it by tx=-0.1 separating your existing figure.
Related
I'm trying to display a figure that contains 3 plots, and each of the plots is a plot of (8,1)-shaped subplots.
Essentially, I want one big figure with three sections each containing (8,1)-shaped subplots.
I'm looking for a way to do this without having to manually set all the proportions and spacings. The reason I'm doing this is to visualize an 8-channel neural signal compared to three other pre-defined signals, each signal being 8 channels.
If it makes any sense this way, I'm trying for something like this (ficticious code):
fig, ax = plt.subplots(n_figures = 3, n_rows = 8, n_cols = 1)
ax[figure_i, row_j, col_k].imshow(image)
Is there a way to do this?
Here is an example of what I am talking about. Ideally it would three subplots, and in each of the subplots there is a set of subplots of shape 8x1. I understand how to plot this all out by going through all the margins and setting the proportions, but I'm wondering if there's a simpler way to do this without having to go through all the additional code and settings as described in the above example code I've written.
You can create this kind of figure by first creating a subplot grid with the appropriate layout using the plt.subplots() function and then looping through the array of axes to plot the data, like in this example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
# Create sample signal data as a 1-D list of arrays representing 3x8 channels
signal_names = ['X1', 'X2', 'X3']
nsignals = len(signal_names) # ncols of the subplot grid
nchannels = 8 # nrows of the subplot grid
nsubplots = nsignals*nchannels
x = np.linspace(0, 14*np.pi, 100)
y_signals = nsubplots*[np.cos(x)]
# Set subplots width and height
subp_w = 10/nsignals # 10 corresponds the figure width in inches
subp_h = 0.25*subp_w
# Create figure and subplot grid with the appropriate layout and dimensions
fig, axs = plt.subplots(nchannels, nsignals, sharex=True, sharey=True,
figsize=(nsignals*subp_w, nchannels*subp_h))
# Optionally adjust the space between the subplots: this can also be done by
# adding 'gridspec_kw=dict(wspace=0.1, hspace=0.3)' to the above function
# fig.subplots_adjust(wspace=0.1, hspace=0.3)
# Loop through axes to create plots: note that the list of axes is transposed
# in this example to plot the signals one after the other column-wise, as
# indicated by the colors representing the channels
colors = nsignals*plt.get_cmap('tab10').colors[:nchannels]
for idx, ax in enumerate(axs.T.flat):
ax.plot(x, y_signals[idx], c=colors[idx])
if ax.is_first_row():
ax.set_title(signal_names[idx//nchannels], pad=15, fontsize=14)
plt.show()
Basically, I want to achieve the same as in https://stackoverflow.com/a/58413766/6197439 - except with a two plots.
The example code for this is pasted below, and here is an animated gif of how it behaves:
The thing is:
I have to make the dual axis a twiny of ax2 (the bottom subplot) so it is drawn below the shared x axis at the bottom - else it gets drawn below the top plot (and overlapping the top of the bottom plot)
After start, I first drag in the bottom subplot - both axes follow as they should
If I zoom in the bottom subplot, both x-axes scale properly - but the twin axes does not have all the labels (that is why I have on_xlims_change, which helped fix that in the linked post, where there was only one plot - but here I cannot get it to work)
If then I drag in the top subplot - only the original x-axis moves, the dual/twinned cloned x-axis does not (the gif doesn't show that, but the same goes for zooming in top subplot as well)
I have tried using the callback on either and both ax and ax2, and I couldn't get an improved behavior - however, note that the gif shows the behavior as in the code posted here (where the callback is not used).
So, how can I make the dual/twinned x-axis follow the original shared x-axis - across both zoom and pan, in both the top and the bottom subplot?
The code:
#!/usr/bin/env python3
import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]
#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=120, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)
ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)
# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))
factor = 655
old_xlims = ax2.get_xlim()
new_xlims = (factor*old_xlims[0], factor*old_xlims[1])
old_tlocs = ax2.get_xticks()
new_tlocs = [i*factor for i in old_tlocs]
print("old_xlims {} new_xlims {} old_tlocs {} new_tlocs {}".format(old_xlims, new_xlims, old_tlocs, new_tlocs))
ax22.set_xticks(new_tlocs)
ax22.set_xlim(*new_xlims)
def on_xlims_change(axes):
old_tlocs = axes.get_xticks()
new_tlocs = [i*factor for i in old_tlocs]
ax22.set_xticks(new_tlocs)
# ax.callbacks.connect('xlim_changed', on_xlims_change)
# ax2.callbacks.connect('xlim_changed', on_xlims_change)
#
# Show
plt.show()
I think I have a solution (code below):
.... thanks to the comment by #ImportanceOfBeingErnest :
axes.get_xticks() gets you the ticks before the change.
Well, now at least it makes sense, why it was so difficult to set it up :) Wish I found this info earlier ... People seem to have had a problem with it:
matplotlib interactive subplots with sharex and twiny
How to to enable sharing for the secondary axis (twiny) in python
I think I would try to share all three axes
The only info I found on this is:
Aplpy multiplot dynamic axis sharing
How share x axis of two subplots after they are created?
Apparently, one can use ax1.get_shared_x_axes().join(ax1, ax2) -> however, this join is not in the sense of "join"ing an array to string in Python, nor in the sense of appending to an array, it is in the sense of a (dis)join(t) set, apparently - so you can join three items, which is what I tried (and it seems to work):
ax.get_shared_x_axes().join(ax, ax2, ax22)
Is this correct?
and just use a different formatter on the last one.
There is decent info on that here:
https://matplotlib.org/3.1.1/gallery/ticks_and_spines/tick-formatters.html
So, finally, my code is:
#!/usr/bin/env python3
import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]
#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=100, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)
# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
#~ ax.get_shared_x_axes().join(ax, ax22) # SO:42718823
ax.get_shared_x_axes().join(ax, ax2, ax22)
#~ ax.autoscale() # <-- needed if no axes limits are explicitely set. SO:42718823
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))
ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)
factor = 655
# FuncFormatter can be used as a decorator
#ticker.FuncFormatter
def major_formatter(x, pos):
#return "[%.2f]" % x
return int(factor*x)
ax22.xaxis.set_major_formatter(major_formatter)
#
# Show
plt.show()
I've been struggling to generate the frequency plot of 2 columns named "Country" and "Company" in my DataFrame and show them as 2 subplots. Here's what I've got.
Figure1 = plt.figure(1)
Subplot1 = Figure1.add_subplot(2,1,1)
and here I'm going to use the bar chart pd.value_counts(DataFrame['Country']).plot('barh')
to shows as first subplot.
The problem is, I cant just go: Subplot1.pd.value_counts(DataFrame['Country']).plot('barh') as Subplot1. has no attribute pd. ~ Could anybody shed some light in to this?
Thanks a million in advance for your tips,
R.
You don't have to create Figure and Axes objects separately, and you should probably avoid initial caps in variable names, to differentiate them from classes.
Here, you can use plt.subplots, which creates a Figure and a number of Axes and binds them together. Then, you can just pass the Axes objects to the plot method of pandas:
from matplotlib import pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
pd.value_counts(df['Country']).plot('barh', ax=ax1)
pd.value_counts(df['Company']).plot('barh', ax=ax2)
Pandas' plot method can take in a Matplotlib axes object and direct the resulting plot into that subplot.
# If you want a two plots, one above the other.
nrows = 2
ncols = 1
# Here axes contains 2 objects representing the two subplots
fig, axes = plt.subplots(nrows, ncols, figsize=(8, 4))
# Below, "my_data_frame" is the name of your Pandas dataframe.
# Change it accordingly for the code to work.
# Plot first subplot
# This counts the number of times each country appears and plot
# that as a bar char in the first subplot represented by axes[0].
my_data_frame['Country'].value_counts().plot('barh', ax=axes[0])
# Plot second subplot
my_data_frame['Company'].value_counts().plot('barh', ax=axes[1])
I have been trouble with trying to find a way to display a 3 element list in the form of a table. What I actually care about is drawing the table. I would like to draw a 1by3 table for each ylabel in a plot.
Below is what I have so far. If I can get each Table instance to show up, I will have what I want. Right now a reference to a table appears and I'm not sure why. If you actually look in the center left where the reference locations appear, you can see one 1by3 table.
Is it possible using matplotlib to generate a new table for each ylabel? The table info is directly related to each row in the bar graph, so it's important that I have a way that they line up.
The number of rows in the bar graph is dynamic, so creating 1 table for the whole figure and trying to dynamically line up the rows with the corresponding bar graph is a difficult problem.
# initialize figure
fig = plt.figure()
gs = gridspec.GridSpec(1, 2, width_ratios=[2, 1])
fig.set_size_inches(18.5, 10.5)
ax = fig.add_subplot(gs[0])
#excluded bar graph code
# create ylabels
for row in range(1,len(data)):
ylabel = [str(data[row][0]),str(data[row][1]),str(data[row][2])]
ylabels.append(ylabel)
#attempting to put each ylabel in a 1by3 table to display
pos = np.arange(0.5,10.5,0.5)
axTables = [None] * len(ylabels)
for x in range(0,len(ylabels)):
axTables[x] = fig.add_subplot(gs[0])
ylabels[x] = axTables[x].table(cellText=[ylabels[x]], loc='left')
locsy, labelsy = plt.yticks(pos,ylabels)
First, yticks will expect text as input, it cannot handle other objects. Second, a table needs to sit within an axes.
So in order to get a table at the position of a tick(label) the idea can be to create an axes at the position of a y ticklabel. An option is the use of mpl_toolkits.axes_grid1.inset_locator.inset_axes. Now the difficulty is that this axes needs to be positionned in data coordinates along the y axis, and in figure (or pixel-) coorinates in the horizontal direction. For this one might use a blended transform. The inset_axes allows to give the width and height as absolute measures (in inches) or in relative, which is handy because we can set the width of the axes to 100% of the bounding box, while the height is still some absolute value (we don't want the axes height to depend on the data coordinates!).
In the following a function ax_at_posy creates such axes.
The table would then sit tight inside the axes, such that all columns are the same width. One would still need to make sure the same fontsize is used throughout.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import matplotlib.transforms as mtrans
# General helper function to create an axes at the position of a yticklabel
def ax_at_posy(y, ax=None, width=0.3, leftspace=0.08, height=0.2):
ax = ax or plt.gca()
trans = mtrans.blended_transform_factory(ax.figure.transFigure, ax.transData)
axins = inset_axes(ax, "100%", height,
bbox_to_anchor=(leftspace, y, width-leftspace, 0.05),
bbox_transform=trans, loc="center right", borderpad=0.8)
axins.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
axins.axis("off")
return axins
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.4)
ax.scatter(np.random.rand(30), np.random.randint(7, size=30), c=np.random.rand(30))
get_data = lambda i: "".join(np.random.choice(list("abcdefgxyzt0"), size=i+2))
data = np.vectorize(get_data)(np.random.randint(2,6,size=(7,3)))
for i, row in enumerate(data):
axi = ax_at_posy(i, ax=ax, width=0.4)
tab = axi.table(cellText=[list(row)], loc='center', bbox=(0,0,1,1))
tab.auto_set_font_size(False)
tab.set_fontsize(9)
plt.setp(tab.get_celld().values(), linewidth=0.72)
plt.show()
I have a plot in which I want to have one panel separate from other four panels. I want the rest of the four panels to share the x axis. The figure is shown below. I want the bottom four panels to have shared x-axis. I tried
f = plt.figure()
ax6=f.add_subplot(511)
ax4=f.add_subplot(515)
ax1=f.add_subplot(512,sharex=ax4)
ax2=f.add_subplot(513,sharex=ax4)
ax3=f.add_subplot(514,sharex=ax4)
However, that does not work for me. The attached figure is made with
f = plt.figure()
ax6=f.add_subplot(511)
ax4=f.add_subplot(515)
ax1=f.add_subplot(512)
ax2=f.add_subplot(513)
ax3=f.add_subplot(514)
and then setting the xticks to none by
ax1.get_xaxis().set_ticklabels([])
ax2.get_xaxis().set_ticklabels([])
ax3.get_xaxis().set_ticklabels([])
using f.subplots_adjust(hspace=0) joins all the subplots. Is there a way to join only the bottom four panels?
Thanks!
It's easiest to use two separate gridspec objects for this. That way you can have independent margins, padding, etc for different groups of subplots.
As a quick example:
import numpy as np
import matplotlib.pyplot as plt
# We'll use two separate gridspecs to have different margins, hspace, etc
gs_top = plt.GridSpec(5, 1, top=0.95)
gs_base = plt.GridSpec(5, 1, hspace=0)
fig = plt.figure()
# Top (unshared) axes
topax = fig.add_subplot(gs_top[0,:])
topax.plot(np.random.normal(0, 1, 1000).cumsum())
# The four shared axes
ax = fig.add_subplot(gs_base[1,:]) # Need to create the first one to share...
other_axes = [fig.add_subplot(gs_base[i,:], sharex=ax) for i in range(2, 5)]
bottom_axes = [ax] + other_axes
# Hide shared x-tick labels
for ax in bottom_axes[:-1]:
plt.setp(ax.get_xticklabels(), visible=False)
# Plot variable amounts of data to demonstrate shared axes
for ax in bottom_axes:
data = np.random.normal(0, 1, np.random.randint(10, 500)).cumsum()
ax.plot(data)
ax.margins(0.05)
plt.show()