(270)

Polars 데이터프레임도 시각화가 되나요?

보통 판다스 데이터프레임 불러와서 지지고 볶고 뭐 해요? 그래프 그리죠. 표로 정리해서 보여주는것보다 그래프 딱 만들어서 도표 딱 보여주면 기깔나쟎아요? 그겁니다. 그리고 우리가 제일 많이 쓰는 맷플롭이나 씨본(+Plotly)에서도 폴라스를 받아줄지 궁금해서 해봤습니다. 이번에 써 볼 데이터프레임은 파일 불러온거 하나(켐플) 있고, 직접 만든거 하나 있습니다.import polars as plimport matplotlib.pyplot as pltimport seaborn as snsimport plotly.express as px # 프로젝트 이런거 아니니까 걍 얘 부를게여위에서부터 순서대로 폴라스, Matplotlib, 씨본, Plotly임다. 불러오십쇼. pyarrow는 불러올 필요 없고 설치만 하..

Polars를 써보자

엥? 폴라스? 그게 뭐예요오? Polars is an open-source library for data manipulation, known for being one of the fastest data processing solutions on a single machine. It features a well-structured, typed API that is both expressive and easy to use. 뭐라는겨 싶겠지만 폴라스는 판다스 비슷한 일을 한다. 데이터프레임을 만들거나 불러오거나 하는 모든 일들이 가능한데 일단 읽는 속도가 판다스보다 20배 빨랐음. 뭐 메모리 어쩌고 하던데 나는 컴퓨터 아키텍쳐까지는 잘 모르니까 패스하고... 판다스랑 좀 다른 부분이 있다는 건 유념하십쇼.im..

Medical Cost Personal Datasets

이거어어어어는... 돌리는건 하나 돌렸습니다. 그럼 왜함? 그 옵튜나인가 뭔가 하는 거 써보려고 했음.Optuna파이썬 라이브러리인데, 하이퍼파라미터 튜닝할때 쓴다. 모델 학습 전 사용자가 직접 설정하는 외부 구성 변수를 하이퍼파라미터라고 하는데, 이걸 사람이 수동으로 일일이 조정해가면서 어느 조건에서 내 모델을 뽕을 잘 뽑을지를 고민...하면서 일일이 하다 보면 할 것도 많고 파라미터 하나하나 일일이 손대가면서 찾기도 힘들잖아요? 그 노가다를 알아서 해주는게 옵튜나입니다. 모델 바이 모델이라 함수에 집어넣는 게 다른데, 아무튼 이거 이거 이거 해줘 하면 지 알아서 음 이렇군 하면서 오케이 가릿 이걸로 진행시켜 한다. 모델에 따라서는 시간이 좀 걸리기도 합니다.안하면 섭한 전처리자녀 수 범주화# 자녀 ..

Red Wine Quality

오늘은 회귀입니다. …회기는 그 경희대 있는 지하철역이예요 선생님들.주성분분석머고 오늘은 ML 안함? 아니 할건데… 저게 회귀분석 할거라고 했죠? 저 데이터 칼럼이 12개인데 하나빼고 다 독립변수니까 지금 독립변수가 11개거든요. 그거 그대로 때려박으면 다중공선성 터질수도 있으니까 압축할 수 있는건 압축하고 가자 이겁니다. 11개 언제 넣었다 뺐다 할거임? wine_x = wine.copy()X = wine_x.drop('quality', axis=1)y = wine['quality']# 1. 스케일링 (PCA 전 필수!)scaler = StandardScaler()X_scaled = scaler.fit_transform(X)# 2. PCA 객체 생성 (일단 모든 성분을 다 뽑아봅니다)pca = PCA(..

Palmer Archipelago (Antarctica) penguin data

예… 그… 펭귄 데이터입니다. 그건 아는데 이걸 왜 꺼냈냐… 분류 할거라서 꺼냈습니다. 예. 아니 진짜 할거임.전처리 하기 전에...그냥 EDA였으면 범주화하고 결측값 확인하고 채우거나 날리거나 했을텐데, 이번에는 그렇게 하고 땡 하면 안된다. 왜냐… 무작정 아무 칼럼이나 학습하는데 썼다간 모델 성능이 떨어지거든요. 그리고 범주형 칼럼중에 학습에 쓸 칼럼은 인코딩도 해 줘야 한다. 학습에 쓸 수 없는 칼럼 날리기내 일일이 올리기 귀찮아서 올리지는 않는다만... 분석하기 전에 항상 .head()랑 .column 써서 뭐 있는지 보고 가죠? shape는 잘 안씀... 아무튼. 거기서 칼럼들을 확인해보고 우선 날릴 것부터 정할거다.# Copypenguin_drop = penguin.copy()# 하고 날려날려..

웹 프로젝트를 깃헙에 올릴 수 있다? 뿌슝빠슝

처음에는 이거 또 복잡한거 아냐? 했는데 진짜 간단합니다.1. 깃헙에 저장소를 만들 때 저장소 이름을 (프로젝트명).github.io로 한다.2. 그 폴더에 readme.md 생성된걸 지우고 HTML, CSS, JS를 올리면 되는데 파일명을 index.html로 해야 한다.3. 풀 커밋 푸시하고 좀 기다렸다가 브라우저 주소창에 (프로젝트명).github.io 치고 접속하면 땡임. 유지보수가 어려운거 아니냐고요? 깃헙 저장소니까 그 안에 있는거 열고 수정하고 풀 커밋 푸시하고 좀 기다리면 반영됨.

포폴 웹페이지화-더 이상의 자세한 설명은 생략한다

어제 그 뼈대 올라왔잖아요? 그러고나서 밤에 잡아서 내용 다 채웠음. 농담같죠? 진짜임.어바웃 페이지뭘 많이 가렸죠? 다 개인정보라 어쩔 수 없음.. 그럼 논문하고 자격증은 왜 안 가렸냐… 논문은 이미 저널에 실린거고 자격증도 내가 땄다고 올렸잖아요. 쟤들은 이미 한번 깐거라 안 가린거임. 원래 생각했던 구성에는 저 카드가 없었습니다. 없었는데 제미나이가 "씁 근데 이거 이렇게만 보면 좀 심심하지 않을까요?라고 하면서 제안한겁니다. 저 선이랑 배경도 지금 조정중인데 선이 굵다고 이쁜건 아니고... 없애봤는데 이것도 아 씁 아닌 것 같고... 그림자는 그래도 배경이나 안이나 톤이 다 연해서 있는 게 나은 것 같긴 하고... 애매함. 그냥 애매함./* 개별 오브젝트를 감싸는 카드*/.card { wid..

포폴 웹페이지화-뼈대 잡기

내용이요? 이제 생각해야지.여기까지 해서 뼈대가 잡혔고 탭 메뉴도 잘 돌아간다. 그럼 이제 내용물만 채우면 됩니다… 1. 저 언더 컨스트럭션 란에는 내 이름과 한줄 설명이 들어갈 예정이다. 그래서 일부러 넓게 안 잡았다. 꽉 차보여도 저 분량이 차지하는 건 가로 75%정도… (모바일은 미뎌쿼리 줘서 95%)2. About에는 본인 약력, 자격증이 들어가고 skills는 뭐 할 수 있냐고 project에 개인적으로 했던 모든 프로젝트들이 들어갈 예정이다.3. About 단을 하나로 할지 두개로 할 지 생각중임... 한쪽은 넓게 잡아서 내 소개 하고(아직 데이터쪽 약력이 없음...) 한쪽에 좀 좁게 잡아서 자격증이랑 외부 활동 내역같은 거 쓸까...4. Project에 아코디언 패널 들어갑니다. 아코디언 패..

개 얼탱이 없는 작업이 온다 두둥

이게 뭐냐고요? 포폴을 웹으로 만들어서 서버에 올리자는 진심 얼탱이없는 작업에 들어갈 예정입니다. 근데 지금 팀플때문에 바빠서 구조 구상하고 색깔만 짜놨음. 대충 그렸음 대충... 1. 내 이름이랑 전번 이메일 깃헙 블로그(티스토리) 링크가 헤더에 들어가고(이부분도 고민 좀 해봐야됨...)2. 그 밑에 내 이력이랑 스킬(뭐뭐 쓸 수 있나) 프로젝트가 들어가는데 이게 탭메뉴입니다. 그니까 얘를 탭하면 전환이 돼야 하는데 이걸 자바스크립트로 해야 하고…3. 프로젝트에는 내 포폴에도 올라가는 프로젝트 세 개가 올라가는데 그거에 대한 설명을 개별 프로젝트로 아코디언 메뉴로 하고 PDF파일을 거기다가 올리든가 할겁니다. 링크 관련해서는 이걸 폰트어썸 아이콘만 넣을지(7.x로 올렸더만…) 이름을 병기할지정도 고민중..

Google Play Store – Most Downloaded Android Apps

이거는 말 그대로 플레이스토어 앱 정보가 있는 데이터인데… 본인 아이폰 씁니다. 근데 왜 플레이스토어죠? 앱스토어 데이터가 없다. 참고로 전처리 할거 꽤 있으니까 잘 따라오십쇼.전처리가격 정상화가격 정상화는 투트랙으로 이뤄질건데, 일단 앱 가격에 붙어있는 $를 다 빼고 float으로 만들어줄거다. 그리고 결측값도 채워줄건데, 결측값이 있는 앱들은 다 무료 앱이라 0으로 때울거다. # 일단 저 달러부터 떼보시죠 playstore_df['Price']0 NaN1 NaN2 NaN3 NaN4 NaN ... 82 $4.9983 $3.4984 $6.9985 $4.9986 $3.99Name: Price, Length: 87, ..

씨본 팔레트 컬러 시뮬레이터 보수작업

https://koreanraichu.tistory.com/853 씨본 컬러 파레트 씨뮬레이터그 Seaborn에서 컬러 커마 가능한거 아시죠? 근데 이게 뭔 색인지 뭔 파레트가 어떻게 나오는지 모르잖아요. ㅇㅋ? ㅇㅇㅋ. 그래서 색 두 개를 입력받은 다음 한 10칸정도로 띄엄띄엄 칠한거 하나, ckoreanraichu.tistory.com이거 함 써보고 고칠점 찾음…입력하는 색상 앞에 #가 들어가면 #을 빼주기이게 왜 필요함? 하실 수도 있는데 컬러 파레트 만드는데서 복사할때 앞에 #이 붙는 경우가 있고, 아닌 경우가 있습니다. 근데 #이 붙으면 안됐거든요? 이러면 사용자 입장에서는 아니 내가 이거 하나 쓰자고 #을 지워야됨??? 이 되는거죠. 뭔지 아시겠죠? const color1 = first_col..

MSA에 군집분석을 끼얹어보세요!

있는것도 복잡한데 저걸 왜 넣냐고요? 라이노바이러스는 좀 덜한데, 이게 기본적으로 300개씩 찾고 그렇다보니 계통수가 무지하게 길어집니다. 이러면 이걸 넣는 나도 고통이고 읽는 사람도 고통이예요. 거의 뭔 스크롤이여 스크롤. 근데 계층적 군집분석 결과가 덴드로그램인데 이거 목 꺾고 옆으로 보면 계통수거든요? 그리고 어쨌든 묶은거니까 이거 넣어보자 해서 넣었죠.실루엣 계수이게 원래는 군집 내에서의 응집도와 다른 군집간의 거리를 비교해서 군집분석이 잘 됐는지, 안 됐는지를 평가하는 지표인데 k-means나 k-medoid에서 군집 개수 나눌때도 쓴다. 그 개수가 돌려돌려 돌림판으로 나오는게 아닙니다… 그럼 계통수는 버리는건가요? 아니, 그거 보고 대충 개수 나눌수도 있다. 이거 봐봐요 이걸로 뭘 어떻게 정할..

얘! clinvar도 EDA가 된단다! (3)

생각보다 분량은 없지만 일단 태블로로 넘어와봤다. 전에도 얘기했지만 염색체가 25개(상+성+미토)라고 했잖아요? 이거 25개를 일일이 파이썬으로 그리면 코딩하는 나도 고달프고 읽는 당신들도 고달프다. 근데 태블로에는 대시보드라는 게 있어요. 그래서 시트 만들고 대시보드에서 필터걸고 염색체별로 볼 수 있음. 파워포인트 애니메이션마냥 스무th합니다 움직임도.대시보드+스토리보드 구성1페이지각 염색체별... 그니까 1번, 2번 이런 식으로 볼 수 있다. 염색체별 변이 TOP 25(변이가 가장 많은 상위 25개), 염색체 내 유전자의 CLNSIG 및 CLVNC 비율이 들어가있다. 그러니까 파이썬에서 일일이 다 할 필요 없이 여기다가 필터 걸어두고 저 표에 있는 염색체 번호를 누르면 무슨 염색체 내에서 변이가 가장..

얘! clinvar도 EDA가 된단다! (2)

그렇다. 대망의 2부가 돌아왔다. 이게 정보 확인하는거 생략하고도 분량 꽤 되니까 알아서 쫓아오십쇼. 다음편에 태블로 얘기만 할거라서 이번편에 다 끝낼거임.전처리쓸 칼럼만 추리기이게 칼럼이 되게 많은데 그걸 우리가 다 쓸 게 아니거든요? 그래서 쓸 것만 추린 다음에 데이터프레임을 재구성하고 그걸 csv파일로 보내야 합니다. 왜냐고? 그걸 보내야 태블로에서도 쓰죠.analysis_column = ['CHROM','POS','REF','ALT','CLNSIG','CLNVC','GENEINFO','CLNREVSTAT'] # 칼럼 뭐하는건지 위에 있어요1. CHROM: 염색체(몇 번 염색체인지)2. POS: 염색체 어디?3. REF, ALT: 비포&애프터 (REF에 있는 시퀀스가 ALT로 바뀐 변이다)4. CL..

