2021년 6월 8일 화요일

[ORM : SQLALCHEMY] N+1 Query Problem

N + 1 이슈란?
쿼리 한번으로 n건의 데이터를 가져와서 원하는 데이터를 얻기 위해 n건의 데이터를 데이터 수 만큼 반복해서 쿼리를 수행하는 문제이다.

ORM에서 가장 흔하게 발생하는 성능 이슈로 언급되며 성능에 큰 영향을 끼치기 때문에 주의 해야한다. 

예제)
python - sqlalchemy
오더 테이블 - 오더 번호, 유저 번호, 주문 상품, ...
유저 테이블 - 유저 번호, 전화번호, 주소, ...

모든 오더와 해당 오더를 주분한 유저의 전화번호를 알고 싶을때

order = db.session.query(Order).all()
for row in order:
    user_number = db.session.query(User.phone).filter(User.id == row.user_id)
    ...

또는

data = db.session.query(Order, User).join(User, Order.user_id == User.id).all()
...

등의 방법으로 데이터를 가져올 것이다.

첫번째 방법이 n개의 오더를 가져와 n번 만큼 for문이 반복하며 유저 테이블을 조회하는
예이다.
"보통 join을 통해 데이터를 가져오지 누가 for문을 이용해 쿼리를 날려 데이터를 가져오나"
 할 수 있지만 코드량이 방대하고, 스파게티 코드이거나 의존성에 의해 해당 이슈가 발생 할 수도 있다.
또한 join의 경우 잘못 사용할 경우 카타시안 곱(n*n)이 발생하여 성능 저하가 일어날 수 있으니 주의해야 한다. 

가끔 join으로 데이터를 가져오기 어려운 경우에는 유저 id: phone 형태의 딕셔너리를 미리 만들어 두어
for row in order:
    user_number = user_dict[row.user_id]
    ...
n+1 -> 2 로 성능을 올릴 수 있다.

2020년 6월 22일 월요일

시스템 트레이딩 :: (1)키움 API를 활용한 백테스팅(Back Testing)

개발환경 : window10 pycharm python3.8 32bit


1. 키움 API 사용

 https://www1.kiwoom.com/nkw.templateFrameSet.do?m=m1408000000

 1) 키움 API 사용을 위해 키움증권 가입 후 키움Open API신청
 2) Open API+ 모듈 다운로드 및 설치
 3) 필요에 따라 개발가이드, KOA Studio 등 다운로드
 4) 상시 모의투자 신청

 오류해결
     1-1 kiwoom' object has no attribute 'oneventconnect'
            Python 32BIT, 버전 3.6 이하인지 확인
            안된다면 키움API 삭제 -> 설치 -> KOA Studio 재다운로드

2. 가상환경 세팅

 1) anaconda 명령프롬트에서 명령어 입력
        set conda_force_32bit=1 (KIWOOM API는 32BIT에서 동작)
        conda create --name envname python=3.6 (PyQt5는 python 3.6버전에서 사용)

 2) 패키지 설치
    anaconda 명령프롬트에서 입력
        acitvate 가상환경이름
        pip install PyQt5 (키움 API사용시 필요)
        pip install pandas (데이터 관리)
        pip install numpy (데이터 관리)

3. KIWOOM API연동

 키움 API는 OCX(Object Linking and Embedding Custom Control)방식을 사용함.
 OCX 방식은 파이썬에서 사용하기 쉽지 않기때문에 PyQt5 패키지의 QAxContainer
 모듈을  통해 OCX를 사용.

 UI를 따로 구현하지 않으면 아래 코드와 같이 키움 API 연동 가능

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__()
        self._create_kiwoom_instance()

    def _create_kiwoom_instance(self):
        self.setControl("KHOPENAPI.KHOpenAPICtrl.1")

if __name__ == '__main__':
    # 키움 api 연결
    app = QApplication(sys.argv)
    kiwoom = Kiwoom()

4. 분 단위 데이터 수집

 개발자 가이드 P.50/53
9.3 대량 DATA(차트 조회) 수신
기존 GetCommData()함수로 항목당 한 개의 데이터를 받아오는 처리
에서 배열로 조회된 전 항목의 데이터를 한꺼번에 받을 수 있게 함수입니다.
차트 조회처럼 반복데이터가 많은 데이터를 한꺼번에 받아서 처리하고 싶을 때 사용하시면 됩니다.

개발자 가이드는 C++언어로 설명돼있기 때문에 참고만,

사용법은 KOA Studio가 더 깔끔해서 보기 좋음

[ opt10080 : 주식분봉차트조회요청 ]

 1. Open API 조회 함수 입력값을 설정합니다.
종목코드 = 전문 조회할 종목코드
SetInputValue("종목코드" ,  "입력값 1");

틱범위 = 1:1분, 3:3분, 5:5분, 10:10분, 15:15분, 30:30분, 45:45분, 60:60분
SetInputValue("틱범위" ,  "입력값 2");

수정주가구분 = 0 or 1, 수신데이터 1:유상증자, 2:무상증자, 4:배당락, 8:액면분할, 16:액면병합, 32:기업합병, 64:감자, 256:권리락
SetInputValue("수정주가구분" ,  "입력값 3");


 2. Open API 조회 함수를 호출해서 전문을 서버로 전송합니다.
CommRqData( "RQName" ,  "opt10080" ,  "0" ,  "화면번호");


 데이터를 요청시 OnReceiveTrData 함수가 호출되며 아래 코드와 같이 요청한 데이터를
 받을 수 있음

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__()
        self._create_kiwoom_instance()
        self._set_signal_slots()
    def _set_signal_slots(self):
        self.OnReceiveTrData.connect(self.receiveTrData)

    def receiveTrData(self, screen_no, rqname, trcode, record_name, next, unused1,                                 unused2, unused3, unused4):
        if trcode == 'opt10080':
            self.count = self.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
            self.opt10080_key = ['현재가', '거래량', '체결시간', '시가', '고가', '저가']
            self.opt10080_data = []
            for i in range(self.count):
                ls = []
                for key in self.opt10080_key:
                    value = abs(int(self.dynamicCall("GetCommData(QString, QString, int,                                          QString)", trcode, rqname, i, key).strip()))
                    ls.append(value)
                self.opt10080_data.append(ls)
            self.tr_event_loop.exit()

