사이킷런의 프레임워크와 연동할 수 있는 전용 XGBoost 래퍼 클래스에는 분류용 XGBoostClassifier, 회귀용 XGBoostRegressor이 있습니다. 래퍼 클래스는 다음과 같은 장점을 가지고 있습니다.

  • 사이킷런의 기본 estimator를 그대로 상속해 만들었기 때문에 fit()과 predict()만으로 학습과 예측이 가능합니다.
  • GridSearchCV, Pipeline 등 다른 사이킷런의 다른 유틸리티를 그대로 함께 사용할 수 있습니다.
  • 기존의 다른 프로그램의 알고리즘으로 XGBoost 래퍼 클래스를 사용할 수도 있습니다.

 

https://smartest-suri.tistory.com/40

 

ML | 파이썬 XGBoost API 사용하여 위스콘신 유방암 예측하기

XGBoost란?트리 기반의 앙상블 학습에서 가장 각광받고 있는 알고리즘 중 하나로, 캐글(kaggle) 등 경연 대회에서 입상한 많은 데이터 사이언티스트들이 XGboost를 사용하면서 널리 알려지게 되었습니

smartest-suri.tistory.com

 

이전 글에서 학습했던 기본 XGBoost API 대신 사이킷런 연동 XGBoost 래퍼 클래스 XGBoostClassifier를 사용해 모델을 학습시키고 예측을 수행해 보겠습니다.

 


 

 

호출 및 hyperparameter

from xgboost import XGBClassifier
xgb_wrapper = XGBClassifier(n_estimators = 400, # num_boost_round -> n_estimators
                            learning_rate = 0.05, # eta -> learning_rate
                            max_depth = 3, 
                            eval_metric = 'logloss')

 

learning_rate와 같이 기존 사이킷런 하이퍼 파라미터와의 호환성 유지를 위해 변경된 하이퍼 파라미터들이 있으므로 유의합니다.

 

 

 

학습 및 예측

fit()과 predict() 메소드를 이용해서 모델을 학습시키고 예측을 수행해 보겠습니다.

# 학습
xgb_wrapper.fit(X_train, y_train, verbose = True)

# 예측
w_preds = xgb_wrapper.predict(X_test)
w_preds[:10]
# array([1, 0, 1, 0, 1, 1, 1, 1, 1, 0])

w_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]
w_pred_proba[:10]
# array([8.8368094e-01, 2.7957589e-03, 8.9875424e-01, 1.8748048e-01,
#        9.9204481e-01, 9.9990714e-01, 9.9954444e-01, 9.9904817e-01,
#        9.9527210e-01, 1.9664205e-04], dtype=float32)

 

아주 빠르고 간단하게 학습과 예측을 수행했습니다.

 

 

평가

이전 포스팅에서 작성해 둔 get_clf_eval() 함수를 이용해서 사이킷런 래퍼 XGBoost로 만들어진 모델의 예측 성능 평가를 해 보겠습니다.

get_clf_eval(y_test, w_preds, w_pred_proba)

지표 이전 이후
Accuracy 약 0.96 약 0.97
Precision 약 0.97 약 0.97
Recall 약 0.97 약 0.98
F1-Score 약 0.97 약 0.98
ROC-AUC 약 0.99 약 0.99

 

이전 실습보다 평가 지표가 조금 상승했습니다. 이번 실습에서는 early stopping을 따로 설정하지 않은 관계로 train 데이터를 train과 valid 데이터로 나누는 과정을 생략하였고, 그래서 트레인 데이터셋의 수가 늘어난 영향이 있을 것으로 파악됩니다. (애초에 트레이닝 데이터가 풍부한 데이터셋은 아닌 관계로)

 

 

여기까지 XGBoost 관련 두 번째 실습을 마쳐봅니다. 감사합니다 :-)

 

XGBoost란?

트리 기반의 앙상블 학습에서 가장 각광받고 있는 알고리즘 중 하나로, 캐글(kaggle) 등 경연 대회에서 입상한 많은 데이터 사이언티스트들이 XGboost를 사용하면서 널리 알려지게 되었습니다. 대체로 분류에 있어서 뛰어난 예측 성능을 보이는 특징을 가지고 있습니다.

 

XGboost는 GBM에 기반하고 있는데요. GBM보다 빠르게 학습이 가능하고 오버핏팅 규제 부재 문제 등을 해결한다는 장점이 있다고 합니다. 그 밖에도 Tree pruning이 가능하여 더 이상 긍정 이득이 없는 분할을 가지치기 해서 분할 수를 더 줄이는 추가적인 장점, 자체 내장된 교차 검증 기능, 결손값을 자체 처리할 수 있는 기능 등의 장점도 가지고 있습니다.

 

XGBoost API 학습을 위해 위스콘신 유방암 데이터로 실습을 진행해 본 결과를 정리하여 공유하고자 합니다 :-)

 

 

 

sklearn dataset 위스콘신 유방암 데이터 불러오기

sklearn의 자체 내장 데이터인 load_breast_cancer을 불러오겠습니다.

import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

dataset = load_breast_cancer()
features = dataset.data
labels = dataset.target

cancer_df = pd.DataFrame(data = features, columns = dataset.feature_names)
cancer_df.head(3)

cancer_df['target'] = labels
cancer_df.head(3)

 

데이터프레임 마지막에 target 컬럼을 추가하여 각 row의 label을 0 또는 1로 나타냈습니다.

dataset.target_names
# array(['malignant', 'benign'], dtype='<U9')

cancer_df['target'].value_counts()
# target
# 1    357
# 0    212
# Name: count, dtype: int64

 

label이 무엇이 있는지 확인해 보니, 0은 malignanat, 1은 benign임을 확인할 수 있었고, 각각 212개와 357개가 있는 것을 확인할 수 있었습니다.

 

 

train, valid, test 데이터셋 나누기

X_features = cancer_df.iloc[:, :-1]
y_label = cancer_df.iloc[:, -1]

 

먼저 target 컬럼을 제거한 X_feature 데이터프레임, target 컬럼만 떼어낸 y_label 데이터프레임을 생성합니다.

# 8:2 비율로 train:test 나누기
X_train, X_test, y_train, y_test = train_test_split(X_features, y_label, 
                                                    test_size = 0.2, 
                                                    random_state = 156)
                                                    
# 9:1 비율로 다시 train:valid 나누기
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, 
                                            test_size = 0.1, 
                                            random_state = 156)

 

train_test_split() 메소드를 이용하여 8:2 비율로 train:test 데이터셋을 나누고,

나누어진 train은 다시 한 번 9:1 비율로 train:valid으로 나누었습니다.

len(X_tr), len(X_val), len(y_tr), len(y_val), len(X_test), len(y_test)
# (409, 46, 409, 46, 114, 114)

X_tr.shape, y_tr.shape
# ((409, 30), (409,))

 

train, valid, test 데이터의 개수와 shape 등을 확인해 보았습니다.

 

 

 

DMatrix

파이썬 래퍼 XGBoost는 전용 데이터 객체인 DMatrix를 사용합니다. DMatrix의 주요 입력 파라미터는 data와 label입니다.

  • data : 피처 데이터 세트
  • label : (분류) 레이블 데이터 세트 / (회귀) 숫자형인 종속값 데이터 세트
dtr = xgb.DMatrix(data = X_tr, label = y_tr)
dval = xgb.DMatrix(data = X_val, label = y_val)
dtest = xgb.DMatrix(data = X_test, label = y_test)

 

 

Hyperparameter

딕셔너리 형태로 하이퍼 파라미터를 설정합니다.

params = {
    'max_depth' : 3,  # 트리의 최대 깊이는 3
    'eta' : 0.05,     # 학습률 0.05
    'objective' : 'binary:logistic',
    'eval_metric' : 'logloss'
}

num_rounds = 400       # 부스팅 반복 횟수는 400회

 

 

XGBoost 모델 학습

  • early_stopping_rounds : 더이상 지표 개선이 없을 경우에 횟수를 모두 채우지 않고 중간에 반복을 빠져 나올 수 있도록 하는 탈출 도구 (조기 중단, 얼리스탑핑)
  • evals : 평가용 데이터 세트, [(학습튜플), (평가튜플)] 형태 - 반복마다 지정된 평가용 데이터 세트에서 eval_metric의 지정된 평가 지표로 예측 오류를 측정 
xgb_model = xgb.train(params = params,
                      dtrain = dtr,
                      num_boost_round = num_rounds,
                      early_stopping_rounds = 50, # 50번째부터 얼리스탑핑 가능
                      evals = [(dtr,'train'), (dval,'eval')])

400회를 다 채우지 못하고 250회에서 중단되었습니다

 

예측 수행

predict() 메소드를 이용해서 test 데이터의 결과를 예측해 보겠습니다.

pred_probs = xgb_model.predict(dtest)

 

예측한 결과를 다양하게 살펴봅시다. (초반 10개값만 봅시다.)

