# 05. raw count를 비교 가능한 값으로 바꾸기

> raw count에 섞인 유전자 길이와 시퀀싱 깊이의 영향을 위젯으로 확인하고, TPM으로 비교 가능한 비율을 만드는 과정을 설명한다.

정렬된 read를 유전자마다 세면 **raw count**가 나옵니다. 하지만 이 숫자에는 실제 발현량뿐 아니라 유전자 길이와 시퀀싱 깊이가 섞여 있습니다. 위젯을 직접 움직이며 두 영향을 분리하고, 비교 목적에 맞는 값으로 바꾸는 과정을 따라갑니다.

> **이 수업의 질문**: 같은 발현량인데도 달라지는 raw count를 어떻게 비교 가능한 값으로 바꾸는가?

정렬까지가 각 이벤트에 `gene_id`를 붙이는 단계였다면, 정량은 그 이벤트를 `GROUP BY gene_id`로 집계하는 단계입니다. raw count는 정확한 집계값이지만, 요청 수가 실행 시간과 서버 수에 함께 좌우되는 것처럼 유전자 발현 외의 조건에도 좌우됩니다.

```python
raw_count = alignments.groupby("gene_id").size()
rpk = raw_count / gene_length_kb
tpm = rpk / rpk.sum() * 1_000_000
```

이 의사코드에서 `raw_count`는 관측값, `gene_length_kb`는 유전자별 고정 메타데이터, `rpk.sum()`은 샘플별 스케일입니다. 어떤 분모를 언제 적용했는지가 지표의 의미를 결정합니다.

> **한 줄 요약**: raw count는 **유전자 길이**와 **시퀀싱 뎁스** 두 가지에 오염돼 있어 그대로 비교할 수 없습니다. TPM은 *길이 → 뎁스* 순으로 보정해, **모든 샘플의 발현량 합계를 정확히 1,000,000으로 고정**합니다. 그래서 TPM 값 하나는 "백만 개 전사체 중 이 유전자 몇 개"라는 **동일한 잣대**가 됩니다.

## 1. raw count는 두 가지에 휘둘린다

정렬된 BAM에서 유전자마다 붙은 read를 세면 정수 하나(raw count)가 나옵니다. 문제는 이 숫자가 **"그 유전자가 얼마나 발현됐나"** 를 곧이곧대로 말해 주지 않는다는 점입니다.

데이터 구조로는 다음과 같은 변환입니다.

| 정렬 레코드 | 집계 키 | 집계 결과 |
| --- | --- | --- |
| read 1 → `GENE_A` | `GENE_A` | |
| read 2 → `GENE_A` | `GENE_A` | `GENE_A: 2` |
| read 3 → `GENE_B` | `GENE_B` | `GENE_B: 1` |

이 숫자를 바로 비교하려면 모든 유전자가 같은 길이이고 모든 샘플을 같은 깊이로 읽었다는 조건이 필요합니다. 실제 데이터는 두 조건을 만족하지 않습니다.

### 함정 ①: 유전자 길이

mRNA를 잘게 조각내어 시퀀싱하므로, **같은 수의 mRNA 분자라도 긴 유전자에서 조각(fragment)이 더 많이 나옵니다.** 즉 read 수는 발현량이 아니라 **발현량 × 길이**에 비례합니다. 아래 애니메이션에서 **유전자 → 전사(mRNA) → 조각내기 → 정렬**을 단계별로 따라가 보세요. 발현량(mRNA 3개)을 똑같이 맞췄는데도, 긴 A는 read가 18개·짧은 B는 6개로 벌어집니다. 마지막 단계에서 길이로 나누면 둘이 같아집니다.



> 이 "÷ 유전자 길이"가 정규화의 첫 단계이고, 그 결과가 **RPK(reads per kilobase)** 입니다. 길이가 사라지면 비로소 **한 샘플 안에서 유전자끼리** 비교할 수 있습니다.

### 함정 ②: 시퀀싱 뎁스

같은 샘플을 더 깊게 시퀀싱하면 모든 유전자의 count가 그만큼 커집니다. 발현이 변한 게 아니라 **더 많이 읽었을 뿐**입니다. 그래서 **서로 다른 샘플**을 비교하려면, 각 유전자 count를 그 샘플의 **전체 read 수(뎁스)** 로 나눠 스케일을 맞춰야 합니다. 아래에서 **같은 조직(tissue)을** 얕게·깊게(3배) 시퀀싱하는 과정을 따라가 보세요: raw count는 전부 3배로 벌어지지만, 전체 read로 나누면 두 샘플이 다시 같아집니다.



