광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ
반응형

지난 이야기: 아 염색체별로 CLNSIG 비중이 이렇구나


Pathogenic 일로와봐 

pathogenic_df = clinvar_df.filter(pl.col('CLNSIG_Group') == 'Pathogenic')

이렇게 하면 됩니다.


CLNVC별로 보기

clnvc_grp = clinvar_df.group_by('CLNVC').agg(
    pl.col('CLNSIG').count().alias("Total")
).sort('Total', descending=True)

묶어드렸습니다^^ 

 

fig = go.Figure()

fig.add_trace(
    go.Bar(x = clnvc_grp['CLNVC'], y = clnvc_grp['Total'], marker_color = px.colors.sequential.algae, text = clnvc_grp['Total'])
)

fig.update_layout(
    width = 1200, height = 800,
    xaxis=dict(title='Pathogenic Variant Count', tickangle = -90),
    yaxis=dict(title='Count'),
    title = "ClinVar Group Distribution",
    margin = dict(t=50, l=10, r=10, b=10)
)

# fig.write_image('pathogenic_distributuion.png')
fig.show()

SNV가 너무 압도적인데...?

 

fig = px.treemap(
    clnvc_grp,
    path = ['CLNVC'],
    values = 'Total',
    color = 'Total',
    color_continuous_scale = 'algae',
    title = "Pathogenic Variant Distribution",
)

# text_auto 대신 update_traces를 사용하여 블록 안에 텍스트를 강제로 넣습니다.
fig.update_traces(
    # label(그룹명)과 value(숫자)를 함께 표시하라는 명령입니다.
    textinfo = "label+value",
    # 숫자 포맷을 지정합니다 (천 단위 콤마: ,d)
    texttemplate = "%{label}<br>%{value:,d}",
    # 텍스트가 블록 크기에 맞게 조절되도록 설정
    textfont_size = 20,
    insidetextfont_family = "NanumSquare_ac" # 설정하신 폰트 적용
)

fig.update_layout(
    width = 1200,
    height = 800,
    margin = dict(t=50, l=10, r=10, b=10)
)

# fig.write_image('pathigenic_distributuion_heatmap.png')
fig.show()

SNV 면적이 압도적이죠?

 

fig = go.Figure()

fig.add_trace(go.Pie(
    labels = clnvc_grp['CLNVC'],
    values = clnvc_grp['Total'],
    textinfo = 'label+percent', # 이름과 퍼센트 동시에 표시
    insidetextorientation = 'radial',
    marker = dict(colors=px.colors.qualitative.Vivid) # 전역 설정 색감 유지
))

fig.update_layout(
    title_text = "Pathogenic Variant Distribution", title_x=0.9,
    width = 1200,
    height = 800,
    margin = dict(t=50, l=20, r=20, b=10)
)

# fig.write_image('pathgenic_distributuion_pie.png')
fig.show()

음… 이거 포폴에 써먹을 수 있나…

 

상염색체가 쪽수가 많은 건 알겠는데, 뭐가 제일 많음?

# 1. 데이터에 있는 실제 값 순서 (가출 방지용)
real_values = ["Sex_Chrom", "Autosome", "Mitochondria"]

# 2. 그래프 위에 표시하고 싶은 예쁜 제목들 (순서 일치 필수!)
display_titles = ["Sex Chromosome", "Autosome", "Mitochondria"]

# 3. 서브플롯 생성
fig = make_subplots(
    rows=1, cols=3,
    subplot_titles=display_titles, # 여기서 예쁜 제목 적용!
    horizontal_spacing=0.08
)

# 4. 반복문은 실제 값(real_values)으로 돌립니다
for i, real_val in enumerate(real_values):
    curr_df = clnvc_grp.filter(pl.col('CHROM_Type') == real_val)

    # 내림차순 정렬해서 보기 좋게
    curr_df = curr_df.sort("Total", descending=True)

    fig.add_trace(
        go.Bar(
            x = curr_df['CLNVC'],
            y = curr_df['Total'],
            text = curr_df['Total'],
            texttemplate = '%{y:,}',
            textposition = 'outside',
            # 색깔은 이미 설정한 대로!
            marker_color = px.colors.sequential.algae[-(i*3+1)]
        ),
        row = 1, col = i + 1
    )

# 5. 레이아웃 정리
fig.update_layout(
    height=600,
    width=1250,
    title_text="ClinVar Distribution by Chromosome Type",
    showlegend=False,
    margin=dict(t=100, l=20, r=20, b=50), # 제목 공간 확보 위해 t(top) 늘림
)

# fig.write_image('pathgenic_chr_distributuion_subplot.png')
fig.show()

그냥 SNV가 압도적이다.

 

chr_order = [str(i) for i in range(1, 23)] + ['X', 'Y', 'MT']

# 2. 비율 계산
clnsig_perc = clnvc_grp.with_columns(
    (pl.col("Total") / pl.col("Total").sum().over("CHROM") * 100).alias("Percentage")
)

