일단 결론부터 말하겠음. 아직 중순 안됐으면 연초나 마찬가지니까 지금 당장에라도 담배 끊으십쇼.
# 모듈
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from lifelines import KaplanMeierFitter # 이친구가 생존곡선을 잘 그려요 아무튼 그럼
import GEOparse # NCBI GEO에 접근할 때 필요함
from Bio import Entrez # NCBI 창고털이 드가자
# 그래프를 그리기 위한 기본 설정
plt.rcParams['font.family'] = 'Nanumbarunpen'
# plt.rcParams['font.family'] = 'AppleGothic'
plt.rcParams['font.size'] = 14
plt.rcParams['axes.unicode_minus'] = False
# NCBI 창고를 털려면 이메일이 필요함
Entrez.email = "blackholekun@gmail.com" # 이메일
그… 생존 곡선이 뭐냐… 이따 그래프로 보여드리겠지만 암종별로(소세포 vs 비소세포, 다양한 폐암 종류별) 생존률을 비교할건데, 그때 꺾은선그래프? 그런걸 그림. 물론 좌상에서 우하로 떨어지는 시기가 달라요. 그걸 그려주는 애가 lifelines인거고 밑에 쟤는 GEO(Gene Expression Omnibus)에 가서 창고털이 할 때 필요한거다.
# 창고는 열려있다! 데이터를 털어라!
print("--- GSE30219 데이터 다운로드 중... ---")
gse_mut = GEOparse.get_GEO(geo="GSE30219", destdir="./") # 진짜 창고 터는중
df_mut = gse_mut.phenotype_data
아 창고를 열어줬으면 털어줘야 인지상정이지.
이거 오래걸리니까 똥 함 때리고 오십셔.
# 정보 확인
df_mut.info() # 거 정보좀 봅시다.
df_mut.isna().sum() # 아... 널이 있었어...
df_mut.head()
원래 데이터 받고 나면 정보 확인하는게 국룰임. 저거말고도 더 있는데 그건 나중에 따로 다뤄드리겠음.
SCLC vs NSCLC 생존률 비교
# 필요한 것만 쏙 빼오기
df_target = df_mut[[
'characteristics_ch1.3.histology',
'characteristics_ch1.7.follow-up time (months)',
'characteristics_ch1.8.status'
]].copy()
# Histology: 암종
# Months: 생존기간
# Status: 생존 상태
df_target.columns = ['Histology', 'Months', 'Status'] # 컬럼 이름 바꿀거임
# 선가공
df_target['Months'] = pd.to_numeric(df_target['Months'], errors='coerce')
# 돌아가심->1, 살아있음->0
df_target['Event'] = df_target['Status'].apply(lambda x: 1 if str(x).upper() == 'DEAD' else 0)
# 암종 크게 분류 (NSCLC vs SCLC)
# NSCLC: 비소세포성 폐암
# SCLC: 소세포성 폐암
def classify_lung_cancer(x):
if x in ['ADC', 'SQC', 'LCC', 'LCNE', 'BAS']: return 'NSCLC (Non-Small Cell)'
elif x == 'SCC': return 'SCLC (Small Cell)'
elif x == 'NTL': return 'Normal/Control'
else: return 'Other'
df_target['Group'] = df_target['Histology'].apply(classify_lung_cancer)
# 생존 분석 시각화
kmf = KaplanMeierFitter()
plt.figure(figsize=(10, 6))
for name, grouped_df in df_target.groupby('Group'):
if name == 'Normal/Control': continue
valid_data = grouped_df.dropna(subset=['Months'])
if len(valid_data) > 0:
kmf.fit(valid_data['Months'], valid_data['Event'], label=name)
kmf.plot_survival_function()
plt.title("Survival Rate: NSCLC vs SCLC (GSE30219)")
plt.xlabel("Months")
plt.ylabel("Survival Probability")
plt.grid(True)
plt.show()
# 데이터 확인용 (인원수)
print(df_target['Group'].value_counts())
으아악 길어! 멈춰! 응 못멈춰요. 이거는 SCLC(소세포성 폐암), NSCLC(비소세포성 폐암)의 생존 곡선이다.

내가 담배 끊으라고 했잖아... 아니 담배 그거 백해무익하다지만 여기서 왜 나옵니까? 모든 폐암 환자가 담배를 피워서 걸리는 건 아니다. 근데 담배를 피우면 폐암, 그것도 소세포성 폐암(저기 초록색 곡선)에 걸린다. NSCLC에 비해 거진 반토막은 난 것 같은 저거 말이다.
# SCLC, NSCLC간 생존율 차이
survival_clc = df_target.groupby('Group').mean('Month').sort_values(by = 'Months', ascending = False)
plt.figure(figsize = (10, 6))
plt.title('SCLC vs NSCLC Survival Rate')
plt.xlabel('Group')
plt.ylabel('Months')
sns.barplot(survival_clc, x = 'Group', y = 'Months', hue = 'Group', palette = 'coolwarm')
plt.show()
print("--- [최종 분석] 주요 암종별 평균 생존 기간 ---")
print(df_target.groupby('Group')['Months'].mean().sort_values(ascending=False))
그... 생존곡선이랑 갖고오는건 제미나이 시켰지만 이건 내가 했음.

