@@ -203,3 +203,167 @@ def sample_heatmap(
203203# swap_axes=True,
204204# show=False,
205205# )
206+
207+
208+ def qsep_heatmap (
209+ data : AnnData ,
210+ normalize : bool = True ,
211+ ax : plt .Axes = None ,
212+ cmap : str = "RdBu_r" ,
213+ ** kwargs ,
214+ ) -> plt .Axes :
215+ """Plot QSep cluster distance heatmap.
216+
217+ Parameters
218+ ----------
219+ data : AnnData
220+ Annotated data matrix containing QSep results.
221+ normalize : bool, optional
222+ If True, normalize distances by diagonal values.
223+ Defaults to True.
224+ ax : matplotlib.axes.Axes, optional
225+ Axes to plot on. If None, current axes will be used.
226+ cmap : str, optional
227+ Colormap to use. Defaults to "RdBu_r".
228+ **kwargs
229+ Additional arguments passed to sns.heatmap.
230+
231+ Returns
232+ -------
233+ matplotlib.axes.Axes
234+ The axes object with the plot.
235+ """
236+ if ax is None :
237+ ax = plt .gca ()
238+
239+ try :
240+ distances = data .uns ["cluster_distances" ]["distances" ]
241+ clusters = data .uns ["cluster_distances" ]["clusters" ]
242+ except KeyError :
243+ raise ValueError (
244+ "Cluster distances not found in data.uns['cluster_distances'], run gr.tl.qsep_score first"
245+ )
246+
247+ if normalize :
248+ # Normalize by diagonal values
249+ norm_distances = distances / np .diag (distances )[:, np .newaxis ]
250+ plot_data = norm_distances [::- 1 ]
251+ vmin = 1.0
252+ vmax = np .max (norm_distances )
253+ else :
254+ plot_data = distances [::- 1 ]
255+ vmin = None
256+ vmax = None
257+
258+ # Create heatmap
259+ sns .heatmap (
260+ plot_data ,
261+ xticklabels = clusters ,
262+ yticklabels = clusters [::- 1 ], # Reverse the y-axis labels
263+ cmap = cmap ,
264+ vmin = vmin ,
265+ vmax = vmax ,
266+ ax = ax ,
267+ ** kwargs ,
268+ )
269+
270+ ax .set_title ("QSep Cluster Distances" + (" (Normalized)" if normalize else "" ))
271+
272+ return ax
273+
274+
275+ def qsep_boxplot (
276+ data : AnnData ,
277+ normalize : bool = True ,
278+ ax : plt .Axes = None ,
279+ palette : str = "Set2" ,
280+ ** kwargs ,
281+ ) -> plt .Axes :
282+ """Plot QSep cluster distances as boxplots.
283+
284+ Parameters
285+ ----------
286+ data : AnnData
287+ Annotated data matrix containing QSep results.
288+ normalize : bool, optional
289+ If True, normalize distances by diagonal values.
290+ Defaults to True.
291+ ax : matplotlib.axes.Axes, optional
292+ Axes to plot on. If None, current axes will be used.
293+ palette : str, optional
294+ Color palette for the boxplots. Defaults to "Set2".
295+ **kwargs
296+ Additional arguments passed to sns.boxplot.
297+
298+ Returns
299+ -------
300+ matplotlib.axes.Axes
301+ The axes object with the plot.
302+ """
303+ if ax is None :
304+ ax = plt .gca ()
305+
306+ try :
307+ distances = data .uns ["cluster_distances" ]["distances" ]
308+ clusters = data .uns ["cluster_distances" ]["clusters" ]
309+ except KeyError :
310+ raise ValueError (
311+ "Cluster distances not found in data.uns['cluster_distances'], run gr.tl.qsep_score first"
312+ )
313+
314+ if normalize :
315+ # Normalize by diagonal values
316+ distances = distances / np .diag (distances )[:, np .newaxis ]
317+
318+ # Create DataFrame for plotting
319+ plot_data = []
320+ for i , ref_cluster in enumerate (clusters ):
321+ for j , target_cluster in enumerate (clusters ):
322+ plot_data .append (
323+ {
324+ "Reference Cluster" : ref_cluster ,
325+ "Target Cluster" : target_cluster ,
326+ "Distance" : distances [i , j ],
327+ "color" : "grey" if i == j else "red" ,
328+ }
329+ )
330+ plot_df = pd .DataFrame (plot_data )
331+ print (plot_df )
332+
333+ # Create boxplot
334+ sns .boxplot (
335+ data = plot_df ,
336+ x = "Distance" ,
337+ y = "Reference Cluster" ,
338+ color = "grey" ,
339+ orient = "h" ,
340+ ax = ax ,
341+ legend = False ,
342+ showfliers = False ,
343+ ** kwargs ,
344+ )
345+
346+ # Add individual points
347+ sns .stripplot (
348+ data = plot_df ,
349+ x = "Distance" ,
350+ y = "Reference Cluster" ,
351+ hue = "color" ,
352+ # hue="Target Cluster",
353+ orient = "h" ,
354+ size = 4 ,
355+ # color=".3",
356+ alpha = 0.6 ,
357+ ax = ax ,
358+ legend = False ,
359+ )
360+
361+ # Customize plot
362+ if normalize :
363+ ax .axvline (x = 1 if normalize else 0 , color = "gray" , linestyle = "--" , alpha = 0.5 )
364+ ax .set_xlabel ("QSep Cluster Distances" + (" (Normalized)" if normalize else "" ))
365+
366+ # Move legend outside
367+ # ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0.0)
368+
369+ return ax
0 commit comments