정규표현식으로 서열 데이터 파싱하기
이 토픽을 마치면
Python의 re 모듈로 텍스트에서 패턴을 검색하고, FASTA 헤더에서 정보를 추출하고, 서열 데이터를 검증할 수 있습니다.
정규표현식이 필요한 순간
FASTA 파일의 헤더를 봅시다:
>sp|P04637|P53_HUMAN Cellular tumor antigen p53 OS=Homo sapiens OX=9606이 한 줄에서 "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 | 숫자 한 개 | \d\d | "42" |
\d+ | 숫자 하나 이상 | \d+ | "123", "4" |
\w | 영숫자 + 밑줄 | \w+ | "gene_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()는 첫 번째만 찾지만, 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로 안 되는 검색"이 필요하면 정규표현식을 쓰세요.