2020년 6월 15일 월요일

파이썬으로 트위터 타임라인 크롤링 및 워드클라우드 생성하기

트위터 타임라인 분석


SNS를 즐기는 사람들이 자신이 어떤 단어를 많이쓰고 누구와 연락을 많이하는지

궁금해하지 않을까 하는 호기심으로 시작해서 트위터 타임라인 분석 프로그램을

만들었습니다. 어떤점이 개선됐으면 좋겠다 생각나는 분은 메일이나 댓글로 편하게

알려주세요 !

댓글로 트위터 아이디와 비밀번호 남겨주시면 타임라인 분석 결과를 메일로 보내드려요.


아이유 타임라인 예시


---------------------------------------------------------------------------------------------------

개발자 분들을 위한 내용

개발환경 : window10 pycharm python3.8


1. HTML 구조 확인

트윗 클래스

F12를 눌러 DevTools를 열고 왼쪽 제일 상단에 있는 버튼

(Select an element in the page to inspect it)을 누르면 클래스를 찾을 수 있습니다.

class : css-901oao r-hkyrab r-1qd0xha r-a023e6 r-16dba41 r-ad9z0x r-bcqeeo

r-bnwqim r-qvutc0 아래 span 태그에 있습니다.


예전에 타임라인 크롤러를 만들때와는 웹 구조가 바뀌었습니다.

혹시 전에 만든 프로그램이 작동하지 않는다면 확인해보세요.

한가지 더 바뀐점이 있습니다.


트위터 타임라인 구조

과거에는 웹페이지를 끝까지 내리면 모든 트윗을 한번에 다 가져올 수 있었지만 현재는

한번에 9개의 트윗밖에 가져오지 못합니다.

구조 변경의 문제로 타임라인의 끝까지 스크롤을 내린다 -> 트윗을 불러온다 에서

트윗을 불러온다 -> 스크롤을 내린다 -> 트윗을 불러온다 -> ... 방식으로 프로그램을

만들게 됐습니다.


2. Selenium

Beautifulsoup도 많이 쓰이는 라이브러리 지만 더 여러가지 상황에서 사용 가능한

Selenium을 사용하였습니다.

get( ) : 웹 페이지 오픈

implicitly_wait( ) : 타임스립

page_source : HTML 소스코드 반환

execute_script("return document.body.scrollHeight") : 현재 페이지 위치 반환

execute_script("window.scrollTo(0, document.body.scrollHeight);") : 스크롤 내리기



아래는 소스코드입니다.

