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

전에 깔짝깔짝 판다스랑 비교했던 폴라스로… EDA가 될지 해봤다. 그래서 전에 했던거랑 내용은 같은데, 비교하는 툴이 달라지는겁니다. 이거 아마 포폴에도 폴라스 플롯틀리로 올라갈듯함. 근데 새로 나온건 알겠어, 이걸 써봐야 해? 네카라쿠배의 배에서 쓴답니다.

 

전처리는 이전 과정이랑 비슷하니까 그룹바이랑 필터 위주로 ㄱㄱ합시다.


clinvar_df = pl.read_csv('data/clinvar_20260404.csv', infer_schema_length=0)

얘는 판다스에서 붙는 메모리 관련 옵션이 아예 안 붙는다. 근데 뭔가 붙어있지 않냐고? 걍 열면 Original error: invalid primitive value found during CSV parsing 에러 뜨니까 걍 다 읽고 판별하셈 한 겁니다. 그렇게 해도 1초 좀 넘게 걸려요.


CLNSIG으로 묶기

이게 일단 돌리는 방식은 예전이랑 동일한데, 데이터가 달라져서(4월 4일자껄로 진행) 결과가 달라질 수 있다. 4월 4일자에 든 게 4,403,603개고 전에 했던건 좀 적게 들어있었음... 그래서 총계 이런거에서는 차이가 날 수밖에 없음.

 

clinvar_df.group_by('CLNSIG_Group').agg(
    pl.col('CLNSIG').count().alias("Total")
)

놀라운 사실을 하나 알려주지면, Polars에서는 단순히 그룹바이가 되는 걸 떠나서 .agg로 집계 매긴 다음 그 집계 칼럼에 별칭을 붙일 수 있다. 판다스도 되긴 되겠지만 쟤는 한번에 저게 된다. 두번째줄 pl.col('CLNSIG').count().alias("Total")가 무슨 의미냐면 CLNSIG 칼럼으로 묶고 셀 건데 그 결과물 칼럼 이름을 Total로 해달라는 의미. SQL 해보셨으면 감이 좀 왔을 것이다. 

 

당연한 얘기지만, alias 없어도 저 코드는 작동을 한다. 근데 어지간하면 있는 편이 좋다.

alias 안 주면 Total이 아니라 CLNSIG으로 나오는데 이게 뭔줄 앎? 그럼 이걸로 이제 플롯틀리 그래프를 그리기 전에 정렬을 좀 해야되는데…

 

clinvar_df.group_by('CLNSIG_Group').agg(
    pl.col('CLNSIG').count().alias("Total")
).sort("Total", descending=True) # 정렬을~ 돌려다아아아오~

걍 뒤에 소트 붙이시면 알아서 정렬 됩니다. 모든 정렬은 오름차순이 기본이니 내림차순으로 정렬해주면 된다. 이걸 이제 그대로 플롯틀리에 떤지면 안되고… 변수명 할당해서 줘야됨.

 

fig = go.Figure()

fig.add_trace(
    go.Bar(x = clnsig_grp['CLNSIG_Group'], y = clnsig_grp['Total'], marker_color = px.colors.sequential.Cividis,text = clnsig_grp['Total'])
)

fig.update_layout(
    width = 1200, height = 800,
    xaxis=dict(title='ClinVar Significance Group'),
    yaxis=dict(title='Count'),
    title = "ClinVar Group Distribution"
)

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

여러분들은 여기서 피똥 쌀 것이다. 일단 나는 여기서 피똥 쌌음... 아니 전역으로 컬러테이블 박아놨는데 마커는 또 일일이 주래요 이게 말이야 당나귀야 세상에...

 

그 결과물이 이거임. 다행히도 폰트 전역으로 설정해둔 건 듣는다. 어 근데 저기 저장 코드는 왜 주석처리 했어요? Plotly는 그래프 복사가 안 되고 저장한 다음에 올려야 하는데, 문제는 그 저장하는걸 할 때 시간이 증말 완전 개같이 오래 걸립니다. 그래서 그래프를 보여줄때는 저 코드에 주석을 씌워놓고 그래프_최종_진짜최종_저장.png 느낌으로 저장할때만 주석을 해제하는 것이다. 참고로 저거 그냥 저장되는 거 아니고 kaleido 까셔야 합니다.

 

# 트리맵은 Plotly Express(px)가 훨씬 직관적입니다.
fig = px.treemap(
    clnsig_grp,
    path = ['CLNSIG_Group'], # 계층 구조 (여기선 그룹 하나)
    values = 'Total',
    color = 'Total',         # 숫자에 따라 색상 농도 조절
    color_continuous_scale = 'algae',
    title = "ClinVar Significance Hierarchy"
)

# 전역 설정된 폰트 적용 확인
fig.update_layout(width=1200, height=800)
# fig.write_image('group_distributuion_heatmap.png')
fig.show()

이 트리맵에는 문제가 하나 있다. 저기 막대그래프를 보시면 Risk/Other가 다른 값들에 비해 비중이 좀 적죠? 예. 블록이 안보입니다. 착한 사람만 보이는 블록임.

 

