QC and Filtering

Step 02 / 08

본 단계에서는 single-cell RNA-seq 데이터의 품질을 평가하고, low-quality cell을 제거하는 과정을 수행합니다. QC는 downstream clustering, annotation, DEG 분석의 신뢰도를 결정하는 핵심 단계입니다.


Part 1. Theoretical Background

A) 왜 QC가 필요한가?

Single-cell RNA-seq 데이터는 개별 세포 수준의 전사체를 측정할 수 있는 강력한 기술이지만, 실제 데이터에는 다양한 기술적 및 생물학적 artifact가 포함될 수 있습니다. 예를 들어 세포 분리 과정에서 손상되거나 죽어가는 세포가 capture될 수 있고, 두 개 이상의 세포가 하나의 droplet에 함께 들어가는 doublet이 발생할 수 있으며, 주변 ambient RNA가 실제 세포의 발현 패턴을 오염시킬 수도 있습니다.

이러한 artifact를 적절히 제거하지 않으면, 이후 단계에서 가짜 cluster가 형성되거나 세포 유형 annotation이 왜곡되고, DEG 결과 역시 신뢰하기 어려워질 수 있습니다. 따라서 QC는 단순한 전처리가 아니라, downstream 분석 전체의 해석 가능성을 높이는 필수 과정입니다.

QC importance and artifact overview

QC를 수행하지 않은 데이터와 수행한 데이터의 분석 결과 비교 (UMAP, marker, DEG, pathway)

핵심 메시지
  • QC는 “나쁜 세포를 버리는 작업”이 아니라, 분석 가능한 세포를 선별하는 과정입니다.
  • 좋은 QC는 clustering, annotation, DEG, pathway 분석의 품질을 크게 좌우합니다.

B) 주요 QC artifact

항목 설명
Low-quality / damaged cells 유전자 수가 지나치게 적거나 mitochondrial gene 비율이 높아, 정상 세포 상태를 반영하지 못할 수 있습니다.
Doublets / multiplets 둘 이상의 세포가 하나의 droplet에 함께 들어가 혼합된 발현 패턴을 보입니다.
Ambient RNA 주변에 떠다니는 RNA가 droplet에 섞여 들어가 실제 세포 발현을 오염시킬 수 있습니다.
Dissociation stress 조직 분리 과정 자체가 스트레스 반응 유전자를 유도하여 인위적인 transcriptome 변화를 만들 수 있습니다.

C) 주요 QC 지표

Seurat 기반 QC에서는 보통 다음 세 가지 지표를 가장 먼저 확인합니다.

일반적으로 nFeature_RNA가 지나치게 낮은 세포는 low-quality cell일 가능성이 있고, 반대로 지나치게 높은 세포는 doublet을 의심할 수 있습니다. percent.mt가 높은 세포는 손상되었거나 stressed 상태일 가능성이 높습니다.

D) QC 기준은 고정되어 있지 않다

QC filtering 기준에는 절대적인 정답이 없습니다. 실제 연구에서는 tissue type, cell type composition, species, sequencing depth, dissociation protocol, disease context에 따라 서로 다른 기준이 사용됩니다.

예를 들어 종양 조직은 스트레스가 높아 mitochondrial gene 비율이 상대적으로 높게 나타날 수 있고, 면역세포는 다른 세포 유형보다 검출 유전자 수가 적을 수 있습니다. 따라서 threshold는 “문헌값을 그대로 복사”하기보다, 데이터 분포를 먼저 확인한 뒤 합리적으로 조정해야 합니다.

중요
QC 기준은 고정값이 아니라, 조직 특성과 연구 목적에 맞게 설정해야 합니다.
Part 2. Hands-on Practice

E) 실습 데이터와 QC 전략

본 실습에서는 mouse colorectal cancer 6개 샘플 (ND1, ND2, ND3, WD1, WD2, WD3)을 사용합니다. 각 샘플은 이미 Seurat object로 불러와 sample_list로 정리한 상태를 가정합니다.

이번 단계에서는 각 샘플에 대해 mitochondrial gene 비율을 계산하고, 분포를 시각화한 뒤, 동일한 기준으로 샘플별 filtering을 수행합니다.

F) mitochondrial gene 비율 계산

mouse 데이터이므로 mitochondrial gene prefix는 보통 mt- 형식을 사용합니다.

sample_list <- lapply(sample_list, function(obj) {
  obj[["percent.mt"]] <- PercentageFeatureSet(obj, pattern = "^mt-")
  return(obj)
})

G) QC 분포 시각화

먼저 개별 샘플에서 QC 지표 분포를 확인합니다. 아래 예시는 ND1 샘플의 violin plot입니다.

VlnPlot(
  sample_list[[1]],
  features = c("nFeature_RNA", "nCount_RNA", "percent.mt"),
  ncol = 3,
  pt.size = 0.1
)

실제 분석에서는 모든 샘플에 대해 같은 분포를 확인하는 것이 좋습니다.

for (nm in names(sample_list)) {
  print(
    VlnPlot(
      sample_list[[nm]],
      features = c("nFeature_RNA", "nCount_RNA", "percent.mt"),
      ncol = 3,
      pt.size = 0.1
    ) + ggtitle(nm)
  )
}
QC violin plots for six mouse CRC samples

