snputils.visualization.admixture
1import numpy as np 2import matplotlib.pyplot as plt 3import matplotlib 4 5def reorder_admixture(Q_mat): 6 """ 7 Reorder Q_mat rows so that rows are grouped by each sample's dominant ancestry, 8 and columns are sorted by descending average ancestry proportion. 9 """ 10 n_samples, K = Q_mat.shape 11 12 # Reorder columns by descending average proportion 13 col_means = Q_mat.mean(axis=0) 14 col_order = np.argsort(col_means)[::-1] # largest first 15 Q_cols_sorted = Q_mat[:, col_order] 16 17 # Group samples by whichever column is argmax 18 row_groups = [] 19 boundary_list = [0] 20 argmax_all = np.argmax(Q_cols_sorted, axis=1) 21 for k in range(K): 22 rows_k = np.where(argmax_all == k)[0] 23 # Sort these rows by their proportion in col k 24 rows_k_sorted = rows_k[np.argsort(Q_cols_sorted[rows_k, k])[::-1]] 25 row_groups.append(rows_k_sorted) 26 boundary_list.append(boundary_list[-1] + len(rows_k_sorted)) 27 28 # Combine them into one final row order 29 row_order = np.concatenate(row_groups) if row_groups else np.arange(n_samples) 30 Q_mat_sorted = Q_cols_sorted[row_order, :] 31 32 return Q_mat_sorted, row_order, boundary_list, col_order 33 34def plot_admixture(ax, Q_mat_sorted, boundary_list, col_order=None, colors=None, show_boundaries=True, show_axes_labels=True, show_ticks=True, set_limits=True, minimal=False): 35 """ 36 Plot a structure-style bar chart of Q_mat_sorted in the given Axes ax. 37 If colors is not None, it should be a list or array of length K. 38 If col_order is not None, colors are reordered according to col_order. 39 40 Optional controls: 41 - show_boundaries (bool): draw vertical lines at group boundaries. Default True. 42 - show_axes_labels (bool): set X/Y axis labels. Default True. 43 - show_ticks (bool): show axis ticks. Default True. 44 - set_limits (bool): set xlim and ylim to [0, n_samples-1] and [0,1]. Default True. 45 - minimal (bool): if True, overrides to disable boundaries, labels, ticks, limits and hides spines. 46 """ 47 n_samples, K = Q_mat_sorted.shape 48 49 # Minimal overrides 50 if minimal: 51 show_boundaries = False 52 show_axes_labels = False 53 show_ticks = False 54 set_limits = False 55 56 # If we have a specific color list and a col_order, reorder the colors to match the columns 57 if (colors is not None) and (col_order is not None): 58 colors = [colors[idx] for idx in col_order] 59 60 # cumulative sum across columns for stacked fill 61 Q_cum = np.cumsum(Q_mat_sorted, axis=1) 62 # Use step='post' with padded x/y so the last bar renders fully and no thin band appears 63 x_edges = np.arange(n_samples + 1) 64 Q_pad = np.vstack([Q_cum, Q_cum[-1]]) 65 66 # fill-between for a stacked bar effect 67 for j in range(K): 68 c = colors[j] if (colors is not None) else None 69 lower = Q_pad[:, j - 1] if j > 0 else np.zeros(n_samples + 1) 70 upper = Q_pad[:, j] 71 ax.fill_between( 72 x_edges, 73 lower, 74 upper, 75 step="post", 76 color=c, 77 linewidth=0, 78 edgecolor='none', 79 ) 80 81 # Vertical lines for group boundaries 82 if show_boundaries: 83 for boundary in boundary_list: 84 ax.axvline(boundary, color='black', ls='--', lw=1.0) 85 86 if set_limits: 87 ax.set_xlim(0, n_samples) 88 ax.set_ylim(0, 1) 89 90 if show_axes_labels: 91 ax.set_xlabel("Samples") 92 ax.set_ylabel("Ancestry Proportion") 93 94 if not show_ticks: 95 ax.set_xticks([]) 96 ax.set_yticks([]) 97 98 if minimal: 99 # Hide all spines for a clean, minimal appearance 100 for spine in ax.spines.values(): 101 spine.set_visible(False)
def
reorder_admixture(Q_mat):
6def reorder_admixture(Q_mat): 7 """ 8 Reorder Q_mat rows so that rows are grouped by each sample's dominant ancestry, 9 and columns are sorted by descending average ancestry proportion. 10 """ 11 n_samples, K = Q_mat.shape 12 13 # Reorder columns by descending average proportion 14 col_means = Q_mat.mean(axis=0) 15 col_order = np.argsort(col_means)[::-1] # largest first 16 Q_cols_sorted = Q_mat[:, col_order] 17 18 # Group samples by whichever column is argmax 19 row_groups = [] 20 boundary_list = [0] 21 argmax_all = np.argmax(Q_cols_sorted, axis=1) 22 for k in range(K): 23 rows_k = np.where(argmax_all == k)[0] 24 # Sort these rows by their proportion in col k 25 rows_k_sorted = rows_k[np.argsort(Q_cols_sorted[rows_k, k])[::-1]] 26 row_groups.append(rows_k_sorted) 27 boundary_list.append(boundary_list[-1] + len(rows_k_sorted)) 28 29 # Combine them into one final row order 30 row_order = np.concatenate(row_groups) if row_groups else np.arange(n_samples) 31 Q_mat_sorted = Q_cols_sorted[row_order, :] 32 33 return Q_mat_sorted, row_order, boundary_list, col_order
Reorder Q_mat rows so that rows are grouped by each sample's dominant ancestry, and columns are sorted by descending average ancestry proportion.
def
plot_admixture( ax, Q_mat_sorted, boundary_list, col_order=None, colors=None, show_boundaries=True, show_axes_labels=True, show_ticks=True, set_limits=True, minimal=False):
35def plot_admixture(ax, Q_mat_sorted, boundary_list, col_order=None, colors=None, show_boundaries=True, show_axes_labels=True, show_ticks=True, set_limits=True, minimal=False): 36 """ 37 Plot a structure-style bar chart of Q_mat_sorted in the given Axes ax. 38 If colors is not None, it should be a list or array of length K. 39 If col_order is not None, colors are reordered according to col_order. 40 41 Optional controls: 42 - show_boundaries (bool): draw vertical lines at group boundaries. Default True. 43 - show_axes_labels (bool): set X/Y axis labels. Default True. 44 - show_ticks (bool): show axis ticks. Default True. 45 - set_limits (bool): set xlim and ylim to [0, n_samples-1] and [0,1]. Default True. 46 - minimal (bool): if True, overrides to disable boundaries, labels, ticks, limits and hides spines. 47 """ 48 n_samples, K = Q_mat_sorted.shape 49 50 # Minimal overrides 51 if minimal: 52 show_boundaries = False 53 show_axes_labels = False 54 show_ticks = False 55 set_limits = False 56 57 # If we have a specific color list and a col_order, reorder the colors to match the columns 58 if (colors is not None) and (col_order is not None): 59 colors = [colors[idx] for idx in col_order] 60 61 # cumulative sum across columns for stacked fill 62 Q_cum = np.cumsum(Q_mat_sorted, axis=1) 63 # Use step='post' with padded x/y so the last bar renders fully and no thin band appears 64 x_edges = np.arange(n_samples + 1) 65 Q_pad = np.vstack([Q_cum, Q_cum[-1]]) 66 67 # fill-between for a stacked bar effect 68 for j in range(K): 69 c = colors[j] if (colors is not None) else None 70 lower = Q_pad[:, j - 1] if j > 0 else np.zeros(n_samples + 1) 71 upper = Q_pad[:, j] 72 ax.fill_between( 73 x_edges, 74 lower, 75 upper, 76 step="post", 77 color=c, 78 linewidth=0, 79 edgecolor='none', 80 ) 81 82 # Vertical lines for group boundaries 83 if show_boundaries: 84 for boundary in boundary_list: 85 ax.axvline(boundary, color='black', ls='--', lw=1.0) 86 87 if set_limits: 88 ax.set_xlim(0, n_samples) 89 ax.set_ylim(0, 1) 90 91 if show_axes_labels: 92 ax.set_xlabel("Samples") 93 ax.set_ylabel("Ancestry Proportion") 94 95 if not show_ticks: 96 ax.set_xticks([]) 97 ax.set_yticks([]) 98 99 if minimal: 100 # Hide all spines for a clean, minimal appearance 101 for spine in ax.spines.values(): 102 spine.set_visible(False)
Plot a structure-style bar chart of Q_mat_sorted in the given Axes ax. If colors is not None, it should be a list or array of length K. If col_order is not None, colors are reordered according to col_order.
Optional controls:
- show_boundaries (bool): draw vertical lines at group boundaries. Default True.
- show_axes_labels (bool): set X/Y axis labels. Default True.
- show_ticks (bool): show axis ticks. Default True.
- set_limits (bool): set xlim and ylim to [0, n_samples-1] and [0,1]. Default True.
- minimal (bool): if True, overrides to disable boundaries, labels, ticks, limits and hides spines.