イメージング型 空間トランスクリプトーム:ニッチ・近傍解析 ─ 「隣り合っている」は観測ではなく定義

Spatial transcriptome
📚 この記事について
単一細胞解像度があるからこそできる、近傍の解析を扱います。近傍グラフの作り方が結論をどう変えるのか、置換検定の帰無仮説に何が隠れているのか、そして切り方の誤りがどう統計を歪めるのかを整理します。
🔙 前の記事:イメージング型 空間トランスクリプトーム:パネル制約下でのアノテーション
🔜 次の記事:イメージング型 空間トランスクリプトーム:空間の細胞間コミュニケーション
📌 前提パネル制約下でのアノテーションscRNA-seq解析:下流解析の全体像

空間トランスクリプトーム解析(spatial transcriptomics)のうちイメージング型(Xenium・CosMx・MERSCOPE など顕微鏡で1分子ずつ数える方式)は、1つ1つの細胞に座標が付いています。だから「どの細胞型が、どの細胞型の隣にいるか」を直接数えられます。これは scRNA-seqにはできないことです。ただし、この解析には最初に決めなければならないことがあります。そもそも「隣」とは何か、という定義です。

1. 「隣」の定義を、先に決める


図:「隣」の定義を、先に決めなければならない
図:「隣」の定義を、先に決めなければならない

近傍グラフの作り方は3つあります。Squidpy(Palla et al., Nature Methods, 2022)は、この3つをすべて扱えます。

作り方 定義 得意 不得意
半径 一定の距離以内を隣とする 距離に物理的な意味がある 密な場所で隣が増えすぎる
k 近傍 近い順に k 個を隣とする 細胞密度によらず隣の数が一定 遠い細胞まで隣にしてしまう
三角分割 接している細胞を隣とする 組織の形に沿う。細胞の接触関係に近い 組織の端で辺が長く伸びる
python
import squidpy as sq
import numpy as np

# 3つの作り方を、それぞれ別のキーに保存して比べる
sq.gr.spatial_neighbors(adata, coord_type="generic",
                        delaunay=True, key_added="delaunay")
sq.gr.spatial_neighbors(adata, coord_type="generic",
                        n_neighs=6, key_added="knn6")
sq.gr.spatial_neighbors(adata, coord_type="generic",
                        radius=30.0, key_added="r30")

# 三角分割では、境界の細胞から長い辺が伸びることがある
#   遠すぎる辺は落としておく
D = adata.obsp["delaunay_distances"]
D.data[D.data > 60.0] = 0
D.eliminate_zeros()

# 各細胞が何個の隣を持ったかを確認する
for key in ["delaunay", "knn6", "r30"]:
    deg = np.asarray(adata.obsp[key + "_connectivities"].sum(axis=1)).ravel()
    print(key, "隣の数 中央値:", float(np.median(deg)),
          "最大:", float(deg.max()))
💡 迷ったら三角分割
細胞どうしが物理的に接している関係を表現するなら、三角分割(Delaunay)が最も自然です。Squidpy の原論文でも、細胞レベルのデータに対して三角分割で近傍グラフを作り、置換検定によるエンリッチメントを計算する例が示されています。ただし、組織の端では辺が異常に長く伸びます。距離の上限を決めて、長すぎる辺は落としてください。
⚠️ 作り方を変えると、結論が変わる
「隣り合っている」は、測定された事実ではありません。こちらが定義したものです。半径 20 µm と 50 µm では、当然ちがう結果になります。どの作り方を使い、パラメータをいくつにしたかを、必ず書き残してください。論文の方法欄に書かれていない場合、その結果は再現できません。

2. 近傍エンリッチメント ─ 偶然と比べる


グラフができたら、「ある2つの細胞型が、偶然より多く隣り合っているか」を検定します。

図:近傍エンリッチメント ─ ラベルを混ぜて比べる
図:近傍エンリッチメント ─ ラベルを混ぜて比べる

やり方は単純です。細胞の位置は動かさず、細胞型のラベルだけを入れ替える。これを何百回も繰り返して、観測された隣接の数が偶然の分布からどれだけ離れているかを z スコアにします。

python
# 近傍エンリッチメント:ラベルを入れ替えて、偶然との差を見る
sq.gr.nhood_enrichment(adata, cluster_key="celltype",
                       connectivity_key="delaunay", n_perms=1000, seed=0)
sq.pl.nhood_enrichment(adata, cluster_key="celltype", method="ward")