얘! clinvar도 EDA가 된단다! (1)

이거 3부작입니다... 일단 분석을 하다 말았고, 태블로도 써야됨.clinvar는 유전적 변이와 인간의 표현형과의 관계에 대한 데이터를 수집하여 보관하는 데이터베이스이다. 이게 데이터가 어떻게 되어있냐면 몇번 염색체 어디에 뭐가 어떻게 뻑나면 어떤 변이더라~ 이런게 들어있는데, vcf파일입니다. 이거 분석하려면 여는것부터 골치아픔. 근데 이게 된다고요? 내가 vcf파일 상태로는 열기도 조작하기도 귀찮아서 아예 거기 안에 있는 내용을 데이터프레임화하고 csv로 만드는 코드를 짰음.VCF파일 내용물'1', '66926', '3385321', 'AG', 'A', '.', '.', 'ALLELEID=3544463;CLNDISDB=Human_Phenotype_Ontology:HP:0000547,MONDO:MOND..

Polars 데이터프레임도 시각화가 되나요?

Coding/Python 2026. 4. 10. 15:59
반응형

보통 판다스 데이터프레임 불러와서 지지고 볶고 뭐 해요? 그래프 그리죠. 표로 정리해서 보여주는것보다 그래프 딱 만들어서 도표 딱 보여주면 기깔나쟎아요? 그겁니다. 그리고 우리가 제일 많이 쓰는 맷플롭이나 씨본(+Plotly)에서도 폴라스를 받아줄지 궁금해서 해봤습니다.

 

이번에 써 볼 데이터프레임은 파일 불러온거 하나(켐플) 있고, 직접 만든거 하나 있습니다.


import polars as pl
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px # 프로젝트 이런거 아니니까 걍 얘 부를게여

위에서부터 순서대로 폴라스, Matplotlib, 씨본, Plotly임다. 불러오십쇼. pyarrow는 불러올 필요 없고 설치만 하면 됨.

 

# 피보나치 수열
dict = {
    'A':[1, 1, 2, 3, 5, 8, 13, 21, 34, 55],
    'B':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

# 을 데이터프레임으로 
df = pl.DataFrame(dict)

그리고 직접 만든 데이터는 이겁니다. 피보나치 수열임.


Matplotlib

둘다 잘 됨. 진짜로 그게 답니다. Null이 있으면 걍 이게 뭐여 패스 하는듯.

 

위가 피보나치, 아래가 산점도다. ...저기 0이 널 아니예요? 나도 그런 줄 알고 확인해봤는데 그냥 수소결합을 안 하는 놈임.

 

Seaborn

근데 왜 씨본은 색이 다르냐면 쟤는 Hue 옵션을 줬음. 쟤만 줬어요.

 

sns.scatterplot(load_df.to_pandas(), x = 'HBA', y = 'HBD')
plt.show()

산점도는 그리다가 결측값때문에 에러가 났는데, 판다스는 NaN(날짜는 NaT)이지만 폴라스는 null이잖아요? 이걸 결측값 처리 안 하고 걍 주면 씨본이 이게 뭐시여 하고 오류를 토하니까 to_pandas()를 준 겁니다. 근데 저게 pyarrow가 설치되어있어야 가능하니까 또 설치한건데… 여러분들이 EDA를 할 거라면 안 쓸 칼럼은 날릴거고, 그게 아니어도 결측값을 어떻게든 채울거잖아요? 결측값 땜질만 적절하게 하면 오류는 볼 일이 없습니다.

 

Plotly

쟤는 왜 캡쳐했나요? 플롯틀리는 그래프 복사가 안됩니다. 쟤 사이즈 왜저래요? 사이즈가 불만이면 그릴때 조절해야 합니다. 조절 안하면 저렇게 넙대대한 놈 나옵니다.

 

아무튼 둘 다 오류 없이 잘 나온 건 맞다.

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

Polars를 써보자

Coding/Python 2026. 4. 9. 13:11
반응형

엥? 폴라스? 그게 뭐예요오?

 

Polars is an open-source library for data manipulation, known for being one of the fastest data processing solutions on a single machine. It features a well-structured, typed API that is both expressive and easy to use.

 

뭐라는겨 싶겠지만 폴라스는 판다스 비슷한 일을 한다. 데이터프레임을 만들거나 불러오거나 하는 모든 일들이 가능한데 일단 읽는 속도가 판다스보다 20배 빨랐음. 뭐 메모리 어쩌고 하던데 나는 컴퓨터 아키텍쳐까지는 잘 모르니까 패스하고... 판다스랑 좀 다른 부분이 있다는 건 유념하십쇼.


import polars as pl

일단 판다스처럼 폴라스도 깔고 불러야 쓸 수 있다. 넘파이...는... 불러야되나?

 

파~일을 열어다~오~

df_csv = pl.read_csv("data/Fluoxetine.csv", separator=";", null_values=["", '""', 'None']) # ChEMBL은 구분자가 세미콜론임다 
df_csv2 = pd.read_csv("data/Fluoxetine.csv", sep=";")

여기서 문제가 하나 있음. 저기서 어떤게 폴라스고 어떤게 판다스게요? 위에껍니다. 세부적인 옵션 빼고 걍 불러오는건 폴라스나 판다스나 비슷해서 pl pd로 구별해야되는데 이게 하다보면 그게 잘 안됩니다. 둘 중 하나만 쓰거나 둘 다 써야되는 상황이면 변수명으로 구별하던가 주석을 다십시오.

 

separator=";"는 csv 구분자 옵션인 거 알겠는데 저 뒤에놈은 뭔가요? 저게 켐블 불러온건데, 구분자가 ;이고 없는건 걍 없거든요? 그러면 판다스는 아 없군 결측값! 하는데 폴라스는 ""로 때워버립니다. 이게 그리고 NaN이랑 null이랑 달라요 폴라스는. 폴라스에서는 결측값이 널인데 이건 SQL의 널 비슷하게 흘러갑니다. ""는 어쨌든 뭐가 있는거니까 결측값이 아니래요. 그래서 부를때 자 이게 결측값이야~ 도 같이 해 준 거다.

 

참고로 같은 파일 불러오는데 폴라스는 2밀리초, 판다스는 21밀리초 걸림.

 

내 하드에 저-장

쓰는것도 코드 자체는 차이가 없다. to_csv 하면 되는'데' 저장한 결과물에서 차이가 난다. 판다스 써보신 분들 to_csv로 저장한거 다시 열었더니 맨 왼쪽 칼럼에 Unnamed:0 생긴 적 있으시죠? 그런거 한 두세번 돌리면 언네임드만 주루룩 생기고 그래서 열고 드롭한 적 있으시죠? 판다스에서 csv로 저장할때는 index=False를 줘야 그게 안 생긴다.

 

그럼 폴라스는요? 폴라스는 그 옵션 안 줘도 인덱스가 따로 저장이 안 된다. 대신 저장하는데 드는 소요시간은 비슷했음.

 

결측값

위에도 썼지만 결측값이 폴라스는 널이다. 그래서 .isna() 비슷한 함수가 .is_null()이고 .isna().sum() 비슷한건 .null_count()인데... 이거 가로로 나옵니다... 그리고 폴라스는 표 출력하면 위에 shape가 같이 나옴.

 

쿼리 되나요?

# 1. 컬럼명 변경 (성공적)
df_csv = df_csv.rename({"ChEMBL ID": "ChEMBL_ID"})

# 2. 컨텍스트 및 테이블 등록
ctx = pl.SQLContext()
ctx.register("chembl_table", df_csv)

# 3. 쿼리 실행 (쌍따옴표 제거!)
result = ctx.execute("""
    SELECT * FROM chembl_table 
    WHERE ChEMBL_ID = 'CHEMBL153036'
""").collect()

result

폴라스에서는 당신이 SQL 쌉고인물이라면 파이썬으로 불러와놓고 SQL 쿼리를 쓸 수 있다. 실화임. SELECT * FROM chembl_table WHERE ChEMBL_ID = 'CHEMBL153036' <<이게 SQL 쿼리예요.

 

df_csv.filter(pl.col("ChEMBL ID") == "CHEMBL153036")

아니 그럼 SQL 알못들은 어쩌라고요! 아 필터 쓰십쇼.

 

데이터의 정보를 확인하는 방법

요즘 EDA할때 생략하는 그거 맞다. 폴라스... 하게되면 이거는 정보 확인하는거 한번 올려드림. 판다스랑 기능은 똑같은데 토해내는게 달라요.

 

df_csv.schema

.info() 비슷한 기능을 하는게 .schema인데 인포처럼 칼럼 몇갠데 몇개 비었고 이런건 안나오고 뭔 타입인지만 나온다. 인포 비슷한거 필요하시면 .glimpse() 쓰십쇼. 아니면 널카운트? 

 

아니면 .describe()에서 칼럼별 통계값 보고 알 수 있다. 예? 뭔 소리임? 폴라스에서는 널이 결측값이라고 했잖아요? 근데 SQL에서 연산에 널끼면 다 널됩니다. 왜냐고요? 널이 응 아니 몰라에서 몰라거든. 설문조사로 치자면 예 아니오 무응답에서 무응답이다. 슈뢰딩거의 고양이는 박스 까보면 얘가 갔는지 있는지는 알 수 있지만 널은 박스 밀봉된 슈뢰딩거의 고양이라 진짜 모름.

 

이 글에서는 그냥 데이터 열고 정보 확인하는것만 비교해봤는데, 다음편에서는 시각화 어떻게 되나 해 볼 예정이다. matplotlib나 seaborn에서는 폴라스를 못 쓴다는 얘기가 있음.

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

Medical Cost Personal Datasets

Coding/EDA 2026. 3. 18. 20:20
반응형

이거어어어어는... 돌리는건 하나 돌렸습니다. 그럼 왜함? 그 옵튜나인가 뭔가 하는 거 써보려고 했음.


Optuna

파이썬 라이브러리인데, 하이퍼파라미터 튜닝할때 쓴다. 모델 학습 전 사용자가 직접 설정하는 외부 구성 변수를 하이퍼파라미터라고 하는데, 이걸 사람이 수동으로 일일이 조정해가면서 어느 조건에서 내 모델을 뽕을 잘 뽑을지를 고민...하면서 일일이 하다 보면 할 것도 많고 파라미터 하나하나 일일이 손대가면서 찾기도 힘들잖아요? 그 노가다를 알아서 해주는게 옵튜나입니다. 

 

모델 바이 모델이라 함수에 집어넣는 게 다른데, 아무튼 이거 이거 이거 해줘 하면 지 알아서 음 이렇군 하면서 오케이 가릿 이걸로 진행시켜 한다. 모델에 따라서는 시간이 좀 걸리기도 합니다.


안하면 섭한 전처리

자녀 수 범주화

# 자녀 수는 모르겠고 유무로 범주화할거임. ㅇㅋ? ㅇㅇㅋ.
cost['have_child'] = cost['children'].apply(lambda x: 0 if x == 0 else 1) # 자녀수가 0이면 0, 0보다 크면 1
# 유자녀가 1입니다

심플하게 유자녀 무자녀로 범주화했다.

 

일부 칼럼 퇴근시키기 

cost.drop('region', axis = 1, inplace = True)
cost.drop('children', axis = 1, inplace = True)

자녀 칼럼: 범주화했으니까 퇴근해도 됨/지역 칼럼: 학습에 도움 안 될 것 같아서 퇴근

 

스케일뤄어어어어

scaler = StandardScaler()

cost[['age','bmi']] = scaler.fit_transform(cost[['age','bmi']])

이정도면 다들 저거 뭔지 아시죠?

 

매핑

# 흡연여부
cost['smoker'] = cost['smoker'].map({'yes': 1, 'no': 0})
cost['sex'] = cost['sex'].map({'male': 1, 'female': 0})

아니 성별은 원핫인코더 쓰려고 했더니 에러토하데… ㅡㅡ


옵튜나 써보기

# 1. 독립변수(X)와 종속변수(y) 분리
X = cost.drop('charges', axis=1)
y = np.log1p(cost['charges']) # 아까 말씀드린 로그 변환!

# 2. 데이터 분할 (보통 8:2나 7.5:2.5로 나눕니다)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"학습 데이터 개수: {len(X_train)}")
print(f"테스트 데이터 개수: {len(X_test)}")

근데 우리 이거 쓰기 전에 학습용 테스트용 나눠야되는건 아시죠?

 

def objective(trial):
    param = {
        'n_estimators': trial.suggest_int('n_estimators', 500, 2000),
        'max_depth': trial.suggest_int('max_depth', 3, 9),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.1),
        'subsample': trial.suggest_float('subsample', 0.6, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
        'random_state': 42
    }

    model = XGBRegressor(**param)
    model.fit(X_train, y_train)

    preds = model.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, preds))

    return rmse

# 최적화 시작
study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=50) # 50번 정도만 돌려봐도 감이 옵니다.

print(f"최고의 파라미터: {study.best_params}")

이렇게 범위를 정해주면... 그 범위 뭐가 뭔지 모르겠으면 에미나이나 지피티, 클로드한테 나 이 모델 돌릴건데 옵튜나 짜줘 하시면 됩니다... 아무튼. 저렇게 주고 맞춰주십쇼 하면 옵튜나가 아 ㅇㅋㅇㅋ 해봅시다! 하면서 뭔가 돌아가요.

 

