scRNA-seq解析:サンプル統合とバッチ補正 ― Harmony(GPU不要の古典的手法)

scRNA-seq

📚 この記事について:「scRNA-seq解析 実践シリーズ(前処理編)」の記事です。全体像と手法比較は 前処理の全体像とツール選択 を参照。本記事は統合(バッチ補正)の古典的手法 Harmony を扱います。コードは初めてでも追えるよう、各ステップで「何のために」を先に説明します。

🖥 動作環境CPU で完結(GPU 不要)

🔀 対になる手法:AI 版(GPU 必須)の サンプル統合:scVI。統合はどちらか一方を選びます。

🔙 前の記事データの読み込みとQC / 🔜 次の記事ダブレットの検出:scrublet

🧩 前提:QC で得たクリーンな AnnData(annotated data、scanpy のデータ形式)/Python の基本。


🎯 この記事の結論

  • やること:複数サンプルの「測定のクセ(バッチ効果)」だけを消し、本物の生物差は残す
  • 必ず守る1つ:近傍グラフで use_rep="X_pca_harmony" を指定(=補正結果を“使う”)
  • 注意:消しすぎ(過補正)に注意。Harmony が直すのは埋め込みで、発現量そのものではない

この記事のゴール

複数サンプルをまたいで、「測定のクセ(バッチ効果)」だけを消し、本物の生物学的な違いは残したデータを、軽量・普通のPCで動く Harmony で作ること。

💡 ポイント:同じ細胞型でも、サンプル(測定)ごとに発現パターンが系統的にずれます。このズレ(バッチ効果)だけを打ち消し、本物の細胞型の違いはそのまま残す——これが統合です。


0. バッチ効果ってなに? なぜ補正するの?

同じ細胞型でも、測定した日・試薬のロット・10x のチップなどが違うと、発現のパターンが系統的にずれます。これがバッチ効果です。

補正しないとどうなるか? 次元削減やクラスタリングで、「細胞型」ではなく「サンプル」でグループが割れてしまい、解釈が壊れます。

💡 統合のゴールは「混ぜる」と「残す」の両立
良い統合とは、次の2つを同時に満たすことです。
– 🔵 同じ細胞型は、サンプルをまたいで混ざる(バッチ補正)
– 🔴 違う細胞型・違う状態は、分かれたまま(生物差の保存)

この2つはトレードオフの関係です。手法ごとに「どちらをどれだけ重視するか」で性格が分かれます(Luecken et al., 2022)。

⚠️ 最大の落とし穴:消しすぎ(過補正)
バッチ補正を強くかけすぎると、本物の細胞型の違いや処理の効果まで消えます。確かに「そろった」けれど、サンプル間の違い(生物学的に意味のある差)も消えています。「WT と KO がきれいに重なった!」は統合の成功ではなく、差を消してしまった失敗かもしれません。だから統合後は必ず評価します(最終節)。

統合で起きること:補正前 → 良い統合 → 過補正(消しすぎ)。色=サンプル、位置=細胞型
統合で起きること:補正前 → 良い統合 → 過補正(消しすぎ)。色=サンプル、位置=細胞型

1. 準備:正規化・特徴選択(HVG)

統合の前に、共通の前処理をします。ここでは、細胞ごとに発現が大きく変動する遺伝子だけに絞る高変動遺伝子(HVG、highly variable genes=高変動遺伝子)選択を行います。seurat_v3 という方法は生(なま)のカウントを必要とするので、先に生カウントを別の場所へ退避してから選びます。

pythonimport numpy as np
import scanpy as sc
import anndata as ad

# combined: 前の記事(QC)で得たクリーンな AnnData(obs["batch"] にサンプル/群ラベル)
adata = combined.copy()

# 生カウントを保存(seurat_v3 は生カウントを要求するため)
adata.layers["counts"] = adata.X.copy()

# HVG を「生カウント」から seurat_v3 で選択(バッチ考慮)
sc.pp.highly_variable_genes(
    adata,
    n_top_genes=2000,
    flavor="seurat_v3",
    layer="counts",
    batch_key="batch",
    subset=False,
)

