degenerate-input-filtering について
このガイドは、統計解析の前に情報を持たない入力や不完全な入力を検出して除外する方法を説明します。表、発現行列、配列データを扱う研究者向けで、除外数と残数を記録し、有効なサンプル数を把握できるようにします。
主な機能
- 01全NaNと定数特徴量の検出
- 02有効な観測数の確認
- 03空ファイルや少数配列の処理
- 04解析場面別の閾値例
- 05分類ごとの除外数と残数の記録
活用シーン
- 01統計検定前の発現行列の整理
- 02相関解析前の定数特徴量の除外
- 03無効な単一配列アラインメントの除外
- 04結合や変換後のデータ再チェック
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill degenerate-input-filteringインストール後、Claude Code で/degenerate-input-filteringを入力して読み込まれたか確認します。
詳細
退化入力のフィルタリングガイド
概要
退化入力とは、統計的な情報を含まないデータポイントです。たとえば、値が一定の特徴量、すべてがNaNの列、単一配列アラインメント、空のファイルなどのエッジケースが該当します。これらが統計検定やモデルに渡されると、相関がNaNになる、p値が1.0になる、スコアが0.0になる、あるいはクラッシュするといった、意味のない結果が生じます。このガイドでは、分析を行う前にこうした入力を検出して除去すること、また、下流の利用者が実際のサンプル数を把握できるよう、除去した項目をすべて報告することを必須の手順として定めます。
主な概念
退化データに該当するもの
データポイントが、計算対象の統計量に寄与できない場合、そのデータポイントは退化しています。根本的な原因は常に同じで、入力に手法が必要とする変動または完全性が欠けています。
| 種類 | 例 | 問題が生じる理由 |
|---|---|---|
| 値が一定の特徴量 | すべてのサンプルで発現量が同一の遺伝子 | 分散 = 0。相関、t検定、フォールドチェンジはいずれも定義できない |
| すべてがNaNの特徴量 | 有効な観測値がない列 | どの集約処理でもNaNが返される |
| 単一配列アラインメント | 配列が1つだけのBLAST結果 | スコア = 0.0。ペアワイズ比較ができない |
| 空のファイル | 0バイトのFASTAまたはCSV | パーサーがクラッシュするか、空のフレームが返される |
| グループ化後の値が1つ | 処置群のサンプルが1つ | 群内分散を定義できず、群間比較に意味がない |
| 長さが0の配列 | 空のFASTAエントリー | アラインメントツールやk-merツールが失敗するか、意味のない結果を生成する |
| ほぼ一定の特徴量 | N-1個の値が同じで、1つだけ外れ値がある遺伝子 | 分散は厳密には0ではないが、相関が単一の点に左右される |
サイレントな失敗が危険な理由
多くの数値計算ライブラリは、退化入力に対してエラーを発生させません。代わりに、次のようなセンチネル値を返します。
numpy.corrcoefは、警告なしで、値が一定の列に対してnanを返します。scipy.stats.spearmanrは、配列の一方が一定の場合、(nan, nan)を返します。scipy.stats.ttest_indは、分散が0の群に対して(nan, nan)を返します。
こうしたNaN値はパイプライン内でサイレントに伝播し、集約統計量、ヒートマップ、ボルケーノプロット、順位付けされた遺伝子リストを汚染します。研究者が問題に気づく頃には、どの上流工程でNaNが発生したのか分からなくなっている可能性があります。
報告の義務
報告せずにフィルタリングすることは、フィルタリングしないこととほぼ同じくらい有害です。項目がひそかに除外されると、実際のサンプル数や特徴量数がユーザーの想定と異なり、検出力の計算が誤ったり、要約統計量が誤解を招いたりします。フィルタリングの各工程で、次の項目を必ず出力してください。
- 除去した項目数。
- 除去した理由。
- 残った項目数。
判定フレームワーク
統計分析を実行する前に、データに適用するフィルタリングチェックを判断するには、このツリーを使用してください。
Is the input tabular (DataFrame)?
├── Yes
│ ├── Are there columns with zero unique values (all NaN)? → Remove, report count
│ ├── Are there columns with exactly one unique value (constant)? → Remove, report count
│ ├── Are there columns with fewer than N valid observations? → Remove, report count
│ └── Are there near-constant columns (1 outlier, rest identical)? → Flag or remove
└── No (file list, sequence set, etc.)
├── Are there empty files (0 bytes)? → Skip, report count
├── Are there single-entry collections (e.g., 1-sequence alignment)? → Skip, report count
└── Are there zero-length entries within a file? → Filter entries, report count
| シナリオ | 推奨されるチェック | しきい値 |
|---|---|---|
| DE分析前の遺伝子発現マトリックス | 分散が0の遺伝子を除去し、N個未満のサンプルでしか検出されない遺伝子を除去する | N = max(3, サンプル数の10%) |
| 2つの特徴量セット間の相関分析 | いずれかのセットで一定の特徴量を除去し、NaNでない共通ペアを10個以上必要とする | 両方のベクトルでnunique >= 2、共通の観測値 >= 10 |
| 多重配列アラインメントのスコアリング | 配列が2つ未満のアラインメントをスキップする | 配列数 >= 2 |
| 共変量をグループ化した生存時間分析 | イベント数が2未満のグループを除去する | グループごとのイベント数 >= 2 |
| 特徴量マトリックスに対するPCAまたはクラスタリング | 分散が0の特徴量およびすべてがNaNの特徴量を除去する | 分散 > 0、かつNaNでない値が1つ以上 |
| バッチ補正(例:ComBat) | いずれかのバッチで欠損している特徴量を除去する | すべてのバッチで特徴量が検出される |
ベストプラクティス
-
統計計算を行う前にフィルタリングし、計算後には行わないでください。 退化した入力は、ゼロ除算、NaN の伝播、多重検定補正の過大評価を引き起こす可能性があります。検定後にフィルタリングしても、すでに問題は発生しています。
-
再利用できる単一のフィルタリング関数を使用してください。 ロジックを一元化することで、スクリプト間の不整合を防ぎ、レポート形式を統一できます。下記の「ワークフロー」セクションにあるリファレンス実装を参照してください。
-
各フィルタリング手順で件数を出力してください。 件数がゼロの場合でも「削除した項目数:0」と出力すれば、チェックが実行されたことを確認できます。これにより、ログが自己説明的になり、監査も可能になります。
-
汎用的なデフォルトに頼らず、分野に適したしきい値を設定してください。 遺伝子発現解析ではサンプルの少なくとも 10% での検出が必要な場合がありますが、欠損値の多いプロテオミクスデータセットでは 5% が適切な場合があります。しきい値とその根拠を記録してください。
-
ほぼ一定の特徴量は慎重に扱ってください。 相異なる値が 1 つある特徴量は、技術的には分散がゼロではありませんが、他の値との相関はその 1 点だけによって決まります。無条件に含めるのではなく、別途フラグを付けることを検討してください。
-
決して黙ってフィルタリングしないでください。 報告せずにデータを削除することは、隠れたデータ操作にあたります。削除するたびに、その理由と件数をログに記録し、有効なサンプルサイズを常に明確にしてください。
-
変換後に再確認してください。 発現データを対数変換すると、カウントがゼロの場合に負の無限大が発生することがあります。データセットを結合すると、新たに NaN の列が生じることがあります。変換や結合の後には、退化チェックを再実行してください。
よくある落とし穴
-
定数値の列で相関を計算し、気づかないまま NaN が発生する。 NaN の結果は後続のランキング、ヒートマップ、エンリッチメントの入力へと気づかないうちに伝播し、誤解を招く図が生成されます。
- 回避方法: 相関を計算する前に、すべての列で
nunique() >= 2を必ず確認してください。以下の参照実装を使用してください。
- 回避方法: 相関を計算する前に、すべての列で
-
単一配列のアラインメントをスコアリングし、0.0 というスコアを実際の結果として報告する。 アラインメントスコアの計算には少なくとも 2 つの配列が必要です。単一配列の「アラインメント」はアラインメントに失敗したのではなく、そもそもアラインメントではありません。
- 回避方法: スコアを計算する前に配列数を数えてください。配列が 2 つ未満のアラインメントファイルはスキップし、その旨を報告してください。
-
データをフィルタリングしても件数を報告せず、有効なサンプルサイズが分からなくなる。 15,000 個の遺伝子に基づくボルケーノプロットは、18,500 個の遺伝子に基づくものとは大きく異なります。しかし、ログメッセージがなければ、どちらなのか誰にも分かりません。
- 回避方法: 削除件数、理由、残存件数の 3 つを必ず出力してください。すべてのノートブックとスクリプトに含めてください。
-
最小サンプル数のしきい値を低く設定しすぎて、1~2 サンプルでしか検出されない遺伝子が残る。 こうした遺伝子は分散の推定が不安定になり、p 値の信頼性が低く、偽発見率が高くなります。
- 回避方法: 下限として
max(3, int(0.1 * n_samples))を使用してください。サンプル数が少なく、10%でも 3 未満になる研究では、しきい値を引き上げてください。
- 回避方法: 下限として
-
マージや変換の後に再チェックするのを忘れる。 2 つのデータセットを結合すると、重複しない特徴量からすべて NaN の列が生じることがあります。ゼロを対数変換すると、負の無限大が生じます。どちらも退化した状態です。
- 回避方法: マージ、結合、数学的変換のたびに、退化データのフィルターを再実行してください。
-
NaN を適切に処理せず、
df.var() > 0を使用する。 列がすべて NaN の場合、var()は NaN を返します。これは> 0ではないため列は削除されますが、ログには「分散がゼロ」と記録され、「すべて NaN」とは記録されません。これは誤解を招きます。- 回避方法: 分散がゼロかどうかを確認する前に、すべて NaN の列を個別に確認してください。それぞれのカテゴリを固有のラベルで報告してください。
-
キュレーション済みデータベース由来だから入力はクリーンだと思い込む。 公開データベースには、プレースホルダー値、欠損値、取り下げられたレコードが含まれています。データソースが信頼できる場合でも、必ず検証してください。
- 回避方法: すべての入力を信頼できないものとして扱ってください。データソースにかかわらず、退化入力の完全なチェックを実行してください。
ワークフロー
統計解析の前に、以下の逐次プロセスを適用してください。
-
ステップ 1: 読み込みと確認
- データを DataFrame または同等の構造に読み込みます。
- 行数、列数、データ型の概要を出力します。
-
ステップ 2: すべて NaN の特徴量を削除
- すべての値が NaN の列(または、データの向きによっては行)を特定します。
- 削除して、件数を出力します。
-
ステップ 3: 定数値の特徴量を削除
- NaN を除外したうえで、
nunique() <= 1の列を特定します。 - 削除して、件数を出力します。
- NaN を除外したうえで、
-
ステップ 4: 有効な観測値が少なすぎる特徴量を削除
- しきい値を設定します(例:サンプルの少なくとも 10%、または少なくとも 3)。
- しきい値を下回る特徴量を削除して、件数を出力します。
-
ステップ 5: ドメイン固有のチェックを適用
- 配列データの場合:単一配列のアラインメント、空ファイル、長さ 0 のエントリをスキップします。
- 発現データの場合:検出頻度の低い遺伝子をフィルタリングします。
- 相関の入力の場合:両方のベクトルが定数ではなく、十分な数の観測値を共有していることを確認します。
-
ステップ 6: 概要を報告
- 最終的な概要として、入力総数、カテゴリ別の削除総数、残存総数を出力します。
参照実装
import pandas as pd
import numpy as np
def filter_degenerate(data, context="features"):
"""Filter degenerate data points and report what was removed.
Always call this BEFORE statistical analysis.
"""
n_before = len(data)
# Filter based on data type
if isinstance(data, pd.DataFrame):
# Remove constant columns (zero variance)
non_constant = data.loc[:, data.nunique() > 1]
# Remove all-NaN columns
non_nan = non_constant.dropna(axis=1, how='all')
filtered = non_nan
elif isinstance(data, list):
# Remove None, empty, and zero-length items
filtered = [x for x in data if x is not None and len(x) > 0]
else:
filtered = data
n_after = len(filtered) if not isinstance(filtered, pd.DataFrame) else filtered.shape[1]
n_removed = n_before if not isinstance(data, pd.DataFrame) else data.shape[1]
n_removed = n_removed - n_after
# MANDATORY: Print the count
print(f"Degenerate {context} filtered: {n_removed} / {n_before} removed")
print(f"Remaining {context}: {n_after}")
return filtered
配列アラインメントのフィルタリング
# Filter single-sequence alignments (score=0.0 is meaningless)
alignment_scores = {}
for aln_file in alignment_files:
n_seqs = count_sequences(aln_file)
if n_seqs < 2:
print(f"Skipping {aln_file}: only {n_seqs} sequence(s)")
continue
score = compute_alignment_score(aln_file)
alignment_scores[aln_file] = score
print(f"Filtered {len(alignment_files) - len(alignment_scores)} single-sequence alignments")
遺伝子発現のフィルタリング
# Filter genes with zero variance (constant expression)
gene_vars = expression_df.var(axis=1)
zero_var = (gene_vars == 0).sum()
print(f"Genes with zero variance: {zero_var}")
expression_filtered = expression_df.loc[gene_vars > 0]
# Filter genes detected in too few samples
min_samples = max(3, int(0.1 * expression_df.shape[1])) # At least 10% of samples
detected = (expression_df > 0).sum(axis=1)
low_detection = (detected < min_samples).sum()
print(f"Genes detected in < {min_samples} samples: {low_detection}")
expression_filtered = expression_filtered.loc[detected >= min_samples]
相関計算前のフィルタリング
from scipy.stats import spearmanr
# Filter features before computing correlations
valid_correlations = {}
skipped_constant = 0
skipped_few_values = 0
for feature in features:
x = data_x[feature].dropna()
y = data_y[feature].dropna()
# Check for constant values
if x.nunique() < 2 or y.nunique() < 2:
skipped_constant += 1
continue
# Check for sufficient data points
common = x.index.intersection(y.index)
if len(common) < 10:
skipped_few_values += 1
continue
rho, pval = spearmanr(x[common], y[common])
valid_correlations[feature] = rho
print(f"Skipped (constant values): {skipped_constant}")
print(f"Skipped (< 10 valid pairs): {skipped_few_values}")
print(f"Valid correlations computed: {len(valid_correlations)}")
想定される出力形式
適切にフォーマットされたフィルタリングレポートは、次のようになります。
Input: 18500 genes
Filtered: 230 genes with zero variance
Filtered: 45 genes with < 3 valid samples
Remaining: 18225 genes for analysis
参考資料
- scipy.stats のドキュメント -- 定数配列や NaN 値など、エッジケースの入力に対する統計関数の動作を含むリファレンスです。
- pandas DataFrame.dropna -- 軸としきい値を指定して NaN を削除する方法についての公式ドキュメントです。
- numpy の NaN 処理 -- 算術関数や集約関数における NaN の伝播セマンティクスを説明する NumPy のドキュメントです。
- Lun, A. et al. (2016) "A step-by-step workflow for low-level analysis of single-cell RNA-seq data with Bioconductor" -- 正規化と差次的発現解析の前に、低発現遺伝子や分散がゼロの遺伝子を除去するなど、遺伝子レベルの品質管理フィルタリングを実演しています。
- Law, C.W. et al. (2018) "RNA-seq analysis is easy as 1-2-3 with limma, Glimma and edgeR" -- 差次的発現解析の前に、発現量が少ない遺伝子や有用でない遺伝子を除去する filterByExpr のような遺伝子フィルタリングを扱っています。
関連スキル
nan-safe-correlation-- 欠損値がある場合に相関を計算する手法です。このガイドにおける、相関計算の入力を事前にフィルタリングする手順を補完します。statistical-analysis-- 統計検定の選択と適用に関する、より広範なガイダンスです。このガイドに従って、退化した入力がすでに除去されていることを前提としています。