# z スコアの表を取り出して、上位だけを見る
import pandas as pd
z = adata.uns["celltype_nhood_enrichment"]["zscore"]
cts = adata.obs["celltype"].cat.categories
zdf = pd.DataFrame(z, index=cts, columns=cts)

pairs = [
    (a, b, float(zdf.loc[a, b]))
    for i, a in enumerate(cts) for b in cts[i:]
]
pairs.sort(key=lambda t: -t[2])
for a, b, zz in pairs[:10]:
    print("%-18s %-18s z=%.1f" % (a, b, zz))
💡 位置を動かさないので、細胞密度の偏りは打ち消される
ラベルだけを入れ替えるということは、細胞がどこに何個いるかは、観測どおりのままということです。だから「細胞が密な領域では何でも隣接が多くなる」という影響は、帰無分布の側にも同じだけ入り、自動的に相殺されます。これがこの検定の優れた点です。
⚠️ 帰無仮説が、ほぼ必ず破れている
この検定の帰無仮説は「細胞型が、組織全体にわたってランダムに配置されている」です。しかし組織には、層構造や領域があります。上皮は上皮のところに、免疫細胞は浸潤したところに集まっている。この仮定は、ほぼ必ず破れています。その結果、多くの組が「有意」になります。p 値で足切りするのではなく、z スコアの大小で並べて上位だけを解釈するのが実務的です。

3. 検定でない指標も併せて見る


指標 何を見るか 検定か
近傍エンリッチメント 偶然より多く隣り合っているか 検定(置換)
相互作用行列 実際に何本の辺が張られているか 要約統計
共起確率 距離を変えたときの一緒にいる確率 要約統計
Ripley の統計量 その細胞型が集まっているか散らばっているか 要約統計
python
# 相互作用行列:検定ではなく、辺の数の要約
sq.gr.interaction_matrix(adata, cluster_key="celltype",
                         connectivity_key="delaunay", normalized=True)
sq.pl.interaction_matrix(adata, cluster_key="celltype")

# 共起確率:距離を変えながら、一緒にいる確率を見る
sq.gr.co_occurrence(adata, cluster_key="celltype")
sq.pl.co_occurrence(adata, cluster_key="celltype",
                    clusters="腫瘍細胞")

# Ripley の統計量:その細胞型が集まっているか、散らばっているか
sq.gr.ripley(adata, cluster_key="celltype", mode="L")
sq.pl.ripley(adata, cluster_key="celltype", mode="L")
💡 z スコアと辺の数を、必ず並べて見る
z スコアが高くても、実際の辺が数本しかないことがあります。稀な細胞型どうしでは、これが起こりやすい。統計的には有意でも、生物学的には偶然の数例かもしれません。相互作用行列で実際の本数を確認してから解釈してください。

共起確率は、距離のスケールを変えながら見られるのが利点です。「近距離では一緒にいるが、遠くでは関係がない」というパターンが見えます。Ripley の統計量は、細胞型ごとに「集まっているのか、ばらけているのか」を距離の関数として示します。

4. 細胞ニッチ ─ まわりの組成でクラスタリングする


2つの細胞型の関係を見るのではなく、各細胞のまわりに何がいるかを1つのベクトルにして、それをクラスタリングする方法があります。こうして得られるまとまりを細胞ニッチと呼びます。

この考え方は、大腸がんの浸潤先進部を多重免疫染色で解析した研究(Schürch et al., Cell, 2020)で確立されました。同研究は、35 人の患者の 140 の組織領域を 56 のタンパク質マーカーで解析し、9 つの保存されたニッチを同定しています。そして、ニッチの断片化やニッチ間の連絡の途絶が、予後の悪さと結びついていることを示しました。

python
import scanpy as sc
import pandas as pd

# 各細胞の「まわりの組成」をベクトルにする
W = adata.obsp["delaunay_connectivities"]
onehot = pd.get_dummies(adata.obs["celltype"]).to_numpy(dtype=float)
comp = W @ onehot # 隣の細胞型を数える
comp = comp / np.maximum(comp.sum(axis=1, keepdims=True), 1)

# 組成ベクトルをクラスタリングすると、ニッチが出てくる
nb = sc.AnnData(comp)
nb.obs_names = adata.obs_names
sc.pp.neighbors(nb, use_rep="X", n_neighbors=15)
sc.tl.leiden(nb, resolution=0.5, key_added="niche", flavor="igraph")
adata.obs["niche"] = nb.obs["niche"].values

# ニッチを組織の上に描く。ここで組織構造と対応するかを確かめる
sq.pl.spatial_scatter(adata, color="niche", shape=None)