최고의 파라미터: {'n_estimators': 815, 'max_depth': 3, 'learning_rate': 0.017046309566345508, 'subsample': 0.9510680208670862, 'colsample_bytree': 0.8874051559739393}

그리고 다 돌아가면 이렇게 돌리면 이렇게 나온다! 하고 알려줍니다. 회귀 돌릴때 옵튜나 쓰면 최상의 파라미터랑 그걸로 돌렸을때 R^2도 같이 나옴.

 

학습

model = XGBRegressor(**study.best_params)
model.fit(X_train, y_train)

하아니 그럼 저 파라미터를 다 복붙해야 하나요? 아니, 그런 귀찮은 짓을 할 필요가 없어요. 우리는 저 파라미터를 변수에 할당해뒀으니까 그거 앞에 애스터리스크 두개 붙여서 모델에 넣으십쇼.

 

# 1. 예측 수행 (로그 상태)
y_pred_log = model.predict(X_test)

# 2. 역변환 (로그 -> 원래 달러 단위)
y_pred = np.expm1(y_pred_log)
y_actual = np.expm1(y_test)

# 3. 성능 지표 확인
print(f"R² Score (결정계수): {r2_score(y_actual, y_pred):.4f}")
print(f"MAE (평균 절대 오차): ${mean_absolute_error(y_actual, y_pred):.2f}")
print(f"MSE (평균 제곱 오차): ${mean_squared_error(y_actual, y_pred):.2f}")
print(f"RMSE (제곱근 평균 제곱 오차): ${np.sqrt(mean_squared_error(y_actual, y_pred)):.2f}")
R² Score (결정계수): 0.8768
MAE (평균 절대 오차): $1999.14
MSE (평균 제곱 오차): $19125564.44
RMSE (제곱근 평균 제곱 오차): $4373.28

?? 근데 내 예상보다 더 잘나왔어 이거...

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

Red Wine Quality

Coding/EDA 2026. 3. 13. 20:00
반응형

오늘은 회귀입니다. …회기는 그 경희대 있는 지하철역이예요 선생님들.


주성분분석

머고 오늘은 ML 안함? 아니 할건데… 저게 회귀분석 할거라고 했죠? 저 데이터 칼럼이 12개인데 하나빼고 다 독립변수니까 지금 독립변수가 11개거든요. 그거 그대로 때려박으면 다중공선성 터질수도 있으니까 압축할 수 있는건 압축하고 가자 이겁니다. 11개 언제 넣었다 뺐다 할거임?

 

wine_x = wine.copy()
X = wine_x.drop('quality', axis=1)
y = wine['quality']

# 1. 스케일링 (PCA 전 필수!)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 2. PCA 객체 생성 (일단 모든 성분을 다 뽑아봅니다)
pca = PCA()
X_pca = pca.fit_transform(X_scaled)

그나마 데이터가 다 수치형이라 FAMD 안 가고 주성분분석 했음.

 

# 전체 성분(11개)에 대해 PCA 수행
pca_full = PCA().fit(X_scaled)

# 개별 분산 설명력
individual_var = pca_full.explained_variance_ratio_
# 누적 분산 설명력
cum_var = np.cumsum(individual_var)

# 결과 출력
for i, (ind, cum) in enumerate(zip(individual_var, cum_var)):
    print(f"PC{i+1}: 개별 {ind:.2f} / 누적 {cum:.2f}")
    if cum >= 0.85 and i > 0 and cum_var[i-1] < 0.85:
        print(f"--- 여기까지 딱 끊으면 정보의 {cum*100:.1f}%가 보존됩니다! ---")
PC1: 개별 0.28 / 누적 0.28
PC2: 개별 0.18 / 누적 0.46
PC3: 개별 0.14 / 누적 0.60
PC4: 개별 0.11 / 누적 0.71
PC5: 개별 0.09 / 누적 0.80
PC6: 개별 0.06 / 누적 0.86
--- 여기까지 딱 끊으면 정보의 85.5%가 보존됩니다! ---
PC7: 개별 0.05 / 누적 0.91
PC8: 개별 0.04 / 누적 0.95
PC9: 개별 0.03 / 누적 0.98
PC10: 개별 0.02 / 누적 0.99
PC11: 개별 0.01 / 누적 1.00

제 6 주성분까지 채용하면 85% 이상의 설명력을 갖는다. 그래서 그 주성분들이 뭔데?

 

for i in range(n_comp):

    loading_scores = pd.Series(
        pca_full.components_[i],
        index=X.columns
    )

    sorted_loadings = loading_scores.abs().sort_values(ascending=False)

    print(f"\nPC{i+1} 핵심 변수 TOP3")
    print(sorted_loadings.head(3))
PC1 핵심 변수 TOP3
fixed acidity    0.489314
citric acid      0.463632
pH               0.438520
dtype: float64

PC2 핵심 변수 TOP3
total sulfur dioxide    0.569487
free sulfur dioxide     0.513567
alcohol                 0.386181
dtype: float64

PC3 핵심 변수 TOP3
alcohol                0.471673
volatile acidity       0.449963
free sulfur dioxide    0.428793
dtype: float64

PC4 핵심 변수 TOP3
chlorides         0.666195
sulphates         0.550872
residual sugar    0.372793
dtype: float64

PC5 핵심 변수 TOP3
residual sugar    0.732144
alcohol           0.350681
pH                0.267530
dtype: float64

PC6 핵심 변수 TOP3
pH                  0.522116
volatile acidity    0.411449
density             0.391152
dtype: float64

이 에미나이 변수 압축한다고 했는데 산점도 볼때부터 알아봤어야 했는데…

 

1. 제 1주성분: fixed acidity, citric acid, pH
2. 제 2주성분: total sulfur dioxide, free sulfur dioxide, alcohol
3. 제 3주성분: alcohol, volatile acidity, free sulfur dioxide
4. 제 4주성분: chlorides, sulphates, residual sugar
5. 제 5주성분: residual sugar, alcohol, pH
6. 제 6주성분: pH, volatile acidity, density

 

이렇게 나왔으면 중복 쳐내면 됩니다.

 

{'free sulfur dioxide', 'fixed acidity', 'sulphates', 'pH', 'total sulfur dioxide', 'residual sugar', 'density', 'chlorides', 'alcohol', 'volatile acidity', 'citric acid'}

저거 리스트에 다 때려박고 set으로 바꾸면 중복 다 쳐냄.

 

회귀분서억

X = X_scaled # 저기 주성분 돌리기 전에 거쳤어요 스케일러

model = LinearRegression()
model.fit(X, y)

pred = model.predict(X)
print("MAE:", mean_absolute_error(y, pred))
print("MSE:", mean_squared_error(y, pred))
rmse = np.sqrt(mean_squared_error(y, pred))
print("RMSE:", rmse)
print("R²:", r2_score(y, pred))
MAE: 0.5004899635644883
MSE: 0.416767167221408
RMSE: 0.6455750670692045
R²: 0.3605517030386882

거 설명력이 너무 약한 거 아니오?

 

잔차분석 했더니 고질라 왔다간거 실화냐?

 

VIF(다중공선성)

이거 왜 보냐면 독립변수끼리 상관이 있나를 보는겁니다. 지들끼리 상관이 있으면 모델 시망됨.

 

wine_x = wine_x.drop('quality', axis=1)

# X는 독립변수 데이터프레임 (스케일링 안 해도 되지만 보통 해도 상관없음)
X_vif = pd.DataFrame()
X_vif["variable"] = wine_x.columns
X_vif["VIF"] = [variance_inflation_factor(wine_x.values, i) for i in range(wine_x.shape[1])]

print(X_vif.sort_values(by="VIF", ascending=False))
                variable          VIF
7                density  1479.287209
8                     pH  1070.967685
10               alcohol   124.394866
0          fixed acidity    74.452265
9              sulphates    21.590621
1       volatile acidity    17.060026
2            citric acid     9.183495
4              chlorides     6.554877
6   total sulfur dioxide     6.519699
5    free sulfur dioxide     6.442682
3         residual sugar     4.662992

망했는데…? 들어내봐야겠지 이거…?

 

부록-알콜과 퀄리티

wine.corr()['quality'].sort_values(ascending=False)
quality                 1.000000
alcohol                 0.476166
sulphates               0.251397
citric acid             0.226373
fixed acidity           0.124052
residual sugar          0.013732
free sulfur dioxide    -0.050656
pH                     -0.057731
chlorides              -0.128907
density                -0.174919
total sulfur dioxide   -0.185100
volatile acidity       -0.390558
Name: quality, dtype: float64

알콜이 상관계수가 제일 높은데?

 

plt.figure()

scatter = plt.scatter(
    wine['alcohol'],
    wine['quality'],
    c=wine['quality'],
    cmap='viridis',
    alpha=0.6
)

plt.xlabel('Alcohol')
plt.ylabel('Quality')
plt.title('Alcohol vs Wine Quality')

plt.colorbar(scatter, label='Quality')
plt.show()

 

이냥반들아 알콜 많이 들어가봐야 소독용 에탄올이라고... 내가 그래서 고량주 안마심. 실험실에서 맡던 소독용 에탄올 냄새 나서...

 

부록 2-XGBoost가 여기서 왜 나와요

우리 방금까지 회귀했는데 쟈는 또 뭐임? 어제 그 분류했던 친구입니다. 이 데이터셋으로 회귀도 하고 분류도 한다고 함.

 

# 일단 쨈
wine_df = wine.copy()
X = wine_df.drop("quality", axis=1)
y = wine_df["quality"]
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

scaler = StandardScaler()

X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
model = XGBRegressor(
    n_estimators=300,
    max_depth=4,
    learning_rate=0.05,
    random_state=42
)

model.fit(X_train, y_train)

pred = model.predict(X_test)

print("R2:", r2_score(y_test, pred)) # R2: 0.4512701630592346

어째 회귀보다 분류가 설명력이 더 좋은 것 같다.

 

SHAP

얘는 또 뭥미? 이건 그러니까 XGBoost와 대화의 시간을 가지면서 왜 그렇게 분류한건지 물어보는 친구다. 오은영박사님 어… 아니 생각하는 의자까지는 아니고…

 

# 1. 모델의 predict 함수를 직접 전달
# 2. masker를 사용하여 데이터의 통계적 분포를 SHAP에게 알려줍니다.
masker = shap.maskers.Independent(data=X_test)
explainer = shap.Explainer(xgb_model.predict, masker)

# 3. SHAP 값 계산 (Permutation 방식은 속도는 좀 걸리지만 매우 정확합니다)
shap_values = explainer(X_test)

# 4. 시각화
shap.summary_plot(shap_values, X_test)

그니까 이제 XGBoost한테 저 샤프가 오은영박사님에 빙의해서 왜 이렇게 분류했냐고 물어봤을 거 아닙니까? 그러니까 모델이 인제 얘를 이렇게 분류한 이유를 얘기해주는겁니다. 얘는 산도가 너무 높아서 이렇게 했고 얘는 밀도가 이래서 이렇게 분류했어요, 이렇게. 그게 저 그래프임다. 

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

Palmer Archipelago (Antarctica) penguin data

Coding/EDA 2026. 3. 11. 23:34
반응형

예… 그… 펭귄 데이터입니다. 그건 아는데 이걸 왜 꺼냈냐… 분류 할거라서 꺼냈습니다. 예. 아니 진짜 할거임.


전처리 하기 전에...

그냥 EDA였으면 범주화하고 결측값 확인하고 채우거나 날리거나 했을텐데, 이번에는 그렇게 하고 땡 하면 안된다. 왜냐… 무작정 아무 칼럼이나 학습하는데 썼다간 모델 성능이 떨어지거든요. 그리고 범주형 칼럼중에 학습에 쓸 칼럼은 인코딩도 해 줘야 한다.

 

학습에 쓸 수 없는 칼럼 날리기

내 일일이 올리기 귀찮아서 올리지는 않는다만... 분석하기 전에 항상 .head()랑 .column 써서 뭐 있는지 보고 가죠? shape는 잘 안씀... 아무튼. 거기서 칼럼들을 확인해보고 우선 날릴 것부터 정할거다.

# Copy
penguin_drop = penguin.copy()

# 하고 날려날려 칼럼
penguin_drop.drop(['studyName','Sample Number','Region','Individual ID','Clutch Completion', 'Comments', 'Date Egg', 'Stage'], axis=1, inplace=True)
penguin_drop.head()

이걸 날리는 이유는 되게 간단하다. 학습에 도움이 안 돼서.

 

결측값 채우기

sns.kdeplot(penguin_drop['Culmen Length (mm)'])

롸? 갑자기 쟤가 왜 나옴? 임퓨터가 평균, 중앙값, 최빈값으로 채워주는건데 문제가 하나 있습니다. 그게 값 분포에 따라 적절한 걸 골라야지 덮어놓고 히히 평균해야징 하다간 모델 성능이 똥멍청이 1이 됩니다. 그래서 때우기 전에 분포를 본 건데, 이게 보니까… 저거 그 어린왕자 그 코끼리 그거 아님? 그럼 어떻게 합니까?

 

num_cols = ['Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Body Mass (g)'] # 일단 떄울 칼럼

imputer = KNNImputer(n_neighbors=5) # 얘는 그 이웃 참고해서 때워주는 친구입니다
penguin_drop[num_cols] = imputer.fit_transform(penguin_drop[num_cols]) # 때-움

임퓨터중에 KNN Imputer라는 게 있는데, 얘는 결측값을 이웃한 그룹들을 참고해서 채워주는 친구다. 그렇게 해서 몸무게까지 네개 채워주면 일단 끝… 왜 저걸 일괄로 채움? 이유는 간단하다. 저 칼럼에 결측값이 두개씩 있었는데 그 두개가 같은 펭귄에서 빠져있었거든…

 