fig = go.Figure()

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

fig.update_layout(
    title_text = "ClinVar Significance Proportion",
    width = 1200,
    height = 800,
    margin = dict(t=50, l=10, r=10, b=10)
)

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

여기서도 안보이죠? 이정도면 거의 주서터널현미경 빌려서 봐야됨… ㅋㅋ

 

CLNSIG-Chromosome

clnsig_grp = clinvar_df.group_by(['CLNSIG_Group', 'CHROM_Type']).agg(
    pl.col('CLNSIG').count().alias("Total")
).sort('CLNSIG_Group')

CLNSIG-염색체 그룹으로 묶어보았다. 언노운은 몇 번 염색체인지 불명인 것 같음.

 

max_total = clnsig_grp['Total'].max()
sizeref = 2. * max_total / (100**2) # 100은 최대 버블 반지름(픽셀)

fig = go.Figure()

# 2. 그룹별로 순회하며 트레이스 추가
# 이렇게 하면 전역 설정된 colorway 순서대로 색이 입혀집니다!
for group_name in clnsig_grp['CLNSIG_Group'].unique():
    # 해당 그룹 데이터만 필터링
    curr_df = clnsig_grp.filter(pl.col('CLNSIG_Group') == group_name)

    fig.add_trace(go.Scatter(
        x = curr_df['CLNSIG_Group'],
        y = curr_df['CHROM_Type'],
        name = group_name, # 범례에 표시될 이름
        mode = 'markers+text',
        marker = dict(
            size = curr_df['Total'],
            sizemode = 'area',
            sizeref = sizeref,
            sizemin = 10,
            # 여기서 color를 지정하지 않으면 전역 설정(colorway)을 따라갑니다!
        ),
        text = curr_df['Total'],
        texttemplate = "%{x}<br>%{text:,d}",
        textposition = "top right",
        textfont = dict(family="NanumSquare_ac", size=12)
    ))

# 3. 레이아웃 설정 (컬러바는 어차피 안 나오지만 확인사살)
fig.update_layout(
    width = 1200,
    height = 800,
    title = "ClinVar Significance by Group (Global Palette Applied)",
    showlegend = True, # 그룹별로 색이 다르니 범례를 보여주는 게 좋습니다
)

# fig.write_image('cln-chr bubble.png')
fig.show()

스읍… 이거 색깔을 x축별로 바꾼건데… algae로 했더니 표가 너무 안남..

 

오… 일단 언노운 퇴근시키자.

 

굿. 저 버블이랑 라벨 간격은 우리가 조절을 못해요...

 

염색체별 CLNSIG 비율

fig = go.Figure()

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

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

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

이거 일단 이대로도 볼 수는 있거든요? 근데 문제가 하나 있음.

 

악 내눈!

 

일단 왜 이런 사태가 일어났는지를 먼저 봐야 한다. clinVar 데이터에 들어있는 염색체가 언노운 빼고 총 25종인데 그 25종이 각개가 아니라 1~22번까지가 상염색체(남녀 다 똑같이 있는거), X랑 Y가 성염색체(남녀 분포 달라요), 그리고 미토콘드리아가 있어요. 이게 다 길이가 같다고 쳐도 쪽수때문에 일단 상염색체 물량이 압도적인데 염색체 그림 찾아보면 어때요? 1번 봐봐요. 크기가 벌써부터 이놈은 키가 커서 1번인가 하게 길어요…

 

그러니까 상염색체에 비하면 다른 염색체들은 거의 뭐 종합운동장에 축구공 하나씩 굴러다니는 정도로 미미한 수준이라 저렇게 나오는거다. 저걸 파훼할 방법이 있냐고?

 

# 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 = clnsig_grp.filter(pl.col('CHROM_Type') == real_val)

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

    fig.add_trace(
        go.Bar(
            x = curr_df['CLNSIG_Group'],
            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('group_distributuion_subplot.png')
fig.show()

서브플롯을 쓰던가 

 

# 1. 비율(Percentage) 계산 (Polars 활용)
clnsig_perc = clnsig_grp.with_columns(
    (pl.col("Total") / pl.col("Total").sum().over("CHROM_Type") * 100).alias("Percentage")
)

# 2. Plotly Express로 그리기 (비율 차트는 PX가 압도적으로 편합니다)
fig = px.bar(
    clnsig_perc,
    x = "CHROM_Type",
    y = "Percentage",
    color = "CLNSIG_Group", # 그룹별 색상
    text = "Percentage",
    title = "ClinVar Significance Proportion (100% Stacked)",
    color_discrete_sequence = px.colors.qualitative.Vivid # 아까 쓰신 Prism!
)

fig.update_traces(texttemplate='%{text:.1f}%', textposition='inside')
fig.update_layout(width=1200, height=800, barmode='stack')

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

누적... 그리셔도 되는데 문제가 뭐다? Risk/Other가 안보여요...

 

이제 다음편에서 Pathogenic한 변이만 골라서 또 해볼거다. 이거 한번에 하면 님들 데이터 요금 급나나가요.

반응형