Source code for snputils.visualization.admixture

import numpy as np
import matplotlib.pyplot as plt
import matplotlib

[docs] def reorder_admixture(Q_mat): """ Reorder Q_mat rows so that rows are grouped by each sample's dominant ancestry, and columns are sorted by descending average ancestry proportion. """ n_samples, K = Q_mat.shape # Reorder columns by descending average proportion col_means = Q_mat.mean(axis=0) col_order = np.argsort(col_means)[::-1] # largest first Q_cols_sorted = Q_mat[:, col_order] # Group samples by whichever column is argmax row_groups = [] boundary_list = [0] argmax_all = np.argmax(Q_cols_sorted, axis=1) for k in range(K): rows_k = np.where(argmax_all == k)[0] # Sort these rows by their proportion in col k rows_k_sorted = rows_k[np.argsort(Q_cols_sorted[rows_k, k])[::-1]] row_groups.append(rows_k_sorted) boundary_list.append(boundary_list[-1] + len(rows_k_sorted)) # Combine them into one final row order row_order = np.concatenate(row_groups) if row_groups else np.arange(n_samples) Q_mat_sorted = Q_cols_sorted[row_order, :] return Q_mat_sorted, row_order, boundary_list, col_order
[docs] 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): """ 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. """ n_samples, K = Q_mat_sorted.shape # Minimal overrides if minimal: show_boundaries = False show_axes_labels = False show_ticks = False set_limits = False # If we have a specific color list and a col_order, reorder the colors to match the columns if (colors is not None) and (col_order is not None): colors = [colors[idx] for idx in col_order] # cumulative sum across columns for stacked fill Q_cum = np.cumsum(Q_mat_sorted, axis=1) # Use step='post' with padded x/y so the last bar renders fully and no thin band appears x_edges = np.arange(n_samples + 1) Q_pad = np.vstack([Q_cum, Q_cum[-1]]) # fill-between for a stacked bar effect for j in range(K): c = colors[j] if (colors is not None) else None lower = Q_pad[:, j - 1] if j > 0 else np.zeros(n_samples + 1) upper = Q_pad[:, j] ax.fill_between( x_edges, lower, upper, step="post", color=c, linewidth=0, edgecolor='none', ) # Vertical lines for group boundaries if show_boundaries: for boundary in boundary_list: ax.axvline(boundary, color='black', ls='--', lw=1.0) if set_limits: ax.set_xlim(0, n_samples) ax.set_ylim(0, 1) if show_axes_labels: ax.set_xlabel("Samples") ax.set_ylabel("Ancestry Proportion") if not show_ticks: ax.set_xticks([]) ax.set_yticks([]) if minimal: # Hide all spines for a clean, minimal appearance for spine in ax.spines.values(): spine.set_visible(False)