# . 대치합니닷 
penguin_drop['Sex'] = penguin_drop['Sex'].replace('.', np.nan)

penguin_drop['Sex'].unique()

성별은 범주형이라 최빈값으로 채워야 하는데, 그거 말고도 다른 문제가 있다. 저 점 뭐야 점. 저것도 결측값으로 바꾸고 최빈값으로 때울거다.

 

imputer = SimpleImputer(strategy='most_frequent')
penguin_drop[['Sex']] = imputer.fit_transform(penguin_drop[['Sex']]) # 때-움

됐으. 이제 동위원소 때우러 가자.

 

num_cols = ['Delta 15 N (o/oo)', 'Delta 13 C (o/oo)'] # 일단 떄울 칼럼

imputer = KNNImputer(n_neighbors=5) # 얘는 그 이웃 참고해서 때워주는 친구입니다
penguin_drop[num_cols] = imputer.fit_transform(penguin_drop[num_cols]) # 때-움

이것도 걍 KNN으로 때웠고, 이제 남은건 범주형 변수 인코딩하고 수치형 변수 스케일러 적용하는거다.

 

스탠다드 스케일러 

scaler = StandardScaler() # 스케일러가 요기잉눼?

num_features = ['Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)',
                'Body Mass (g)', 'Delta 15 N (o/oo)', 'Delta 13 C (o/oo)'] # 스케일러
cat_features = ['Island', 'Sex'] # 인코더

preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), num_features),
        ('cat', OneHotEncoder(handle_unknown='ignore'), cat_features)
    ])

X_processed = preprocessor.fit_transform(penguin_drop)

일단 저 칼럼 트랜스포머가 뭐 하는 친구인지는 모르겠고 저게 다 된 거 맞습니다. 이대로 학습 들어가면 되는'데'...

 

이제 학습해야징 히히

y = penguin_drop['Species'] # 문제지
X_train, X_test, y_train, y_test = train_test_split(X_processed, y, test_size = 0.2, random_state=42, stratify=y) # 기본값 8:2

print(f'전체 데이터의 수 : {len(X_processed)}')
print(f'학습 데이터의 수 : {len(X_train)}')
print(f'테스트 테이터의 수 : {len(X_test)}')

있어봐요 우리 모델도 아직 못골랐어... 일단 나눈거야... 저거 참고로 비율은 보통 5:3:2가 국룰인데, 5는 학습용이고 3은 과적합 여부 확인용, 2가 테스트용이다.

 

랜덤포레스트

forest = RandomForestClassifier(random_state=42) # 얘는 근데 숲이랑 뭔 상관이 있길래 이름이 랜덤포리스트인겨
forest.fit(X_train, y_train) # 학습
forest_pred = forest.predict(X_test)

이게 다냐고? 예. 구글링했더니 뭐가 막 장황하게 나오긴 했는데 기본적으로는 이게 다다.

 

# 얼마나 맞췄는지 점수(%) 확인
print(f"정확도: {accuracy_score(y_test, forest_pred):.3f}")

# 종별로 얼마나 잘 분류했는지 상세 리포트
print(classification_report(y_test, forest_pred))
정확도: 0.986
                                           precision    recall  f1-score   support

      Adelie Penguin (Pygoscelis adeliae)       0.97      1.00      0.98        30
Chinstrap penguin (Pygoscelis antarctica)       1.00      0.93      0.96        14
        Gentoo penguin (Pygoscelis papua)       1.00      1.00      1.00        25

                                 accuracy                           0.99        69
                                macro avg       0.99      0.98      0.98        69
                             weighted avg       0.99      0.99      0.99        69

????? 아니 이게 내가 예상했던것보다 너무 잘나왔는데? 나 한 80퍼 되나 했는데 이게 뭐시여?????? 되게 지금 당황스러운데? 근데 아델리펭귄에 대한 정밀도는 좀 떨어지는데, 이게 그 결측값때문일수도 있다.

 

이 모델이 펭귄을 분류하는 데 있어서 중요하게 생각한 게 뭘까? 에 대한 답. 부리 길이와 날개 길이, 그리고 13-탄소가 피쳐 TOP 3인데... 저 동위원소 뭔데요? 저거 먹이활동같은거 추적할때 넣는 방사성 동위원소입니다.

 

SVM(서포트 벡터 머신)

svm_model = SVC(kernel='rbf', C=1.0, random_state=42)
svm_model.fit(X_train, y_train)
svm_predictions = svm_model.predict(X_test)
# 1. 성적표 (Classification Report)
print("--- SVM 분류 성적표 ---")
print(classification_report(y_test, svm_predictions))

# 2. 오답 분석 (Confusion Matrix)
cm = confusion_matrix(y_test, svm_predictions)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=svm_model.classes_)
disp.plot(cmap='viridis')
plt.title("SVM Confusion Matrix")
plt.show()
--- SVM 분류 성적표 ---
                                           precision    recall  f1-score   support

      Adelie Penguin (Pygoscelis adeliae)       1.00      1.00      1.00        30
Chinstrap penguin (Pygoscelis antarctica)       1.00      1.00      1.00        14
        Gentoo penguin (Pygoscelis papua)       1.00      1.00      1.00        25

                                 accuracy                           1.00        69
                                macro avg       1.00      1.00      1.00        69
                             weighted avg       1.00      1.00      1.00        69

어… 이게… 이렇게 됨?

 

이거는… 걍 히트맵 그리는게 나을듯… 저 선 진짜 거슬려요.

 

XGBoost

# 1. 변환기 생성
le = LabelEncoder()

# 2. 정답지(y)를 숫자로 변환
y_train_encoded = le.fit_transform(y_train)
y_test_encoded = le.transform(y_test)

# 다중 분류이므로 objective를 'multi:softmax'로 설정하는 게 정석입니다
xgb_model = XGBClassifier(objective='multi:softmax', n_estimators=50, random_state=42)
xgb_model.fit(X_train, y_train_encoded)

# 결과 확인
print(classification_report(y_test, le.inverse_transform(xgb_model.predict(X_test))))

롸? 라벨인코더 쟤는 왜 나옴? 쟤는 답지도 인코딩해줘야 학습합니다... 그리고 XGBoost랑 LightGBM은 싸이킷런에 없으니까 따로 설치하십쇼.

 

                                           precision    recall  f1-score   support

      Adelie Penguin (Pygoscelis adeliae)       0.97      1.00      0.98        30
Chinstrap penguin (Pygoscelis antarctica)       1.00      0.93      0.96        14
        Gentoo penguin (Pygoscelis papua)       1.00      1.00      1.00        25

                                 accuracy                           0.99        69
                                macro avg       0.99      0.98      0.98        69
                             weighted avg       0.99      0.99      0.99        69

랜덤포레스트랑 비슷한디…?

 

LightGBM

얘도 설치하셔야됩니다… lightbgm 쳐놓고 왜 못깔지 이러고 있었음…ㅋㅋㅋㅋ

 

lgbm_model = lgb.LGBMClassifier(n_estimators=10, random_state=42, verbose=-1)
lgbm_model.fit(X_train, y_train_encoded)

# 1. 모델이 예측한 값(숫자)을 받아옵니다
lgb_pred_encoded = lgbm_model.predict(X_test)

# 2. 숫자를 다시 원래 펭귄 이름(문자열)으로 돌립니다
# 여기서 le(LabelEncoder)가 아까 학습(fit)된 상태여야 합니다
lgb_pred = le.inverse_transform(lgb_pred_encoded)

쟤는 좀 적죠…? 50번 돌렸더니 10번만에 여까지해 됐어 퍼펙트해 하고 모델이 GG쳤음…

 

print("--- LightGBM 분류 성적표 ---")
print(classification_report(y_test, lgb_pred))

# 3. 마지막 혼동 행렬 시각화
cm = confusion_matrix(y_test, lgb_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=lgbm_model.classes_)
disp.plot(cmap='Greens')
plt.title("LightGBM Confusion Matrix")
plt.show()
--- LightGBM 분류 성적표 ---
                                           precision    recall  f1-score   support

      Adelie Penguin (Pygoscelis adeliae)       0.94      0.97      0.95        30
Chinstrap penguin (Pygoscelis antarctica)       1.00      0.86      0.92        14
        Gentoo penguin (Pygoscelis papua)       0.96      1.00      0.98        25

                                 accuracy                           0.96        69
                                macro avg       0.97      0.94      0.95        69
                             weighted avg       0.96      0.96      0.96        69

빨리 GG친 것 치고는 니가 성적이 제일 꼴찌여 이자식아...

 

근데 그럴수밖에 없는게 XGBoost나 LightGBM 둘 다 스케일이 큰 애들에 특화되어 있습니다. 그 미국에서 호미 엄청 사가는거 아십니까? 거기는 막 옥수수밭에서 길 잃어먹는 사람도 있을 정도로 밭이 커갖고 농기구들도 스케일이 장난 아니예요. 근데 그게 호미랑 뭔 상관이냐고? 그 농기구들로 정원 손질하려니 세밀하게 안되는거지 이제. 잡초 뽑으려다가 엄한 꽃도 다치고 그러거든요. 근데 호미는 미쿡 농기구 스케일에 비하면 스케일도 작지, 하나로 다 하지… 그거임다.

 

아래 두 개는 입장에서 펭귄 데이터갖고 분류하라는건 미쿡 농기구로 화분에 꽃심는격임.

반응형

'Coding > EDA' 카테고리의 다른 글

Medical Cost Personal Datasets  (0) 2026.03.18
Red Wine Quality  (0) 2026.03.13
Google Play Store – Most Downloaded Android Apps  (0) 2026.03.02
얘! clinvar도 EDA가 된단다! (3)  (0) 2026.02.18
얘! clinvar도 EDA가 된단다! (2)  (0) 2026.02.17
Lv. 36 라이츄

Lv. 36 라이츄

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

웹 프로젝트를 깃헙에 올릴 수 있다? 뿌슝빠슝

Coding/코딩잡담 2026. 3. 8. 22:53
반응형

처음에는 이거 또 복잡한거 아냐? 했는데 진짜 간단합니다.


1. 깃헙에 저장소를 만들 때 저장소 이름을 (프로젝트명).github.io로 한다.

2. 그 폴더에 readme.md 생성된걸 지우고 HTML, CSS, JS를 올리면 되는데 파일명을 index.html로 해야 한다.

3. 풀 커밋 푸시하고 좀 기다렸다가 브라우저 주소창에 (프로젝트명).github.io 치고 접속하면 땡임.

 

유지보수가 어려운거 아니냐고요? 깃헙 저장소니까 그 안에 있는거 열고 수정하고 풀 커밋 푸시하고 좀 기다리면 반영됨.

반응형

'Coding > 코딩잡담' 카테고리의 다른 글

VScode ERD editor  (0) 2026.06.16
백준 섭종공지  (0) 2026.04.15
안티그래비티를 써보았다.  (0) 2025.12.19
CONNECT BY와 계층형 질의  (0) 2025.11.14
스키마  (0) 2025.11.14
Lv. 36 라이츄

Lv. 36 라이츄

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

포폴 웹페이지화-더 이상의 자세한 설명은 생략한다

Coding/JavaScript 2026. 3. 8. 14:00
반응형

어제 그 뼈대 올라왔잖아요? 그러고나서 밤에 잡아서 내용 다 채웠음. 농담같죠? 진짜임.


어바웃 페이지

뭘 많이 가렸죠? 다 개인정보라 어쩔 수 없음.. 그럼 논문하고 자격증은 왜 안 가렸냐… 논문은 이미 저널에 실린거고 자격증도 내가 땄다고 올렸잖아요. 쟤들은 이미 한번 깐거라 안 가린거임.

 

원래 생각했던 구성에는 저 카드가 없었습니다. 없었는데 제미나이가 "씁 근데 이거 이렇게만 보면 좀 심심하지 않을까요?라고 하면서 제안한겁니다. 저 선이랑 배경도 지금 조정중인데 선이 굵다고 이쁜건 아니고... 없애봤는데 이것도 아 씁 아닌 것 같고... 그림자는 그래도 배경이나 안이나 톤이 다 연해서 있는 게 나은 것 같긴 하고... 애매함. 그냥 애매함.

/* 개별 오브젝트를 감싸는 카드*/
.card {
    width: 95%;
    border: 1px solid var(--sub-dark);
    border-radius: 10px;
    margin: 10px 0;
    padding: 10px;
    background-color: var(--white);
    box-shadow: 0 2px 6px rgba(0,0,0,0.08);
    transition: all 0.2s ease;
}

.card:hover {
    box-shadow: 0 6px 14px rgba(0,0,0,0.12);
    transform: translateY(-2px);
}

이렇게 트랜지션이랑 호버를 주면 마우스를 댔을 때 뜨는데 문제가 하나 있다. 여러분들 그거 아십니까? 모바일에서 호버 안되는거. 당연한 얘기지만, 서마터폰이나 태블릿 PC에서는 마우스를 쓰는 게 아니라 손으로 누르잖아요. 드래그도 손으로 하는데 어쨌든 이게 손으로 뭘 누른 상태에서 움직이고 걍 볼 때는 손을 또 안 대잖아요? 그래서 의미가 없다 이거요.

 

