<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://loremipsum0116.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://loremipsum0116.github.io/" rel="alternate" type="text/html" /><updated>2026-05-22T13:14:25+00:00</updated><id>https://loremipsum0116.github.io/feed.xml</id><title type="html">Bio Tech Blog</title><subtitle>바이오인포매틱스를 공부하며 의생명정보학 학습 내용을 정리하는 공간입니다.</subtitle><author><name>현석</name></author><entry xml:lang="ko"><title type="html">부록 E24: 대규모 데이터 처리와 재현성</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-24/" rel="alternate" type="text/html" title="부록 E24: 대규모 데이터 처리와 재현성" /><published>2026-05-19T16:37:00+00:00</published><updated>2026-05-19T16:37:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-24</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-24/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-24.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 대규모 데이터 처리와 재현성(reproducibility)을 배웁니다. 재현성은 같은 데이터와 같은 절차를 사용했을 때 같은 결과를 다시 얻을 수 있는 성질입니다. 생물정보학에서 재현성은 선택이 아니라 연구 신뢰도의 핵심입니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>원본 데이터(raw data): 분석을 시작하기 전의 최초 데이터입니다.</li>
  <li>처리 데이터(processed data): 원본 데이터를 정리하거나 변환해 만든 데이터입니다.</li>
  <li>메타데이터(metadata): 데이터에 대한 설명 정보입니다. 예를 들어 샘플 ID, 조직, 조건, 날짜가 있습니다.</li>
  <li>데이터 출처(provenance): 데이터가 어디서 왔고 어떤 과정을 거쳐 만들어졌는지에 대한 기록입니다.</li>
  <li>체크섬(checksum): 파일이 바뀌지 않았는지 확인하는 짧은 계산값입니다.</li>
  <li>압축(compression): 파일 크기를 줄이는 방식입니다.</li>
  <li>스트리밍(streaming): 큰 파일을 한 번에 다 올리지 않고 조금씩 읽는 방식입니다.</li>
  <li>병렬 처리(parallel processing): 여러 작업을 동시에 실행하는 방식입니다.</li>
  <li>무작위 시드(random seed): 무작위 계산을 다시 같은 방식으로 만들기 위해 고정하는 값입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-24-large-scale-data-reproducibility.svg" alt="대규모 데이터 처리와 재현성" /></p>

<h2 id="가장-쉬운-비유-요리-대회-재현">가장 쉬운 비유: 요리 대회 재현</h2>

<p>어떤 요리가 맛있게 나왔는데 레시피, 재료 출처, 조리 시간, 오븐 온도를 아무것도 기록하지 않았다면 다시 만들기 어렵습니다. 분석도 같습니다. 결과 그림이 좋아 보여도 데이터, 코드, 환경, 실행 순서가 기록되지 않으면 믿기 어렵습니다.</p>

<h2 id="원본-데이터는-함부로-덮어쓰지-않습니다">원본 데이터는 함부로 덮어쓰지 않습니다</h2>

<p>원본 데이터는 실험이나 공개 데이터베이스에서 받은 최초 상태입니다. 이 파일은 되도록 읽기 전용처럼 다루는 것이 좋습니다.</p>

<pre><code class="language-txt">data/raw/        원본 데이터
data/processed/  처리 데이터
results/         분석 결과
scripts/         분석 코드
reports/         보고서
</code></pre>

<p>원본을 직접 수정하면 나중에 어떤 처리를 했는지 추적하기 어렵습니다. 처리 결과는 별도 폴더에 저장하는 습관이 좋습니다.</p>

<h2 id="메타데이터-없이는-데이터가-반쪽입니다">메타데이터 없이는 데이터가 반쪽입니다</h2>

<p>유전자 발현 행렬만 있으면 숫자는 많지만 의미가 부족합니다. 샘플이 암인지 정상인지, 어떤 조직인지, 어떤 배치에서 실험했는지, 어떤 날짜에 만들어졌는지 알아야 해석할 수 있습니다.</p>

<pre><code class="language-txt">sample_id   condition   tissue   batch
S1          normal      liver    B1
S2          cancer      liver    B1
S3          cancer      lung     B2
</code></pre>

<p>메타데이터가 부정확하면 좋은 알고리즘도 잘못된 결론을 낼 수 있습니다.</p>

<h2 id="체크섬은-파일-지문입니다">체크섬은 파일 지문입니다</h2>

<p>큰 파일은 전송 중 깨지거나 잘못 복사될 수 있습니다. 체크섬은 파일 내용으로 계산한 짧은 값입니다. 파일이 조금만 바뀌어도 체크섬이 달라집니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">md5sum </span>sample.fastq.gz
</code></pre></div></div>

<p>이 명령은 파일의 MD5 체크섬을 계산합니다. 공개 데이터베이스에서 제공하는 체크섬과 비교하면 파일이 제대로 내려받아졌는지 확인할 수 있습니다.</p>

<h2 id="큰-파일은-조금씩-읽어야-합니다">큰 파일은 조금씩 읽어야 합니다</h2>

<p>작은 CSV는 한 번에 메모리에 올려도 됩니다. 하지만 수십 GB 파일을 한 번에 읽으려 하면 메모리가 터질 수 있습니다. 이때는 스트리밍이나 청크(chunk) 단위 처리가 필요합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>

<span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">"</span><span class="s">large_table.csv</span><span class="sh">"</span><span class="p">,</span> <span class="n">chunksize</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">chunk</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
</code></pre></div></div>

<p>이 코드는 큰 CSV를 1만 행씩 나눠 읽습니다. 한 번에 전체 파일을 올리지 않으므로 메모리 부담이 줄어듭니다.</p>

<h2 id="병렬-처리는-빠르지만-조심해야-합니다">병렬 처리는 빠르지만 조심해야 합니다</h2>

<p>샘플 100개를 하나씩 처리하면 오래 걸립니다. 여러 CPU를 사용하면 동시에 처리할 수 있습니다. 하지만 병렬 처리는 자원 사용량을 늘립니다. CPU를 16개 쓴다고 항상 16배 빨라지는 것은 아니고, 메모리나 디스크 속도가 병목이 될 수 있습니다.</p>

<pre><code class="language-txt">빠른 실행 = CPU 수만의 문제가 아님
메모리, 디스크, 네트워크, 알고리즘도 함께 중요함
</code></pre>

<h2 id="무작위-시드는-결과-재현에-중요합니다">무작위 시드는 결과 재현에 중요합니다</h2>

<p>클러스터링, 머신러닝, 데이터 분할에는 무작위성이 들어갈 수 있습니다. 같은 코드를 실행했는데 결과가 조금씩 달라질 수 있습니다. 이때 무작위 시드를 고정하면 같은 무작위 선택을 다시 만들 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="nf">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</code></pre></div></div>

<p>시드는 마법이 아닙니다. 모든 도구와 모든 병렬 환경에서 완벽한 재현을 보장하지는 않지만, 재현성을 높이는 기본 습관입니다.</p>

<h2 id="재현-가능한-분석의-최소-구성">재현 가능한 분석의 최소 구성</h2>

<p>재현 가능한 분석에는 최소한 다음이 필요합니다.</p>

<pre><code class="language-txt">1. 원본 데이터 위치와 버전
2. 메타데이터
3. 분석 코드
4. 실행 환경
5. 파이프라인 또는 실행 순서
6. 주요 파라미터
7. 결과 파일과 보고서
</code></pre>

<p>이 중 하나라도 빠지면 나중에 같은 결과를 만들기 어려울 수 있습니다.</p>

<h2 id="노트북과-스크립트의-균형">노트북과 스크립트의 균형</h2>

<p>주피터 노트북은 탐색과 설명에 좋습니다. 하지만 실행 순서가 꼬이면 숨은 상태 때문에 결과가 달라질 수 있습니다. 중요한 최종 분석은 스크립트나 워크플로우로 정리하고, 노트북은 보고서나 탐색용으로 쓰는 것이 안전합니다.</p>

<h2 id="실전-보강-chunk와-메모리-계산">실전 보강: chunk와 메모리 계산</h2>

<p>큰 파일을 한 번에 메모리에 올리면 실패할 수 있습니다. 그래서 일정한 크기로 나눠 읽습니다.</p>

<pre><code class="language-txt">전체 파일 크기 = 50GB
한 번에 읽는 chunk = 1GB
필요한 chunk 수 = 50 / 1 = 50개
</code></pre>

<p>chunk 처리는 느려 보일 수 있지만, 메모리 부족으로 작업이 죽는 것보다 훨씬 안전합니다. pandas에서도 <code class="language-plaintext highlighter-rouge">chunksize</code>를 사용하면 큰 CSV를 조금씩 읽을 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">"</span><span class="s">large_counts.csv</span><span class="sh">"</span><span class="p">,</span> <span class="n">chunksize</span><span class="o">=</span><span class="mi">100000</span><span class="p">):</span>
    <span class="nf">process</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="실전-보강-병렬-처리와-재현성-체크리스트">실전 보강: 병렬 처리와 재현성 체크리스트</h2>

<p>병렬 처리는 여러 작업을 동시에 실행해 시간을 줄일 수 있습니다. 하지만 각 작업이 4GB 메모리를 쓰고 8개를 동시에 실행하면 필요한 메모리는 대략 다음과 같습니다.</p>

<pre><code class="language-txt">4GB × 8 = 32GB
</code></pre>

<p>CPU만 보고 병렬 수를 늘리면 메모리나 디스크가 먼저 터질 수 있습니다.</p>

<p>재현 가능한 분석을 남기려면 최소한 다음을 기록해야 합니다.</p>

<pre><code class="language-txt">원본 데이터 위치와 checksum
메타데이터와 샘플 제외 기준
코드 버전 또는 commit hash
실행 환경과 패키지 버전
워크플로우/스크립트 실행 명령
주요 파라미터
최종 결과 파일과 생성 날짜
</code></pre>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: random seed만 고정하면 완전 재현된다.</strong> 데이터, 코드, 환경, 병렬 처리 방식도 영향을 줍니다.</li>
  <li><strong>오해 2: 노트북 파일 하나만 있으면 충분하다.</strong> 실행 순서가 꼬이면 위에서 아래로 다시 실행했을 때 다른 결과가 나올 수 있습니다.</li>
  <li><strong>오해 3: raw data를 조금 고쳐도 괜찮다.</strong> 원본을 덮어쓰면 추적이 끊깁니다.</li>
  <li><strong>오해 4: checksum은 보안 전문가용이다.</strong> 큰 생물정보학 파일이 제대로 복사됐는지 확인하는 기본 도구입니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>이 장은 부록 E의 마무리입니다. E19 Git은 코드 기록, E20 환경관리는 실행 조건, E21 워크플로우는 절차, E22 HPC/클라우드는 계산 자원, E23 소프트웨어 공학은 안전한 코드 작성으로 이어집니다. 이 요소들이 함께 있어야 분석 결과를 다시 만들 수 있습니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>생물정보학 연구는 데이터가 크고 단계가 많으며, 도구 버전도 자주 바뀝니다. 재현성 없이는 결과를 검증하기 어렵고, 논문 심사나 협업에서도 신뢰를 얻기 어렵습니다. 재현성은 연구자의 양심이 아니라 연구 방법의 일부입니다.</p>

<h2 id="어려운-개념-보강-재현성-최소-세트와-provenance를-점검표로-만들기">어려운 개념 보강: 재현성 최소 세트와 provenance를 점검표로 만들기</h2>

<p>재현성은 “코드를 저장했다”만으로 충분하지 않습니다. 같은 결과를 다시 만들려면 최소한 데이터, 코드, 환경, 실행 순서, 결과 해석 기록이 함께 있어야 합니다.</p>

<p>가장 작은 재현성 세트는 다음처럼 생각할 수 있습니다.</p>

<pre><code class="language-txt">1. 원본 데이터 위치와 체크섬
2. metadata table
3. 실행 코드 또는 workflow 파일
4. environment.yml 또는 Dockerfile
5. 실행 명령어와 실행 날짜
6. 주요 결과 파일
7. README 또는 재현성 보고서
</code></pre>

<p>provenance는 데이터의 이력입니다. 즉, “이 파일이 어디서 와서 어떤 과정을 거쳐 만들어졌는가”를 기록하는 것입니다.</p>

<pre><code class="language-txt">raw FASTQ
→ trimming
→ trimmed FASTQ
→ alignment
→ BAM
→ counting
→ count matrix
→ differential expression
→ result table
</code></pre>

<p>각 화살표마다 사용한 도구, 버전, 명령어, 입력 파일, 출력 파일이 기록되어야 나중에 추적할 수 있습니다.</p>

<p>체크섬은 파일의 지문입니다. 예를 들어 원본 FASTQ를 내려받은 직후 체크섬을 기록해 두면, 나중에 파일이 바뀌었는지 확인할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sha256sum </span>sample.fastq.gz <span class="o">&gt;</span> checksums.txt
<span class="nb">sha256sum</span> <span class="nt">-c</span> checksums.txt
</code></pre></div></div>

<p>첫 줄은 현재 파일의 체크섬을 저장하고, 둘째 줄은 나중에 같은 파일인지 검사합니다. MD5도 많이 쓰이지만, 새로 기록할 때는 SHA-256처럼 더 강한 해시를 쓰는 편이 좋습니다.</p>

<p>README에는 사람이 다시 실행할 수 있는 문장이 있어야 합니다. 예를 들어 다음 질문에 답해야 합니다.</p>

<pre><code class="language-txt">이 프로젝트의 목적은 무엇인가?
원본 데이터는 어디에 있는가?
어떤 순서로 실행하는가?
필요한 환경은 어떻게 만드는가?
최종 결과 파일은 무엇인가?
주의해야 할 제한점은 무엇인가?
</code></pre>

<p>흔한 오해는 결과 파일만 잘 보관하면 충분하다고 생각하는 것입니다. 결과 파일은 중요하지만, 원본 데이터와 절차가 없으면 그 결과가 어떻게 만들어졌는지 검증하기 어렵습니다. 생물정보학에서 재현성은 친절한 문서화가 아니라 연구 결과를 믿을 수 있게 만드는 안전장치입니다.</p>

<h2 id="미니-실습-블록-checksum과-readme로-분석-재현성-점검하기">미니 실습 블록: checksum과 README로 분석 재현성 점검하기</h2>

<p>이 실습은 <strong>checksum과 README로 분석 재현성 점검하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 원본 파일이 바뀌지 않았는지 확인하고, 다른 사람이 분석을 다시 실행할 수 있게 최소 정보를 남겨야 하기 때문입니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sha256sum </span>data/raw/sample01_R1.fastq.gz <span class="o">&gt;</span> checksums.txt
<span class="nb">sha256sum</span> <span class="nt">-c</span> checksums.txt

<span class="nb">cat</span> <span class="o">&gt;</span> README.md <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
# RNA-seq mini project

## Input
- data/raw/sample01_R1.fastq.gz
- data/raw/metadata.tsv

## Environment
- environment.yml

## Run
snakemake -j 4

## Output
- results/counts.tsv
- figures/pca.png
</span><span class="no">EOF
</span></code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. checksum은 파일 내용의 지문입니다. 같은 파일이면 같은 checksum이 나오고, 한 글자라도 바뀌면 값이 달라집니다. README는 입력, 환경, 실행법, 출력을 설명합니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. 논문 보충자료, 분석 인수인계, 장기 프로젝트 보관에서 필수에 가깝습니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. 결과 파일만 보관하고 원본, 코드, 환경, 실행 명령을 잃어버리면 재현 가능한 분석이라고 보기 어렵습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>대규모 데이터 처리에서는 원본 데이터 보존, 메타데이터 관리, 체크섬 확인, 청크 처리, 병렬 처리, 환경 기록이 중요합니다. 재현 가능한 분석은 데이터, 코드, 환경, 실행 순서, 결과를 함께 남기는 분석입니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-24" /><category term="reproducibility" /><category term="large-data" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E23: 소프트웨어 공학 기초</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-23/" rel="alternate" type="text/html" title="부록 E23: 소프트웨어 공학 기초" /><published>2026-05-19T16:36:00+00:00</published><updated>2026-05-19T16:36:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-23</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-23/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-23.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 소프트웨어 공학(software engineering)의 기초를 배웁니다. 소프트웨어 공학은 코드를 더 읽기 쉽고, 고치기 쉽고, 오류를 찾기 쉽게 만드는 방법입니다. 계산생물학 연구 코드는 처음에는 작은 스크립트로 시작하지만, 시간이 지나면 여러 사람이 쓰는 분석 도구가 될 수 있습니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>함수(function): 특정 일을 수행하도록 묶은 코드 조각입니다.</li>
  <li>모듈(module): 함수나 변수들을 담은 파이썬 파일입니다.</li>
  <li>테스트(test): 코드가 의도대로 작동하는지 확인하는 코드입니다.</li>
  <li>로깅(logging): 프로그램 실행 중 어떤 일이 일어났는지 기록하는 방법입니다.</li>
  <li>예외 처리(exception handling): 오류가 났을 때 프로그램이 어떻게 대응할지 정하는 방식입니다.</li>
  <li>설정(configuration): 파일 경로, 옵션, 임계값처럼 코드 밖에서 바꿀 수 있게 둔 값입니다.</li>
  <li>문서화(documentation): 코드 사용법과 의도를 설명하는 작업입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-23-software-engineering-basic.svg" alt="소프트웨어 공학 기초" /></p>

<h2 id="가장-쉬운-비유-실험실-정리-습관">가장 쉬운 비유: 실험실 정리 습관</h2>

<p>실험실에서 시약병 이름을 안 붙이고, 노트를 안 쓰고, 장비 사용법을 안 남기면 나중에 아무도 실험을 재현할 수 없습니다. 코드도 같습니다. 변수 이름이 엉망이고, 함수가 너무 길고, 테스트가 없으면 나중에 본인도 이해하기 어렵습니다.</p>