pred_probs[:10]
# array([9.3829882e-01, 3.6695320e-03, 7.5020140e-01, 4.9393266e-02,
#        9.8045909e-01, 9.9958366e-01, 9.9899417e-01, 9.9919862e-01,
#        9.9767953e-01, 5.2314228e-04], dtype=float32)

 

어떤 값인지 한 눈에 보이지 않으므로 np.round()를 이용해서 살펴보겠습니다.

np.round(pred_probs[:10])
# array([1., 0., 1., 0., 1., 1., 1., 1., 1., 0.], dtype=float32)
# 일의 자리까지 반올림

np.round(pred_probs[:10], 3)
# array([0.938, 0.004, 0.75 , 0.049, 0.98 , 1.   , 0.999, 0.999, 0.998,
#       0.001], dtype=float32)
# 소수 셋째 자리까지 반올림

 

반올림을 해서 보니까 예측한 값이 0과 1 사이의 값으로 도출되었음을 확인할 수 있었습니다. 

이제 예측 확률이 0.5보다 크면 1, 그렇지 않으면 0으로 최종 예측값을 결정하여 preds 리스트에 저장하겠습니다.

preds = [1 if x > 0.5 else 0 for x in pred_probs]
preds[:10]
# [1, 0, 1, 0, 1, 1, 1, 1, 1, 0]

 

 

get_clf_eval() 함수

모델을 평가할 수 있는 함수를 작성해 보겠습니다.

from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix, f1_score, roc_auc_score

def get_clf_eval(y_test, pred = None, pred_proba = None):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)
    precision = precision_score(y_test, pred)
    recall = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('Confusion Matrix')
    print(confusion)
    print(f'accuracy : {accuracy}, precision : {precision:.4f}, recall : {recall:.4f}')
    print(f'f1 : {f1}, roc_auc : {roc_auc}')
get_clf_eval(y_test, preds, pred_probs)

  • 정확도 accuracy 약 0.96
  • 정밀도 precision 약 0.97
  • 재현율 recall 약 0.97
  • F1-스코어 0.97
  • ROC-AUC 약 0.99

평가 지표가 굉장히 좋네요 :-)

 

 

XGBoost 내장 시각화 기능 수행하기

XGBoost의 plot_importance() API피처의 중요도를 막대그래프 형식으로 나타냅니다.

  • 기본 평가 지표로 f스코어를 기반으로 해당 피처의 중요도를 나타냅니다.
  • f스코어는 해당 피처가 트리 분할 시 얼마나 자주 사용되었는지를 지표로 나타낸 값입니다.
  • xgboost 패키지는 plot_importance()를 이용해 바로 피처 중요도를 시각화할 수 있습니다.
  • plot_importance() 호출 시 파라미터로 앞에서 학습이 완료된 모델 객체 및 matplotlib의 ax 객체를 입력하면 됩니다.
import matplotlib.pyplot as plt
%matplotlib inline

fix, ax = plt.subplots(figsize = (10, 12))
plot_importance(xgb_model, ax = ax)
plt.tight_layout()
plt.savefig('plot.png')

 

 

 

여기까지 XGBoost API 실습 포스팅이었습니다 :-)

감사합니다.

 

2024년 5월 4일 (토) AWS CCP (Certified Cloud Practitioner) 자격증 시험에 응시해서 한 번에 합격했습니다 :-)

준비 이유, 공부 과정, 합격 후기 3가지 테마로 나누어 회고를 작성해 보고자 합니다.

 


 

1. 응시한 이유

현업에서 클라우드 기반의 업무를 하는 곳이 많다는 것을 알게 된 이후로 줄곧 클라우드에 대한 호기심을 가지고 있었습니다. 하지만 학교에서 일하던 저에게는 클라우드에 대한 기초지식도 경험도 전무했습니다. 갈수록 클라우드가 뭔지 점점 더 궁금했습니다. 그래서 배우기로 했습니다. 궁금한 게 있으면 배워버리면 그만 아니겠습니까?

 

가장 배우고 싶었던 건 아마존 웹서비스(AWS)였는데요. 아마존 웹서비스의 클라우드 환경에 대해 전반적인 지식을 습득하고, 어떤 기능과 서비스가 있는지 파악하고 싶었습니다. 그리고 CCP 자격증이 그러한 저의 목적에 가장 부합한다는 것을 알게 되었습니다.

 

https://aws.amazon.com/ko/certification/certified-cloud-practitioner/

 

AWS Certified Cloud Practitioner 자격증 | AWS Certification | AWS

이 자격증을 취득하면 AWS 클라우드, 서비스 및 용어에 대한 높은 수준의 이해가 입증됩니다. 이 자격증은 IT 배경이 아닌 사람이 클라우드로 전환하는 후보자가 클라우드 경력을 쌓을 수 있는 입

aws.amazon.com

 

2. 공부 과정

저는 다음과 같이 3가지 방법으로 이번 시험을 준비했습니다.

1. Udemy 강의를 들으며 AWS 구조, 서비스, 기능 등에 대한 이해와 Hands-On 실습

 

제가 들은 강의는 <[NEW] Ultimate AWS Certified Cloud Practitioner CLF-C02> 입니다. 처음엔 '15시간이면 짧네'라고 생각하고 듣기 시작했는데요. 실제로 체감되는 강의 시간은 그보다 훨씬 길었습니다. 특히 저는 모든 실습을 따라했고 따로 정리까지 해가며 들어서 진도가 더 더뎠는데요. 강의를 다 듣는 데 꼬박 3주가 걸렸습니다. 다른 일이나 공부를 병행하지 않았다면 2주면 충분했을 것 같습니다.

 

말씀드린 대로 저는 자격증 취득 뿐만 아니라 정말 AWS에 대해 제대로 알고 싶었기 때문에 강의를 들었고, 대체적으로 만족합니다. 프랑스 액센트라던지 강사의 쩝쩝대는 소리 등이 썩 유쾌하지는 않았기 때문에 이 강사의 다른 강의를 또 듣진 않을 것 같습니다. 그래도 강의 구성이나 내용 면에서는 무척 만족하고 있어요. 강의를 완강한 이후로 내가 AWS에 어떤 서비스가 있고, 언제 어떤 서비스를 이용할 수 있을지 파악하고 있다는 자신감이 생겼습니다.

 

다만 강의가 영어로 진행되므로 영어에 대한 거부감이 있으시면 해당 강의를 비추드립니다. 또 저처럼 AWS 서비스에 대한 이해가 목적이 아닌 빠르게 자격증 취득만 목표로 하고 계신 분, 어느정도 기초 지식이 있으신 분들께서도 굳이 강의를 들으실 필요는 없을 듯합니다. 무엇보다 배울 의지가 없는 분들께서는 해당 강의를 절대 끝까지 완강하지 못하실 것이라고 생각합니다.

 

(*) 강의는 할인 가격으로 17,000원에 구매했습니다.

 

2. AWS Skill Builder 웹사이트의 AWS Cloud Practitioner Essentials Course를 반복 학습하며 서비스별 특징과 기능에 대한 이해 강화

기본 : 클릭  / 한국어 자막 : 클릭

 

스킬 빌더 웹사이트의 에센셜 코스는 CCP 준비하는 사람이라면 반드시 봐야 하는 필수 코스라고 생각합니다. 정말 추천합니다. AWS에서 준비한 만큼 설명이 아주 깔끔하고 친절하게 잘 돼있습니다.

 

"설명을 읽고 복습 -> 서비스별 중요 포인트 기능과 컨셉 암기 -> 확인 문제 풀면서 복습 -> 마지막 모의고사 풀면서 복습" 하는 과정을 3회독 반복하고 났더니, 시험을 통과할 수 있겠다는 자신감이 더욱 up up 되었습니다. 따로 유데미나 강의를 안 들으시는 분들께서는 반드시 에센셜 코스의 모든 내용을 숙지하고 암기하시면서 시험을 준비하시기 바랍니다.

 

아, 저는 영어로 봤는데요. 한국어 번역도 매끄럽게 잘 돼있는지는 모르겠습니다만, 영어 문장도 어렵지 않게 되어 있으니 한번 영어로 공부하는 것도 도전해 보시길 바랍니다. 

 

3. Udemy 모의고사를 2회독하면서 이해가 부족한 점을 찾아서 보완

 

마지막으로 부족한 부분은 Udemy 모의고사 <6 Practice Exams | AWS Certified Cloud Practitioner CLF-C02> 를 여러 번 풀면서 보완했습니다. 1회독 할 때는 문제를 풀면서 모르는 개념을 찾아서 외워가면서 풀었구요, 오답노트를 따로 정리했습니다. 2회독 할 때는 실전 느낌으로 풀었습니다. 좀 지저분한 문제들도 많긴 하지만 그래도 대체적으로 실전이랑 비슷한 느낌이라고 생각합니다. 1회독 할 땐 너무 어려운거 아냐? 싶었는데 2회독 하니까 적정 난이도로 느껴지더라고요. 유데미 모의고사를 70% 이상으로 패스할 수 있는 수준이라면 실제 시험도 무리 없이 합격하실 수 있을 거예요.

 

