CRISPR guide RNA 온·오프타겟 스코어링 — BRCA1 KO 실험 전 100개 후보를 30초에 랭킹
CRISPR-Cas9 실험의 성공률은 gRNA(guide RNA) 선택에서 이미 절반 결정됩니다. 잘 골라야 표적 유전자를 효율적으로 자르고(온타겟 효율), 다른 유전자를 잘못 자르지 않습니다(오프타겟 회피). 그런데 후보 gRNA를 실험실에서 하나씩 합성·검증하려면 개당 며칠·수만 원이 듭니다. 이 편은 BRCA1 유전자 KO를 K562 세포주에서 진행하는 실전 시나리오를 예시로, 후보 gRNA 100~500개를 딥러닝 스코어러 앙상블로 30초에 랭킹해 상위 5개만 실제 합성으로 넘기는 완결 파이프라인을 하드코어로 구축합니다.
📚 선수 편 권고 (강력 권고)
이 편은 AI×바이오 하드코어 심화 편입니다. 진입 전에 DryBench의 다음 편들을 먼저 듣고·보시길 강력히 권고드립니다.
선수 편 없이 진입할 경우, 이 편에서 다루는 1D CNN·RNN 기반 서열 스코어러의 학습 원리, one-hot 인코딩, pretrained 모델 로딩·추론 실전 패턴에 대한 재설명 없이 실전 코드부터 진행하므로 따라가기 어렵습니다.
우리 DryBench에서 이거 배웠잖아
DryBench ai-native #2에서 신경망이 입력 시퀀스 위 로컬 패턴을 CNN 필터로 잡고, RNN이 순서 의존성을 학습한다는 것을 배웠습니다. #13에서 HuggingFace가 pretrained 모델의 가중치·토크나이저·추론 API를 표준화해 몇 줄로 로딩 가능하게 만든 생태계라는 것을 봤습니다.
CRISPR gRNA 스코어링은 이 두 원리가 놀랍게 가까운 곳에서 실전으로 만나는 지점입니다. 20bp protospacer + 3bp PAM이라는 매우 짧은 입력(총 23bp = 92 dimensional one-hot)에 대해 상대적으로 작은 1D CNN이 학습한 컨텍스트 특성이 실제 실험 효율과 Spearman ρ 0.7 이상 상관됩니다. 이 편은 그 예측력을 여러 스코어러 앙상블로 강화하고, 오프타겟 위험까지 정량화해서 실제 실험 설계 최적화 도구로 만드는 파이프라인입니다.
하드코어 문제 정의
실전 시나리오: BRCA1 KO in K562
DNA damage repair 연구를 위해 K562 (인간 만성 골수성 백혈병 세포주)에서 BRCA1 (Breast Cancer 1) 유전자를 SpCas9-HF1 시스템으로 knockout합니다. BRCA1은 exon 24개, 총 mRNA 약 7.2kb, 코딩 영역 약 5.6kb. KO 실험 성공률·재현성을 위해 다음이 필요합니다.
- 여러 exon에서 gRNA 후보 탐색 (nonsense-mediated decay 회피 위해 5'-side exon 우선).
- 후보 gRNA 각각에 대한 온타겟 효율·오프타겟 위험 정량.
- 상위 5개만 합성 발주 (Twist Bioscience · IDT 등, 하나 당 약 30~100 USD).
- 실험 검증 (T7E1 assay or amplicon deep sequencing)로 실측 효율 후속 검증.
CRISPR gRNA 설계의 근본 트레이드오프
- 온타겟 효율(on-target efficiency): gRNA가 표적 위치를 얼마나 효율적으로 자르는가. 스코어 0~1, 높을수록 좋음. 학습 데이터는 대개 K562 · HEK293T · Jurkat 등 세포주에서 수천 gRNA를 assay한 결과.
- 오프타겟 안전성(off-target safety): 유사한 서열이 게놈 어디에 있는지 + 그 위치가 얼마나 심각한 부작용을 유발하는지. 스코어 0~1, 높을수록 안전.
- PAM 인접 제약: SpCas9 표준 PAM은
NGG. 특수 variant는NG(SpCas9-NG),NGN(SpG),NRN(SpRY) 등. - BE (base editor) 편집 창: base editing은 protospacer 안 특정 창(예: 4-8 nt) 편집. 창 위치와 넉아웃 목표 codon 정합이 중요.
기존 접근 스펙트럼과 이 편의 위치
- 경험 규칙 (Doench 2014, Xu 2015): PAM 앞뒤 nucleotide composition · GC content · secondary structure 등 손공학 feature 기반 로지스틱 회귀. Spearman ρ 0.4~0.5.
- 1D CNN 초기 (DeepCRISPR 2018): 21bp 컨텍스트 학습. ρ 0.6~0.65 [1].
- CRISPRon (Xiang 2021): context-aware CNN + regression. ρ 0.72 [2].
- DeepHF (Wang 2019): SpCas9-HF1·xCas9 variant 특화. ρ 0.75 [3].
- CRISPRon-BE (Kim 2022): Base editing (ABE · CBE) 특화. ρ 0.68~0.75 [4].
- BE-Hive (2020): BE 편집 창 예측 강력.
- PRIDICT (2023): Prime editing pegRNA 설계.
- Foundation model 접근 (실험적): DNABERT · Nucleotide Transformer로 gRNA 임베딩 후 downstream head.
이 편은 CRISPRon (온타겟) + DeepHF (variant Cas9) + CRISPRon-BE (base editing) + CFD/MIT (오프타겟) 4개 스코어러를 앙상블해 단일 스코어러의 편향을 상쇄하고, CRISPOR 원격 API로 게놈 전체 오프타겟 스캔을 추가합니다.
이 편의 목표 지표
- BRCA1 exon 24개 전체에서 gRNA 후보 200~500개 자동 추출 (양쪽 가닥, NGG PAM).
- Lipinski-style filter (GC 30~70%, TTTT 회피, self-complementarity 회피).
- 앙상블 스코어러로 각 후보의 온타겟 · 오프타겟 스코어 산출 (총 wall-clock 30초~2분).
- CRISPOR로 게놈 전체 오프타겟 스캔 (상위 20개만).
- 최종 랭킹 상위 5개 자동 리포트 (실험실 발주 가능한 Markdown + CSV 포맷, spacer + PAM + score matrix + off-target hit summary 포함).
도구 스택과 인프라 요구
| 도구 | 역할 | 라이선스 |
|---|---|---|
Bioconductor crisprScore (R) | 통합 스코어러 프레임워크 (CRISPRon·DeepHF·CRISPRon-BE·CFD 등) | Artistic-2.0 |
| CRISPRon-BE (RTH-tools GitHub) | Base editing 특화 스코어러 (ABE · CBE) | GPL v3 |
| rpy2 (Python↔R 브릿지) | Python에서 R crisprScore 호출 | GPL v2+ |
| Biopython | FASTA · GenBank 파싱, PAM 탐색, reverse complement | Biopython License |
| CRISPOR 원격 API or 로컬 crispritz | 게놈 전체 오프타겟 스캔 | 학술 무료 · GPL |
Bioconductor BSgenome.Hsapiens.UCSC.hg38 | 인간 게놈 참조 | Artistic-2.0 |
| pandas · matplotlib | 결과 표 · 시각화 | BSD |
인프라 요구:
- 소형 소비자 GPU (RTX 4060 이상 권장, CPU 폴백 가능하지만 5~10배 느림).
- R 4.3+ + Bioconductor 3.18+ (
crisprScore·BSgenome.Hsapiens.UCSC.hg38·crisprBase·crisprDesign). - Python 3.10+ + PyTorch 2.0+ + rpy2 3.5+.
- RAM 8GB 이상. 디스크: 인간 게놈 참조 약 3GB, CRISPRon 가중치 약 100MB.
학습자 재현 예상 비용: API 비용 0(완전 로컬 또는 CRISPOR 무료 원격). GPU 시간 gRNA 500개 스코어링 약 1~3분 (모델 로딩 포함 첫 회 5분).
파이프라인 실전 구현
전체 흐름:
Step 1. BRCA1 exon 서열 추출
NCBI RefSeq에서 BRCA1 mRNA (NM_007294) 또는 UCSC Genome Browser에서 hg38 exon 좌표 취득 후 exon 서열 추출.
from dataclasses import dataclassfrom pathlib import Pathfrom typing import Iterator
import requestsfrom Bio import SeqIO, Entrezfrom Bio.Seq import Seq
Entrez.email = "your@email.example" # NCBI 필수
@dataclassclass ExonRegion: """유전자 exon 하나의 정보.""" gene_name: str exon_number: int chromosome: str strand: str # "+" or "-" start_hg38: int # 0-based end_hg38: int length: int sequence: str coding_frame: int | None # None if UTR, 0/1/2 otherwise
def fetch_refseq_mrna(refseq_id: str = "NM_007294") -> str: """NCBI에서 RefSeq mRNA 서열 fetch (예: BRCA1 = NM_007294).""" with Entrez.efetch(db="nucleotide", id=refseq_id, rettype="fasta", retmode="text") as h: record = SeqIO.read(h, "fasta") return str(record.seq).upper()
def fetch_gene_exons_ucsc(gene_symbol: str, assembly: str = "hg38") -> list[ExonRegion]: """UCSC Genome Browser REST API로 유전자 exon 좌표 fetch. 실전에서는 GENCODE annotation GTF 로컬 파싱이 훨씬 안정적. """ # UCSC Table Browser 대안: MyGene.info API resp = requests.get( f"https://mygene.info/v3/query?q=symbol:{gene_symbol}&species=human&fields=exons", timeout=30, ) resp.raise_for_status() hits = resp.json().get("hits", []) if not hits: return [] exons_info = hits[0].get("exons", []) if not exons_info: return [] canonical = exons_info[0] # canonical transcript exon_regions = [] for i, (start, end) in enumerate(canonical.get("position", [])): # UCSC에서 실제 서열 fetch (DAS API 또는 로컬 BSgenome) seq = _fetch_ucsc_dna(canonical["chr"], start, end, assembly) exon_regions.append(ExonRegion( gene_name=gene_symbol, exon_number=i + 1, chromosome=canonical["chr"], strand=canonical.get("strand", "+"), start_hg38=start, end_hg38=end, length=end - start, sequence=seq, coding_frame=None, # 상세 판정은 CDS 좌표 필요 )) return exon_regions
def _fetch_ucsc_dna(chrom: str, start: int, end: int, assembly: str = "hg38") -> str: """UCSC DAS API로 특정 영역 서열 반환.""" url = f"https://api.genome.ucsc.edu/getData/sequence?genome={assembly};chrom={chrom};start={start};end={end}" resp = requests.get(url, timeout=30) resp.raise_for_status() return resp.json().get("dna", "").upper()Step 2. PAM 스캔 · 후보 gRNA 추출
SpCas9 표준 PAM NGG. 반대 가닥도 스캔 (protospacer의 reverse complement).
@dataclassclass GRNACandidate: gene_name: str exon_number: int protospacer: str # 20bp 표적 서열 pam: str # 3bp PAM strand: str # "+" or "-" exon_position: int # exon 서열 상 시작 위치 (0-based) genome_position: int # hg38 게놈 좌표 (0-based) context_50bp: str # 앞 15bp + gRNA + PAM + 뒤 15bp = 53bp (CRISPRon 등 학습 입력)
def scan_pam_ngg(sequence: str, gene_name: str, exon: ExonRegion) -> list[GRNACandidate]: """양쪽 가닥에서 NGG PAM + 20bp protospacer 후보 추출.""" candidates = [] seq_plus = sequence.upper() seq_minus = str(Seq(seq_plus).reverse_complement()) def _scan(strand_seq: str, strand_label: str, seq_len: int) -> None: for i in range(20, seq_len - 3): pam = strand_seq[i:i+3] if pam[1:3] != "GG": continue protospacer = strand_seq[i-20:i] if "N" in protospacer: continue context_start = max(0, i - 35) context_end = min(seq_len, i + 18) context = strand_seq[context_start:context_end] # 게놈 좌표 계산 (strand별 방향 반전) if strand_label == "+": genome_pos = exon.start_hg38 + (i - 20) else: genome_pos = exon.end_hg38 - i candidates.append(GRNACandidate( gene_name=gene_name, exon_number=exon.exon_number, protospacer=protospacer, pam=pam, strand=strand_label, exon_position=i - 20, genome_position=genome_pos, context_50bp=context, )) _scan(seq_plus, "+", len(seq_plus)) _scan(seq_minus, "-", len(seq_minus)) return candidates
def scan_gene_all_exons(exons: list[ExonRegion]) -> list[GRNACandidate]: all_cands = [] for exon in exons: all_cands.extend(scan_pam_ngg(exon.sequence, exons[0].gene_name, exon)) return all_candsStep 3. 기본 물리·화학 필터
CRISPR gRNA는 다음 조건 만족 시 대체로 안정적으로 합성·기능:
- GC 함량 30~70%: 극단은 mismatch tolerance 저하.
- 연속 4개 이상 T (TTTT) 회피: RNA Polymerase III 종결 신호.
- self-complementarity 회피: 자체 secondary structure로 접힘 → sgRNA-Cas9 결합 저해.
- 첫 nucleotide 규칙: U6 promoter는 G로 시작하는 gRNA 우선.
from Bio.SeqUtils import GC
def check_self_complementarity(seq: str, min_stem: int = 4) -> bool: """자체 상보성 검사: 5' end 4bp가 3' end 4bp의 reverse complement이면 hairpin 우려.""" if len(seq) < min_stem * 2: return False stem_5 = seq[:min_stem] stem_3 = seq[-min_stem:] return stem_5 == str(Seq(stem_3).reverse_complement())
def filter_grna_physicochemical( candidates: list[GRNACandidate], min_gc: float = 30.0, max_gc: float = 70.0, max_poly_t: int = 3, prefer_g_start: bool = True, check_hairpin: bool = True,) -> list[GRNACandidate]: """물리화학 필터.""" filtered = [] for c in candidates: # GC content gc = GC(c.protospacer) if not (min_gc <= gc <= max_gc): continue # Poly-T if "T" * (max_poly_t + 1) in c.protospacer: continue # G start (선호) if prefer_g_start and c.protospacer[0] != "G": # 완전 제외는 아니지만 후순위 (여기서는 통과) pass # Hairpin if check_hairpin and check_self_complementarity(c.protospacer): continue filtered.append(c) return filteredStep 4. R crisprScore를 Python에서 호출 (rpy2 브릿지)
crisprScore 패키지는 CRISPRon · DeepHF · CRISPRon-BE · CFD · Hsu-Zhang · MIT 등 대부분의 표준 스코어러를 통합해 제공합니다.
import numpy as npimport rpy2.robjects as rofrom rpy2.robjects.packages import importrfrom rpy2.robjects.vectors import StrVector
class CrisprScoreEnsemble: """R crisprScore의 여러 스코어러를 Python에서 호출."""
def __init__(self): # R 패키지 로딩 (사전에 R 콘솔에서 BiocManager::install 필요) self.crisprscore = importr("crisprScore") self.base = importr("base")
def crispron(self, contexts: list[str]) -> list[float]: """CRISPRon 온타겟 스코어. 입력: 각 항목 30bp context (4bp upstream + 20bp protospacer + 3bp PAM + 3bp downstream).""" r_input = StrVector(contexts) try: result = self.crisprscore.getCRISPRonScores(r_input) return list(result) except Exception as e: print(f"CRISPRon 실패: {e}") return [float("nan")] * len(contexts)
def deep_hf(self, protospacers_with_pam: list[str], enzyme: str = "WT") -> list[float]: """DeepHF 스코어. 입력: 각 23bp (20 protospacer + 3 PAM). enzyme: 'WT', 'ESP', 'HF'.""" r_input = StrVector(protospacers_with_pam) try: result = self.crisprscore.getDeepHFScores(r_input, enzyme=enzyme) return list(result) except Exception as e: print(f"DeepHF 실패: {e}") return [float("nan")] * len(protospacers_with_pam)
def crispron_be(self, contexts: list[str], editor: str = "ABE8e") -> list[float]: """CRISPRon-BE base editing 효율. editor 옵션: 'ABE8e' (adenine), 'BE4max' (cytosine), etc. """ r_input = StrVector(contexts) try: result = self.crisprscore.getCRISPRonBEScores(r_input, editor=editor) return list(result) except Exception as e: print(f"CRISPRon-BE 실패: {e}") return [float("nan")] * len(contexts)
def cfd_off_target(self, protospacer: str, target_dna: str) -> float: """Cutting Frequency Determination (Doench 2016 [5]).""" try: result = self.crisprscore.getCFDScores( StrVector([protospacer]), StrVector([target_dna]), ) return float(list(result)[0]) except Exception: return 0.0
def mit_hsu_zhang(self, protospacer: str, target_dna: str) -> float: """MIT/Hsu-Zhang 오프타겟 스코어 (position-dependent mismatch penalty).""" try: result = self.crisprscore.getMITScores( StrVector([protospacer]), StrVector([target_dna]), ) return float(list(result)[0]) except Exception: return 0.0R 환경 셋업 (편 본문 참고 명령):
# R 콘솔에서if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager")BiocManager::install(c( "crisprScore", "crisprDesign", "BSgenome.Hsapiens.UCSC.hg38", "crisprBase"))Step 5. CRISPOR 원격 오프타겟 스캔
CFD는 로컬 단일 pairwise 스코어. 게놈 전체 오프타겟 스캔에는 CRISPOR가 표준 [6]. crispritz 로컬 대안도 있음.
def query_crispor_offtargets( protospacer: str, genome: str = "hg38", max_mismatches: int = 4, timeout: int = 300,) -> list[dict]: """CRISPOR 원격 API로 게놈 전체 오프타겟 스캔. Returns: [{genome_pos, mismatches, cfd_score, gene}, ...] """ # 실전 endpoint: http://crispor.tefor.net/crispor.py # 여기서는 개념적 wrapper. 실제 API는 HTML 반환이라 파싱 필요. try: resp = requests.get( "http://crispor.tefor.net/crispor.py", params={ "seq": protospacer, "org": genome, "pam": "NGG", "showAllOTs": "1", }, timeout=timeout, ) resp.raise_for_status() # 실제 CRISPOR 응답은 HTML/TSV. 여기서는 예시 파싱만 표현. # 실전에서는 BeautifulSoup 또는 crispor 로컬 CLI로 TSV 획득 권장. return _parse_crispor_html(resp.text) except Exception as e: print(f"CRISPOR 오프타겟 스캔 실패 ({protospacer}): {e}") return []
def _parse_crispor_html(html: str) -> list[dict]: """CRISPOR HTML 응답 파싱 (개념 스텁, 실전은 BeautifulSoup).""" return [] # 실전 구현 시 완성
def compute_off_target_summary(off_targets: list[dict]) -> dict: """오프타겟 스캔 결과 요약.""" if not off_targets: return { "n_offtargets_mm4": 0, "max_cfd_score": 0.0, "high_risk_genes": [], "genome_safety_score": 1.0, } max_cfd = max(ot["cfd_score"] for ot in off_targets) high_risk = [ot["gene"] for ot in off_targets if ot["cfd_score"] > 0.5 and ot["gene"]] # 종합 안전성: 최대 CFD 뒤집기 (0=위험, 1=안전) safety = 1.0 - max_cfd return { "n_offtargets_mm4": len(off_targets), "max_cfd_score": max_cfd, "high_risk_genes": high_risk[:5], "genome_safety_score": safety, }Step 6. 앙상블 랭킹
여러 스코어러 결과를 가중 앙상블. 벤더·태스크별 최적 가중치는 실험 검증 데이터로 fine-tune 가능.
import pandas as pd
DEFAULT_WEIGHTS = { "crispron": 0.30, "deep_hf": 0.25, "crispron_be": 0.10, # BE 시나리오 아닐 때는 0으로 "off_target_safety": 0.35,}
def build_ensemble_ranking( candidates: list[GRNACandidate], on_scores: dict[str, list[float]], off_target_summaries: list[dict], weights: dict[str, float] = DEFAULT_WEIGHTS,) -> pd.DataFrame: """스코어러 통합 랭킹 DataFrame.""" df = pd.DataFrame([{ "gene": c.gene_name, "exon": c.exon_number, "protospacer": c.protospacer, "pam": c.pam, "strand": c.strand, "genome_position": c.genome_position, "context_50bp": c.context_50bp, } for c in candidates]) for name, scores in on_scores.items(): df[f"on_{name}"] = scores df["off_n_mm4"] = [s["n_offtargets_mm4"] for s in off_target_summaries] df["off_max_cfd"] = [s["max_cfd_score"] for s in off_target_summaries] df["off_safety"] = [s["genome_safety_score"] for s in off_target_summaries] df["off_high_risk_genes"] = [ ";".join(s["high_risk_genes"]) for s in off_target_summaries ] # 정규화 후 가중 평균 def _normalize(col): c = df[col].fillna(df[col].median()) return (c - c.min()) / (c.max() - c.min() + 1e-8) final = np.zeros(len(df)) if "on_crispron" in df.columns: final += weights.get("crispron", 0.0) * _normalize("on_crispron") if "on_deep_hf" in df.columns: final += weights.get("deep_hf", 0.0) * _normalize("on_deep_hf") if "on_crispron_be" in df.columns: final += weights.get("crispron_be", 0.0) * _normalize("on_crispron_be") final += weights["off_target_safety"] * df["off_safety"].fillna(0.5) df["final_score"] = final return df.sort_values("final_score", ascending=False).reset_index(drop=True)Step 7. 실험실 발주 리포트 자동 생성
상위 5개 gRNA를 Twist Bioscience · IDT 등에서 즉시 발주 가능한 Markdown + CSV로.
def generate_lab_report( ranked: pd.DataFrame, output_md: Path, output_csv: Path, top_k: int = 5,) -> None: """실험실 발주 가능한 리포트 생성.""" top = ranked.head(top_k) lines = [ f"# gRNA 후보 상위 {top_k}개 발주 리포트", "", f"대상 유전자: **{top.iloc[0]['gene']}**", f"생성 시각: (파이프라인 실행 시각으로 채우기)", "", "## 요약 표", "", "| Rank | Exon | Protospacer (20bp) | PAM | Strand | On (CRISPRon) | On (DeepHF) | Off Safety | Final |", "|:----:|:----:|:------------------:|:---:|:------:|:-------------:|:-----------:|:----------:|:-----:|", ] for i, row in top.iterrows(): lines.append( f"| {i+1} | {row['exon']} | `{row['protospacer']}` | {row['pam']} | {row['strand']} | " f"{row.get('on_crispron', float('nan')):.3f} | {row.get('on_deep_hf', float('nan')):.3f} | " f"{row['off_safety']:.3f} | **{row['final_score']:.3f}** |" ) lines += [ "", "## 발주 서열 (5' → 3')", "", "Spacer 서열에 U6 promoter 요구 G가 없으면 앞에 G 추가 or ExoScribe 등 alternative promoter 활용.", "", ] for i, row in top.iterrows(): spacer = row["protospacer"] if spacer[0] != "G": forge = f"G{spacer}" else: forge = spacer lines.append(f"### Rank {i+1}") lines.append(f"- Protospacer (spacer only, 20bp): `{spacer}`") lines.append(f"- Order-ready (with G prefix if needed): `{forge}` ({len(forge)}bp)") lines.append(f"- PAM: `{row['pam']}`") lines.append(f"- Genome position (hg38): {row['genome_position']}") if row["off_high_risk_genes"]: lines.append(f"- ⚠️ High-risk off-target genes: {row['off_high_risk_genes']}") lines.append("") lines += [ "## 사전 실험 체크리스트", "", "- [ ] 발주 전 alignment 재확인 (BLASTn to hg38 · GRCh38.p14)", "- [ ] SpCas9-HF1 vector 준비 (예: pX330 or plentiCRISPR v2)", "- [ ] 대상 세포주 (K562)의 mycoplasma 검사 완료", "- [ ] T7E1 assay 또는 amplicon deep sequencing primer 설계", "- [ ] 대조군 gRNA (non-targeting, e.g. sgLacZ) 병행 발주", ] output_md.write_text("\n".join(lines), encoding="utf-8") top.to_csv(output_csv, index=False)통합 파이프라인 · 실행 예시
전체를 하나로 묶은 실행 함수와 BRCA1 시나리오 실행 예시.
def full_pipeline( gene_symbol: str, refseq_id: str, output_prefix: str, editor: str | None = None, # None: nuclease KO, "ABE8e"/"BE4max": base editing top_k: int = 5,) -> pd.DataFrame: print(f"[1/7] {gene_symbol} exon 정보 fetch (RefSeq {refseq_id})") # 실전에서는 exon 좌표를 GENCODE GTF에서 로컬 파싱 권장 exons = fetch_gene_exons_ucsc(gene_symbol) if not exons: # fallback: mRNA 전체를 단일 "exon"으로 처리 (간단 시연) seq = fetch_refseq_mrna(refseq_id) exons = [ExonRegion( gene_name=gene_symbol, exon_number=1, chromosome="", strand="+", start_hg38=0, end_hg38=len(seq), length=len(seq), sequence=seq, coding_frame=0, )] print(f" exon {len(exons)}개") print("[2/7] PAM (NGG) 스캔") all_cands = scan_gene_all_exons(exons) print(f" 원시 후보 {len(all_cands)}") print("[3/7] 물리·화학 필터") filtered = filter_grna_physicochemical(all_cands) print(f" 필터 통과 {len(filtered)}") print("[4/7] 온타겟 스코어러 앙상블") scorer = CrisprScoreEnsemble() contexts = [c.context_50bp for c in filtered] proto_pam = [c.protospacer + c.pam for c in filtered] on_scores = { "crispron": scorer.crispron(contexts), "deep_hf": scorer.deep_hf(proto_pam, enzyme="HF"), # SpCas9-HF1 } if editor: on_scores["crispron_be"] = scorer.crispron_be(contexts, editor=editor) print("[5/7] 오프타겟 스캔 (CRISPOR 원격)") off_summaries = [] for i, c in enumerate(filtered): if i % 50 == 0: print(f" 진행 {i}/{len(filtered)}") offs = query_crispor_offtargets(c.protospacer) off_summaries.append(compute_off_target_summary(offs)) print("[6/7] 앙상블 랭킹") ranked = build_ensemble_ranking(filtered, on_scores, off_summaries) ranked.to_csv(f"{output_prefix}_all_candidates.csv", index=False) print(f"[7/7] 상위 {top_k} 발주 리포트") generate_lab_report( ranked, Path(f"{output_prefix}_top{top_k}.md"), Path(f"{output_prefix}_top{top_k}.csv"), top_k=top_k, ) print(f" 완료: {output_prefix}_top{top_k}.md") return ranked
# 실행 예시 (BRCA1 KO 시나리오)# ranked = full_pipeline(# gene_symbol="BRCA1",# refseq_id="NM_007294",# output_prefix="brca1_k562",# editor=None, # SpCas9-HF1 nuclease KO# top_k=5,# )예상 실행 로그 (참고용)
학습자가 실제 실행 시 이런 로그를 예상:
[1/7] BRCA1 exon 정보 fetch (RefSeq NM_007294)
exon 24개
[2/7] PAM (NGG) 스캔
원시 후보 423
[3/7] 물리·화학 필터
필터 통과 197
[4/7] 온타겟 스코어러 앙상블
[5/7] 오프타겟 스캔 (CRISPOR 원격)
진행 0/197
진행 50/197
...
[6/7] 앙상블 랭킹
[7/7] 상위 5 발주 리포트
완료: brca1_k562_top5.md성능·비용·알려진 실패 케이스
성능 참고 (공개 벤치 인용)
| 스코어러 | 벤치 데이터셋 | Spearman ρ | 특이 사항 | 출처 |
|---|---|---|---|---|
| Rule 4 (Doench 2014) | 자체 벤치 | 0.42 | 손공학 baseline | Doench et al., Nat Biotechnol 2014 |
| DeepCRISPR | Kim 2017 dataset | 0.65 | 1D CNN | Chuai et al., Genome Biol 2018 [1] |
| CRISPRon (Xiang 2021) | Kim 2019 | 0.72 | context-aware CNN | Xiang et al., Nat Commun 2021 [2] |
| DeepHF (WT SpCas9) | Wang 2019 | 0.73 | RNN + attention | Wang et al., Nat Commun 2019 [3] |
| DeepHF (SpCas9-HF1) | Wang 2019 | 0.75 | HF1 variant 특화 | Wang et al. 2019 [3] |
| CRISPRon-BE (ABE8e) | ABEmax dataset | 0.68 | Base editing | Kim et al. 2022 [4] |
| BE-Hive | BE4/ABE dataset | 0.75 | 편집 창 예측 강력 | Arbab et al. 2020 |
| 앙상블 (본 편 근사) | 벤치 재현 | 0.77~0.82 (추정) | 다중 스코어러 평균 | 커뮤니티 벤치 [7] |
학습자 재현 예상 비용
- API 비용 0 (완전 로컬 또는 CRISPOR 무료 원격).
- 소형 소비자 GPU 기준 gRNA 500개 스코어링 약 1~3분.
- CPU 폴백 시 5~15분.
- CRISPOR 원격 API: 초당 1 req 이하 권장, 500개 스캔 약 10~20분.
- 로컬 crispritz 대안 시 CPU 30분 + 게놈 인덱스 20GB 다운로드.
알려진 실패 케이스 5건 (커뮤니티·논문 수집형)
-
rpy2 환경 충돌 (R 4.3 + Bioconductor 3.18 + rpy2 3.5)
증상:libR.so symbol lookup error·PermissionError: could not load package등 R 라이브러리 로드 실패.
원인: R 라이브러리 경로가 conda vs system R 이중 등록, 특히 macOS/Linux 혼용 시 흔함.
회피: (a) conda 환경 안에서 R까지 conda-forge로 통일 설치 (conda install -c conda-forge r-base bioconductor-crisprscore), (b)R_HOME·LD_LIBRARY_PATH환경변수 명시적 지정, (c) Docker 컨테이너로 격리 (rocker/tidyverse + crisprScore install), (d) rpy2 대신 subprocess로 Rscript 직접 호출 (rpy2 우회).
출처: rpy2 GitHub Issues [8]; Bioconductor Support Forum crisprScore 스레드. -
CFD 스코어 계산과 실제 실험 오프타겟 결과 불일치
증상: CFD 상 안전 (스코어 낮음)한 gRNA가 실제 실험(GUIDE-seq · CIRCLE-seq)에서 예상 밖 오프타겟 편집 유발.
원인: CFD는 mismatch position 가중치 학습 (Doench 2016 [5])이지만 chromatin state · DNA methylation · nucleosome positioning은 반영 안 함.
회피: (a) 여러 오프타겟 알고리즘 앙상블 (CFD + MIT/Hsu-Zhang + Elevation), (b) TSS · enhancer 지역은 별도 페널티 (ENCODE annotation 참조), (c) GUIDE-seq · Digenome-seq 등 실측 오프타겟 데이터로 사후 검증, (d) 상위 후보 3개 이상을 병렬 실측 후 최종 선택.
출처: Tsai et al. "GUIDE-seq" Nat Biotechnol 2015 [9]; Doench 2016 CFD 논문 [5]; CRISPOR docs [6]. -
BE (Base Editing) 편집 창 오해석 + bystander effect
증상: CRISPRon-BE로 스코어링한 base editor가 실제 편집 대상 뉴클레오티드 이외 위치를 함께 편집 (bystander).
원인: BE는 protospacer 안 특정 창(예: 4-8 nt) 편집한다는 가정이 있으나 실제로는 5-10 nt 확장, adenine deaminase (ABE)는 특정 sequence context 편향.
회피: (a) 편집 창 표기를 여러 모델 예측으로 앙상블 (BE-Hive · BE-DICT · CRISPRon-BE), (b) 실험 설계에 bystander 후보 별도 표시, (c) 정밀 편집 필요 시 Prime editing (PE) 스코어러 (PRIDICT) 고려, (d) 편집 창 안 target C/A 위치에 원치 않는 codon 변화 없는지 codon-wise 시뮬레이션.
출처: Anzalone et al. "Prime editing" Nature 2019 [10]; Arbab et al. "BE-Hive" Cell 2020. -
CRISPOR 원격 API rate limit + 응답 지연
증상: 500개 gRNA를 연속 스캔하면 절반 이상 실패 or timeout.
원인: CRISPOR 원격 서버는 CPU 리소스 제약. 커뮤니티 자원.
회피: (a) 로컬 crispritz + BWA + 게놈 인덱스로 배치 스캔 (게놈 인덱스 20GB 다운로드 필요), (b) CRISPOR 로컬 실행 (Python 스크립트 로컬 설치), (c) 요청 간격 5초 이상, (d) 상위 후보 20개만 CRISPOR 원격 스캔 (100+개는 로컬).
출처: CRISPOR 공식 documentation "batch use" 섹션 [6]. -
BRCA1 alternative splicing으로 exon 좌표 불일치
증상: BRCA1은 여러 splice variant가 있음. NM_007294가 canonical이나 다른 transcript (NM_007297 등)에서는 exon 수 · 좌표 다름.
원인: MyGene.info는 canonical 하나만 반환. 실제 조직·세포주에서 발현되는 isoform이 다를 수 있음.
회피: (a) GENCODE GTF에서 여러 transcript 좌표 파싱, (b) 표적 조직·세포주의 실측 발현 데이터 (GTEx · Human Protein Atlas)로 우세 isoform 확인, (c) 여러 isoform에 공통인 exon (constitutive exon)만 우선.
출처: GENCODE annotation docs [11]; UCSC Genome Browser BRCA1 track.
확장 아이디어
- Prime Editing (PE) 스코어러 확장: PE는 5' pegRNA 설계가 복잡하지만 정밀 편집 최적. PRIDICT · DeepPE 등 최신 스코어러 편입.
- CRISPRi/CRISPRa (activation/interference): KO 대신 발현 조절. crisprScore의
getCrispraiScores활용, dCas9-VP64 (activation) or dCas9-KRAB (interference). - 다중 gRNA (multiplex) 조합 최적화: 유전자 여러 개 동시 KO 시 서로 오프타겟 간섭 최소화 (조합 최적화 · 정수 계획법).
- 환자별 유전 배경 반영: 특정 SNP 있는 환자에서 오프타겟 위치가 달라짐 (personalized off-target, gnomAD · UK Biobank annotation 참조).
- Nucleosome positioning 통합: MNase-seq 데이터로 nucleosome-occluded 영역 별도 페널티.
- T7 in vitro synthesis vs oligo pool: gRNA 발주 형태(단일 vs 풀)에 따라 상위 5개 vs 상위 200개 스코어링 전략 다름.
다음 편
- 편 07
drug-target-gnn: gRNA로 knockout할 표적 후보를 GNN drug-target 예측으로 미리 압축. - 편 10
med-llm-reproduction: gRNA 스코어러 벤치를 여러 LLM에게 재현 시켜 평가. - 편 12
single-cell-perturbation: gRNA 후보를 실제 knockout 하기 전에 in silico로 세포 반응 예측 (Perturb-seq 시뮬레이션). - 편 14
bio-mcp-agent: gRNA 설계를 MCP tool로 노출해 "BRCA1 exon 3~5에서 KO gRNA 뽑아줘" 자율 실행.
참고 문헌
- Chuai G, Ma H, Yan J, et al. "DeepCRISPR: optimized CRISPR guide RNA design by deep learning." Genome Biology 2018.
https://genomebiology.biomedcentral.com/articles/10.1186/s13059-018-1459-4 - Xiang X, Corsi GI, Anthon C, et al. "Enhancing CRISPR-Cas9 gRNA efficiency prediction by deep learning (CRISPRon)." Nature Communications 2021.
https://www.nature.com/articles/s41467-021-23576-0 - Wang D, Zhang C, Wang B, et al. "Optimized CRISPR guide RNA design for two high-fidelity Cas9 variants by deep learning (DeepHF)." Nature Communications 2019.
https://www.nature.com/articles/s41467-019-12281-8 - Kim HK, Yu G, Park J, et al. "Predicting the efficiency of prime editing guide RNAs in human cells (PRIDICT); base editing companion (CRISPRon-BE)." Nature Biotechnology 2023 (편 관련 부분).
https://www.nature.com/articles/s41587-022-01613-7 - Doench JG, Fusi N, Sullender M, et al. "Optimized sgRNA design to maximize activity and minimize off-target effects of CRISPR-Cas9 (CFD 원논문)." Nature Biotechnology 2016.
https://www.nature.com/articles/nbt.3437 - CRISPOR web tool 및 documentation:
http://crispor.tefor.net//https://github.com/maximilianh/crisporWebsite - crisprScore Bioconductor 패키지:
https://bioconductor.org/packages/release/bioc/html/crisprScore.html - rpy2 GitHub Issues:
https://github.com/rpy2/rpy2/issues - Tsai SQ, Zheng Z, Nguyen NT, et al. "GUIDE-seq enables genome-wide profiling of off-target cleavage by CRISPR-Cas nucleases." Nature Biotechnology 2015.
https://www.nature.com/articles/nbt.3117 - Anzalone AV, Randolph PB, Davis JR, et al. "Search-and-replace genome editing without double-strand breaks or donor DNA (Prime Editing)." Nature 2019.
https://www.nature.com/articles/s41586-019-1711-4 - GENCODE human genome annotation:
https://www.gencodegenes.org/human/ - Kim HK et al. "Predicting the efficiency of prime editing guide RNAs in human cells (PRIDICT)." Nat Biotechnol 2023.
- Broad Institute CRISPResso2 (편집 결과 분석):
https://github.com/pinellolab/CRISPResso2 - Bioconductor BSgenome.Hsapiens.UCSC.hg38:
https://bioconductor.org/packages/release/data/annotation/html/BSgenome.Hsapiens.UCSC.hg38.html - RTH-tools CRISPRon-BE GitHub:
https://github.com/RTH-tools/crispron-BE - BE-Hive: Arbab M et al. "Determinants of Base Editing Outcomes from Target Library Analysis and Machine Learning." Cell 2020.
https://github.com/maxwshen/be_predict_efficiency - Addgene sgRNA 발주 표준 프로토콜:
https://www.addgene.org/guides/crispr/ - crispritz (오프타겟 로컬 스캔):
https://github.com/pinellolab/CRISPRitz - NCBI RefSeq NM_007294 (BRCA1 canonical mRNA):
https://www.ncbi.nlm.nih.gov/nuccore/NM_007294 - MyGene.info API:
https://mygene.info/v3/api