<p>좋은 코드는 똑똑해 보이는 코드가 아니라, 다시 읽었을 때 이해되고 안전하게 고칠 수 있는 코드입니다.</p>

<h2 id="함수를-작게-나누기">함수를 작게 나누기</h2>

<p>나쁜 예시는 한 함수가 모든 일을 하는 코드입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">analyze_everything</span><span class="p">():</span>
    <span class="c1"># 파일 읽기, 필터링, 통계, 그림 저장을 모두 여기서 처리
</span>    <span class="k">pass</span>
</code></pre></div></div>

<p>더 나은 방향은 일을 나누는 것입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">load_counts</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">filter_low_counts</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">min_count</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">df</span><span class="p">[</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">count</span><span class="sh">"</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="n">min_count</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">save_result</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
    <span class="n">df</span><span class="p">.</span><span class="nf">to_csv</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">index</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>

<p>함수가 작으면 테스트하기 쉽고, 오류 위치도 찾기 쉽습니다.</p>

<h2 id="이름은-설명입니다">이름은 설명입니다</h2>

<p>변수 이름 <code class="language-plaintext highlighter-rouge">x</code>, <code class="language-plaintext highlighter-rouge">tmp</code>, <code class="language-plaintext highlighter-rouge">data2</code>는 짧지만 의미가 약합니다. 분석 코드에서는 조금 길어도 의미 있는 이름이 좋습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">filtered_counts</span> <span class="o">=</span> <span class="nf">filter_low_counts</span><span class="p">(</span><span class="n">counts</span><span class="p">,</span> <span class="n">min_count</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</code></pre></div></div>

<p>이 이름은 “낮은 count를 걸러낸 결과”라는 뜻을 바로 알려줍니다.</p>

<h2 id="테스트는-작은-안전장치입니다">테스트는 작은 안전장치입니다</h2>

<p>테스트는 코드가 맞게 작동하는지 확인하는 코드입니다. 예를 들어 평균을 계산하는 함수를 만들었다면, 쉬운 입력으로 결과를 확인할 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">mean</span><span class="p">(</span><span class="n">values</span><span class="p">):</span>
    <span class="k">return</span> <span class="nf">sum</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">/</span> <span class="nf">len</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_mean</span><span class="p">():</span>
    <span class="k">assert</span> <span class="nf">mean</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span> <span class="o">==</span> <span class="mi">2</span>
</code></pre></div></div>

<p>생물정보학에서 모든 복잡한 분석을 완벽히 테스트하기는 어렵습니다. 하지만 파일 파싱, 필터링, 간단한 계산 함수는 테스트할 수 있습니다.</p>

<h2 id="로깅은-실행-기록입니다">로깅은 실행 기록입니다</h2>

<p><code class="language-plaintext highlighter-rouge">print</code>도 도움이 되지만, 긴 분석에서는 로깅이 더 좋습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">logging</span>

<span class="n">logging</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Loading count table</span><span class="sh">"</span><span class="p">)</span>
<span class="n">logging</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sh">"</span><span class="s">Filtering low-count genes</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>로그가 있으면 어느 단계까지 실행되었는지, 어디서 실패했는지 찾기 쉽습니다.</p>

<h2 id="오류를-숨기지-말기">오류를 숨기지 말기</h2>

<p>나쁜 예시는 모든 오류를 그냥 무시하는 코드입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="nf">run_analysis</span><span class="p">()</span>
<span class="k">except</span><span class="p">:</span>
    <span class="k">pass</span>
</code></pre></div></div>

<p>이렇게 하면 분석이 실패했는데도 모를 수 있습니다. 오류는 무조건 없애야 하는 것이 아니라, 의미 있게 드러나야 합니다.</p>

<p>더 나은 방향은 오류 메시지와 맥락을 남기는 것입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="nf">run_analysis</span><span class="p">()</span>
<span class="k">except</span> <span class="nb">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="k">raise</span> <span class="nc">FileNotFoundError</span><span class="p">(</span><span class="sh">"</span><span class="s">입력 파일 경로를 확인하세요</span><span class="sh">"</span><span class="p">)</span> <span class="k">from</span> <span class="n">e</span>
</code></pre></div></div>

<h2 id="설정값은-코드에서-분리하기">설정값은 코드에서 분리하기</h2>

<p>파일 경로나 임계값을 코드 안에 여기저기 박아 넣으면 재사용이 어렵습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">MIN_COUNT</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">INPUT_PATH</span> <span class="o">=</span> <span class="sh">"</span><span class="s">data/counts.csv</span><span class="sh">"</span>
</code></pre></div></div>

<p>더 큰 프로젝트에서는 YAML이나 JSON 설정 파일을 사용할 수 있습니다. 이렇게 하면 코드 수정 없이 다른 데이터에 적용할 수 있습니다.</p>

<h2 id="실전-보강-돌아가는-코드와-믿을-수-있는-코드는-다르다">실전 보강: 돌아가는 코드와 믿을 수 있는 코드는 다르다</h2>

<p>생물정보학 코드에서 가장 위험한 말은 “일단 돌아가니까 맞겠지”입니다. 코드는 실행되면서도 잘못된 결과를 만들 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">load_counts</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
    <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">df</span>
</code></pre></div></div>

<p>이 코드는 파일을 읽지만, 필요한 열이 있는지 확인하지 않습니다. 더 안전한 코드는 최소한 입력을 검증합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">load_counts</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
    <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
    <span class="n">required</span> <span class="o">=</span> <span class="p">{</span><span class="sh">"</span><span class="s">gene</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">count</span><span class="sh">"</span><span class="p">}</span>
    <span class="n">missing</span> <span class="o">=</span> <span class="n">required</span> <span class="o">-</span> <span class="nf">set</span><span class="p">(</span><span class="n">df</span><span class="p">.</span><span class="n">columns</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">missing</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">필수 열이 없습니다: </span><span class="si">{</span><span class="n">missing</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">df</span>
</code></pre></div></div>

<h2 id="실전-보강-오류를-숨기면-분석이-조용히-망한다">실전 보강: 오류를 숨기면 분석이 조용히 망한다</h2>

<p>다음 코드는 매우 위험합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="nf">run_analysis</span><span class="p">()</span>
<span class="k">except</span><span class="p">:</span>
    <span class="k">pass</span>
</code></pre></div></div>

<p>오류가 났는데도 아무 일 없었던 것처럼 지나가기 때문입니다. 분석 파이프라인에서는 차라리 명확히 실패하고 로그를 남기는 것이 안전합니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: 예외 처리는 오류를 없애는 기능이다.</strong> 오류를 적절히 보고하고 대응하는 기능입니다.</li>
  <li><strong>오해 2: 테스트는 큰 프로젝트에서만 필요하다.</strong> 작은 함수일수록 테스트하기 쉽고 효과도 큽니다.</li>
  <li><strong>오해 3: print가 있으면 로깅은 필요 없다.</strong> 장기 실행 분석에서는 시간, 단계, 오류 수준을 남기는 logging이 더 적합합니다.</li>
  <li><strong>오해 4: 하드코딩은 빠른 해결이다.</strong> 경로와 임계값이 코드에 박히면 다른 데이터에서 쉽게 깨집니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>소프트웨어 공학은 E13~E22의 모든 분석을 안전하게 만드는 기반입니다. 테스트 가능한 함수, 명확한 로그, 설정 분리는 E21 워크플로우와 E24 재현성의 실제 구현 방식입니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>연구 코드는 논문 그림 하나만 만들고 끝날 수도 있지만, 그 그림을 믿으려면 코드가 정확해야 합니다. 소프트웨어 공학 기본기는 결과를 더 안전하게 만들고, 동료가 코드를 이해하고 재사용할 수 있게 합니다.</p>

<h2 id="미니-실습-블록-작은-함수와-테스트로-fasta-코드-안전하게-만들기">미니 실습 블록: 작은 함수와 테스트로 FASTA 코드 안전하게 만들기</h2>

<p>이 실습은 <strong>작은 함수와 테스트로 FASTA 코드 안전하게 만들기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 분석 코드가 길어질수록 작은 함수와 테스트가 없으면 오류를 찾기 어려워지기 때문입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">gc_content</span><span class="p">(</span><span class="n">seq</span><span class="p">):</span>
    <span class="n">gc</span> <span class="o">=</span> <span class="n">seq</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="sh">"</span><span class="s">G</span><span class="sh">"</span><span class="p">)</span> <span class="o">+</span> <span class="n">seq</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="sh">"</span><span class="s">C</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">gc</span> <span class="o">/</span> <span class="nf">len</span><span class="p">(</span><span class="n">seq</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_gc_content</span><span class="p">():</span>
    <span class="k">assert</span> <span class="nf">gc_content</span><span class="p">(</span><span class="sh">"</span><span class="s">GC</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="mf">1.0</span>
    <span class="k">assert</span> <span class="nf">gc_content</span><span class="p">(</span><span class="sh">"</span><span class="s">AT</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="mf">0.0</span>

<span class="nf">test_gc_content</span><span class="p">()</span>
<span class="nf">print</span><span class="p">(</span><span class="nf">gc_content</span><span class="p">(</span><span class="sh">"</span><span class="s">ATGC</span><span class="sh">"</span><span class="p">))</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">gc_content</code>는 한 가지 일만 하는 작은 함수입니다. <code class="language-plaintext highlighter-rouge">assert</code>는 기대한 결과가 맞는지 확인하는 간단한 테스트입니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. 서열 길이, GC 함량, sample_id 검증 같은 작은 로직을 안전하게 쌓을 때 필요합니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. 빈 서열 <code class="language-plaintext highlighter-rouge">''</code>을 넣으면 <code class="language-plaintext highlighter-rouge">len(seq)</code>가 0이라 나눗셈 오류가 생길 수 있으므로 예외 처리를 고려해야 합니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>좋은 연구 코드는 작고 명확한 함수, 의미 있는 이름, 테스트, 로그, 적절한 오류 처리, 설정 분리를 갖춥니다. 소프트웨어 공학은 거창한 개발자 기술이 아니라, 계산 연구를 덜 위험하게 만드는 습관입니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-23" /><category term="software-engineering" /><category term="testing" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E22: HPC와 클라우드</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-22/" rel="alternate" type="text/html" title="부록 E22: HPC와 클라우드" /><published>2026-05-19T16:35:00+00:00</published><updated>2026-05-19T16:35:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-22</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-22/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-22.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 HPC와 클라우드(cloud)를 배웁니다. HPC는 고성능 컴퓨팅(high-performance computing)의 약자입니다. 많은 CPU, 큰 메모리, 큰 저장공간을 가진 공용 계산 장비라고 생각하면 됩니다. 클라우드는 AWS, Google Cloud, Azure처럼 인터넷으로 서버와 저장공간을 빌려 쓰는 방식입니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>HPC: 연구기관이나 대학에서 운영하는 고성능 계산 클러스터입니다.</li>
  <li>클러스터(cluster): 여러 컴퓨터 노드를 묶어 큰 계산 자원처럼 쓰는 시스템입니다.</li>
  <li>노드(node): 클러스터 안의 개별 서버입니다.</li>
  <li>작업(job): 클러스터에 제출하는 계산 요청입니다.</li>
  <li>스케줄러(scheduler): 여러 사용자의 작업을 줄 세우고 자원을 배정하는 시스템입니다.</li>
  <li>SLURM: HPC에서 자주 쓰이는 작업 스케줄러입니다. 앞으로는 SLURM이라고 부르겠습니다.</li>
  <li>CPU: 일반 계산을 수행하는 장치입니다.</li>
  <li>GPU: 딥러닝처럼 병렬 계산이 많은 작업에 유용한 장치입니다.</li>
  <li>메모리(memory): 프로그램이 실행 중 데이터를 올려 두는 작업 공간입니다.</li>
  <li>walltime: 작업이 실행될 최대 시간 제한입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-22-hpc-cloud.svg" alt="HPC와 클라우드" /></p>

<h2 id="가장-쉬운-비유-개인-책상과-공동-작업장">가장 쉬운 비유: 개인 책상과 공동 작업장</h2>

<p>노트북은 개인 책상입니다. 작은 계산은 충분히 할 수 있습니다. 하지만 유전체 데이터 수십 개를 정렬하거나, 단일세포 데이터 수백만 개를 처리하거나, 딥러닝 모델을 학습하려면 개인 책상으로 부족합니다.</p>

<p>HPC는 대학이나 연구소의 공동 작업장입니다. 여러 사람이 함께 쓰기 때문에 작업을 마음대로 바로 실행하지 않고, 스케줄러에 제출합니다.</p>

<h2 id="slurm-작업-제출의-기본">SLURM 작업 제출의 기본</h2>

<p>SLURM에서는 보통 작업 스크립트를 작성한 뒤 제출합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c">#SBATCH --job-name=rnaseq_qc</span>
<span class="c">#SBATCH --cpus-per-task=4</span>
<span class="c">#SBATCH --mem=16G</span>
<span class="c">#SBATCH --time=02:00:00</span>

fastqc sample.fastq.gz
</code></pre></div></div>

<p>이 스크립트는 CPU 4개, 메모리 16GB, 최대 2시간을 요청하고 <code class="language-plaintext highlighter-rouge">fastqc</code>를 실행합니다. 제출은 다음처럼 합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sbatch job.sh
</code></pre></div></div>

<p>작업 목록은 다음 명령으로 볼 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>squeue
</code></pre></div></div>

<p>작업을 취소할 때는 다음처럼 합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scancel JOB_ID
</code></pre></div></div>

<h2 id="자원-요청은-너무-작아도-너무-커도-문제입니다">자원 요청은 너무 작아도, 너무 커도 문제입니다</h2>

<p>메모리를 너무 적게 요청하면 작업이 중간에 죽을 수 있습니다. 반대로 너무 크게 요청하면 대기 시간이 길어지고 공동 자원을 낭비합니다. 그래서 처음에는 작은 테스트 데이터로 필요한 자원을 대략 확인하고, 실제 분석에서 적절하게 요청하는 습관이 중요합니다.</p>

<p>예를 들어 1개 샘플 FASTQC에는 큰 메모리가 필요하지 않지만, 큰 단일세포 행렬을 처리할 때는 메모리가 많이 필요할 수 있습니다.</p>

<h2 id="클라우드는-빌려-쓰는-계산-자원입니다">클라우드는 빌려 쓰는 계산 자원입니다</h2>

<p>클라우드는 필요한 만큼 서버를 만들고 사용한 만큼 비용을 내는 방식입니다. 장점은 유연성입니다. 갑자기 큰 GPU가 필요하면 빌릴 수 있습니다. 단점은 비용 관리입니다. 서버를 켜 둔 채 잊으면 돈이 계속 나갈 수 있습니다.</p>

<p>클라우드에서 중요한 것은 계산 비용뿐 아니라 저장 비용과 데이터 이동 비용입니다. 유전체 데이터는 크기 때문에 업로드, 다운로드, 장기 저장 비용도 생각해야 합니다.</p>

<h2 id="hpc와-클라우드의-차이">HPC와 클라우드의 차이</h2>

<p>HPC는 보통 기관에서 제공하는 공용 자원입니다. 사용 규칙이 있고, 작업을 줄 서서 실행합니다. 클라우드는 필요한 자원을 직접 빌리는 방식입니다. 자유도는 높지만 비용 책임도 큽니다.</p>

<pre><code class="language-txt">HPC: 연구기관의 공동 계산 자원
클라우드: 인터넷으로 빌려 쓰는 계산 자원
</code></pre>

<p>둘 중 하나만 정답은 아닙니다. 연구실 환경, 데이터 보안, 비용, 분석 규모에 따라 선택합니다.</p>

<h2 id="데이터-보안과-권한">데이터 보안과 권한</h2>

<p>인간 유전체 데이터, 임상 데이터, 환자 정보는 민감할 수 있습니다. HPC나 클라우드에 데이터를 올릴 때는 기관 규정, 접근 권한, 암호화, 데이터 반출 규칙을 확인해야 합니다. 계산을 잘하는 것보다 먼저 데이터를 안전하게 다루는 태도가 중요합니다.</p>

<h2 id="실전-보강-cpu-메모리-walltime을-숫자로-읽기">실전 보강: CPU, 메모리, walltime을 숫자로 읽기</h2>

<p>SLURM 스크립트의 자원 요청은 계산 자원 계약서와 비슷합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#SBATCH --cpus-per-task=8</span>
<span class="c">#SBATCH --mem=32G</span>
<span class="c">#SBATCH --time=04:00:00</span>
</code></pre></div></div>

<p>이 요청은 CPU 8개, 메모리 32GB, 최대 실행 시간 4시간을 뜻합니다. 프로그램이 32GB보다 많은 메모리를 쓰면 작업이 죽을 수 있고, 4시간을 넘으면 아직 계산 중이어도 종료될 수 있습니다.</p>

<p>자원을 무조건 크게 요청하는 것도 답은 아닙니다. 실제로 4GB만 쓰는 작업에 128GB를 요청하면 대기 시간이 길어지고 공동 자원을 낭비합니다.</p>

<h2 id="실전-보강-cpu를-늘려도-항상-빨라지지-않는다">실전 보강: CPU를 늘려도 항상 빨라지지 않는다</h2>

<p>어떤 프로그램은 여러 CPU를 잘 사용하지만, 어떤 프로그램은 CPU 1개만 사용합니다. CPU 8개를 요청해도 프로그램이 병렬 처리를 지원하지 않으면 7개는 놀 수 있습니다.</p>

<p>저장공간도 계산 자원입니다. 샘플 20개가 각각 임시 파일 8GB를 만들면 필요한 임시 저장공간은 다음과 같습니다.</p>

<pre><code class="language-txt">20 × 8GB = 160GB
</code></pre>

<p>중간 파일까지 고려하지 않으면 작업 중간에 디스크가 꽉 차 실패할 수 있습니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: CPU를 많이 요청하면 무조건 빨라진다.</strong> 프로그램이 병렬 처리를 지원해야 합니다.</li>
  <li><strong>오해 2: 메모리 부족과 시간 초과는 같은 문제다.</strong> 하나는 작업 공간 부족, 다른 하나는 제한 시간 초과입니다.</li>
  <li><strong>오해 3: 클라우드는 싸고 무제한이다.</strong> 켜 둔 시간, 저장공간, 데이터 전송에 비용이 듭니다.</li>
  <li><strong>오해 4: GPU는 모든 계산을 빠르게 한다.</strong> 딥러닝처럼 GPU에 맞는 계산에서 특히 유용합니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>HPC와 클라우드는 E13~E15의 대용량 분석, E16의 복잡도, E21 워크플로우 실행과 직접 연결됩니다. 어떤 자원을 요청할지는 알고리즘, 데이터 크기, 워크플로우 구조를 함께 보고 결정해야 합니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>생물정보학 데이터는 점점 커지고 있습니다. 노트북에서 연습은 할 수 있지만, 실제 연구에서는 서버, HPC, 클라우드를 써야 하는 상황이 많습니다. 그래서 작업 제출, 자원 요청, 로그 확인, 비용 관리, 데이터 보안은 연구자의 기본기가 됩니다.</p>

<h2 id="어려운-개념-보강-cpu-메모리-walltime-요청을-계산-감각으로-잡기">어려운 개념 보강: CPU, 메모리, walltime 요청을 계산 감각으로 잡기</h2>

<p>HPC 작업 스크립트에서 자원 요청은 감으로만 쓰면 안 됩니다. 너무 작게 쓰면 작업이 죽고, 너무 크게 쓰면 대기 시간이 길어지거나 공동 자원을 낭비합니다. 입문 단계에서는 “작은 테스트 → 사용량 확인 → 본 작업 요청” 순서로 접근하면 됩니다.</p>

<p>SLURM 예시를 다시 봅시다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#SBATCH --cpus-per-task=4</span>
<span class="c">#SBATCH --mem=16G</span>
<span class="c">#SBATCH --time=02:00:00</span>
</code></pre></div></div>

<p>각 줄의 의미는 다음과 같습니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--cpus-per-task=4</code>: 이 작업 하나에 CPU core 4개를 요청합니다.</li>
  <li><code class="language-plaintext highlighter-rouge">--mem=16G</code>: 작업 전체에 메모리 16GB를 요청합니다.</li>
  <li><code class="language-plaintext highlighter-rouge">--time=02:00:00</code>: 최대 2시간 동안 실행되게 요청합니다.</li>
</ul>

<p>간단한 계산 예시를 봅시다. 샘플 1개를 처리할 때 메모리 3GB 정도가 필요하고, 동시에 샘플 4개를 처리하려고 한다면 최소 감각은 다음과 같습니다.</p>

<pre><code class="language-txt">필요 메모리 ≈ 3GB × 4개 = 12GB
여유분 포함 요청 ≈ 16GB
</code></pre>

<p>CPU도 마찬가지입니다. 어떤 도구가 <code class="language-plaintext highlighter-rouge">-t 8</code> 또는 <code class="language-plaintext highlighter-rouge">--threads 8</code>을 사용한다면, SLURM에도 CPU를 8개 정도 요청해야 합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#SBATCH --cpus-per-task=8</span>
hisat2 <span class="nt">-p</span> 8 <span class="nt">-x</span> genome <span class="nt">-U</span> sample.fastq.gz <span class="nt">-S</span> sample.sam
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">-p 8</code>은 도구에게 8개 스레드를 쓰라고 말하는 것이고, <code class="language-plaintext highlighter-rouge">--cpus-per-task=8</code>은 스케줄러에게 8개 CPU를 배정해 달라고 말하는 것입니다. 둘 중 하나만 맞고 다른 하나가 틀리면 자원 사용이 꼬일 수 있습니다.</p>

<p>walltime은 실제 실행 시간을 예측해 제한을 거는 값입니다. 테스트에서 1개 샘플이 20분 걸렸고 비슷한 샘플 6개를 순차 처리한다면 단순 추정은 다음과 같습니다.</p>

<pre><code class="language-txt">20분 × 6개 = 120분 = 2시간
여유분 포함 요청 = 3시간 정도
</code></pre>

<p>다만 병렬 처리, 파일 입출력, 큐 대기, 서버 상태에 따라 실제 시간은 달라질 수 있습니다. 그래서 처음부터 거대한 전체 작업을 던지기보다 작은 샘플로 자원 사용량을 확인하는 것이 안전합니다.</p>

<h2 id="미니-실습-블록-hpc-job-script로-분석-작업-제출하기">미니 실습 블록: HPC job script로 분석 작업 제출하기</h2>

<p>이 실습은 <strong>HPC job script로 분석 작업 제출하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 대용량 생물정보학 분석은 개인 노트북보다 HPC에서 실행하는 경우가 많고, CPU·메모리·시간 요청이 필요하기 때문입니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c">#SBATCH --job-name=fastqc</span>
<span class="c">#SBATCH --cpus-per-task=4</span>
<span class="c">#SBATCH --mem=8G</span>
<span class="c">#SBATCH --time=02:00:00</span>
<span class="c">#SBATCH --output=logs/fastqc_%j.out</span>

<span class="nb">mkdir</span> <span class="nt">-p</span> results/fastqc logs
fastqc data/raw/sample01_R1.fastq.gz <span class="nt">-o</span> results/fastqc
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">#SBATCH</code> 줄은 스케줄러에게 필요한 자원을 알려 줍니다. <code class="language-plaintext highlighter-rouge">%j</code>는 job ID를 파일명에 넣어 로그가 덮어써지지 않게 합니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. FASTQC, alignment, variant calling처럼 오래 걸리는 작업을 서버 큐에 제출할 때 씁니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. 메모리를 너무 작게 요청하면 작업이 죽고, 너무 크게 요청하면 대기 시간이 길어질 수 있습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>HPC는 기관의 고성능 계산 클러스터이고, 클라우드는 인터넷으로 빌려 쓰는 계산 자원입니다. SLURM은 HPC 작업을 제출하고 관리하는 대표 스케줄러입니다. 큰 분석에서는 CPU, GPU, 메모리, 시간, 저장공간을 명확히 이해해야 합니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-22" /><category term="hpc" /><category term="cloud" /><category term="slurm" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E21: 워크플로우 관리</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-21/" rel="alternate" type="text/html" title="부록 E21: 워크플로우 관리" /><published>2026-05-19T16:34:00+00:00</published><updated>2026-05-19T16:34:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-21</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-21/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-21.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 워크플로우 관리(workflow management)를 배웁니다. 워크플로우는 여러 분석 단계를 정해진 순서와 의존성에 따라 자동으로 실행하는 구조입니다. 생물정보학 분석은 보통 한 번의 코드 실행으로 끝나지 않습니다. 품질관리, trimming, 정렬, 정량, 통계분석, 그림 생성이 줄줄이 이어집니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>워크플로우(workflow): 여러 분석 단계를 연결한 전체 흐름입니다.</li>
  <li>파이프라인(pipeline): 입력 데이터가 여러 처리 단계를 지나 결과로 바뀌는 과정입니다.</li>
  <li>규칙(rule): 어떤 입력 파일을 받아 어떤 출력 파일을 만들지 정의한 단위입니다.</li>
  <li>의존성(dependency): 어떤 결과를 만들기 전에 먼저 필요한 파일이나 단계입니다.</li>
  <li>DAG(directed acyclic graph): 순환이 없는 방향 그래프입니다. 워크플로우의 실행 순서를 표현할 때 씁니다.</li>
  <li>Snakemake: 규칙 기반 워크플로우 도구입니다. 앞으로는 Snakemake라고 부르겠습니다.</li>
  <li>Nextflow: 대규모 파이프라인과 컨테이너 실행에 강한 워크플로우 도구입니다. 앞으로는 Nextflow라고 부르겠습니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-21-workflow-management.svg" alt="워크플로우 관리" /></p>

<h2 id="가장-쉬운-비유-자동-조립-라인">가장 쉬운 비유: 자동 조립 라인</h2>

<p>공장에서 부품을 조립할 때 순서가 있습니다. 바퀴를 달기 전에 차체가 있어야 하고, 도색 전에 표면 처리가 필요합니다. 생물정보학 분석도 비슷합니다. BAM 파일을 만들기 전에 FASTQ 정렬이 필요하고, 변이 검출 전에 정렬 결과가 필요합니다.</p>

<p>워크플로우 도구는 이 순서를 기억하고 필요한 단계만 자동으로 실행합니다.</p>

<h2 id="왜-그냥-bash-스크립트로-부족할-수-있는가">왜 그냥 bash 스크립트로 부족할 수 있는가</h2>

<p>간단한 분석은 bash 스크립트로도 가능합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fastqc sample.fastq.gz
hisat2 <span class="nt">-x</span> genome <span class="nt">-U</span> sample.fastq.gz <span class="nt">-S</span> sample.sam
samtools <span class="nb">sort </span>sample.sam <span class="nt">-o</span> sample.bam
</code></pre></div></div>

<p>하지만 샘플이 100개가 되고, 중간에 일부 단계가 실패하고, 몇 개 파일만 다시 만들어야 한다면 복잡해집니다. 워크플로우 도구는 이미 만들어진 파일은 건너뛰고, 필요한 출력이 없는 단계만 다시 실행할 수 있습니다.</p>

<h2 id="snakemake의-기본-생각">Snakemake의 기본 생각</h2>

<p>Snakemake는 “이 출력 파일을 만들려면 어떤 입력 파일과 명령이 필요한가?”를 규칙으로 적습니다.</p>

<pre><code class="language-snakefile">rule sort_bam:
    input:
        "mapped/sample.sam"
    output:
        "mapped/sample.sorted.bam"
    shell:
        "samtools sort {input} -o {output}"
</code></pre>

<p>이 규칙은 <code class="language-plaintext highlighter-rouge">mapped/sample.sam</code>을 입력으로 받아 <code class="language-plaintext highlighter-rouge">mapped/sample.sorted.bam</code>을 만든다는 뜻입니다. Snakemake는 출력 파일이 필요한지 보고, 필요하면 shell 명령을 실행합니다.</p>

<h2 id="여러-샘플을-다루는-방식">여러 샘플을 다루는 방식</h2>

<p>생물정보학은 여러 샘플을 반복 처리하는 일이 많습니다. 예를 들어 sample1, sample2, sample3이 있다면 같은 규칙을 샘플마다 적용해야 합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SAMPLES</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">sample1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">sample2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">sample3</span><span class="sh">"</span><span class="p">]</span>
</code></pre></div></div>

<p>워크플로우에서는 샘플 이름 목록과 파일 이름 패턴을 이용해 반복 처리를 자동화합니다. 사람이 명령어를 100번 복사해 붙이는 것보다 오류가 훨씬 적습니다.</p>

<h2 id="dag는-실행-순서-지도입니다">DAG는 실행 순서 지도입니다</h2>

<p>DAG는 분석 단계의 의존성을 그림으로 보여줍니다.</p>

<pre><code class="language-txt">FASTQ → QC
FASTQ → 정렬 → BAM 정렬 → count
count + metadata → 통계분석 → 그림
</code></pre>

<p>화살표는 “앞 단계가 끝나야 뒤 단계가 가능하다”는 뜻입니다. DAG가 있으면 어떤 단계가 먼저 실행되어야 하는지 명확해집니다.</p>

<h2 id="로그와-실패-처리">로그와 실패 처리</h2>

<p>긴 분석에서는 실패가 자연스럽게 생깁니다. 파일 경로가 틀릴 수도 있고, 메모리가 부족할 수도 있고, 입력 파일이 깨졌을 수도 있습니다. 그래서 각 단계의 로그를 남기는 것이 중요합니다.</p>

<pre><code class="language-snakefile">log:
    "logs/sort_bam.log"
</code></pre>

<p>로그는 나중에 오류 원인을 찾는 단서입니다. 좋은 파이프라인은 결과만 만드는 것이 아니라, 실패했을 때 어디서 왜 실패했는지 추적할 수 있어야 합니다.</p>

<h2 id="설정-파일을-분리하기">설정 파일을 분리하기</h2>

<p>파이프라인 코드 안에 샘플 이름, 경로, 옵션을 모두 박아 넣으면 재사용이 어렵습니다. 설정 파일을 따로 두면 같은 코드로 다른 프로젝트를 처리할 수 있습니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">reference</span><span class="pi">:</span> <span class="s">data/reference.fa</span>
<span class="na">samples</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">sample1</span>
  <span class="pi">-</span> <span class="s">sample2</span>
<span class="na">threads</span><span class="pi">:</span> <span class="m">8</span>
</code></pre></div></div>

<p>설정과 코드를 분리하면 파이프라인을 더 깨끗하게 관리할 수 있습니다.</p>

<h2 id="실전-보강-wildcard와-샘플-확장">실전 보강: wildcard와 샘플 확장</h2>

<p>워크플로우 도구의 힘은 같은 규칙을 여러 샘플에 안전하게 반복하는 데 있습니다.</p>

<pre><code class="language-snakefile">SAMPLES = ["S1", "S2", "S3"]

rule sort_bam:
    input:
        "mapped/{sample}.sam"
    output:
        "mapped/{sample}.sorted.bam"
    shell:
        "samtools sort {input} -o {output}"
</code></pre>

<p>여기서 <code class="language-plaintext highlighter-rouge">{sample}</code>은 wildcard입니다. <code class="language-plaintext highlighter-rouge">S1</code>, <code class="language-plaintext highlighter-rouge">S2</code>, <code class="language-plaintext highlighter-rouge">S3</code>에 대해 각각 같은 규칙이 적용됩니다. 샘플이 3개면 sorted BAM도 3개 만들어집니다. 샘플이 100개여도 규칙을 100번 복사하지 않습니다.</p>

<h2 id="실전-보강-dag와-실패한-단계만-다시-실행하기">실전 보강: DAG와 실패한 단계만 다시 실행하기</h2>

<p>워크플로우는 출력 파일 사이의 의존성을 보고 DAG를 만듭니다.</p>

<pre><code class="language-txt">FASTQ → QC
FASTQ → alignment → sorted BAM → variant calling → VCF
</code></pre>

<p>만약 variant calling 단계만 실패했다면 FASTQ QC와 alignment를 처음부터 다시 할 필요가 없습니다. 이미 필요한 중간 파일이 있고 변경되지 않았다면 실패한 단계부터 다시 실행할 수 있습니다. 이것이 긴 생물정보학 파이프라인에서 매우 중요합니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: 워크플로우는 bash를 길게 쓴 것과 같다.</strong> 의존성 추적, 재실행 판단, 로그 관리가 핵심 차이입니다.</li>
  <li><strong>오해 2: 출력 파일 이름만 맞으면 된다.</strong> input/output 관계가 잘못되면 엉뚱한 파일로 분석할 수 있습니다.</li>
  <li><strong>오해 3: DAG에 순환이 있어도 괜찮다.</strong> A가 B를 필요로 하고 B가 A를 필요로 하면 시작할 수 없습니다.</li>
  <li><strong>오해 4: config 파일은 귀찮은 장식이다.</strong> 샘플 목록과 파라미터를 코드에서 분리해야 재사용과 재현이 쉬워집니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>워크플로우는 E13 RNA-seq, E14 변이 분석, E15 single-cell 분석을 실제로 여러 샘플에 반복 적용하는 방법입니다. 실행 환경은 E20, 계산 자원은 E22, 결과 재현은 E24와 이어집니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>연구 결과는 한 번 눌러서 나온 것이 아닙니다. 수많은 중간 파일과 처리 단계가 결과를 만듭니다. 워크플로우 관리는 이 과정을 코드로 기록해, 다시 실행하고 검증할 수 있게 만듭니다.</p>

<h2 id="어려운-개념-보강-snakemake-rule의-inputoutput을-읽는-법">어려운 개념 보강: Snakemake rule의 input/output을 읽는 법</h2>

<p>Snakemake에서 초보자가 가장 많이 틀리는 부분은 <code class="language-plaintext highlighter-rouge">input</code>, <code class="language-plaintext highlighter-rouge">output</code>, <code class="language-plaintext highlighter-rouge">shell</code>의 관계입니다. rule은 “출력 파일을 만들기 위한 약속”입니다. 사람이 명령을 위에서 아래로 실행한다고 생각하기보다, Snakemake가 필요한 출력 파일을 보고 거꾸로 필요한 입력을 찾아간다고 이해해야 합니다.</p>

<pre><code class="language-snakefile">rule sort_bam:
    input:
        "mapped/{sample}.sam"
    output:
        "mapped/{sample}.sorted.bam"
    shell:
        "samtools sort {input} -o {output}"
</code></pre>

<p>각 요소의 의미는 다음과 같습니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">rule sort_bam</code>: 규칙 이름입니다. 사람이 알아보기 위한 이름입니다.</li>
  <li><code class="language-plaintext highlighter-rouge">input</code>: 이 결과를 만들기 전에 있어야 하는 파일입니다.</li>
  <li><code class="language-plaintext highlighter-rouge">output</code>: 이 규칙이 만들어야 하는 파일입니다.</li>
  <li><code class="language-plaintext highlighter-rouge">{sample}</code>: wildcard입니다. <code class="language-plaintext highlighter-rouge">S1</code>, <code class="language-plaintext highlighter-rouge">S2</code> 같은 샘플 이름이 들어갈 자리입니다.</li>
  <li><code class="language-plaintext highlighter-rouge">shell</code>: 실제로 실행할 명령입니다.</li>
</ul>

<p>예를 들어 최종 목표가 <code class="language-plaintext highlighter-rouge">mapped/S1.sorted.bam</code>이면 Snakemake는 <code class="language-plaintext highlighter-rouge">{sample}</code>을 <code class="language-plaintext highlighter-rouge">S1</code>로 채워서 다음 관계를 만듭니다.</p>

<pre><code class="language-txt">input  = mapped/S1.sam
output = mapped/S1.sorted.bam
shell  = samtools sort mapped/S1.sam -o mapped/S1.sorted.bam
</code></pre>

<p>흔한 오류는 shell 명령이 output과 다른 파일을 만드는 경우입니다.</p>

<pre><code class="language-snakefile">rule bad_sort:
    input:
        "mapped/{sample}.sam"
    output:
        "mapped/{sample}.sorted.bam"
    shell:
        "samtools sort {input} -o result.bam"
</code></pre>

<p>이 규칙은 <code class="language-plaintext highlighter-rouge">output</code>에 <code class="language-plaintext highlighter-rouge">mapped/{sample}.sorted.bam</code>을 적어 놓고 실제 명령은 <code class="language-plaintext highlighter-rouge">result.bam</code>을 만듭니다. 그러면 Snakemake 입장에서는 약속한 output이 생기지 않은 것입니다. 워크플로우에서는 파일 이름 약속이 곧 논리입니다.</p>

<p>DAG는 이 약속들의 연결입니다. 어떤 rule의 output이 다른 rule의 input이 되면 두 단계가 연결됩니다. 그래서 workflow를 설계할 때는 “명령어를 어떤 순서로 쓸까?”보다 “각 단계가 어떤 파일을 받아 어떤 파일을 만드는가?”를 먼저 생각하는 편이 안전합니다.</p>

<h2 id="미니-실습-블록-snakemake-rule의-inputoutput-연결-확인하기">미니 실습 블록: Snakemake rule의 input/output 연결 확인하기</h2>

<p>이 실습은 <strong>Snakemake rule의 input/output 연결 확인하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 워크플로우 도구는 파일 의존성을 기준으로 실행 순서를 정하므로 input과 output이 어긋나면 전체 파이프라인이 멈추기 때문입니다.</p>

<pre><code class="language-snakefile">rule count_reads:
    input:
        "data/raw/{sample}.fastq.gz"
    output:
        "results/{sample}.read_count.txt"
    shell:
        "zcat {input} | wc -l &gt; {output}"
</code></pre>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">{sample}</code>은 wildcard입니다. Snakemake는 원하는 output을 만들기 위해 필요한 input을 역으로 찾고, rule 사이의 의존성을 DAG로 구성합니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. 여러 샘플의 QC, trimming, alignment, counting을 자동 반복할 때 필요합니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. shell 명령이 실제로 만드는 파일 경로와 <code class="language-plaintext highlighter-rouge">output</code>에 적힌 경로가 다르면 Snakemake는 결과를 찾지 못합니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>워크플로우 관리는 여러 분석 단계를 의존성에 따라 자동 실행하는 방법입니다. Snakemake와 Nextflow는 대표적인 도구입니다. 좋은 워크플로우는 입력, 출력, 규칙, 로그, 설정 파일을 명확히 남깁니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-21" /><category term="workflow" /><category term="snakemake" /><category term="nextflow" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E20: Conda, Docker, 환경관리</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-20/" rel="alternate" type="text/html" title="부록 E20: Conda, Docker, 환경관리" /><published>2026-05-19T16:33:00+00:00</published><updated>2026-05-19T16:33:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-20</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-20/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-20.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 환경관리(environment management)를 배웁니다. 환경은 코드가 실행될 때 필요한 파이썬 버전, R 버전, 패키지, 외부 도구의 조합입니다. 같은 코드라도 환경이 다르면 결과가 달라지거나 아예 실행되지 않을 수 있습니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>환경(environment): 코드 실행에 필요한 언어, 패키지, 도구, 버전의 묶음입니다.</li>
  <li>패키지(package): 남이 만들어 둔 코드 묶음입니다. 예를 들어 pandas, numpy, scanpy가 있습니다.</li>
  <li>의존성(dependency): 어떤 패키지가 실행되기 위해 필요한 다른 패키지입니다.</li>
  <li>Conda: 독립된 분석 환경을 만들고 패키지를 설치하는 도구입니다. 앞으로는 Conda라고 부르겠습니다.</li>
  <li>가상환경(virtual environment): 프로젝트별로 따로 만든 실행 공간입니다.</li>
  <li>Docker: 실행 환경을 통째로 이미지로 포장하고 컨테이너로 실행하는 도구입니다. 앞으로는 Docker라고 부르겠습니다.</li>
  <li>이미지(image): 실행 환경을 담은 설계도입니다.</li>
  <li>컨테이너(container): 이미지를 실제로 실행한 격리된 공간입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-20-conda-docker-environment.svg" alt="Conda, Docker, 환경관리" /></p>

<h2 id="가장-쉬운-비유-요리-레시피와-주방">가장 쉬운 비유: 요리 레시피와 주방</h2>

<p>코드가 레시피라면 환경은 주방입니다. 레시피가 같아도 오븐 온도, 재료, 도구가 다르면 결과가 달라질 수 있습니다. 생물정보학 분석도 같습니다. 코드가 같아도 <code class="language-plaintext highlighter-rouge">pandas</code> 버전이나 <code class="language-plaintext highlighter-rouge">samtools</code> 버전이 다르면 오류가 나거나 결과 형식이 달라질 수 있습니다.</p>

<h2 id="왜-환경관리가-필요한가">왜 환경관리가 필요한가</h2>

<p>연구실에서 흔한 문제는 이런 것입니다.</p>

<pre><code class="language-txt">내 컴퓨터에서는 됐는데 서버에서는 안 됩니다.
작년에는 돌아갔는데 올해 다시 실행하니 오류가 납니다.
동료가 보낸 코드가 내 환경에서는 패키지 충돌로 실행되지 않습니다.
</code></pre>

<p>이런 문제를 줄이기 위해 환경을 명시적으로 기록해야 합니다.</p>

<h2 id="conda-기본-흐름">Conda 기본 흐름</h2>

<p>Conda로 프로젝트 전용 환경을 만들 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda create <span class="nt">-n</span> rnaseq <span class="nv">python</span><span class="o">=</span>3.11
conda activate rnaseq
conda <span class="nb">install </span>pandas numpy scipy
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">rnaseq</code>는 환경 이름입니다. 이 환경 안에 설치한 패키지는 다른 프로젝트 환경과 섞이지 않습니다.</p>

<p>환경 목록은 다음처럼 볼 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda <span class="nb">env </span>list
</code></pre></div></div>

<p>환경을 나갈 때는 다음 명령을 씁니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda deactivate
</code></pre></div></div>

<h2 id="environmentyml로-환경-기록하기">environment.yml로 환경 기록하기</h2>

<p>환경을 문서로 남기려면 <code class="language-plaintext highlighter-rouge">environment.yml</code> 파일을 만들 수 있습니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">rnaseq</span>
<span class="na">channels</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">conda-forge</span>
  <span class="pi">-</span> <span class="s">bioconda</span>
<span class="na">dependencies</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">python=3.11</span>
  <span class="pi">-</span> <span class="s">pandas</span>
  <span class="pi">-</span> <span class="s">numpy</span>
  <span class="pi">-</span> <span class="s">samtools</span>
</code></pre></div></div>

<p>이 파일이 있으면 다른 사람은 다음 명령으로 비슷한 환경을 만들 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda <span class="nb">env </span>create <span class="nt">-f</span> environment.yml
</code></pre></div></div>

<p>생물정보학에서는 <code class="language-plaintext highlighter-rouge">bioconda</code> 채널이 자주 등장합니다. bioconda는 생물정보학 도구를 Conda로 설치할 수 있게 모아 둔 저장소입니다.</p>

<h2 id="docker는-환경을-통째로-포장합니다">Docker는 환경을 통째로 포장합니다</h2>

<p>Conda가 패키지를 정리하는 도구라면, Docker는 실행 공간을 더 통째로 포장하는 도구에 가깝습니다. Docker 이미지는 “이 분석을 실행하기 위한 작은 컴퓨터 설계도”처럼 생각하면 됩니다.</p>

<p>간단한 Dockerfile은 다음처럼 생길 수 있습니다.</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3.11</span>
<span class="k">RUN </span>pip <span class="nb">install </span>pandas numpy scipy
<span class="k">WORKDIR</span><span class="s"> /analysis</span>
<span class="k">COPY</span><span class="s"> analysis.py .</span>
<span class="k">CMD</span><span class="s"> ["python", "analysis.py"]</span>
</code></pre></div></div>

<p>이 파일은 파이썬 3.11을 바탕으로 패키지를 설치하고 <code class="language-plaintext highlighter-rouge">analysis.py</code>를 실행하는 환경을 정의합니다.</p>

<h2 id="버전-고정이-중요한-이유">버전 고정이 중요한 이유</h2>

<p>패키지는 계속 업데이트됩니다. 업데이트는 좋은 일이지만, 연구 재현성에는 위험이 될 수 있습니다. 예를 들어 어떤 함수의 기본 옵션이 바뀌면 같은 코드도 다른 결과를 낼 수 있습니다.</p>

<p>그래서 중요한 분석에서는 버전을 적어 두는 것이 좋습니다.</p>

<pre><code class="language-txt">python=3.11
pandas=2.2.0
samtools=1.19
</code></pre>

<p>모든 버전을 영원히 고정할 필요는 없지만, 논문 결과나 중요한 보고서를 재현해야 한다면 환경 기록은 필수입니다.</p>

<h2 id="conda와-docker의-차이">Conda와 Docker의 차이</h2>

<p>Conda는 상대적으로 배우기 쉽고 연구실 서버에서 많이 씁니다. Docker는 환경을 더 강하게 격리하지만, 일부 HPC 환경에서는 권한 문제로 바로 쓰기 어려울 수 있습니다. 이때는 Singularity 또는 Apptainer 같은 컨테이너 도구를 쓰기도 합니다. 입문 단계에서는 “Conda는 패키지 환경, Docker는 실행 환경 포장” 정도로 이해하면 충분합니다.</p>

<h2 id="실전-보강-environmentyml만으로-끝이-아닐-수-있다">실전 보강: environment.yml만으로 끝이 아닐 수 있다</h2>

<p><code class="language-plaintext highlighter-rouge">environment.yml</code>은 환경 재현에 큰 도움이 되지만 완벽한 마법은 아닙니다. 같은 패키지 이름이라도 채널, 운영체제, 하위 의존성 버전에 따라 결과가 달라질 수 있습니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">rnaseq</span>
<span class="na">channels</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">conda-forge</span>
  <span class="pi">-</span> <span class="s">bioconda</span>
<span class="na">dependencies</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">python=3.11</span>
  <span class="pi">-</span> <span class="s">pandas=2.2</span>
  <span class="pi">-</span> <span class="s">samtools=1.19</span>
</code></pre></div></div>

<p>이 파일은 “어떤 환경을 만들려고 했는지”를 기록합니다. 더 강한 재현성이 필요하면 lock file, Docker image tag, 실행한 운영체제, 주요 도구 버전까지 함께 남깁니다.</p>

<h2 id="실전-보강-hpc에서는-docker가-바로-안-될-수-있다">실전 보강: HPC에서는 Docker가 바로 안 될 수 있다</h2>

<p>Docker는 편하지만 많은 HPC에서는 보안상 일반 사용자가 Docker를 직접 실행하지 못합니다. 대신 Apptainer/Singularity 같은 컨테이너 도구를 쓰는 경우가 많습니다. 입문 단계에서는 “로컬이나 클라우드에서는 Docker, HPC에서는 Apptainer 계열을 만날 수 있다” 정도로 이해하면 됩니다.</p>

<p>Docker 이미지도 <code class="language-plaintext highlighter-rouge">latest</code> 태그만 쓰면 시간이 지나 이미지 내용이 바뀔 수 있습니다. 가능하면 명시적인 버전 태그를 사용해야 합니다.</p>

<h2 id="숫자-감각-버전-조합이-많아지는-이유">숫자 감각: 버전 조합이 많아지는 이유</h2>

<p>환경관리는 단순히 패키지 이름을 적는 일이 아닙니다. 패키지 5개가 있고 각 패키지가 가능한 버전 3개씩만 있어도 조합은 <code class="language-plaintext highlighter-rouge">3^5 = 243</code>가지가 됩니다.</p>

<pre><code class="language-txt">패키지 5개 × 각 3개 버전 후보 → 3×3×3×3×3 = 243가지 조합
</code></pre>

<p>실제 생물정보학 도구는 여기에 운영체제, 채널, 컴파일 옵션, 외부 바이너리 버전까지 얽힙니다. 그래서 <code class="language-plaintext highlighter-rouge">environment.yml</code>, lock file, Docker/Apptainer 이미지, 도구 버전 기록이 필요합니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: Conda를 쓰면 무조건 재현된다.</strong> 채널과 하위 의존성 차이로 달라질 수 있습니다.</li>
  <li><strong>오해 2: Docker면 운영체제까지 완전히 같은 컴퓨터가 된다.</strong> host 커널, 파일 경로, 권한, GPU 설정 등은 여전히 영향을 줄 수 있습니다.</li>
  <li><strong>오해 3: 패키지 충돌은 내 실력 부족 때문이다.</strong> 생물정보학 도구 의존성은 실제로 복잡합니다.</li>
  <li><strong>오해 4: 버전 기록은 논문 끝나고 해도 된다.</strong> 분석할 때 바로 기록해야 나중에 재현됩니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>환경관리는 E21 워크플로우와 결합될 때 강해집니다. 같은 규칙이 같은 환경에서 실행되어야 결과를 믿을 수 있습니다. E24 재현성의 핵심 구성요소도 코드, 데이터, 환경, 파라미터입니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>생물정보학 분석은 여러 도구를 이어 붙입니다. Python, R, samtools, STAR, DESeq2, scanpy가 한 프로젝트 안에 함께 들어갈 수 있습니다. 환경을 기록하지 않으면 나중에 같은 분석을 다시 실행하기 어렵습니다.</p>

<h2 id="미니-실습-블록-conda-환경과-dockerfile로-실행-환경-기록하기">미니 실습 블록: Conda 환경과 Dockerfile로 실행 환경 기록하기</h2>

<p>이 실습은 <strong>Conda 환경과 Dockerfile로 실행 환경 기록하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 같은 코드도 Python, R, 패키지 버전이 달라지면 실행 결과가 달라지거나 아예 실행되지 않을 수 있기 때문입니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">rnaseq-mini</span>
<span class="na">channels</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">conda-forge</span>
  <span class="pi">-</span> <span class="s">bioconda</span>
<span class="na">dependencies</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">python=3.11</span>
  <span class="pi">-</span> <span class="s">pandas</span>
  <span class="pi">-</span> <span class="s">snakemake</span>
  <span class="pi">-</span> <span class="s">fastqc</span>
  <span class="pi">-</span> <span class="s">multiqc</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">environment.yml</code>은 conda 환경의 이름, 채널, 패키지 목록을 기록합니다. 다른 사람은 이 파일로 비슷한 실행 환경을 만들 수 있습니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. FASTQC, MultiQC, Snakemake, pandas를 한 프로젝트에서 같이 쓸 때 환경을 공유하는 데 필요합니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. 패키지 이름만 있고 버전이 전혀 없으면 시간이 지나며 다른 버전이 설치될 수 있습니다. 중요한 도구는 버전을 고정하거나 보고서에 기록합니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>환경관리는 코드가 같은 조건에서 다시 실행되도록 만드는 작업입니다. Conda는 프로젝트별 패키지 환경을 만들고, Docker는 실행 환경을 통째로 포장합니다. 좋은 연구 코드는 코드뿐 아니라 환경 파일도 함께 남깁니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-20" /><category term="conda" /><category term="docker" /><category term="environment" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E19: Git과 버전관리</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-19/" rel="alternate" type="text/html" title="부록 E19: Git과 버전관리" /><published>2026-05-19T16:32:00+00:00</published><updated>2026-05-19T16:32:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-19</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-19/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-19.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 Git과 버전관리(version control)를 배웁니다. 버전관리는 파일이 언제, 어떻게, 왜 바뀌었는지 기록하는 방법입니다. 생물정보학 연구에서는 코드, 분석 설정, 작은 설명 문서가 계속 바뀝니다. 이 기록이 없으면 나중에 “이 그림은 어떤 코드로 만들었지?”라는 질문에 답하기 어렵습니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>버전관리(version control): 파일의 변경 이력을 기록하고 되돌릴 수 있게 하는 방식입니다.</li>
  <li>Git: 가장 널리 쓰이는 버전관리 도구입니다. 앞으로는 Git이라고 부르겠습니다.</li>
  <li>저장소(repository): Git이 변경 이력을 관리하는 프로젝트 폴더입니다.</li>
  <li>커밋(commit): 특정 시점의 변경 내용을 하나의 기록으로 남긴 것입니다.</li>
  <li>스테이징(staging): 커밋에 넣을 파일을 고르는 중간 단계입니다.</li>
  <li>브랜치(branch): 같은 프로젝트에서 다른 실험 방향을 따로 진행하는 갈래입니다.</li>
  <li>병합(merge): 한 브랜치의 변경 내용을 다른 브랜치에 합치는 일입니다.</li>
  <li>원격 저장소(remote repository): GitHub 같은 외부 서버에 있는 저장소입니다.</li>
  <li>pull request: 다른 사람에게 내 변경 내용을 검토하고 합쳐 달라고 요청하는 방식입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-19-git-version-control.svg" alt="Git과 버전관리" /></p>

<h2 id="가장-쉬운-비유-게임-저장-슬롯">가장 쉬운 비유: 게임 저장 슬롯</h2>

<p>게임을 하다가 중요한 지점마다 저장한다고 생각해 봅시다. 실수하면 이전 저장 지점으로 돌아갈 수 있습니다. Git의 커밋도 비슷합니다. 분석 코드가 잘 작동하는 시점마다 커밋을 남기면, 나중에 코드가 망가져도 과거 기록을 확인할 수 있습니다.</p>

<p>단, Git은 게임 저장보다 더 강력합니다. 단순히 되돌리는 것뿐 아니라, 어떤 줄이 바뀌었는지, 누가 바꿨는지, 왜 바꿨는지 메시지로 남길 수 있습니다.</p>

<h2 id="git의-기본-흐름">Git의 기본 흐름</h2>

<p>Git의 기본 흐름은 다음과 같습니다.</p>

<pre><code class="language-txt">파일 수정
→ git status로 상태 확인
→ git add로 커밋할 파일 선택
→ git commit으로 변경 기록 저장
→ 필요하면 git push로 원격 저장소에 업로드
</code></pre>

<p>아주 작은 프로젝트에서 시작하면 다음처럼 진행할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
git status
git add analysis.py
git commit <span class="nt">-m</span> <span class="s2">"Add first analysis script"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">git init</code>은 현재 폴더를 Git 저장소로 만듭니다. <code class="language-plaintext highlighter-rouge">git status</code>는 어떤 파일이 바뀌었는지 보여줍니다. <code class="language-plaintext highlighter-rouge">git add</code>는 다음 커밋에 넣을 파일을 고릅니다. <code class="language-plaintext highlighter-rouge">git commit</code>은 실제 기록을 남깁니다.</p>

<h2 id="커밋-메시지는-연구-노트입니다">커밋 메시지는 연구 노트입니다</h2>

<p>나쁜 커밋 메시지는 다음처럼 너무 모호합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit <span class="nt">-m</span> <span class="s2">"update"</span>
</code></pre></div></div>

<p>좋은 커밋 메시지는 변경 내용을 짧게 설명합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit <span class="nt">-m</span> <span class="s2">"Filter low-quality cells before PCA"</span>
</code></pre></div></div>

<p>생물정보학에서는 작은 분석 선택 하나가 결과를 바꿀 수 있습니다. 그래서 커밋 메시지는 단순한 코드 기록이 아니라 연구 노트에 가깝습니다.</p>

<h2 id="브랜치는-안전한-실험-공간입니다">브랜치는 안전한 실험 공간입니다</h2>

<p>분석 코드를 바꿔 보고 싶은데 기존 결과를 망가뜨리고 싶지 않을 수 있습니다. 이때 브랜치를 씁니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git branch try-new-normalization
git switch try-new-normalization
</code></pre></div></div>

<p>이제 새 브랜치에서 정규화 방법을 바꿔 보다가 괜찮으면 원래 브랜치에 병합할 수 있습니다. 별로라면 버리면 됩니다. 브랜치는 “실험용 작업대”라고 생각하면 쉽습니다.</p>

<h2 id="diff와-log는-과거를-보는-창입니다">diff와 log는 과거를 보는 창입니다</h2>

<p><code class="language-plaintext highlighter-rouge">git diff</code>는 아직 커밋하지 않은 변경 내용을 보여줍니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git diff
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">git log</code>는 커밋 기록을 보여줍니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git log <span class="nt">--oneline</span>
</code></pre></div></div>

<p>분석 결과가 갑자기 달라졌다면, diff와 log를 확인해서 어떤 코드 변경이 영향을 줬는지 추적할 수 있습니다.</p>

<h2 id="gitignore는-기록하지-않을-파일-목록입니다">.gitignore는 기록하지 않을 파일 목록입니다</h2>

<p>모든 파일을 Git에 넣으면 안 됩니다. 대용량 FASTQ, BAM, 중간 결과 파일, 비밀번호가 들어 있는 설정 파일은 Git에 올리면 위험하거나 비효율적입니다. <code class="language-plaintext highlighter-rouge">.gitignore</code> 파일에는 기록하지 않을 파일 패턴을 적습니다.</p>

<pre><code class="language-txt">data/raw/
results/tmp/
.env
*.bam
</code></pre>

<p>이 예시는 원시 데이터 폴더, 임시 결과 폴더, 비밀 설정 파일, BAM 파일을 Git 추적 대상에서 제외합니다.</p>

<h2 id="github는-백업이자-협업-장소입니다">GitHub는 백업이자 협업 장소입니다</h2>

<p>Git은 내 컴퓨터에서도 쓸 수 있습니다. GitHub는 원격 저장소를 제공하는 서비스입니다. 원격 저장소에 코드를 올리면 다른 컴퓨터에서도 받을 수 있고, 동료와 함께 검토할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add origin https://github.com/user/project.git
git push <span class="nt">-u</span> origin main
</code></pre></div></div>

<p>다만 인간 유전체 원본 데이터, 환자 정보, 개인 식별 정보는 공개 저장소에 올리면 안 됩니다. 코드와 작은 예제 데이터, 문서는 공개할 수 있지만 민감한 데이터는 별도 보안 규칙을 따라야 합니다.</p>

<h2 id="실전-보강-생물정보학에서-git에-올리면-안-되는-것">실전 보강: 생물정보학에서 Git에 올리면 안 되는 것</h2>

<p>Git은 코드와 작은 설정 파일을 추적하는 데 좋지만, 모든 파일을 넣는 도구는 아닙니다. FASTQ, BAM, 큰 HDF5 파일, 환자 개인정보가 들어 있는 원자료는 보통 Git에 올리면 안 됩니다.</p>

<pre><code class="language-txt">Git에 적합: scripts/, Snakefile, README.md, environment.yml, 작은 예제 데이터
Git에 부적합: 수십 GB FASTQ/BAM, 개인식별정보, API key, 비밀번호
</code></pre>

<p>이런 파일은 <code class="language-plaintext highlighter-rouge">.gitignore</code>에 넣어 실수로 커밋되지 않게 해야 합니다.</p>

<h2 id="실전-보강-커밋은-작고-의미-있게">실전 보강: 커밋은 작고 의미 있게</h2>

<p>좋은 커밋은 나중에 분석 과정을 되짚을 수 있는 연구 노트입니다.</p>

<pre><code class="language-txt">나쁜 예: update
좋은 예: Add mitochondrial QC filter for single-cell data
좋은 예: Fix sample_id join in variant annotation table
</code></pre>

<p>한 커밋에 분석 코드 수정, 결과 파일 교체, 환경 변경을 모두 넣으면 나중에 원인을 찾기 어렵습니다. 가능하면 “한 가지 의미 있는 변경”을 하나의 커밋으로 남기는 습관이 좋습니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: GitHub에 올리면 자동 백업이므로 아무거나 올려도 된다.</strong> 민감 데이터와 대용량 원자료는 위험합니다.</li>
  <li><strong>오해 2: commit은 최종 완성본에만 한다.</strong> 작은 단위로 자주 기록해야 되돌리기 쉽습니다.</li>
  <li><strong>오해 3: branch는 고급 개발자만 쓴다.</strong> 분석 실험을 안전하게 분리할 때도 유용합니다.</li>
  <li><strong>오해 4: Git은 데이터베이스 백업 도구다.</strong> Git은 코드 버전관리 도구에 가깝습니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>Git은 E20 환경 파일, E21 워크플로우 파일, E23 테스트 코드, E24 재현성 문서를 함께 관리하는 중심 도구입니다. 단, 실제 대규모 데이터는 별도의 스토리지와 checksum으로 관리해야 합니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>계산생물학 연구는 코드가 곧 실험 과정입니다. 실험실에서 실험 노트를 남기듯이, 계산 연구자는 Git으로 코드의 실험 노트를 남깁니다. Git을 쓰면 분석이 망가졌을 때 되돌릴 수 있고, 논문 그림을 만든 코드의 정확한 버전을 추적할 수 있습니다.</p>

<h2 id="미니-실습-블록-git-commit으로-분석-변경-추적하기">미니 실습 블록: Git commit으로 분석 변경 추적하기</h2>

<p>이 실습은 <strong>Git commit으로 분석 변경 추적하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 분석 코드와 설정이 바뀐 이유를 기록하지 않으면 나중에 어떤 코드로 그림을 만들었는지 알기 어렵기 때문입니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git status
git diff
git add scripts/run_qc.sh README.md
git commit <span class="nt">-m</span> <span class="s2">"Add FASTQ QC script and document input paths"</span>
git log <span class="nt">--oneline</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">git status</code>는 변경 상태, <code class="language-plaintext highlighter-rouge">git diff</code>는 바뀐 내용, <code class="language-plaintext highlighter-rouge">git add</code>는 커밋에 넣을 파일 선택, <code class="language-plaintext highlighter-rouge">git commit</code>은 기록 저장입니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. QC 기준 변경, reference 버전 변경, Snakemake rule 수정 같은 분석 변경을 추적할 때 필요합니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. <code class="language-plaintext highlighter-rouge">update</code>, <code class="language-plaintext highlighter-rouge">fix</code> 같은 메시지만 남기면 나중에 무엇을 왜 바꿨는지 알기 어렵습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>Git은 프로젝트의 변경 이력을 관리하는 도구입니다. 기본 흐름은 수정, add, commit, push입니다. 커밋은 분석 기록이고, 브랜치는 안전한 실험 공간이며, <code class="language-plaintext highlighter-rouge">.gitignore</code>는 기록하지 않을 파일을 정하는 규칙입니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-19" /><category term="git" /><category term="version-control" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E18: API와 데이터 수집</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-18/" rel="alternate" type="text/html" title="부록 E18: API와 데이터 수집" /><published>2026-05-19T16:31:00+00:00</published><updated>2026-05-19T16:31:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-18</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-18/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-18.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 API와 데이터 수집을 배웁니다. API는 프로그램이 다른 프로그램이나 서버에 정해진 방식으로 요청을 보내고 데이터를 받는 통로입니다. 생물정보학에서는 공개 데이터베이스에서 유전자, 단백질, 논문, 변이 정보를 가져올 때 API를 자주 씁니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>API(application programming interface): 프로그램끼리 정해진 규칙으로 데이터를 주고받게 해 주는 통로입니다. 앞으로는 API라고 부르겠습니다.</li>
  <li>요청(request): 데이터를 달라고 서버에 보내는 메시지입니다.</li>
  <li>응답(response): 서버가 돌려주는 결과입니다.</li>
  <li>REST API: 인터넷 주소와 HTTP 방식으로 데이터를 요청하는 API 형태입니다.</li>
  <li>JSON: key-value 구조로 데이터를 표현하는 텍스트 형식입니다.</li>
  <li>XML: 태그를 이용해 데이터를 표현하는 텍스트 형식입니다.</li>
  <li>파싱(parsing): 받은 데이터에서 필요한 정보를 꺼내는 작업입니다.</li>
  <li>rate limit: 서버가 너무 많은 요청을 막기 위해 정한 요청 제한입니다.</li>
  <li>페이지네이션(pagination): 결과가 많을 때 여러 페이지로 나누어 받는 방식입니다.</li>
  <li>캐시(cache): 한 번 받은 데이터를 저장해 두고 다시 쓰는 방식입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-18-api-data-collection.svg" alt="API와 데이터 수집" /></p>

<h2 id="가장-쉬운-비유-식당-주문-창구">가장 쉬운 비유: 식당 주문 창구</h2>

<p>API는 식당 주문 창구와 비슷합니다. 손님은 아무 말이나 하는 것이 아니라 메뉴판의 규칙에 맞춰 주문합니다. “김치찌개 하나 주세요”라고 요청하면 식당은 김치찌개를 줍니다.</p>

<p>데이터 서버도 비슷합니다. 정해진 주소와 규칙에 맞춰 요청하면, 서버는 JSON이나 XML 같은 형식으로 데이터를 돌려줍니다.</p>

<h2 id="url은-데이터-요청-주소입니다">URL은 데이터 요청 주소입니다</h2>

<p>웹사이트 주소처럼 보이는 URL은 데이터 요청 주소가 될 수 있습니다.</p>

<pre><code class="language-txt">https://example.org/api/gene/TP53
</code></pre>

<p>이 주소는 “TP53 유전자 정보를 주세요”라는 요청처럼 설계될 수 있습니다. 실제 API마다 주소 규칙은 다릅니다. 그래서 API 문서를 읽는 습관이 중요합니다.</p>

<h2 id="json-이해하기">JSON 이해하기</h2>

<p>JSON은 파이썬 딕셔너리와 비슷하게 생겼습니다.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"gene"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TP53"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"organism"</span><span class="p">:</span><span class="w"> </span><span class="s2">"human"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"chromosome"</span><span class="p">:</span><span class="w"> </span><span class="s2">"17"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">gene</code>, <code class="language-plaintext highlighter-rouge">organism</code>, <code class="language-plaintext highlighter-rouge">chromosome</code>은 key이고, 오른쪽 값은 value입니다. 파이썬에서는 JSON을 딕셔너리처럼 다룰 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">gene</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">TP53</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">organism</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">human</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">chromosome</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">17</span><span class="sh">"</span>
<span class="p">}</span>

<span class="nf">print</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="sh">"</span><span class="s">gene</span><span class="sh">"</span><span class="p">])</span>
</code></pre></div></div>

<p>결과는 다음과 같습니다.</p>

<pre><code class="language-txt">TP53
</code></pre>

<h2 id="파이썬으로-api-요청하기">파이썬으로 API 요청하기</h2>

<p>파이썬에서는 <code class="language-plaintext highlighter-rouge">requests</code> 라이브러리를 자주 씁니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">requests</span>

<span class="n">url</span> <span class="o">=</span> <span class="sh">"</span><span class="s">https://example.org/api/gene/TP53</span><span class="sh">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span>

<span class="nf">print</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">requests.get(url)</code>은 해당 주소로 요청을 보내는 코드입니다. <code class="language-plaintext highlighter-rouge">response.json()</code>은 응답을 JSON으로 해석해 파이썬 객체로 바꾸는 코드입니다.</p>

<p>실제 생물정보학에서는 NCBI, Ensembl, UniProt 같은 데이터베이스 API를 사용할 수 있습니다. 다만 각 사이트마다 주소와 요청 규칙이 다르므로 공식 문서를 확인해야 합니다.</p>

<h2 id="xml도-만날-수-있습니다">XML도 만날 수 있습니다</h2>

<p>XML은 태그로 데이터를 감싸는 형식입니다.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;gene&gt;</span>
  <span class="nt">&lt;name&gt;</span>TP53<span class="nt">&lt;/name&gt;</span>
  <span class="nt">&lt;organism&gt;</span>human<span class="nt">&lt;/organism&gt;</span>
<span class="nt">&lt;/gene&gt;</span>
</code></pre></div></div>

<p>JSON보다 길어 보이지만, 생물학 데이터베이스에서는 XML 형식도 자주 등장합니다. 입문 단계에서는 “JSON은 딕셔너리처럼, XML은 태그 구조처럼 생겼다” 정도를 알면 됩니다.</p>

<h2 id="rate-limit-너무-빨리-많이-요청하면-안-됩니다">rate limit: 너무 빨리 많이 요청하면 안 됩니다</h2>

<p>공개 데이터베이스 서버는 많은 사람이 함께 씁니다. 짧은 시간에 너무 많은 요청을 보내면 서버에 부담을 줄 수 있습니다. 그래서 API는 보통 rate limit를 둡니다.</p>

<p>좋은 데이터 수집 코드는 다음 태도를 가져야 합니다.</p>

<pre><code class="language-txt">필요한 데이터만 요청하기
요청 사이에 짧은 쉬는 시간 두기
이미 받은 데이터는 캐시하기
오류가 나면 무한 반복하지 않기
API 사용 규칙 확인하기
</code></pre>

<h2 id="페이지네이션">페이지네이션</h2>

<p>검색 결과가 너무 많으면 한 번에 전부 주지 않고 여러 페이지로 나누어 줄 수 있습니다. 이것을 페이지네이션이라고 합니다.</p>

<p>예를 들어 1000개 결과가 있는데 한 번에 100개씩 준다면, 10번 요청해야 모든 결과를 받을 수 있습니다. 이때 다음 페이지 주소나 토큰을 잘 따라가야 합니다.</p>

<h2 id="캐시와-재현성">캐시와 재현성</h2>

<p>API에서 받은 데이터는 시간이 지나면 바뀔 수 있습니다. 유전자 주석이나 단백질 정보가 업데이트될 수 있기 때문입니다. 그래서 중요한 분석에서는 언제, 어떤 API에서, 어떤 조건으로 데이터를 받았는지 기록해야 합니다.</p>

<p>캐시는 같은 요청을 반복하지 않기 위해 받은 결과를 저장하는 방식입니다. 서버 부담도 줄이고, 분석 재현성도 높일 수 있습니다.</p>

<h2 id="실전-보강-api는-항상-정상-응답을-주지-않는다">실전 보강: API는 항상 정상 응답을 주지 않는다</h2>

<p>API 요청은 인터넷을 통해 외부 서버에 질문하는 일입니다. 따라서 실패할 수 있습니다. 상태 코드(status code)는 요청 결과를 알려주는 숫자입니다.</p>

<pre><code class="language-txt">200: 정상 응답
404: 요청한 주소나 자료를 찾을 수 없음
429: 너무 많이 요청해서 제한됨
500: 서버 쪽 오류
</code></pre>

<p>초보자는 <code class="language-plaintext highlighter-rouge">requests.get(url).json()</code>만 쓰고 끝내기 쉽지만, 실제 연구 코드에서는 상태 코드와 timeout을 확인해야 합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
<span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">status_code</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="실전-보강-페이지네이션과-재현성-기록">실전 보강: 페이지네이션과 재현성 기록</h2>

<p>API가 결과 10,000개를 한 번에 주지 않고 100개씩 나눠 줄 수 있습니다. 이것을 페이지네이션이라고 합니다. 첫 페이지만 저장하면 데이터가 일부만 수집됩니다.</p>

<p>또 API 데이터는 시간이 지나면 바뀔 수 있습니다. 그래서 다음 정보를 기록해야 합니다.</p>

<pre><code class="language-txt">요청 URL
쿼리 조건
수집 날짜
API 버전
페이지 수
응답 파일 checksum 또는 저장 경로
</code></pre>

<h2 id="숫자-감각-페이지네이션-요청-수-계산하기">숫자 감각: 페이지네이션 요청 수 계산하기</h2>

<p>API 수집에서도 간단한 계산이 필요합니다. 검색 결과가 10,000개이고 API가 한 번에 100개씩만 돌려준다면 전체를 받으려면 최소 100번 요청해야 합니다.</p>

<pre><code class="language-txt">전체 결과 수 = 10,000개
한 페이지 크기 = 100개
필요 페이지 수 = 10,000 / 100 = 100페이지
</code></pre>

<p>첫 페이지만 저장하면 전체의 1%만 수집한 셈입니다. 그래서 다음 페이지 토큰, 총 결과 수, 실제 저장한 개수를 함께 확인해야 합니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: API에서 받은 데이터는 항상 완전하다.</strong> 페이지네이션, rate limit, 오류 응답 때문에 일부만 받을 수 있습니다.</li>
  <li><strong>오해 2: JSON 구조는 영원히 같다.</strong> API 버전이 바뀌면 key 이름이나 구조가 바뀔 수 있습니다.</li>
  <li><strong>오해 3: 429가 뜨면 더 빠르게 재시도하면 된다.</strong> rate limit에 걸렸다는 뜻이므로 기다리거나 요청 속도를 줄여야 합니다.</li>
  <li><strong>오해 4: 캐시는 게으른 방식이다.</strong> 같은 요청을 반복하지 않고 재현성을 높이는 좋은 습관입니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>API로 받은 유전자 주석, 변이 정보, 문헌 정보는 E17 데이터베이스 구조로 정리할 수 있습니다. 수집 코드의 버전은 E19 Git으로 관리하고, 실행 환경은 E20으로 기록해야 합니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>생물정보학 연구자는 모든 데이터를 직접 만들지 않습니다. 공개 데이터베이스에서 필요한 정보를 가져와 자신의 데이터와 합칩니다. 예를 들어 유전자 ID를 유전자 이름으로 바꾸거나, 변이에 질병 정보를 붙이거나, 단백질 기능 정보를 가져올 수 있습니다. API는 이런 작업을 자동화하게 해 줍니다.</p>

<h2 id="미니-실습-블록-api로-받은-데이터를-파일과-로그로-남기기">미니 실습 블록: API로 받은 데이터를 파일과 로그로 남기기</h2>

<p>이 실습은 <strong>API로 받은 데이터를 파일과 로그로 남기기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, API 데이터는 시간이 지나며 바뀔 수 있으므로 언제 어떤 주소에서 받았는지 기록해야 재현성이 생기기 때문입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">requests</span>
<span class="kn">import</span> <span class="n">json</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="n">url</span> <span class="o">=</span> <span class="sh">"</span><span class="s">https://example.org/api/gene/TP53</span><span class="sh">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span>

<span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="sh">"</span><span class="s">results/tp53.json</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">w</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">json</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>

<span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="sh">"</span><span class="s">results/api_log.txt</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">a</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">log</span><span class="p">:</span>
    <span class="n">log</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span><span class="si">}</span><span class="se">\t</span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="se">\t</span><span class="si">{</span><span class="n">response</span><span class="p">.</span><span class="n">status_code</span><span class="si">}</span><span class="se">\n</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">requests.get</code>은 URL에 요청을 보내고, <code class="language-plaintext highlighter-rouge">response.json()</code>은 JSON 응답을 Python 객체로 바꿉니다. 로그에는 시간, URL, 상태코드를 남깁니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. Ensembl, NCBI, UniProt 같은 데이터베이스에서 유전자·단백질 정보를 가져올 때 필요합니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. API 결과를 매번 새로 받으면 데이터베이스 업데이트 때문에 과거 결과와 달라질 수 있습니다. 받은 원본 응답도 보관하는 것이 좋습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>API는 프로그램이 데이터 서버에 정해진 방식으로 요청하고 응답을 받는 통로입니다. JSON과 XML은 API 응답에서 자주 보는 형식이며, 파이썬에서는 <code class="language-plaintext highlighter-rouge">requests</code>로 데이터를 가져올 수 있습니다. 좋은 데이터 수집은 rate limit, 페이지네이션, 캐시, 재현성 기록을 함께 고려해야 합니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-18" /><category term="api" /><category term="data-collection" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E17: 데이터베이스와 SQL</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-17/" rel="alternate" type="text/html" title="부록 E17: 데이터베이스와 SQL" /><published>2026-05-19T16:30:00+00:00</published><updated>2026-05-19T16:30:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-17</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-17/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-17.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 데이터베이스(database)와 SQL을 배웁니다. 데이터베이스는 데이터를 규칙 있게 저장하고 찾기 위한 시스템입니다. SQL은 데이터베이스에 질문을 던지는 언어입니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>데이터베이스(database): 데이터를 체계적으로 저장하고 관리하는 시스템입니다.</li>
  <li>테이블(table): 행과 열로 이루어진 표입니다.</li>
  <li>행(row): 한 개체에 대한 기록입니다. 예를 들어 환자 한 명, 샘플 하나, 유전자 하나가 한 행이 될 수 있습니다.</li>
  <li>열(column): 기록의 속성입니다. 예를 들어 환자 나이, 샘플 ID, 유전자 이름이 열이 될 수 있습니다.</li>
  <li>기본키(primary key): 각 행을 구분하는 고유한 값입니다.</li>
  <li>외래키(foreign key): 다른 테이블의 기본키를 가리키는 값입니다.</li>
  <li>조인(join): 두 테이블을 공통 열을 기준으로 연결하는 작업입니다.</li>
  <li>인덱스(index): 검색을 빠르게 하기 위한 자료구조입니다.</li>
  <li>SQL: 테이블에서 원하는 데이터를 찾고, 걸러내고, 연결하는 데 쓰는 언어입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-17-database-sql.svg" alt="데이터베이스와 SQL" /></p>

<h2 id="가장-쉬운-비유-여러-장의-출석부를-연결하기">가장 쉬운 비유: 여러 장의 출석부를 연결하기</h2>

<p>한 표에는 학생 이름과 학번이 있고, 다른 표에는 학번과 시험 점수가 있다고 해 봅시다. 두 표를 학번으로 연결하면 “학생 이름과 시험 점수”를 함께 볼 수 있습니다.</p>

<p>생물정보학도 비슷합니다. 환자 정보 표, 샘플 정보 표, 유전자 발현 표, 변이 표가 따로 있을 수 있습니다. 이 표들을 샘플 ID나 환자 ID로 연결해야 제대로 해석할 수 있습니다.</p>

<h2 id="테이블의-기본-구조">테이블의 기본 구조</h2>

<p>예를 들어 환자 테이블은 이렇게 생길 수 있습니다.</p>

<pre><code class="language-txt">patient_id   age   disease
P001         54    cancer
P002         40    normal
P003         62    cancer
</code></pre>

<p>여기서 <code class="language-plaintext highlighter-rouge">patient_id</code>는 각 환자를 구분하는 값입니다. 같은 환자 ID가 두 번 나오면 헷갈리므로 기본키로 쓰기 좋습니다.</p>

<p>샘플 테이블은 이렇게 생길 수 있습니다.</p>

<pre><code class="language-txt">sample_id   patient_id   tissue
S001        P001         tumor
S002        P001         normal
S003        P002         blood
</code></pre>

<p>여기서 <code class="language-plaintext highlighter-rouge">patient_id</code>는 환자 테이블과 연결되는 값입니다.</p>

<h2 id="select-원하는-열-가져오기">SELECT: 원하는 열 가져오기</h2>

<p>SQL의 기본 형태는 다음과 같습니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">patient_id</span><span class="p">,</span> <span class="n">age</span>
<span class="k">FROM</span> <span class="n">patients</span><span class="p">;</span>
</code></pre></div></div>

<p>뜻은 <code class="language-plaintext highlighter-rouge">patients</code> 테이블에서 <code class="language-plaintext highlighter-rouge">patient_id</code>와 <code class="language-plaintext highlighter-rouge">age</code> 열을 가져오라는 말입니다.</p>

<p>모든 열을 가져오려면 <code class="language-plaintext highlighter-rouge">*</code>를 쓸 수 있습니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">patients</span><span class="p">;</span>
</code></pre></div></div>

<p>하지만 실제 분석에서는 필요한 열만 가져오는 습관이 좋습니다.</p>

<h2 id="where-조건으로-걸러내기">WHERE: 조건으로 걸러내기</h2>

<p>암 환자만 보고 싶다면 조건을 붙입니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">patients</span>
<span class="k">WHERE</span> <span class="n">disease</span> <span class="o">=</span> <span class="s1">'cancer'</span><span class="p">;</span>
</code></pre></div></div>

<p>나이가 50 이상인 환자를 고를 수도 있습니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span>
<span class="k">FROM</span> <span class="n">patients</span>
<span class="k">WHERE</span> <span class="n">age</span> <span class="o">&gt;=</span> <span class="mi">50</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="join-표-연결하기">JOIN: 표 연결하기</h2>

<p>환자 테이블과 샘플 테이블을 연결하려면 조인을 씁니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">p</span><span class="p">.</span><span class="n">patient_id</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">age</span><span class="p">,</span> <span class="n">s</span><span class="p">.</span><span class="n">sample_id</span><span class="p">,</span> <span class="n">s</span><span class="p">.</span><span class="n">tissue</span>
<span class="k">FROM</span> <span class="n">patients</span> <span class="n">p</span>
<span class="k">JOIN</span> <span class="n">samples</span> <span class="n">s</span>
  <span class="k">ON</span> <span class="n">p</span><span class="p">.</span><span class="n">patient_id</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">patient_id</span><span class="p">;</span>
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">p</code>와 <code class="language-plaintext highlighter-rouge">s</code>는 테이블 별명입니다. 긴 이름을 매번 쓰지 않기 위해 붙입니다. <code class="language-plaintext highlighter-rouge">ON</code> 뒤에는 두 테이블을 어떤 열로 연결할지 적습니다.</p>

<h2 id="group-by-묶어서-세기">GROUP BY: 묶어서 세기</h2>

<p>질병 그룹별 환자 수를 세고 싶다면 다음처럼 쓸 수 있습니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">disease</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">patients</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">disease</span><span class="p">;</span>
</code></pre></div></div>

<p>결과는 대략 이런 식입니다.</p>

<pre><code class="language-txt">cancer   2
normal   1
</code></pre>

<h2 id="인덱스가-필요한-이유">인덱스가 필요한 이유</h2>

<p>작은 표에서는 검색이 금방 끝납니다. 하지만 수백만 행짜리 변이 테이블에서는 특정 위치나 특정 유전자를 찾는 일이 느릴 수 있습니다. 인덱스는 책의 찾아보기처럼 검색을 빠르게 해 줍니다.</p>

<p>인덱스는 성능을 높일 수 있지만, 아무 열에나 무조건 많이 만드는 것이 정답은 아닙니다. 인덱스도 저장 공간과 관리 비용이 들기 때문입니다.</p>

<h2 id="생물학-데이터베이스">생물학 데이터베이스</h2>

<p>생물정보학에서는 이미 만들어진 생물학 데이터베이스를 자주 씁니다. 예를 들어 유전자 정보, 단백질 정보, 변이 정보, 논문 정보가 데이터베이스로 제공됩니다. NCBI, Ensembl, UniProt 같은 이름을 자주 보게 됩니다.</p>

<p>이런 데이터베이스를 잘 쓰려면 “내가 찾는 것이 유전자인가, 전사체인가, 단백질인가, 변이인가?”를 먼저 분명히 해야 합니다.</p>

<h2 id="실전-보강-patient_id와-sample_id를-헷갈리면-생기는-문제">실전 보강: patient_id와 sample_id를 헷갈리면 생기는 문제</h2>

<p>생물정보학 데이터에서는 환자 한 명에게 샘플이 여러 개 있을 수 있습니다.</p>

<pre><code class="language-txt">patients
patient_id   disease
P001         cancer
P002         normal

samples
sample_id   patient_id   tissue
S001        P001         tumor
S002        P001         normal
S003        P002         blood
</code></pre>

<p>여기서 <code class="language-plaintext highlighter-rouge">patient_id</code>는 사람을 구분하고, <code class="language-plaintext highlighter-rouge">sample_id</code>는 실제 측정된 샘플을 구분합니다. 환자 단위 분석인지 샘플 단위 분석인지 헷갈리면 같은 환자를 여러 번 세거나, tumor/normal paired 분석을 잘못할 수 있습니다.</p>

<h2 id="실전-보강-join은-붙이기가-아니라-매칭입니다">실전 보강: JOIN은 붙이기가 아니라 매칭입니다</h2>

<p>JOIN은 두 표를 그냥 옆으로 붙이는 작업이 아닙니다. 공통 key가 맞는 행끼리 매칭합니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">samples</span><span class="p">.</span><span class="n">sample_id</span><span class="p">,</span> <span class="n">patients</span><span class="p">.</span><span class="n">disease</span>
<span class="k">FROM</span> <span class="n">samples</span>
<span class="k">JOIN</span> <span class="n">patients</span>
<span class="k">ON</span> <span class="n">samples</span><span class="p">.</span><span class="n">patient_id</span> <span class="o">=</span> <span class="n">patients</span><span class="p">.</span><span class="n">patient_id</span><span class="p">;</span>
</code></pre></div></div>

<p>이 쿼리는 sample마다 해당 환자의 disease 정보를 붙입니다. 만약 key가 중복되거나 잘못되면 행 수가 예상보다 늘어날 수 있습니다. 이것은 생물정보학에서 아주 흔한 오류입니다.</p>

<h2 id="숫자-감각-join-결과-행-수-확인하기">숫자 감각: JOIN 결과 행 수 확인하기</h2>

<p>SQL에서도 작은 계산 감각이 필요합니다. 예를 들어 환자 2명과 샘플 3개가 있을 때, 샘플 테이블을 기준으로 환자 정보를 JOIN하면 보통 결과는 샘플 수인 3행이 됩니다.</p>

<pre><code class="language-txt">patients: P001, P002 → 2행
samples:  S001(P001), S002(P001), S003(P002) → 3행
JOIN 결과: 샘플마다 환자 정보를 붙이므로 3행
</code></pre>

<p>그런데 같은 <code class="language-plaintext highlighter-rouge">patient_id</code>가 양쪽 테이블에 중복되어 many-to-many로 매칭되면 행 수가 예상보다 커질 수 있습니다. 생물정보학에서는 이런 중복 JOIN 때문에 샘플 수, 변이 수, 발현값 집계가 부풀려지는 일이 흔합니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: ID 이름이 비슷하면 같은 뜻이다.</strong> <code class="language-plaintext highlighter-rouge">patient_id</code>, <code class="language-plaintext highlighter-rouge">sample_id</code>, <code class="language-plaintext highlighter-rouge">run_id</code>는 서로 다른 수준의 ID입니다.</li>
  <li><strong>오해 2: JOIN하면 항상 행 수가 유지된다.</strong> one-to-many 관계에서는 행 수가 늘 수 있습니다.</li>
  <li><strong>오해 3: NULL은 0과 같다.</strong> NULL은 값이 없거나 모른다는 뜻입니다.</li>
  <li><strong>오해 4: 인덱스는 무조건 좋다.</strong> 검색은 빨라질 수 있지만 저장공간과 업데이트 비용이 듭니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>SQL은 E13 count matrix, E14 변이 주석, E15 single-cell metadata를 정리하는 데 쓰입니다. API로 받은 데이터(E18)도 결국 테이블 구조로 정리해야 분석하기 쉽습니다.</p>

<h2 id="미니-실습-블록-sql로-sample-metadata와-결과-연결하기">미니 실습 블록: SQL로 sample metadata와 결과 연결하기</h2>

<p>이 실습은 <strong>SQL로 sample metadata와 결과 연결하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 분석 결과표와 샘플 정보를 안전하게 연결하려면 어떤 키로 JOIN하는지 이해해야 하기 때문입니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">c</span><span class="p">.</span><span class="n">sample_id</span><span class="p">,</span> <span class="n">m</span><span class="p">.</span><span class="n">condition</span><span class="p">,</span> <span class="k">c</span><span class="p">.</span><span class="n">gene_id</span><span class="p">,</span> <span class="k">c</span><span class="p">.</span><span class="k">count</span>
<span class="k">FROM</span> <span class="n">counts</span> <span class="k">AS</span> <span class="k">c</span>
<span class="k">JOIN</span> <span class="n">metadata</span> <span class="k">AS</span> <span class="n">m</span>
  <span class="k">ON</span> <span class="k">c</span><span class="p">.</span><span class="n">sample_id</span> <span class="o">=</span> <span class="n">m</span><span class="p">.</span><span class="n">sample_id</span>
<span class="k">WHERE</span> <span class="n">m</span><span class="p">.</span><span class="n">condition</span> <span class="o">=</span> <span class="s1">'treated'</span><span class="p">;</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">JOIN ... ON</code>은 두 테이블의 공통 키를 기준으로 행을 연결합니다. 여기서는 <code class="language-plaintext highlighter-rouge">sample_id</code>가 count와 metadata를 연결하는 열쇠입니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. 여러 실험 결과를 데이터베이스에 저장하고 조건별로 필요한 행만 조회할 때 쓰입니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. sample_id가 중복되거나 누락되면 JOIN 결과가 예상보다 많아지거나 줄어들 수 있습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>데이터베이스는 큰 표들을 체계적으로 저장하는 시스템이고, SQL은 그 표에 질문을 던지는 언어입니다. <code class="language-plaintext highlighter-rouge">SELECT</code>, <code class="language-plaintext highlighter-rouge">FROM</code>, <code class="language-plaintext highlighter-rouge">WHERE</code>, <code class="language-plaintext highlighter-rouge">JOIN</code>, <code class="language-plaintext highlighter-rouge">GROUP BY</code>를 이해하면 생물정보학 데이터의 상당 부분을 조회하고 연결할 수 있습니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-17" /><category term="database" /><category term="sql" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E16: 알고리즘과 복잡도</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-16/" rel="alternate" type="text/html" title="부록 E16: 알고리즘과 복잡도" /><published>2026-05-19T16:29:00+00:00</published><updated>2026-05-19T16:29:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-16</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-16/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-16.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 알고리즘(algorithm)과 복잡도(complexity)를 배웁니다. 알고리즘은 문제를 푸는 절차입니다. 복잡도는 그 절차가 얼마나 오래 걸리고, 얼마나 많은 메모리를 쓰는지 보는 감각입니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>알고리즘(algorithm): 입력을 받아 원하는 출력을 만드는 단계적 절차입니다.</li>
  <li>입력(input): 알고리즘에 들어가는 데이터입니다.</li>
  <li>출력(output): 알고리즘이 만들어 내는 결과입니다.</li>
  <li>시간복잡도(time complexity): 입력이 커질 때 실행 시간이 얼마나 늘어나는지 나타내는 감각입니다.</li>
  <li>공간복잡도(space complexity): 입력이 커질 때 메모리 사용량이 얼마나 늘어나는지 나타내는 감각입니다.</li>
  <li>선형시간(O(n)): 입력 개수에 비례해 시간이 늘어나는 경우입니다.</li>
  <li>제곱시간(O(n²)): 입력 개수의 제곱에 비례해 시간이 늘어나는 경우입니다.</li>
  <li>해시(hash): 값을 빠르게 찾기 위해 key를 이용하는 방식입니다.</li>
  <li>동적 계획법(dynamic programming): 작은 문제의 답을 저장해 큰 문제를 푸는 방법입니다.</li>
  <li>그래프 탐색(graph traversal): 노드와 간선을 따라 연결된 구조를 살펴보는 방법입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-16-algorithms-complexity.svg" alt="알고리즘과 복잡도" /></p>

<h2 id="가장-쉬운-비유-요리법과-장보기-비용">가장 쉬운 비유: 요리법과 장보기 비용</h2>

<p>알고리즘은 요리법과 비슷합니다. 라면 끓이기 알고리즘은 물을 끓이고, 면과 스프를 넣고, 몇 분 기다리는 절차입니다. 입력은 물, 면, 스프이고 출력은 라면입니다.</p>

<p>복잡도는 그 요리법의 비용 감각입니다. 한 명분 라면은 쉽지만 100명분 라면은 시간이 더 걸립니다. 입력이 커질수록 시간이 얼마나 늘어나는지 보는 것이 시간복잡도입니다.</p>

<h2 id="선형시간-하나씩-한-번만-보기">선형시간: 하나씩 한 번만 보기</h2>

<p>유전자 이름 목록에서 <code class="language-plaintext highlighter-rouge">TP53</code>이 있는지 찾는 코드를 봅시다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">genes</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">BRCA1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">EGFR</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">TP53</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">MYC</span><span class="sh">"</span><span class="p">]</span>