🔧 環境メモflavor="seurat_v3" は scanpy の HVG 選択アルゴリズムの名前で、内部で scikit-misc を使います(R や Seurat 本体は不要)。未導入なら pip install scikit-misc

💡 なぜ遺伝子を絞るの?
遺伝子の大半は、どの細胞でもほぼ一定の発現で、細胞集団を見分ける手がかりになりません(シグナルよりノイズを足すだけ)。発現の変動が大きい遺伝子だけに絞ると、細胞集団の構造が見えやすくなり、計算も軽くなります。


2. Harmony で統合する

仕組み(やさしく)

Harmony は 主成分分析(PCA、principal component analysis:情報を圧縮して扱いやすくする手法)の空間で動きます。やっていることはシンプルで、

  1. 似た細胞をゆるくグループ分けし、
  2. 各グループの中で「サンプル(バッチ)ごとの偏り」を打ち消し、
  3. これを何度か繰り返してバッチ差をそろえる、

というものです(Korsunsky et al., 2019)。深層学習を使わない軽量・高速で、普通のPCで十分な方法です。

コード(正規化 → PCA → Harmony → 近傍 → クラスタリング)

まず正規化(公平に比較できるよう数値をそろえる)と対数変換をして、HVG に絞って PCA をかけます。

pythonimport scanpy as sc

# 正規化と対数変換
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
adata.raw = adata                       # 全遺伝子の正規化済みデータを退避

# HVG に絞ってスケーリング → PCA
adata_h = adata[:, adata.var.highly_variable].copy()
sc.pp.scale(adata_h, max_value=10)
sc.tl.pca(adata_h, n_comps=30)

次に Harmony を適用します。

pythonimport harmonypy

def harmony_integrate_robust(adata, batch_key, basis="X_pca", adjusted="X_pca_harmony"):
    ho = harmonypy.run_harmony(adata.obsm[basis], adata.obs, [batch_key])
    Z = ho.Z_corr
    if Z.shape[0] != adata.n_obs:        # 古い向き (PCs, cells) の場合は転置
        Z = Z.T
    adata.obsm[adjusted] = np.asarray(Z) # → (細胞数, PC数) で格納
    return adata

harmony_integrate_robust(adata_h, "batch")   # obsm["X_pca_harmony"] が作られる

# ★ 補正後の埋め込みで近傍グラフを作る(ここが最重要)
sc.pp.neighbors(adata_h, use_rep="X_pca_harmony", n_neighbors=15)
sc.tl.umap(adata_h)
sc.tl.leiden(adata_h, key_added="leiden_harmony",
             flavor="igraph", n_iterations=2, directed=False, resolution=1.0)

sc.pl.umap(adata_h, color=["batch", "leiden_harmony"], ncols=2, frameon=False)

🔧 補足:なぜ自前のヘルパー関数?
harmonypy 2.0 では出力の向きが変わり、よく使われる sc.external.pp.harmony_integrate() が形状エラーで落ちることがあります。上の harmony_integrate_robustrun_harmony を直接呼ぶ版)なら、新旧どちらのバージョンでも動きます

⚠️ 落とし穴①:Harmony の結果を「使い忘れる」(最重要)
sc.pp.neighbors() は、何も指定しないと X_pca補正していないPCA)を使います。Harmony を回しても、use_rep="X_pca_harmony"指定しなければ補正は一切反映されません。「Harmony したのにサンプルで割れる」原因のNo.1がこれです。補正した結果を指定して使う——ここだけは必ず守ってください。