(*) 할인 가격으로 17,000원에 구매했습니다.

 

3. 합격 후기

 

 

시험이 끝나자마자 바로 합격/불합격 결과를 확인할 수 있어서 속이 무척 시원했습니다. 시험 당일 밤에 축하 합격 메일이 왔고, 성적표와 자격증 pdf를 다운받을 수 있었습니다. 이렇게 포트폴리오에 한 줄 더 추가할 수 있게 된 점이 무척 기쁘지만, 무엇보다도 클라우드 경험이 없었던 저에게 AWS라는 방대한 플랫폼을 이해하고 수많은 서비스를 파악할 수 있는 능력이 생겼다는 것이 정말 뿌듯합니다. 

 

공부하면서 가장 궁금하고 써보고싶었던 서비스들을 몇 개 정리해 보면 다음과 같은데요.

  1. Rekognition
  2. Quicksight
  3. DynamoDB
  4. EMR
  5. CloudTrail
  6. Lambda, Eventbridge

위 서비스들을 맘껏 사용하고 다룰 수 있는 곳에 취직하면 얼마나 행복할까... 그런 생각을 해 봅니다. (ㅎㅎ)

 

마지막으로, 저는 이어서 AWS Certified Data Engineer - Associate 자격증을 준비하려고 합니다. 5월 말, 6월 초 취득을 목표로 하고 있습니다. 끊임없이 배우고 발전하는 스스로가 되겠다고 오늘도 다짐하며, 자격증 시험 합격 회고 작성을 마무리 하겠습니다.

 

 

감사합니다 :-)

 

 

 

SK플래닛 T아카데미 ASAC 빅데이터 분석, AI 과정 5주 차 회고를 작성합니다.

 

 

지난 5주차에는 셀레니움을 이용해서 웹크롤링 하는 방법, 태블로 프로그램을 이용해 데이터 시각화하는 방법, pandas를 이용해서 데이터 정제 하는 방법을 다루었습니다. 셀레니움과 태블로를 저는 처음 사용해 봤기 때문에 해당 수업을 특히 집중해서 들었고 정말 재밌게 실습에 참여했습니다. 수업이 끝나고 따로 원하는 사이트를 정해서 혼자서 셀레니움 복습도 해 보고, 공공데이터를 받아 태블로 시각화도 복습해 보면서 감을 조금씩 익혔습니다. 네이버 쇼핑 데이터를 크롤링하다 차단을 당하는 웃지 못할 해프닝도 있었습니다. 다양한 방법을 동원해 봤는데 결국 안 풀리더라고요(^^;;;) 개인 프로젝트 주제로 네이버 쇼핑 데이터가 필요했었다면 큰 낭패를 보았을 뻔했습니다. 휴!

 

 

지난 주부터 드디어 본격적인 개인 EDA 프로젝트 준비에 돌입했습니다. 많은 동기들이 주제를 오래 고민했지만 저는 그중에서도 특히 갈피를 잡지 못해 많이 헤맸던 것 같습니다. 다른 사람들은 어떻게 주제를 선정하나 살펴보니, 먼저 본인이 취업하고 싶은 분야를 정하고, 그 분야에 어필할 수 있는 방향으로 주제를 세부화해 나가시더라고요. 예를 들면 나는 쿠팡에 취직을 하고 싶으니 쿠팡의 자체 브랜드에 관해 조사한다던지, 화장품에 관심이 많으니 화장품 성분과 리뷰의 상관관계를 조사한다던지 하는 것이죠. 저의 경우에는 '취업하고 싶은 분야'랄 게 딱히 없었기 때문에 문제가 되었습니다. 여기에도 관심이 있고 저기에도 관심이 있다고 할까요. 내가 모르는 새로운 어떤 분야에든 데이터 사이언티스트로 진출해서 열린 마음으로 일해보고 싶다!는 오픈 마인드였는데, 이게 개인프로젝트 주제 선정에는 도움이 되지 않더라고요.

 

 

결론부터 말씀드리면 저는 제가 지난 20대를 몸담아 바친 교육계와 관련된 주제를 선정하게 되었습니다. 사실 저는 이쪽 주제를 선정하는 것은 처음부터 약간 기피했습니다. 개인 EDA 프로젝트를 통해 완전히 새로운 분야에 진출하는 초석을 다질  욕심이 있었기 때문인데요. 프로젝트를 어느정도 완성해 가는 지금 생각해 보면 결국 제가 잘 아는 도메인을 선정한 것이 올바른 결정이었다는 생각이 듭니다. (물론 만약 특정 분야에 큰 관심이 있었더라면 그 분야를 연구하는 게 낫겠지만요.) 제가 잘 아는 분야인 만큼 연구의 방향성이 눈에 더 잘 보이기도 했고, 인사이트가 꼬리에 꼬리를 물고 확장되어감을 느낄 수 있었기 때문입니다. 

 

 

저의 개인 프로젝트 흐름은 다음과 같습니다.

  • 2022 개정 교육과정 분석, 관련 데이터 수집 분석, 관련 연구 논문 여러 개 분석, 직접 AL/ML 모델링 후 현황 연구 검증, 앞으로의 연구 방향성 제시

걱정까진 아니지만 그래도 아직 고민하고 있는 부분은, 다른 동기들의 수집 데이터가 대부분 구매 패턴 데이터, 후기 데이터 등 유저에 관련된 데이터들이더라고요. 저도 적당히 크롤링을 하긴 했지만 상대적으로 국가 문서와 연구 논문 분석이 메인을 이루기 때문에 이런 부분에서 데이터가 빈약해 보이지 않을까 하는 생각이 들었습니다. 또, 취업 측면에서 본다면 확실히 고객 관련 데이터를 분석하는 것이 기업의 매출 신장과 직결되기 때문에 더 어필될 것도 맞을 테고요. 주제에 필요한 데이터가 다르긴 하지만 그래도 약간의 조바심이 나는 것은 사실이기 때문에, 발표 전에 시간이 남으면 관련된 유저 데이터도 한번 검색해서 분석해보고자 합니다.

 

 

그래도 차별화 되는 점은 제 프로젝트에 인공지능 모델링을 직접 수행하고 연구 논문을 검증하는 과정이 포함되었다는 것인데요. 제가 알기론 다른 동기들 중에 딥러닝을 이번 개인 EDA 프로젝트 소재로 삼으신 분은 없는 걸로 알고 있습니다. 아직 아삭 과정에서 배우지 않은 파트이기도 하고요. 다만 저는 인공지능 엔지니어링 쪽에 관심이 많아 이전부터 독학을 해오고 있던지라 경험과 기본지식을 가지고 있는 상태였고, 강사님께서 딥러닝 쪽에 뜻이 있다면 이렇게 이렇게 해보자며 코칭도 해 주셔서, 큰 용기를 내어 프로젝트에 포함을 시키게 되었습니다. 그리고 아주 잘한 결정이라고 생각해요. 확실히 프로젝트가 빠른 성장에 도움이 됩니다. 주제에 관련된 국내외 논문을 이것저것 검색하고 직접 읽고 모델링을 해서 검증까지 해보게 되면서 크고 작은 산을 정말 많이 넘어야 했고, 남의 코드도 많이 들여다봐야 했거든요. 역시 사람은 고난과 역경을 극복하면서 발전하는 것 같습니다. 

 

 

아, 생각보다 해외 연구 논문 읽는 게 재밌더라고요. 외국인 친구들이랑 가깝게 잘 지내고 영어 원서도 한 달에 한 권씩 읽고 강의도 유데미 해외강의로만 들으면서 영어공부를 꾸준히 한 것이 큰 도움이 되나 봅니다. 연구 논문에 나오는 많은 용어들 전부 평소에 제가 공부하면서 쓰고 익히던 것들이라 읽고 이해하는 데 막힘이 없어서 뿌듯했습니다. 취업할 때 이런 점을 잘 어필해 봐야겠습니다. 다들 영어 공부 열심히 하시면 언제든 이렇게 도움이 된다는 점! 기억하시고 꾸준히 공부하시면 좋겠습니다.

 

 

개인 프로젝트가 끝나면 AWS CCP 자격증 시험이 곧바로 있는데요, 그 자격증 시험이 끝나면 저는 Kaggle 대회 참여를 시작해보려고 합니다. 케글 케글 말로만 들었지 실제로 써본 적은 아직 없었는데 이번 프로젝트 준비하면서 정말 케글에 지겹도록...(ㅋㅋㅋㅋㅋㅋ) 많이 접속해야 했거든요. 어떤 식으로 사이트가 구성되고 운영되고 내가 써먹을 수 있는지 적당히 잘 알았어요. 동기들 중에 딥러닝에 관심이 있는 분이 계시다면 스터디를 구성해 보는 것도 좋은 방법일 것 같습니다.

 

 

