
前面我们介绍了怎样在空间转录组数据分析中,基于定义好的细胞类型,自动识别出肿瘤边界,然后拟合处一条肿瘤边界线,最后分别统计每个细胞距离这条边界线的物理距离,将边界线往Tumor内部的距离定义成正值,边界往外的距离定义成负值,这样的话我们就能够得到所有细胞与肿瘤边界的距离,同时根据距离的正负值也能辨别细胞在边界线的哪个方向。
根据前面介绍的分析,拿到以下结果:

有了上述结果后,我们可以回答一系列生物学问题了,
绘图展示

以下介绍怎样实现文章中常用的展示方式,最终绘图结果如下:

绘图代码:
import pandas as pd
import numpy as np
import scanpy as sc
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 定义函数
def plot_spatial_distance_distribution(
adata,
celltype_col='celltype',
distance_col='distance',
target_celltypes=[],
bin_width=20,
x_range=(-240, 200)
):
# 1. 准备数据
df = adata.obs[[celltype_col, distance_col]].copy()
df = df[df[celltype_col].isin(target_celltypes)]
# 2. 定义分箱 (Bins), 创建从 x_range[0] 到 x_range[1] 的区间
bins = np.arange(x_range[0], x_range[1] + bin_width, bin_width)
df['bin'] = pd.cut(df[distance_col], bins=bins, include_lowest=True, right=False)
# 3. 计算上方柱状图数据 (Total Cell Count per bin)
count_data = df.groupby('bin', observed=False).size()
# 4. 计算下方堆叠图数据 (Proportions), 交叉表:行是bin,列是celltype
pivot_df = pd.crosstab(df['bin'], df[celltype_col])
prop_df = pivot_df.div(pivot_df.sum(axis=1), axis=0).fillna(0) # 归一化:将每行的计数转换为比例 (行和为1)
prop_df = prop_df[target_celltypes]
# 5. 准备绘图用的 X 轴坐标和标签, 获取每个bin的中心点用于绘图
x_centers = [b.mid for b in count_data.index]
x_labels = [f"[{int(b.left)}-{int(b.right)}]"for b in count_data.index]
# 6. 设置颜色
colors = sns.color_palette("tab20", len(target_celltypes))
# ================= 绘图开始 =================
fig = plt.figure(figsize=(8, 8))
gs = gridspec.GridSpec(2, 1, height_ratios=[1, 4], hspace=0.05)
# --- 上图:Cell Count (柱状图) ---
ax0 = plt.subplot(gs[0])
ax0.bar(x_centers, count_data.values, width=bin_width*0.9, color='#6096BA', align='center')
# 样式调整
ax0.set_ylabel("Cell count", fontsize=12)
ax0.axvline(x=0, color='gray', linestyle='--', linewidth=1.5) # 0点虚线
ax0.set_xticks([]) # 隐藏X轴刻度
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
ax0.spines['bottom'].set_visible(False)
ax0.set_xlim(x_range[0], x_range[1])
# --- 下图:Proportion (堆叠面积图) ---
ax1 = plt.subplot(gs[1])
# 绘制堆叠图 (Stackplot), transpose() 是因为 stackplot 需要 y 轴数据为 (M, N)
ax1.stackplot(x_centers, prop_df.T.values, labels=target_celltypes, colors=colors, alpha=0.9)
# 样式调整
ax1.set_ylabel("Proportion", fontsize=16)
ax1.set_ylim(0, 1)
ax1.set_xlim(x_range[0], x_range[1])
ax1.axvline(x=0, color='gray', linestyle='--', linewidth=1.5) # 0点虚线
# 设置X轴标签
ax1.set_xticks(x_centers)
ax1.set_xticklabels(x_labels, rotation=90, fontsize=10)
ax1.set_xlabel("Unit: µm", loc='left', fontsize=12)
# 添加底部箭头和文字 (Inside / Outside), 这里需要手动调整位置坐标
y_arrow = -0.35 # 箭头在X轴下方的位置
ax1.text(-120, y_arrow+0.05, "Inside", ha='center', fontsize=14)
ax1.arrow(-60, y_arrow, -100, 0, head_width=0.05, head_length=15, fc='k', ec='k', clip_on=False)
ax1.text(120, y_arrow+0.05, "Outside", ha='center', fontsize=14)
ax1.arrow(60, y_arrow, 100, 0, head_width=0.05, head_length=15, fc='k', ec='k', clip_on=False)
ax1.text(0, y_arrow, "Tumor border", ha='center', fontsize=16, clip_on=False)
# Legend (图例), 将图例放在底部
handles, labels = ax1.get_legend_handles_labels()
ax1.legend(
handles, labels,
loc='center left', # 图例盒子的锚点在"左中"
bbox_to_anchor=(1.02, 0.5), # 将锚点固定在坐标轴右边缘外侧 (X=1.02 稍微留点空隙, Y=0.5 垂直居中)
ncol=1, # 设为单列,垂直排列
frameon=False, # 去除图例边框
fontsize=14,
borderaxespad=0 # 减少内边距
)
# 边框清理
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
plt.show()
函数使用:
plot_spatial_distance_distribution(
adata,
celltype_col='celltype',
distance_col='is_fitted_boundary_dist_',
target_celltypes=adata.obs['celltype'].drop_duplicates(),
bin_width=20,
x_range=(-240, 240),
)