/*여러분 그 호버도 모바일에서는 끄셔야 하는 거 아십니까 */
@media (hover: hover) {
    .card:hover {
        box-shadow: 0 6px 14px rgba(0,0,0,0.12);
        transform: translateY(-2px);
    }

    .accordion:hover {
    background-color: var(--accent);
    }

    .link i:hover {
        color: var(--accent);
        transform: translateY(-1px) scale(1.05);
    }

    .btn:hover {
        color: var(--accent);
    }
}

예. 미디어쿼리 주십시오.

 

근데 메인페이지는 이거 말고는 내용 말고 뭐 없는데 내용을 다 가려버려서 더 서술할게 없음.

 

스킬

어차피 헤더는 고정이니까 여기만 캡쳐하겠음... 이건 뭐냐면 내가 할 수 있는 것들을 나열한거다. 저 그리드 레이아웃은 나중에 스킬에 뭔가 더 추가되면 바뀔 수도 있는데 지금은 2열 2행이고 저기도 카드 적용되어있고 어이콘 폰트어썸이고... 그게 다임. 여기서 보고 가실건 저 체크입니다. 저게 li태그로 단 건데... 엥? li에 저런거 없는데요?

 

ul {
    margin: 0;
    list-style-position: inside;
    list-style-type: none;
}

li::before {
    content: "\f00c"; 
    
    /* 7 버전 폰트 패밀리 확인 (보통 'Font Awesome 7 Free' 또는 'Font Awesome 7 Brands') */
    font-family: "Font Awesome 7 Free"; 
    font-weight: 900; 
    color: var(--sub-color);
    margin: 0 10px;
}

무늬를 빼고 ::before에 폰트어썸 달았음. 내가 포폴이라 체크표로 달긴 했는데 저거 응용하면 리스트에 이모지 다는 것도 가능합니다.

 

프로젝트

머여 삼색엔딩도 아니고 왜 색깔이 3개임? 그림을 잘 보시면 저기 초록색으로 된 부분은 아코디언 패널이 열려있죠? 그겁니다. 보라색이 기본이고 파란색은 당신이 마우스를 올리고 있는 패널, 초록색은 열어서 내용을 보고 있는 패널입니다. 근데 이렇게 놓고 보니 얘도 호버를 줄 필요는 없는 것 같아서 저 보고 있는 패널만 액센트로 빼야겠음.

 

저기는 아코디언 패널인데 지금까지 한 프로젝트들이 들어갑니다. 저기에는 생물쪽 한 거+부트캠프 팀플(아직 안 넣음)+생물학이 아닌 다른 EDA도 같이 들어가는데… 예, 그 캐글 EDA도 들어갑니다… 근데 여기서는 그림 안 넣고, 그냥 프로젝트에 대한 개요(이런거 했다)만 올린 다음 PDF를 올리거나 깃헙으로 유도할 예정임. 그래서 프로젝트 요얄이랑 과정만 있잖아요.

 

근데 왜 아코디언 패널로 함? 프로젝트 하나당 짧게짧게 한다 쳐도 이게 여러개거든요? 캐글도 선별해서 올리고 있지만 벌써 두개나 올라갔음… 이걸 그냥 보여주는건 좀 아닌 것 같음… 그래서 보고싶은 것만 펴서 보시라고 아코디언 패널로 넣은거다. 그럼 사진은요? 그래프 왜 뺌? 저게 이미지를 보여줄라면 서버에 그 이미지를 같이 올려야되거든요... 일단 그게 귀찮음...

 

참고로 깃헙이랑 태블로(폰트어썸에 아이콘이 없어서 저걸로 함)는 동작 잘 하는데 파일 링크는 동작을 안 합니다. 왜냐고? 링크를 파일로 안 걸었거든. 아직 파일이 안 올라간 상태입니다. 그리고 캐글 EDA나 캠블 EDA는 깃헙 링크만 있지 PDF도 없습니다. 개별 포폴을 안 만들었거든…

 

// 프로젝트 아코디언 패널
accordion.forEach((title) => {
    title.addEventListener('click', () => {
        // 클릭된 제목 바로 다음 요소(accordion_contents)를 타겟팅합니다.
        const content = title.nextElementSibling;
        const icon = title.querySelector('.icon-toggle');

        title.classList.toggle('active');

        // 보이고 안 보이고를 토글합니다.
        if (content.style.display === 'block') {
            content.style.display = 'none';
            icon.style.transform = 'rotate(0deg)';
        } else {
            content.style.display = 'block';
            icon.style.transform = 'rotate(180deg)';
        }
    });
});

기존보다 업그레이드된 아코디언 패널 코드나 보고 가십셔.


1. 저 색깔이 울트라바이올렛이랑 비리디언으로 잡은건데 비리디언이 서브입니다. 서브 먼저 정하고 이색저색 해보다가 울트라바이올렛으로 한건데 얘가 채도가 그렇게 밝은 색이 아님. 좋게 말하자면 눈뽕이 없고 나쁘게 말하자면 그렇게 확 뛰는 색이 아닙니다. 그래서 그것때문에 고민을 많이 했음… 그런데 왜 보라색이죠? 내가 보라색 좋아함. 그 라미 다크라일락 몸통같은 보라색 좋아하는데… 팬톤아… 어떻게 2027년 색깔로 안되겠니…? 되겠냐 

 

2. 액센트가 원래 골드컬러였는데 메인컬러가 보라색+서브컬러가 비리디언이라 액센트가 너무 붕 떠요. 그니까 그 색깔에 문제가 있는 게 아니라 그냥 안 맞는거임. 그래서 액센트를 파란색으로 한겁니다. 저게 비리디언+코발트블루 조합이었으면 아마 액센트로 골드컬러도 맞았을건데...

 

3. 헤더 오른쪽에 아이콘들 다 눌립니다. 진짜로 링크 걸어놨음.

 

4. 이걸 어디에 올릴지는 정해지지 않았는데, 이거 올려도 여기에 링크는 안 올릴듯합니다. 일단 저게 포폴이라 내 개인정보를 다 깠어요... 아까도 가리고 올렸잖음. 물론 이 글을 보고 있는 당신이 인사담당자고 내가 당신이 재직중인 회사에 지원하면서 이거 포폴인데 보실래요? 한다거나 나랑 링크드인 팔로워거나 하면 볼 수는 있겠지만 기본적으로 블로그에는 안 올릴 생각입니다. 지금 깃헙에는 올라가있는데 서버에서 내리고 나면 깃헙에서도 내릴까 생각중임.

 

5. CSS가 뭔가 많아지면서 주석의 소중함을 깨달았음… 주석이 없으니까 뭐가 뭔지 모르겠어요 이거… JS는 생각보다 뭐가 없는데 CSS가 어유 진짜 와… 이거 다 올리면 네이버가 짜름 5000자 넘는다고…

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

포폴 웹페이지화-뼈대 잡기

Coding/JavaScript 2026. 3. 7. 14:00
반응형

내용이요? 이제 생각해야지.


여기까지 해서 뼈대가 잡혔고 탭 메뉴도 잘 돌아간다. 그럼 이제 내용물만 채우면 됩니다…

 

1. 저 언더 컨스트럭션 란에는 내 이름과 한줄 설명이 들어갈 예정이다. 그래서 일부러 넓게 안 잡았다. 꽉 차보여도 저 분량이 차지하는 건 가로 75%정도… (모바일은 미뎌쿼리 줘서 95%)

2. About에는 본인 약력, 자격증이 들어가고 skills는 뭐 할 수 있냐고 project에 개인적으로 했던 모든 프로젝트들이 들어갈 예정이다.

3. About 단을 하나로 할지 두개로 할 지 생각중임... 한쪽은 넓게 잡아서 내 소개 하고(아직 데이터쪽 약력이 없음...) 한쪽에 좀 좁게 잡아서 자격증이랑 외부 활동 내역같은 거 쓸까...

4. Project에 아코디언 패널 들어갑니다. 아코디언 패널이 뭐냐면 그 클릭하면 접히는? 그런거 있음. 프로젝트별로 패널 하나씩 할당됩니다.

5. HTML 코드는 어마무시하게 긴데 자바스크립트는 아직까진 그렇게 길지 않음… CSS……. (마른세수)

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

개 얼탱이 없는 작업이 온다 두둥

Coding/JavaScript 2026. 3. 6. 10:51
반응형

이게 뭐냐고요? 포폴을 웹으로 만들어서 서버에 올리자는 진심 얼탱이없는 작업에 들어갈 예정입니다. 근데 지금 팀플때문에 바빠서 구조 구상하고 색깔만 짜놨음.

 

대충 그렸음 대충...

 

1. 내 이름이랑 전번 이메일 깃헙 블로그(티스토리) 링크가 헤더에 들어가고(이부분도 고민 좀 해봐야됨...)

2. 그 밑에 내 이력이랑 스킬(뭐뭐 쓸 수 있나) 프로젝트가 들어가는데 이게 탭메뉴입니다. 그니까 얘를 탭하면 전환이 돼야 하는데 이걸 자바스크립트로 해야 하고…

3. 프로젝트에는 내 포폴에도 올라가는 프로젝트 세 개가 올라가는데 그거에 대한 설명을 개별 프로젝트로 아코디언 메뉴로 하고 PDF파일을 거기다가 올리든가 할겁니다.

 

링크 관련해서는 이걸 폰트어썸 아이콘만 넣을지(7.x로 올렸더만…) 이름을 병기할지정도 고민중임. 나머지는 뭐… 그렇죠 뭘.

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

Google Play Store – Most Downloaded Android Apps

Coding/EDA 2026. 3. 2. 16:36
반응형

이거는 말 그대로 플레이스토어 앱 정보가 있는 데이터인데… 본인 아이폰 씁니다. 근데 왜 플레이스토어죠? 앱스토어 데이터가 없다.

 

참고로 전처리 할거 꽤 있으니까 잘 따라오십쇼.


전처리

가격 정상화

가격 정상화는 투트랙으로 이뤄질건데, 일단 앱 가격에 붙어있는 $를 다 빼고 float으로 만들어줄거다. 그리고 결측값도 채워줄건데, 결측값이 있는 앱들은 다 무료 앱이라 0으로 때울거다.

 

# 일단 저 달러부터 떼보시죠 
playstore_df['Price']
0       NaN
1       NaN
2       NaN
3       NaN
4       NaN
      ...  
82    $4.99
83    $3.49
84    $6.99
85    $4.99
86    $3.99
Name: Price, Length: 87, dtype: object

저기 가격 붙어있는거 옆에 달러를 다 떼줄겁니다. ㅇㅋ? ㅇㅇㅋ.

 

playstore_df['Price'] = playstore_df['Price'].str.replace('$', '', regex=False)
playstore_df['Price']
0      NaN
1      NaN
2      NaN
3      NaN
4      NaN
      ... 
82    4.99
83    3.49
84    6.99
85    4.99
86    3.99
Name: Price, Length: 87, dtype: object

그럼에도 아직 오브젝트인 이유는 일단 결측값이 있어서가 아닐까 하는 합리적인 의심을 해봅니다.

 

# 일단 가격이 결측값이 0인 모든 앱의 가격들이 다 무료인지 봅시다. 
free_index = playstore_df.query('Price.isna()').index

for idx in free_index:
    print(playstore_df['Type'].loc[idx])

0으로 때웁시다. 다 무료네.

 

playstore_df['Price'] = playstore_df['Price'].fillna(0) # 채우고 
playstore_df['Price'] = pd.to_numeric(playstore_df['Price']) # 바꾸면

playstore_df['Price'] # 정상화 끝

때운 다음 형변환도 해줘서 이제 float입니다.

 

날짜 형변환

# 날짜가 두개지요? 
# 난 저 리치드가 제출일인 줄 알았는데 일고보니 특정 다운로드 수에 도달한 날짜였음... 그래서 리치드가 퍼블리시드보다 뒤 시점입니다. 
playstore_df['Date_Reached'] = pd.to_datetime(playstore_df['Date_Reached'], errors='coerce')
playstore_df['Date_Published'] = pd.to_datetime(playstore_df['Date_Published'], errors='coerce')

에러나서 봤더니 언노운이 껴있더라고?

 

년도 추출

# 발매년도
playstore_df['Release_year'] = playstore_df['Date_Published'].dt.year
playstore_df['Release_year']

대체 NaT 껴있는거랑 플로트랑 뭔 상관인거임?

 

마일스톤 도달까지 걸린 일수

# 이걸로 되는겨? 
diff = playstore_df['Date_Reached'] - playstore_df['Date_Published']
playstore_df['To_reach'] = diff.dt.days

이거 데이터프레임에는 n days로 표기되는데 타입은 데이트타임임다.

 

설마 이상한거 없겠지

playstore_df.query('To_reach < 0')
App	Developer	Downloads	Date_Reached	Date_Published	Category	Pre_installed	Type	Price	Release_year	To_reach
7	Android Accessibility Suite	Google	10B+	2022-12-18	NaT	Accessibility tool	Yes	Free	0.00	NaN	-9223372036854775808
19	Google Hangouts	Google	5B-10B	2021-06-03	NaT	Communication	NaN	Free	0.00	NaN	-9223372036854775808
55	Duolingo	Duolingo Inc.	500M-1B	NaT	NaT	Games & education	No	Free	0.00	NaN	-9223372036854775808
78	Camera ZOOM FX Premium	androidslide	1M-5M	2012-03-19	NaT	Photo editor	No	Paid	4.99	NaN	-9223372036854775808

 

아뇨 있는데요? 

 

# 날립시다. 
playstore_df = playstore_df.dropna()
playstore_df

저 데이로 매겨야되는데 결측값 껴있으면 거시기하니까 날리자 걍.


오케이 렛츄고

마일스톤별 분석

특정 마일스톤별로 묶어서 한번 봅시다.

