XeniumとVisiumのデータ構造の違いと統合解析への指針
作成日: 2024年11月
データセット: 10x Genomics Cross-Platform Ovarian Cancer Dataset
著者: Spatial Data Analysis Team
目次
1. データ構造の本質的な違い
1.1 観測単位の違い(最重要)
Visium / Visium HD
- 物理的グリッド: 固定された正方形領域(8µm × 8µm)
- 生物学的意味: 不明確(1グリッドに0〜複数細胞)
- データ性質: 空間的に均一だが、生物学的には混合
- 本データセット: 445,751スポット
Xenium
- 生物学的単位: セグメンテーションで同定された個々の細胞
- 生物学的意味: 明確(1観測 = 1細胞)
- データ性質: 空間的に不均一だが、生物学的に純粋
- 本データセット: 200,900細胞
重要な帰結: Visiumは「場所」を測定、Xeniumは「細胞」を測定
1.2 遺伝子カバレッジの違い
Visium
- 全トランスクリプトーム: 18,132遺伝子(本データセット)
- 利点: 未知の遺伝子も検出、探索的解析に強い
- 欠点: 低発現遺伝子の検出感度が低い(ドロップアウト)
Xenium
- ターゲットパネル: 5,001遺伝子(本データセット)
- 利点: パネル遺伝子は超高感度、分子数に近い定量
- 欠点: パネル外の遺伝子は完全欠損
本データセットでの互換性:
- 共通遺伝子: 4,831(Xeniumの96.6%)
- Visium専用: 13,301遺伝子
- Xenium専用: 170遺伝子
実用上の問題: Xeniumパネルに含まれる遺伝子のみで解析する必要
1.3 空間情報の表現方法
Visium
座標系: 規則的グリッド (row, col)
隣接関係: 自明(上下左右の4近傍または8近傍)
距離計算: ユークリッド距離が直感的
データ構造: numpy配列やスパース行列で効率的
Xenium
座標系: 不規則な点群 (x_centroid, y_centroid)
隣接関係: 不明確(KNNやドロネー三角形分割が必要)
距離計算: 細胞密度を考慮する必要
データ構造: 点群として扱う必要
実装への影響: Xeniumでは空間近傍グラフを明示的に構築する必要
1.4 データのスパース性
本データセットでの実測値:
-
Visium HD: 非ゼロ率 6.9%(スパース率 93.1%)
- 561.49M 非ゼロエントリ / 8.08B 全要素
-
Xenium: 非ゼロ率 15.7%(スパース率 84.3%)
- 158.27M 非ゼロエントリ / 1.00B 全要素
重要な観察:
- Xeniumの方が約2.3倍高い検出率
- これは技術的な違い(NGS vs イメージング)を反映
- 正規化手法の選択に影響(Visium用の手法は過剰補正の可能性)
1.5 データ品質メトリクス(参考値)
| メトリクス | Visium HD | Xenium |
|---|---|---|
| 観測数 | 445,751 | 200,900 |
| 遺伝子数 | 18,132 | 5,001 |
| 中央値 total counts/観測 | ~1,000-2,000 | ~500-1,000 |
| 中央値 検出遺伝子数/観測 | ~500-1,000 | ~300-500 |
| 検出率 | 6.9% | 15.7% |
2. Visiumモデルへの適用戦略
2.1 前提となる典型的なVisiumモデルの想定
多くのVisium向けツール(Tangram, SpaGCN, STdeconvolve等)は以下を想定:
# 入力データ構造
adata.X # (n_spots, n_genes) のカウント行列
adata.obsm['spatial'] # (n_spots, 2) の座標
adata.obs # スポットのメタデータ
adata.var # 遺伝子のメタデータ
# 空間構造の想定
- グリッド状の規則的配置
- 隣接関係が自明
- スポット数: 数千〜数万程度
- 計算量: O(N) または O(N log N) で処理可能
2.2 戦略A: 細胞を「超高解像度スポット」として扱う
基本アイデア: Xeniumの各細胞を、極めて小さいVisiumスポットと見なす
ステップ1: データ形式の統一
# Xeniumデータの準備
import pandas as pd
# cells.parquet または cells.csv から座標を読み込み
cells_df = pd.read_csv('cells.csv')
cells_df = cells_df.set_index('cell_id')
# AnnDataに座標を追加(Visiumと同じ形式)
adata_xenium.obsm['spatial'] = cells_df.loc[
adata_xenium.obs_names,
['x_centroid', 'y_centroid']
].values
ステップ2: 空間近傍グラフの構築
import squidpy as sq
# KNN法で近傍グラフを構築(Visiumの6近傍に相当)
sq.gr.spatial_neighbors(
adata_xenium,
coord_type='generic',
n_neighs=6
)
# または距離ベース(例: 50µm以内の細胞)
sq.gr.spatial_neighbors(
adata_xenium,
coord_type='generic',
radius=50.0
)
ステップ3: 遺伝子の絞り込み
# 共通遺伝子のみに限定
common_genes = list(
set(adata_visium.var_names) & set(adata_xenium.var_names)
)
print(f"共通遺伝子数: {len(common_genes)}")
adata_xenium_filtered = adata_xenium[:, common_genes].copy()
# さらに高品質遺伝子のみに絞る(推奨)
# 両プラットフォームで100+観測で発現している遺伝子
visium_counts = (adata_visium[:, common_genes].X > 0).sum(axis=0)
xenium_counts = (adata_xenium[:, common_genes].X > 0).sum(axis=0)
high_quality_genes = [
gene for i, gene in enumerate(common_genes)
if visium_counts[0, i] >= 100 and xenium_counts[0, i] >= 100
]
print(f"高品質遺伝子数: {len(high_quality_genes)}")
ステップ4: ダウンサンプリング(必須)
import numpy as np
# 空間的に均等なサンプリング
def spatial_uniform_sampling(adata, n_sample=10000, n_grid=100):
coords = adata.obsm['spatial']
x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
# グリッド分割
x_bins = np.linspace(x_min, x_max, n_grid + 1)
y_bins = np.linspace(y_min, y_max, n_grid + 1)
x_idx = np.digitize(coords[:, 0], x_bins) - 1
y_idx = np.digitize(coords[:, 1], y_bins) - 1
grid_idx = x_idx * n_grid + y_idx
# 各グリッドから均等にサンプリング
sample_idx = []
cells_per_grid = max(1, n_sample // len(np.unique(grid_idx)))
for gid in np.unique(grid_idx):
cells_in_grid = np.where(grid_idx == gid)[0]
n_to_sample = min(cells_per_grid, len(cells_in_grid))
sampled = np.random.choice(cells_in_grid, n_to_sample, replace=False)
sample_idx.extend(sampled)
# 目標数に調整
if len(sample_idx) > n_sample:
sample_idx = np.random.choice(sample_idx, n_sample, replace=False)
return adata[sample_idx].copy()
# 実行
adata_xenium_sampled = spatial_uniform_sampling(
adata_xenium_filtered,
n_sample=10000
)
print(f"サンプリング後: {adata_xenium_sampled.shape}")
利点:
- ✅ 細胞レベルの解像度を保持
- ✅ 生物学的に意味のある単位で解析
- ✅ セルタイプ情報が直接得られる
欠点:
- ❌ 計算量が爆発的に増加(ダウンサンプリング必須)
- ❌ モデルが想定していない高解像度
- ❌ 不規則な点群への対応が必要
2.3 戦略B: グリッド化(ビニング)
基本アイデア: Xeniumデータを人工的にVisiumのグリッド構造に変換
実装方法
def bin_xenium_to_grid(adata_xenium, bin_size=55):
"""
XeniumデータをVisium風のグリッドに変換
Parameters:
-----------
adata_xenium : AnnData
Xeniumデータ(spatial座標付き)
bin_size : float
グリッドサイズ(µm)。55はVisium標準、8はVisium HD
Returns:
--------
adata_binned : AnnData
グリッド化されたデータ
"""
coords = adata_xenium.obsm['spatial']
x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
# グリッド定義
x_bins = np.arange(x_min, x_max + bin_size, bin_size)
y_bins = np.arange(y_min, y_max + bin_size, bin_size)
# 各細胞がどのグリッドに属するか
x_idx = np.digitize(coords[:, 0], x_bins) - 1
y_idx = np.digitize(coords[:, 1], y_bins) - 1
grid_idx = x_idx * len(y_bins) + y_idx
# グリッドごとに発現を集計
unique_grids = np.unique(grid_idx)
n_grids = len(unique_grids)
n_genes = adata_xenium.n_vars
# 疎行列で効率的に集計
from scipy.sparse import lil_matrix
binned_X = lil_matrix((n_grids, n_genes))
grid_coords = []
for i, gid in enumerate(unique_grids):
mask = grid_idx == gid
# このグリッドに属する細胞の発現を合計
binned_X[i] = adata_xenium.X[mask].sum(axis=0)
# グリッドの中心座標
grid_x = x_bins[gid // len(y_bins)] + bin_size / 2
grid_y = y_bins[gid % len(y_bins)] + bin_size / 2
grid_coords.append([grid_x, grid_y])
# 新しいAnnDataオブジェクト作成
import anndata as ad
adata_binned = ad.AnnData(
X=binned_X.tocsr(),
var=adata_xenium.var.copy(),
obs=pd.DataFrame(index=[f"grid_{i}" for i in range(n_grids)])
)
adata_binned.obsm['spatial'] = np.array(grid_coords)
return adata_binned
# 実行例
adata_xenium_binned = bin_xenium_to_grid(
adata_xenium,
bin_size=55 # Visium標準サイズ
)
print(f"グリッド化後: {adata_xenium_binned.shape}")
利点:
- ✅ Visiumモデルにそのまま適用可能
- ✅ 計算量が大幅に削減される
- ✅ グリッド構造を前提とするCNNモデルでも使える
- ✅ 実装が比較的シンプル
欠点:
- ❌ Xeniumの高解像度情報が失われる
- ❌ 細胞境界をまたぐ場合の処理が曖昧
- ❌ 単一細胞レベルの情報が得られない
2.4 具体例: Tangramへの適用
Tangramは、scRNA-seqデータを空間データにマッピングするツールです。
標準的なTangramの使用法(Visium)
import tangram as tg
# 標準的な使用法
tg.pp_adatas(ad_sc, ad_sp, genes=None)
ad_map = tg.map_cells_to_space(
ad_sc, # 参照scRNA-seq
ad_sp, # 空間データ(Visium)
mode='cells',
density_prior='uniform'
)
Xeniumを使う場合の修正版
import tangram as tg
# 1. 共通遺伝子に絞る
common_genes = ad_sc.var_names.intersection(ad_xenium.var_names)
print(f"共通遺伝子数: {len(common_genes)}")
ad_sc_common = ad_sc[:, common_genes].copy()
ad_xenium_common = ad_xenium[:, common_genes].copy()
# 2. Xeniumをダウンサンプリング(必須)
ad_xenium_sampled = spatial_uniform_sampling(
ad_xenium_common,
n_sample=10000
)
# 3. 前処理
tg.pp_adatas(ad_sc_common, ad_xenium_sampled, genes=None)
# 4. マッピング実行
ad_map = tg.map_cells_to_space(
ad_sc_common,
ad_xenium_sampled,
mode='cells',
density_prior='rna_count_based', # ★ 重要: uniformではなく
num_epochs=500,
device='cuda:0' # GPU推奨
)
# 5. 結果の可視化
tg.project_cell_annotations(ad_map, ad_xenium_sampled, annotation='cell_type')
重要な変更点:
-
density_prior='rna_count_based': Xeniumは細胞密度が不均一 - ダウンサンプリングは必須(20万細胞は処理不可)
- 共通遺伝子のみを使用
- GPU使用を強く推奨
2.5 他のモデルへの適用例
SpaGCN(グラフベースのクラスタリング)
import SpaGCN as spg
# Xeniumの場合、空間グラフを明示的に構築
import squidpy as sq
sq.gr.spatial_neighbors(adata_xenium_sampled, n_neighs=6)
# SpaGCN実行
spg.prefilter_genes(adata_xenium_sampled, min_cells=3)
spg.prefilter_specialgenes(adata_xenium_sampled)
# 空間グラフを使用
adj = adata_xenium_sampled.obsp['spatial_connectivities']
l = spg.search_l(p=0.5, adj=adj, start=0.01, end=1000, tol=0.01)
# クラスタリング
res = spg.search_res(
adata_xenium_sampled,
adj,
l=l,
target_num=10
)
STdeconvolve(セルタイプ分解)
# Xeniumは既に単一細胞レベルなので、
# STdeconvolveは不要(直接セルタイプアノテーション可能)
# ただし、グリッド化したXeniumには適用可能
# adata_xenium_binned を使用
3. 実装上の主要な懸念点
3.1 計算量の問題(最大の課題)
問題の本質
Visium想定:
5,000スポット × 5,000遺伝子 = 25,000,000要素
メモリ: ~200MB(スパース行列)
計算時間: 数分〜数十分
Xenium実際:
200,900細胞 × 5,001遺伝子 = 1,004,590,900要素
メモリ: ~8GB(スパース行列)
計算時間: 数時間〜不可能
比率: 約40倍のデータ量
アルゴリズムの計算量
| アルゴリズム | 計算量 | Visium (5K) | Xenium (200K) | 比率 |
|---|---|---|---|---|
| Tangram | O(N×M) | 25M | 1B | 40x |
| SpaGCN | O(N²) | 25M | 40B | 1,600x |
| 近傍探索 | O(N log N) | 60K | 2.6M | 43x |
結論: そのままでは計算不可能。ダウンサンプリングは必須
解決策の優先順位
1. ダウンサンプリング(必須)
# 推奨: 10,000細胞(元の5%)
# これでVisiumの2倍程度の計算量に
adata_xenium_sampled = spatial_uniform_sampling(adata_xenium, 10000)
2. タイリング(推奨)
# 空間を1mm×1mmのタイルに分割
tiles = create_spatial_tiles(adata_xenium, tile_size=1000)
results = []
for tile in tiles:
result = process_tile(tile) # 各タイルを独立に処理
results.append(result)
# 結果を統合
final_result = merge_tile_results(results)
3. バッチ処理(GPU使用時)
# GPUメモリに収まる単位で処理
batch_size = 1000
for i in range(0, n_cells, batch_size):
batch = adata_xenium[i:i+batch_size]
process_batch(batch)
3.2 遺伝子カバレッジの制約
問題の詳細
本データセットの場合:
Xeniumパネル: 5,001遺伝子
共通遺伝子: 4,831(96.6%)
→ 実質的には十分
しかし:
- 一部の重要なマーカー遺伝子が欠損する可能性
- 細胞型によってはパネルに含まれないマーカー
- 新規遺伝子や未知の転写産物は検出不可
対策
1. 事前確認
# 重要なマーカー遺伝子がパネルに含まれるか確認
marker_genes = ['CD3D', 'CD8A', 'CD4', 'MS4A1', 'CD14', 'FCGR3A']
missing_markers = [g for g in marker_genes if g not in adata_xenium.var_names]
if missing_markers:
print(f"警告: 以下のマーカーがパネルに含まれません: {missing_markers}")
2. 高品質遺伝子の選定
# 両プラットフォームで十分な発現がある遺伝子のみ使用
def select_high_quality_genes(adata_v, adata_x, min_cells=100):
common = list(set(adata_v.var_names) & set(adata_x.var_names))
v_counts = (adata_v[:, common].X > 0).sum(axis=0)
x_counts = (adata_x[:, common].X > 0).sum(axis=0)
hq_genes = [
gene for i, gene in enumerate(common)
if v_counts[0, i] >= min_cells and x_counts[0, i] >= min_cells
]
return hq_genes
3. パネル拡張の検討
- カスタムXeniumパネルの設計
- 複数のパネルを組み合わせる
- 他の技術(MERFISH等)との併用
3.3 ノイズモデルの不一致
Visium(NGS)のノイズ特性
1. PCR増幅バイアス
- 配列依存的な増幅効率の違い
- GCバイアス
2. シーケンシングエラー
- ランダムエラー(~0.1%)
- 体系的エラー(特定の配列で高頻度)
3. ドロップアウト
- 確率的な検出漏れ
- 低発現遺伝子で顕著
- ゼロインフレーション
Xenium(イメージング)のノイズ特性
1. バックグラウンド蛍光
- 非特異的ハイブリダイゼーション
- 自家蛍光
2. セグメンテーションエラー
- 細胞境界の誤検出
- 隣接細胞間のコンタミネーション
3. プローブ効率のばらつき
- プローブ設計依存
- 組織環境依存
正規化手法の選択
Visium用の正規化(注意が必要):
# SCTransform: NGSのドロップアウトを補正
# → Xeniumには不適切(過剰補正)
# LogNormalize: 総カウント数で正規化
# → 比較的安全だが、技術的違いは残る
Xenium推奨の正規化:
# 最小限の正規化
import scanpy as sc
# オプション1: log1p変換のみ
sc.pp.normalize_total(adata_xenium, target_sum=1e4)
sc.pp.log1p(adata_xenium)
# オプション2: raw countsを使用
# 一部のモデル(Tangram等)はraw countsを推奨
実装例
def normalize_for_integration(adata_visium, adata_xenium, method='minimal'):
"""
統合解析のための正規化
Parameters:
-----------
method : str
'minimal': log1p変換のみ
'standard': 標準的な正規化
'raw': 正規化なし
"""
if method == 'minimal':
# 両方に最小限の正規化
for adata in [adata_visium, adata_xenium]:
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
elif method == 'standard':
# Visiumは標準的な正規化
sc.pp.normalize_total(adata_visium, target_sum=1e4)
sc.pp.log1p(adata_visium)
sc.pp.scale(adata_visium)
# Xeniumは軽めの正規化
sc.pp.normalize_total(adata_xenium, target_sum=1e4)
sc.pp.log1p(adata_xenium)
elif method == 'raw':
# 正規化なし
pass
return adata_visium, adata_xenium
3.4 セグメンテーション品質への依存
Xenium特有の問題
細胞セグメンテーションの品質 → 解析結果の信頼性
問題のパターン:
1. Over-segmentation: 1細胞が複数に分割
→ 発現量が分散、低カウント細胞が増加
2. Under-segmentation: 複数細胞が1つに統合
→ 異なる細胞型の発現が混合
3. 境界の誤検出: 細胞境界が不正確
→ 隣接細胞からのコンタミネーション
QCメトリクスの確認
def check_segmentation_quality(adata_xenium):
"""
セグメンテーション品質のチェック
"""
# 1. 細胞サイズの分布
if 'cell_area' in adata_xenium.obs.columns:
area = adata_xenium.obs['cell_area']
print(f"細胞面積: median={area.median():.1f}, "
f"range=({area.min():.1f}, {area.max():.1f})")
# 異常に小さい/大きい細胞を検出
too_small = (area < area.quantile(0.01)).sum()
too_large = (area > area.quantile(0.99)).sum()
print(f"異常サイズ: 小={too_small}, 大={too_large}")
# 2. 総カウント数の分布
total_counts = adata_xenium.X.sum(axis=1).A1
print(f"総カウント: median={np.median(total_counts):.0f}")
# 3. 検出遺伝子数の分布
n_genes = (adata_xenium.X > 0).sum(axis=1).A1
print(f"検出遺伝子数: median={np.median(n_genes):.0f}")
# 4. 異常細胞の割合
low_count = (total_counts < 10).sum()
low_genes = (n_genes < 10).sum()
print(f"低品質細胞: カウント<10が{low_count}個, "
f"遺伝子<10が{low_genes}個")
return {
'total_counts': total_counts,
'n_genes': n_genes
}
# 実行
qc_metrics = check_segmentation_quality(adata_xenium)
フィルタリング
def filter_low_quality_cells(adata_xenium,
min_counts=50,
min_genes=20,
max_counts=None):
"""
低品質細胞の除去
"""
# QCメトリクスの計算
sc.pp.calculate_qc_metrics(adata_xenium, inplace=True)
# フィルタリング
sc.pp.filter_cells(adata_xenium, min_counts=min_counts)
sc.pp.filter_cells(adata_xenium, min_genes=min_genes)
if max_counts:
adata_xenium = adata_xenium[
adata_xenium.obs['total_counts'] < max_counts
]
print(f"フィルタリング後: {adata_xenium.shape}")
return adata_xenium
3.5 座標系と空間スケールの違い
問題
Visium:
- 座標単位: ピクセルまたはµm
- スケール: 固定グリッド(8µm, 55µm等)
- 原点: 画像の左上または中心
Xenium:
- 座標単位: µm(通常)
- スケール: 細胞サイズに依存(不均一)
- 原点: 組織の任意の点
→ 直接比較や統合が困難
対策
1. 座標の正規化
def normalize_coordinates(adata, scale='um'):
"""
座標を正規化
"""
coords = adata.obsm['spatial'].copy()
# 原点を(0, 0)に
coords -= coords.min(axis=0)
# スケールを統一
if scale == 'normalized':
# [0, 1]に正規化
coords /= coords.max(axis=0)
adata.obsm['spatial'] = coords
return adata
2. H&E画像との対応付け(Registration)
import cv2
from skimage import transform
def register_to_image(adata, he_image, transform_matrix=None):
"""
空間座標をH&E画像に対応付け
"""
coords = adata.obsm['spatial']
if transform_matrix is None:
# アフィン変換行列を推定
# (ランドマークポイントが必要)
transform_matrix = estimate_transform(coords, he_image)
# 座標を変換
coords_transformed = transform.matrix_transform(
coords,
transform_matrix
)
adata.obsm['spatial_registered'] = coords_transformed
return adata
4. 実装の推奨フロー
4.1 データ準備フェーズ
ステップ1: データ読み込みと基本QC
import scanpy as sc
import pandas as pd
import numpy as np
# 1. Visium HDの読み込み
adata_visium = sc.read_10x_h5('visium_hd.h5')
adata_visium.var_names_make_unique()
# 2. Xeniumの読み込み
adata_xenium = sc.read_10x_h5('xenium.h5')
adata_xenium.var_names_make_unique()
# 3. Xeniumの座標情報を追加
cells_df = pd.read_csv('cells.csv').set_index('cell_id')
adata_xenium.obsm['spatial'] = cells_df.loc[
adata_xenium.obs_names,
['x_centroid', 'y_centroid']
].values
# 4. 基本QC
sc.pp.calculate_qc_metrics(adata_visium, inplace=True)
sc.pp.calculate_qc_metrics(adata_xenium, inplace=True)
print(f"Visium: {adata_visium.shape}")
print(f"Xenium: {adata_xenium.shape}")
ステップ2: 共通遺伝子の選定
# 共通遺伝子の抽出
common_genes = sorted(list(
set(adata_visium.var_names) & set(adata_xenium.var_names)
))
print(f"共通遺伝子数: {len(common_genes)}")
# 高品質遺伝子の選定
def select_high_quality_genes(adata_v, adata_x, common_genes,
min_cells=100, min_counts=10):
"""
両プラットフォームで十分な発現がある遺伝子を選定
"""
# 各遺伝子が何細胞で発現しているか
v_detection = (adata_v[:, common_genes].X > 0).sum(axis=0).A1
x_detection = (adata_x[:, common_genes].X > 0).sum(axis=0).A1
# 平均発現量
v_mean = adata_v[:, common_genes].X.mean(axis=0).A1
x_mean = adata_x[:, common_genes].X.mean(axis=0).A1
# フィルタリング
hq_genes = [
gene for i, gene in enumerate(common_genes)
if (v_detection[i] >= min_cells and
x_detection[i] >= min_cells and
v_mean[i] >= min_counts and
x_mean[i] >= min_counts)
]
return hq_genes
high_quality_genes = select_high_quality_genes(
adata_visium,
adata_xenium,
common_genes
)
print(f"高品質遺伝子数: {len(high_quality_genes)}")
# データを絞り込み
adata_visium_filtered = adata_visium[:, high_quality_genes].copy()
adata_xenium_filtered = adata_xenium[:, high_quality_genes].copy()
4.2 前処理フェーズ
ステップ3: Xeniumのダウンサンプリング
def spatial_uniform_sampling(adata, n_sample=10000, n_grid=100, seed=42):
"""
空間的に均等なサンプリング
"""
np.random.seed(seed)
coords = adata.obsm['spatial']
# 空間範囲
x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
# グリッド分割
x_bins = np.linspace(x_min, x_max, n_grid + 1)
y_bins = np.linspace(y_min, y_max, n_grid + 1)
x_idx = np.digitize(coords[:, 0], x_bins) - 1
y_idx = np.digitize(coords[:, 1], y_bins) - 1
grid_idx = x_idx * n_grid + y_idx
# 各グリッドから均等にサンプリング
sample_idx = []
unique_grids = np.unique(grid_idx)
cells_per_grid = max(1, n_sample // len(unique_grids))
for gid in unique_grids:
cells_in_grid = np.where(grid_idx == gid)[0]
n_to_sample = min(cells_per_grid, len(cells_in_grid))
sampled = np.random.choice(cells_in_grid, n_to_sample, replace=False)
sample_idx.extend(sampled)
# 目標数に調整
if len(sample_idx) > n_sample:
sample_idx = np.random.choice(sample_idx, n_sample, replace=False)
return adata[sample_idx].copy()
# 実行
adata_xenium_sampled = spatial_uniform_sampling(
adata_xenium_filtered,
n_sample=10000
)
print(f"サンプリング後: {adata_xenium_sampled.shape}")
ステップ4: 空間近傍グラフの構築
import squidpy as sq
# Xeniumの近傍グラフ
sq.gr.spatial_neighbors(
adata_xenium_sampled,
coord_type='generic',
n_neighs=6 # Visiumの6近傍に相当
)
print("空間近傍グラフを構築しました")
print(f" 平均近傍数: {adata_xenium_sampled.obsp['spatial_connectivities'].sum(axis=1).mean():.1f}")
ステップ5: 正規化
# 最小限の正規化
for adata in [adata_visium_filtered, adata_xenium_sampled]:
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
print("正規化完了")
4.3 モデル適用フェーズ
ステップ6: モデルの実行(例: Tangram)
import tangram as tg
# scRNA-seqデータの準備(別途用意)
# ad_sc = sc.read_h5ad('reference_scrna.h5ad')
# 共通遺伝子に絞る
common_genes_final = ad_sc.var_names.intersection(
adata_xenium_sampled.var_names
)
ad_sc_common = ad_sc[:, common_genes_final].copy()
ad_xenium_common = adata_xenium_sampled[:, common_genes_final].copy()
print(f"最終的な共通遺伝子数: {len(common_genes_final)}")
# Tangram前処理
tg.pp_adatas(ad_sc_common, ad_xenium_common, genes=None)
# マッピング実行
ad_map = tg.map_cells_to_space(
ad_sc_common,
ad_xenium_common,
mode='cells',
density_prior='rna_count_based',
num_epochs=500,
device='cuda:0'
)
print("Tangramマッピング完了")
ステップ7: 結果の可視化と検証
# セルタイプアノテーションの転送
tg.project_cell_annotations(
ad_map,
ad_xenium_common,
annotation='cell_type'
)
# 可視化
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
sc.pl.embedding(
ad_xenium_common,
basis='spatial',
color='cell_type',
ax=ax,
show=False
)
plt.title('Xenium with Transferred Cell Types')
plt.show()
# 統計サマリー
cell_type_counts = ad_xenium_common.obs['cell_type'].value_counts()
print("\nセルタイプ分布:")
print(cell_type_counts)
4.4 データ保存とエクスポート
# 前処理済みデータの保存
output_dir = 'processed_data'
import os
os.makedirs(output_dir, exist_ok=True)
# AnnData形式で保存
adata_xenium_sampled.write_h5ad(
f'{output_dir}/xenium_processed_10k.h5ad'
)
adata_visium_filtered.write_h5ad(
f'{output_dir}/visium_processed.h5ad'
)
# 結果の保存
ad_xenium_common.write_h5ad(
f'{output_dir}/xenium_with_annotations.h5ad'
)
print(f"データを {output_dir} に保存しました")
5. 成功の鍵(まとめ)
5.1 必須事項(これがないと失敗)
1. ✅ ダウンサンプリング
目標: 10,000細胞程度(元の5%)
方法: 空間的に均等なサンプリング
理由: 計算量を現実的な範囲に
2. ✅ 共通遺伝子の選定
目標: 高品質な遺伝子のみ使用
基準: 両プラットフォームで100+細胞で発現
理由: ノイズを減らし、信頼性を向上
3. ✅ 空間近傍グラフの構築
方法: KNN (k=6) または距離ベース (radius=50µm)
理由: Xeniumは不規則な点群のため
ツール: squidpy.gr.spatial_neighbors()
5.2 推奨事項(やった方が良い)
4. 🔸 空間的均等サンプリング
ランダムサンプリングではなく、空間的に均等に
→ 組織の全領域を代表するサンプルを得る
5. 🔸 正規化の最小化
Visium用の複雑な正規化は避ける
→ log1p程度に留める
→ raw countsを使うモデルも多い
6. 🔸 セグメンテーションQC
低品質細胞を事前に除外
→ 総カウント < 50
→ 検出遺伝子数 < 20
→ 異常な細胞サイズ
5.3 注意事項(知っておくべき)
7. ⚠️ 計算リソース
推奨環境:
- GPU: NVIDIA (CUDA対応)
- メモリ: 16GB以上
- ストレージ: 50GB以上
処理時間の目安:
- データ読み込み: 1-5分
- ダウンサンプリング: 1-2分
- モデル実行: 10-60分(GPU使用時)
8. ⚠️ 結果の解釈
技術的な違いを考慮:
- Visium: 複数細胞の混合
- Xenium: 単一細胞(セグメンテーション依存)
→ 完全に一致する結果は期待できない
→ 傾向やパターンの一致を確認
9. ⚠️ バリデーション
複数の手法で結果を確認:
- Tangram + SpaGCN
- 異なるパラメータ設定
- 既知のマーカー遺伝子での検証
5.4 トラブルシューティング
メモリ不足エラー
# 対策1: さらにダウンサンプリング
adata_xenium_sampled = spatial_uniform_sampling(adata_xenium, n_sample=5000)
# 対策2: スパース行列を維持
adata.X = scipy.sparse.csr_matrix(adata.X)
# 対策3: backed modeで読み込み
adata = sc.read_h5ad('data.h5ad', backed='r')
計算時間が長すぎる
# 対策1: GPU使用
device = 'cuda:0' # Tangramなど
# 対策2: タイリング
tiles = create_spatial_tiles(adata, tile_size=1000)
# 各タイルを並列処理
# 対策3: より小さいサンプルでテスト
adata_test = adata[:1000] # 1000細胞でテスト
結果が不自然
# チェック項目:
1. 共通遺伝子数は十分か? (>1000推奨)
2. セグメンテーション品質は良いか?
3. 正規化は適切か?
4. パラメータは適切か? (density_prior等)
# デバッグ:
# - 既知のマーカー遺伝子で可視化
# - QCメトリクスを再確認
# - より単純なモデルで試す
6. 参考リソース
6.1 主要ツール
| ツール | 用途 | URL |
|---|---|---|
| Scanpy | 基本的なデータ処理 | https://scanpy.readthedocs.io/ |
| Squidpy | 空間解析 | https://squidpy.readthedocs.io/ |
| Tangram | セルタイプマッピング | https://github.com/broadinstitute/Tangram |
| SpaGCN | 空間クラスタリング | https://github.com/jianhuupenn/SpaGCN |
6.2 関連論文
- Tangram: "Deep learning and alignment of spatially resolved single-cell transcriptomes with Tangram" (Nature Methods, 2021)
- SpaGCN: "SpaGCN: Integrating gene expression, spatial location and histology to identify spatial domains and spatially variable genes by graph convolutional network" (Nature Methods, 2021)
- Xenium技術: "Xenium In Situ: High-plex multiomic imaging in tissues" (10x Genomics)
- Visium HD技術: "Visium HD Spatial Gene Expression" (10x Genomics)
6.3 10x Genomics ドキュメント
- Visium HD: https://www.10xgenomics.com/products/spatial-gene-expression
- Xenium: https://www.10xgenomics.com/products/xenium-in-situ
- データフォーマット: https://support.10xgenomics.com/
7. まとめ
XeniumとVisiumの本質的な違い
Visium: 「場所」を測定
- 物理的グリッド
- 全トランスクリプトーム
- 低解像度、高カバレッジ
Xenium: 「細胞」を測定
- 生物学的単位
- ターゲットパネル
- 高解像度、限定的カバレッジ
統合解析の鍵
1. ダウンサンプリング(必須)
2. 共通遺伝子の選定(必須)
3. 空間グラフの構築(必須)
4. 適切な正規化(推奨)
5. QCとバリデーション(推奨)
期待できること
✅ セルタイプの空間分布
✅ 組織構造の理解
✅ 細胞間相互作用の推定
✅ マーカー遺伝子の発現パターン
期待できないこと
❌ 完全に一致する定量値
❌ パネル外の遺伝子情報
❌ 全細胞の解析(ダウンサンプリング必須)
❌ リアルタイム処理
このガイドが、XeniumとVisiumデータの統合解析の成功に役立つことを願っています。
質問や追加情報が必要な場合は、関連するNotebook (02_xenium_vs_visium_structure.ipynb) を参照してください。