<span class="k">for</span> <span class="n">gene</span> <span class="ow">in</span> <span class="n">genes</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">gene</span> <span class="o">==</span> <span class="sh">"</span><span class="s">TP53</span><span class="sh">"</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">찾았습니다</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>목록 길이가 4개면 최대 4번 봅니다. 길이가 100개면 최대 100번 봅니다. 이런 식으로 입력 크기에 비례하는 경우를 선형시간이라고 합니다.</p>

<h2 id="제곱시간-모든-쌍을-비교하기">제곱시간: 모든 쌍을 비교하기</h2>

<p>모든 샘플 쌍의 거리를 계산한다고 생각해 봅시다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">samples</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">s1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">s2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">s3</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">s4</span><span class="sh">"</span><span class="p">]</span>

<span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">samples</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">samples</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>

<p>샘플이 4개면 4×4=16번 비교합니다. 샘플이 100개면 100×100=10,000번 비교합니다. 이런 구조는 입력이 커질 때 빠르게 무거워집니다.</p>

<h2 id="해시와-딕셔너리">해시와 딕셔너리</h2>

<p>파이썬 딕셔너리는 key로 값을 빠르게 찾을 수 있습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expression</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">BRCA1</span><span class="sh">"</span><span class="p">:</span> <span class="mf">12.5</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">TP53</span><span class="sh">"</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">EGFR</span><span class="sh">"</span><span class="p">:</span> <span class="mf">20.1</span>
<span class="p">}</span>