def get_timeline_post_by_me(page_source):
    '''
    페이지 소스를 입력하면 해당 유저의 타임라인에 직접 작성한 글을 리스트 형태로 반환
    :param page_source:
    :return:
    '''
    soup = BeautifulSoup(page_source, 'lxml')
    tweets = soup.find_all("div", {"class": "css-901oao r-hkyrab r-1qd0xha r-a023e6 
r-16dba41 r-ad9z0x r-bcqeeo r-bnwqim r-qvutc0"})

    tweet_list1 = []
    for text in tweets:
        tweet_list1.append(text.text)

    return tweet_list1


if __name__ == '__main__':

    # username = 'lily199iu'
    username = input('사용자 아이디 입력 >>>')
    userpage = 'https://twitter.com/' + username + '/with_replies'

    profile = webdriver.FirefoxProfile()
    profile.update_preferences()
    driver = webdriver.Firefox(profile)
    driver.get(userpage)
    # driver.implicitly_wait(1)


    lastHeight = driver.execute_script("return document.body.scrollHeight")
    print('lastHeight(시작 높이) : ', lastHeight)
    result_tweet_list = [0]
    last_page = 0
    i = 0

    while True:
        # 페이지 소스 저장
        html = driver.page_source

        # 페이지 소스에서 타임라인과 길이 반환환
        tweet_list = []

        error_check = 0
        while 1:
            error_check += 1
            if tweet_list != []:
                break
            if error_check > 1000:
                print('1000번 시도내에 접속못함')
                sys.exit()
            print('아직 못가져옴')
            tweet_list = get_timeline_post_by_me(html)

        for tweet in tweet_list:
            result_tweet_list.append(tweet)

        while 1:
            i += 1
            #일단 1번 누르고시작
            for _ in range(1):
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

            #현재 위치 반환
            newHeight = driver.execute_script("return document.body.scrollHeight")

            #현재위치와 전의 위치가 같다면 종료
            if lastHeight == newHeight:
                print('newHeight : ', newHeight)
                print('페이지 위치가 같음. 종료!')
                break

            #현재위치와 전의 위치가 다르다면 다시
            else:
                lastHeight = newHeight
                print('열번을 누르니 위치가 달라짐. 다시!')

        if result_tweet_list[-7:] == tweet_list[-7:]:
            i += 1
            print('마지막 리스트와 가져온 리스트가 동일 종료')
            if i > 200:
                break

    driver.close()
    result_tweet_list = list(set(result_tweet_list))
    print('트윗 갯수', len(result_tweet_list))

    '''
    wordcloud = WordCloud().generate(tweet_list)
    wordcloud.words_
    plt.figure(figsize=(12,12))
    plt.imshow(wordcloud, interpolation='bilinear')
    plt.axis('off')
    plt.show()
    '''


    # 3. 트윗터 패키지 안에 konlpy 모듈호출
    twitter = Twitter()
    sentences_tag = []

    # 4. 각 문장별로 형태소 구분하기
    for sentence in tweet_list:
        morph = twitter.pos(sentence)
        sentences_tag.append(morph)
        print(morph)
        print('-'*30)

    # 5. 명사 혹은 형용사인 품사만 선별해 리스트에 담기
    noun_adj_list = []
    for sentence1 in sentences_tag:
        for word, tag in sentence1:
            if tag in ['Noun', 'Adjective', 'KoreanParticle', 'ScreenName', 'Exclamation', 'Alpha']:
                noun_adj_list.append(word)

    # 6. 선별된 품사별 빈도수 계산 & 상위 빈도 10위 까지 출력
    counts = Counter(noun_adj_list)
    print('상위 노출단어 10개')
    print(counts.most_common(10))
    print('전체 노출단어')
    print(counts)

    font_path = 'c:\\windows\\fonts\\NanumGothic.ttf'
    text = noun_adj_list
    wordcloud = WordCloud(
        font_path = font_path,
        background_color='white',
        width = 800,
        height = 800
    ).generate(' '.join(text))
    plt.imshow(wordcloud, interpolation = 'bilinear')
    plt.axis('off')
    plt.show()

2019년 11월 20일 수요일

앙상블 모델 :: 베깅, 부스팅, 스태킹


앙상블 학습(Ensemble learning)


  • 앙상블이란 프랑스어로 조화, 통일을 의미한다
  • 앙상블앙상블 학습은 개별로 학습한 여러 학습기를 조합해 일반화 성능을 향상시키는 방법이라고 할 수 있다
  • 배깅(Bagging), 부스팅(Boosting), 스태킹(Stacking) 3종류로 나눌 수 있고 널리 알려진 랜덤포레스트(Randomforest) 모델은 배깅 방법에 속한다
  • 앙상블 모델은 보통 지도학습(supervised learning)에 사용되고 군집화나 학습데이터가 없는 아웃라이어 감지(outlier detection), 어노멀리 감지(anomaly detection), 클러스터링(clustering) 같은 것에는 쓰기 어렵다 (비지도학습에도 앙상블을 할 수 있다고 하는데 잘 쓰이지 않는다고함)

베깅(Bagging)



  • 베깅은 부트스트랩 어그리게이팅(Bootstrap AGGregING)의 약어로 샘플링을 다시 샘플링(Bootstrap) 하고 취합(AGGregING) 하는것 이다

  • 통계학에서는 중복을 허용한 리샘플링(resampling)을 부트스트래핑(bootstraping)이라 하고 이와 반대로 중복을 허용하지 않는 샘플링 방식을 페이스팅(pasting)이라고 한다
ex) 나의 경우에는 10만개의 데이터중 7만5천개의 데이터를 학습데이터로 사용한다

7만5천개의 데이터를 가지고 여러 데이터 셋으로 나눈다면 학습데이터가 부족해지고 이로인해 모형들이 편향이 생기거나 분산이 커지는 문제가 생길 수 있다

하지만 베깅의 경우 복원추출(resampling)을 하기때문에 어떤 데이터는 여러번 뽑힐수도있고 어떤 데이터는 한번도 뽑히지 않을 수 도 있다 때문에 75000개의 데이터를 가지고 75000번을 추출해도 75000가지의 서로다른 샘플데이터를 추출할 수 있게된다

이 특성을 이용해 조금씩 다른 모델들을 만들고 그것들의 결과를 취합하는것이다

데이터가 비슷하니 결과도 별 차이가 없다고 생각할 수 있지만 데이터가 빼곡해지는 효과가 있고 조금씩 다른 모델들이 투표를 하는 방식이므로 베깅으로 만들어진 앙상블 모델은 결과들에 대한 편차가 크지않고 안정적인 결과를 보여지도록 향상된다

학습데이터가 원래 편향이 있다면 그로 인한 편향문제까지는 해결하지 못하지만 미지의 데이터(Unseen data)에 상당히 괜찮은 성능을 보이고 노이즈나 아웃라이어에 대해서도 강해지는 것으로 알려져있다

보팅(Voting)


공부를 하다보니 보팅도 앙상블 학습의 한 부류로 취급하여 베깅, 부스팅, 스태킹, 보팅 4가지로 분류하는 경우도 있었다

  • 보팅은 투표, 선거 라는 뜻으로 뜻 그대로의 역할을 수행한다
  • 여러개의 머신러닝 알고리즘 모델을 학습시킨 후 새로운 데이터에 대해 각 모델의 예측값을 가지고 다수결 투표를 통해 최종 클래스를 예측한다
  • 보팅은 hard voting Classifier, soft voting Classifier 두가지로 나뉜다
voting
출처 https://stats.stackexchange.com/questions/349540/hard-voting-soft-voting-in-ensemble-based-methods/349556

Hard voting(단순 투표)



  • 위 사진의 경우는 hard voting 방식이다 4개의 분류 모델이 각 1, 1, 2, 1로 예측했다
  • 1 : 3표 , 2 : 1표 로 1이 더 많은 표를 받았으니 최종 예측 결과는 1이된다
  • hard voting은 Unanimous voting, Majority voting, Plurality voting으로 나뉜다
  • 만장일치 투표(Unanimous voting) : 만장일치일 때만 decision
  • 과반수 투표(Majority voting) : 다수결로 선택된 decision이 전체의 50%를 넘어야됨
  • 다수결 투표(Plurality voting) : --

Soft voting(가중치 투표)



  • soft voting은 앙상블에 사용되는 모든 분류기가 클래스의 확률을 예측할 수 있을때 사용한다
  • 각 분류기의 예측값은 1, 1, 2, 1 지만 해당 레이블을 선택할 확률은 다르기 때문에 확률을 가지고 평균을 구한 뒤 평균이 가장 높은 클래스로 최종 앙상블 예측이 이루어진다
  • 예) 0.9, 0.7, 0.6, 0.8 
            label 1 : (0.9 + 0.7 + 0.8) / 3 = 0.8 
            label 2 : 0.6 /1 = 0.6 
            label 1의 확률이 더 높으므로 최종 예측 결과는 1

부스팅(Boosting)


  • 부스팅은 가중치를 활용하여 약 분류기를 강 분류기로 만드는 방법이다
  • 베깅이 서로 독립적인 분류기를 만들고 결과를 통합하여 예측한다면 부스팅은 모델간에 영향을 준다
  • 모델이 예측을 하면 그 예측 결과에 따라 데이터에 가중치가 부여되고 새로운 모델을 만드는 위 과정이 반복된다
  • 오답에 대해서는 높은 가중치를 부여하고 정답에 대해서는 낮은 가중치를 부여한다 따라서 오답을 정답으로 맞추기 위해 오답에 더 집중할 수 있게 된다