# 3. Plotly Express로 그리기
fig = px.bar(
    clnsig_perc,
    x = "CHROM",
    y = "Percentage",
    color = "CLNVC",
    text = "Percentage",
    title = "CLNVC ratio by Chromosome",
    color_discrete_sequence = px.colors.qualitative.Vivid,
    # --- 이 부분이 마법의 한 줄입니다 ---
    category_orders = {"CHROM": chr_order}
    # ----------------------------------
)

fig.update_traces(
    texttemplate='%{text:.1f}%',
    textposition='inside',
    insidetextfont_family="NanumSquare_ac" # 설정하신 폰트 적용
)

fig.update_layout(
    width=1250,
    height=800,
    barmode='stack',
    xaxis=dict(title='Chromosome'),
    yaxis=dict(title='Percentage (%)'),
    margin=dict(t=100, l=20, r=20, b=50),
)

# fig.write_image('chr_ratio.png')
fig.show()

저게 문자로 잡혀서 순서 수기로 잡아줘야 합니다. 아무튼 각 염색체별로 제일 많은 건 SNV고 그 다음에 Deletion이다. 미토콘드리아의 경우 Deletion 다음으로 Insertion, 나머지 염색체는 Deletion 다음으로 Duplication이 제일 많다.

 

# 1. 염색체 정렬 순서 정의
chr_order = [str(i) for i in range(1, 23)] + ['X', 'Y', 'MT', 'M']
# 순서대로 숫자를 매칭한 딕셔너리 생성 (1:0, 2:1, ..., X:22, Y:23...)
chr_map = {val: i for i, val in enumerate(chr_order)}

# 2. 데이터 집계 및 필터링
top_gene_df = (
    pathogenic_df
    .group_by(['CHROM', 'GENE_SYMBOL'])
    .agg(pl.count('GENE_SYMBOL').alias("Count"))
    .filter(pl.col("Count") == pl.col("Count").max().over("CHROM"))
    .unique(subset=["CHROM"], keep="first")
)

# 3. 정렬 로직 수정 (최신 Polars 버전 대응)
top_gene_df = (
    top_gene_df
    .with_columns(
        pl.col("CHROM").cast(pl.Utf8)
    )
    .with_columns(
        # replace 대신 replace_strict를 사용하고, 매핑 안 되는 값은 99로 처리합니다.
        pl.col("CHROM").replace_strict(chr_map, default=99).alias("sort_idx")
    )
    .sort("sort_idx")
)

위에도 썼지만 X, Y가 껴서 문자라 수동으로 정렬해줘야 합니다... 아무튼 각 염색체별로 변이가 가장 많은 유전자를 갖고왔다. 판다스였으면 idxmax 쓰는건데 까비...

 

# 1. 표에 들어갈 데이터 정리 (위에서 정렬된 top_gene_df 활용)
header_values = ["<b>Chromosome</b>", "<b>Top Gene Symbol</b>", "<b>Variant Count</b>"]
cell_values = [
    top_gene_df["CHROM"],
    [f"<b>{gene}</b>" for gene in top_gene_df["GENE_SYMBOL"]], # 유전자 이름 강조
    [f"{count:,}" for count in top_gene_df["Count"]]           # 천 단위 콤마
]

# 2. Plotly Table 생성
fig = go.Figure(data=[go.Table(
    # 컬럼 너비 비율 조절
    columnwidth = [100, 150, 120],

    # 헤더 스타일
    header = dict(
        values = header_values,
        fill_color = '#2E4A3F', # Algae 계열의 짙은 색
        align = 'center',
        font = dict(color='white', size=16, family="NanumSquare_ac"),
        height = 40
    ),

    # 셀 스타일
    cells = dict(
        values = cell_values,
        fill_color = ['#F5F5F5', 'white'], # 줄바꿈 배경색 효과
        align = ['center', 'center', 'right'],
        font = dict(color='#333', size=14, family="NanumSquare_ac"),
        height = 30
    )
)])

# 3. 레이아웃 설정
fig.update_layout(
    title = "Top Mutated Gene by Chromosome (ClinVar Pathogenic)",
    width = 800,
    height = 900, # 데이터가 24줄(1~22, X, Y, M) 정도 되니 높이를 넉넉하게
    margin = dict(t=80, l=20, r=20, b=20)
)

# fig.write_image('chr_gene.png')
fig.show()

그… 여러분… Plotly에서는 표를 그릴 수 있습니다. 뭐 마우스 올려도 뭐 없긴 한데 아무튼.

 

근데 이런것도 될 줄은 몰랐지… 이게 뭔 표임? 각 염색체별로 변이가 가장 많은 유전자들만 꼽아서 그 중에서 1, 2, 3등을 매긴 결과다. BRCA2, TTN, BRCA1 순으로 변이가 많다. 근데 저 SRY는 뭐죠? 성별 결정하는 유전자입니다. 찾아보니 고환 결정 인자란다.

 