⚠️ 落とし穴②:Harmony が直すのは「埋め込み」であって「発現量」ではない
Harmony が補正するのは PCA の低次元表現(埋め込み X_pca_harmonyです。カウントデータ(遺伝子発現量)そのものは変えていません。UMAP やクラスタリングは、この補正後の埋め込みから作られます。
👉 つまり「バッチ補正後の発現量」というものは存在しません。遺伝子発現量を使う解析(発現変動解析〔DEG〕など)は、補正後の埋め込みではなく、元の正規化済み発現(adata.raw など)を使う点に注意してください。

💡 なぜ regress_out を入れないの?
古い手順では、PCA の前に sc.pp.regress_out(["total_counts","pct_counts_mt"]) を挟むことがよくありました。しかし今のベストプラクティスでは、既定では行いません(Heumos et al., 2023)。技術指標の回帰除去は、本物の生物シグナルまで削るリスクがあり、効果も限定的だからです。


3. 統合できたかを評価する(消しすぎていないか)

「UMAP(uniform manifold approximation and projection:高次元データを2次元に配置する手法)できれいに重なった」だけでは不十分です。確認するのは2点:「同じ細胞型はサンプルをまたいで重なったか(バッチ差は消えたか)」と「違う細胞型の違いは残ったか(消しすぎていないか)」です。

python# ① バッチが混ざっているか(混ざっていれば技術差は除けている)
sc.pl.umap(adata_h, color="batch", frameon=False)

# ② 既知マーカーで細胞型が保たれているか(消えていないか)
known_markers = ["GeneA", "GeneB", "GeneC"]   # 自分の系のマーカーに置換
sc.pl.umap(adata_h, color=known_markers, color_map="RdBu_r", frameon=False)

💡 数値で評価したいなら scib パッケージ(Python 完結なら scib-metrics
目で見るのに加え、scib で数値化できます。代表的な指標は、
バッチ混合(よく混ざったか):iLISI、kBET(高いほど良い)
生物保存(細胞型が残ったか):cLISI、ARI、NMI(既知ラベルとの一致度)


4. もう一方の選択肢:AI 手法の scVI(GPU 必須)

統合にはもう一つ、深層学習ベースの scVI という選択肢があります(詳細は別記事 サンプル統合:scVI(GPU必須のAI手法) で解説)。強いバッチ効果や大規模データに強く、この後のダブレット検出(SOLO)・アノテーション・マルチモーダル解析へ発展させやすい一方、実用上 GPU が必須です。

📌 どちらを選ぶ?(詳しくは 全体像の記事
小〜中規模/生物シグナルが明瞭/GPU が無い・素早く回したい本記事の Harmony
大規模/強いバッチ効果/GPU が使え、発展解析に繋げたいscVI(別記事)


⚠️ つまずきポイントまとめ

  • use_rep="X_pca_harmony" の指定忘れ(最重要)→ 補正が反映されず、サンプルで割れる。
  • Harmony は埋め込みを補正するだけ → 発現量を使う解析(DEG 等)は元の正規化発現を使う。
  • harmonypy 2.0 の向き問題run_harmony を直接呼ぶ堅牢版で回避。
  • regress_out を入れない(今は既定で非推奨)。
  • 過補正(消しすぎ) → 「重なった=成功」ではない。必ず評価する。

まとめ

  • 統合のゴールは「バッチ差を消し、生物学的な違いは残す」の両立。消しすぎ(過補正)に注意し、必ず評価する。
  • Harmony は軽量・普通のPCで完結。ポイントは3つ:neighborsuse_rep="X_pca_harmony" を必ず指定/補正されるのは埋め込み(発現量解析は元の発現を使う)/regress_out は使わない。
  • GPU が使え、強いバッチ・大規模・発展性を求めるなら scVI(別記事) を検討。

次の記事:ダブレットの検出 ― scrublet(GPU不要) — 2細胞が1つに化けた人工物を、普通のPCで完結する古典的手法で検出・除去します。


関連記事


参考文献

  • Korsunsky, I., Millard, N., Fan, J., et al. (2019). Fast, sensitive and accurate integration of single-cell data with Harmony. Nature Methods, 16, 1289–1296. doi:10.1038/s41592-019-0619-0
  • Luecken, M. D., Büttner, M., Chaichoompu, K., et al. (2022). Benchmarking atlas-level data integration in single-cell genomics. Nature Methods, 19, 41–50. doi:10.1038/s41592-021-01336-8
  • Heumos, L., Schaar, A. C., Lance, C., et al. (2023). Best practices for single-cell analysis across modalities. Nature Reviews Genetics, 24, 550–572. doi:10.1038/s41576-023-00586-w
  • Single-cell best practices(統合の章): https://www.sc-best-practices.org/cellular_structure/integration.html

コメント

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