# 마일스톤별 그룹화 
playstore_df.groupby('Downloads').size()
Downloads
10B+       16
10M+        5
1B-5B      24
1M-5M       9
500M-1B    11
5B-10B     12
5M-10M      6
dtype: int64

얘는 전처리 감도 안오더라… 여기서 마일스톤 그룹별로는 어떤 앱이 가장 빨리 달성했을까?

 

가장 빨리 마일스톤을 달성한 앱

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
min_idx = playstore_df.groupby('Downloads')['To_reach'].idxmin()

# 오케이 렛츠씨 
playstore_df.loc[min_idx]

와 게임이 두개나 있는데 내가 모르는 게임이야...

 

마일스톤 달성에 오래걸린 앱

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
max_idx = playstore_df.groupby('Downloads')['To_reach'].idxmax()

# 오케이 렛츠씨 
playstore_df.loc[max_idx]

인별이 오래걸린건 좀 의외다. 구글번역기... 음... 사실 요즘 한국인들은 다 파파고 씁니다...

 

게임! 게임을 보자! 

game_df = playstore_df.query('Category.str.contains("Game")')
game_df

왜 쿼리에 저게 들어가냐고요? 카테고리가 게임, 어쩌고 게임 & 저쩌고 다 이난리라서요. 진짜 정규화 마려웠음.

 

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
min_idx = game_df.groupby('Downloads')['To_reach'].idxmin()

# 오케이 렛츠씨 
game_df.loc[min_idx]

슈터가 세개지요…

 

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
max_idx = game_df.groupby('Downloads')['To_reach'].idxmax()

# 오케이 렛츠씨 
game_df.loc[max_idx]

포고는 솔직히 느그언틱이 뻘짓만 덜했어도 좀 더 빨리 도달했을것같은데. 

 

마인크래프트나 로블록스나 다들 애들이 좋아하는 게임이간 한데, 마인크래프트는 '돈을 내고' 게임을 사야 하고 로블록스는 설치는 공짜고 인게임 안에서 뭘 사야 하는 구조다. 그럼 유료게임들이 다 마일스톤 달성이 오래 걸리냐 하면 그것도 아닌게, 위에 빨리 달성한 게임들에도 유료게임이 있어요. 게임이 오래 가려면 재미와 게임성도 중요하지만 운영을 X같이 하면 안됩니다.

 

무료앱

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
min_idx = free_df.groupby('Downloads')['To_reach'].idxmin()

# 오케이 렛츠씨 
free_df.loc[min_idx]

일단 유튜브 뮤직은 한번도 쓴적 없지만 가끔 Flo는 쓴다. 근데 이것도 요금제 무료로 주는거 있어서 쓰는거지 보통은 잘 안 씀. 나는 음원 다운로드해서 폰에 넣고다닌다. 애초에 내가 듣고 다니는 노래 중에 특정 음원사이트에는 수록 안 되는 곡도 있고(특히 멜론) 대부분 요금이 구독제거든.. 내가 구독하는건 포홈 박스(1년에 2만원)랑 닌스온, 드롭박스, 아이클라우드가 다다.

 

그리고 유튜브는 이것들 광고 꼬라지가 X같아서 구독하기 싫음. 막말로 얘네 돈만 많이 주면 나도 광고해줄걸?

 

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
max_idx = free_df.groupby('Downloads')['To_reach'].idxmax()

# 오케이 렛츠씨 
free_df.loc[max_idx]

구글번역기... 음... 내 중고딩 시절에는 번역이 발퀄이라 안 썼고 요즘은 파파고나 DeepL이 잘 되어있어서 안씁니다...

 

유료앱

# 마일스톤별로 묶은 다음 최솟값의 인덱스를 추출 
min_idx = paid_df.groupby('Downloads')['To_reach'].idxmin()

# 오케이 렛츠씨 
paid_df.loc[min_idx]

얘네들 뭐 하는 게임임? 한번도 못본거같은데..

 

# 마일스톤별로 묶은 다음 최댓값의 인덱스를 추출 
max_idx = paid_df.groupby('Downloads')['To_reach'].idxmax()

# 오케이 렛츠씨 
paid_df.loc[max_idx]

문제는 얘들도 뭐 하는 애들인지 모르겠음.

 

년도별로 보기

2010~2020

참고로 2010년도 이전 앱도 있긴 있습니다. 근데 내가 갤럭시 A를 본 게 2010년인데? 뭔 경우냐 이건?

 

year_min_idx = playstore_df.query('2010 <= Release_year < 2021').groupby(['Release_year'])['To_reach'].idxmin()
playstore_df.loc[year_min_idx]

음... 스타듀밸리는 그럴만 했지. 후르츠 닌자도 예전에 스마트폰이나 어른패드에서 많이 했던 게임 중 하나다. 일단 저 게임은 룰이 대단히 간단한데, 과일만 싹둑해야 합니다. 근데 그 과일만 싹둑하는 작업이 은근 어려운게 포인트임. 염소 시뮬레이터는... 그래요.. 걔들은 약 빨고 게임을 만들었어...

 

year_max_idx = playstore_df.query('2010 <= Release_year < 2021').groupby(['Release_year'])['To_reach'].idxmax()
playstore_df.loc[year_max_idx]

우리 그 클래스챗이 따로 있는데 거기서 가끔 문제가 터집니다. 마이크가 뻑나서 본의아니게 다리를 얻고 목소리를 잃은 인어공주가 되기도 하고, 소리가 안들려서 베토벤이 이 상황에서 작곡을 했구나를 깨닫기도 함. 그럴때 대안으로 쓰는게 구글미트예요. 많이 쓰임.

 

스타듀밸리는… 2019년에 설마 저거 하나 들어간거 아니지?

 

2021~

2021년 이후는 코드 올리고 자시고 할 것도 없는게, 이게 답니다. 데이터 원본 행 수가 좀 빈약함…

 

모스트 다운로드 오브 구글 앱

google_df = playstore_df.query('Developer.str.contains("Google") and Downloads == "10B+"')
google_df

B가 10억이니까 쟤들은 최소 100억번 이상 다운로드 됐다는 얘기가 되겠죠…

 

google_df_s = google_df.sort_values('To_reach', ascending=False)
sns.barplot(google_df_s, x = 'App', y = 'To_reach', hue = 'App', palette='viridis')
plt.title('To reach for milestone: Google apps')
plt.xlabel('App')
plt.xticks(rotation = 45)
plt.ylabel('To Reach (days)')
plt.show()

저 안드로이드 스위치 뭐 하는 앱인지 아시는 분은 제보 바랍니다.

 

모스트 다운로드 오브 게임

most_game_df = playstore_df.query('Category.str.contains("Games") and Downloads.str.contains("B")')
most_game_df = most_game_df.sort_values(['Downloads','To_reach'], ascending = [True, False])
sns.barplot(most_game_df, x = 'App', y = 'To_reach', hue = 'Downloads', palette='viridis')
plt.title('To reach for milestone: Games')
plt.xlabel('App')
plt.xticks(rotation = 45)
plt.ylabel('To Reach (days)')
plt.show()

내 언젠가 저 세팅도 화이트그리드로 싹 바꿀것이다…

 

근데 로블록스가 애들한테 인기있는 게임 아니었나? 생각보다 도달하는데 오래걸렸다?

반응형

'Coding > EDA' 카테고리의 다른 글

Red Wine Quality  (0) 2026.03.13
Palmer Archipelago (Antarctica) penguin data  (0) 2026.03.11
얘! clinvar도 EDA가 된단다! (3)  (0) 2026.02.18
얘! clinvar도 EDA가 된단다! (2)  (0) 2026.02.17
얘! clinvar도 EDA가 된단다! (1)  (0) 2026.02.15
Lv. 36 라이츄

Lv. 36 라이츄

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

씨본 팔레트 컬러 시뮬레이터 보수작업

Coding/JavaScript 2026. 2. 22. 02:42
반응형

https://koreanraichu.tistory.com/853

 

씨본 컬러 파레트 씨뮬레이터

그 Seaborn에서 컬러 커마 가능한거 아시죠? 근데 이게 뭔 색인지 뭔 파레트가 어떻게 나오는지 모르잖아요. ㅇㅋ? ㅇㅇㅋ. 그래서 색 두 개를 입력받은 다음 한 10칸정도로 띄엄띄엄 칠한거 하나, c

koreanraichu.tistory.com

이거 함 써보고 고칠점 찾음…


입력하는 색상 앞에 #가 들어가면 #을 빼주기

이게 왜 필요함? 하실 수도 있는데 컬러 파레트 만드는데서 복사할때 앞에 #이 붙는 경우가 있고, 아닌 경우가 있습니다. 근데 #이 붙으면 안됐거든요? 이러면 사용자 입장에서는 아니 내가 이거 하나 쓰자고 #을 지워야됨??? 이 되는거죠. 뭔지 아시겠죠?

 

