보통 판다스 데이터프레임 불러와서 지지고 볶고 뭐 해요? 그래프 그리죠. 표로 정리해서 보여주는것보다 그래프 딱 만들어서 도표 딱 보여주면 기깔나쟎아요? 그겁니다. 그리고 우리가 제일 많이 쓰는 맷플롭이나 씨본(+Plotly)에서도 폴라스를 받아줄지 궁금해서 해봤습니다.
이번에 써 볼 데이터프레임은 파일 불러온거 하나(켐플) 있고, 직접 만든거 하나 있습니다.
import polars as pl
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px # 프로젝트 이런거 아니니까 걍 얘 부를게여
위에서부터 순서대로 폴라스, Matplotlib, 씨본, Plotly임다. 불러오십쇼. pyarrow는 불러올 필요 없고 설치만 하면 됨.
위가 피보나치, 아래가 산점도다. ...저기 0이 널 아니예요? 나도 그런 줄 알고 확인해봤는데 그냥 수소결합을 안 하는 놈임.
Seaborn
근데 왜 씨본은 색이 다르냐면 쟤는 Hue 옵션을 줬음. 쟤만 줬어요.
sns.scatterplot(load_df.to_pandas(), x = 'HBA', y = 'HBD')
plt.show()
산점도는 그리다가 결측값때문에 에러가 났는데, 판다스는 NaN(날짜는 NaT)이지만 폴라스는 null이잖아요? 이걸 결측값 처리 안 하고 걍 주면 씨본이 이게 뭐시여 하고 오류를 토하니까 to_pandas()를 준 겁니다. 근데 저게 pyarrow가 설치되어있어야 가능하니까 또 설치한건데… 여러분들이 EDA를 할 거라면 안 쓸 칼럼은 날릴거고, 그게 아니어도 결측값을 어떻게든 채울거잖아요? 결측값 땜질만 적절하게 하면 오류는 볼 일이 없습니다.
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배 빨랐음. 뭐 메모리 어쩌고 하던데 나는 컴퓨터 아키텍쳐까지는 잘 모르니까 패스하고... 판다스랑 좀 다른 부분이 있다는 건 유념하십쇼.
여기서 문제가 하나 있음. 저기서 어떤게 폴라스고 어떤게 판다스게요? 위에껍니다. 세부적인 옵션 빼고 걍 불러오는건 폴라스나 판다스나 비슷해서 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 쿼리예요.
요즘 EDA할때 생략하는 그거 맞다. 폴라스... 하게되면 이거는 정보 확인하는거 한번 올려드림. 판다스랑 기능은 똑같은데 토해내는게 달라요.
df_csv.schema
.info() 비슷한 기능을 하는게 .schema인데 인포처럼 칼럼 몇갠데 몇개 비었고 이런건 안나오고 뭔 타입인지만 나온다. 인포 비슷한거 필요하시면 .glimpse() 쓰십쇼. 아니면 널카운트?
아니면 .describe()에서 칼럼별 통계값 보고 알 수 있다. 예? 뭔 소리임? 폴라스에서는 널이 결측값이라고 했잖아요? 근데 SQL에서 연산에 널끼면 다 널됩니다. 왜냐고요? 널이 응 아니 몰라에서 몰라거든. 설문조사로 치자면 예 아니오 무응답에서 무응답이다. 슈뢰딩거의 고양이는 박스 까보면 얘가 갔는지 있는지는 알 수 있지만 널은 박스 밀봉된 슈뢰딩거의 고양이라 진짜 모름.
이 글에서는 그냥 데이터 열고 정보 확인하는것만 비교해봤는데, 다음편에서는 시각화 어떻게 되나 해 볼 예정이다. matplotlib나 seaborn에서는 폴라스를 못 쓴다는 얘기가 있음.
이거어어어어는... 돌리는건 하나 돌렸습니다. 그럼 왜함? 그 옵튜나인가 뭔가 하는 거 써보려고 했음.
Optuna
파이썬 라이브러리인데, 하이퍼파라미터 튜닝할때 쓴다. 모델 학습 전 사용자가 직접 설정하는 외부 구성 변수를 하이퍼파라미터라고 하는데, 이걸 사람이 수동으로 일일이 조정해가면서 어느 조건에서 내 모델을 뽕을 잘 뽑을지를 고민...하면서 일일이 하다 보면 할 것도 많고 파라미터 하나하나 일일이 손대가면서 찾기도 힘들잖아요? 그 노가다를 알아서 해주는게 옵튜나입니다.
모델 바이 모델이라 함수에 집어넣는 게 다른데, 아무튼 이거 이거 이거 해줘 하면 지 알아서 음 이렇군 하면서 오케이 가릿 이걸로 진행시켜 한다. 모델에 따라서는 시간이 좀 걸리기도 합니다.
안하면 섭한 전처리
자녀 수 범주화
# 자녀 수는 모르겠고 유무로 범주화할거임. ㅇㅋ? ㅇㅇㅋ.
cost['have_child'] = cost['children'].apply(lambda x: 0 if x == 0 else 1) # 자녀수가 0이면 0, 0보다 크면 1
# 유자녀가 1입니다
머고 오늘은 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
이거 왜 보냐면 독립변수끼리 상관이 있나를 보는겁니다. 지들끼리 상관이 있으면 모델 시망됨.
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))
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한테 저 샤프가 오은영박사님에 빙의해서 왜 이렇게 분류했냐고 물어봤을 거 아닙니까? 그러니까 모델이 인제 얘를 이렇게 분류한 이유를 얘기해주는겁니다. 얘는 산도가 너무 높아서 이렇게 했고 얘는 밀도가 이래서 이렇게 분류했어요, 이렇게. 그게 저 그래프임다.
롸? 갑자기 쟤가 왜 나옴? 임퓨터가 평균, 중앙값, 최빈값으로 채워주는건데 문제가 하나 있습니다. 그게 값 분포에 따라 적절한 걸 골라야지 덮어놓고 히히 평균해야징 하다간 모델 성능이 똥멍청이 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라는 게 있는데, 얘는 결측값을 이웃한 그룹들을 참고해서 채워주는 친구다. 그렇게 해서 몸무게까지 네개 채워주면 일단 끝… 왜 저걸 일괄로 채움? 이유는 간단하다. 저 칼럼에 결측값이 두개씩 있었는데 그 두개가 같은 펭귄에서 빠져있었거든…
일단 저 칼럼 트랜스포머가 뭐 하는 친구인지는 모르겠고 저게 다 된 거 맞습니다. 이대로 학습 들어가면 되는'데'...
이제 학습해야징 히히
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))
근데 그럴수밖에 없는게 XGBoost나 LightGBM 둘 다 스케일이 큰 애들에 특화되어 있습니다. 그 미국에서 호미 엄청 사가는거 아십니까? 거기는 막 옥수수밭에서 길 잃어먹는 사람도 있을 정도로 밭이 커갖고 농기구들도 스케일이 장난 아니예요. 근데 그게 호미랑 뭔 상관이냐고? 그 농기구들로 정원 손질하려니 세밀하게 안되는거지 이제. 잡초 뽑으려다가 엄한 꽃도 다치고 그러거든요. 근데 호미는 미쿡 농기구 스케일에 비하면 스케일도 작지, 하나로 다 하지… 그거임다.
뭘 많이 가렸죠? 다 개인정보라 어쩔 수 없음.. 그럼 논문하고 자격증은 왜 안 가렸냐… 논문은 이미 저널에 실린거고 자격증도 내가 땄다고 올렸잖아요. 쟤들은 이미 한번 깐거라 안 가린거임.
원래 생각했던 구성에는 저 카드가 없었습니다. 없었는데 제미나이가 "씁 근데 이거 이렇게만 보면 좀 심심하지 않을까요?라고 하면서 제안한겁니다. 저 선이랑 배경도 지금 조정중인데 선이 굵다고 이쁜건 아니고... 없애봤는데 이것도 아 씁 아닌 것 같고... 그림자는 그래도 배경이나 안이나 톤이 다 연해서 있는 게 나은 것 같긴 하고... 애매함. 그냥 애매함.
이렇게 트랜지션이랑 호버를 주면 마우스를 댔을 때 뜨는데 문제가 하나 있다. 여러분들 그거 아십니까? 모바일에서 호버 안되는거. 당연한 얘기지만, 서마터폰이나 태블릿 PC에서는 마우스를 쓰는 게 아니라 손으로 누르잖아요. 드래그도 손으로 하는데 어쨌든 이게 손으로 뭘 누른 상태에서 움직이고 걍 볼 때는 손을 또 안 대잖아요? 그래서 의미가 없다 이거요.
근데 메인페이지는 이거 말고는 내용 말고 뭐 없는데 내용을 다 가려버려서 더 서술할게 없음.
스킬
어차피 헤더는 고정이니까 여기만 캡쳐하겠음... 이건 뭐냐면 내가 할 수 있는 것들을 나열한거다. 저 그리드 레이아웃은 나중에 스킬에 뭔가 더 추가되면 바뀔 수도 있는데 지금은 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. 이걸 어디에 올릴지는 정해지지 않았는데, 이거 올려도 여기에 링크는 안 올릴듯합니다. 일단 저게 포폴이라 내 개인정보를 다 깠어요... 아까도 가리고 올렸잖음. 물론 이 글을 보고 있는 당신이 인사담당자고 내가 당신이 재직중인 회사에 지원하면서 이거 포폴인데 보실래요? 한다거나 나랑 링크드인 팔로워거나 하면 볼 수는 있겠지만 기본적으로 블로그에는 안 올릴 생각입니다. 지금 깃헙에는 올라가있는데 서버에서 내리고 나면 깃헙에서도 내릴까 생각중임.
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])
# 날짜가 두개지요?
# 난 저 리치드가 제출일인 줄 알았는데 일고보니 특정 다운로드 수에 도달한 날짜였음... 그래서 리치드가 퍼블리시드보다 뒤 시점입니다.
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')
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
마인크래프트나 로블록스나 다들 애들이 좋아하는 게임이간 한데, 마인크래프트는 '돈을 내고' 게임을 사야 하고 로블록스는 설치는 공짜고 인게임 안에서 뭘 사야 하는 구조다. 그럼 유료게임들이 다 마일스톤 달성이 오래 걸리냐 하면 그것도 아닌게, 위에 빨리 달성한 게임들에도 유료게임이 있어요. 게임이 오래 가려면 재미와 게임성도 중요하지만 운영을 X같이 하면 안됩니다.
일단 유튜브 뮤직은 한번도 쓴적 없지만 가끔 Flo는 쓴다. 근데 이것도 요금제 무료로 주는거 있어서 쓰는거지 보통은 잘 안 씀. 나는 음원 다운로드해서 폰에 넣고다닌다. 애초에 내가 듣고 다니는 노래 중에 특정 음원사이트에는 수록 안 되는 곡도 있고(특히 멜론) 대부분 요금이 구독제거든.. 내가 구독하는건 포홈 박스(1년에 2만원)랑 닌스온, 드롭박스, 아이클라우드가 다다.
그리고 유튜브는 이것들 광고 꼬라지가 X같아서 구독하기 싫음. 막말로 얘네 돈만 많이 주면 나도 광고해줄걸?
음... 스타듀밸리는 그럴만 했지. 후르츠 닌자도 예전에 스마트폰이나 어른패드에서 많이 했던 게임 중 하나다. 일단 저 게임은 룰이 대단히 간단한데, 과일만 싹둑해야 합니다. 근데 그 과일만 싹둑하는 작업이 은근 어려운게 포인트임. 염소 시뮬레이터는... 그래요.. 걔들은 약 빨고 게임을 만들었어...
있는것도 복잡한데 저걸 왜 넣냐고요? 라이노바이러스는 좀 덜한데, 이게 기본적으로 300개씩 찾고 그렇다보니 계통수가 무지하게 길어집니다. 이러면 이걸 넣는 나도 고통이고 읽는 사람도 고통이예요. 거의 뭔 스크롤이여 스크롤. 근데 계층적 군집분석 결과가 덴드로그램인데 이거 목 꺾고 옆으로 보면 계통수거든요? 그리고 어쨌든 묶은거니까 이거 넣어보자 해서 넣었죠.
실루엣 계수
이게 원래는 군집 내에서의 응집도와 다른 군집간의 거리를 비교해서 군집분석이 잘 됐는지, 안 됐는지를 평가하는 지표인데 k-means나 k-medoid에서 군집 개수 나눌때도 쓴다. 그 개수가 돌려돌려 돌림판으로 나오는게 아닙니다… 그럼 계통수는 버리는건가요? 아니, 그거 보고 대충 개수 나눌수도 있다.
한타바이러스 실루엣 계수
이거 봐봐요 이걸로 뭘 어떻게 정할거야… 저거 계통수 산출한거에서 덩어리 수 보고 정했어요 결국…
k-medoid
아니 근데 왜 하필 저놈임? 일단 계통수를 만들고 쟤까지 진행하는거라 거리행렬이 준비되어있는데, k-medoid는 그 거리행렬을 주기만 하면 됩니다. 그리고 서열이 이미 준비되어있으니 대표서열로 걍 하면 되고, 이친구는 중앙값으로 하는거라 이상치에 영향을 덜 받는다.
아니 우리 거리행렬이 있었어요?
# 1. 거리 계산
calculator = DistanceCalculator('identity')
dm = calculator.get_distance(alignment)
이거 찾수?
인플루엔자 K-medoid
인플루엔자는 한 '아종' 안에서 갈라지는거고 코로나바이러스는 스파이크만 해서 트리 관련 통계가 한타, 라이노랑 다르지만 이건 일단 계통수만 도출할 수 있으면 다 그릴 수 있기 때문에 계통수를 대체할 수 있다. 단, 쿼리를 잘 짜야 한다는 거… 저기 혼자 떨어진 점 보여요? 저거 partial CDS인데 쟤 끼면 군집 분포가 이상해진다.
생각보다 분량은 없지만 일단 태블로로 넘어와봤다. 전에도 얘기했지만 염색체가 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이 없으면 밑에 아무것도 안 뜹니다. 그니까 진짜 밑에 아무것도 안 뜸. 이걸 어떻게 처리를 해보려고 했는데 제미나이가 똥을 싸요 이번에는. 아이 이 에미나이. 그래서 걍 뒀습니다. 방법이 없어요 나도.
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번부터 다 본다? 하나씩 그리면 스크롤이 너무 길고 그렇다고 모으자니 그래프가 뵈지도 않아요.
하필이면 제일 잡기 빡센 놈이 제일 많네… 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이랑은 다릅니다. 걔는 태생이 이사다니는 놈임.
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다.
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개 이상인 것만 강조하는거라 저렇게 나온거임…
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개의 유전자가 들어있다.
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 변이 있네 조졌다 이럴것까진 없음. 우리는 늘 그렇듯이 여러분들이 최대한 건강하게 살 수 있도록 연구할겁니다.
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에 문제 생겼을 때 발생하는 병 중 하나다.
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은 찾아보니 암 억제 유전자다. 이건 또 뭐고? 암 억제 유전자는 발현되면 암을 막는 유전자고, 암 유전자(옹코진)는 발현되면 암 되는 유전자다. 전자는 브레이크, 후자는 엑셀.
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()
clinvar는 유전적 변이와 인간의 표현형과의 관계에 대한 데이터를 수집하여 보관하는 데이터베이스이다. 이게 데이터가 어떻게 되어있냐면 몇번 염색체 어디에 뭐가 어떻게 뻑나면 어떤 변이더라~ 이런게 들어있는데, vcf파일입니다. 이거 분석하려면 여는것부터 골치아픔. 근데 이게 된다고요?
내가 vcf파일 상태로는 열기도 조작하기도 귀찮아서 아예 거기 안에 있는 내용을 데이터프레임화하고 csv로 만드는 코드를 짰음.
'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 변수만 새 파일명으로 수정하면 된다.
# 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만개 됨..
똥때리고 왔더니 데이터프레임이 나왔다... 그럼 csv로 저장하시면 됩니다. 참고로 이거 여는것도 오래 걸림.
근데 태블로가 왜 거기서 나오냐고요? 사람 염색체가 25개입니다. 왜죠? 상염색체 22개+성염색체 두개(XY)+미토콘드리아(얘도 지 유전자가 따로 있음) 해서 25개다. 이걸 하나하나 따로 보는 코딩 자체도 빡세고 귀찮은데 노트북 길어지면 보겠어요? 한 3번염색체까지 보고 에이씨 이거 언제끝나 하겠지. 그리고 저걸 다 시각화하면 그래프가 25개 나오는데 배치도 배치지만 저정도면 그래프 그려도 제대로 안보여요.
그러니까 우리가 할 분석들 중에서 염색체에 대한 시각화를 태블로로 뺄거다. 거기서 대시보드 만들고 필터 만들어서 샥샥 하면 끝남.