다음 회고는 개인 EDA 프로젝트가 끝난 다음 작성해 보도록 하겠습니다. 감사합니다 :)

 

 

 

 

 

한동안 코테를 잠시 안풀었더니

효율성 테스트를 통과하는데 조금 헤맸던 문제입니다..!

 

Stack 알고리즘을 활용하지 않고 문제를 풀었을 때 테스트 케이스 정확성은 통과하는 데 문제가 없으나, 효율성 테스트를 통과하기 어려우실 수 있습니다. 효율성 테스트까지 통과할 수 있는 문제 풀이 방법을 알려드릴게요 :)

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/12909

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

문제 상황 : 괄호가 바르게 짝지어졌다는 것은 '(' 문자로 열렸으면 반드시 짝지어서 ')' 문자로 닫혀야 한다는 뜻입니다. 예를 들어

"()()" 또는 "(())()" 는 올바른 괄호입니다.
")()(" 또는 "(()(" 는 올바르지 않은 괄호입니다.


'(' 또는 ')' 로만 이루어진 문자열 s가 주어졌을 때, 문자열 s가 올바른 괄호이면 true를 return 하고, 올바르지 않은 괄호이면 false를 return 하는 solution 함수를 완성해 주세요.


 

1.

먼저 빠른 탈출을 위해 다음 4가지 경우에는 함수를 False로 얼리 리턴하도록 설정해주겠습니다.

  • 주어진 문자열이 ")" 로 시작하는 경우
  • 주어진 문자열이 "(" 로 끝나는 경우
  • 주어진 문자열의 길이가 홀수인 경우
  • 주어진 문자열이 ")" 또는 "(" 한 종류로만 이루어진 경우
def solution(s):
    if s[0] == ")" or s[-1] == "(" or len(s) % 2 == 1 or len(set(s)) == 1:
        return False

 

 

 

2.

다음으로 stack 리스트를 선언해주도록 하겠습니다.

    stack = []

 

우리는 stack 리스트에 "(" 열기 괄호만 넣어줄 것입니다.

왜 열기 괄호만 넣어줄까요? 예시를 통해 한번 확인해 봅시다.

 

문자열 s = "(((())"가 있다고 합시다. 딱 봐도 열기 괄호는 4개인데 닫기 괄호는 2개여서 짝이 맞지 않는다는 것을 알 수 있습니다. 우리는 for루프를 통해 0번째 자릿값부터 차례대로 돌면서 해당 자리의 값이 "("이면 stack에 넣어주고, ")"이면 stack에 있는 열기괄호를 하나 빼 줄 것입니다.

    for char in s:
        if char == '(':
            stack.append(char)
        elif char == ')':
            if not stack:
                return False
            stack.pop()

s = "(((())"

  1. 첫 번째 자리 "(" -> stack = "(" 
  2. 두 번째 자리 "(" -> stack = "(", "("
  3. 세 번째 자리 "(" -> stack = "(", "(", "("
  4. 네 번째 자리 "(" -> stack = "(", "(", "(", "("
  5. 다섯 번째 자리 ")" -> stack이 비어있지 않으므로 stack.pop()
    -> stack = "(", "(", "("
  6. 여섯 번째 자리 ")" -> stack이 비어있지 않으므로 stack.pop()
    -> stack = "(", "("
    if stack:
        return False
    else:
        return True

 

이터레이션을 다 돌고 났을 때

stack이 비어 있으면 짝이 맞으므로 True

stack에 남은 값이 있다면 짝이 맞지 않는 것이므로 False를 리턴합니다.

 

결국 "("의 갯수와 ")"의 갯수가 동일한지 확인하는 것과 다름 없습니다. 다른 방법으로도 정확성 문제는 통과하는 데 어려움이 없으나, 효율성에 문제가 생기는 관계로 stack 알고리즘을 통해 효율성을 높여준 것이라고 보시면 되겠습니다.

 

 

3.

마지막으로 한 번에 코드를 보겠습니다.

def solution(s):
    if s[0] == ")" or s[-1] == "(" or len(s) % 2 == 1 or len(set(s)) == 1:
        return False

    stack = []
    for char in s:
        if char == '(':
            stack.append(char)
        elif char == ')':
            if not stack:
                return False
            stack.pop()
    
    if stack:
        return False
    else:
        return True

 

 

감사합니다!

 

 

지난 글에서 서울특별시 공중화장실 공중데이터를 판다스 데이터프레임으로 만들고 간단히 정제작업을 해 보았는데요. 정제한 데이터프레임을 가지고 태블로를 이용해서 아주 간단히만 시각화 작업을 진행해 보았습니다.

 

 

 

대시보드 구성 방법

  1. 서울시의 25개 구별 공중화장실 수 합계를 계산하여 그 수를 비교할 수 있도록 시각화했습니다.
  2. 대시보드의 왼쪽에는 지도를 배치하여 화장실의 수를 원의 크기와 색깔로 직관적으로 파악할 수 있도록 구성했습니다
    • 지도를 확대하면 보이지 않는 레이블을 모두 확인할 수 있어요.
    • 화장실 수가 많을수록 원의 크기가 큽니다.
    • 화장실 수가 많을수록 원의 색깔이 진합니다.
  3. 대시보드의 오른쪽에는 가로막대그래프를 배치하여 수치별로 좀더 직관적인 비교가 가능하도록 구성했습니다.
    • 오른쪽의 비교 파라미터를 이용해서 비교선을 100단위로 조절하면서 이동시킬 수 있어요.
    • 비교선을 움직이면서 비교선을 기준으로 색깔이 바뀌는 것을 확인할 수 있어요.

 

태블로 퍼블릭으로 보러가기 (클릭)

 

지하철

지하철

public.tableau.com

클릭하시면 태블로 퍼블릭 웹사이트에서 인터렉티브하게 직접 결과를 조절해 보실 수 있습니다.

 


 

 

<시각화 이후 생각해볼만한 것들>

 

1. 구별 화장실 수와 구별 면적의 관계는 어떻게 되는가?
2. 구별 화장실 수와 지하철 역의 개수의 상관관계가 있는가?
3. 구별 화장실 수와 구별 인구 수의 관계는 어떻게 되는가? 인구와 구별 화장실 총 수는 비례하는가?
4. 서울특별시 구별 장애인 화장실 데이터를 따로 구할 수 있는가? 구할 수 있다면, 전체 화장실과 장애인 화장실의 비율을 비교해 보자.
5. 상업 단지와 화장실 수의 상관관계가 있는가?

6. 관광 구역과 화장실 수의 상관관계가 있는가?

7. 공공화장실 중에서 지하철 역 화장실의 개수를 특정할 수 있는가? 있다면 그 비율은 어떻게 되는가?

8. 지하철 역 화장실 개수를 구할 수 있다면, 지하철 노선별 유동인구 데이터와 병합하여 화장실의 갯수가 적절하게 비치되어 있는지 비교해보자.

 

 

 

여기까지입니다 :-) 좀더 생각해볼 수 있을것 같지만 본격적인 개인 EDA 프로젝트 준비를 위해서 이번엔 이정도로 간단히만 포스팅을 마치려고 합니다. 쉽고 짧은 작업이었지만 맘에드는 공공데이터를 구하고, 정제하고, 시각화 후 생각해볼거리 도출까지 은근 시간이 걸렸네요. 다음엔 더 능숙하고 멋진 프로젝트를 가져와서 공유해보겠습니다. 감사합니다. :)

 

 


 

 

(+) 판다스 데이터프레임 CSV파일로 내보내기 한 후 태블로에 불러온 과정

 

t.to_csv("toilet_df.csv")

 

먼저 to_csv() 메소드를 통해 간단하게 csv파일로 내보내기를 해주었습니다. 저는 구글 코랩에서 실습을 진행했습니다.

 

 

 

 

 

구글 코랩의 왼쪽 파일메뉴에서 간단하게 바로 다운로드를 해서 다운로드 폴더에 넣어 주었는데요. 로컬 환경에 따로 다운로드하지않고 태블로 퍼블릭 환경에서 구글 드라이브를 연동해서 바로 오픈해도 됩니다.

 

 

 

 

 

태블로 퍼블릭 프로그램을 실행하고 로컬 환경에 다운로드한 toilet_df.csv 파일을 오픈했습니다.

 

 

 