<span class="nf">print</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="sh">"</span><span class="s">TP53</span><span class="sh">"</span><span class="p">])</span>
</code></pre></div></div>

<p>리스트에서 하나씩 찾는 것보다 딕셔너리로 바로 찾는 것이 보통 훨씬 빠릅니다. 생물정보학에서는 유전자 ID → 유전자 이름, 변이 ID → 주석, 샘플 ID → 환자 정보처럼 key-value 구조가 자주 나옵니다.</p>

<h2 id="정렬과-탐색">정렬과 탐색</h2>

<p>정렬(sorting)은 데이터를 순서대로 놓는 작업입니다. 예를 들어 read count가 큰 유전자부터 보고 싶다면 정렬이 필요합니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">]</span>
<span class="nf">print</span><span class="p">(</span><span class="nf">sorted</span><span class="p">(</span><span class="n">values</span><span class="p">))</span>
</code></pre></div></div>

<p>결과는 다음과 같습니다.</p>

<pre><code class="language-txt">[3, 5, 10, 20]
</code></pre>

<p>정렬된 데이터에서는 탐색을 더 빠르게 할 수 있습니다. 책의 단어가 알파벳순으로 정리되어 있으면 찾기 쉬운 것과 같습니다.</p>

<h2 id="동적-계획법과-서열-비교">동적 계획법과 서열 비교</h2>