const color1 = first_color.value.replace(/#/g, '').trim(); // 응 샵 들어가
const color2 = second_color.value.replace(/#/g, '').trim(); // 얘도 데려가

#은 잘 뺐는데 생성이 안돼서 봤더니 함수에 다른걸로 들어가서 그런거였음… 제미나이 이 에미나이 왜 자꾸 딴데서 돌리고 있냐고… 클로드가 찾아줘서 알았음 나도… 아무튼 이거 수정해서 잘됩니다.

 

그라데이션 종류 추가

그 단색으로 해서 라이트&다크가 있고 diverging이라고 해서 양 끝단에 색 두개+가운데 색 하나 있는 게 있어요. 그걸 만들어야됨.

const cmap_div_light = document.querySelector('.cmap2'); // Light
const cmap_div_dark = document.querySelector('.cmap3'); // Dark
const cmap_div_divl = document.querySelector('.cmap4'); // Diverging(light)
const cmap_div_divd = document.querySelector('.cmap5'); // Diverging(dark)

일단 네개 추가요… diverging도 가운데 색 따라서 라이트 다크 있음.

 

// 라이트
cmap_div_light.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #ffffff)`; // 왼->오

// 다크
cmap_div_dark.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #000000)`; // 왼->오

//diverging(light)
cmap_div_divl.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #ffffff, ${"#" + color2})`; // 왼->오

//diverging(dark)
cmap_div_divd.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #000000, ${"#" + color2})`; // 왼->오

이게 한번에 될라나...

 

일단 그라데이션은 다 나왔으니까 위치나 잡아봅시다…

 

.cmap_wrapper > div {
    width: 100%;
    height: 90px;
    margin: 10px 0;
}

일단 하위 div가 늘었는데 그 div들 사양이 다 같아서 이렇게 줬음.

 

그리고 p태그 문구 바꿨습니다. 저기 개별로 추가하긴 힘들어...

 

그라데이션은 순서대로 2배색 그라데이션, 단색(밝음), 단색(어두움), diverging(밝은색), diverging(어두운색)입니다. 여기다가 두번째색 단색도 넣으면 될듯?

 

// 그라데이션
cmap_div.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, ${"#" + color2})`; // 왼->오

// 라이트
cmap_div_light.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #ffffff)`; // 왼->오

// 다크
cmap_div_dark.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #000000)`; // 왼->오

// 라이트
cmap_div_light2.style.backgroundImage = `linear-gradient(to right, ${"#" + color2}, #ffffff)`; // 왼->오 (두번째색)

// 다크
cmap_div_dark2.style.backgroundImage = `linear-gradient(to right, ${"#" + color2}, #000000)`; // 왼->오 (두번째색)

//diverging(light)
cmap_div_divl.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #ffffff, ${"#" + color2})`; // 왼->오

//diverging(dark)
cmap_div_divd.style.backgroundImage = `linear-gradient(to right, ${"#" + color1}, #000000, ${"#" + color2})`; // 왼->오

한 화면에서 보기 힘들어졌음.. 일단 기능은 잘 됩니다.

 

근데 한가지 문제가 있다면 씨본에서 diverging 만들때 rgb가 아니라 husl을 입력함… 아 이것도 rgb 해달라고… husl 변환 귀찮다고…

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

MSA에 군집분석을 끼얹어보세요!

Coding/Python 2026. 2. 20. 20:00
반응형

있는것도 복잡한데 저걸 왜 넣냐고요? 라이노바이러스는 좀 덜한데, 이게 기본적으로 300개씩 찾고 그렇다보니 계통수가 무지하게 길어집니다. 이러면 이걸 넣는 나도 고통이고 읽는 사람도 고통이예요. 거의 뭔 스크롤이여 스크롤. 근데 계층적 군집분석 결과가 덴드로그램인데 이거 목 꺾고 옆으로 보면 계통수거든요? 그리고 어쨌든 묶은거니까 이거 넣어보자 해서 넣었죠.


실루엣 계수

이게 원래는 군집 내에서의 응집도와 다른 군집간의 거리를 비교해서 군집분석이 잘 됐는지, 안 됐는지를 평가하는 지표인데 k-means나 k-medoid에서 군집 개수 나눌때도 쓴다. 그 개수가 돌려돌려 돌림판으로 나오는게 아닙니다… 그럼 계통수는 버리는건가요? 아니, 그거 보고 대충 개수 나눌수도 있다.

 

한타바이러스 실루엣 계수

이거 봐봐요 이걸로 뭘 어떻게 정할거야… 저거 계통수 산출한거에서 덩어리 수 보고 정했어요 결국…

 

k-medoid

아니 근데 왜 하필 저놈임? 일단 계통수를 만들고 쟤까지 진행하는거라 거리행렬이 준비되어있는데, k-medoid는 그 거리행렬을 주기만 하면 됩니다. 그리고 서열이 이미 준비되어있으니 대표서열로 걍 하면 되고, 이친구는 중앙값으로 하는거라 이상치에 영향을 덜 받는다.

 

아니 우리 거리행렬이 있었어요?

# 1. 거리 계산
calculator = DistanceCalculator('identity')
dm = calculator.get_distance(alignment)

이거 찾수?

 

인플루엔자 K-medoid

인플루엔자는 한 '아종' 안에서 갈라지는거고 코로나바이러스는 스파이크만 해서 트리 관련 통계가 한타, 라이노랑 다르지만 이건 일단 계통수만 도출할 수 있으면 다 그릴 수 있기 때문에 계통수를 대체할 수 있다. 단, 쿼리를 잘 짜야 한다는 거… 저기 혼자 떨어진 점 보여요? 저거 partial CDS인데 쟤 끼면 군집 분포가 이상해진다.

반응형

'Coding > Python' 카테고리의 다른 글

Polars 데이터프레임도 시각화가 되나요?  (0) 2026.04.10
Polars를 써보자  (0) 2026.04.09
M1V1 = M2V2  (0) 2026.02.15
코로나바이러스 MSA  (0) 2026.02.05
라이노바이러스 유전자로 MSA를 해보았다  (0) 2026.01.27
Lv. 36 라이츄

Lv. 36 라이츄

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

얘! clinvar도 EDA가 된단다! (3)

Coding/EDA 2026. 2. 18. 20:00
반응형

생각보다 분량은 없지만 일단 태블로로 넘어와봤다. 전에도 얘기했지만 염색체가 25개(상+성+미토)라고 했잖아요? 이거 25개를 일일이 파이썬으로 그리면 코딩하는 나도 고달프고 읽는 당신들도 고달프다. 근데 태블로에는 대시보드라는 게 있어요. 그래서 시트 만들고 대시보드에서 필터걸고 염색체별로 볼 수 있음. 파워포인트 애니메이션마냥 스무th합니다 움직임도.


대시보드+스토리보드 구성

1페이지

각 염색체별... 그니까 1번, 2번 이런 식으로 볼 수 있다. 염색체별 변이 TOP 25(변이가 가장 많은 상위 25개), 염색체 내 유전자의 CLNSIG 및 CLVNC 비율이 들어가있다. 그러니까 파이썬에서 일일이 다 할 필요 없이 여기다가 필터 걸어두고 저 표에 있는 염색체 번호를 누르면 무슨 염색체 내에서 변이가 가장 많은 유전자(상위 25개), 염색체 내 변이 구성비를 볼 수 있다.

 

근데 저 밑에 있는건 뭐예요? 염색체+돌연변이로 조합하면 특정 염색체+특정 돌연변이로 상위 25개를 볼 수 있다.

 

저 25개만 보는게 염색체 단독으로는 잘 됐는데 돌연변이 끼니까 잘 안돼서 지피티한테 버스터콜쳤더니 애가 똥볼참. ㅡㅡ 거기다가 맥북 들고 스벅가기 해보고싶어서 스벅가서 했는데 앞사람이 완죤 티나게 짜증내서 대충 여기까지만 해결보고 왔음다...

 

2페이지

1페이지랑 두 개가 같은 구성인데, 이제 Pathogenic한 변이만 모아서 본 것이다. SNV TOP은 염색체별로 SNV가 가장 많은 유전자 상위 25개.

 

3페이지

그니까 태블로는 필터 기능이 있어요. 그리고 필터에서 와일드카드를 쓰면 검색도 가능합니다. 그럼 뭐 해보고 싶어요? 그죠. 유전자별로 보고싶잖아요. 저거 하나씩 있는거 일일이 파이썬에서 쿼리때리고 싶지 아니하잖아요. 물론 파이썬으로도 인풋이나 sys.stdin.readline() 쓰면 구현은 할 수 있겠지만 실행 누르기 귀찮잖아...

 

봐봐요 필터링이 된다니까?

 

근데 저게 문제가 하나 있음… Pathiogenic이 없으면 밑에 아무것도 안 뜹니다. 그니까 진짜 밑에 아무것도 안 뜸. 이걸 어떻게 처리를 해보려고 했는데 제미나이가 똥을 싸요 이번에는. 아이 이 에미나이. 그래서 걍 뒀습니다. 방법이 없어요 나도.

 

아 스크롤바 뵈기싫다 아

반응형
Lv. 36 라이츄

Lv. 36 라이츄

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

얘! clinvar도 EDA가 된단다! (2)

Coding/EDA 2026. 2. 17. 20:00
반응형

그렇다. 대망의 2부가 돌아왔다.

 

이게 정보 확인하는거 생략하고도 분량 꽤 되니까 알아서 쫓아오십쇼. 다음편에 태블로 얘기만 할거라서 이번편에 다 끝낼거임.


전처리

쓸 칼럼만 추리기

이게 칼럼이 되게 많은데 그걸 우리가 다 쓸 게 아니거든요? 그래서 쓸 것만 추린 다음에 데이터프레임을 재구성하고 그걸 csv파일로 보내야 합니다. 왜냐고? 그걸 보내야 태블로에서도 쓰죠.

analysis_column = ['CHROM','POS','REF','ALT','CLNSIG','CLNVC','GENEINFO','CLNREVSTAT'] # 칼럼 뭐하는건지 위에 있어요

1. CHROM: 염색체(몇 번 염색체인지)
2. POS: 염색체 어디?
3. REF, ALT: 비포&애프터 (REF에 있는 시퀀스가 ALT로 바뀐 변이다)
4. CLNSIG: 임상적 유의성
5. CLNVC: 변이 타입(얘가 껴들어간겨 빠진겨 바뀐겨 뒤집어진겨)
6. GENEINFO: 유전자 이름+Entrez ID
7. 별점(왜 있는거냐)

 

이 칼럼들만 갖고와서

analysis_column = ['CHROM','POS','REF','ALT','CLNSIG','CLNVC','GENEINFO','CLNREVSTAT'] # 칼럼 뭐하는건지 위에 있어요
clinvar_df_analysis = clinvar_df[analysis_column]

clinvar_df_analysis

이렇게 하면 데이터프레임 재구성은 끝난다.

 

그러고도 결측값이 왜 있는거냐고

이러고도 결측값이 있는 이유는 아직 연구가 덜 됐기 때문이다. 내가 isna()./sum()으로 확인해보고 원본까지 대조해본 결과임.

 

fill_values = {
    'CLNSIG': 'Unknown_Significance',
    'CLNREVSTAT': 'No_Assertion',
    'GENEINFO': 'Unknown_Gene',
    'MC': 'Unknown_Consequence'
}

clinvar_df_analysis = clinvar_df_analysis.fillna(value=fill_values)

clinvar_df_analysis['GENE_SYMBOL'] = clinvar_df_analysis['GENEINFO'].apply(
    lambda x: x.split(':')[0] if ':' in x else x
)

그래서 이게 최선이었습니다. 아, 하는 김에 유전자 이름도 분리함.

 

CLNSIG 범주화

# # 묶기 위한 조건 설정
conditions = [
    clinvar_df_analysis['CLNSIG'].str.contains('Pathogenic|Likely_pathogenic', case=False, na=False),
    clinvar_df_analysis['CLNSIG'].str.contains('Benign|Likely_benign', case=False, na=False),
    clinvar_df_analysis['CLNSIG'].str.contains('Uncertain_significance|VUS', case=False, na=False),
    clinvar_df_analysis['CLNSIG'].str.contains('Conflicting', case=False, na=False),
    clinvar_df_analysis['CLNSIG'].str.contains('risk_factor|drug_response|association|protective|Affects', case=False, na=False)
]

# 결과 그룹명
choices = ['Pathogenic', 'Benign', 'VUS', 'Conflicting', 'Risk/Other']

# 기본값은 Unknown으로 설정
clinvar_df_analysis['CLNSIG_Group'] = np.select(conditions, choices, default='Unknown')

# 결과 확인
print(clinvar_df_analysis['CLNSIG_Group'].value_counts())

나노 반도체 단위로 나뉘어진 CLNSIG을 대충 간소화했다. 여기까지 하고 to_csv로 저장해주면 태블로에서도 불러올 수 있습니다.

 

분석 드가자

CLNSIG별로 보기

clnsig_group = clinvar_df_analysis.groupby('CLNSIG_Group')['CLNSIG_Group'].count().sort_values(ascending=False)
clnsig_group

VUS는 말 그대로 몰?루인거고 Benign은 변이는 변이인데 누구나 하나쯤은 다 갖고 있는 뭐 그런거다. 그리고 그 다음으로 많은 게 Pathogenic이다.

 

ax = sns.barplot(clnsig_group)

plt.title('CLNSIG Group에 따른 변이 수')
plt.xlabel('CLNSIG Group')
plt.yscale('log') # 이거 안하면 막대기 하나 안보임

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, fmt='%d', padding=3, fontsize=10)

plt.tight_layout()
plt.show()

아… 나도 막대기 색을 나누고 싶었는데요… 아… 이게… size()로 했더니 시리즈가 돼서 애가 인식을 못해……

 

염색체 종류별 CLNSIG

아니 그럼 1번부터 다 보나요? 놉. 아까 내가 썼는지 모르겠는데 염색체도 범주화했다. 상염색체(1~22), 성염색체(XY), 미토콘드리아(얘는 지꺼 따로 있음)+언노운 이렇게 있음. 전에도 얘기했지만 1번부터 다 본다? 하나씩 그리면 스크롤이 너무 길고 그렇다고 모으자니 그래프가 뵈지도 않아요.

clnsig_chr_group = clinvar_df_analysis.groupby(['CHROM_Type','CLNSIG_Group']).size().unstack().fillna(0)
clnsig_chr_group
ax = clnsig_chr_group.plot(kind='bar', stacked=True, ax=plt.gca(), color=sns.color_palette("Purples_r", n_colors=5))
plt.title('염색체 그룹에 따른 CLNSIG 그룹 수')
plt.xlabel('염색체')
plt.xticks(ticks=[0, 1, 2, 3], labels=['상염색체','미토콘드리아','성염색체','불명'])
plt.yscale('log') # 이거 안하면 막대기 하나 안보임
plt.tight_layout()
plt.show()

성염색체와 달리 상염색체와 미오콘드리아는 Benign 다음으로 VUS가 두드러지게 많다. 성염색체는 이렇게 보면 비슷비슷해보는데 차이가 존재하긴 함.

 

Pathogenic의 비중

clnsig_chr_group = clinvar_df_analysis.groupby(['CHROM_Type','CLNSIG_Group']).size().unstack().fillna(0)
clnsig_chr_group['total'] = clnsig_chr_group.sum(axis=1)
clnsig_chr_group['Pathogenic rate'] = round(clnsig_chr_group['Pathogenic'] / clnsig_chr_group['total'] * 100, 2)
clnsig_chr_group

뭔가… 비중이 생각보다 얼마 안됨… 아, Pathogenic은 발병시키는, 병원(호스피털 말고)성의 뭐 그런 뜻이다. 그니까 패소제닉한건 터지면 병되는건데 이게 유전자 바이 유전자지만 암이 되는 경우도 있고, 유전병이 되는 경우도 있다.

 

ax = sns.barplot(clnsig_chr_group, x = 'CHROM_Type', y = 'Pathogenic rate', hue='CHROM_Type')
plt.title('염색체별 Pathogenic 비율')
plt.xlabel('염색체')
plt.ylabel('Pathogenic 비율 (%)')
plt.xticks(ticks=[0, 1, 2], labels=['상염색체','미토콘드리아','성염색체'])
plt.xlim(-0.5, 2.5)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, fmt='%.2f', padding=3, fontsize=10)

# 얘는 로그 빼도 됩니다. 백분율이라;;
plt.tight_layout()
plt.show()

불명은 아예 0이라 축 조절하면서 빼버림… 저 축 틱이 0부터 2까지라고 범위도 0부터 2까지로 하시면 막대들이 양 옆으로 달라붙습니다. 항상 여유 범위를 주십시오.

 

Pathogenic한 변이들

clnsig_pathogenic = clinvar_df_analysis.query('CLNSIG == "Pathogenic"') # Pathogenic
clnsig_pathogenic

이제 우리는 병원성 변이들에 주목해보자.

 

clnsig_pathogenic.groupby('CLNVC').size().sort_values(ascending=False)

하필이면 제일 잡기 빡센 놈이 제일 많네… Single nucleotide variant가 뭐냐면… 그… 나비효과 아세요? 나비의 날갯짓이 지구 반대편에서 토네이도 된다는. 딱 그짝이다. 사람 몸 크기에 비하면 DNA 염기가 되게 작거든요. 염색체 안에 저런게 수백 수천만개가 때려박혀져 있는데 그 중에서 고거 하나 바뀐걸로 아미노산이 바뀌고(바꼈는데 같은 아미노산을 지정하는 경우도 있음) 그걸로 단백질 접힘이 바뀌고 접힘이 바뀐 단백질이 몸에 큰 영향을 끼친다.

 

저게 왜 잡기 빡세냐고? 스케일이 작잖아요. 염기가 뭉텅이로 빠진것도 아니고 딱 하나 빠진거잖아요. 마치 쌀알 10000개인 밥과 10001개인 밥을 주고 어떤게 만개게? 하는거랑 비슷하다.

 

ax = sns.barplot(clnsig_pathogenic_nvc)
purple_color = plt.colormaps['Purples'](0.2)

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 20000:
        pass
    else:
        patch.set_color(purple_color)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('변이 유형별 분포')
plt.ylabel('Count')
plt.show()

