Yours Ever, Data Chronicles
파이썬으로 지저분한 데이터 가공하기 -1편 / 파이썬 데이터 분석 실무 테크닉 100 본문
파이썬으로 지저분한 데이터 가공하기 -1편 / 파이썬 데이터 분석 실무 테크닉 100
Everly. 2022. 1. 14. 13:04저번 포스팅에서 다뤘던 쇼핑몰 데이터는 프로그램으로 알아서 데이터가 쌓이고 있어, 비교적 깨끗한 데이터였는데요.
하지만 이번 포스팅에서 다룰 데이터는 사람이 수기로 입력한 엑셀 데이터로, '지저분한 데이터'를 가공하는 방법을 포스팅해보겠습니다.
제가 회사에서 일했을 때의 데이터는 데이터베이스에서 내려받을 수 있는, 비교적 깔끔한 데이터였지만 아직까지 비즈니스 현장에서는 사람이 직접 손으로 입력한 데이터가 많습니다. 어떠한 데이터 형식으로 되어있든지간에, 개떡같은 데이터도 찰떡같이 분석하는 것이 데이터 사이언티스트라면 기본이겠죠?
이번 시간에 다룰 데이터는 가상의 대리점 데이터로, 매출현황과 고객정보가 들어있는 2개의 데이터를 제공받았습니다.
- uriage.csv : 매출 이력 데이터, 상품 A~Z까지 26개의 상품을 취급함.
- ko.xlsx : 대리점에서 관리하는 고객 정보
다음은 대리점에서 의뢰한 내용입니다.
우리 회사에서는 고객 정보를 엑셀로 관리합니다. 장사는 잘되고 있고, 대리점 매출도 안정적입니다.
데이터가 풍부할 때 데이터 분석을 해두면 여러 가지 발견을 할 수 있지 않을까 하여, 시험 삼아 데이터 분석을 부탁드립니다!
✔Table of Contents
Tech 11. 지저분한 데이터를 읽어들이자.
import pandas as pd
uriage = pd.read_csv('2장/uriage.csv')
ko = pd.read_excel('2장/kokyaku_daicho.xlsx')
display(uriage.head(), ko.head())
데이터를 불러오는 것은 이전에 한 것과 마찬가지로, csv와 xlsx로 되어 있으니 각각 pd.read_csv, pd.read_excel로 읽어들인다.
그런데 데이터를 확인해보면, item_name에서 상품이름이 통일이 되어있지 않고, item_price에 결측치가 보임.
'등록일'은 시간도 잘못되어 있다.. 42782 시간이 어딨어?
uriage 데이터의 customer_name과 ko 데이터의 고객이름 열을 나중에 조인시킬 것인데 이 또한 맞지가 않다.(ko 데이터의 '고객이름'은 띄어쓰기가 되어 있음)
→ 데이터의 정합성에 오류가 있다.
Tech 12. 데이터의 오류를 살펴보자.
상품은 모두 A~Z까지만 있는데, 대문자와 소문자가 섞여 있으며 공백도 들어가 있다.
- 상품A
- 상품 A
- 상품a
- 상품 a
=> 이런 것들은 모두 다 같은 '상품A'로 보아야 한다!
uriage['item_price'].value_counts(dropna=False)
숫자 데이터는 숫자만 들어 있으나, 결측치(NaN)이 387개나 있다. 결측치가 이렇게 많으면 분석에 방해가 되기 때문에 잘 처리하는 것이 필요!
우선은 시험 삼아서 이 데이터들을 전처리하지 않고 집계해보면 어떻게 되는지를 알아보자.
Tech 13. 데이터에 오류가 있는 상태로 집계하면 어떻게 될까?
이렇게 오류가 있을 때, 전처리하지 않고 그냥 집계하면 어떻게 되는지를 알아보자.
먼저, uriage 데이터(매출이력)에서 월별, 상품별로 몇 개나 팔렸는지를 집계해본다.
#날짜를 나타내는 purchase_date를 datetime 형식으로 바꾸고 연월만 빼냄
uriage['purchase_date'] = pd.to_datetime(uriage['purchase_date'])
uriage['purchase_ym'] = uriage['purchase_date'].dt.strftime("%Y%m")
uriage.head()
그리고 월별, 상품별로 몇 개가 팔렸는지를 알고 싶으므로,
피봇 테이블 함수에서 index(행)으로 월별을, column(열)으로 상품명을, 그리고 집계함수로는 size를 사용한다.
*size로 집계 시 NaN(결측치)의 개수도 함께 세어주며, fill_value를 0으로 했으므로 NaN으로 나타내는 경우 0으로 쓴다.
*참고로 NaN은 제외한 개수를 세어주고자 하는 경우는 'size'가 아닌 'count'로 집계한다.
uriage.pivot_table(index = 'purchase_ym', columns = 'item_name', aggfunc ='size', fill_value = 0)
여기서는 NaN이 워낙 많았기 때문에 집계함수를 'size'로 사용하였다.
그런데 결과를 보면, 같은 상품인 상품 S와 상품 s가 다른 값으로 잡혀 집계된다.
또한 여기서 상품은 A~Z까지 총 26개여야만 하는데, 컬럼 수를 보면 99개로 늘어나있다. (즉, 상품 수가 99개로 잡혔다는 것..!)
이번에는 월별, 상품별로 매출이 어느 정도인지를 알아보자.
피봇 테이블 함수에서 index(행)으로 월별을, column(열)으로 상품명을, 그리고 집계할 대상인 values로 상품가격을, 집계함수로는 sum을 사용한다.(상품가격의 총합)
uriage.pivot_table(index = 'purchase_ym', columns='item_name', values='item_price', aggfunc = 'sum', fill_value = 0)
마찬가지로 집계결과가 옳지 않다. 이제 오류를 수정해보자.
Tech 14. 상품명 오류 수정하기(문자열 전처리)
앞서, 상품명의 오류가 있었다. 이는 이렇게 처리한다.
1) 상품명에 어떤 것은 공백이 있고, 어떤 것은 없었다. -> 모두 공백을 없애자!
2) 어떤 것은 대문자, 어떤 것은 소문자였다. -> 모두 대문자로 만들자!
현재 uriage 데이터의 item_name은 오류 때문에 26개여야 하는 상품수가 99개로 나오는 것을 알 수 있다.
문자열 데이터를 처리해 26개로 바꿔보자.
#먼저, 상품의 공백을 모두 메꾸기
uriage['item_name'] = uriage['item_name'].str.replace(" ", '')
#상품 알파벳을 모두 대문자로 바꾸기
uriage['item_name'] = uriage['item_name'].str.upper()
uriage.head()
그럼 이제 다시 개수를 세어보자.
이제는 item_name의 unique한 개수가 26개로 잘 수정되었음을 알 수 있다.
Tech 15. item_price의 결측치 처리하기
아까 하나 더 수정해야 했던 것 중에, 가격을 나타내는 열인 item_price는 숫자로 구성되어 있긴 하나 결측치가 아주 많았다.
isnull 메서드를 통해, 'item_price' 열에만 결측치가 387개나 있음을 알았다.
결측치는 수가 적으면 아예 지워버려도 된다. 또는, 평균이나 중간값으로 채우는 등 보간법을 사용해도 된다.
그런데 이 케이스에선 "집계 기간에 상품 단가의 변동이 없다"는 전제 조건이 있었으므로, 같은 상품의 단가와 같은 가격으로 채워넣자.
즉, 상품A에 대해 어떤 행은 100원이라는 값이 있는데, 어떤 행은 NaN으로 되어 있는 경우 이를 100원으로 채워주자는 것이다.
이를 위해선 어떻게 해야 할까? 고유한 상품명 26개를 for문으로 돌려, 이 상품명 하나에 해당할 때마다 전체 데이터프레임에서 검사하여 NaN을 올바른 가격값으로 채워주는 방법을 사용하자.
조금 복잡할 수 있는데, 데이터프레임에서 검사할 때 loc와 불린 인덱싱(True인 것만 뽑기)을 사용하면 된다! 직접 해보면서 알아보자.
먼저 상품별로 가장 최저가 및 최고가가 어떻게 되는지를 출력해보자. NaN이 있는 경우, 최저가로는 NaN이 뽑히게 된다. (단, skipna를 False로 해야 NaN이 출력된다!)
for i in list(uriage['item_name'].unique()):
print(i, '최저가: ', uriage.loc[uriage['item_name'] == i , 'item_price'].min(skipna=False), #결측치가 있으니 NaN으로 나옴.
' 최고가: ', uriage.loc[uriage['item_name']==i, 'item_price'].max())
결과를 보면 상품Z를 제외하고는 모든 상품에서 NaN이 있으며,
NaN을 제거하고 봤을 때 최저가와 최고가의 가격은 차이가 없음. 그래도 안전하게 최고가 가격을 각 상품별 가격에 채워넣도록 하자.
불린 인덱싱을 위한 True/False로 뽑히는 변수를 생성한다.
flg_is_null = uriage['item_price'].isnull() #price가 NaN인 애들만 뽑음 -> T/F로 되어있으니 인덱싱으로 True인 애들만 뽑는다!
flg_is_null
이렇게 만들어진 flg_is_null 변수는 True / False로만 출력되며, item_price 값이 NaN이면 True, 아니면 False이다.
그래서 이따 for문으로 돌릴 때 flg_is_null이 True인 애들은 비어 있는 애들이므로, flg_is_null이 False인 애들의 item_price값으로 채워넣을 것이다.
for i in list(uriage['item_name'].unique()):
price = uriage.loc[(~flg_is_null) & (uriage['item_name'] == i), 'item_price'].max() #price값이 있는 애들의 최고가를 가져옴
uriage.loc[(flg_is_null) & (uriage['item_name'] == i), 'item_price'] = price #그래서 비어있는 애들에 위의 price로 채워줌
uriage.head()
위와 같이 for문을 실행해주면 item_price 값이 잘 채워진 것을 확인할 수 있다!
하지만 혹시 모르게 NaN이 안 채워졌을 수 있으니 검산에 검산하기!
이전에 item_price는 결측치가 True(있음) 였는데, 이제는 False이므로 제대로 들어갔다 +_+
아까 뽑았던 상품별 최저가, 최고가 코드를 그대로 돌려보자.
for i in list(uriage['item_name'].sort_values().unique()):
print(i, '최저가: ', uriage.loc[uriage['item_name'] == i , 'item_price'].min(skipna=False),
' 최고가: ', uriage.loc[uriage['item_name']==i, 'item_price'].max())
최대금액과 최저금액이 일치하므로, 제대로 전처리했음을 확인할 수 있다.
(아까와 조금 코드가 달라진 것은, item_name의 unique값을 리스트로 만들 때 sort_values를 활용해 상품명을 알파벳순으로 보기 좋게 정리했다.)
설명이 길어지는 관계로 Tech 16부터는 다음 포스팅에서 알아보자 :)
'Data Science > Analysis Study' 카테고리의 다른 글
파이썬 EDA : 고객의 전체 모습 파악하기 -2편 / 파이썬 데이터 분석 실무 테크닉 100 (8) | 2022.01.17 |
---|---|
파이썬 EDA : 고객의 전체 모습 파악하기 -1편 / 파이썬 데이터 분석 실무 테크닉 100 (3) | 2022.01.17 |
파이썬으로 지저분한 데이터 가공하기 -2편 / 파이썬 데이터 분석 실무 테크닉 100 (0) | 2022.01.14 |
쇼핑몰 데이터 주문 수 분석하기 (월별, 상품별) -2편 / 파이썬 데이터 분석 실무 테크닉 100 (6) | 2022.01.12 |
쇼핑몰 데이터 주문 수 분석하기 (월별, 상품별) -1편 / 파이썬 데이터 분석 실무 테크닉 100 (1) | 2022.01.12 |