<p>동적 계획법은 작은 문제의 답을 표에 저장하면서 큰 문제를 푸는 방법입니다. 서열 정렬(sequence alignment)에서 중요합니다.</p>

<p>예를 들어 두 문자열을 비교할 때, 앞부분끼리의 최적 비교 결과를 저장해 두면 뒤쪽 계산을 반복하지 않아도 됩니다. 입문 단계에서는 “서열 정렬은 모든 가능성을 막무가내로 다 해보지 않고, 작은 비교 결과를 재사용한다” 정도로 이해하면 충분합니다.</p>

<h2 id="그래프-탐색">그래프 탐색</h2>

<p>그래프는 점과 선으로 이루어진 구조입니다. 단백질 상호작용망, 유전자 조절망, 대사경로는 그래프로 볼 수 있습니다.</p>

<p>그래프 탐색은 한 노드에서 시작해 연결된 노드들을 따라가는 작업입니다. 예를 들어 어떤 단백질과 직접 또는 간접적으로 연결된 단백질들을 찾을 수 있습니다.</p>

<h2 id="복잡도-감각이-왜-필요한가">복잡도 감각이 왜 필요한가</h2>

<p>작은 예제에서는 어떤 코드든 잘 돌아갑니다. 하지만 생물정보학 데이터는 큽니다. read가 수천만 개, 세포가 수십만 개, 변이가 수백만 개일 수 있습니다. 이때 비효율적인 알고리즘은 현실적으로 끝나지 않을 수 있습니다.</p>

