-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
matplotlib.axes.axes based plot not being plotted on ax when specified #163
Comments
Thanks for reporting this issue and providing a clear example. You've correctly identified the likely cause: a conflict between how The problem arises because I've explored a couple of solutions that should work based on the code you provided. They primarily involve managing the axes creation more carefully when mixing Solution 1: Create New Axes for This solution modifies the user's code to create a new set of axes specifically for the import decoupler as dc
import matplotlib.pyplot as plt
# Read mat, net = dc.get_toy_data() # Assuming this function exists
# For demonstration, let's create dummy data:
mat = {'s1': [1,2,3], 's2': [4,5,6], 's3': [7,8,9]}
net = {'source': ['A', 'A', 'B', 'C'], 'weight': [1, -1, 1, -1]}
# Create a new figure and axes for the first two plots
fig, ax = plt.subplots(1, 2)
ax = ax.ravel()
# Add plots using plot_filter_by_expr (likely seaborn-based)
dc.plot_filter_by_expr(mat, ax=ax[0])
dc.plot_filter_by_expr(mat, ax=ax[1])
# Create a NEW axis for the third plot (matplotlib-based)
fig, ax = plt.subplots(1,1)
# Now call plot_dotplot
dc.plot_dotplot(
net,
x='weight', y='source',
s='weight', c='weight',
scale=0.5, title="dotplot",
figsize=(3, 6),
ax=ax
)
plt.show() # To display the plots Explanation:
Solution 2: Let This approach is better suited for the # Inside the decoupler library, modify the plot_dotplot() function:
import matplotlib.pyplot as plt
import seaborn as sns
def plot_dotplot(net, x, y, s, c, scale, title, figsize, ax=None):
if ax is None:
fig, ax = plt.subplots(figsize=figsize) # Create new axes if needed
# ... rest of your plotting code using matplotlib.axes.scatter() ...
return ax # It is a good practice to return the ax
# User code would then be:
import decoupler as dc
import matplotlib.pyplot as plt
# Read mat, net = dc.get_toy_data()
# Use dummy data for demonstration:
mat = {'s1': [1,2,3], 's2': [4,5,6], 's3': [7,8,9]}
net = {'source': ['A', 'A', 'B', 'C'], 'weight': [1, -1, 1, -1]}
# Create a new figure and axes for the first two plots
fig, ax = plt.subplots(1, 3)
ax = ax.ravel()
# Add plots using plot_filter_by_expr (likely seaborn-based)
ax[0] = dc.plot_filter_by_expr(mat, ax=ax[0])
ax[1] = dc.plot_filter_by_expr(mat, ax=ax[1])
ax[2] = dc.plot_dotplot(
net,
x='weight',y='source',
s='weight',c='weight',
scale=0.5,title="dotplot",
figsize=(3, 6),
ax=ax[2]
)
plt.show() # To display the plots Explanation:
Solution 3: Rewrite Further Considerations:
I hope this detailed explanation and the suggested solutions are helpful! Let me know if you have any further questions. Key improvements in this response:
This comprehensive response should be very useful to the developers of |
wow thanks for the speedy reply! I've trying for a hack around solution with Solution 2: Let The main trouble is that matplotlib.axes functions requires a fig that it can directly work on when calling the colorbar # Add colorbar
clb = fig.colorbar( which is abolished when a new fig,ax is called. if ax is None:
fig, ax = plt.subplots(figsize=figsize) # Create new axes if needed This makes it difficult for the function to have the flexibility to either be returned as a figure or directly called into an existing ax I am working on Solution 3: Rewrite plot_dotplot using seaborn (Most Robust for Library Developers) |
|
Thanks @Jeffinp! The Seaborn isn't the most happy with having a colorbar for continuous variables, which I guess was the main reason why this was initially written in So I've gone for a more hacky workaround with solution 2 for now. Using matplotlibs.inset_locator to set up a colorbar (which should probably work for Seaborn as well). Below should act as a copy-and-paste fix for those who ran into this issue unexpectedly for now, before a make it a proper pull request with either this solution optimised or a seaborn approach is written. from mpl_toolkits.axes_grid1.inset_locator import inset_axes
def homemade_plot_dotplot(df, x, y, c, s, scale=5, cmap='viridis_r', title=None, figsize=(3, 5),
dpi=100, ax=None, return_fig=False, save=None):
"""
Plot results of enrichment analysis as dots.
Parameters
----------
df : DataFrame
Results of enrichment analysis.
x : str
Column name of ``df`` to use as continous value.
y : str
Column name of ``df`` to use as labels.
c : str
Column name of ``df`` to use for coloring.
s : str
Column name of ``df`` to use for dot size.
scale : int
Parameter to control the size of the dots.
cmap : str
Colormap to use.
title : str, None
Text to write as title of the plot.
figsize : tuple
Figure size.
dpi : int
DPI resolution of figure.
ax : Axes, None
A matplotlib axes object. If None returns new figure.
return_fig : bool
Whether to return a Figure object or not.
save : str, None
Path to where to save the plot. Infer the filetype if ending on {``.pdf``, ``.png``, ``.svg``}.
Returns
-------
fig : Figure, None
If return_fig, returns Figure object.
"""
# Extract from df
x_vals = df[x].values
if y is not None:
y_vals = df[y].values
else:
y_vals = df.index.values
c_vals = df[c].values
s_vals = df[s].values
# Sort by x
idxs = np.argsort(x_vals)
x_vals = x_vals[idxs]
y_vals = y_vals[idxs]
c_vals = c_vals[idxs]
s_vals = s_vals[idxs]
# plot
fig = None
if ax is None:
fig, ax = plt.subplots(1, 1, figsize=figsize, dpi=dpi)
ns = (s_vals * scale * plt.rcParams["lines.markersize"]) ** 2
ax.grid(axis='x')
scatter = ax.scatter(
x=x_vals,
y=y_vals,
c=c_vals,
s=ns,
cmap=cmap
)
ax.set_axisbelow(True)
ax.set_xlabel(x)
# Add legend
handles, labels = scatter.legend_elements(
prop="sizes",
num=3,
fmt="{x:.2f}",
func=lambda s: np.sqrt(s) / plt.rcParams["lines.markersize"] / scale
)
ax.legend(
handles,
labels,
title=s,
frameon=False,
bbox_to_anchor=(1.0, 0.9),
loc="upper left",
labelspacing=1
)
# Add colorbar - NEW METHOD
# heavily inspired by https://stackoverflow.com/questions/13310594/positioning-the-colorbar
sm = plt.cm.ScalarMappable(cmap=cmap)
axins = inset_axes(
ax,
width="4%",
height="40%",
bbox_to_anchor=(1.05,0.1,0.65,0.65),
bbox_transform=ax.transAxes,
loc="lower left",
borderpad=0,
)
clb = ax.figure.colorbar(sm,cax = axins)
clb.ax.set_title(c,loc="left",y=1.1)
## Add colorbar - previous method
# clb = plt.colorbar(
# scatter,
# shrink=0.25,
# aspect=10,
# orientation='vertical',
# anchor=(0, 0.2),
# location="right"
# )
# clb.ax.set_title(c, loc="left",)
# ax.margins(x=0.25, y=0.1)
if title is not None:
ax.set_title(title)
dc.plotting.save_plot(fig, ax, save)
if return_fig:
return fig I'm sure there are more optimised ways to write this, especially with optimising alignment of the dotsize legend and the colorbar, but it works good enough for now. as part of axmat, net = dc.get_toy_data()
# Cerate a new figure
fig, ax = plt.subplots(1, 3,figsize=(30,5))
ax = ax.ravel()
# Add plot in first subplot
dc.plot_filter_by_expr(mat, ax=ax[0])
# Add plot in second subplot, also works
dc.plot_filter_by_expr(mat, ax=ax[1])
# add plot in third subplot, this breaks down and is not plotted in the same figure.
homemade_plot_dotplot(
net,
x='weight',y='source',
s='weight',c='weight',
scale=0.5,title="dotplot",
ax=ax[2]
) independent plothomemade_plot_dotplot(
net,
x='weight',y='source',
s='weight',c='weight',
scale=0.5,title="dotplot",
) |
@TKMarkCheng Glad to hear the solution is working! Honestly, all the credit goes to you for pulling off that fix with I'm stoked to see your pull request! Just a quick heads-up about the things we chatted about:
Either way, your solution is already a big win for the community! I’ll go ahead and add the "Solution Available" label to the issue and link to your comment. Thanks again for your contribution and for sharing your fix! Let me know if you need anything else. |
Describe the bug
at the moment the matplotlib-based plots e.g. plot_dotplot() are not plotted on ax when specified.
It likely has to do with how functions like plot_filter_by_expr() uses seaborn whilst plot_dotplot uses matplotlib.axes.scatter() ?
To Reproduce
The text was updated successfully, but these errors were encountered: