노션은 여러 사람들이 사용하는 협업 툴로 데이터베이스 기능이 있습니다. 이번 글은 멘토링과 같은 다수의 인원의 일정을 구글캘린더에 동기화 하는 방법을 기술합니다. 이렇게 하면 매번 노션페이지에 들어가서 일정을 확인하고 내 캘린더에 기록할 번거로움이 사라집니다.
목차
1. 요구사항 정의
저는 부업으로 멘토링을 하고 있습니다. 다수의 인원이 저에게 멘토링을 신청하는데, 기존에는 슬랙에 멘토링 가능시간을 올리면 답글로 신청하는 방식이였습니다. 하지만 여러 채널에서 멘토링 신청을 받는 상황이 와서 같은 시간에 약속을 잡는 상황이 발생할 수 도 있었습니다. 또한, 이런 멘토링 신청내용을 저의 구글 캘린더에 담아서 한번에 일정을 확인하고 싶어졌습니다.
기본적으로 Make라는 자동화 툴에서는 노션의 데이터베이스에 상호작용할 수 있는 인터페이스를 제공합니다. 데이터베이스 조회, 생성, 추가, 업데이트 등의 옵션을 제공합니다.
최종적으로 완성시킬 Flow는 다음과 같습니다.
노션 데이터베이스 -> Make -> 구글 / 노션 캘린더 기입
2. 노션데이터베이스 기입 -> Make
노션에 캘린더 형식으로 데이터베이스를 만들고, 속성으로 "멘티 이름"과 "날짜 시각"을 정확하게 기입하도록 유도합니다. 이 속성을 추후 Make툴의 파라미터로 받아서 구글 캘린더에 작성됩니다.
달력 데이터베이스신청 페이지
2. Make 세부사항
Make에서 Notion 홈페이지에 대한 권한을 줄 수 있습니다. 거기서 특정 데이터베이스 이름을 기준으로 데이터를 관찰할 수 있고, 새로운 이벤트가 열리면 이를 trigger 삼아 구글 캘린더에 전송하는 시스템입니다.
Connection: 연결할 노션 계정과 페이지
Watch Database Item: 관찰할 기준
Database ID: 데이터베이스 식별자, 노션 페이지를 지정해주면 자동으로 연결 가능한 DB가 확인됨
Limit: 1번 실행에 추가할 수
Connection: 연결할 구글 캘린더 계정
Create an Event: 간단하게(Quickly), 자세하게(In Detail)
Calender ID: 캘린더 이름
Event Name: 캘린더에 적을 제목으로, 멘티 이름 속성을 Notion에 받아와 추가했습니다( Ex [1on1] 홍길동)
Start Date: 노션에 기입한 날짜&시각을 기입, 노션 DB와 자료형 일치
Duration: 추가할 시간
그 밖에도 노션에서 가져오는 정보들이 변수명으로 적혀있어서 필요에 따라 선택하여 넣을 수 있습니다.
3. 시연
왼쪽 하단에보면 매 15분 마다 Tigger가 작동합니다. 노션 데이터베이스를 관찰하는 빈도입니다.
왼쪽 하단에 Run Once를 눌러서 즉시 실행을 시킬 수도 있으며, 트리거의 조건에 따라 기입했던 노션 데이터베이스의 정보를 받아온 것을 확인할 수 있습니다. Propeties Value의 멘티이름과 날짜시각이 잘 들어온 것을 볼 수 있습니다.
지난 통계학/데이터사이언스 책 추천에 이어서 엔지니어링 관련 책 추천을 해보려 합니다. 데이터 사이언스에서 CS, 백엔드, 프론트, 네트워크, 데이터 모델링 등 필수적이진 않지만 알면 알수록 본인의 성과와 확장성이 높아질 것입니다. 참고로 이전 책 추천은 다음 링크를 참고하세요.
이번에 시간을 공유하는 삶이라는 타임 트래킹 소모임에 참여하게 되었습니다. 일단위로 시간을 기록하고 일주일 회고를 12주간 반복하는 모임입니다. 이 모임에서 아이디어를 얻어 나의 업무기록을 자동화하는 여정에 대해서 Make 툴을 중심으로 작성해보겠습니다.
목차
1. 요구사항 정의
시간단위로 업무를 기록하는 모임이기 때문에 최대한 시간을 기록하는 것에 공수를 줄이고 싶었습니다. 매번 업무와 일상의 context change가 될 때마다 캘린더를 키고 기입하는 것이 아닌, 최소한의 트리거로 캘린더에 기입하는 것이 목적이였습니다. 일단 가장 루틴한 업무부터 기록해보자는 생각이 들었습니다. 루틴하게 9-6를 캘린더에 밀어넣고 사후 조정해도 되지만, 점심시간이나 집중하지 않을 때 혹은 사이드 프로젝트를 시작하고 종료하는 시간을 기록해보고자 했습니다. 예컨데 다음과 같은 절차로 캘린더 기입이 되었으면 했습니다.
핸드폰 터치 -> (무엇인가) -> 노션 캘린더 기입
갤럭시에는 루틴와 굿락이라는 어플로 어느정도 자동화 할 수 있지만 "캘린더에 현재 시각을 기준으로 1시간동안 업무를 제목으로 기록해줘" 와 같은 디테일은 챙길 수 없습니다. 그래서 2가지 툴을 도입했습니다.
2. Tasker
먼저 갤럭시에서 Custom 된 HTTP POST 요청을 보낼 수 있어야합니다. 이를 도와주는 도구로 Tasker라는 유료앱(4400원)이 존재합니다. UI는 투박한데 모바일에서 가장 자동화하기 좋은 어플로 평가받더라구요. 아이폰에서 단축어와 대응될 수 있겠습니다.
우편 == POST
추가적으로 % 옵션을 이용하여 현재 시각 등등 매개변수를 POST요청에 태워서 보낼 수 있습니다. 참고로 google calendar에 기입하려면 summary와 text 키 값이 필수라고 합니다.
3. Make
Make는 GUI로 구현하는 자동화툴로서 Notion, Gmail, ChatGPT, Calendar 등 다양한 서비스의 자동화를 지원합니다. 접근성이 쉽고 테스트하기 좋은 서비스입니다. 또한 노션캘린더와 구글캘린더는 양방향 동기화가 되기 때문에 고민이 덜어집니다. 결론적으로 다음과 같은 루틴이 완성됩니다.
갤럭시 Tasker 자동화클릭 -> make -> Google Calender -> Notion Calender
3. 시연
Tasker에서 생성된 어플을 누르면 온종일 대기하고 있는 Make가 응답을 받고
Calendar 노드가 정해진 규칙(제목: summary 변수, 시작 지금, 종료 15분 뒤) 으로 일정을 보내어
구글 캘린더에 기입된 후 노션 캘린더에도 동기화 됩니다.
4. 후기
Tasker와 Maker 조합이면 핸드폰으로도 웬만한 자동화 모두 가능할 것 같고 사실 make단독으로도 루틴을 만들어서 생성하는게 꽤나 활용할 것 같습니다. 업무 외에도 휴식이나 수면과 같은 시간을 자동으로 기입해 보겠습니다.
데이터 중심 어플리케이션 설계에 이어 컴퓨터 밑바닥의 비밀이라는 책을 스터디 합니다. 1장은 프로그래밍 언어가 작동하는 기본을 설명하는 단원으로 컴파일러와 링커 그리고 추상화에에 대한 내용입니다. 이번에는 조금 색다르게 비유법을 통해서 설명해보겠습니다.
목차
1. 용어 정리
신규개념
설명
컴파일러(compiler)
고수준의 언어를 저수준의 언어로 번역하는 프로그램
토큰(Token)
컴파일러가 쪼갠 각 항목에 정보를 결합한 것
해석(parsing)
소스코드에서 토큰을 추출하는 과정
GCC 컴파일러
유닉스/리눅스 계열의 표준 컴파일러
대상 파일(object file)
소스파일을 컴파일한 결과로 기계어로 쓰여진 프로그램
링커(Linker)
컴파일러가 만든 대상파일을 하나의 실행파일로 합쳐주는 프로그램
심벌(Symbol)
전역 변수(Global Variable)과 함수(function)을 포함하는 모든 변수 이름들
라이브러리
컴파일된 패키지와 모든 함수의 선언을 포함한 헤더 파일로 만든 것
헤더 파일(header file)
컴파일러가 외부 심벌 정보를 기록하는 표
2. 프로그램이 음식점이라면
여기 이세계 식당이 있습니다. 요리사와 요리법이 존재하는 것 같지만 재료는 무한이 복제되는 마법 창고에서 생성됩니다. 이 때 요리사는 재료와 요리법의 순서를 조합하여 하나의 완성된 요리를 하게 됩니다. 이를 우린 컴퓨터에서 컴파일러라고 부릅니다.
만약 제육볶음을 만든다면? 파, 고기 앞다리살과 같은 재료가 필요할 것이구요, 파는 썰고 고기는 적당히 볶고 맛나게 먹는 그런 레시피가 만들어질 것 입니다. 이렇듯 컴파일러는 데이터와 코드의 조합으로 실행 가능한 그리고 기계가 이해할 수 있는 대상 파일을 만들게 됩니다.
그런데 말입니다. 옆 집 중국 음식점의 두반장이 그렇게 인기가 많다는데요? 제육볶음에 한 번 넣어보면 어떨까요? 마침 중국집 위치가 적혀있는 정보도 가지고 있네요! 찾아가서 가져옵니다. 이때 두반장은 외부 변수이고 외부 재료의 위치는 심벌 테이블로 비유될 수 있습니다.
자 이제 제육볶음 레시피를 개발했으니 우리 수 쉐프에게 보고하죠. 쉪! 이게 새로운 제육볶음 레시피입니다. 쉐프가 흡족해 하는군요. 이걸 우린 컴퓨터에서 링커 라고 합니다. 이 링커는 흡족한 대상 파일을 모아 하나의 요리법으로 정리합니다. 이를 우리는 라이브러리라고 하구요. 마치 밀키트를 연상 시키는군요!
그런데 말입니다. 컴파일러를 열심히 설명해봤는데, 인터프리터라는 것도 있단 말이죠? 말로는 파이썬, 자바스크립트가 인터프리터라고 하고 C가 컴파일러라는데 잘 이해가 되지 않는 군요. 요리법으로 비교하면 말이죠, 컴파일러는 설계형 요리사입니다. 차근히 대상파일을 보고 변수가 잘 선언되어있는지 에러는 나지 않는지 검토합니다. 레시피를 검토하는 것처럼 말이죠! 반면 인터프리터는 즉석 요리사입니다. 일단 만들고 해석합니다. 그러다보니 아차! 제육이 없군요! 다시 시작 해야겠어요.
3. 동적/정적 라이브러리
그런데 말입니다. 남의 요리법을 가져다 쓸 수 있다는 사실. 우린 라이브러리를 가져다 쓰는 것에 익숙한데 이렇게 남의 노하우, 요리법을 복사해다가 내 음식점을 저장해놀 수 있겠습니다. 마라탕이 그렇게 인기가 많대요!! 우리도 써봐요. 문제는 요리법이 세상에 너~무 많아서 이걸 모두 복사해놓다간 음식 창고보다 문서창고가 더 많아지겠어요. 이거이거 문제군요.
그러지 말고 우리 돕고 삽시다! 저기 동문시장 청년몰은 요리법을 공유하는 시스템이 있대요!! 몰래 복사해 놓을 것이아니라 필요할 때 마다 가져다 쓰면 된답니다 오호!! 역시 사람은 돕고 살아야해요. 운영시간 내내 가져다 써도 되고 주문이 들어올 때 마다 가져다 써도 된다니 이거 참 인심이 좋은 곳입니다.
이렇게 동적 라이브러리는 장점이 많아요! 매번 요리법을 복제하지 않아도 되고요! 요리법 ver 2.0가 나와도 수정된 요리법만 가져오면 만사 Ok! 요리법을 복사해다 쓰던(정적 라이브러리)시절보다 훨씬 편하군요. 추가 메뉴 확장도 좋아요. 요즘 또 안성재 쉐프가 유명하니까 도토리 국수 플러그인을 써보겠습니다. 또 다른 요리법과 쓰까 쓸 수 있는 점도 아주 좋군요!
하지만 단점도 있는데요! 아무래도 요리법을 공유 주방에서 가져오다보니 살짝 시간이 소요가 되네요! 아무렴 어떠한가요. 그리고 잘 쓰던 중국집 위치가 이사 갔대요 흑흑 원래는 참조표를 보고 다녔는데 이사갔나 보군요. 이런 경우를 대비해서 멀-티버스를 만들어서 극복~ 했답니다. (급마무리)
4. 후기
보통 데이터를 음식으로 표현하니까 적용해봤는데 재밌으면서도 엄밀하지 않은 부분이 공존하는 그런 글이 되었네요. 사실 링커가 내부 변수는 관심을 가지지 않는다 와 같은 사실도 한 번 표현해보고자 했는데 가상화 부분이 살짝 이해가 안되어서 추후 메모리 단원에서 정리할 예정!
데이터 중심 어플리케이션 설계(대중애설) 스터디의 후기를 작성하고 좋았던 방법과 이 책을 스터디로 선택하려는 사람들을 위한 기록을 남겨봅니다.
1. 스터디 방법
사용 플랫폼: 슬랙 - 허들
기간: 25년 2월 ~ 4월 24일
참여 방법: 총 12장을 매주 읽고 블로그/노션에 NDA 템플릿을 이용해서 정리하기
새롭게 알게된 점(New)
어려웠거나 이해하지 못한 부분(Difficulty)
추가 내용(Amendment)
스터디 방법: 매주 일요일 오후 10시에 모여 작성한 내용 읽으며 질의응답
2. 후기
책 후기
"데이터"라는 문구만 보고 데이터분석/사이언티스트가 도전할 수도 있겠으나 명확히는 설계에 관점이 맞춰져 있기 때문에 백엔드에 관심 있는 사람들이 보는게 좋다. Part 1에서는 신뢰성, 데이터 모델, 저장소와 검색, 부호화 Part 2에서는 분산 데이터, 복제, 파티셔닝, 트랜젝션, 일관성 Part 3 배치 처리, 스트림 처리에 대해서 다루는 흐름으로 폭 넓게 데이터 시스템 설계에 대한 이해를 도울 수 있을 것으로 판단할 수 있다. 하지만 각 단원마다의 내용이 압축되어 소개하므로 각 시스템에 대해서 설계하고 구현한 사람들에는 복기의 느낌으로 읽어보는 것이 가장 큰 도움이 될 수 있다. 다른 이론서와 비슷하게 책 자체를 필사해서 적는 블로그가 많기 때문에(본인 포함...) 참고 할만한 블로그가 적다. 늘 느끼지만 이런 난이도 높은 책들은 완벽하게 이해하려하기 보다는 책/단원에서 얘기말하고자 하는 바를 인내심있게 쫓되 나의 무지를 너무 탓하지 않고 꾸준히 읽는 것이 스터디 완료의 비결인 듯! 단원이 말하고자 바를 요약하고 나의 언어로 재구성해서 작성하는 셀프 스터디가 필요하다. 본인이 데이터 관련 직무라면 3장 정도는 읽어보는거 매우 추천
자율적으로 글을 쓰고 발표하기로 하였어서 고정적인 2-3명의 발표자가 있고 청중이 5명정도 유지되었다. 특히 청중에는 N사 광고도메인 개발자 분이 계셔서, 추상적으로 읽을 수 밖에 없는 주니어 개발자들의 질문이나 고민을 실무적으로 대답해주는 부분이 이번 스터디의 가장 큰 특징이였다. 느슨한 스터디에도 꾸준히 진행할 수 있었던 동기부여가 된 것 같다. 서로 얻을 수 있는 부분을 자연스럽게 구축하는 것이 스터디에서 중요함을 느꼈다. 꽤나 잘 진행된 스터디어서 다음 책은 컴퓨터밑바닥의 비밀 이라는 책을 진행할 예정
집앞의 횡단보도를 자주 지나가는데, 횡단보도를 기다릴지 아닐지 늘 고민하면서 지나갑니다.'4명 기다리고 있는데 금방 바뀌지 않을까? 좀 더 가서 사거리에서 건너는게 나을까?' 저는 데이터를 수집해서 언제쯤 횡단보도가 바뀌는지, 그리고 이를 활용할 수 있는 분포는 뭐가 있는지 확인해보자는 문제의식을 정의하고 해결하는 방법을 작성했습니다. 월간 데이터노트 4기의 스터디로 진행된 분석입니다. 월간 데이터노트 참여에 관심이 있다면 다음 공지를 확인해주세요!
횡단보도를 이용하는 사람은 주민, 학생, 출퇴근 직장인들이 주류를 이루며, 10시/ 4시 정도에는 일부 유모차를 동반한 엄마가 등장
2. 데이터 수집
4차선도로이며 횡단보도 기준 빨간불 2분, 파란불 25초
제가 주로 이동하는 시간인 출퇴근/점심시간/퇴근 시간에 수시로 22개 수기 수집
평균 4.1명, 중위 3.5, 표준편차 3.6명, IQR 기준 이상치 2개(13, 14)
시간대 분포
3. 분포 모델 검토
처음에는 포아송 분포라고 생각하고 호기심 발제했음
포아송 분포
정의: 일정 시간 & 공간 내에 독립적으로 발생하는 사건의 횟수를 모델링
분포의 특징과 적합성 체크
사건은 서로 독립적으로 발생 → 맞음
사건은 동시에 발생하지 않음 → 동시에 사람들이 도착하기도 함
평균 발생률은 관측 구간 내 일정 → 맞음
분산/평균의 값이 1임 → 아님 3.01임 (과산포임)
다른 조건이 어느정도 맞으나 분산과 평균이 같지 않아서 Claude가 음이항 분포를 추천함
음이항 분포
정의: r번째 성공을 관측할 때 까지 필요한 시행 횟수나 성공 확률이 p인 베르누이 시행에서 r번의 성공을 관측할때까지 실패한 횟수의 분포
분포의 특징과 적합성 체크
분산이 평균보다 큰 과산포(overdispersion) 데이터에 적합
Ex) 특정 지역의 범죄 발생 건수(범죄가 특정 지역에 집중)
Ex2) 소비자 구매 행동(일부 고객이 훨씬 더 자주 구매)
데이터에 과산포가 존재(분산> 평균) → 맞음
사건 발생이 군집화 되는 경향이 있음 → 맞음 일부시간대 몰림
개체별로 발생률에 차이가 있음 → 있을 수 있음(주부/학생 등)
발생 확률이 시간에 따라 변할 수 있음 → 그러함
4. 분석
히스토그램 및 분포 시각화
포아송 분포와 음이항 분포 적합
적합도 검정 (카이제곱 검정)
AIC 및 BIC를 사용한 모델 비교
import matplotlib.pyplot as plt
import platform
def set_matplotlib_font():
system = platform.system()
if system == "Windows":
plt.rc('font', family='Malgun Gothic')
elif system == "Darwin": # macOS
plt.rc('font', family='AppleGothic')
elif system == "Linux":
plt.rc('font', family='NanumGothic')
else:
print("Unknown system. Please set font manually.")
plt.rcParams['axes.unicode_minus'] = False
# 폰트 설정 함수 호출
set_matplotlib_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats
from statsmodels.discrete.discrete_model import Poisson, NegativeBinomial
df = data.copy()
df.columns = ['timestamp','count']
print("데이터 샘플:")
print(df.head())
# 기본 통계량
print("\n기본 통계량:")
print(df['count'].describe())
mean_count = df['count'].mean()
var_count = df['count'].var()
print(f"평균: {mean_count:.4f}")
print(f"분산: {var_count:.4f}")
print(f"분산/평균 비율: {var_count/mean_count:.4f}")
# 히스토그램 및 분포 시각화
plt.figure(figsize=(12, 6))
# 히스토그램
plt.subplot(1, 2, 1)
# sns.histplot(df['count'], kde=True, stat='density', discrete=True)
df['count'].hist(density = True)
plt.title('횡단보도 건너는 사람 수 히스토그램')
plt.xlabel('사람 수')
plt.ylabel('빈도')
# 경험적 분포와 이론적 분포 비교
x = np.arange(0, df['count'].max() + 1)
# 포아송 분포 확률 질량 함수
poisson_pmf = stats.poisson.pmf(x, mean_count)
# 음이항 분포 매개변수 추정
# 방법 1: 모멘트 방법
r = mean_count**2 / (var_count - mean_count) if var_count > mean_count else 100
p = mean_count / var_count if var_count > mean_count else mean_count / (mean_count + 1)
# 음이항 분포 확률 질량 함수
nb_pmf = stats.nbinom.pmf(x, r, p)
plt.subplot(1, 2, 2)
plt.bar(x, [df['count'].value_counts().get(i, 0) / len(df) for i in x], alpha=0.5, label='실제 데이터')
plt.plot(x, poisson_pmf, 'ro-', label=f'포아송 분포 (λ={mean_count:.2f})')
plt.plot(x, nb_pmf, 'go-', label=f'음이항 분포 (r={r:.2f}, p={p:.2f})')
plt.title('경험적 분포와 이론적 분포 비교')
plt.xlabel('사람 수')
plt.ylabel('확률')
plt.legend()
plt.tight_layout()
# 포아송 분포 적합도 검정 (카이제곱 검정)
observed = np.zeros(df['count'].max() + 1)
for i in range(len(observed)):
observed[i] = df['count'].value_counts().get(i, 0)
expected = len(df) * stats.poisson.pmf(np.arange(len(observed)), mean_count)
# 카이제곱 값 계산
# 기대빈도가 5 미만인 셀은 통합
min_expected = 5
observed_adj = []
expected_adj = []
last_obs = 0
last_exp = 0
for obs, exp in zip(observed, expected):
if exp >= min_expected:
observed_adj.append(obs)
expected_adj.append(exp)
else:
last_obs += obs
last_exp += exp
if last_exp > 0:
observed_adj.append(last_obs)
expected_adj.append(last_exp)
observed_adj = np.array(observed_adj)
expected_adj = np.array(expected_adj)
chi2 = np.sum((observed_adj - expected_adj) ** 2 / expected_adj)
df_chi2 = len(observed_adj) - 1 - 1 # 매개변수 1개 추정 (자유도 = 셀 수 - 1 - 추정된 매개변수 수)
p_value = 1 - stats.chi2.cdf(chi2, df_chi2) if df_chi2 > 0 else None
print("\n포아송 분포 적합도 검정 (카이제곱 검정):")
print(f"카이제곱 값: {chi2:.4f}")
print(f"자유도: {df_chi2}")
print(f"p-value: {p_value if p_value is not None else '자유도가 0 이하'}")
print(f"결론: {'포아송 분포에 적합하지 않음 (p < 0.05)' if p_value is not None and p_value < 0.05 else '포아송 분포에 적합함 (p >= 0.05)' if p_value is not None else '검정 불가 (자유도 부족)'}")
# AIC 및 BIC를 사용한 모델 비교
# 인공 변수를 만들어 모델 적합 (상수 모델)
X = np.ones((len(df), 1))
y = df['count'].values
# 포아송 회귀 모델
poisson_model = Poisson(y, X)
poisson_results = poisson_model.fit(disp=0)
# 음이항 회귀 모델
try:
nb_model = NegativeBinomial(y, X)
nb_results = nb_model.fit(disp=0)
print("\n모델 비교 (AIC 및 BIC):")
print(f"포아송 모델 AIC: {poisson_results.aic:.4f}")
print(f"음이항 모델 AIC: {nb_results.aic:.4f}")
print(f"포아송 모델 BIC: {poisson_results.bic:.4f}")
print(f"음이항 모델 BIC: {nb_results.bic:.4f}")
print(f"더 적합한 모델: {'음이항 분포' if nb_results.aic < poisson_results.aic else '포아송 분포'} (AIC 기준)")
except:
print("\n음이항 회귀 모델을 적합할 수 없습니다. 데이터가 충분하지 않거나 모델이 수렴하지 않습니다.")
# 시간대별 분석
df['hour'] = df['timestamp'].dt.hour
df['time_of_day'] = pd.cut(df['hour'],
bins=[0, 12, 18, 24],
labels=['아침(0-12시)', '오후(12-18시)', '저녁(18-24시)'],
include_lowest=True)
time_stats = df.groupby('time_of_day')['count'].agg(['mean', 'var', 'count'])
time_stats['var/mean'] = time_stats['var'] / time_stats['mean']
print("\n시간대별 통계:")
print(time_stats)
# 시간대별 분포 시각화
plt.figure(figsize=(10, 6))
sns.boxplot(x='time_of_day', y='count', data=df)
plt.title('시간대별 횡단보도 건너는 사람 수 분포')
plt.xlabel('시간대')
plt.ylabel('사람 수')
plt.tight_layout()
plt.show()
# 결론
print("\n분포 분석 결론:")
if var_count > mean_count:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 크므로 과산포(overdispersion)가 존재합니다.")
print("따라서 포아송 분포보다 음이항 분포가 더 적합할 수 있습니다.")
elif var_count < mean_count:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 작으므로 과소산포(underdispersion)가 존재합니다.")
print("이 경우 포아송 분포보다 이항 분포가 더 적합할 수 있습니다.")
else:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})과 거의 같으므로 포아송 분포에 적합합니다.")
모델 비교 (AIC 및 BIC) -> 더 적합한 모델: 음이항 분포 (AIC 기준)
포아송 모델 AIC: 126.3588
음이항 모델 AIC: 111.9646
포아송 모델 BIC: 127.4498
음이항 모델 BIC: 114.1467
모수 추정 방법: 모먼트 방법과 최대 우도 추정법 존재
모멘트 방법
# 데이터의 평균과 분산 계산
mean = np.mean(data)
var = np.var(data, ddof=1) # 표본 분산
# 모수 추정
p = mean / var
r = mean * p / (1 - p)
5. 결론
눈으로 보기엔 포아송 분포가 더 잘 맞는거같은데 AIC기준으로는 음이항 분포가 더 잘맞다고 한다.
카이제곱을 검정을 쓰는 이유는 관측된 빈도와 분포의 기대 빈도와 비교하는 방법이기 떄문
AIC/BIC가 사용된 이유는 다른 통계 모델의 상대적 품질을 비교하는 지표이기 때문(상대적 평가)
음이항 분포의 모수
r(성공 횟수): 1.94
p(성공 확률): 0.32
위 내용을 가지고 95%의 신뢰도로 보행자 수는 11명 -> 엥? 너무 많고 비현실적임, 수집된 데이터중 13,14명인 사례는 출퇴근,점심시간 이였으며 10명 이상 건너는 상황은 잘 발견되지 않음, 50%정도만 넘어도 기다릴 가치는 있다고 생각되어 수정
from scipy.stats import nbinom
# 95% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.95, r, p)
max_count_95
# 11명
# 50% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.5, r, p)
max_count_95
# 3명
누적확률 50% 이상은 3명, 3명 있으면 기다릴 가치가 있다.
6. 회고
GPT는 GP"F"로 바꿔야함. 앞뒤 서론이 너무 길고 공감 투가 너무 많음. Claude는 코딩능력은 좋은데 필요 이상으로 스켈레톤 코드를 제공해주며, 단순 수식 계산도 틀린 점이 자주 발견됨
포아송,음이항 분포 좀 다시 고찰할 필요가 있음. 내 상황에 적절한 분포를 선정하고 데이터를 수집하는게 좋겠다.
균등한 데이터 수집을 위해서 하루종일 혹은 대표성을 띄는 시간에 주기적으로 측정하는게 좋겠다.(평소에 부지런하자!)
Ex) 평일 오전 출근시간 가정 8-9시 집중관찰
본 주제는 매달 한 번씩 호기심을 주제로 분석하는 모임 <월간 데이터 노트>의 결과입니다. 관심이 있으시면 다음 링크를 확인해 보세요!!
분산 설명률: 80~90%의 분산을 설명하는데 필요한 주성분이 원래 특성 수의 절반 이하인가?
모델 성능: PCA적용 후 모델 성능이 유지되거나 향상되었는가?
다중공선성: 원본 특성간에 높은 상관관계가 많이 존재하는가?
절차
결측치, 이상치 처리, 원-핫 인코딩 사전 진행
스케일링
PCA 적용
설명된 분산확인 및 분산비율 시각화
적절한 주성분의 갯수 선택
선택된 주성분의 갯수로 PCA다시 적용
각 주성분이 원래 특성에 얼마나 기여하는지 확인
상위 5개 특성확인
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 데이터는 이미 전처리되었다고 가정 (결측치, 이상치 처리, 원-핫 인코딩 등)
# X는 전처리된 특성 데이터
# 1. 스케일링 (PCA 적용 전 반드시 필요)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 2. PCA 적용
pca = PCA()
X_pca = pca.fit_transform(X_scaled)
# 3. 설명된 분산 비율 확인
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance_ratio = np.cumsum(explained_variance_ratio)
# 4. 설명된 분산 비율 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(explained_variance_ratio) + 1), cumulative_variance_ratio, marker='o', linestyle='-')
plt.xlabel('Number of Principal Components')
plt.ylabel('Cumulative Explained Variance Ratio')
plt.title('Explained Variance by Components')
plt.axhline(y=0.8, color='r', linestyle='--', label='80% Variance Threshold')
plt.axhline(y=0.9, color='g', linestyle='--', label='90% Variance Threshold')
plt.legend()
plt.grid(True)
plt.show()
# 5. 적절한 주성분 개수 선택 (예: 80% 분산 유지)
n_components = np.argmax(cumulative_variance_ratio >= 0.8) + 1
print(f"80% 분산을 유지하기 위한 주성분 개수: {n_components}")
# 6. 선택된 주성분 개수로 PCA 다시 적용
pca_selected = PCA(n_components=n_components)
X_pca_selected = pca_selected.fit_transform(X_scaled)
# 7. 각 주성분이 원래 특성에 얼마나 기여하는지 확인
components_df = pd.DataFrame(
pca_selected.components_,
columns=X.columns
)
# 8. 각 주성분에 대한 기여도가 높은 상위 5개 특성 확인
for i, component in enumerate(components_df.values):
sorted_indices = np.argsort(np.abs(component))[::-1]
top_features = [X.columns[idx] for idx in sorted_indices[:5]]
top_values = [component[idx] for idx in sorted_indices[:5]]
print(f"PC{i+1} 주요 특성:")
for feature, value in zip(top_features, top_values):
print(f" {feature}: {value:.4f}")
print()
# 9. 2D 시각화 (처음 두 개 주성분으로)
plt.figure(figsize=(10, 8))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', alpha=0.8)
plt.title('First Two Principal Components')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.colorbar(label='Target Class')
plt.grid(True)
plt.show()
판단근거1: 설명된 분산비율
하기의 누적분산 비율이 총 컬럼 12개에서 7개를 담아야지 80%를 설명하므로 차원축소가 효과적이지 않다는 판단 가능
# 누적 설명된 분산 비율을 확인
print("각 주성분별 설명된 분산 비율:")
for i, ratio in enumerate(explained_variance_ratio):
print(f"PC{i+1}: {ratio:.4f}")
print("\n누적 설명된 분산 비율:")
for i, ratio in enumerate(cumulative_variance_ratio):
print(f"PC1-PC{i+1}: {ratio:.4f}")
각 주성분별 설명된 분산 비율:
PC1: 0.2566
PC2: 0.1327
PC3: 0.1071
PC4: 0.0823
PC5: 0.0815
PC6: 0.0719
PC7: 0.0608
PC8: 0.0567
PC9: 0.0472
PC10: 0.0388
PC11: 0.0335
PC12: 0.0308
누적 설명된 분산 비율:
PC1-PC1: 0.2566
PC1-PC2: 0.3893
PC1-PC3: 0.4964
PC1-PC4: 0.5787
PC1-PC5: 0.6602
PC1-PC6: 0.7321
PC1-PC7: 0.7930
PC1-PC8: 0.8497
PC1-PC9: 0.8969
PC1-PC10: 0.9357
PC1-PC11: 0.9692
PC1-PC12: 1.0000
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
# 원본 스케일링된 데이터에 대한 교차 검증
clf_original = RandomForestClassifier(random_state=42)
scores_original = cross_val_score(clf_original, X_scaled, y, cv=5, scoring='roc_auc')
# PCA 적용 데이터에 대한 교차 검증
clf_pca = RandomForestClassifier(random_state=42)
scores_pca = cross_val_score(clf_pca, X_pca_selected, y, cv=5, scoring='roc_auc')
print(f"원본 데이터 평균 ROC-AUC: {scores_original.mean():.4f} (±{scores_original.std():.4f})")
print(f"PCA 데이터 평균 ROC-AUC: {scores_pca.mean():.4f} (±{scores_pca.std():.4f})")
다중공선성 검사
특성간 높은 상관관계 발견(0.7 이상) 되면 PCA가 유용할 수 있음
해당 높은 특성들은 독립적인 주성분으로 변환
from sklearn.preprocessing import StandardScaler
from scipy.stats import spearmanr
import seaborn as sns
# 상관관계 행렬 계산
correlation_matrix = X.corr()
# 상관관계 히트맵 표시
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=False, cmap='coolwarm', center=0)
plt.title('Feature Correlation Matrix')
plt.tight_layout()
plt.show()
# 높은 상관관계(0.7 이상)를 가진 특성 쌍 찾기
high_corr_pairs = []
for i in range(len(correlation_matrix.columns)):
for j in range(i+1, len(correlation_matrix.columns)):
if abs(correlation_matrix.iloc[i, j]) > 0.7:
high_corr_pairs.append((correlation_matrix.columns[i], correlation_matrix.columns[j], correlation_matrix.iloc[i, j]))
print("높은 상관관계를 가진 특성 쌍:")
for pair in high_corr_pairs:
print(f"{pair[0]} - {pair[1]}: {pair[2]:.4f}")
from imblearn.under_sampling import RandomUnderSampler
SMOTE 클래스 대신 사용하면 됨
오버샘플링
소수 클래수 복제
SMOTE
클래스 가중치
모델이 직접 적용, 데이터가 작아 오버샘플링이 부담스러울 때
class_weight = 'balanced' (Logistic, RF, SVC, GBC 등 지원)
SMOTE 코드
from imblearn.over_sampling import SMOTE
X = df.drop(columns=['Category', 'target'])
y = df['target']
smote = SMOTE(random_state = 42)
X_resampled, y_resampled = smote.fit_resample(X, y)
2.4. 평가지표 metrics
메트릭
설명
사용시점
Precision
Positive 예측 한 것 중 실제 Positive
FP가 중요한 문제(스팸 필터처럼 잘못 걸러내면 안됄 때)
Recall(Sensitivity)
실제 Positive중 Positive로 예측한 비율
FN가 중요힌 문제(암 진단처럼 놓지면 안될 때)
F1-score
Precision과 Recall의 조화 평균
둘 다 중요할 때
AUC-ROC
다양한 threshhold에서 TPR vs FPR 곡선의 아래 면적
전체적인 분류 성능이 중요할 때
PR-AUC
Precision vs Recall 곡선의 면적
불균형이 매우 심하여 Positive가 극소수 일때
메트릭 코드 모음
# 분류모델
from sklearn.metrics import accuracy_score, precision_score,recall_score
from sklearn.metrics import f1_score, confusion_matrix, roc_auc_score, classification_report
# 회귀모델
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.metrics import r2_score, adjusted_rand_score
# 비지도학습 군집
from sklearn.metrics import silhouette_score
classification_report 읽기
support: 해당 클래스의 샘플 수
macro avg: 일반적인 지표
weight avg: 샘플수 가중치를 더한 지표
ROC Curve
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, roc_auc_score
# 모델 학습 (예시)
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
# 테스트 데이터에 대한 예측 확률
y_pred_proba = model.predict_proba(X_test)[:, 1] # 양성 클래스(1)에 대한 확률
# ROC 커브 계산
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
# AUC 계산
roc_auc = roc_auc_score(y_test, y_pred_proba)
# ROC 커브 그리기
plt.figure()
plt.plot(fpr, tpr, color='blue', label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='gray', linestyle='--') # 대각선 기준선
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()
2.5. 머신러닝 스켈레톤 코드 - 분류
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
X = df.drop(columns = ['Kwh8','date','datetime'])
y = df['Kwh8']
X_train, X_test, y_train, y_test = train_test_split(X,y, random_state= 42, stratify= y, test_size= 0.2)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
# 모델 리스트와 이름
models = [
('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
('RandomForest', RandomForestClassifier(random_state=42)),
('SVM', SVC(random_state=42))
]
# 모델 학습 및 평가
for model_name, model in models:
model.fit(X_train, y_train)
# 예측 수행
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
# 성능 평가 (train)
print(f'Train - {model_name} Model')
print(classification_report(y_train, y_pred_train))
# 성능 평가 (test)
print(f'Test - {model_name} Model')
print(classification_report(y_test, y_pred_test))
2.5. 머신러닝 스켈레톤 코드 - 분류(cv)
from sklearn.metrics import classification_report, accuracy_score
from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold
# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, stratify=y, test_size=0.3)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
# 교차 검증 설정
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 모델 리스트와 이름
models = [
('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
('RandomForest', RandomForestClassifier(random_state=42)),
('SVM', SVC(random_state=42))
]
# 모델 학습 및 평가
for model_name, model in models:
# 교차 검증 수행
cv_scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
print(f'\n{model_name} Cross-Validation:')
print(f'CV Scores: {cv_scores}')
print(f'Mean CV Score: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')
# 전체 훈련 데이터로 모델 학습
model.fit(X_train, y_train)
# 예측 수행
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
# 성능 평가 (train)
print(f'\nTrain - {model_name} Model')
print(classification_report(y_train, y_pred_train))
# 성능 평가 (test)
print(f'\nTest - {model_name} Model')
print(classification_report(y_test, y_pred_test))
print('-' * 70)
# 다양한 교차 검증 방법 비교 (선택적)
print("\n다양한 교차 검증 방법 비교 (RandomForest 모델):")
cv_methods = {
'KFold (k=5)': KFold(n_splits=5, shuffle=True, random_state=42),
'StratifiedKFold (k=5)': StratifiedKFold(n_splits=5, shuffle=True, random_state=42),
'StratifiedKFold (k=10)': StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
}
rf_model = RandomForestClassifier(random_state=42)
for cv_name, cv_method in cv_methods.items():
cv_scores = cross_val_score(rf_model, X_train, y_train, cv=cv_method, scoring='accuracy')
print(f'{cv_name}: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')
2.6. 종속변수에 영향 해석하기
로지스틱 회귀 베타계수 추정
양수는 변수가 증가감에 따라 간염 화률의 증가, 음수는 감소
로즈오즈 해석: 계수가 0.5이면 1단위 증가할 때 간염 발생 로그오즈 0.05 증가
오즈비 해석: exp(계수)값으로 계산하며 1.5이면 변수 1단위 증가할 때 간염 발생 오즈 50% 증가 0.8이면 20% 감소, 1이면 영향 미치지 않음
# 로지스틱 회귀 모델 학습
log_model = LogisticRegression(solver='liblinear')
log_model.fit(X_train, y_train)
# 계수 및 변수명 가져오기
coefficients = log_model.coef_[0]
feature_names = X.columns
# 계수 크기순으로 정렬하여 출력
coef_df = pd.DataFrame({'Feature': feature_names, 'Coefficient': coefficients})
coef_df = coef_df.sort_values('Coefficient', ascending=False)
print("로지스틱 회귀 계수 (양수=간염 위험 증가, 음수=간염 위험 감소):")
print(coef_df)
# 오즈비(Odds Ratio) 계산 - 해석이 더 직관적임
coef_df['Odds_Ratio'] = np.exp(coef_df['Coefficient'])
print("\n오즈비 (1보다 크면 위험 증가, 1보다 작으면 위험 감소):")
print(coef_df[['Feature', 'Odds_Ratio']])
Feature Importance
# Random Forest 모델 학습
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train)
# 특성 중요도 추출 및 시각화
importances = rf_model.feature_importances_
indices = np.argsort(importances)[::-1]
feature_importance_df = pd.DataFrame({
'Feature': feature_names[indices],
'Importance': importances[indices]
})
plt.figure(figsize=(10, 6))
sns.barplot(x='Importance', y='Feature', data=feature_importance_df[:10])
plt.title('Top 10 Important Features for Hepatitis Prediction (Random Forest)')
plt.tight_layout()
plt.show()
SHAP value
게임 이론이 기반, 각 특성이 모델 예측에 미치는 기여도를 배분
양수면 해당 특성이 예측 확률을 증가시키는 방향으로 기여, 음수는 감소시키는 방향으로 기여
import tensorflow as tf
import numpy as np
import os
# TensorFlow 1.x 환경에서 Eager Execution을 비활성화
tf.compat.v1.disable_eager_execution()
# 데이터 생성 (이진 분류용)
X_data = np.random.rand(100, 2) # 100개의 샘플, 2개의 특성
y_data = np.random.randint(2, size=(100, 1)) # 100개의 이진 타겟 값 (0 또는 1)
# 모델 정의 (Sequential API)
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(10, input_dim=2, activation='relu')) # 은닉층
model.add(tf.keras.layers.Dense(1, activation='sigmoid')) # 이진 분류를 위한 sigmoid 출력층
# 모델 컴파일
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 체크포인트 설정
checkpoint_path = "training_checkpoints/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)
# 체크포인트 콜백 설정
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
save_weights_only=True,
verbose=1)
# 모델 학습 (체크포인트 콜백 적용)
model.fit(X_data, y_data, epochs=10, batch_size=10, callbacks=[cp_callback])
# 모델 저장 (전체 모델 저장)
model.save('saved_model/my_model')
# 모델 불러오기
new_model = tf.keras.models.load_model('saved_model/my_model')
# 불러온 모델 평가
loss, accuracy = new_model.evaluate(X_data, y_data)
print(f"불러온 모델 정확도: {accuracy:.4f}")
# 체크포인트에서 가중치만 불러오기
new_model.load_weights(checkpoint_path)
# 불러온 체크포인트의 가중치로 다시 평가
loss, accuracy = new_model.evaluate(X_data, y_data)
print(f"체크포인트에서 불러온 모델 정확도: {accuracy:.4f}")
# 분류 예측
predictions = new_model.predict(X_data)
print(f"예측값 (0 또는 1로 변환): \n{(predictions > 0.5).astype(int)}")
3. 머신러닝 - 비지도학습
3.1. Kmeans
import pandas as pd
from sklearn.cluster import KMeans
# KMeans 설정 및 클러스터링 수행
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(data)
# 클러스터 결과를 데이터프레임에 추가
data['cluster'] = kmeans.labels_
# 결과 확인
print(data.head())
3.2. DBSCAN
import pandas as pd
from sklearn.cluster import DBSCAN
# DBSCAN 설정 및 클러스터링 수행
dbscan = DBSCAN(eps=0.5, min_samples=5)
dbscan.fit(data)
# 클러스터 결과를 데이터프레임에 추가
data['cluster'] = dbscan.labels_
# 결과 확인
print(data.head())
3.3. Elbow method
# 클러스터 수에 따른 inertia (군집 내 거리 합) 계산
inertia = []
K = range(1, 11) # 1~10개의 클러스터를 테스트
for k in K:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(data)
inertia.append(kmeans.inertia_)
# Elbow Method 그래프 시각화
plt.figure(figsize=(8, 6))
plt.plot(K, inertia, 'bx-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Inertia')
plt.title('Elbow Method For Optimal k')
plt.show()
3.4. 실루엣 계수
from sklearn.metrics import silhouette_score
# 실루엣 계수를 저장할 리스트
silhouette_avg = []
# 2~10개의 클러스터를 테스트
K = range(2, 11)
for k in K:
kmeans = KMeans(n_clusters=k, random_state=42)
labels = kmeans.fit_predict(data)
silhouette_avg.append(silhouette_score(data, labels))
# 실루엣 계수 시각화
plt.figure(figsize=(8, 6))
plt.plot(K, silhouette_avg, 'bx-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score For Optimal k')
plt.show()
지난장 배치에서는 유닉스의 배치처리 방식과 맵리듀스를 기반으로 한 분산파일 시스템에대해서 알아보았습니다. 많이 혼란스럽고 어려운 장이였는데요, 이번 장에서는 스트림 처리에 대해서 알아봅니다. 배치를 기반으로 하는 설명이 나온다고 해서 많이 어려울 줄 알았는데 일부 읽을만한 내용들이 있어서 재밌었습니다.
목차
신규개념(New)
개념
설명
스트림(stream)
시간 흐름에 따라 점진적으로 생산된 데이터
레코드(record)
이벤트, 특정 시점에 일어난 사건에 대한 세부사항을 포함하는 작고 독립된 불변 객체
생산자(producer)
publisher, sender
소비자(consumer)
subscrier, recipient
트리거(trigger)
테이블에 대한 이벤트에 반응해 자동으로 실행되는 작업
메시징 시스템(Messaging system)
로그 데이터, 이벤트 메시지 등 API로 호출할 때 보내는 데이터를 처리하는 시스템
윈도우(window)
집계하는 시간의 간격
1. 배치와 스트림 처리의 차이
배치
스트림
정의
입력으로 파일 집합을 읽어 출력으로 새로운 파일 집합을 생성하는 기술
연속적으로 발생하는 데이터를 실시간 혹은 준 실시간으로 처리하는 기술
활용예시
검색 색인, 추천시스템, 분석
실시간 로그 분석, 거래 모니터링, 실시간 알림 시스템 등
출력
파생 데이터(요약 데이터ㅡ 결과 리포트 등)
실시간으로 처리된 이벤트 결과(알림 등)
특징
끝이 있음(작업 단위가 명확)
영원히 끝나지 않음(데이터 흐름이 지속적으로 발생)
2. 메시징 시스템
2.1 메시징 시스템 기본
새로운 이벤트에 대해서 소비자에게 알려주고 쓰이는 일반적인 방법은 메시징 시스템(messaging system) 이다. 메시징 시스템을 이해하기 위해서 다음 블로그에서 글 발췌했다.
ADP 시험을 준비하기 위하여 정리했던 방법론과 코드를 저장합니다.빅분기는 범위가 더 좁지만 공통된 부분이 많습니다. 선형계획법이나 기타 내용은 저도 몰라서 못넣었습니다 🤣 잘못된 내용이나 모호한 내용, 추가되어야하는 내용이 있으면 지적해주세요! 다들 시험에 합격하시길 바라요~
# 일반모듈
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import os
import datetime as dt
# 머신러닝 - 전처리
from sklearn.preprocessing import StandardScaler, MinMaxScaler,
LabelEncoder, OneHotEncoder, RobustScaler
from sklearn.impute import SimpleImputer, KNNImputer
from imblearn.over_sampling import SMOTE, RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score,
KFold, StratifiedKFold
# 머신러닝 - 모델
from sklearn.linear_model import LogisticRegression, RidgeClassifier, LinearRegression,
Lasso, Ridge, ElasticNet
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier,
RandomForestRegressor, GradientBoostingRegressor
from sklearn.ensemble import VotingClassifier
from sklearn.svm import SVC, SVR
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
# 머신러닝 - 비지도
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.decomposition import PCA
# 머신러닝 - 평가
from sklearn.metrics import (classification_report, confusion_matrix,
roc_auc_score, precision_recall_curve, auc,
mean_squared_error, mean_absolute_error, r2_score, accuracy_score,
precision_score, recall_score, f1_score)
from sklearn.metrics.pairwise import cosine_similarity
# 시각화
import matplotlib.pyplot as plt
import seaborn as sns
# 통계 - scipy
import scipy.stats as stats
from scipy.stats import ttest_ind, ttest_rel, f_oneway, chi2_contingency,
mannwhitneyu, wilcoxon, pearsonr, spearmanr, normaltest
# 통계 - statsmodels
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.multicomp import pairwise_tukeyhsd
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.stats.diagnostic import het_breuschpagan
# 시계열
from statsmodels.tsa.stattools import adfuller, acf, pacf, kpss
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# pmdarima (자동 ARIMA)
from pmdarima import auto_arima
한글폰트 설정(선택사항)
import matplotlib.pyplot as plt
import platform
def set_matplotlib_font():
system = platform.system()
if system == "Windows":
plt.rc('font', family='Malgun Gothic')
elif system == "Darwin": # macOS
plt.rc('font', family='AppleGothic')
elif system == "Linux":
plt.rc('font', family='NanumGothic')
else:
print("Unknown system. Please set font manually.")
plt.rcParams['axes.unicode_minus'] = False
# 폰트 설정 함수 호출
set_matplotlib_font()
1.1. datetime
datetime
│
├── datetime # 날짜와 시간을 모두 처리하는 클래스
│ ├── now() # 현재 날짜와 시간을 반환
│ ├── strptime() # 문자열을 datetime 객체로 변환
│ ├── strftime() # datetime 객체를 문자열로 변환
│ ├── year,month, day # 년도, 월, 일 추출
│ ├── hour,minute, second # 시간, 분, 초 추출
│ ├── date(), time() # 날짜 (년, 월, 일)만, 시간 (시, 분, 초)만 반환
│ ├── replace() # 특정 날짜/시간 값을 변경하여 새로운 datetime 객체 반환
│ ├── weekday() # 요일 반환 (월: 0, 일: 6)
│ └── combine() # 날짜와 시간을 결합하여 새로운 datetime 객체 생성
│
├── date # 날짜만 처리하는 클래스
│ ├── today() # 오늘 날짜 반환
│ ├── fromisoformat() # ISO 형식의 문자열에서 date 객체 생성
│ ├── year,month, day # 년도, 월, 일 추출
│
├── time # 시간만 처리하는 클래스
│ ├── hour,minute, second # 시간, 분, 초 추출
│ └── microsecond # 마이크로초 추출
│
├── timedelta # 두 날짜 또는 시간 간의 차이를 계산하는 클래스
│ ├── days # 차이 나는 일수
│ ├── seconds # 차이 나는 초
│ └── total_seconds() # 총 차이 시간을 초 단위로 반환
│
└── timezone # 시간대 정보를 다루는 클래스
├── utc # UTC 시간대 객체
└── tzinfo # 사용자 정의 시간대 설정을 위한 클래스
1.2. numpy==1.21.6
numpy
│
├── 기본 통계 함수
│ ├── mean() # 데이터의 평균값 계산
│ ├── median() # 데이터의 중앙값 계산
│ ├── std() # 데이터의 표준편차 계산
│ ├── var() # 데이터의 분산 계산
│ ├── sum() # 데이터의 합계 계산
│ ├── prod() # 데이터의 곱 계산
│
├── 퍼센타일 및 백분위 함수
│ ├── percentile() # 데이터의 특정 퍼센타일 값 계산
│ ├── quantile() # 데이터의 특정 분위 값 계산
│
├── 최소값/최대값 관련 함수
│ ├── min() # 데이터의 최소값 반환
│ ├── max() # 데이터의 최대값 반환
│ ├── argmin() # 최소값의 인덱스 반환
│ ├── argmax() # 최대값의 인덱스 반환
│
├── 데이터 생성 및 처리 함수
│ ├── histogram() # 데이터의 히스토그램 계산
│ ├── unique() # 데이터에서 고유 값 반환
│ ├── bincount() # 정수 배열의 값의 빈도 계산
│
├── 랜덤 데이터 생성 (통계적 실험 시 사용 가능)
│ ├── random.randn() # 표준 정규분포를 따르는 랜덤 값 생성
│ ├── random.normal() # 정규분포를 따르는 랜덤 값 생성
│ ├── random.randint() # 정수 범위에서 랜덤 값 생성
│ ├── random.choice() # 데이터에서 랜덤 샘플 추출
1.3. scipy==1.7.3
scipy
│
├── stats # 통계 분석과 확률 분포 관련 함수 제공
│ ├── norm # 정규분포 관련 함수 (PDF, CDF, 랜덤 샘플링 등)
| |── uniform # 균등분포
| |── bernoulli # 베르누이 분포
| |── binom # 이항분포
│ ├── ttest_ind # 독립 두 표본에 대한 t-검정
│ ├── ttest_rel # 대응표본 t-검정
│ ├── mannwhitneyu # Mann-Whitney U 비모수 검정
│ ├── chi2_contingency # 카이제곱 독립성 검정
│ ├── shapiro # Shapiro-Wilk 정규성 검정
│ ├── kstest # Kolmogorov-Smirnov 검정 (분포 적합성 검정)
│ ├── probplot # Q-Q plot 생성 (정규성 시각화)
│ ├── pearsonr # Pearson 상관계수 계산
│ ├── spearmanr # Spearman 순위 상관계수 계산
│ └── describe # 기술 통계량 제공 (평균, 표준편차 등)
│
├── optimize # 함수 최적화 및 곡선 피팅 관련 모듈
│ ├── minimize # 다변수 함수의 최소값을 찾는 최적화 도구
│ ├── curve_fit # 비선형 곡선 피팅 (최소제곱법 기반)
│ └── root # 방정식의 근을 찾는 도구
│
├── linalg # 선형대수 관련 함수
│ ├── inv # 행렬의 역행렬 계산
│ ├── det # 행렬식 계산
│ └── svd # 특이값 분해(SVD) 수행
│
├── interpolate # 데이터 보간 관련 함수
│ ├── interp1d # 1차원 선형 보간 함수
│ └── griddata # 다차원 보간 수행 (비정형 데이터에 사용)
│
└── special # 특수 함수 (감마 함수, 베타 함수 등)
├── gamma # 감마 함수
└── beta # 베타 함수
pandas
│
├── melt: pivot 형태를 다시바꾸기
│
├── DataFrame # 2차원 데이터 구조, 테이블 형태의 데이터 관리
│ ├── groupby # 데이터프레임 그룹핑
│ ├── merge # SQL 스타일로 데이터프레임 병합
│ ├── join # 데이터프레임 조인 (인덱스를 기준)
│ ├── pivot_table # 피벗 테이블 생성
│ ├── apply # 사용자 정의 함수를 데이터프레임에 적용
│ ├── isnull # 결측치 여부 확인
│ ├── fillna # 결측치 대체
│ └── drop # 행 또는 열 삭제
│
├── Series # 1차원 데이터 구조, 배열 형태의 데이터 관리
│ ├── value_counts # 고유 값의 빈도수 반환
│ └── unique # 고유 값 반환
│
├── time_series # 시계열 데이터 처리 도구
│ ├── to_datetime # 문자열을 날짜 형식으로 변환
│ ├── resample # 시계열 데이터 리샘플링
│ ├── shift # 데이터를 앞이나 뒤로 이동
│ └── rolling # 이동 평균 등 롤링 윈도우 계산
│
└── plotting # 데이터 시각화 도구
├── plot # 라인 플롯, 기본 시각화 함수
├── hist # 히스토그램 생성
├── boxplot # 박스 플롯 생성
└── scatter # 산점도 생성
1.5. statsmodel==0.13.2
statsmodels
│
├── api # 주요 모듈에 접근하기 위한 API 제공
│
├── stats # 통계적 테스트 및 유틸리티
│ ├── diagnostic # 모델 진단 도구 (자기상관, 이분산성 등)
│ ├── stattools # 통계 도구 (자기상관 함수, 부분자기상관 함수 등)
│ ├── multivariate # 다변량 통계 분석
│ ├── anova # 분산 분석(ANOVA) 관련 함수
│ │ ├── anova_lm # 선형 모델에 대한 ANOVA 테이블
│ │ └── multicomp # 다중 비교 방법
│ └── nonparametric # 비모수 통계 검정 (KDE, 커널 회귀 등)
│
├── duration # 생존 분석(Survival Analysis) 관련 모듈
│ ├── hazard_regression # 위험 회귀 모델
│ │ ├── cox # Cox 비례 위험 모델
│ │ └── phreg # 비례 위험 회귀 모델
│ ├── survfunc # 생존 함수 추정 (Kaplan-Meier 등)
│ │ ├── kaplan_meier # 카플란-마이어 추정량
│
├── tsa # 시계열 분석(Time Series Analysis) 관련 모듈
│ ├── arima # ARIMA 모델 구현
│ ├── statespace # 상태 공간 모델 (State Space Models)
│ ├── vector_ar # 벡터 자기회귀(VAR) 모델
│ ├── seasonal # 계절성 분해 (SARIMAX 등)
│ └── filters # 칼만 필터, HP 필터 등
from statsmodels.stats.proportion import proportion_confint
# 성공 횟수와 전체 시행 횟수
x = np.sum(data) # 성공 횟수
n = len(data) # 전체 시행 횟수
lower_bound, upper_bound = proportion_confint(success_count, n, alpha=alpha, method='normal')
print(f"{confidence_level*100}% 신뢰구간: ({lower_bound}, {upper_bound})")
이표본 t검정일 때 정규성 & 등분산 검정을 진행하게 되는데, 등분산일 경우 두 표본의 분산을 하나로 합쳐서 추정하는 "합동 분산"활용이 가능해짐. 이는 검정통계량의 분산을 하나로 합치게 되고 좀 더 정확한 추정이 가능하게함. 하지만 등분산이 만족되지 않은 경우 비모수 검정인 맨-휘트니를 진행함
# 독립표본 t 검정
from scipy.stats import ttest_ind,ttest_rel
# 등분산성을 만족하는 경우
statistic, p_value = ttest_ind(data1, data2, equal_var=True)
# 등분산성을 만족하지 않는 경우
# statistic, p_value = ttest_ind(data1, data2, equal_var=False)
#대응표본 t검정
# statistic, p_value = ttest_rel(data_before, data_after)
print(statistic, p_value)
분산 분석
목적: 그룹간 분산과그룹내 분산을 비교
$H_0:$ 모든 그룹의 평균값이 같다.
$H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
import pandas as pd
import numpy as np
# 그룹별 데이터 생성 (예시 데이터)
np.random.seed(123)
group_A = np.random.normal(loc=50, scale=5, size=30)
group_B = np.random.normal(loc=55, scale=5, size=30)
group_C = np.random.normal(loc=60, scale=5, size=30)
# 데이터 프레임 생성
df = pd.DataFrame({
'score': np.concatenate([group_A, group_B, group_C]),
'group': ['A'] * 30 + ['B'] * 30 + ['C'] * 30
})
import statsmodels.api as sm
from statsmodels.formula.api import ols
# ANOVA 모델 생성
model = ols('score ~ C(group)', data=df).fit()
# 분산분석표 생성
anova_table = sm.stats.anova_lm(model, typ=2)
# 결과 출력
print(anova_table)
import statsmodels.api as sm
from statsmodels.formula.api import ols
# 이원분산분석 모델 생성 (교호작용 포함)
model = ols('값 ~ C(요인1) * C(요인2)', data=df).fit()
# 분산분석표 생성
anova_table = sm.stats.anova_lm(model, typ=2)
# 결과 출력
print(anova_table)
Welch Anova
목적: 그룹 간의 등분산성이 가정이 필요없는 경우 사용
$H_0:$ 모든 그룹의 평균이 같다.
$H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.api as sm
from statsmodels.formula.api import ols
# 기술 통계량 확인
print(data.groupby('group').agg(['mean', 'std', 'count']))
# 등분산성 검정 (Levene's test)
levene_stat, levene_p = stats.levene(group1, group2, group3)
print(f"\nLevene's test: statistic={levene_stat:.4f}, p-value={levene_p:.4f}")
if levene_p < 0.05:
print("The variances are significantly different. Welch ANOVA is appropriate.")
else:
print("The variances are not significantly different. Standard ANOVA can be used.")
# Welch ANOVA 수행
welch_stat, welch_p = stats.f_oneway(group1, group2, group3, welch=True)
print(f"\nWelch ANOVA: F-statistic={welch_stat:.4f}, p-value={welch_p:.4f}")
if welch_p < 0.05:
print("Reject null hypothesis: At least one group mean is different.")
else:
print("Fail to reject null hypothesis: No significant difference between group means.")
# 사후 검정 (Games-Howell post-hoc test)
from statsmodels.stats.multicomp import pairwise_tukeyhsd
# 참고: statsmodels에는 Games-Howell 검정이 직접 구현되어 있지 않아 Tukey HSD를 대신 사용
# 실제 연구에서는 Games-Howell 테스트 사용 권장 (등분산 가정 필요 없음)
tukey_results = pairwise_tukeyhsd(data['value'], data['group'], alpha=0.05)
print("\nPost-hoc test (Tukey HSD, note: Games-Howell would be better for unequal variances):")
print(tukey_results)
4. 통계검정 - 비모수적 방법
4.1. 비모수방법 표 정리
데이터가 특정 분포를 따르지 않거나 데이터 양이 적어 분포가정을 만족시키기 어려울때 사용. 데이터의 순위나 중앙값을 사용
from scipy.stats import kruskal
# 예시 데이터 (세 그룹의 독립 표본)
group1 = [10, 12, 14, 16, 18]
group2 = [15, 17, 19, 21, 23]
group3 = [24, 26, 28, 30, 32]
# Kruskal-Wallis H 검정
stat, p_value = kruskal(group1, group2, group3)
4.6. 맥니마 검정
목적: 이항 데이터에서 전후 또는 대응된 두 집단의 비율 변화
가정: 데이터는 binary, 대응 표본 존재, 표본크기 충분히 커야함
$H_0$: 두 대응된 이항 분포간 변화 없음
$H_1$: 두 대응된 이항 분포간 변화 있음
import numpy as np
from statsmodels.stats.contingency_tables import mcnemar
# 2x2 분할표 생성 (a, b, c, d)
# 예시 데이터: (성공/실패 전후)
# [[성공 전/성공 후, 성공 전/실패 후],
# [실패 전/성공 후, 실패 전/실패 후]]
table = np.array([[40, 10], # 성공 전/후
[5, 45]]) # 실패 전/후
# 맥니마 검정 수행
result = mcnemar(table, exact=True) # exact=True: 정확한 검정 수행 (표본 크기가 작을 경우)
# 결과 출력
print('맥니마 검정 통계량:', result.statistic)
print('p-value:', result.pvalue)
4.7. 피셔의 정확검정
목적: 작은 표본에서 두 범주형 변수간의 독립성의 검정
가정: 각셀 기대빈도(N≤5) 이항 분포 또는 범주형 자료료 표현됨, 행과 열의 합은 고정
$H_0$: 두 변수는 독립적이다.
$H_1$: 두 변수는 독립적이지 않다.
import numpy as np
from scipy import stats
# 2x2 분할표 데이터 (예시)
# [[그룹 A, 그룹 B],
# [그룹 A, 그룹 B]]
table = np.array([[8, 2], # 결과 X
[1, 5]]) # 결과 Y
# 피셔의 정확검정 수행
odds_ratio, p_value = stats.fisher_exact(table, alternative='two-sided')
# 결과 출력
print('오즈비:', odds_ratio)
print('p-value:', p_value)
4.8. 프리드먼 검정
목적: 반복 측정된 세개 그룹 간의 차이를 비교하는 검정
가정: 연속형 or 순위형 데이처 사용, 각 그룹은 동일한 주제/대상에 여러번 측정된 결과
$H_0$ : 세 개 이상의 관련된 그룹 간의 차이가 없다.
$H_1$: 적어도 하나의 그룹에서 다른 그룹과 유의미한 차이가 있다.
import numpy as np
from scipy.stats import friedmanchisquare
# 예시 데이터: 세 개의 처리에 대한 측정값
# 동일한 대상(피험자)에 대해 세 번의 처리를 반복적으로 측정한 값
# 행은 피험자, 열은 각각의 처리
data1 = [10, 20, 30, 40] # 처리 1
data2 = [12, 21, 29, 35] # 처리 2
data3 = [14, 22, 27, 37] # 처리 3
# 프리드먼 검정 수행
statistic, p_value = friedmanchisquare(data1, data2, data3)
# 결과 출력
print('프리드먼 검정 통계량:', statistic)
print('p-value:', p_value)
4.9. 부호검정
목적: 관련된 두 그룹 간의 차이를 평가하는 검정, 중앙 값의 차이를 평가
가정: 대응된 표본
$H_0:$ 두 그룹간 중앙값 차이가 없다.
$H_1$ : 두 그룹간 중앙값 차이가 있다.
import numpy as np
from scipy import stats
# 예시 데이터: 두 그룹에 대한 대응된 표본 (처치 전후 데이터)
before = [85, 70, 90, 80, 65, 75, 95, 70] # 처리 전
after = [80, 65, 88, 82, 63, 78, 92, 72] # 처리 후
# 부호검정 수행
result = stats.binom_test((before > after).sum(), n=len(before), p=0.5, alternative='two-sided')
# 결과 출력
print('p-value:', result)
4.10. 코크란 q검정
목적: 세개 이상의 이항 처리에 대한 대응 표본의 비율의 차이를 비교하기 위한 검정
가정: 데이터는 binary, 대응된 표본
$H_0$: 세 개 이상의 처리 간에 차이가 없다.
$H_1$ : 적어도 하나의 처리에서는 다른 처리와 유의미한 차이가 있다.
import numpy as np
import pandas as pd
from statsmodels.sandbox.stats.runs import cochrans_q
# 예시 데이터: 세 가지 처리에 대한 이항형 결과 (성공/실패)
# 각 행은 개별 피험자, 각 열은 다른 처리
data = pd.DataFrame({
'treatment1': [1, 0, 1, 1, 0], # 처리 1 결과 (성공: 1, 실패: 0)
'treatment2': [0, 1, 1, 1, 0], # 처리 2 결과
'treatment3': [1, 0, 1, 0, 1], # 처리 3 결과
})
# 코크란 Q 검정 수행
statistic, p_value = cochrans_q(data)
# 결과 출력
print('코크란 Q 검정 통계량:', statistic)
print('p-value:', p_value)
import statsmodels.stats.api as sms
import statsmodel.api as sm
model = sm.OLS(y,X).fit
static , pvalue, f_static , f_pv = sms.het_breuschpagan(model.resid,model.model.exog)
정규성 가정: 잔차항이 정규분포를 따름
QQ플랏
sharpiro-wilk 검정
$H_0:$ 정규성을 따른다.
$H_1:$ 정규성을 따르지 않는다.
from scipy.stats import shapiro
static, pvalue = shapiro(model.resid)
전체 코드
# 1. 잔차 vs 예측값 : 잔차의 등분산성 가정
# 2. 잔차의 히스토그램: 잔차의 정규성 가정
# 3. QQPLOT: 잔차의 정규성 가정
# 4. 다중공선성: 독립변수의 독립성 가정
# 5. 더빈왓슨 검정: 잔차의 독립성 가정
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.stats.outliers_influence import variance_inflation_factor as VIF
def review_lr(X,y, add_const = True):
if add_const:
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
else:
model = sm.OLS(y, X).fit()
residuals = model.resid
fitted_values = model.fittedvalues
plt.figure(figsize = (10,6))
#1. 잔차 vs 예측값
plt.subplot(2,2,1)
sns.residplot(x = fitted_values, y = residuals, lowess = True)
plt.title('Residual vs Fitted')
# 2. 잔차의 히스토그램
plt.subplot(2,2,2)
sns.histplot(residuals, kde = True)
plt.title('Residuals Distribution')
# 3. 잔차의 QQplot
plt.subplot(2,2,3)
sm.qqplot(residuals, line = '45', fit = True, ax=plt.gca())
plt.title('QQ plot)')
# 4. 다중공선성
plt.subplot(2,2,4)
vif_data = pd.DataFrame()
vif_data['VIF'] = [VIF(X.values,i) for i in range(X.shape[1])]
vif_data['Feature'] = X.columns
sns.barplot(x = 'VIF', y = 'Feature', data = vif_data)
plt.title('VIF')
plt.show()
# 5. 잔차의 독립성 검정: 더빈왓슨검정
# -> 기본적으로 sm.OLS(Y,X).summary()에서 제공
dw_stat = sm.stats.durbin_watson(residuals)
print(f"Durbin-Watson statistic: {dw_stat:.3f}")
'''
0에 가까우면 잔차의 양의 자기 상관성
2에 가까우면 잔차의 독립성 가정 만족
4에 가까우면 잔차가 음의 자기 상관성을 가짐
'''
#6. 잔차의 정규성 검정: # Shapiro-Wilk 검정
from scipy import stats
residuals = model.resid
shapiro_test = stats.shapiro(residuals)
print(f"Shapiro-Wilk Test Statistic: {shapiro_test.statistic:3f}")
print(f"p-value: {shapiro_test.pvalue:3f}")
# 결과 해석
if shapiro_test.pvalue > 0.05:
print("p-value가 0.05보다 큼: 잔차가 정규분포를 따를 가능성이 높음 (귀무가설을 기각할 수 없음).")
else:
print("p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).")
return model.summary()
y = df['MEDV']
X = df.drop(columns=['MEDV'])
review_lr(X,y)
Durbin-Watson statistic: 1.246
Shapiro-Wilk Test Statistic: 0.917579
p-value: 0.000000
p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).
선형회귀 다중공선성 관리 방법
상관관계가 0.7 이상인 것 기준
도메인지식을 바탕으로 중요하다고 생각되는 변수를 유지
다중공선성이 높은 변수들이 많다면 차원 축소 진행
corr_df = df.drop(columns =['MEDV']).corr().unstack().reset_index()
corr_df.columns =['col1','col2','corr']
corr_df =corr_df[corr_df['col1'] != corr_df['col2']]
.sort_values('corr').reset_index(drop=True)[::2].reset_index(drop=True)
display(corr_df)
print('''
강한 양의 상관관계 및 강한 음의 상관관계를 가지는 컬럼들이 다수 존재한다. 다중공선성이 발생할 여지가 크기에 이를 해결하기 위해 두가지 접근을 진행한다.
1. vif값을 기준으로 변수를 제거하는 방식
2. pca를 이용해서 변수간 선형관계를 줄인 축소된 차원으로 분석을 진행
''')
VIF
$VIF_i = \frac{1}{1 - R_i^2}$
$R^{2}_{i}$: VIF를 측정하고 싶은 독립변수를 종속변수로 놓고, 나머지 독립변수를 이용해 회귀분석을 진행한 결정계수
즉 결정계수가 90%이 된다는 것은 VIF가 10이 된다는 것
from statsmodels.stats.outliers_influence import variance_inflation_factor
df_vif = df.drop(columns =['MEDV'])
vif = pd.DataFrame()
vif['VIF Factor'] = [variance_inflation_factor(df_vif.values,i) for i in range(df_vif.shape[1])]
vif['features'] = df_vif.columns
display(vif)
방법1) 변수 제거
방법2) 차원축소(PCA)
거리기반의 알고리즘이기 때문에 정규화 필요
전체 변수를 넣어도, 부분변수를 넣어도되나, 전자의 경우 해석력이 많이 손상됌
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
sc =StandardScaler()
scaled_df = sc.fit_transform(df.drop(columns =['MEDV']))
pca = PCA(n_components=7)
pca_df = pca.fit_transform(scaled_df)
X = pca_df
y = df['MEDV']
X = sm.add_constant(X)
model = sm.OLS(y,X).fit()
display(model.summary())
방법3) Lidge/Lasso 회귀
Lasso(L1, 절대값):
변수를 선택하고 싶은 경우
일부 변수만 중요하고, 나머지는 무시해도 되는 상황
희소한 데이터셋에서 불필요한 변수를 제거하여 모델의 해석력을 높이고 싶은 경우
Ridge(L2, 제곱):
모든 변수가 모델에 중요할 때
다중공선성 문제를 해결하고 과적합을 방지하고 싶은 경우
변수 선택보다는 모델 성능 향상이 더 중요한 경우
6. 시계열 검정
정상성 검정
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
# ADF 검정 수행
result = adfuller(data)
print('ADF Statistic:', result[0])
print('p-value:', result[1])
from statsmodels.tsa.seasonal import seasonal_decompose
# 시계열 분해 (Additive 모델)
# period는 데이터의 계절성 주기 설정 (예: 월별 데이터의 경우 12)
decomposition = seasonal_decompose(data, model='additive', period=12)
# 분해 결과 시각화
decomposition.plot()
plt.show()
지수 평활 모형
from statsmodels.tsa.holtwinters import ExponentialSmoothing
# 단순 지수 평활 모델 적합
model = ExponentialSmoothing(data, trend=None, seasonal=None)
model_fit = model.fit(smoothing_level=0.8, optimized=False)
# 예측값 생성
fitted_values = model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(fitted_values, label='예측값', color='red')
plt.legend()
plt.show()
6.1. 자기회귀(AR) 모형
현재 값이 과거 값들의 선형 조합으로 표현될 수 있다고 가정
자기상관에 강한 시계열과 추세나 패턴이 지속되는 경향이 있을때 유용(Ex 주식, 기온 데이터)
시차가 2라면 $y_t$와 $y_{t-2}$의 두 시차를 고려하며 중간 시차 $y_{t-1}$이 영향을 제거
첫 번째 선은 차수 1(lag1)
다음 그래프를 보고 AR(2) 결정
파란선: 유의성 경계
6.3. MA 모형
현재 값이 과거의 오차항(예측 오류)의 선형 조합으로 표현된다고 가정
단기적인 충격이나 이벤트의 반응을 모델링 하는데 유용(주가 실적 발표)
PACF: 점진적으로 감소, ACF: q 시차 이후 급격히 0에 가까워짐
# MA(q) 모델 - ARMA로 구현
from statsmodels.tsa.arima.model import ARIMA
model_ma = ARIMA(data, order=(0,0,q)).fit()
forecast = model_ma.forecast(steps=10)
6.4. ACF(Auto Correlation Function)
자기 상관 함수는 각 시차(lag)에 대해 현재 값과 과거 값의 상관관계를 계산, 시차 q를 결정
첫 번째 세로 선은 차수 0으로 자기와의 상관관계(항상 1.0)
다음은 MA(3)이 결정되는 ACF 그림
ACF, PACF 그리기
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# 한 그림에 두 플롯 함께 표시하기
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
plot_acf(data, lags=40, alpha=0.05, ax=axes[0])
axes[0].set_title('ACF')
plot_pacf(data, lags=40, alpha=0.05, ax=axes[1])
axes[1].set_title('PACF')
plt.tight_layout()
plt.show()
6.5. ARIMA
ARIMA
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt
# ARIMA 모델 적합 (p=1, d=1, q=1 파라미터로 예시)
model = ARIMA(data, order=(1, 1, 1))
model_fit = model.fit()
# 예측값 생성
fitted_values = model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(fitted_values, label='ARIMA 예측값', color='red')
plt.legend()
plt.title('ARIMA 모델 적합 결과')
plt.show()
# 모델 요약 출력 (선택사항)
print(model_fit.summary())
SARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
# SARIMA 모델 적합 (p, d, q, P, D, Q, m 설정)
p = 1 # AR 차수
d = 1 # 차분 차수
q = 1 # MA 차수
P = 1 # 계절 AR 차수
D = 1 # 계절 차분 차수
Q = 1 # 계절 MA 차수
m = 12 # 계절 주기 (예: 월별 데이터의 경우 12)
model = SARIMAX(data, order=(p, d, q), seasonal_order=(P, D, Q, m))
model_fit = model.fit()
print(model_fit.summary())
# 예측
forecast = model_fit.forecast(steps=12)
print(forecast)
import pmdarima as pm
from statsmodels.tsa.arima.model import ARIMA
data = df.copy()
# 자동으로 최적의 ARIMA 모델 찾기
auto_model = pm.auto_arima(data,
start_p=0, start_q=0,
max_p=5, max_q=5,
d=None, max_d=2,
seasonal=False,
trace=True,
error_action='ignore',
suppress_warnings=True,
stepwise=True)
print(f"최적의 ARIMA 모델 파라미터: {auto_model.order}")
# 최적 모델 적합
best_model = ARIMA(data, order=auto_model.order)
best_model_fit = best_model.fit()
# 예측값 생성
best_fitted_values = best_model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(best_fitted_values, label=f'최적 ARIMA{auto_model.order} 예측값', color='red')
plt.legend()
plt.title('optimization result')
#진단코드
model.plot_diagnostics(figsize=(12, 8))
plt.tight_layout()
plt.show()
VAR 모형
from statsmodels.tsa.vector_ar.var_model import VAR
# 데이터 분할 (학습 및 테스트 데이터)
train_data = df[:-10]
test_data = df[-10:]
# VAR 모델 적합
model = VAR(train_data)
model_fit = model.fit(maxlags=15, ic='aic')
print(model_fit.summary())
# 예측
lag_order = model_fit.k_ar
forecast_input = train_data.values[-lag_order:]
forecast = model_fit.forecast(y=forecast_input, steps=10)
# 예측 결과를 데이터프레임으로 변환
forecast_df = pd.DataFrame(forecast, index=test_data.index, columns=df.columns)
print(forecast_df)
이번 장에는 batch processing이라고 불리우는 일괄처리 방법에 대해서 기술한다. 분산 저장 시스템에서 함께 등장하는 맵리듀스 방식과 과거 유닉스 방식의 유사점을 비교하면서 설명하는 장이다.
신규개념
개념
설명
데몬 프로세스
멀티태스킹 운영 체재에서 사용자가 직접 제어하지 않고, 백그라운드에서 작업을 하는 프로그램
NAS
Network Attached Storage, 여러 사용자가 TCP/IP 네트워크로 파일을 저장하고 공유할 수 있는 중앙 집중식 파일 서버
SAN
Storage Area Network, 서버, 스토리지 시스템, 소프트웨어 및 서비스를 결합하여 특정 환경에 맞게 조정된 전용 네트워크
RAID
여러개의 디스크를 묶어 하나의 디스크처럼 사용하는 기술
맵리듀스 워크플로
Work Flow, 맵 리듀스 작업을 관리하는 워크 플로우 시스템
1. 요청응답 방식의 시스템 3가지
서비스(온라인 시스템)
서비스는 클라이언트로부터 요청이나 지시가 올 때까지 기다린다. 요청이 들어오면 가능한 빨리 요청을 처리하며 응답시간이 그 지표가된다.
일괄 처리 시스템(오프라인 시스템)
매우 큰 입력 데이터를 받아 처리하는 작업을 수행하고 결과 데이터를 생산한다. 하루에 한번 수행과 같이 반복적인 일정으로 수행하며, 성능지표는 처리량이다.
스트림 처리 시스템(준실시간 시스템)
스트림 처리는 입력 이벤트가 발생한 후 바로 작동한다. 일괄 처리 시스템보다는 지연시간이 낮지만 기반은 같다.
2. 일괄 처리 시스템의 예시
맵 리듀스는 검색 엔진에서 사용할 색인을 위하여 처음 사용됨. 색인은 용어 사전 파일로 효율적으로 특정 키워드를 조회해 키워드가 포함된 문서 ID의 모록을 찾아 검색 결과를 관련성 순으로 순위를 매기는 등의 일을 함. 머신러닝 시스템(스팸 필터, 이상 검출, 이미지 인식)을 구축하거나 추천시스템이 그 예
3. 유닉스의 철학과 단점
1. 각 프로그램이 한가지 일만 하도록 작성하라. 2. 모든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라. 3. 소프트웨어를 빠르게 써볼 수 있게하고 구축하라. 4. 프로그래밍 작업을 줄이려면 미숙한 도움보다는 도구를 사용하라.
유닉스 도구는 입력 파일에 손상을 주지 않으며, 어느 시점이든 less로 보내 원하는 형태의 출력이 확인 가능하여 디버깅이 유용하고, 특정 파이프라인 단계의 출력에 파일을 쓰고 다음 단계의 입력으로 사용가능하여 전체 파이프라인 재시작이 필요없는 장점 등이 있다. 하지만 이런 장점에도 불구하고 단일장비에서만 실행된다는 점 때문에 하둡 같은 도구의 필요성이 생겼다.
4. 맵리듀스와 분산파일 시스템
하둡 맵리듀스 구현에서 파일 시스템은 HDFS(Hadoop Distributed File System)이라고 하는데 GFS(Google File System)를 재구현한 오픈소스이다. HDFS는 비공유 원칙을 기반으로 하기 때문에 NAS, SAN의 아키텍쳐와 다른 점이다. 이때 2가지 콜백 함수로 구현되어있다.
매퍼(Mapper): 모든 입력 레코드마다 한번씩 실행되며, 입력 레코드로 부터 키와 값을 추출하는 작업이다.
리듀서(Reducer): 맵리듀스 프레임워크는 매퍼가 생산한 키-값 쌍을 받아 같은 키를 가지느 레코드를 모으고 해당 값의 집합을 반복해 리듀서 함수를 호출한다. 리듀서는 이때 출력 레코드를 생산한다.
맵리듀스에서 맵퍼는 입력 파일 블록에 따라서 파티셔닝됨. 매퍼의 출력은 재파티셔닝해 정렬하고 리듀셔 파이션으로 병합한다. 이 과정의 목적은 모든 데이터(같은 키 값을 가지는 모든 레코드)를 같은 장소로 가져오는 것이다. 맵리듀스 이후 데이터플로 엔진은 필요한 경우가 아니라면 정렬하지 않는다. 그렇지 않다면 대체로 비슷한 방법으로 파티셔닝 한다.
내결함성
맵리듀스는 빈번히 디스크에 기록한다. 디스크에 기록하면 개별 태스크가 실패하더라도 전체 작업을 재수행하지 않고 쉽게 복구할 수 있다. 하지만 작업이 실패하지 않는 경우 수행 시간이 느려지는 것은 감수해야한다. 데이터플로 엔진은 중간 상태를 최대한 구체화하지 않고 대신 메모리 상태를 유지한다. 이것은 특정 노드가 실패한다면 재계산 양이 늘어난다는 것을 의미한다. 결정적 연산자를 사용하면 재계산이 필요한 데이터의 양을 줄일 수 있다.