> 정리하면 정규화가 풀어야 할 숙제는 둘입니다: **길이**(유전자 간 비교를 막음)와 **뎁스**(샘플 간 비교를 막음).

## 2. 한 걸음씩 보정하기: RPK → RPKM/FPKM → TPM

두 보정을 **어떤 순서로** 적용하느냐가 지표를 가릅니다.

| 지표 | 보정 | 계산 |
| --- | --- | --- |
| **CPM** (counts per million) | 뎁스만 | count ÷ (전체 read/10⁶) |
| **RPK** (reads per kilobase) | 길이만 | count ÷ 길이(kb) |
| **RPKM / FPKM** | **뎁스 → 길이** | count ÷ (전체 read/10⁶) ÷ 길이(kb) |
| **TPM** | **길이 → 뎁스** | (count ÷ 길이) 를 구한 뒤, **그 합**으로 다시 나눔 ×10⁶ |

- **RPKM**과 **FPKM**은 사실상 같은 값입니다. read를 세면 RPKM, **fragment**(paired-end에서 read 쌍 하나 = fragment 하나)를 세면 FPKM일 뿐입니다.
- **RPKM/FPKM은 뎁스로 먼저 나눈 뒤 길이로 나눕니다.** 그래서 샘플마다 **합계가 제각각**이 됩니다.
- **TPM은 순서를 뒤집습니다.** 먼저 길이로 나눠 RPK를 구하고(유전자별 "농도"), 그다음 **그 RPK들의 합**으로 나눠 백만을 곱합니다. 이 마지막 단계가 결정적입니다: **합으로 나누므로 결과의 총합이 항상 10⁶** 이 됩니다.

:::note[🧩 왜 순서만 바꿨는데 성질이 달라지나]
RPKM은 "전체 read 중 이 유전자의 몫"을 길이로 보정한 값이라, **분모(전체 read)가 샘플마다 고정돼 있지 않으면** 합이 어긋납니다. TPM은 아예 **자기 샘플의 RPK 총합**으로 다시 나누기 때문에, 정의상 **Σ TPM = 1,000,000** 으로 못 박힙니다. 같은 두 보정을 쓰지만, TPM은 결과를 **비율(전체의 1/100만 단위)** 로 강제하는 셈입니다.
:::

각 약어의 정확한 정의, 기호와 수식은 [RNA-seq 발현량 정규화 지표](/reference/rna-seq-normalization/)에 분리해 두었습니다. 여기서는 TPM이 풀려는 두 문제와 계산 결과의 성질에만 집중합니다.

## 3. TPM 2단계 계산: 직접 해보기

TPM의 실제 계산은 딱 두 단계입니다.

> **Step 1.** 유전자별 count ÷ 길이(kb) = **RPK**: 긴 유전자일수록 read가 많은 편향 제거
>
> **Step 2.** 각 RPK ÷ (그 샘플의 RPK 총합) × 10⁶ = **TPM**: 뎁스 보정 + 총합을 100만으로 고정

아래 계산기에서 **지표(Raw/RPKM/TPM)** 를 바꿔 가며, 특히 맨 아래 **열 합계 Σ** 가 어떻게 달라지는지 보세요. Sample A는 네 유전자가 *동일하게* 발현된 기준 샘플이고, Sample B는 시나리오 버튼·슬라이더로 바꿀 수 있습니다.



## 4. 왜 TPM이 샘플 간 비교에 유리한가

위 계산기에서 지표를 **TPM**으로 두고 Sample B 시나리오를 눌러 보면 한 가지가 드러납니다:

> **어떤 시나리오에서도 Σ TPM = 1,000,000.** 뎁스를 ×3으로 늘려도, 한 유전자를 폭증시켜도 총합은 늘 100만입니다.

이게 핵심입니다. 총합이 고정돼 있으니 **TPM 값 하나가 "전체 전사체 100만 개 중 이 유전자의 몫"** 이라는 **절대적이지 않지만 일관된 잣대**가 됩니다. 두 샘플의 TPM을 나란히 놓고 바로 비교할 수 있는 이유죠.