따로 사용하지 않을 예정인 산지, 부지번 컬럼은 숨기기(Hide) 해주었습니다. 이후 시트 2개에서 작업을 하고 대시보드 1개에서 두개를 합쳐주는 방식으로 간단히 끝을 내주었습니다.

 

 

 

 
서울시 공중화장실 공공데이터를 가지고 아주 간단한 데이터 시각화, 분석 실습을 해 보려고 합니다.
먼저 이번 포스팅에는 파이썬 pandas 라이브러리를 이용해서 데이터 전처리 작업한 것을 간단히 정리해 보았습니다. 
데이터 시각화, 분석은 태블로 프로그램을 이용하여 마친 뒤 다음 포스팅에 이어서 올리도록 하겠습니다.
 
 
https://data.seoul.go.kr/dataList/OA-1370/S/1/datasetView.do

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

 
사용한 데이터 링크입니다.
 


 

1. pandas : 필요없는 컬럼 삭제, 인덱스 지정

import pandas as pd

t = pd.read_csv("toilet.csv", engine='python', encoding = "cp949")
t

 
pd.read_csv를 이용해서 데이터를 불러오는데 한글 깨짐이 좀 있어서 encoding = "cp949"를 이용해주니 깔끔하게 불러오기가 잘 되었습니다.

 
먼저 value_counst()를 이용해서 대략적으로 확인해보니 별 다른 정보가 담겨있지 않은 관계로 새주소명, 생성일 컬럼은 삭제하기로 결정했습니다. 또, 고유번호에 중복값이 없는것을 확인한 관계로 고유번호를 인덱스로 지정해주겠습니다.

# 새주소명, 생성일 컬럼 드랍(삭제)
t = t.drop('새주소명', axis=1)
t.drop('생성일', axis = 1, inplace = True)

# 고유번호 컬럼 중복값 없는지 확인
len(t['고유번호'].unique()) == len(t['고유번호'])  # True

# 고유번호 인덱스화
t.set_index('고유번호', inplace = True)

 
필요없는 컬럼을 삭제하고 인덱스를 고유번호로 바꾸어 주니 어느정도 보는 게 깔끔해졌습니다.
이제 구명과 법정동명을 확인해보려고 하는데요.
 
 

2. 구명

t['구명'].value_counts()

 
value_counts()를 이용해서 확인해 보니

  1. 끝에 '구'가 붙어 있지 않은 구
  2. 오타 작렬한 구
  3. 빌딩이 왜 여기서 나와? 갈암구는 또 어디야? 갈현송방차풀소는 뭐야?

이것들을 해결해줘야 할 것 같습니다.
먼저 구가 안붙어있는 것들에 구를 붙여줘 보기로 했습니다. (예:노원 > 노원구)

t['구명'] = t['구명'].apply(lambda x: x + '구' 
                          if x in ['동작', '금천', '강서', '양천', '노원', '관악', '영등포', '서대문'] 
                          else x)

 
'구명'이 동작/금천/강서/양천/노원/관악/영등포/서대문 중 하나인 경우
컬럼에 apply와 lambda 함수를 이용해서 끝에 '구'를 붙여 줬습니다. 해당사항이 없는 경우는 그냥 놔두도록 처리했습니다.

t['구명'].value_counts()


그 외 갈현송방차풀소~남서울빌딩에 해당하는 row들은 그냥 제거하겠습니다.

# 이것들에 해당하는 '구명'을 가진 row들을 제외한(~) 줄만 남겨서 t에 재할당
t = t[~t['구명'].isin(['갈현송방차풀소', '송북구', '송파ㅜ', '영등로구', 
                      '영등표구', '송파두성빌딩', '갈암구', '구로수', '남서울빌딩'])]
len(t['구명'].value_counts().index)
# 25

 
혹시 몰라 확인해 보니 총 25개의 구가 있는것이 잘 확인되었습니다. 검색해보니 서울에는 25개 자치구와 426개 행정동이 있다고 하네요! 서울 살면서도 계속 까먹어요... 상식으로 외워둬야지.
 
법정동은 426개를 일일이 확인하기 불가능 + 의미가 없는 것 같아서 일단 놔두도록 하겠습니다. 다음 포스팅에는 태블로를 이용하여 간단한 시각화를 해서 가져오도록 하겠습니다!
 

 

 

 
 
벌써 4주 차 회고를 작성합니다. 4주차도 매일 1시간 30분 일찍 도착해서 아침공부를 하고, 9-6 수업을 듣고, 6-9 저녁공부를 하고 집에 갔습니다. 뿌듯합니다.
 
 
분명 첫주에 패딩을 입고 다녔었는데... 이 회고를 작성하고 있는 오늘(일요일)의 낮 최고 기온이 28도에 육박했습니다. 그래서 반팔 반바지를 입고 나가서 카공을 하고 왔어요. 후텁지근했지만 아직 습하지는 않아서 따뜻하고 공기가 바삭한 게 저는 참 좋았습니다. 매일 하는 게 공부밖에 없는 단조로운 일상을 보내다 보니, 이런 날씨 변화가 주변을 좀 더 돌아보고 한숨 돌리게 하는 것이 반가웠습니다.
 
 
이번 주에 처음으로 이력서라는 걸 작성했습니다. 임용고시에 합격한 교사는 교육청에서 알아서 발령을 내 주기 때문에 이력서나 자기소개서 같은 걸 작성할 필요가 없습니다. 그래서 경험이 전무했어요. 다행히 지난번에 들었던 이력서와 자기소개서 작성 특강 내용이 큰 도움이 되었습니다. 특강 오신 강사님께서 공유해주신 기본 템플릿을 참고해서 기본 이력서를 국문과 영문으로 하나씩 작성하고, 혹시 몰라 노션 페이지도 간단하게 제작을 했습니다. 생각보다 오랜 시간과 많은 노력을 들여야 했습니다.
 
 
항상 학생들이 쓴 글을 평가하고 학부모들이 제출한 신청서를 심사하고 분류하는 일을 하던 제가 이제는 저좀 뽑아주십사 서류를 써서 제출하는 입장이 된 것이 재밌습니다. 사람 일이 어떻게 될지 모른다는 걸 매일 체감합니다. 과연 내가 인사 담당자라면 나의 이력서를 보고 마음이 동할까? 하는 한편의 메타인지를 발동해 가며 최대한 나를 잘 표현하고자 노력했어요. 힘들었지만 재밌기도 했습니다. 저의 성장 과정에 대해 돌아보고 스스로의 강점과 단점에 대해 숙고해 보는 경험을 통해 앞으로 원하는 기업에 지원하기까지 무엇을 더 보충하고 발전시켜야 할 것인지 플랜을 세울 수 있었습니다.
 
 
아무래도 아직 완성한 프로젝트가 하나도 없다 보니 프로젝트 칸은 비워뒀어요. 생각은 하고 있었지만, 텅 빈 프로젝트란을 보니 제대로 된 프로젝트 뭐든 하나라도 있어야한다는 생각이 확 들더라고요. 지금까지 저는 데이터분석과 머신러닝에 필요한 각종 프레임워크에 익숙해지고 기술적인 능력을 키우는 데 집중을 하고 있었거든요. 공부는 계속하되 가능한 빨리 개인 프로젝트를 구체화기로 마음먹었습니다. 다만 프로젝트 주제를 정하는 일이 생각보다 어려운 게 걱정입니다. 동기들과 이야기해 보면 다들 관심 있는 분야 하나정도 가지고 있더라고요. 금융이면 금융, 게임이면 게임, 유통이면 유통, 그런 거 말이죠. 저는 딱히 하나의 분야에 꽂혀 있지는 않고, 데이터 사이언티스트로서 머신러닝과 딥러닝을 아우를 수 있는 멀티플레이어로 성장하고 싶다.. 뭐 그런 거시적인 목표만 있는 상태입니다. 그래서 주제 구체화가 더욱 막막한데요. 앉아서 머릿속으로 고민만 하면 답이 나오지 않는다는 걸 인생 경험으로 알고 있기 때문에 먼저 데이터 수집부터 이것저것 시작해보려고 합니다.
 
 
이번주에 수업에서 웹 크롤링 기법 여러가지를 배웠는데, 크롤링을 해본 적이 없던 저에게 정말 큰 도움이 되었습니다. 소위 노가다성 작업이었음에도 불구하고 생각보다 훨씬 더 재밌었어요. 어떤 식으로 블로그에 정리를 할까 하다가, 수업 내용을 그대로 정리해서 올리면 편하긴 하겠지만 남는 게 없을 것 같아서, 간단한 토이 플젝을 진행해 봤습니다. 연금복권 720+ 당첨 결과를 크롤링 후 정제해서 데이터프레임화하고 간단한 분석을 해보는 실습을 하고 과정을 정리해 포스팅했어요. 정말 재밌었고 복습이 쏙쏙 됐습니다. 역시 스스로 생각하고 작업한 내용을 글로 출력하면 배운 내용을 체화하고 내 것으로 만드는 데 정말 큰 도움이 되네요. 다음 주에는 셀레니움 프레임워크 사용하는 법을 배우는데 큰 기대가 됩니다.
 
 
다음 주 회고에서는 주제를 좀더 구체화해서 가지고올 수 있기를 기대하며, 다들 일교차와 냉방병 조심하시고 건강하세요.
 