출처 https://swalloow.github.io/bagging-boosting

스태킹(Staking)


  • 스태킹은 서로 다른 모델들을 조합해서 최고의 성능을 내는 모델을 생성한다
  • SVM, RandomForest, KNN 등 다양한 알고리즘을 사용할 수 있고 조합을 통해 서로의 장점은 취하고 약점은 보완할 수 있게된다
  • 하지만 많은 연산량을 가진다

2019년 11월 16일 토요일

2018 빅콘테스트 게임유저 이탈 예측 :: (3) 예측모델

2018 빅콘테스트 게임유저 이탈 예측


모델링


데이터 결합


예측모델을 만들기 위해 acc_id를 기준으로 결합

데이터 분할


현재는 평가기간이 지나 예측 대상으로는 평가가 불가능 하여 train_data 100000건을
train_data 75000, test_data 25000개로 분할하여 예측


학습 데이터와 테스트 데이터에 label별로 균등하게 잘 들어간것을 확인

Parallel Compution


분석하는 데이터이 양이 많기 때문에 시간이 오래 걸려 조금이라도 시간을 줄여보고자 했다

R은 풍부한 통계모델과 강력한 데이터 처리 도구와 시각화 도구가 있지만
in-memory 연산 모드를 사용하여 처리할 수 있는 데이터의 크기가 제한되고
싱글-스레드(Single-thread) 프로그램이기 때문에 놀고있는 자원이 생기게 된다

doParallel 패키지를 이용한 병렬처리


doParallel :: detectCores() 함수를 이용한 사용 가능 코어 확인


8개의 코어가 사용 가능하고 효율을 위해 4개의 코어만 사용함

싱글코어 VS 멀티코어


코드 실행시 속도가 개선 되는지 확인하기위해 10억개의 난수를 발생


싱글코어 : 272.58


멀티코어 : 263.77



약 1% 성능 향상

단순 난수발생 이므로 큰 효과를 보지 못한 것으로 예상된다

NNET



시간이 오래 걸려 실행시켜 놓고 밤에 돌려놓고 아침에 돌아와보니 결과가 나왔다

대기전력으로 바뀐 시간까지 포함된 건지 모르겠지만 약 3~4시간이 걸렸다




신경망 구조 확인



변수의 수가 너무 많아서 사진에 변수이름이 다 나오지 못한다
bestTune은 size = 15, decay = 0.001

예측결과 확인


gmodels :: CrossTable()을 사용하여 결과 확인




25000의 테스트 데이터를 예측한 결과

week : 75% 
month : 69.1% 
2month : 52.1% 
retained : 66.8%
총 예측률 : 64.3%

2month를 제외한 나머지 3개의 label은 예측률이 준수한 것을 확인할 수 있다
EDA에서 예측한 것과 동일하게 2month와 month를 예측하는데 어려움이 있고
그 중에서도 2month를 month로 예측하는 경우가 압도적이다

변수 중요도 확인


어떤 변수들이 결과에 영향을 많이 주는지 확인을 하고 타 예측모델과 비교해보기로 했다

gmodels :: garson() + ggplot2를 사용하여 그래프 생성



RandomForest


랜덤포레스트는 분류예측 모델로 이미 유명하고 별다른 사전 처리 없이 변수를 잘 선택해 준다는 특징이 있다








변수 중요도 확인



두 그래프에서 서로 중요도가 겹치는 변수도 있고 아닌 변수도 있고 NNET과도 비슷하게 판별한 변수가 있는 반면에 반대로 판단한 변수도 존재한다


변수 중요도 확인


어느 것이 예측력을 확인해보고 변수 중요도를 다시 확인해 보도록 함



25000의 테스트 데이터를 예측한 결과

week : 83.7% 
month : 70.2% 
2month : 56.8% 
retained : 76%
총 예측률 : 71.7%

별다른 설정 없이 모델을 만들었음에도 불구하고 NNET(64.3%)보다 예측 성능이 7% 가량 뛰어나다

랜덤포레스트 모델 역시 2month label을 잘 분류해내지 못하는 것이 보이고 NNET과 동일하게 2month를 month로 분류하고 있다

month와 2month를 혼동하는 문제를 해결하기 위한 방법을 연구할 필요가 있겠다

2019년 11월 11일 월요일

2018 빅콘테스트 게임유저 이탈 예측 :: (2) 전처리

2018 빅콘테스트 게임유저 이탈 예측

전처리

데이터 구조 확인

  • train_label
>  str(train_label)






  • train_activity
>  str(train_activity)


























  • train_payment
>  str(train_payment)








  • train_party
>  str(train_party)











  • train_guild
>  str(train_guild)







  • train_trade
>  str(train_trade)











변수 생성 및 전처리

  • 아이템을 받은 횟수, 아이템을 준 횟수, 길드 가입 여부, 파티 플레이 횟수 변수 생성
  • 생성한 변수와 train_activity, train_label, train_payment 변수를 결합
  • 데이터에 음수 값을 없애고 과적합을 막기 위해 정규화를 진행
            normalize = (x - min(x)) / (max(x) - min(x))

EDA

label별 특징을 보기 위해 label을 기준으로 데이터를 결합후 합을 구하고 그래프 확인

levels값이  순서대로 돼있지 않아 예쁘게 보이기 위해 week month month2 retained
순으로 변경 후 출력


아이템 홍문 경험치와 접속일수 같이 label별로 특징이 뚜렷한 변수들도 있지만 그렇지 않은 변수들이 더 많았고 week와 retained은 특징이 뚜렷한 반면에 month와 2month는 특징이 뚜렷하지 않아 분류하기 어려움이 있을것으로 예상된다

2019년 11월 5일 화요일

2018 빅콘테스트 게임유저 이탈 예측 :: (1) 개요 및 문제설명

2018 빅콘테스트 게임유저 이탈 예측


개요


대회 문제

  • 유저들의 게임 활동정보를 이용하여 향후 게임 서비스에서 이탈하는 시점 예측하기

대회문제