<p>예를 들어 1,000개 데이터를 모든 쌍 비교하면 1,000,000번입니다. 100,000개라면 10,000,000,000번입니다. 입력이 커질 때 비용이 어떻게 변하는지 생각해야 합니다.</p>

<h2 id="실전-보강-n이-커질-때-숫자가-어떻게-폭발하는가">실전 보강: n이 커질 때 숫자가 어떻게 폭발하는가</h2>

<p>복잡도는 기호만 외우면 쓸모가 없습니다. 입력 크기가 커질 때 실제 숫자가 어떻게 바뀌는지 봐야 합니다.</p>

<pre><code class="language-txt">샘플 수 n = 10
모든 쌍 비교 n² = 100번
중복 없는 쌍 비교 n(n-1)/2 = 45번

샘플 수 n = 1,000
모든 쌍 비교 n² = 1,000,000번
중복 없는 쌍 비교 n(n-1)/2 = 499,500번
</code></pre>

<p>작은 데이터에서는 아무 코드나 빨라 보입니다. 하지만 샘플 수가 100배가 되면 O(n²) 코드는 대략 10,000배 무거워질 수 있습니다.</p>

<h2 id="실전-보강-서열-정렬과-dp-표-크기">실전 보강: 서열 정렬과 DP 표 크기</h2>