감사합니다.
 


 
파이썬 requests, BeautifulSoup 라이브러리를 이용한 웹크롤링 후 데이터분석 실습을 해보았습니다 :-) 
 
연금복권720+은 제가 한달에 2-3회정도 꾸준하게 구매하는 최애 복권인데요. 슬프게도 지금까지 제대로 당첨된 적은 단 한번도 없지만, 앞으로도 저는 꾸준히 구매를 할 예정인 아주아주 매력적인 복권입니다. 1등에 당첨이 되면 (세전) 700만원을 매월 20년동안 수령할 수 있어요. 동행복권 온라인 사이트에서 간단히 온라인 구매를 할 수도 있구요. 1등 번호는 온라인 1명, 오프라인 1명 총 2명이 당첨될 수 있습니다. 자세한 복권 구조는 동행복권 홈페이지를 참고해 보시구요.
 
복권의 경우 통계를 공부해보신 분들께는 아주 친숙한 소재이실텐데요. (저는 아닙니다.ㅋㅋㅋ) 동행복권 사이트에서는 복권 당첨번호를 엑셀파일로도 제공하고 통계 자료를 따로 분석해서 메뉴도로 제공하고 있습니다. 다만 저는 철저히 requests 라이브러리를 이용한 웹크롤링에 익숙해지기 위해서 엑셀 파일이나 통계자료를 건드리지 않고 처음부터 끝까지 혼자 힘으로 본 실습을 했습니다! 
 

 
[참고] 본 포스팅은 수리링 본인의 공부 기록을 목적으로 작성하였습니다. 해당 라이브러리에 대해 전혀 모르시는 분께서 보면서 따라하시기엔 많이 불친절하게 느껴질 수 있습니다. 참고하시고 봐 주시면 감사드리겠습니다 :-)
 
[참고] 본 포스팅은 책, 강의, 다른 사람의 포스팅을 참고하지 않은 스스로의 창작물입니다! 참고하여 포스팅 하시는 경우 출처 밝혀주심 감사드리겠습니다!
 


 
[실습 목차]

  1. 206회차로 모의 실습
  2. 원하는 회차 구간을 입력하면 모든 정보를 담아 데이터프레임으로 리턴하는 함수 작성
  3. 데이터프레임으로 간단한 데이터분석 (은근 재밌으니 귀찮으시면 이것만 보고 가세요...^^)

1-1. 숨은 URL 찾아내기

 
동행복권 사이트의 회차별 당첨번호 페이지(클릭)에 가 봅니다.
 

 
 
회차 바로가기 메뉴를 통해 원하는 회차를 선택해서 당첨 결과를 볼 수 있었습니다.
 

 
 
그런데 기본 URL에 회차 정보가 드러나지 않고 숨어 있어요. 206회를 조회해도, 200회를 조회해도 계속 같은 URL이 유지됩니다. 따라서 회차를 특정하여 정보를 뽑아낼 수가 없는 상황입니다. 우리는 회차를 조회할 수 있는 상세URL을 알아내야 해요.
 
문제상황을 해결하기 위해 크롬 웹브라우저의 inspection(개발자 도구) 메뉴의 Network 탭을 확인해 봅시다.
 

 
 
위와 같이 네트워크 탭을 켜둔 상태로 조회 버튼을 눌러봅니다. Name 탭의 맨 첫 번째 gameResult 어쩌구를 클릭한 다음 Payload를 확인합니다. (누가 봐도 수상한) Round: 206 이라는 정보를 확인했습니다. 기존 url 뒤에 &Round=206을 붙여 주면 될 것 같다는 합리적 의심을 해봅니다.
 

https://dhlottery.co.kr/gameResult.do?method=win720&amp;amp;Round=205

 
 
주소 뒤에 &Round=205 를 붙여넣고 엔터를 치니 205회 당첨결과 페이지로 잘 이동합니다 ㅎㅎ 찾았다 요놈! 이제 상세 url주소를 찾았으니 코드를 작성하면서 원하는 데이터를 뽑아내 보겠습니다.
 


 

1-2. requests, BeautifulSoup 라이브러리

* 본 실습에서 해당 라이브러리에 대한 상세 설명은 생략합니다

import requests
from bs4 import BeautifulSoup as BS

 
먼저 requests와 BeautifulSoup 라이브러리를 임포트해줍니다.

url = "https://dhlottery.co.kr/gameResult.do?method=win720&Round=206"
res = requests.get(url)
soup = BS(res.text, "html.parser")

 
우리가 찾아낸 url을 선언해 준 다음 차례대로 라이브러리에 넣어서 html 자료를 뽑아냅니다.

 
soup을 실행해 보니 html 정보가 잘 들어왔습니다 :)
저는 html 코드를 하나하나 뜯어보면서 원하는 정보를 뽑아내 봤어요.

nums = rows[0].find_all("span", {"class":"num"})

#조, 당첨번호
group = int(nums[0].find("span").text)
n_1 = int(nums[1].find("span").text)
n_2 = int(nums[2].find("span").text)
n_3 = int(nums[3].find("span").text)
n_4 = int(nums[4].find("span").text)
n_5 = int(nums[5].find("span").text)
n_6 = int(nums[6].find("span").text)

print(f"{group}조 {n_1}, {n_2}, {n_3}, {n_4}, {n_5}, {n_6}")
# '3조 4, 8, 9, 0, 7, 5'

 
먼저 제일 중요한 1등 조, 6개의 당첨번호를 뽑아봤습니다. 

# 등위(등수명) 
# rows[0]이므로 첫번째 1등을 구함 -> 나중에 인덱스를 바꾸어 다른 등수의 이름도 구할 수 있음
rank = rows[0].find_all("td")[0].text
rank
# '1등'

 
등수명도 뽑아봤어요. 이정도는 그냥 작성해도 되지만 연습삼아서 뽑아봤습니다 :)

# 당첨결과(매)
rank_counts = int(rows[0].find_all("td", {"class":"ta_right"})[-1].text.strip())
rank_counts
# 2

 
1등의 당첨 매수를 뽑아봤습니다. 206회차는 1등이 2명입니다. 연금복권 1등은 온라인/오프라인 각1명씩 최대 2명이 나올 수 있습니다. 가끔 1등이 1명밖에 없을 때도 많아요. 아주 드물게 0명일 때도 있는 거 같아요.

# 보너스 당첨번호 6자리
bonus_nums = []
for i in range(6):
    bonus_num = rows[7].find_all("span", {"class" : "num"})[i].find("span").text
    bonus_num = int(bonus_num)
    bonus_nums.append(bonus_num)

print(bonus_nums)
# [5, 8, 7, 6, 9, 5]

 
보너스 당첨번호 6자리도 뽑아봤습니다.

# 보너스 당첨결과(매)
bonus_counts = int(rows[7].find_all("td", {"class":"ta_right"})[-1].text.strip())
bonus_counts
# 10

 
10명이나 당첨됐네요.
 


 

2-1. 회차를 입력할 수 있는 함수로 작성해보기

206회차를 가지고 적당히 연습을 해 봤으니, 원하는 회차를 입력하면 하나씩 모두 조회해서 딕셔너리로 담아 리턴하는 함수를 작성해 보았습니다.

def win720(round):
	# 입력받은 회차 번호로 url을 만들고 정보를 받아냅니다.
    url = f"https://dhlottery.co.kr/gameResult.do?method=win720&Round={round}"
    res = requests.get(url)
    soup = BS(res.text, "html.parser")
    rows = soup.find("tbody").find_all("tr")
	
    # data_dict에 앞으로 하나씩 정보를 추가할 겁니다. 먼저 라운드 값을 첫 번째로 넣어줬습니다.
    data_dict = {"round":round}
    nums = rows[0].find_all("span", {"class":"num"})

    # 1등 조, 당첨번호
    group = int(nums[0].find("span").text)
    n_1 = int(nums[1].find("span").text)
    n_2 = int(nums[2].find("span").text)
    n_3 = int(nums[3].find("span").text)
    n_4 = int(nums[4].find("span").text)
    n_5 = int(nums[5].find("span").text)
    n_6 = int(nums[6].find("span").text)

    data_dict["group"] = group
    data_dict["n_1"] = n_1
    data_dict["n_2"] = n_2
    data_dict["n_3"] = n_3
    data_dict["n_4"] = n_4
    data_dict["n_5"] = n_5
    data_dict["n_6"] = n_6

    # 1-7등 당첨자수
    for i in range(7):
        rank_counts = rows[i].find_all("td", {"class":"ta_right"})[-1].text.strip()
        rank_counts = re.sub(",","", rank_counts)
        rank_counts = int(rank_counts)
        column_name = f"rank{i+1}"
        data_dict[column_name] = rank_counts

    # 보너스 당첨번호 6개
    for i in range(6):
        bonus_num = rows[7].find_all("span", {"class" : "num"})[i].find("span").text
        column_name = f"bonus_{int(i)+1}"
        data_dict[column_name] = bonus_num

    # 보너스 당첨자수
    bonus_counts = int(rows[7].find_all("td", {"class":"ta_right"})[-1].text.strip())
    data_dict["bonus"] = bonus_counts

    return data_dict

 