# 各ニッチが、どの細胞型でできているかを表にする
prof = pd.DataFrame(comp, columns=adata.obs["celltype"].cat.categories)
prof["niche"] = adata.obs["niche"].values
print(prof.groupby("niche").mean().round(2))
💡 ニッチは、細胞型より上の単位
細胞型が「その細胞が何か」を表すのに対し、ニッチは「その細胞がどんな環境にいるか」を表します。同じ T細胞でも、腫瘍に囲まれた T細胞と、リンパ濾胞の中の T細胞では、置かれている状況が違います。細胞型とニッチの両方を obs に持つことで、「腫瘍ニッチにいる T細胞」のような、状況を含めた集団を定義できます。
⚠️ ニッチの数は、自分で決める
クラスタリングの解像度を上げれば、ニッチは細かく分かれます。「いくつが正しい」という基準はありません。組織像と照らして、意味のある領域に対応しているかで判断します。解像度をいくつか振って、ニッチを組織の上に描いて比べてください。クラスタリングの解像度を決めるときと、同じ判断です。

5. 切り方の誤りが、隣接の統計を歪める


ここが、この方式に固有の、最も注意すべき点です。

図:切り方の誤りは、隣接の統計をそのまま歪める
図:切り方の誤りは、隣接の統計をそのまま歪める
切り方の誤り 起きること 統計への影響
1つの細胞を2つに割った その2つは必ず隣接し、同じ型 同じ型どうしの隣接が水増しされる
2つの細胞を1つにまとめた その隣接ペアが消える 違う型どうしの隣接が過小になる
隣の分子を拾った 隣の型としてラベルされる 存在しない隣接が作られる
⚠️ 結果を見ただけでは、生物学と区別できない
「この2つの細胞型は隣り合っている」という結論が、切り方の産物である可能性を、常に持っています。しかも、隣接の統計だけを見ていては、区別がつきません。とくに疑うべきは同じ型どうしの強い隣接です。過分割で最も出やすいパターンだからです。セグメンテーション手法の選び方で計算した純度の指標と、併せて確認してください。

最も強い検証は、切り方を変えて、同じ解析を回すことです。結論が保たれるなら、それは切り方に依存していません。

python
# 切り方を変えて、同じ解析を回す
#   結論が保たれるかどうかが、最も強い検証になる
res = {}
for name, ad in [("装置の既定", ad_vendor),
                  ("分子ベース", ad_baysor)]:
    sq.gr.spatial_neighbors(ad, coord_type="generic", delaunay=True)
    sq.gr.nhood_enrichment(ad, cluster_key="celltype", n_perms=1000, seed=0)
    res[name] = ad.uns["celltype_nhood_enrichment"]["zscore"]

# 2つの z スコア行列の相関を見る
a = res["装置の既定"].ravel()
b = res["分子ベース"].ravel()
print("z スコアの相関:", round(float(np.corrcoef(a, b)[0, 1]), 3))
# 低ければ、結論は切り方に依存している

# とくに、同じ型どうしの隣接を確認する
diag = np.diag(res["装置の既定"])
print("同型どうしの z スコア:", np.round(diag, 1))
💡 これは論文に書ける検証になる
「セグメンテーションを2種類で行い、近傍エンリッチメントの z スコアが強く相関することを確認した」と書けば、その結論が計算の産物でないことの根拠になります。査読でも問われやすい点なので、先に済ませておく価値があります。

6. 組織の形が、結果を歪める


  • 境界効果:組織の端にある細胞は、片側にしか隣がいません。端に多い細胞型は、隣の数が系統的に少なくなります。
  • 組織の外の空白:三角分割では、空白をまたいで長い辺が張られます。距離の上限を決めて落としてください。
  • 組織片が分かれている場合:離れた組織片どうしが辺でつながることがあります。組織ごとにグラフを作るのが正しい。
⚠️ 壊れた領域を残したまま解析しない
転写産物レベルの QCで見たように、分子の密度が落ちていた領域を残したまま近傍解析を行うと、その領域の細胞は型付けが不確かなまま隣接統計に入ります。確信度の低い細胞がどこにあるかを地図にして、かたまっている領域は解析から外すことを検討してください。

7. 複数サンプルを比べる


サンプル間でニッチや隣接パターンを比べるとき、比べられる形にそろえる必要があります。

  • 近傍グラフの作り方とパラメータを、全サンプルで統一する。
  • セグメンテーションも統一する。切り方が違えば、隣接の統計も違ってくる。
  • 細胞型のラベル体系をそろえる。サンプルごとに独立でアノテーションすると、同じ名前が同じものを指さない。
  • ニッチは、全サンプルをまとめてクラスタリングする。別々にやると、ニッチ1どうしが対応しない。