뜬금없이 막대그래프가 왜 나왔냐고? 그건 간단하다. 생존곡선이 겹치는 게 많아서 보기가 어렵잖아… 그래서 생존율 평균내서 막대그래프 그린거다. NSCLC랑 SCLC를 비교해보면 거의 반토막이죠?
암종별 생존률 비교
폐암이 폐암이다 땡! 이 아님. 세분류 들어가면 여러가지 암종이 있고 그게 또 유전자 변이에 따라 예후가 나뉩니다... EGFR만 변이가 있는게 아녀... 걔도 어디 바뀌느냐에 따라 L858R T790M 이렇게 있어...
# 암종별 생존곡선 그리기
# 위에 그거...같은데?
df_study = df_mut[[
'characteristics_ch1.3.histology',
'characteristics_ch1.7.follow-up time (months)',
'characteristics_ch1.8.status'
]].copy()
df_study.columns = ['Histology', 'Months', 'Status']
# 전처리: 숫자 변환 및 사망 이벤트 정의
df_study['Months'] = pd.to_numeric(df_study['Months'], errors='coerce')
# 위랑 같음. 사망->1, 살아있음->0
df_study['Event'] = df_study['Status'].apply(lambda x: 1 if str(x).upper() == 'DEAD' else 0)
# 암종별 비교
# ADC: 선암
# SQC: 편평상피세포암
# NTL: 정상 폐
# BAS: 기저세포양 편평세포암
# LCC: 대세포암
# LCNE: 대세포 신경내분비암
# SCC: 소세포암
# CARCI: 유암종
# Other: 기타
plt.figure(figsize=(12, 8))
ax = plt.subplot(111)
kmf = KaplanMeierFitter()
# 정상 제외하고 분석
groups = df_study[df_study['Histology'] != 'NTL']['Histology'].unique()
for name in groups:
group_data = df_study[df_study['Histology'] == name].dropna(subset=['Months'])
if len(group_data) > 5: # 샘플 수가 너무 적은 그룹 제외
kmf.fit(group_data['Months'], group_data['Event'], label=name)
kmf.plot_survival_function(ax=ax)
plt.title("Lung Cancer Survival by Histology (GSE30219)", fontsize=15)
plt.xlabel("Months")
plt.ylabel("Survival Probability")
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()
# 인원수 및 사망자 확인
print("--- 그룹별 현황 ---")
print(df_study.groupby('Histology')['Event'].agg(['count', 'sum']).rename(columns={'sum': 'deaths'}))
- ADC: 선암
- SQC: 편평상피세포암
- NTL: 정상 폐
- BAS: 기저세포양 편평세포암
- LCC: 대세포암
- LCNE: 대세포 신경내분비암
- SCC: 소세포암
- CARCI: 유암종(신경내분비계 세포에서 발생하는 종양)
- Other: 기타
카테고리가 이렇게 있음. 저 안티그래비티 저거 주석 자동완성 해주는 건 좋은데 다 양성 폐암이라고 하면 어카냐고 아... ㅡㅡ

이렇게 봐서는 뭐가 뭔지 잘 모르실거 다 압니다.
# 암종별 평균 생존률(막대그래애프)
survival_stats = df_study.groupby('Histology')['Months'].mean().sort_values(ascending=False)
sns.barplot(x=survival_stats.index, y=survival_stats.values, hue = survival_stats.index, palette='coolwarm')
plt.title('Average Survival Months by Histology')
plt.xlabel('Histology')
plt.ylabel('Average Survival Months')
plt.tight_layout()
plt.show()
print("--- [최종 분석] 주요 암종별 평균 생존 기간 ---")
print(df_study.groupby('Histology')['Months'].mean().sort_values(ascending=False))

아더 옆에 SCC 보여요? 저게 소세포암임.
결론: 담배 끊어라
위에도 말했지만 모든 폐암환자가 담배를 피워서 걸리는 건 아니다. 근데 담배를 피우면 폐암에 걸린다. 그니까 담배를 피우면 폐암에 걸린다라는 명제는 참이지만, 여러분들도 아시죠? 어떤 명제가 참이라고 해서 그 역도 참이라는 법은 없어요. 그러니까 모든 폐암 환자들을 '담배 펴서 그렇게 됐다'고 하면 안됨.
하지만 담배를 피면 폐암에 걸립니다. 그것도 예후가 정말 안 좋은 소세포성 폐암에요. 암종별 생존률 꼴찌를 달리고, 평균 생존 기간도 정말 짧은 암이요. 병상에서 하씨 담배좀 일찍 끊을걸 하는 시간조차 주지도 않고, 암세포는 그냥 미친듯이 분열하고 전이되어서 나 자신을 급속도로 갉아먹는거예요. 기침하다가 피토해서 병원갔다? 의사쌤이 심각한 표정으로 진지하게 궁서체로 말하는 걸 듣게 될 거라고.
'Coding > Python' 카테고리의 다른 글
| 포켓몬과 이항분포 (0) | 2026.01.21 |
|---|---|
| 데이터프레임의 정보를 확인하는 몇 가지 방법 (0) | 2026.01.08 |
| 베이즈 정리 (0) | 2026.01.06 |
| 인플루엔자의 해마글루티닌 게놈을 받아서 MSA를 해보자 (0) | 2026.01.05 |
| 한타바이러스의 시퀀스를 받아서 MSA를 해보자 (후편) (0) | 2026.01.05 |