Visualizing easydecon results

These recipes use Matplotlib only. Every example aligns values to table.obs.index before plotting.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import easydecon as ed

table = ed.get_table(sdata)
coords = np.asarray(table.obsm["spatial"])

If your platform’s y-axis convention is image-like, call ax.invert_yaxis() after plotting.

Hard assignment map

assignment_column = result.diagnostics.get(
    "results_column",
    result.assigned_labels.columns[0],
)
labels = result.assigned_labels[assignment_column].reindex(table.obs.index)

fig, ax = plt.subplots(figsize=(6, 6))
for label in labels.dropna().astype(str).unique():
    mask = labels.astype(str).eq(label).to_numpy()
    ax.scatter(coords[mask, 0], coords[mask, 1], s=8, label=label)

ax.set_title("easydecon assignments")
ax.set_aspect("equal")
ax.set_xlabel("Spatial x")
ax.set_ylabel("Spatial y")
ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left", frameon=False)
fig.tight_layout()

Unassigned locations are omitted here. Assignment counts are counts of spatial units, not cell counts.

Single-cell-type prior

cell_type = "Myeloid"
values = result.priors_df[cell_type].reindex(table.obs.index).fillna(0.0)

fig, ax = plt.subplots(figsize=(6, 6))
points = ax.scatter(coords[:, 0], coords[:, 1], c=values.to_numpy(), s=8)
fig.colorbar(points, ax=ax, label="Phase 1 prior")
ax.set_title(f"{cell_type} Phase 1 prior")
ax.set_aspect("equal")
fig.tight_layout()

Single-cell-type posterior

cell_type = "Myeloid"

if result.posterior_df is None:
    raise ValueError("This workflow has no posterior_df; use assignment_df instead.")

values = result.posterior_df[cell_type].reindex(table.obs.index).fillna(0.0)

fig, ax = plt.subplots(figsize=(6, 6))
points = ax.scatter(coords[:, 0], coords[:, 1], c=values.to_numpy(), s=8)
fig.colorbar(points, ax=ax, label="Posterior support")
ax.set_title(f"{cell_type} posterior support")
ax.set_aspect("equal")
fig.tight_layout()

Phase 1 versus posterior comparison

def plot_spatial_values(table, values, title, colorbar_label):
    coords = np.asarray(table.obsm["spatial"])
    values = values.reindex(table.obs.index).fillna(0.0)

    fig, ax = plt.subplots(figsize=(6, 6))
    points = ax.scatter(coords[:, 0], coords[:, 1], c=values.to_numpy(), s=8)
    fig.colorbar(points, ax=ax, label=colorbar_label)
    ax.set_title(title)
    ax.set_aspect("equal")
    ax.set_xlabel("Spatial x")
    ax.set_ylabel("Spatial y")
    fig.tight_layout()
    return fig, ax
plot_spatial_values(table, result.priors_df["Myeloid"], "Myeloid prior", "Prior")
plot_spatial_values(
    table,
    result.posterior_df["Myeloid"],
    "Myeloid posterior",
    "Posterior support",
)

Assignment counts

assignment_column = result.diagnostics.get(
    "results_column",
    result.assigned_labels.columns[0],
)
counts = (
    result.assigned_labels[assignment_column]
    .dropna()
    .value_counts()
    .sort_values(ascending=False)
)

fig, ax = plt.subplots(figsize=(7, 4))
counts.plot.bar(ax=ax)
ax.set_title("Assigned spatial locations per group")
ax.set_xlabel("Marker group")
ax.set_ylabel("Number of spatial locations")
fig.tight_layout()

Posterior heatmap

if result.posterior_df is None:
    raise ValueError("This workflow has no posterior_df; use assignment_df instead.")

matrix = result.posterior_df.copy()
matrix = matrix.loc[matrix.max(axis=1).sort_values(ascending=False).index]
matrix = matrix.iloc[:100]

fig, ax = plt.subplots(figsize=(8, 5))
image = ax.imshow(matrix.to_numpy(), aspect="auto", interpolation="nearest")
ax.set_xticks(range(matrix.shape[1]))
ax.set_xticklabels(matrix.columns, rotation=90)
ax.set_ylabel("Spatial locations")
ax.set_title("Posterior support")
fig.colorbar(image, ax=ax, label="Posterior support")
fig.tight_layout()

Maximum-posterior confidence map

if result.posterior_df is None:
    raise ValueError("This workflow has no posterior_df; use assignment_df instead.")

confidence = result.posterior_df.max(axis=1).reindex(table.obs.index).fillna(0.0)

fig, ax = plt.subplots(figsize=(6, 6))
points = ax.scatter(coords[:, 0], coords[:, 1], c=confidence.to_numpy(), s=8)
fig.colorbar(points, ax=ax, label="Maximum posterior support")
ax.set_title("Assignment confidence")
ax.set_aspect("equal")
fig.tight_layout()

Unassigned-location map

assignment_column = result.diagnostics.get(
    "results_column",
    result.assigned_labels.columns[0],
)
labels = result.assigned_labels[assignment_column].reindex(table.obs.index)
unassigned = labels.isna().to_numpy()

fig, ax = plt.subplots(figsize=(6, 6))
ax.scatter(coords[~unassigned, 0], coords[~unassigned, 1], s=6, alpha=0.3)
ax.scatter(coords[unassigned, 0], coords[unassigned, 1], s=10)
ax.set_title("Unassigned spatial locations")
ax.set_aspect("equal")
fig.tight_layout()

Niche composition plot

niches, smoothed = ed.detect_niches_from_easydecon_result(
    sdata,
    result,
    n_neighbors=6,
    n_niches=5,
)

fig, ax = ed.plot_niche_compositions(smoothed, niches)

Niche IDs are categorical labels and do not imply an ordering.