6개 mouse CRC 샘플(ND1~3, WD1~3)의 QC violin plot 예시. 각 샘플의 nFeature_RNA, nCount_RNA, percent.mt 분포를 비교할 수 있습니다.

H) ⚠️ VlnPlot() 실행 중 오류 해결

일부 환경에서 VlnPlot() 실행 시 아래와 같은 에러가 발생할 수 있습니다. 이는 Seurat 패키지 버전 문제 또는 내부 S4 객체 처리 오류로 인해 발생하는 경우가 많습니다.

'S4SXP': should not happen - please report
Error during wrapup
Error: no more error handlers available
VlnPlot error fix guide

VlnPlot 실행 오류 및 해결 방법 요약

해결 방법
  1. Seurat 최신 개발 버전 설치
  2. R 세션 완전 재시작
  3. layer = "counts" 옵션 추가
# Seurat 최신 버전 설치
install.packages("devtools")
devtools::install_github("satijalab/seurat", ref = "main", force = TRUE)

# 설치 후 R 재시작
packageVersion("Seurat")

# VlnPlot 재실행
VlnPlot(
  sample_list[[1]],
  features = c("nFeature_RNA", "nCount_RNA", "percent.mt"),
  ncol = 3,
  pt.size = 0.1,
  layer = "counts"
)
Tip
Seurat v5에서는 assay 구조가 변경되면서 layer 개념이 도입되었습니다. 따라서 일부 plotting 함수에서 layer="counts" 옵션을 명시하면 더 안정적으로 실행됩니다.

I) 시각화 팁: 여러 결과를 한 Figure로 정리하기

샘플 수가 많을 경우 각 plot을 개별적으로 출력하면 비교가 불편할 수 있습니다. 이럴 때는 patchwork 패키지를 이용해 여러 결과를 하나의 figure로 정리하면 샘플 간 QC 분포를 훨씬 쉽게 비교할 수 있습니다.

library(patchwork)
library(ggplot2)

plot_list <- lapply(names(sample_list), function(nm) {
  VlnPlot(
    sample_list[[nm]],
    features = c("nFeature_RNA", "nCount_RNA", "percent.mt"),
    ncol = 3,
    pt.size = 0.1
  ) + ggtitle(nm)
})

final_plot <- wrap_plots(plot_list, ncol = 6) &
  theme(
    plot.margin = margin(2, 2, 2, 2),
    panel.spacing = unit(0.2, "lines"),
    text = element_text(size = 7)
  )

final_plot
시각화 팁
  • 여러 샘플을 한 장에 배치하면 QC 분포를 한눈에 비교할 수 있습니다.
  • ncol, 글자 크기, margin, panel spacing을 조정하면 figure 가독성을 높일 수 있습니다.
  • 발표자료나 논문 figure에서는 한 샘플씩 보는 것보다 통합 figure가 훨씬 유용합니다.
주의
R에서는 플롯 창 크기, 배치 방식, 저장 해상도, 글자 크기 설정에 따라 같은 코드라도 그림이 다르게 보일 수 있습니다. 따라서 최종 figure를 저장할 때는 화면에서만 확인하지 말고, 실제 저장된 결과물(png, pdf)을 반드시 다시 확인하는 것이 좋습니다.

J) filtering 기준 설정

여기서는 실습용 예시로 다음 기준을 사용합니다.

이 값은 하나의 예시이며, 실제 연구에서는 샘플 분포와 조직 특성을 고려해 조정할 수 있습니다.

K) 샘플별 filtering 수행

sample_list_filtered <- lapply(sample_list, function(obj) {
  subset(
    obj,
    subset =
      nFeature_RNA > 200 &
      nFeature_RNA < 6000 &
      percent.mt < 10
  )
})

L) filtering 전후 cell 수 비교

# filtering 전 cell 수
before_cells <- sapply(sample_list, ncol)

# filtering 후 cell 수
after_cells <- sapply(sample_list_filtered, ncol)

qc_summary <- data.frame(
  sample = names(before_cells),
  before = before_cells,
  after = after_cells,
  removed = before_cells - after_cells
)

qc_summary

이 표를 통해 각 샘플에서 얼마나 많은 세포가 제거되었는지 확인할 수 있습니다.

M) filtering 후 분포 재확인

for (nm in names(sample_list_filtered)) {
  print(
    VlnPlot(
      sample_list_filtered[[nm]],
      features = c("nFeature_RNA", "nCount_RNA", "percent.mt"),
      ncol = 3,
      pt.size = 0.1
    ) + ggtitle(paste0(nm, " (filtered)"))
  )
}

filtering 후에는 분포가 더 안정적으로 정리되었는지 다시 확인하는 것이 중요합니다.

N) 다음 단계로 넘길 객체 준비

이후 단계에서는 filtering된 객체를 사용합니다.

sample_list <- sample_list_filtered
rm(sample_list_filtered)

names(sample_list)
요약
  • QC는 low-quality cell, doublet, ambient RNA, dissociation artifact를 줄이기 위한 필수 단계입니다.
  • 주요 QC 지표는 nFeature_RNA, nCount_RNA, percent.mt입니다.
  • QC 기준에는 절대적인 정답이 없으며, 조직 특성과 데이터 분포에 따라 달라집니다.
  • 본 실습에서는 6개 mouse CRC 샘플에 대해 동일한 기준으로 filtering을 수행했습니다.