正規表現で配列データをパースする
このトピックを終えたら
Pythonのreモジュールでテキストからパターンを検索し、FASTAヘッダーから情報を抽出し、配列データを検証できるようになります。
正規表現が必要な場面
FASTAファイルのヘッダーを見てみましょう:
>sp|P04637|P53_HUMAN Cellular tumor antigen p53 OS=Homo sapiens OX=9606この1行から「P04637」(UniProt ID)だけを抽出したいとします。split("|")で文字列を分割することもできますが、ヘッダー形式がファイルごとに異なると、毎回違うコードを書く必要があります。
**正規表現(Regular Expression、regex)**は「パターン」でテキストを検索します。「パイプ記号の間にある英数字」のようなパターンを定義すれば、どんな形式のヘッダーでも同じコードで抽出できます。
実験での例え — 特定の抗体が特定のエピトープ(パターン)に結合するように、正規表現はテキストの特定パターンに「結合」します。
Python reモジュールの基礎
import re
text = "EGFR expression: 12.5 ng/mL"
match = re.search(r"\d+\.\d+", text)if match: value = float(match.group()) print(f"数値: {value}")
assert value == 12.5re.search(パターン, テキスト) — テキストからパターンに一致する最初の部分を見つけます。
主要パターン文字
| パターン | 意味 | 例 | マッチ結果 |
|---|---|---|---|
\d | 数字1つ | \d\d | "42" |
\d+ | 数字1つ以上 | \d+ | "123", "4" |
\w | 英数字+アンダースコア | \w+ | "gene_1" |
. | 任意の1文字 | a.b | "a1b", "a-b" |
* | 前のパターン0回以上 | ab*c | "ac", "abc", "abbc" |
+ | 前のパターン1回以上 | ab+c | "abc", "abbc"(acはX) |
? | 前のパターン0または1回 | colou?r | "color", "colour" |
[ABC] | A、B、Cのいずれか | [ATGC]+ | "ATGCGTA" |
^ | 行の先頭 | ^> | FASTAヘッダー行 |
| | または(OR) | cat|dog | "cat"または"dog" |
パターンの前にrを付けるとraw stringになります(r"\d+")。バックスラッシュ(\)がPython文字列のエスケープと衝突するのを防ぎます。正規表現では常にr"..."形式を使ってください。
実践:FASTAヘッダーのパース
import re
header = ">sp|P04637|P53_HUMAN Cellular tumor antigen p53 OS=Homo sapiens OX=9606"
uniprot_id = re.search(r"\|(\w+)\|", header)if uniprot_id: print(f"UniProt ID: {uniprot_id.group(1)}")
organism = re.search(r"OS=(.+?) OX=", header)if organism: print(f"Organism: {organism.group(1)}")出力:
UniProt ID: P04637
Organism: Homo sapiens() — 括弧で囲んだ部分がキャプチャグループです。group(1)で括弧内の内容だけを抽出します。
.+? — ?を付けると最小マッチ(lazy match)になります。できるだけ少なくマッチします。?なしの.+はできるだけ多くマッチする(greedy)ため、意図と異なる結果になることがあります。
実践:DNA配列からモチーフを探す
特定の制限酵素認識部位を配列からすべて見つけたい時:
import re
sequence = "ATCGAATTCGCGAATTCTTGAATTCAA"
# EcoRI認識部位: GAATTCsites = [m.start() for m in re.finditer(r"GAATTC", sequence)]print(f"EcoRI切断位置: {sites}")print(f"切断部位数: {len(sites)}")
assert len(sites) == 3assert sites == [4, 13, 20]re.finditer()はすべてのマッチを繰り返し見つけます。re.search()は最初の1つだけですが、finditer()は全部見つけます。
実践:配列の検証
入力された配列が有効なDNA配列かどうかを検証します:
import re
def is_valid_dna(seq: str) -> bool: return bool(re.fullmatch(r"[ATGCatgc]+", seq))
print(is_valid_dna("ATGCGATCGA"))print(is_valid_dna("ATGXYZ"))print(is_valid_dna(""))
assert is_valid_dna("ATGCGATCGA") == Trueassert is_valid_dna("ATGXYZ") == Falseassert is_valid_dna("") == Falsere.fullmatch() — 文字列全体がパターンと一致する必要があります。re.search()と違い、部分マッチは許可されません。
reモジュール主要関数一覧
| 関数 | 用途 | 戻り値 |
|---|---|---|
re.search(パターン, テキスト) | 最初のマッチを探す | Matchオブジェクトまたは None |
re.findall(パターン, テキスト) | すべてのマッチをリストで | 文字列リスト |
re.finditer(パターン, テキスト) | すべてのマッチを巡回 | Matchオブジェクトiterator |
re.sub(パターン, 置換, テキスト) | パターンを別の文字列に置換 | 新しい文字列 |
re.fullmatch(パターン, テキスト) | 全体がパターンに一致するか | Matchオブジェクトまたは None |
やってみよう(Faded Example)
空欄を埋めて、FASTAヘッダーから遺伝子名を抽出するコードを完成させてください。
importheader = ">gene_BRCA1 | Homo sapiens | chromosome 17"match = re.(r"gene_(\w+)", header)if match:gene_name = match.group()print(f"遺伝子: {gene_name}")
よくあるエラーと解決法
Q: パターンは合っているのにNoneが返されます
大文字・小文字を確認してください。re.search(r"gaattc", "GAATTC")はマッチしません。大文字・小文字を無視するにはre.IGNORECASEフラグを追加してください:re.search(r"gaattc", "GAATTC", re.IGNORECASE)
Q: \dが動作しません
文字列の前にrを付けたか確認してください。"\d"はPythonが\dをエスケープシーケンスとして解釈しようとします。r"\d"にすればraw stringとして処理され、正規表現エンジンにそのまま渡されます。
Q: 正規表現が複雑すぎます。必ず使わなければなりませんか?
簡単な場合はin、startswith()、split()などの文字列メソッドの方が読みやすいです。正規表現はパターンが複雑な場合や、複数の形式を一度に処理する必要がある時に力を発揮します。「Ctrl+Fではできない検索」が必要なら、正規表現を使いましょう。