더럽게 길지만 그래도 잘 작동했습다^_^;;;; 
너무 길어져서 쓰면서 불길했는데 그래도 오류 수정 2-3번만에 원하는 대로 값이 나와서 다행이였어요

 
205회차로 테스트를 해 봤는데요. 조, 1등 넘버 6자리, 등수별 당첨매수, 보너스 번호 6자리, 보너스 당첨매수가 딕셔너리로 제대로 들어온 것을 확인했습니다 :) 이게 뭐라고 너무 재밌었어요 (ㅋㅋㅋㅋ)
 


 

2-2. 회차 구간을 설정하고 데이터프레임을 리턴하는 함수 작성하기

 
위에서 작성한 win720()함수를 가지고 원하는 회차 구간의 모든 정보를 담은 데이터프레임을 반환하는 함수를 작성해 주었습니다.

def lucky_chart(start, end):
    lucky_results = []

    for i in range(start, end+1):
        win = win720(i)
        values = list(win.values())
        print(values)
        print(len(values))
        lucky_results.append(values)

    columns = list(win.keys())
    print(columns)
    print(len(columns))
    
    import pandas as pd
    df = pd.DataFrame(lucky_results, columns = columns)
    
    return df.set_index("round")

 
중간 중간에 있는 print 함수들은 제가 함수를 작성하면서 중간 과정을 시각화하기 위해 굳이 넣어줬구요, 깔끔하게 없애줘도 됩니다.

history = lucky_chart(190, 206)

 
190회부터 206회차까지 럭키차트 함수를 돌려보았습니다.

 
요런식으로 진행상황을 시각화 하기 위해 print 함수를 넣어줬습니다. (중간에 오류가 있었어서 저런식으로 시각화 하면서 수정해줬어요!)
 

history

 
알흠다운 판다스 데이터프레임이 완성되었어요 ❤️
 


 

3. 간단 데이터분석

마지막으로 데이터분석은 1회차부터 206회차까지로 구간을 늘려서 진행했습니다! (1등은 제껍니다.)
 

  • 1등 조 비율이 어떻게 될까?
# 조별 value_counts() 구하기
group_counts = history['group'].value_counts()

# matplotlib 임포트
import matplotlib.pyplot as plt

# 차트 작성
plt.pie(group_counts,
        labels = group_counts.index,
        shadow = True,
        autopct = '%1.2f%%')
plt.title("Rank 1 groups ratio")
plt.legend()
plt.show()

 
아주.. 흥미로운.. 결과입니댜... 연금복권720+ 1회차부터 206회차까지 모든 데이터들을 살펴본 결과... 지금까지 가장 많은 1등을 배출한 조는 4조였습니다. 4조 > 1조 > 3조 > 5조 > 2조 순이네요. 
 
연금복권을 아는 분들께서는 이해를 하실텐데, 저는 혹시라도 번호 6개를 다 맞췄지만 조가 다를 때 2등이라도 당첨되도록 + 혹시라도 1등이 되면 2등도 동시 당첨되도록(ㅋㅋㅋ) 번호 6개를 고르고 나면 모든 조(1~5조)로 총 5줄(5,000원)을 구매해버립니다. 솔직히 이게 더 확률이 낮을 것 같기는 한데.......... 만약 특정 조를 골라서 구매해야 한다면 앞으로 저는 1조 또는 4조를 고르겠습니댜.
 

  • 지금까지 역대 2등은 몇명이 나왔을까?
import seaborn as sns

sns.set_style("darkgrid")
sns.set_palette("bright")

sns.barplot(history["rank2"].value_counts())
history['rank2'].agg(['min', 'max', 'mean'])
# min     0.0
# max     8.0
# mean    4.5

 
1등에 가려 2등의 당첨자 수는 사실 잘 확인해 본 적이 없는데요. 2등에 당첨되면 매달 100만원씩 10년간 수령할 수 있거든요. 2등이라도 당첨시켜 주신면 제가 굉장히 감사할텐데요. 몇 명이나 당첨되나 보니, 역대 2등 당첨자 수는 최대 8명, 최소 0명(ㅋㅋㅋㅋㅋㅋ), 평균 4.5명이 나왔다고 합니다. 그래프로 확인해 보니 2등이 한 명도 나오지 않은 회차가 20번이 넘네요? 실화냐?
 

  • 그럼 1등이 한 번도 안 나온 회차도 있을까?
history['rank1'].value_counts()
# 1    101
# 2     63
# 0     42
sns.countplot(data = history,
              x = 'rank1',
              legend = 'full')

아니 미친... 1등이 0명인 회차가 40회가 넘는다고? 1등이 2명 다 나온 적보다 1명밖에 안 나온 적이 더 많다고? 여러분 빨리 연금복권 사세요! 저거 다 우리돈이라고(흥분)
 

  • 1번부터 6번까지 각 자리마다 번호가 몇번씩 나왔을까
df = pd.DataFrame()

for i in range(1, 7):
    col = f"n_{i}"
    df[col] = history[col].value_counts().sort_index()

df
for i in range(1, 7):
    col = f"n_{i}"
    print(f"{i}번째 자리에서 가장 많이 나온 숫자는 {df[col].idxmax()}")

 
각 자리에서 가장 많이 나온 숫자는 순서대로 4 - 4 - 9 - 0 - 5 - 6 이었습니다. 이게 엄청 큰 의미가 있을지는 모르겠지만, 해당 자리에 어떤 숫자를 고를지 고민되신다면 이 정보도 참고해 봐도 좋을 것 같습니다.

for i in range(1, 7):
    col = f"n_{i}"
    print(f"{i}번째 자리에서 가장 조금 나온 숫자는 {df[col].idxmin()}")

 
반대로 각 자리별로 가장 조금 나온 숫자도 구해봤어요. 두 정보를 종합하면, 숫자 0 4 7 9가 자주 보이네요. 반대로 0 4 7 9를 제외한 1 2 3 5 6 8 을 고르는 것도 안전하게 갈 수 있는(?) 방법일 수도 있을 것 같고.. 복권의 세계는 정말 어렵네요.
 
tmi지만 저는 항상 첫자리를 6으로 구매를 하는데, 첫자리 6은 꼴찌 0에 이어서 두 번째로 나온 횟수가 적네요. 전략을 바꿔야 하나.... 고민이 되지만 (ㅋㅋㅋㅋㅋㅋㅋㅋㅋ) 복권 통계는 재미일 뿐이라고 생각합니다.
 
 
 
 
여기까지 간단 분석을 마쳐 보겠습니다! :-)
감사합니다.
 

 
 

 

https://school.programmers.co.kr/learn/courses/30/lessons/12945

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

오랜만에 피보나치 수열 문제 풀이를 했습니다. 처음에 간단한 재귀함수를 적용해서 풀이를 했더니 테스트는 모두 통과하는데 효율성 테스트에서 시간 초과로 계속 실패를 하는 문제가 발생했습니다. 그래서 효율성을 올리기 위해 메모화를 적용해서 풀이를 했더니 효율성 테스트도 모두 통과할 수가 있었습니다 :-)

 

간단하게 풀이방법을 설명해 보겠습니다!

 

 

피보나치 수열

 


1. 메모화 없이 기본 피보나치 수열 함수 작성하기

def basic_fibonacci(n):
    if n in [1, 2]:
        return 1
    if n == 3:
        return 2

    return basic_fibonacci(n-2) + basic_fibonacci(n-1)

 

만약 주어진 정수가 1 또는 2라면 바로 1을 리턴,

주어진 정수가 3이라면 바로 2를 리턴하도록 초기 설정을 해 줍니다.

4 이상의 정수 n이 주어졌을 때 재귀함수 형식으로 n-2와 n-1에 basic_fibonacci 함수를 적용한 값을 구해 더한 값을 리턴합니다.

basic_fibonacci(8), basic_fibonacci(9), basic_fibonacci(10), basic_fibonacci(11)
# (21, 34, 55, 89)

 

위 함수는 제대로 작동하지만, n의 값이 커질수록 효율성이 급격하게 떨어진다는 단점이 있어요. 메모화를 이용해서 함수의 효율을 높여보겠습니다.


2. 메모화를 적용한 피보나치 수열 함수 작성하기

def fibonacci(n, memo = {1:1, 2:1, 3:2, 4:3, 5:5, 6:8}):
    if n in [2, 3, 4, 5, 6]:
        return memo[n]
    
    if n > 6:
        for i in range(7, n+1):
            if i in memo: pass
            else:
                memo[i] = memo[i-2] + memo[i-1]
    
    return memo[n]

 

하나씩 뜯어보겠습니다 :)