<p>동적 계획법으로 두 서열을 비교할 때는 보통 표를 만듭니다. 길이가 각각 <code class="language-plaintext highlighter-rouge">n</code>, <code class="language-plaintext highlighter-rouge">m</code>인 두 서열을 비교하면 대략 <code class="language-plaintext highlighter-rouge">(n+1) × (m+1)</code>칸을 채웁니다.</p>

<pre><code class="language-txt">서열 A 길이 = 100
서열 B 길이 = 200
DP 표 크기 = 101 × 201 = 20,301칸
</code></pre>

<p>서열이 조금만 길어져도 표가 커집니다. 그래서 생물정보학에서는 정확한 알고리즘과 빠른 휴리스틱 방법을 상황에 맞게 고릅니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: 내 노트북에서 빨랐으니 서버에서도 항상 충분하다.</strong> 작은 테스트 데이터와 실제 전체 데이터는 규모가 다릅니다.</li>
  <li><strong>오해 2: for문은 무조건 나쁘다.</strong> 문제는 for문 자체가 아니라 입력 크기에 따라 반복이 어떻게 늘어나는지입니다.</li>
  <li><strong>오해 3: 메모리는 실행 시간과 별개다.</strong> 너무 큰 표나 배열은 메모리를 터뜨려 실행 자체를 실패하게 할 수 있습니다.</li>
  <li><strong>오해 4: Big-O는 정확한 초 단위 시간이다.</strong> Big-O는 증가 추세를 보는 도구입니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>복잡도 감각은 E15 single-cell, E21 워크플로우, E22 HPC, E24 대규모 데이터 처리에서 계속 등장합니다. 어떤 코드를 병렬화하기 전에 먼저 알고리즘 자체가 너무 무거운지 판단해야 합니다.</p>

<h2 id="어려운-개념-보강-big-o를-숫자로-체감하기">어려운 개념 보강: Big-O를 숫자로 체감하기</h2>

<p>복잡도 표기에서 <code class="language-plaintext highlighter-rouge">O(n)</code>, <code class="language-plaintext highlighter-rouge">O(n²)</code> 같은 표기는 정확한 실행 시간을 초 단위로 말하는 공식이 아닙니다. 입력이 커질 때 계산량이 어떤 속도로 늘어나는지 보는 약속입니다. 여기서 <code class="language-plaintext highlighter-rouge">n</code>은 입력 개수입니다. 샘플 수, read 수, 유전자 수, 변이 수가 모두 상황에 따라 <code class="language-plaintext highlighter-rouge">n</code>이 될 수 있습니다.</p>

<p>선형시간 <code class="language-plaintext highlighter-rouge">O(n)</code>은 입력을 한 번 훑는 느낌입니다.</p>

<pre><code class="language-txt">n개를 각각 한 번씩 본다 → 대략 n번 작업
</code></pre>

<p>제곱시간 <code class="language-plaintext highlighter-rouge">O(n²)</code>은 모든 쌍을 비교하는 느낌입니다.</p>

<pre><code class="language-txt">n개 각각을 다른 n개와 비교한다 → 대략 n × n번 작업
</code></pre>

<p>숫자로 보면 차이가 커집니다.</p>

<pre><code class="language-txt">n = 100     → O(n)은 100번,     O(n²)은 10,000번
n = 10,000  → O(n)은 10,000번,  O(n²)은 100,000,000번
</code></pre>

<p>이 차이는 생물정보학에서 바로 문제가 됩니다. 샘플 100개끼리 모든 쌍의 거리를 계산하는 것은 괜찮을 수 있지만, 세포 100만 개끼리 모든 쌍을 비교하면 거의 불가능해질 수 있습니다.</p>

<p>공간복잡도도 중요합니다. 100만 × 100만 거리 행렬을 전부 저장하려면 원소가 1조 개입니다. 원소 하나가 8바이트 실수라면 필요한 메모리는 다음과 같습니다.</p>

<pre><code class="language-txt">1,000,000 × 1,000,000 × 8 bytes
= 8,000,000,000,000 bytes
≈ 8 TB
</code></pre>

<p>이런 계산은 “코드가 논리적으로 맞는가”와 별개로 “현실적으로 실행 가능한가”를 판단하게 해 줍니다.</p>

<p>흔한 오해는 <code class="language-plaintext highlighter-rouge">O(n²)</code>이면 항상 나쁘고 <code class="language-plaintext highlighter-rouge">O(n)</code>이면 항상 좋다고 생각하는 것입니다. 작은 데이터에서는 <code class="language-plaintext highlighter-rouge">O(n²)</code>도 충분히 괜찮을 수 있고, 큰 데이터에서는 <code class="language-plaintext highlighter-rouge">O(n)</code>이라도 파일 입출력이나 메모리 병목 때문에 느릴 수 있습니다. 복잡도는 최종 답이 아니라, 위험한 병목을 미리 발견하는 지도입니다.</p>

<h2 id="미니-실습-블록-fasta-길이-계산에서-시간복잡도-생각하기">미니 실습 블록: FASTA 길이 계산에서 시간복잡도 생각하기</h2>

<p>이 실습은 <strong>FASTA 길이 계산에서 시간복잡도 생각하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 생물정보학 파일은 매우 크므로 같은 일을 한 번 훑는 코드와 모든 쌍을 비교하는 코드는 실행 시간이 크게 달라지기 때문입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">seqs</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">ATGC</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ATGCGC</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">A</span><span class="sh">"</span><span class="p">]</span>

<span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">seq</span> <span class="ow">in</span> <span class="n">seqs</span><span class="p">:</span>
    <span class="n">total</span> <span class="o">=</span> <span class="n">total</span> <span class="o">+</span> <span class="nf">len</span><span class="p">(</span><span class="n">seq</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="n">total</span><span class="p">)</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. 이 코드는 서열 리스트를 한 번만 훑으므로 서열 개수를 n이라고 할 때 대략 O(n)입니다. 반면 모든 서열 쌍을 비교하면 O(n²)에 가까워질 수 있습니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. read 수, 유전자 수, 세포 수가 커질수록 알고리즘 선택이 분석 시간과 메모리에 직접 영향을 줍니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. 작은 예제에서 빠른 코드도 실제 FASTQ 수억 줄에서는 병목이 될 수 있습니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>알고리즘은 문제를 푸는 절차이고, 복잡도는 그 절차의 시간과 메모리 비용 감각입니다. 생물정보학에서는 데이터가 크기 때문에 선형시간, 제곱시간, 해시, 정렬, 동적 계획법, 그래프 탐색 같은 기본 개념을 알아야 합니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-16" /><category term="algorithm" /><category term="complexity" /><summary type="html"><![CDATA[]]></summary></entry><entry xml:lang="ko"><title type="html">부록 E15: single-cell 분석 프로그래밍</title><link href="https://loremipsum0116.github.io/ko/learning/appendix-e-15/" rel="alternate" type="text/html" title="부록 E15: single-cell 분석 프로그래밍" /><published>2026-05-19T16:28:00+00:00</published><updated>2026-05-19T16:28:00+00:00</updated><id>https://loremipsum0116.github.io/ko/learning/appendix-e-15</id><content type="html" xml:base="https://loremipsum0116.github.io/ko/learning/appendix-e-15/"><![CDATA[<!-- source: _posts/ko/learning/2026-05-20-appendix-e-15.md -->

<h2 id="이-장에서-배울-것">이 장에서 배울 것</h2>

<p>이번 장에서는 단일세포 분석 프로그래밍(single-cell analysis programming)을 배웁니다. 단일세포 분석은 여러 세포를 한꺼번에 평균내지 않고, 세포 하나하나의 유전자 발현을 따로 측정하고 분석하는 방법입니다.</p>

<p>핵심 용어를 먼저 정리하겠습니다.</p>