⚠️ パネルが違うサンプルは、慎重に
パネルが違えば、分けられる細胞型も違います。共通の細胞型に粗くまとめてから比べるのが安全です。パネルの拡大が検出効率を下げうることも報告されており(Cervilla et al., Genome Biology, 2026)、共通遺伝子に絞っても条件がそろうとは限りません。

8. チェックリスト


  • 近傍グラフの作り方とパラメータを決め、記録した
  • 三角分割なら、長すぎる辺を落とした
  • 各細胞の隣の数の分布を確認したか
  • z スコアと実際の辺の本数を並べて見たか
  • z スコアの上位だけを解釈しているか(p 値で足切りしていないか)
  • ニッチを組織の上に描いて、構造と対応するか確認したか
  • 同じ型どうしの隣接を、純度の指標と併せて確認したか
  • 切り方を変えて、結論が保たれるかを確かめたか
  • 組織の端や空白の影響を考慮したか
  • 複数サンプルなら、グラフ・切り方・ラベル体系をそろえたか

まとめ


  • 「隣り合っている」は観測ではなく、こちらが定義したもの。半径・k 近傍・三角分割で結論が変わる。迷ったら三角分割。
  • 近傍エンリッチメントは位置を固定してラベルだけ入れ替えるので、細胞密度の偏りは自動的に相殺される。
  • しかし帰無仮説は「組織全体でランダム」であり、組織には構造があるのでほぼ必ず破れている。p 値ではなくz スコアの順位で解釈する。
  • z スコアと実際の辺の本数を並べて見る。稀な型どうしでは、有意でも数本しかないことがある。
  • ニッチは細胞型より上の単位。まわりの組成をベクトルにしてクラスタリングする(Schürch et al., 2020)。細胞型とニッチの両方を持つ。
  • 切り方の誤りが、隣接の統計をそのまま歪める。過分割は同じ型どうしの隣接を水増しし、過統合はペアを消す。
  • 結果を見ただけでは生物学と区別できないので、切り方を変えて結論が保たれるかを確かめる。これは論文に書ける検証になる。
  • 組織の端・空白・分かれた組織片が、グラフを歪める。距離の上限を決める。

関連記事


参考文献


  • Palla, G., Spitzer, H., Klein, M., et al. (2022). Squidpy: a scalable framework for spatial omics analysis. Nature Methods, 19(2), 171–178. doi:10.1038/s41592-021-01358-2
  • Schürch, C. M., Bhate, S. S., Barlow, G. L., et al. (2020). Coordinated cellular neighborhoods orchestrate antitumoral immunity at the colorectal cancer invasive front. Cell, 182(5), 1341–1359.e19. doi:10.1016/j.cell.2020.07.005
  • Petukhov, V., Xu, R. J., Soldatov, R. A., et al. (2022). Cell segmentation in imaging-based spatial transcriptomics. Nature Biotechnology, 40(3), 345–354. doi:10.1038/s41587-021-01044-w
  • Heidari, E., Moorman, A., Unyi, D., et al. (2025). Segger: fast and accurate cell segmentation of imaging-based spatial transcriptomics data. bioRxiv(プレプリント). doi:10.1101/2025.03.14.643160
  • Janesick, A., Shelansky, R., Gottscho, A. D., et al. (2023). High resolution mapping of the tumor microenvironment using integrated single-cell, spatial and in situ analysis. Nature Communications, 14(1), 8353. doi:10.1038/s41467-023-43458-x
  • Cervilla, S., Grases, D., Perez, E., et al. (2026). A technical comparison of spatial transcriptomics platforms across six cancer types. Genome Biology, 27, 22. doi:10.1186/s13059-026-03937-y
  • Marconato, L., Palla, G., Yamauchi, K. A., et al. (2025). SpatialData: an open and universal data framework for spatial omics. Nature Methods, 22(1), 58–62. doi:10.1038/s41592-024-02212-x
  • Wolf, F. A., Angerer, P., & Theis, F. J. (2018). SCANPY: large-scale single-cell gene expression data analysis. Genome Biology, 19, 15. doi:10.1186/s13059-017-1382-0
  • Traag, V. A., Waltman, L., & van Eck, N. J. (2019). From Louvain to Leiden: guaranteeing well-connected communities. Scientific Reports, 9, 5233. doi:10.1038/s41598-019-41695-z

コメント

タイトルとURLをコピーしました