[책리뷰] 파이썬 데이터 분석 실무 테크닉 100
저희 회사에서도 그렇고, 비즈니스 현장에서는 데이터 사이언티스트(=데이터 분석가)가 부족하다고 합니다. 하지만 제 주위에는 분석 업무로 일하고 있는 지인들이 많을 뿐만 아니라, 분석가로 취업하고 싶은 취준생도 많거든요.
왜 이런 간극이 있는 것일까요? chatgpt, 쉽게 제공되는 개발환경 등으로 인해 누구나 엔지니어가 될 수 있는 축복받은 시대임에도 불구하고 말이죠.
그 이유 중 하나는 바로 도메인에 맞게 응용 능력이 있는 데이터 사이언티스트가 없다는 것입니다. 머신러닝 관련 책에서는 ‘붓꽃(iris)을 분류해보자!’라는 예제만을 알려주지만, 실제 비즈니스 현장에서는 이러한 과제만을 해결하지 않기 때문이죠.
저의 경우에도 제가 일하고 있는 도메인 영역은 잘 알고 있지만, 다른 도메인 영역에서 어떤 데이터를 다루고 그런 데이터를 다룰 때 어떤 문제가 발생하는지 모릅니다.
다른 도메인 영역의 데이터 분석 사례를 공부하고 싶어서 찾던 중에, 마음에 쏙 드는 책을 발견하여 이번 한 주 동안 공부하려고 합니다😘
📗 책 소개
소개할 책은 파이썬 데이터 분석 실무 테크닉 100 입니다.
이 책은 실제 비즈니스 현장을 가정한 100개의 예제를 풀면서 현장 감각을 익히고, 기술을 현장에 맞는 형태로 응용할 수 있는 힘을 기를 수 있게 설계한 분석 실습서라고 할 수 있습니다. 물론 이 책을 본다고 완전한 전문가가 되는 건 불가능하겠지만, 현장 감각을 익히기엔 좋을 것 같다고 생각이 들었습니다. 깊게는 아니더라도 최소한 ‘이런 데이터를 보면서, 이런 접근으로 가는구나’를 알 수 있기 때문이죠.
저처럼 ‘비즈니스 현장에서 기술이 어떻게 응용되는지 알고 싶다’ 라는 생각하신 분들께 도움이 될 것이라 기대하고 있습니다. 이번주 중으로 책 완독을 목표로 하고 있어서, 해당 부분은 다 읽고 다시 후기를 업데이트 하도록 하겠습니다.
📌 최종 후기 📌
일부 챕터는 정리를 마저 못했지만, 완독까지는 했는데요!
생각했던 것만큼 깊이 있는 내용을 다루지 않아서 아쉬웠지만, 분석을 막 시작하는 분들에게는 맛보기용으로 좋을 것 같다는 생각을 했습니다.
다른 도메인에는 어떤 상황에서 어떤 데이터를 보고자 하는지 알 수 있어서 좋았습니다.
하지만 분석을 하고 계시거나 실무에 계신 분들이 이 책을 읽으신다면, 조금 아쉬움이 많이 남는 책일 것이라 생각됩니다😢
가볍게 보기에 좋은 책 같아요!
이 책의 저자는 데이터 사이언티스트의 실제 모습은 전략 컨설턴트에 가깝다고 말합니다. 즉, 데이터를 통해 조직의 의사결정을 하는 것에 도움을 주는 역할을 하는거죠.
예시를 들며 ‘실제 현장에서는 이런 일이 있다’라고 적어주신 부분이 모두 공감 되어서 더 재밌게 술술 읽어지더라구요.
저자는 업무를 할 때 필요로 되는 것 중 하나가 ‘현장력’이라고 말합니다.
현장력은 [어떤 데이터를 어떻게 분석하고 가시화해야 하는지 경험에 근거해서 앞을 바라보는 능력]을 의미하는데요. 현장력을 구사해야 의뢰인이 원하는 분석을 할 수 있고, 그 보상으로 매출 증대에 공헌할 뿐만 아니라 현장 경험을 탄탄하게 쌓을 수 있다고 합니다.
📕 책 구성
책은 기초부터 실전까지 다루고 있기에 총 4부로 구성되어 있습니다. (4부, 10개 장)
- 1부 - 기초 편
- 비즈니스 현장에서 실제로 얻을 수 있는 데이터를 분석하기 위한 데이터 가공 방법
- 2부 - 실전 편(1)
- 머신러닝 기술을 사용해 고객을 분석하는 방법
- 데이터를 사용해 문제를 발견하고 해결하는 과정
- 머신러닝 기술을 사용해 고객을 분석하는 방법
- 3부 - 실전 편(2)
- 최적화 기술 적용 방법 + 경영 현황 개선 방안
- 4부 - 발전 편
- 이미지 인식 기술과 자연어 처리 기술과 같은 AI기술
- 데이터화되지 않은 정보를 이용해서 고객의 잠재적인 수요를 파악하는 방법
- 이미지 인식 기술과 자연어 처리 기술과 같은 AI기술
전체 10개의 장에서 상황에 맞는 사례가 있는데요. 고객으로부터 받은 의뢰(사례)를 바탕으로 다음의 두가지를 고민할 수 있도록 방향성을 제시합니다.
1️⃣ 현재 상황에 처한 정보와 데이터가 무엇인지 (전제조건)
2️⃣ 고객의 요구에 부응하기 위해 어떤 분석을 진행해야 하는지
분석 방법은 하나가 아니기 때문에,
공부하면서 ‘이렇게 해도 되지 않을까?’ 관점으로 개선점을 고민할 수 있다는 점이 이 책의 장점 중 하나인 것 같습니다.
(저도 책을 보면서, 제가 개인적으로 궁금한 부분은 따로 코드쳐서 보기도 하였어요! 이런 부분에서 더 재밌게 다가왔습니다)
📘 공부 리스트
앞으로 매 chapter마다 공부한 내용을 정리한 글을 쓸 예정인데요. 글이 완성될 때마다 포스팅 url 업데이트를 하도록 하겠습니다😊
- 예제코드 : 데이터 및 예제코드
- 1부 - 기초편) 데이터 가공
- 2부 - 실전편) 머신러닝
- 3부 - 실전편) 최적화 문제
- 6장 - 물류의 최적경로를 컨설팅하는 테크닉
* 7장 - 물류 네트워크 최적 설계를 위한 테크닉 - 8장 - 수치 시뮬레이션으로 소비자의 행동을 예측하는 테크닉
- 6장 - 물류의 최적경로를 컨설팅하는 테크닉
- 4부 - 발전편) 이미지 처리 / 언어 처리
- 9장 - 잠재고객을 파악하기 위한 이미지 인식 테크닉
* 10장 - 앙케트 분석을 위한 자연어 처리 테크닉
- 9장 - 잠재고객을 파악하기 위한 이미지 인식 테크닉
📒 중요한 부분 요약
5장까지는 분석보다는 데이터 전처리에 더 신경을 쓴 책이라고 느껴졌습니다. 좋았던 부분도 일부 있었기에, 까먹지말자는 차원에서 따로 정리를 해보았습니다.
⬜ 좋았던 언급들
- 비즈니스 현장에서의 데이터 분석은 상상처럼 ‘화려’하지 않고 현장이라서 해야 하는 사소한 업무가 의외로 많습니다.
- 어떤 데이터를 어떻게 연결해서 활용할 것인가가 데이터 분석가의 역량을 보여주는 부분입니다.
- 데이터를 적절히 가공해서 가시화하는 것만으로도 많은 정보를 얻을 수 있기 때문에 유능한 데이터 분석 엔지니어는 적절한 데이터 가공 기술을 구사합니다.
⬛ 경고(warning) 비표시
import warnings
warnings.filterwarnings('ignore')
⬛ 데이터 결합하기
데이터를 결합하는 방법은 세로로 결합(union)하는 방법과 가로로 결합(join)하는 방법이 있습니다.
1. UNION
세로로 결합하기 위해서는 concat
함수를 사용하며,
ignore_index=True
하지 않으면 이전 데이터에 있던 인덱스를 그대로 가져오기 때문에, 특별하게 인덱스를 유지해야하는 경우가 아니면 해당 옵션 사용이 필요합니다.
transaction = pd.concat([transaction_1, transaction_2], ignore_index=True)
2. JOIN
가로로 결합하기 위해서는 merge
함수를 사용하며,
추가하고 싶은 데이터 칼럼이 무엇인지 & 공통되는 데이터 칼럼(key)가 무엇인지 파악하는 것이 중요합니다.
- on : join key 칼럼
- how : join 종류
join_data = pd.merge(transaction_detail, transaction[["transaction_id", "payment_date", "customer_id"]], on="transaction_id", how="left")
⬛ 데이터 파악하기
데이터 분석을 진행할 때 제일 먼저 살펴봐야할 데이터 파악 방법입니다.
1. 결측치
# 결측치 칼럼 확인
uriage.isnull().any(axis=0)
# 결측치 개수 파악
join_data.isnull().sum()
2. 데이터 범위
# 데이터 range 파악
join_data.describe()
3. 변수 타입
# 데이터형 확인
join_data.dtypes
4. 개별 개수 확인
pd.unique(uriage.item_name)
⬛ 데이터 집계하기
1. groupby
customer_clustering.groupby(["cluster","routine_flg"], as_index=False).count()[["cluster","routine_flg","customer_id"]]
만약 집계함수를 여러개를 쓰고 싶을 때는 다음과 같이 사용하면 됩니다.
uselog_customer = uselog_months.groupby("customer_id").agg(["mean","median","max","min"])["count"]
uselog_customer = uselog_customer.reset_index(drop=False)
2. pivot
- index : 행
- columns : 열
- values : 집계하고싶은 칼럼
- aggfunc : 집계 함수
price_sum_data = pd.pivot_table(join_data, index='payment_month', columns='item_name', values=['price'], aggfunc='sum') price_sum_data
이때 가로 값을 뽑고싶을 때는 다음과 같이 추출할 수 있습니다.
list(price_sum_data.index)
칼럼은 두개 존재하며 다음과 같이 볼수 있습니다.
price_sum_data.columns
그렇기 때문에 하나의 칼럼에 대한 값을 추출하기 위해서는 다음과 같이 작성해야 합니다.
price_sum_data['price']['PC-A']
⬛ 데이터 수정하기
1. 대문자 변환
uriage["item_name"] = uriage["item_name"].str.upper()
2. 공백 제거
uriage["item_name"] = uriage["item_name"].str.replace(" ", "")
3.결측치 채워넣기
책에서는 결측치에 같은 상품의 단가를 이용하여 수정하였습니다.
# step1 : null값인 행 뽑기
flg_is_null = uriage["item_price"].isnull()
# step2 : null인 행의 item_name 추출하기
# step3 : null이 아닌 행의 item_name 가격 뽑아보기
# step4 : 위 내용을 loop문을 통해 만들어주기
for trg in list(uriage.loc[flg_is_null,"item_name"].unique()):
price = uriage.loc[(~flg_is_null)&(uriage["item_name"]==trg), "item_price"].max()
uriage["item_price"].loc[(flg_is_null)&(uriage["item_name"]==trg)] = price
또다른 방법으로는, 특정 값을 일괄 넣을 수 있습니다.
customer_join["calc_date"] = customer_join["calc_date"].fillna(pd.to_datetime("20190430"))
4. 변수명 변경
uselog_months.rename(columns={"log_id":"count"}, inplace=True)
5. 변수 제거
del uselog_months["usedate"]
6. 조건문
아래 예시에서는 where
함수를 통해 4 미만인 경우는 그대로 0을 두고, 4 이상인 경우만 1로 대입합니다.
uselog_weekday["routine_flg"] = 0
uselog_weekday["routine_flg"] = uselog_weekday["routine_flg"].where(uselog_weekday["count"]<4,1)
7. 표준화 처리
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
customer_clustering_sc = sc.fit_transform(customer_clustering)
8. 결측치 제거
칼럼을 특정하고 싶을 경우 subset
에서 제한하면 됩니다.
predict_data.dropna(subset=["count_1"])
⬛ 데이터 필터링
loc
함수를 통해서 필터링할 수 있습니다.
예제1
customer_newer = join_data.loc[(join_data["end_date"] >= pd.to_datetime("20190331")) | (join_data["end_date"].isna())]
예제2
customer_end = customer_join.loc[customer_join["is_deleted"]==1]
⬛ DATE 타입 변환
1. 숫자인지, date인지 확인
flg_is_serial = kokyaku_daicho["등록일"].astype("str").str.isdigit()
flg_is_serial.sum() # 22건의 숫자 데이터 존재
2. 숫자를 날짜로 변환
pd.to_timedelta
: 숫자를 날짜로 변환loc
: 숫자인 부분 추출 (1번 참고)fromSerial = pd.to_timedelta(kokyaku_daicho.loc[flg_is_serial, "등록일"].astype("float"), unit="D") + pd.to_datetime("1900/01/01")
3. 날짜 타입으로 변환
fromString = pd.to_datetime(kokyaku_daicho.loc[~flg_is_serial, "등록일"])
4. yyyy-mm 단위로 추출
join_data["payment_month"] = join_data["payment_date"].dt.strftime("%Y-%m")
4번의 경우 3번과 같이 진행되어야하기 때문에 다음과 같이 붙여서 set라고 생각하면 됩니다.
use_log["usedate"] = pd.to_datetime(use_log["usedate"])
use_log["yyyymm"] = use_log["usedate"].dt.strftime("%Y-%m")
5. weekday 추출
use_log["weekday"] = use_log["usedate"].dt.weekday
6. 기간 계산
from dateutil.relativedelta import relativedelta
predict_data["period"] = 0
predict_data["now_date"] = pd.to_datetime(predict_data["연월"], format="%Y%m")
predict_data["start_date"] = pd.to_datetime(predict_data["start_date"])
for i in range(len(predict_data)):
delta = relativedelta(predict_data["now_date"][i], predict_data["start_date"][i])
predict_data["period"][i] = int(delta.years*12 + delta.months)
⬛ LOOP 활용
# skipna는 NaN의 무시 여부를 설정하여, NaN이 존재할 경우 최소값이 NaN으로 표시됨
for trg in list(uriage["item_name"].sort_values().unique()):
print(trg + "의 최고가 : " + str(uriage.loc[uriage["item_name"]==trg]["item_price"].max())
+ "의 최저가 : " + str(uriage.loc[uriage["item_name"]==trg]["item_price"].min(skipna=False)))
⬛ 데이터 전처리 예시
예제1 - 이번달부터 과거 5개월분의 이용 횟수와 다음 달의 이용 횟수
year_months = list(uselog_months["연월"].unique())
predict_data = pd.DataFrame()
# 18년 10월 ~ 19년 3월까지 반복하면서, 과거 6개월분의 이용 데이터를 취득해서 추가
for i in range(6, len(year_months)):
tmp = uselog_months.loc[uselog_months["연월"]==year_months[i]]
tmp.rename(columns={"count":"count_pred"}, inplace=True) # 예측하고 싶은 달의 데이터
for j in range(1,7):
tmp_before = uselog_months.loc[uselog_months["연월"]==year_months[i-j]]
del tmp_before["연월"]
tmp_before.rename(columns={"count":"count_{}".format(j-1)}, inplace=True) # count0이 1개월 전으로, 과거 6개월의 데이터 나열
tmp = pd.merge(tmp, tmp_before, on="customer_id", how="left")
predict_data = pd.concat([predict_data, tmp], ignore_index=True)
predict_data = predict_data.dropna()
predict_data = predict_data.reset_index(drop=True)
예제2 - 이번달과 1개월 전의 이용횟수 집계
year_months = list(uselog_months["연월"].unique())
uselog = pd.DataFrame()
for i in range(1, len(year_months)):
tmp = uselog_months.loc[uselog_months["연월"]==year_months[i]]
tmp.rename(columns={"count":"count_0"}, inplace=True)
tmp_before = uselog_months.loc[uselog_months["연월"]==year_months[i-1]]
del tmp_before["연월"]
tmp_before.rename(columns={"count":"count_1"}, inplace=True)
tmp = pd.merge(tmp, tmp_before, on="customer_id", how="left")
uselog = pd.concat([uselog, tmp], ignore_index=True)
⬛ 시각화
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,4)) ## 캔버스 생성
ax = fig.add_subplot() ## 그림 뼈대(프레임) 생성
ax.plot(join_data[join_data["item_name"]=="PC-A"].groupby("payment_month").sum()["price"],marker='o',label='A') ## 선그래프 생성
ax.plot(join_data[join_data["item_name"]=="PC-B"].groupby("payment_month").sum()["price"],marker='o',label='B')
ax.plot(join_data[join_data["item_name"]=="PC-C"].groupby("payment_month").sum()["price"],marker='o',label='C')
ax.plot(join_data[join_data["item_name"]=="PC-D"].groupby("payment_month").sum()["price"],marker='o',label='D')
ax.plot(join_data[join_data["item_name"]=="PC-E"].groupby("payment_month").sum()["price"],marker='o',label='E')
ax.legend() ## 범례
plt.title("price sum")
plt.show()
%matplotlib inline
plt.plot(list(price_sum_data.index), price_sum_data['price']['PC-A'], label='PC-A')
plt.plot(list(price_sum_data.index), price_sum_data['price']['PC-B'], label='PC-B')
plt.plot(list(price_sum_data.index), price_sum_data['price']['PC-C'], label='PC-C')
plt.plot(list(price_sum_data.index), price_sum_data['price']['PC-D'], label='PC-D')
plt.plot(list(price_sum_data.index), price_sum_data['price']['PC-E1'], label='PC-E')
plt.legend()