<ul>
  <li>단일세포 RNA-seq(scRNA-seq): 세포 하나하나의 RNA 발현량을 측정하는 실험입니다. 앞으로는 필요한 곳에서 scRNA-seq이라고도 부르겠습니다.</li>
  <li>세포 바코드(cell barcode): read가 어느 세포에서 왔는지 알려주는 짧은 이름표입니다.</li>
  <li>UMI(unique molecular identifier): 같은 RNA 분자에서 나온 read를 구분하기 위한 짧은 이름표입니다. 앞으로는 UMI라고 부르겠습니다.</li>
  <li>count matrix: 행은 세포, 열은 유전자, 값은 발현 count인 숫자 표입니다.</li>
  <li>품질관리(QC): 품질이 낮은 세포나 유전자를 걸러내는 단계입니다.</li>
  <li>정규화(normalization): 세포마다 read 수가 다른 문제를 보정하는 단계입니다.</li>
  <li>고변동 유전자(highly variable genes): 세포들 사이에서 차이를 잘 보여주는 유전자들입니다.</li>
  <li>차원축소(dimensionality reduction): 수천 개 유전자 정보를 2~50개 정도의 축으로 줄여 보는 방법입니다.</li>
  <li>클러스터링(clustering): 비슷한 세포끼리 묶는 작업입니다.</li>
  <li>세포 유형 주석(cell type annotation): 각 클러스터가 어떤 세포 유형인지 이름을 붙이는 작업입니다.</li>
</ul>

<p><img src="/assets/images/learning/appendix/e/e-15-single-cell-programming.svg" alt="single-cell 분석 프로그래밍" /></p>

<h2 id="가장-쉬운-비유-교실-전체-평균-대신-학생별-성적표-보기">가장 쉬운 비유: 교실 전체 평균 대신 학생별 성적표 보기</h2>

<p>일반 RNA-seq은 반 전체 평균 성적을 보는 것과 비슷합니다. 평균이 80점이면 반 전체가 어느 정도인지 알 수 있지만, 어떤 학생이 수학을 잘하고 어떤 학생이 영어를 잘하는지는 보이지 않습니다.</p>

<p>단일세포 분석은 학생별 성적표를 보는 것과 비슷합니다. 세포 하나하나가 “학생”이고, 유전자 발현량이 “과목 점수”입니다. 그래서 세포마다 어떤 특징이 있는지 볼 수 있습니다.</p>

<h2 id="count-matrix의-모양">count matrix의 모양</h2>

<p>단일세포 분석의 기본 데이터는 큰 숫자 표입니다.</p>

<pre><code class="language-txt">cell      GeneA  GeneB  GeneC
cell_1      0      5      1
cell_2      3      0      8
cell_3      1      7      0
</code></pre>

<p>여기서 한 행은 세포 하나입니다. 한 열은 유전자 하나입니다. 값은 그 세포에서 해당 유전자가 얼마나 관찰되었는지를 나타냅니다.</p>

<h2 id="세포-바코드와-umi">세포 바코드와 UMI</h2>

<p>단일세포 실험에서는 많은 세포의 RNA가 한꺼번에 처리됩니다. 그러면 read가 어느 세포에서 왔는지 구분해야 합니다. 이때 세포 바코드가 붙습니다.</p>

<p>UMI는 같은 RNA 분자가 PCR 과정에서 여러 번 복사되어 read가 많아 보이는 문제를 줄이는 데 도움을 줍니다. 쉽게 말해 세포 바코드는 “어느 세포의 것인가?”, UMI는 “어느 원래 분자의 것인가?”를 알려주는 이름표입니다.</p>

<h2 id="qc-먼저-나쁜-세포를-걸러야-합니다">QC: 먼저 나쁜 세포를 걸러야 합니다</h2>

<p>단일세포 데이터에는 품질이 낮은 세포가 섞일 수 있습니다. 예를 들어 죽어 가는 세포, RNA가 너무 적게 잡힌 세포, 두 세포가 한 방울에 같이 들어간 경우가 있을 수 있습니다.</p>

<p>QC에서 자주 보는 값은 다음과 같습니다.</p>

<pre><code class="language-txt">세포별 총 count 수
세포별 검출 유전자 수
미토콘드리아 유전자 비율
</code></pre>

<p>미토콘드리아 유전자 비율이 너무 높으면 세포가 손상되었을 가능성을 의심할 수 있습니다. 물론 기준값은 데이터와 실험 조건에 따라 달라집니다.</p>

<h2 id="python에서-anndata로-다루기">Python에서 AnnData로 다루기</h2>

<p>파이썬에서는 Scanpy라는 라이브러리를 많이 씁니다. Scanpy는 단일세포 데이터를 AnnData라는 형태로 다룹니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">scanpy</span> <span class="k">as</span> <span class="n">sc</span>

<span class="n">adata</span> <span class="o">=</span> <span class="n">sc</span><span class="p">.</span><span class="nf">read_h5ad</span><span class="p">(</span><span class="sh">"</span><span class="s">cells.h5ad</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">adata</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">adata.shape</code>이 <code class="language-plaintext highlighter-rouge">(1000, 20000)</code>이라면 세포 1000개와 유전자 20000개가 있다는 뜻입니다.</p>

<h2 id="기본-분석-흐름">기본 분석 흐름</h2>

<p>단일세포 분석은 보통 다음 흐름으로 진행됩니다.</p>

<pre><code class="language-txt">count matrix 읽기
→ QC
→ 정규화
→ 로그 변환
→ 고변동 유전자 선택
→ PCA
→ 이웃 그래프 만들기
→ 클러스터링
→ UMAP 시각화
→ 세포 유형 주석
</code></pre>

<p>이 흐름은 처음 보면 길지만, 큰 뜻은 단순합니다. 먼저 나쁜 데이터를 줄이고, 세포 간 차이를 잘 보여주는 유전자를 고른 다음, 비슷한 세포끼리 묶습니다.</p>

<h2 id="작은-코드-예시">작은 코드 예시</h2>

<p>실제 코드는 데이터마다 달라지지만, 흐름은 다음처럼 생겼습니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">scanpy</span> <span class="k">as</span> <span class="n">sc</span>

<span class="n">adata</span> <span class="o">=</span> <span class="n">sc</span><span class="p">.</span><span class="nf">read_h5ad</span><span class="p">(</span><span class="sh">"</span><span class="s">cells.h5ad</span><span class="sh">"</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">pp</span><span class="p">.</span><span class="nf">filter_cells</span><span class="p">(</span><span class="n">adata</span><span class="p">,</span> <span class="n">min_genes</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">pp</span><span class="p">.</span><span class="nf">normalize_total</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">pp</span><span class="p">.</span><span class="nf">log1p</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">tl</span><span class="p">.</span><span class="nf">pca</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">pp</span><span class="p">.</span><span class="nf">neighbors</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">tl</span><span class="p">.</span><span class="nf">leiden</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
<span class="n">sc</span><span class="p">.</span><span class="n">tl</span><span class="p">.</span><span class="nf">umap</span><span class="p">(</span><span class="n">adata</span><span class="p">)</span>
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">pp</code>는 전처리(preprocessing), <code class="language-plaintext highlighter-rouge">tl</code>은 분석 도구(tool)를 뜻합니다. 처음에는 함수 이름을 다 외우기보다, 각 줄이 파이프라인의 어느 단계인지 이해하는 것이 중요합니다.</p>

<h2 id="클러스터를-해석할-때-조심할-점">클러스터를 해석할 때 조심할 점</h2>

<p>클러스터가 나뉘었다고 해서 자동으로 생물학적 세포 유형이 정해지는 것은 아닙니다. 특정 마커 유전자(marker gene, 어떤 세포 유형을 알려주는 단서 유전자)를 보고, 기존 생물학 지식과 비교해 해석해야 합니다.</p>

<p>예를 들어 어떤 클러스터에서 T 세포 마커가 높게 나오면 그 클러스터를 T 세포로 추정할 수 있습니다. 하지만 자동 주석 결과를 무조건 믿으면 안 됩니다. 실험 조건, 종, 조직, 데이터 품질에 따라 해석이 달라질 수 있습니다.</p>

<h2 id="실전-보강-0이-많다는-뜻과-dropout">실전 보강: 0이 많다는 뜻과 dropout</h2>

<p>single-cell count matrix에는 0이 매우 많습니다. 어떤 세포에서 어떤 유전자의 count가 0이라고 해서 그 유전자가 생물학적으로 완전히 꺼져 있다고 단정하면 안 됩니다. 실제로는 RNA 분자가 있었지만 잡히지 않았을 수 있습니다. 이런 현상을 dropout이라고 부릅니다.</p>

<pre><code class="language-txt">cell      GeneA  GeneB  GeneC
cell_1      0      5      1
cell_2      3      0      8
cell_3      0      0      0
</code></pre>

<p><code class="language-plaintext highlighter-rouge">cell_3</code>처럼 전체 count가 지나치게 낮은 세포는 품질이 낮은 세포일 수 있습니다. 반면 특정 유전자 하나만 0인 것은 dropout일 수 있습니다. 그래서 single-cell 분석은 한 유전자 하나만 보지 않고, 여러 marker gene과 전체 패턴을 함께 봅니다.</p>

<h2 id="실전-보강-doublet-batch-effect-umap-해석">실전 보강: doublet, batch effect, UMAP 해석</h2>

<p>단일세포 실험에서는 두 세포가 한 방울에 같이 들어가 하나의 세포처럼 기록될 수 있습니다. 이것을 doublet이라고 합니다. doublet은 두 세포 유형의 marker가 섞여 이상한 세포처럼 보일 수 있습니다.</p>

<p>Batch effect도 중요합니다. 첫째 날 처리한 샘플과 둘째 날 처리한 샘플이 기술적 차이 때문에 서로 다른 클러스터로 보일 수 있습니다. 이때 클러스터가 생물학적 세포 유형 차이인지, 실험 배치 차이인지 구분해야 합니다.</p>

<p>UMAP은 복잡한 유전자 발현 패턴을 2차원 그림으로 압축한 것입니다. 가까운 점들이 비슷한 세포일 가능성은 있지만, UMAP 위의 거리와 방향을 물리적 거리처럼 과해석하면 안 됩니다.</p>

<h2 id="초보자가-자주-하는-오해">초보자가 자주 하는 오해</h2>

<ul>
  <li><strong>오해 1: 클러스터 하나는 반드시 세포 유형 하나다.</strong> 클러스터는 계산으로 묶은 결과이며, marker gene과 생물학 지식으로 해석해야 합니다.</li>
  <li><strong>오해 2: UMAP에서 멀면 반드시 완전히 다른 세포다.</strong> UMAP은 시각화 도구이지 절대 거리 지도는 아닙니다.</li>
  <li><strong>오해 3: 0 count는 항상 발현 없음이다.</strong> single-cell에서는 dropout 때문에 0이 많이 생깁니다.</li>
  <li><strong>오해 4: QC 기준은 모든 데이터에 똑같다.</strong> 조직, 플랫폼, 실험 조건에 따라 기준을 조정해야 합니다.</li>
</ul>

<h2 id="이전-개념과-다음-개념의-연결">이전 개념과 다음 개념의 연결</h2>

<p>single-cell 분석은 RNA-seq의 count matrix 사고를 세포 단위로 확장한 것입니다. 세포와 유전자가 많아지면 E16 알고리즘 복잡도와 E22 HPC가 중요해지고, 분석 환경과 결과 재현은 E20, E21, E24와 직접 연결됩니다.</p>

<h2 id="생물정보학에서-왜-중요한가">생물정보학에서 왜 중요한가</h2>

<p>단일세포 분석은 세포 유형, 발생 과정, 암 미세환경, 면역세포 상태를 연구하는 데 매우 강력합니다. 프로그래밍 관점에서는 큰 희소 행렬(sparse matrix, 대부분 값이 0인 행렬)을 다루고, 차원축소와 클러스터링을 연결하는 대표적인 분석입니다.</p>

<h2 id="어려운-개념-보강-단일세포-qc-지표와-정규화-공식-읽기">어려운 개념 보강: 단일세포 QC 지표와 정규화 공식 읽기</h2>

<p>단일세포 분석에서 어려운 부분은 알고리즘 이름보다 QC 지표의 의미입니다. 세포 하나하나가 분석 단위이므로, 품질이 낮은 세포를 그대로 두면 클러스터가 생물학적 차이가 아니라 기술적 잡음으로 나뉠 수 있습니다.</p>

<p>자주 보는 QC 지표는 세 가지입니다.</p>

<pre><code class="language-txt">total_counts: 한 세포에서 관찰된 전체 UMI 또는 read count
n_genes_by_counts: 한 세포에서 검출된 유전자 수
mito_fraction: 미토콘드리아 유전자 count 비율
</code></pre>

<p>미토콘드리아 비율은 보통 다음처럼 계산합니다.</p>

<pre><code class="language-txt">mito_fraction = 미토콘드리아 유전자 count 합 / 전체 count 합
</code></pre>

<p>예를 들어 어떤 세포의 전체 count가 10,000이고, 그중 미토콘드리아 유전자 count가 1,500이면 다음과 같습니다.</p>

<pre><code class="language-txt">mito_fraction = 1,500 / 10,000 = 0.15 = 15%
</code></pre>

<p>미토콘드리아 비율이 지나치게 높으면 세포가 손상되었거나 스트레스를 받았을 가능성을 의심할 수 있습니다. 다만 모든 조직에 같은 기준을 기계적으로 적용하면 안 됩니다. 조직과 실험 방식에 따라 정상 범위가 달라질 수 있습니다.</p>

<p>정규화는 세포마다 전체 count가 다른 문제를 줄이기 위해 사용합니다. 한 가지 단순한 직관은 “각 세포의 총량을 비슷한 규모로 맞춘 뒤 비교한다”입니다.</p>

<pre><code class="language-txt">정규화 값 = 특정 유전자 count / 해당 세포 전체 count × 10,000
</code></pre>

<p>예를 들어 한 세포에서 GeneA count가 50이고 전체 count가 10,000이면 정규화 값은 50입니다. 다른 세포에서 GeneA count가 100이고 전체 count가 20,000이어도 정규화 값은 50입니다. raw count는 다르지만 전체 규모를 고려하면 비슷한 비율이라는 뜻입니다.</p>

<p>그다음 <code class="language-plaintext highlighter-rouge">log1p</code> 변환을 자주 씁니다.</p>

<pre><code class="language-txt">log1p(x) = log(1 + x)
</code></pre>

<p><code class="language-plaintext highlighter-rouge">1</code>을 더하는 이유는 count가 0인 값도 계산할 수 있게 하기 위해서입니다. 로그 변환은 매우 큰 값의 영향을 줄이고, 작은 값들의 차이를 보기 쉽게 만듭니다.</p>

<p>주의할 점은 정규화와 차원축소가 생물학적 진실을 자동으로 만들어 주지는 않는다는 것입니다. UMAP에서 가까운 점은 비슷한 발현 패턴을 가질 가능성이 있지만, 거리 자체를 실제 생물학적 시간이나 물리적 거리처럼 해석하면 위험합니다.</p>

<h2 id="미니-실습-블록-single-cell-count-matrix와-cell-metadata-확인하기">미니 실습 블록: single-cell count matrix와 cell metadata 확인하기</h2>

<p>이 실습은 <strong>single-cell count matrix와 cell metadata 확인하기</strong>를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, single-cell 분석은 샘플보다 세포 단위 metadata가 중요하고, 품질 낮은 세포가 클러스터 해석을 왜곡할 수 있기 때문입니다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>

<span class="n">cell_meta</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">({</span>
    <span class="sh">"</span><span class="s">cell_id</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="sh">"</span><span class="s">C1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">C2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">C3</span><span class="sh">"</span><span class="p">],</span>
    <span class="sh">"</span><span class="s">n_genes</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">2500</span><span class="p">],</span>
    <span class="sh">"</span><span class="s">pct_mito</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="mf">5.2</span><span class="p">,</span> <span class="mf">30.1</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">]</span>
<span class="p">})</span>

<span class="n">filtered</span> <span class="o">=</span> <span class="n">cell_meta</span><span class="p">[(</span><span class="n">cell_meta</span><span class="p">[</span><span class="sh">"</span><span class="s">n_genes</span><span class="sh">"</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">500</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">cell_meta</span><span class="p">[</span><span class="sh">"</span><span class="s">pct_mito</span><span class="sh">"</span><span class="p">]</span> <span class="o">&lt;</span> <span class="mi">20</span><span class="p">)]</span>
<span class="nf">print</span><span class="p">(</span><span class="n">filtered</span><span class="p">)</span>
</code></pre></div></div>

<p>각 코드 요소의 의미를 풀어보면 다음과 같습니다. <code class="language-plaintext highlighter-rouge">n_genes</code>는 한 세포에서 검출된 유전자 수, <code class="language-plaintext highlighter-rouge">pct_mito</code>는 미토콘드리아 유전자 비율입니다. 극단적으로 낮은 <code class="language-plaintext highlighter-rouge">n_genes</code>나 높은 <code class="language-plaintext highlighter-rouge">pct_mito</code>는 품질 문제 신호일 수 있습니다.</p>

<p>생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. single-cell RNA-seq에서 QC 후 normalization, PCA, clustering, marker gene 해석으로 넘어가기 전 단계입니다.</p>

<p>흔한 오해 또는 주의점도 있습니다. QC 기준은 데이터와 조직에 따라 달라질 수 있으므로 숫자를 기계적으로 복사하면 안 됩니다.</p>

<h2 id="핵심-정리">핵심 정리</h2>

<p>단일세포 분석은 세포 하나하나의 유전자 발현을 표로 만들고, 품질관리, 정규화, 차원축소, 클러스터링, 세포 유형 주석을 거쳐 해석합니다. 핵심은 세포를 하나의 벡터로 보고, 비슷한 세포끼리 묶어 생물학적 의미를 찾는 것입니다.</p>]]></content><author><name>현석</name></author><category term="learning" /><category term="appendix-e" /><category term="appendix-e-15" /><category term="single-cell" /><category term="scanpy" /><summary type="html"><![CDATA[]]></summary></entry></feed>