반면 **RPKM**은 시나리오를 바꿀 때마다 **Σ RPKM이 출렁입니다.** 합계가 샘플마다 다르면, 같은 RPKM 숫자라도 두 샘플에서 차지하는 비율이 달라 나란히 비교할 때 미묘하게 어긋납니다. [이것이 오늘날 발현량 비교에 FPKM보다 TPM을 권장하는 이유](https://doi.org/10.1007/s12064-012-0162-3)입니다.

:::tip[한 문장 정리]
**RPKM은 "먼저 뎁스로 나눠서" 합이 안 맞고, TPM은 "나중에 자기 합으로 나눠서" 항상 100만.** 그래서 [Sid 케이스](/lessons/rna-seq-analysis/)처럼 한 샘플을 GTEx·PCAWG 같은 공개 데이터와 나란히 비교할 때는 **TPM**을 씁니다.
:::

## 5. 주의: TPM도 만능은 아니다

TPM이 모든 비교를 해결해 주지는 않습니다. 위 위젯에서 이미 힌트가 나왔습니다.

- **TPM은 절대량이 아니라 비율입니다.** 총합이 100만으로 고정돼 있어, 한 유전자가 폭증하면(계산기의 ‘G4 폭증’) **다른 유전자들의 TPM이 실제로는 안 변했는데도 떨어져** 보입니다. 파이를 나눠 갖기 때문입니다(compositional effect).
- **엄밀한 차등발현(DEG) 분석에는 raw count를 씁니다.** DESeq2 같은 도구는 count의 변동과 시퀀싱 깊이를 모델 안에서 직접 다루므로 raw count를 요구합니다.
- **정리하면 쓰임이 갈립니다**: 한 샘플을 레퍼런스 분포와 견주거나(아웃라이어 탐색), 유전자 간·샘플 간 발현을 *눈으로* 비교할 때는 **TPM**. 여러 샘플의 *통계적 차등발현*을 검정할 때는 **raw count + 전용 툴**.

차등발현이 반복 샘플의 변동을 다루는 원리는 [여러 샘플의 차등발현 검정](/lessons/bulk-rna-deg/)에서, 음이항분포와 다중검정은 [통계적 검정과 다중검정](/reference/statistical-testing/)에서 설명합니다.

같은 정렬 결과에서 [RSEM으로 TPM을 만드는 단계](/practice/rna-seq-from-fastq/#3-4-rsem으로-tpm-계산)와 [featureCounts로 raw count를 만드는 단계](/practice/rna-seq-from-fastq/#3-5-featurecounts로-raw-count-만들기)를 나란히 실행하면 두 지표의 쓰임 차이를 파일 수준에서 확인할 수 있습니다.

## 정리: 지표 한눈 비교

| 지표 | 길이 보정 | 뎁스 보정 | 샘플 내 유전자 비교 | 샘플 간 비교 | 주 쓰임 |
| --- | :---: | :---: | :---: | :---: | --- |
| **raw count** | ✗ | ✗ | ✗ | ✗ | DEG 도구(DESeq2/edgeR) 입력 |
| **CPM** | ✗ | ✓ | ✗ | △ | 대략적 뎁스 보정 |
| **RPK** | ✓ | ✗ | ✓ | ✗ | (중간 단계) |
| **RPKM/FPKM** | ✓ | ✓ | ✓ | △ (합이 달라짐) | 과거 표준, 지금은 지양 |
| **TPM** | ✓ | ✓ | ✓ | ✓ (Σ=10⁶) | 발현량 비교·레퍼런스 대조 |

다음 [유전자가 함께 만드는 작업 흐름](/lessons/pathway/)에서는 여러 유전자가 연결되어 하나의 세포 반응을 만드는 방식을 배웁니다. 이렇게 만든 TPM 행렬의 실제 활용은 [한 샘플에서 아웃라이어 유전자를 찾는 과정](/lessons/rna-seq-analysis/)에서 이어집니다.

---

### 출처

- Wagner et al., *"Measurement of mRNA abundance using RNA-seq data: RPKM measure is inconsistent among samples"*, Theory in Biosciences 2012: [DOI](https://doi.org/10.1007/s12064-012-0162-3)
- Lior Pachter, *"Models for transcript quantification from RNA-Seq"* (TPM 정의): [arXiv:1104.3889](https://arxiv.org/abs/1104.3889)
- [RSEM](https://github.com/deweylab/RSEM) · [featureCounts (Subread)](https://subread.sourceforge.net/) · [DESeq2](https://bioconductor.org/packages/DESeq2/)