# 1. 염색체 정렬 순서 정의
chr_order = [str(i) for i in range(1, 23)] + ['X', 'Y', 'MT', 'M']
# 순서대로 숫자를 매칭한 딕셔너리 생성 (1:0, 2:1, ..., X:22, Y:23...)
chr_map = {val: i for i, val in enumerate(chr_order)}

# 2. 데이터 집계 및 필터링
top_gene_df = (
    pathogenic_df
    .group_by(['CHROM', 'CLNVC','GENE_SYMBOL'])
    .agg(pl.count('GENE_SYMBOL').alias("Count"))
    .filter(pl.col("Count") == pl.col("Count").max().over("CHROM"))
    .unique(subset=["CHROM"], keep="first")
)

# 3. 정렬 로직 수정 (최신 Polars 버전 대응)
top_gene_df = (
    top_gene_df
    .with_columns(
        pl.col("CHROM").cast(pl.Utf8)
    )
    .with_columns(
        # replace 대신 replace_strict를 사용하고, 매핑 안 되는 값은 99로 처리합니다.
        pl.col("CHROM").replace_strict(chr_map, default=99).alias("sort_idx")
    )
    .sort("sort_idx")
)
# 1. 변이 수 기준으로 데이터 정렬 (표 상단에 TOP 3가 오게 하려면 여기서 정렬)
# 현재 top_gene_df가 염색체 순서라면, 강조 로직을 수치 기준으로 적용해야 합니다.
top_gene_df = top_gene_df.sort("Count", descending=True)

# 2. 행별 배경색 결정 함수
def get_rank_color(i):
    if i == 0: return '#FFD700' # Gold (1위: BRCA2)
    if i == 1: return '#E5E4E2' # Platinum/Silver (2위: TTN)
    if i == 2: return '#CD7F32' # Bronze (3위: BRCA1)
    return 'white' if i % 2 == 0 else '#F9F9F9' # 나머지 가독성용 줄무늬

row_colors = [get_rank_color(i) for i in range(len(top_gene_df))]

# 3. 데이터 구성
header_values = ["<b>Chromosome</b>", "<b>CLNVC</b>", "<b>Top Gene Symbol</b>", "<b>Variant Count</b>"]
cell_values = [
    top_gene_df["CHROM"],top_gene_df["CLNVC"],
    [f"<b>{g}</b>" if i < 3 else g for i, g in enumerate(top_gene_df["GENE_SYMBOL"])],
    [f"{c:,}" for c in top_gene_df["Count"]]
]

# 4. Table 생성
fig = go.Figure(data=[go.Table(
    columnwidth = [80, 150, 100],
    header = dict(
        values = header_values,
        fill_color = '#2E4A3F', # 헤더는 짙은 Algae색 유지
        align = 'center',
        font = dict(color='white', size=16, family="NanumSquare_ac")
    ),
    cells = dict(
        values = cell_values,
        fill_color = [row_colors] * 3,
        align = ['center', 'center', 'right'],
        # 글자색은 무조건 검은색 계열로 고정하여 가시성 확보
        font = dict(color='#333', size=14, family="NanumSquare_ac"),
        height = 35,
        line_color = '#E5E5E5' # 셀 구분선 살짝 추가
    )
)])

fig.update_layout(
    title = "Top 3 Mutated Genes Highlighted",
    width = 750,
    height = 1010,
    margin = dict(t=80, l=20, r=20, b=20)
)

# fig.write_image('chr_gene_clnvc.png')
fig.show()

보시면 미토콘드리아(MT-ATP6) 빼고 다 SNV죠? 저게 개인적으로는 나비효과라고 생각하는데, 염기 하나가 바뀐 것 때문에 단백질이 바뀌고, 그걸로 인해서 기능에 문제가 생기는 게 질병으로 이어지기 때문이다. 그러니까 아주 작은 염기 하나가 바뀌는 것 때문에, 염기보다 훨배 큰 사람이 영향을 받는다 이거지.

 

반대로 변이가 가장 적은 유전자인데... 이게 1? 왜 1이죠? 일단 이렇다 하고 단정지을 수는 없는데... 치사유전 아세요? 혈우병이 치사유전이잖아요. 여자의 경우 혈우병 유전자를 양쪽 염색체가 둘 다 갖고있으면 보통은 사산됩니다. 물론 생물학에는 100%가 없으니 그 와중에도 정말 드문 확률로 살아서 나올 가능성은 있지만...

 

웨스턴블록 해본 사람들은 표적 단백질 말고 컨트롤? 그런거 같이 매겨서 밴드 굵기 보셨을텐데 이걸 왜 볼까요? 그죠. 이거 발현 안되면 X되는 단백질이라 보는겁니다. 풀때기에 엽록체가 없다고 생각해보세요. 광합성을 못해서 멀리 가십니다. 

 

아니 왜 갑자기 웨스턴 얘기가 나옴? 나도 100% 그렇다고는 못 하지만, 저 유전자들은 변이가 생기면 질병 생기는 수준이 아니라 그냥 죽거나(생존을 못하거나) 대를 못 이을 가능성이 있습니다.

반응형