이탈예측

  • 고객 관계 관리(Customer Relation Management, CRM) 분야에서 중요하게 다루는 문제
  • 비용 효율적 신규 고객 유입을 위해 필요한 비용 > 기존 고객 유지에 필요한 비용)
  • 정확한 예측이 근본적으로 어려움
            - 저마다 다른 욕구 및 선호도
            - 데이터에서 확인 불가능한 외적인 문제로 인한 이탈 
            - 빠른 변화 
            - 이탈에 대한 기준 모호


분석대상

Blade & Soul

문제 설명


데이터



데이터 규모

  • 학습 데이터 : (계정 아이디기준) 10만 명의 게임 활동 데이터

  • 평가 데이터 : (계정 아이디 기준) 4만 명의 게임 활동 데이터

레이블 및 이탈 기준

  • 이탈 기준 : 4주 이상 게임 미접속
  • 제공 데이터 시점 이후 12주 동안의 접속이력으로 판단
  • 레이블 : 총 4개 클래스로 구분
            -  Week : 1주 이내 이탈
            -  Month : 2~4주 이내 이탈
            -  2Month : 5~8주 이내 이탈
            -  Retained : 잔존

    데이터 종류

    • 주요 활동 정보 : 게임 내에서 수행하는 주요 활동량을 유저별로 1주일 단위로 집계
    • 결제 정보 : 사용자가 게임 활동을 위해 결제한 정보를 1주일 단위로 집계
    • 사회 관계 정보 : 유저 간에 상호 작용 및 사회 관계에 대한 정보 (사회 관계 정보에는 이탈 예측 대상자가 아닌 유저들도 포함되어 있음)

    제공 파일 종류

    • train_label.csv : 학습데이터의 레이블 정보




    • train_activity.csv, test_activity.csv : 유저의 인게임 활동 정보를 일주일 단위로 집계한 정보









    • train_payment.csv, test_activity.csv : 유저별 주간 결제 금액을 집계한 파일





    • train_party.csv, test_party.csv : 유저간 파티 구성 관계를 집계한 파일











    • train_guild.csv, test_guild.csv : 문파별 문파원 목록을 집계한 파일




    • train_trade.csv, test_trade.csv : 유저간 1:1 거래 내역을 집계한 파일













    데이터 보안

    • 개인 정보 보호를 위해 데이터 전처리 후 제공됨
            -  모든 계정 아이디는 해싱을 통해 마스킹 처리
            -  모든 통계량은 표준화(standardization)를 통해 실제값을 알 수 없게 변환
                𝑥𝑛𝑒𝑤 = (𝑥𝑜𝑙𝑑 − 𝜇𝑥) / 𝜎𝑥 ,(𝝁 𝒙 : 𝑚𝑒𝑎𝑛 𝑜𝑓 𝑥, 𝝈 𝒙 : 𝑠𝑡𝑎𝑛𝑑𝑎𝑟𝑑 𝑑𝑒𝑣𝑖𝑎𝑡𝑖𝑜𝑛 𝑜𝑓 𝑥)

    Appendix

    • 레이블 부여 예시


    위 내용은 2018 빅콘테스트 Analysis 분야 챔피언리그 문제 설명회 자료에서 발췌했습니다

    2019년 1월 16일 수요일

    Capstone Design :: twitterscraper python활용

    이번 게시물에서는 python-twitter의 7일이 지난 트윗을 수집할 수 없는 문제점을 해결하기

    위해 twitterscraper를 사용하였습니다.

    Github(https://github.com/taspinar/twitterscraper)에서 상세 정보를 볼 수 있습니다.

    twitterscraper는 트위터에서 제공하는 토큰을 이용하지 않는 API여서 7일의 제한이 걸리지

    않습니다.

    twitter api 설치했던 것과 동일한 방법으로 twitterscraper를 설치해 줍니다.

    설치됐다면

    from twitterscraper import query_tweets

    import 시켜줍니다.

    import twitterscraper로 해도 될 거 같은데 그렇게는 import가 되지 않더군요.

    twitterscraper의 검색 함수는 query_tweets()입니다.

    매개변수(parameter)로는 query, limit, begindate, enddate, poolsize, lang이 있습니다.

    리턴값은 fullname, id, likes, replies, retweets, text, timestamp, user 입니다.

    query_tweets()함수를 이용하여 트윗을 수집해 보겠습니다.

    list_of_tweets = query_tweets('디아블로', begindate=datetime.date(2018,1,1), enddate=datetime.date(2019,1,15), poolsize=150, lang='ko')
    for tweet in list_of_tweets:
        print(tweet.fullname, tweet.timestamp, tweet.text)
        print("\n")

    2018년1월1일부터 2019년1월15일까지 "diablo"가 들어간 트윗을 출력하는 것을

    볼 수 있습니다.

    ///////////////////////////////////////////////////////////////////////////////////////////////////////

    파란여우 2017.09.04일 입대 2018-12-16 07:02:43 로스트아크 2시간 해본 결과
    디아블로 상위호환으로 판명
    1417
    sjrnf1357

    미르 2018-12-16 06:48:41 디아블로 탈모임같은 것만 아니었도 상황이 더 좋았을까요?ㅠㅠ
    1418
    Ahrinicle

    wabi4B 2018-12-16 06:28:17 35mm vs 50mm 표준렌즈 대결구도는 디아블로 천상과 지옥의 영원한 전쟁을 보는 것 같다. 결론이 안나.
    1419
    ORe8yB9YwYkmMxs

    전다선 2018-12-16 05:52:31 역시 디아블로 에미야 영혼석이 짜다가;;;
    1420
    moriyasu_jeon

    영강은 2018-12-16 05:44:48 블리자드 “‘디아블로3’ 최대 경쟁 상대 ‘블·소’”
    1421
    kmsxipjc167

    :Al에드루프lA: 2018-12-16 05:35:42 배틀라이트 로얄..어느 일정한 거리에 몬스터가 있으면 배틀로얄 방식속에 뭔가 디아블로 같은 느낌 낫겠당...ㅋ
    1422
    llYHTll

    Monkey Monk 2018-12-16 05:16:18 안녕하세요. 디아블로3에 나오는 영어를 올리는 봇입니다. 수도승 버전입니다.
    1423
    d3_monk_words

    ///////////////////////////////////////////////////////////////////////////////////////////////////////

    저는 몇 개의 트윗을 수집했나 확인하기 위해서 숫자를 붙였습니다

    그런데 보다 보면 이상한 점이 있습니다. tweet.text에 디아블로라는 단어가 포함되지

    않았음에도 수집해오는 것입니다. 이로 본문 내용에 검색 단어가 포함돼 있지 않아도

    답글하는 아이디, 멘션, 계정 아이디에도 영향을 받는것을 알 수 있습니다.

    python-twitter역시 위와 동일합니다.

    list_of_tweets = query_tweets('디아블로', begindate=datetime.date(2018,1,1), enddate=datetime.date(2019,1,15), poolsize=150, lang='ko')
    a=1for tweet in list_of_tweets:
        if "디아블로" in tweet.text:
            print(str(a) + "\n")
            print(tweet.fullname, tweet.user, tweet.timestamp, tweet.text)
            print("\n")
            a=a+1

    위 내용에서 tweet.text에서 디아블로라는 단어를 찾아주는 조건문을 넣었습니다.

    약 1년 치의 트윗을 확인해본 결과 본문에 디아블로라는 단어가 모두 포함됐습니다.

    디아블로를 검색했을 때는 가계정이나 광고성 글이 보이지 않았지만 다음 포스팅에서는

    필터링하는 코드도 추가하도록 하겠습니다.

    2019년 1월 15일 화요일

    Capstone Design :: python-twitter을 이용하여 트윗 수집하기

    이번 글에서는 데이터 구조와 함수 사용한 것을 작성하겠습니다.

    앞서 작성했던 글을 보면 첫 번째로 할 일이 트윗 수집, 웹 크롤링 이라고 할 수 있습니다.

    우선 api를 사용하기 전에 함수의 매개변수(parameter)를 알아야 되고 함수의 리턴값이

    어떻게 반환되는지도 알아야 합니다.

    자세한 사항은 twitter developer 사이트에 가면 확인할 수 있습니다.

    GetSearch

    우선 GetSearch를 이용하여 아무거나 검색한 후 데이터 타입을 확인해 보겠습니다.

    results = api.GetSearch(raw_query = 'q="python twitter api"')
    print(type(results))

    결과는 <class 'list'> 입니다.

    그럼 list들은 어떠한 타입으로 이루어졌나 확인해 보겠습니다.

    print(type(results[0]))

    결과는<class 'twitter.models.Status'> 입니다.

    데이터를 출력해 보겠습니다

    for res in results:
        print(res)
        print("\n")

    {"created_at": "Tue Jan 15 06:56:16 +0000 2019", "hashtags": [], "id":

    1085068131372912646, "id_str": "1085068131372912646", "lang": "ko", "source": "<a

    href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>", "text":

    "\ud2b8\uc704\ud130 \uace0\uae09\uac80\uc0c9\uacfc\npython twitter api /

    GetSearch() \ud568\uc218 \uc0ac\uc6a9\uc5d0 \ub300\ud55c

    \ud3ec\uc2a4\ud305\nhttps://t.co/nfwMnVNtlA", "urls": [{"expanded_url":

    "https://fouaaa.blogspot.com/2019/01/twitter-search.html", "url":

    "https://t.co/nfwMnVNtlA"}], "user": {"created_at": "Thu Oct 11 13:41:47 +0000 2018",

    "default_profile": true, "default_profile_image": true, "description": "Capstone Design

    \uc9c4\ud589\uc911\n\ud2b8\uc704\ud130 \ubd84\uc11d\uc5d0

    \uad00\uc2ec\uc788\uc73c\uba74

    \ub4e4\ub824\uc8fc\uc138\uc694\ud83d\ude00\ud83d\ude00\n#twitter

    api\n#python-twitter", "followers_count": 1, "geo_enabled": true, "id":

    1050380947378515968, "id_str": "1050380947378515968", "lang": "ko", "name": "Tester",

    "profile_background_color": "F5F8FA", "profile_image_url":

    "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",

    "profile_image_url_https":

    "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",

    "profile_link_color": "1DA1F2", "profile_sidebar_border_color": "C0DEED",

    "profile_sidebar_fill_color": "DDEEF6", "profile_text_color": "333333",

    "profile_use_background_image": true, "screen_name": "3LTYxHTRnlMRHX8",

    "statuses_count": 6, "url": "https://t.co/U0lxs3fyMk"}, "user_mentions": []}

    출력 결과는 너무 길어서 잘랐습니다.

    위 출력 결과를 보면 created_at, hashtags, id .. 등의 정보를 가지고 있는 걸 볼 수

    있습니다. 중간에 제 아이디도 보이네요.

    그런데 글들이 알수없는 문자로 깨져서 나오는걸 볼 수 있습니다.

    이는 결과값을 아크키코드로 가져와서 한글이 정상적으로 출력 되지 않는 것이므로

    유니코드로 변환시켜서 볼 수 있습니다.

    print(u"아스키코드") 식으로 출력할 수 있습니다.


    저는 GetSearch함수를 이용해 가져온 모든 값이 필요하지 않습니다.

    class Status(TwitterModel):    """A class representing the Status structure used by the twitter API.    """    def __init__(self, **kwargs):        self.param_defaults = {            'contributors': None, 'coordinates': None, 'created_at': None, 'current_user_retweet': None, 'favorite_count': None,             'favorited': None, 'full_text': None, 'geo': None, 'hashtags': None,  'id': None, 'id_str': None,            'in_reply_to_screen_name': None, 'in_reply_to_status_id': None,  'in_reply_to_user_id': None, 'lang': None,            'location': None, 'media': None,  'place': None,  'possibly_sensitive': None,  'quoted_status': None, 'quoted_status_id': None,             'quoted_status_id_str': None,  'retweet_count': None,  'retweeted': None,  'retweeted_status': None,  'scopes': None,  'source': None,             'text': None,  'truncated': None, 'urls': None,  'user': None, 'user_mentions': None,            'withheld_copyright': None, 'withheld_in_countries': None, 'withheld_scope': None,        }

    이중에서 저는 created_at, text, user만을 이용할 것입니다.

    for res in results:
        print(res.created_at, res.text, res.user)
        print("\n")

    이렇게 원하는 정보만 가져올 수 있고 user 정보 중에서도 원하는 정보만 가져올 수

    있습니다. user 정보는 twitter developer 사이트에서 확인할 수 있습니다.

    너무 길어서 여기서는 언급하지 않겠습니다.

    import datetime
    
    
    nowtime = datetime.datetime.now()
    since = nowtime - datetime.timedelta(days=10)
    until = since + datetime.timedelta(days=1)

    datetime을 이용하여 현재 날짜를 구하고

    현재 날짜부터 10일 전 날짜, 그 전날짜를 구합니다.

    
    
    while(since < datetime.datetime.now()):
        product = '"갤럭시 s9"'    results = api.GetSearch(term = product, until = until.strftime("%Y-%m-%d"), since = since.strftime("%Y-%m-%d"), count=100, lang='ko', result_type ='mixed')
    
    #friends_count 팔로잉 수#followers_count 팔로워 수    for res in results:
            if(res.user.friends_count > 10 and res.user.friends_count < 300 and res.user.followers_count > 10 and res.user.friends_count - res.user.followers_count < 50 and res.user.followers_count - res.user.friends_count < 200):
                print(res.id,res.user.screen_name, res.user.friends_count, res.user.followers_count, res.created_at, res.hashtags,res.text)
                print("\n")
    
        since = since + datetime.timedelta(days=1)
        until = until + datetime.timedelta(days=1)

    검색이 가능한 10일 전 날짜부터 현재 날짜까지 while을 통하여 검색을 진행했습니다.

    if의 조건문은 광고나 가계정들을 걸러내기 위해 조건문을 설정해 주었습니다.

    가계정은 follower가 별로 없고 있다고 해도 follower와 following를 비교했을 때

    following수가 더 많을 것 이라고 생각했고 실제로 그런 경우가 조금 더 많이 발견돼서

    이렇게 조건을 주었습니다.

    하지만 가장 큰 난관에 봉착했습니다. 약 일주일 전에 게시된 트윗밖에 검색할 수 없다는 것

    입니다. 데이터를 수집하고 분석하여 점수화를 시키기에는 턱없이 부족한 양입니다.

    혹시나 하는 생각으로 tweepy도 사용해 봤습니다.

    하지만 역시 트위터에서 제공하는 토큰값을 받아서 사용하는 api기 때문에 7일간의

    트윗밖에 검색할 수 없습니다.

    그 이전의 트윗을 수집하는 방법을 더 공부해야겠습니다.

    2019년 1월 14일 월요일

    Capstone Design :: GetSearch, 트위터 고급검색 연산자

    졸업작품을 진행중 GetSearch() 함수를 이용하며 고급검색에 대하여 공부하게 됐고

    이에 대해서 포스팅합니다.

    트위터 고급 검색


    1. 다음 단어를 모두 포함

    여러 가지 검색어를 모두 포함한 검색방법 입니다.

    [트위터 고급검색]을 검색할 경우 [트위터] 와 [고급검색] 두 가지 단어가 

    글에 모두 들어가야 한다고 생각하면 됩니다.

    AND 연산자라고 생각하면 되겠습니다.

    2. 다음 문구 그대로 포함

    여러 가지 검색어를 한가지 단어로 인식하고 검색하는 방법입니다.

    [트위터 고급검색 사용]을 검색하면 "이 글은 트위터 고급검색 사용 방법에 관한 글입니다"

    라는 식으로 나오게 됩니다. 쉽게 말해 여러 단어가 붙어있어야 합니다.

    실제로 연산자 사용 시 1번은 [트위터 고급검색 사용] 2번은 ["트위터 고급검색 사용"]으로 

    쿼리가 날아가게 됩니다.

    3. 다음 단어 적어도 하나 포함

    여러 가지 검색어 중 하나라도 포함하는 글을 보여주는 검색 방법 입니다.

    [트위터 고급검색 방법]을 검색하면

    "트위터는 SNS 중 하나다", "고급검색은 어렵다.". "여러 가지 방법이 있다."

    처럼 띄어쓰기로 구분된 각 단어가 하나라도 포함되면 모두 출력됩니다.

    OR 연산이라고 생각하면 되겠습니다.

    4. 다음 단어 제외

    사용처가 궁금한 기능입니다.

    5. 해시태그

    여러가지 해시태그 검색어중 해시태그에 하나라도 포함되면 글을 보여줍니다

    흔히 알고있는 [#트위터], [#고급검색] 형태의 태그 입니다.

    해시태그 검색도 3번 연산자와 같이 OR 연산을 합니다.

    6. 언어

    언어 종류를 선택할수 있습니다.

    선택하지 않으면 모든 언어가 기본이고 필요시 원하는 언어를 선택하여 특정 언어로

    작성된 글만 볼 수 있습니다.

    7. 다음 계정에서 작성

    작성자 아이디를 검색합니다. OR 연산을 합니다.

    8. 다음 계정으로 보냄

    받는사람 아이디를 검색합니다. OR 연산을 합니다.

    9. 다음 지역 주변

    설정한 지역과 거리를 설정할수 있습니다.

    상세 설정은 near:지역 within:거리 로 설정 가능

    10. 시작일

    지정한 날짜에 생성된 글을 볼 수 있게 해줍니다.

    쿼리는 since:2019-01-01 until:2019-01-31 형태로 날아갑니다.

    since만 설정하면 설정된 날짜 이후의 모든 글을 볼 수 있고

    until만 설정하면 설정된 날짜 이전의 모든 글을 보여줍니다.

    ==========================================================

    프로그래밍과 웹에서 날리는 쿼리가 비슷하므로 여기 같이 작성하겠습니다.

    아래 링크로 가면 트위터 고급 검색을 할 수 있습니다.


    트위터 검색창에 아무런 검색이나 하고 왼쪽에 있는 검색 필터를 눌러 표시로 바꿔주면

    아래에 고급 검색이 나오게 됩니다. 고급 검색을 누르면 위 링크로 이동합니다.

    고급 검색 예시

    다음 단어 적어도 하나포함 : twitter api

    언어 : 한국어

    다음 계정에서 작성 : 3LTYxHTRnlMRHX8

    날짜 : 2019-01-01 에서 2019-01-14

    위 처럼 검색하면 주소창이

    https://twitter.com/search?

    l=ko&q=twitter%20OR%20api%20from%3A3LTYxHTRnlMRHX8%20

    since%3A2019-01-01%20until%3A2019-01-14&src=typd&lang=ko

    로 바뀌게 됩니다. 여기서 자세히 볼 부분은 q= 부터입니다.

    이는 GetSearch() 함수 인수중 raw_query를 사용하여 쿼리를 작성할때 사용되기 때문입니

    다.

    이게 무슨 뜻이냐면 어떤 파라미터(parameter)를 이용해야될지 모를때, 문법을 모르겠을때

    고급검색을 이용하면 된다는 뜻이기도 합니다.

    GetSearch()함수를 이용하는 방법은 여러가지가 있겠지만 그중에서도 2가지 방법을 작성 

    하겠습니다.

    첫번째는 results =

    api.GetSearch(raw_query="q=twitter%20OR%20api%20from%3A3LTYxHTRnlMRHX8

    %20since%3A2019-01-01%20until%3A2019-01-14&src=typd&lang=ko") 입니다.

    위에서 설명한것처럼 고급 검색을 이용하여 검색하면 get 방식을 이용하여 전달된 값들이

    주소창에 나타나게 되는데 raw_query는 이처럼 고급 검색과 같은 방식으로 검색을 하게 됩

    니다. 이 방법은 단점이 있는데 값을 전달할때 정확히 전달 되도록 특수문자를 16진수로

    표기해서 전달 한다는 것입니다. 공백은 %20, : (쌍점)은 %3A 입니다.

    두번째 방법은 파라미터 하나하나에 값을 넣어주는 방법입니다.

    results = api.GetSearch(term = product, until = until.strftime("%Y-%m-%d"),

    since = since.strftime("%Y-%m-%d"), count=100, lang='ko', result_type ='mixed')

    개인적으로 첫번째 방법보다 작성하기가 더 수월했습니다.

    아래는 시간함수를 이용했는데 이유는 standard search api 이용자는 최근 날짜에서 7일

    지난 트윗 밖에 가져올수 없기 때문입니다. 저같은 경우에는 10일 까지 검색이 됐었습니다.

    그 전에 작성된 트윗을 검색하고 싶다면 premium 이용자가 되면 됩니다.

    GetSearch() 함수의 반환 값이나 사용 코드는 다른 글에 포스팅 하도록 하겠습니다.

    2019년 1월 12일 토요일

    Capstone Design :: 개발환경 구축

    개발환경 구축


    참 애를 많이 먹었다.

    코드 몇 줄 설치 몇 개 하는 게 왜 이리 힘든건지 오류가 참 많이 난다.

    우선 개발하기 앞서 필요한 툴들을 설치한다.

    Python, Pycharm, Mamp, Atom을 설치해준다. 여기까진 크게 어려운 부분이 없으니

    따로 포스팅하지 않겠다.

    후에 개인적으로 애먹었던 부분은 조금 상세하게 포스팅하도록 하겠다.

    프로그래머 지망생일 뿐이니 너그러이 이해 바람..

    설치가 다 끝났다면 첫 단계로 할 것은 Twitter API 를 import 하는 것이다

    우선 트위터에 가입을 해야 된다. 개발자 신청을 해야 토큰을 받아오고 API Aouth 인증을

    할 수 있기 때문이다. (사실 검색만 하는 입장이어서 토큰값까지 필요한지는 잘 모르겠다.. 

    하지만 일단 받아보도록 한다.)

    https://developer.twitter.com 여기로 가서 신청하면 됐던 거로 기억한다.

    신청하면 몇 가지 질문을 하는데 간략하게 대답해도 인증이 완료된다.

    너무 엉망으로 하면 다시 제출하라고 답신이 오니 참고 !

    아.. 예전에는 인증과정이 없었는데 생긴 지 얼마 안 된듯싶다. 조금만 더 빨리할껄 그랬다.

    토큰값을 받아왔다면 파이참을 이용해 Twitter API를 사용할 차례다.

    우선 설치해 주도록 한다.

    나는 Pycharm을 처음 써봐서 환경변수 설정을 하면 Python에 설치한 라이브러리를

    사용할 수 있는줄 알았는데 아니었다. 나 같은 사람을 위해 적어본다.

    설치 방법은 간단하다.

    ************************************************************************************************

    File - Settings - (왼쪽 메뉴에) Project : 프로젝트명 - Project Interpreter -

    (오른쪽에) + 클릭 - 검색창에 python-twitter 를 검색 - 왼쪽 아래 Install Package

    ************************************************************************************************

    이렇게 하면 설치가 시작된다. 참 쉬운걸 가지고 나는 한참 해맸었다.

    python에 설치하려고 Git Bash도 이용해보고 별짓을 다했기 때문이다.

    이건 번외로 포스팅 하도록 하겠다.

    설치가 다됐으면 Twitter api를 import 해보자

    import twitter 해주면 된다.

    다음으로는 Aouth를 인증해 주면 된다.

    개발자 인증을 통해서 받은 토큰값을 넣어주면 된다.

    api = twitter.Api(consumer_key='________________________________',
                      consumer_secret='________________________________',
                      access_token_key='________________________________',
                      access_token_secret='________________________________')

    토큰값은 보안상의 이유로 노출시키면 안된다.

    인증이 잘 됐다면 테스트를 해보자

    status = api.PostUpdate('python-twitter api 성공 !')

    만약 성공했다면 개발자 신청시 사용했던 아이디 타임라인에 글이 작성된걸 볼 수 있다.



    여기까지 했다면 개발환경 구축은 거의 끝났다고 볼수있다.


    // 기록 겸 공부겸 포스팅 하다보니 twitter api 사용법을 포스팅 하는 블로그 같이 됐다.

       내가 공부하고 개발 하면서 어려웠던 부분을 공유하고 서로 도움 받을수 있다면

       이렇게 자세히 포스팅 하는게 좋을것같다 !