def fibonacci(n, memo = {1:1, 2:1, 3:2, 4:3, 5:5, 6:8}):

 

가장 먼저 함수를 선언할 때 파라미터 값으로 memo라는 이름으로 딕셔너리를 포함해 주었습니다. 저는 간단하게 n이 1부터 6번째일때 피보나치 수열 값을 딕셔너리에 초기값으로 미리 메모를 해 두었습니다.

def fibonacci(n, memo = {1:1, 2:1, 3:2, 4:3, 5:5, 6:8}):
    if n in [2, 3, 4, 5, 6]:
        return memo[n]

 

내가 구하고자 하는 것은 피보나치 수열의 n번째 값입니다. 만약 n이 2, 3, 4, 5, 6인 경우 (문제 조건에서 n은 2 이상의 정수라고 했으므로 1은 제외합니다) memo 딕셔너리의 해당하는 value 값을 바로 리턴하고 함수를 종료하게 됩니다.

    if n > 6:
        for i in range(7, n+1):
            if i in memo: pass
            else:
                memo[i] = memo[i-2] + memo[i-1]
    
    return memo[n]

 

만약 n이 6보다 크면 어떻게 해야 할까요?

 

주어진 n의 값이 50일 때를 예시로 생각해 봅시다. 우리는 50번째 값을 구하기 위해 48, 49번째 값이 필요합니다.

49번째 값을 구하기 위해서는 47, 48번째가 필요하고

48번째 값을 구하기 위해서는 46, 47번째가 필요합니다.

결국 50번째 값을 구하기 위해서는 1부터 49번째까지 피보나치 수열 값을 모두 알아야 합니다.

 

그럼 메모화는 어떻게 작동할까요?

제가 만약 50번째 피보나치 수열 값을 구하기 전에,

40번째 피보나치 수열 값을 먼저 구했다고 생각해 보겠습니다.

fibonacci(40)
# 102334155

 

우리는 40번째 피보나치 수열 값을 구하기 위해 지금 1번째~39번째 피보나치 수열의 값을 열심히 구했습니다.

이제 다음으로 50번째 피보나치 수열 값을 구하려고 합니다.

 

 

먼저 메모화를 해놓지 않은 경우를 생각해 봅시다.

우리는 이미 1번부터 40번째까지의 값을 이전에 미리 구한 전적이 있지만

따로 기록을 해 두지 않았기 때문에 같은 계산을 또 반복해야 합니다.

1번째~49번째 피보나치 수열 값을 열심히 또 구하는 거죠.

 

하지만 제가 기록을(메모화를) 해 두었다면 얘기가 달라집니다.

즉 처음에 fibonacci(40)을 계산했을 때

memo 딕셔너리에 1번부터 40번까지 피보나치 수열 값을

key(n) : value(값) 형식으로 미리 저장을 해 두었다면,

우리는 fibonacci(50)을 구할 때 

1. 1번부터 40번까지는 간단히 딕셔너리에서 값을 찾아 가져올 수 있습니다.

2. 41번부터 49번째 값은 새로 구하고 딕셔너리에 값을 추가해주는 방식으로 계속해서 메모를 해나갈 수 있습니다.

 

+ 참고로 함수를 여러번 작동하면서 메모장에 추가하는 key와 value 값은 매번 초기화되지 않고 계속해서 누적 기록을 보관합니다.

 

def fibonacci(n, memo = {1:1, 2:1, 3:2, 4:3, 5:5, 6:8}):
    # 주어진 정수가 2-6중 하나일 경우 바로 메모장에서 수열값을 찾아 리턴, 함수종료
    if n in [2, 3, 4, 5, 6]:
        return memo[n]
    
    # 주어진 정수가 6보다 큰 경우(7 이상)
    if n > 6:
    	# 7부터 n까지의 정수 i에 대해서
        for i in range(7, n+1):
            # 만약 메모장에 i값이 이미 메모되어 있다면 그냥 넘어가고
            if i in memo: 
                pass
            # 만약 메모장에 i값이 없다면 피보나치 수를 구해서 추가해주세요
            else:
                memo[i] = memo[i-2] + memo[i-1]
    
    # 메모장에서 n번째 피보나치 수열 값을 찾아 리턴해주세요
    return memo[n]

 

최종 피보나치 함수입니다 :)

내가 구하고자 하는 n의 피보나치 값을 찾기 위해 7부터 n-1번째의 수열값을 계속해서 차례대로 메모장에 누적 메모해나가는 방식입니다. 재귀함수랑 약간 비슷한듯 다른 느낌이네요!


3. 최종 문제풀이

마지막으로 문제 해결을 위해 피보나치 수열을 활용해서 soluton()함수를 작성했습니다. 저는 코딩테스트 문제를 풀 때 이렇게 함수를 나누어 작성하는것을 좋아합니다. 함수 안에 함수 작성하는거 시러요.....(개취)

def fibonacci(n, memo = {1:1, 2:1, 3:2, 4:3, 5:5, 6:8}):
    if n in [2, 3, 4, 5, 6]:
        return memo[n]
    
    if n > 6:
        for i in range(7, n+1):
            if i in memo: 
                pass
            else:
                memo[i] = memo[i-2] + memo[i-1]
    
    return memo[n]

def solution(n):
    fibo_num = fibonacci(n)
    
    # fibonacci(30)< 1234567 < fibonacci(31)
    # 만약 n이 30 이하라면 바로 fibo_num을 리턴하고(몫이 0이므로 나머지와 동일)
    if n <= 30 : 
        return fibo_num
    # 만약 n이 30보다 크다면 fibo_num을 1234567으로 나눈 나머지를 리턴
    else :
        return fibo_num % 1234567

 

정확성: 100.0
합계: 100.0 / 100.0

 

 

 

 

풀이나 설명에 오류가 있다면 댓글로 알려주세요!

감사합니다! :-)

 

 

FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead. with pd.option_context('mode.use_inf_as_na', True): 

 

주피터 노트북이나 코랩에서 pandas, seaborn, matplotlib 등 라이브러리를 사용할 때 위와 같이 퓨처 워닝 어쩌구 하면서 경고 메세지가 나타나서 꼴보기 싫은 경우가 있습니다.

 

이럴 때 warning 라이브러리를 임포트하는 방식으로 간단하게 해결이 가능합니다.

 

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

 

before

 

after

 

 

 

 

 

코딩테스트 문제를 해결할 때면 2개 이상의 정수의 최대공약수 또는 최소공배수를 구해야 하는 경우가 종종 있습니다. 파이썬에서 최대공약수와 최소공배수는 넘파이 라이브러리를 이용해서 아주 간단하게 구해낼 수 있는데요. 

 

먼저 함수를 소개해 드리기 전에, 최대공약수와 최소공배수를 영어로 뭐라고 부르는지 알고 넘어갈게요.

  • gcd : greatest common division (최대공약수)
  • lcm : lowest common multiple (최소공배수)

이런 간단한 영어 정도는 숙지해 두시면 좀더 직관적으로 쉽게 프로그래밍하실 수 있어요 :-)

 

[1] 최대공약수 구하기

import numpy as np

 

먼저 넘파이 라이브러리를 임포트해줍니다.

np.gcd(12, 20)
# 4

np.gcd(30, 45)
# 15

 

np.gcd() 함수 안에 두 정수를 넣으면 두 정수의 최대공약수를 리턴합니다. gcd인 이유는 위에서 설명한 대로 greatest common divison의 약자 gcd가 최대공약수를 의미하기 때문입니다.

 

만약 3개 이상의 정수의 최대공약수를 구하고 싶다면 어떻게 할 수 있을까요?

np.gcd.reduce([15, 25, 35])
# 5

np.gcd.reduce([15, 27, 18])
# 3

before = np.arange(0, 20, 5)
after = np.gcd.reduce(before)
after
# 5

 

np.gcd.reduce() 함수 안에 리스트 또는 넘파이 어레이를 넣어주면 리스트 또는 어레이 안의 모든 정수의 최대 공약수를 리턴합니다.

 

 

[2] 최소공배수 구하기

np.lcm(10, 14)
# 70

np.lcm.reduce([10, 15, 20])
# 60

before = np.array([3, 7, 10])
after = np.lcm.reduce(before)
after
# 210

 

np.lcm() 함수 안에 두 정수를 넣으면 두 정수의 최소공배수를 리턴합니다. lcm인 이유는 위에서 설명한 대로 lowest common multiple의 약자 lcm이 최소공배수를 의미하기 때문입니다.

 

마찬가지로 np.lcm.reduce()  함수 안에 리스트 또는 넘파이 어레이를 넣어서 3개 이상의 정수의 최소공배수도 구해낼 수 있습니다.

 

 


 

 

알고 있으면 아주아주 유용한 파이썬 넘파이 모듈 이용해서 최소공배수/최대공약수 구하는 방법이었습니다.

 

감사합니다 :-)

+ Recent posts