SNV는 알겠는데 저 뒤에 두개는 뭐냐… Deletion은 염기가 하나든 여러개든 있었는데 없었습니다 된 거고 Duplication은 시퀀스가 갑자기 원쁠원이 되는거다. 예를 들어서 GAATTC가 하나였다가 두개가 되는 거 말이다. transposon이랑은 다릅니다. 걔는 태생이 이사다니는 놈임.

 

염색체 유형별 분류

clnsig_pathogenic_chrom = clnsig_pathogenic.groupby('CHROM_Type').size().sort_values(ascending=False)
clnsig_pathogenic_chrom
ax = sns.barplot(clnsig_pathogenic_chrom)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('염색체 종류별 분포')
plt.ylabel('Count')
plt.yscale('log')
plt.show()

상염색체는 22개고(1~22) 성염색체는 두개라 그런가…?

 

염색체별 세분류

clnsig_pathogenic_chrom = clnsig_pathogenic.groupby('CHROM').size().sort_values(ascending=False)
clnsig_pathogenic_chrom
ax = sns.barplot(clnsig_pathogenic_chrom[:10])
purple_color = plt.colormaps['Purples'](0.2)

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 10000:
        pass
    else:
        patch.set_color(purple_color)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('변이 유형별 분포 TOP 10 (염색체별)')
plt.ylabel('Count')
plt.show()

다 보기는 좀 거시기해서 TOP 10만 봤다. 17번, 2번 다음으로 X염색체가 많고 그 다음으로 1, 11번까지가 TOP 5다.

 

clnsig_pathogenic_chrom_Auto = clnsig_pathogenic.query('CHROM_Type == "Autosome"').groupby('CHROM').size().sort_values(ascending=False)
clnsig_pathogenic_chrom_Auto
ax = sns.barplot(clnsig_pathogenic_chrom_Auto[:10])
purple_color = plt.colormaps['Purples'](0.2)

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 10000:
        pass
    else:
        patch.set_color(purple_color)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('변이 유형별 분포 TOP 10 (상염색체)')
plt.ylabel('Count')
plt.show()

성염색체 빼고 상염색체만 봅시다. 17, 2, 1, 11, 16순으로 많다. 저거는 변이 개수가 10000개 이상인 것만 강조하는거라 저렇게 나온거임…

 

clnsig_pathogenic_chrom_Sex = clnsig_pathogenic.query('CHROM_Type == "Sex_Chrom"').groupby('CHROM').size().sort_values(ascending=False)
clnsig_pathogenic_chrom_Sex
ax = sns.barplot(clnsig_pathogenic_chrom_Sex)
purple_color = plt.colormaps['Purples'](0.2)

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 10000:
        pass
    else:
        patch.set_color(purple_color)

for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('변이 유형별 분포 TOP 10 (상염색체)')
plt.ylabel('Count')
plt.yscale('log')
plt.show()

아놔 타이틀 수정해야되네.. 엥? X염색체가 더 많네요? 그 또한 체급차이다. Y염색체는 X염색체에 비해 짤똥하고 들어있는 유전자도 수십개정도지만, X염색체는 8~900개의 유전자가 들어있다.

 

유전자 TOP 10

clnsig_pathogenic_gene = clnsig_pathogenic.groupby('GENE_SYMBOL').size().sort_values(ascending=False)
clnsig_pathogenic_gene
ax = sns.barplot(clnsig_pathogenic_gene[:10])

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 3500:
        pass
    else:
        patch.set_color(purple_color)


for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('변이 유형별 분포 TOP 10 (유전자별)')
plt.xlabel('유전자')
plt.ylabel('Count')
plt.show()

BRCA 어디서 들어봤다 그죠? 안젤리나 졸리가 저 유전자에 변이가 있어서 유방을 절제하고 복원했잖음. 아니 왜 그렇게까지 해요? 저기 변이 있으면 유방암, 난소암에 걸릴 확률이 올라갑니다. 그러니까 암이라는 상태이상에 취약해지는 디버프인 셈이다. 이 변이도 유전되기때문에 아마 본인이 BRCA 변이가 있다면 가족중에 유방암이나 난소암에 걸린 사람이 계실 것이다.

 

그렇다고 어씨 나도 BRCA 변이 있네 조졌다 이럴것까진 없음. 우리는 늘 그렇듯이 여러분들이 최대한 건강하게 살 수 있도록 연구할겁니다.

 

SNV TOP 10

clnsig_pathogenic_snv = clnsig_pathogenic.query('CLNVC == "single_nucleotide_variant"').groupby('GENE_SYMBOL').size().sort_values(ascending=False)
clnsig_pathogenic_snv
ax = sns.barplot(clnsig_pathogenic_snv[:10])

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 1000:
        pass
    else:
        patch.set_color(purple_color)


for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('SNV가 가장 많은 유전자 TOP 10')
plt.xlabel('유전자')
plt.ylabel('Count')
plt.show()

NF1은 신경섬유종(1형)과 관련 있는 유전자고, FBN1은 찾아보니 피브릴린 1이란다. 마르판 증후군도 FBN1에 문제 생겼을 때 발생하는 병 중 하나다.

 

Deletion TOP 10

clnsig_pathogenic_del = clnsig_pathogenic.query('CLNVC == "Deletion"').groupby('GENE_SYMBOL').size().sort_values(ascending=False)
clnsig_pathogenic_del
ax = sns.barplot(clnsig_pathogenic_del[:10])

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 1000:
        pass
    else:
        patch.set_color(purple_color)


for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('Deletion이 가장 많은 유전자 TOP 10')
plt.xlabel('유전자')
plt.ylabel('Count')
plt.show()

TOP 3은 어디서 많이 보셨던 애들이고… ATM은 찾아보니 암 억제 유전자다. 이건 또 뭐고? 암 억제 유전자는 발현되면 암을 막는 유전자고, 암 유전자(옹코진)는 발현되면 암 되는 유전자다. 전자는 브레이크, 후자는 엑셀.

 

Duplication TOP 10

clnsig_pathogenic_du = clnsig_pathogenic.query('CLNVC == "Duplication"').groupby('GENE_SYMBOL').size().sort_values(ascending=False)
clnsig_pathogenic_du
ax = sns.barplot(clnsig_pathogenic_du[:10])

# 모든 막대(patch) 가져오기
for patch in ax.patches:
    # 예: 특정 조건(height가 20 이상인 경우)의 막대만 색상 변경
    if patch.get_height() > 500:
        pass
    else:
        patch.set_color(purple_color)


for container in ax.containers:
    # fmt='%d'는 정수로 표시, label_type='edge'는 막대 끝에 표시
    ax.bar_label(container, padding=3, fontsize=10)

plt.title('Duplication이 가장 많은 유전자 TOP 10')
plt.xlabel('유전자')
plt.ylabel('Count')
plt.show()

일단 여기까지 하고 태블로로 빠졌음.

 

반응형

'Coding > EDA' 카테고리의 다른 글

Google Play Store – Most Downloaded Android Apps  (0) 2026.03.02
얘! clinvar도 EDA가 된단다! (3)  (0) 2026.02.18
얘! clinvar도 EDA가 된단다! (1)  (0) 2026.02.15
Ramen ratings  (0) 2026.02.11
Post-COVID Video Games Worldwide (2021-2025)  (0) 2026.02.10
Lv. 36 라이츄

Lv. 36 라이츄

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

얘! clinvar도 EDA가 된단다! (1)

Coding/EDA 2026. 2. 15. 20:00
반응형

이거 3부작입니다... 일단 분석을 하다 말았고, 태블로도 써야됨.


clinvar는 유전적 변이와 인간의 표현형과의 관계에 대한 데이터를 수집하여 보관하는 데이터베이스이다. 이게 데이터가 어떻게 되어있냐면 몇번 염색체 어디에 뭐가 어떻게 뻑나면 어떤 변이더라~ 이런게 들어있는데, vcf파일입니다. 이거 분석하려면 여는것부터 골치아픔. 근데 이게 된다고요?

 

내가 vcf파일 상태로는 열기도 조작하기도 귀찮아서 아예 거기 안에 있는 내용을 데이터프레임화하고 csv로 만드는 코드를 짰음.


VCF파일 내용물

'1', '66926', '3385321', 'AG', 'A', '.', '.', 'ALLELEID=3544463;CLNDISDB=Human_Phenotype_Ontology:HP:0000547,MONDO:MONDO:0019200,MeSH:D012174,MedGen:C0035334,OMIM:268000,OMIM:PS268000,Orphanet:791;CLNDN=Retinitis_pigmentosa;CLNHGVS=NC_000001.10:g.66927del;CLNREVSTAT=criteria_provided,_single_submitter;CLNSIG=Uncertain_significance;CLNSIGSCV=SCV005419006;CLNVC=Deletion;CLNVCSO=SO:0000159;GENEINFO=OR4F5:79501;MC=SO:0001627|intron_variant;ORIGIN=0'

'1', '66926', '3385321', 'AG', 'A', '.', '.'는 위치 정보다. 1번 염색체 66926번째 염기에 생긴 변이고, clinvar 아이디는 3385321이고, AG가 A로 바뀐 변이라는 얘기. 뒤에 점 두개는 QUAL, FILTER인데… 왜 비었냐…

 

그 뒤는 INFO로 따로 또 딕셔너리로 묶여있는 공간이다. 그래서 이게 다 뭐냐고?
1. ALLELEID: 대립 유전자 아이디
2. CLNDISDB: 질병 데이터베이스 링크
3. CLNDN: 질병 이름
4. CLNHGVS: HGVS 명명법
5. CLNREVSTAT: 검토 신뢰도
6. CLNSIG: 임상적 유의성
7. CLNVC: 변이 타입
8. GENEINFO: 유전자 정보(이름:Entrez ID)
9. MC: 분자적 영향
10. ORIGIN: 변이의 기원(0: 특정되지 않음)


csv파일로 만들기

일단 저 clinvar가 주기적으로 업데이트되는 데이터베이스입니다. 뭐 하루에 하나씩 바뀌고 그런건 아니고 1~2주마다 바뀌는듯한데, 바뀔때마다 파일명이 같이 바뀌거든요.

vcf_path = 'data/clinvar_20260208.vcf'
f_name = re.search('clinvar_[0-9]{8}', vcf_path).group()

print(f'file name: {f_name}')

근데 저 이름 형식이 바뀌는건 아니라 큐식정(정규표현식)으로 패턴 잡아서 찾아서 이름 추출하면 장땡이긴 해요. 저렇게만 해두면 새 파일을 받았을 때 vcf_path 변수만 새 파일명으로 수정하면 된다.

 

# 1. VCF 읽기 (이전의 DtypeWarning 해결 버전)
def read_vcf_full(path):
    with open(path, 'r') as f:
        lines = [l for l in f if not l.startswith('##')]

    df = pd.read_csv(
        io.StringIO(''.join(lines)),
        sep='\t',
        dtype={'#CHROM': str},
        low_memory=False
    ).rename(columns={'#CHROM': 'CHROM'})

    # 2. INFO 컬럼을 딕셔너리로 파싱하는 함수
    def parse_info(info_str):
        # 'KEY=VALUE' 쌍들을 분리하여 딕셔너리 생성
        info_dict = {}
        for item in info_str.split(';'):
            if '=' in item:
                key, value = item.split('=', 1)
                info_dict[key] = value
            else:
                info_dict[item] = True  # 값이 없는 플래그(Flag) 처리
        return info_dict

    # 3. INFO 파싱 적용 및 데이터프레임 확장
    info_df = pd.DataFrame(df['INFO'].apply(parse_info).tolist())

    # 4. 기존 컬럼과 합치기 (INFO 원본은 삭제)
    final_df = pd.concat([df.drop(columns=['INFO']), info_df], axis=1)

    return final_df

# 실행
clinvar_df = read_vcf_full(vcf_path)

데이터프레임을 만드는 과정인데 이게 정말 개같이 오래 걸립니다. 돌려놓고 똥때리고 오십쇼. 왜 오래걸리냐면 쟤가 행만 한 400만개 됨..

 

clinvar_df.to_csv(f'data/{f_name}.csv', index=False)
print(f'saved: data/{f_name}.csv')

똥때리고 왔더니 데이터프레임이 나왔다... 그럼 csv로 저장하시면 됩니다. 참고로 이거 여는것도 오래 걸림. 

 

근데 태블로가 왜 거기서 나오냐고요? 사람 염색체가 25개입니다. 왜죠? 상염색체 22개+성염색체 두개(XY)+미토콘드리아(얘도 지 유전자가 따로 있음) 해서 25개다. 이걸 하나하나 따로 보는 코딩 자체도 빡세고 귀찮은데 노트북 길어지면 보겠어요? 한 3번염색체까지 보고 에이씨 이거 언제끝나 하겠지. 그리고 저걸 다 시각화하면 그래프가 25개 나오는데 배치도 배치지만 저정도면 그래프 그려도 제대로 안보여요.

 

그러니까 우리가 할 분석들 중에서 염색체에 대한 시각화를 태블로로 뺄거다. 거기서 대시보드 만들고 필터 만들어서 샥샥 하면 끝남.

반응형

'Coding > EDA' 카테고리의 다른 글

얘! clinvar도 EDA가 된단다! (3)  (0) 2026.02.18
얘! clinvar도 EDA가 된단다! (2)  (0) 2026.02.17
Ramen ratings  (0) 2026.02.11
Post-COVID Video Games Worldwide (2021-2025)  (0) 2026.02.10
또 ChEMBL을 털어보았다  (0) 2026.01.28
Lv. 36 라이츄

Lv. 36 라이츄

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

방명록