🤔시스템에 의해 관리되는 데이터가 아닌 경우, 종종 사람의 입력 실수나 누락 등으로 오류가 포함될 가능성이 있다. 이로인해 잘못된 결과를 도출하거나, 가공 처리부터 어려움을 겪는 경우가 있다.
실제 현장에서도 잘 정리된 데이터 보다는 소위 '지저분한' 데이터 형식을 만나게 될 가능성이 높으므로, 이런 데이터를 처리하는 경험을 쌓아보자.
상품 A~Z까지 26개 상품을 취급하며, 매출 이력과 고객 정보 데이터는 담당직원이 직접 시스템에 입력한다. 집계기간 상품 단가 변동은 없고, 매출이력은 시스템에서 csv파일로 출력했다. 고객 정보는 주별로 집계하여 엑셀로 관리한다.
데이터 | 개요 |
---|---|
uriage.csv | 매출이력 (2019.01~2019.07) |
kokyaku_daicho.xlsx | 대리점에서 관리하는 고객 정보 |
# 경고(warning) 비표시
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
uriage_data = pd.read_csv("uriage.csv")
uriage_data.head()
purchase_date | item_name | item_price | customer_name | |
---|---|---|---|---|
0 | 2019-06-13 18:02 | 상품A | 100.0 | 김가온 |
1 | 2019-07-13 13:05 | 상 품 S | NaN | 김우찬 |
2 | 2019-05-11 19:42 | 상 품 a | NaN | 김유찬 |
3 | 2019-02-12 23:40 | 상품Z | 2600.0 | 김재현 |
4 | 2019-04-22 3:09 | 상품a | NaN | 김강현 |
kokyaku_data = pd.read_excel("kokyaku_daicho.xlsx")
kokyaku_data.head()
고객이름 | 지역 | 등록일 | |
---|---|---|---|
0 | 김 현성 | H시 | 2018-01-04 00:00:00 |
1 | 김 도윤 | E시 | 42782 |
2 | 김 지한 | A시 | 2018-01-07 00:00:00 |
3 | 김 하윤 | F시 | 42872 |
4 | 김 시온 | E시 | 43127 |
uriage_data["item_name"].head()
0 상품A
1 상 품 S
2 상 품 a
3 상품Z
4 상품a
Name: item_name, dtype: object
▶상품 명의 형식이 일관되지 않음을 볼 수 있다. 대소문자 / 공백 유무로 다른 데이터로 인식하기 때문에 정리가 필요해보임.
uriage_data["item_price"].head()
0 100.0
1 NaN
2 NaN
3 2600.0
4 NaN
Name: item_price, dtype: float64
▶결측치도 있음.
결측치를 처리하는 방법에 따라 결과도 많이 달라지기 때문에 전체 분포도 잘 살펴보자.
kokyaku_data["등록일"].head()
0 2018-01-04 00:00:00
1 42782
2 2018-01-07 00:00:00
3 42872
4 43127
Name: 등록일, dtype: object
▶등록일 기준에 4xxxx은 무엇?
데이터에 입력 오류나 표기방법 차이가 부정합을 일으키면 '데이터의 정합성에 문제가 있다'라고 하는데, 위의 데이터가 좋은 사례이다.
⏺ 먼저 매출이력에서 상품별 월 매출 합계를 집계해보자.
uriage_data["purchase_date"] = pd.to_datetime(uriage_data["purchase_date"])
uriage_data["purchase_month"] = uriage_data["purchase_date"].dt.strftime("%Y%m")
res = uriage_data.pivot_table(index="purchase_month", columns="item_name", aggfunc="size", fill_value=0)
res
item_name | 상 품 n | 상품 E | 상품 M | 상품 P | 상품 S | 상품 W | 상품 X | 상품W | 상 품O | 상 품Q | ... | 상품k | 상품l | 상품o | 상품p | 상품r | 상품s | 상품t | 상품v | 상품x | 상품y |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
purchase_month | |||||||||||||||||||||
201901 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
201902 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
201903 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
201904 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
201905 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
201906 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
201907 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 1 | 0 | 2 | 0 | 0 | 0 | 0 | 0 |
7 rows × 99 columns
▶컬럼을 보면 상품S와 상품s를 다른 품목으로 인식한 것을 볼 수 있다. 총 상품은 26개인데 위의 표를 보면 99개의 컬럼, 즉 상품을 99가지로 인식하고 있다.
가로축에 item_price를 설정해서 집계해보자.
res = uriage_data.pivot_table(index="purchase_month", columns="item_name", values="item_price", aggfunc="sum", fill_value=0)
res
item_name | 상 품 n | 상품 E | 상품 M | 상품 P | 상품 S | 상품 W | 상품 X | 상품W | 상 품O | 상 품Q | ... | 상품k | 상품l | 상품o | 상품p | 상품r | 상품s | 상품t | 상품v | 상품x | 상품y |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
purchase_month | |||||||||||||||||||||
201901 | 1400 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 1100 | 1200 | 1500 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
201902 | 0 | 0 | 0 | 0 | 0 | 0 | 2400 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1900 | 2000 | 2200 | 0 | 0 |
201903 | 0 | 500 | 1300 | 1600 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
201904 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2300 | 0 | 1700 | ... | 0 | 0 | 0 | 0 | 0 | 1900 | 0 | 0 | 0 | 0 |
201905 | 0 | 0 | 0 | 0 | 1900 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1200 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2500 |
201906 | 0 | 0 | 0 | 0 | 0 | 2300 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1600 | 0 | 0 | 0 | 0 | 2400 | 0 |
201907 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 1500 | 0 | 1800 | 0 | 0 | 0 | 0 | 0 |
7 rows × 99 columns
⏺위의 상태로 분석을 하면 전혀 의미없는 결과가 나오기 때문에 가공단계가 전처리로 얼마나 중요한지 확인했다. 오류를 이제 수정해보자.
#상품명의 수 확인
len(uriage_data["item_name"].unique())
99
#대문자 통일
uriage_data["item_name"] = uriage_data["item_name"].str.upper()
# 공백 수정
uriage_data["item_name"] = uriage_data["item_name"].str.replace(" ", "")
uriage_data["item_name"] = uriage_data["item_name"].str.replace(" ", "")
#상품별 나열
uriage_data.sort_values(by=["item_name"], ascending=True)
purchase_date | item_name | item_price | customer_name | purchase_month | |
---|---|---|---|---|---|
0 | 2019-06-13 18:02:00 | 상품A | 100.0 | 김가온 | 201906 |
1748 | 2019-05-19 20:22:00 | 상품A | 100.0 | 김시훈 | 201905 |
223 | 2019-06-25 08:13:00 | 상품A | 100.0 | 김유진 | 201906 |
1742 | 2019-06-13 16:03:00 | 상품A | 100.0 | 김건희 | 201906 |
1738 | 2019-02-10 00:28:00 | 상품A | 100.0 | 김하랑 | 201902 |
... | ... | ... | ... | ... | ... |
2880 | 2019-04-22 00:36:00 | 상품Y | NaN | 김동욱 | 201904 |
2881 | 2019-04-30 14:21:00 | 상품Y | NaN | 김하준 | 201904 |
1525 | 2019-01-24 10:27:00 | 상품Y | 2500.0 | 김범준 | 201901 |
1361 | 2019-05-28 13:45:00 | 상품Y | 2500.0 | 김수현 | 201905 |
3 | 2019-02-12 23:40:00 | 상품Z | 2600.0 | 김재현 | 201902 |
2999 rows × 5 columns
print(len(uriage_data["item_name"].unique()))
print(uriage_data["item_name"].unique())
26
['상품A' '상품S' '상품Z' '상품V' '상품O' '상품U' '상품L' '상품C' '상품I' '상품R' '상품X' '상품G'
'상품P' '상품Q' '상품Y' '상품N' '상품W' '상품E' '상품K' '상품B' '상품F' '상품D' '상품M' '상품H'
'상품T' '상품J']
▶상품명 처리!
uriage_data.isnull().any()
purchase_date False
item_name False
item_price True
customer_name False
purchase_month False
dtype: bool
▶item_price 컬럼에 결측치가 있다.
✔해당 데이터의 경우, 집계 기간에 상품 단가 변동이 없었기 때문에, 결측치는 같은 상품 단가를 이용해서 수정할 수 있다.
#어떤 행에 결측치 있는지 저장
flg_is_null = uriage_data["item_price"].isnull()
flg_is_null
0 False
1 True
2 True
3 False
4 True
...
2994 False
2995 False
2996 True
2997 True
2998 False
Name: item_price, Length: 2999, dtype: bool
#결측치 있는 상품명 리스트
list(uriage_data.loc[flg_is_null, "item_name"])[:5]
['상품S', '상품A', '상품A', '상품A', '상품A']
# unique()로 중복을 제거해서 불필요한 반복 방지 후 for문 돌리기
for trg in list(uriage_data.loc[flg_is_null, "item_name"].unique()):
# 결측치가 있는 상품과 같은 상품을 찾고, 올바르게 입력된 행의 loc을 찾아 그 금액을 가져온다.
price = uriage_data.loc[(~flg_is_null) & (uriage_data["item_name"] == trg), "item_price"].max()
# 가져온 금액으로 수정
uriage_data["item_price"].loc[(flg_is_null) & (uriage_data["item_name"]==trg)] = price
uriage_data.head()
purchase_date | item_name | item_price | customer_name | purchase_month | |
---|---|---|---|---|---|
0 | 2019-06-13 18:02:00 | 상품A | 100.0 | 김가온 | 201906 |
1 | 2019-07-13 13:05:00 | 상품S | 1900.0 | 김우찬 | 201907 |
2 | 2019-05-11 19:42:00 | 상품A | 100.0 | 김유찬 | 201905 |
3 | 2019-02-12 23:40:00 | 상품Z | 2600.0 | 김재현 | 201902 |
4 | 2019-04-22 03:09:00 | 상품A | 100.0 | 김강현 | 201904 |
▶결측치 처리 후 검증
uriage_data.isnull().any()
purchase_date False
item_name False
item_price False
customer_name False
purchase_month False
dtype: bool
▶각 상품 금액이 정상적으로 수정되었는지 확인해보자.
for trg in list(uriage_data["item_name"].sort_values().unique()):
print(trg + "의최고가:" + str(uriage_data.loc[uriage_data["item_name"]==trg]["item_price"].max())
+ "의최저가:" + str(uriage_data.loc[uriage_data["item_name"]==trg]["item_price"].min(skipna=False)))
#skipna는 NaN의 무시여부를 설정. False로 지정했기 떄문에 NaN이 존재하는 경우 최솟값이 NaN으로 표시된다.
상품A의최고가:100.0의최저가:100.0
상품B의최고가:200.0의최저가:200.0
상품C의최고가:300.0의최저가:300.0
상품D의최고가:400.0의최저가:400.0
상품E의최고가:500.0의최저가:500.0
상품F의최고가:600.0의최저가:600.0
상품G의최고가:700.0의최저가:700.0
상품H의최고가:800.0의최저가:800.0
상품I의최고가:900.0의최저가:900.0
상품J의최고가:1000.0의최저가:1000.0
상품K의최고가:1100.0의최저가:1100.0
상품L의최고가:1200.0의최저가:1200.0
상품M의최고가:1300.0의최저가:1300.0
상품N의최고가:1400.0의최저가:1400.0
상품O의최고가:1500.0의최저가:1500.0
상품P의최고가:1600.0의최저가:1600.0
상품Q의최고가:1700.0의최저가:1700.0
상품R의최고가:1800.0의최저가:1800.0
상품S의최고가:1900.0의최저가:1900.0
상품T의최고가:2000.0의최저가:2000.0
상품U의최고가:2100.0의최저가:2100.0
상품V의최고가:2200.0의최저가:2200.0
상품W의최고가:2300.0의최저가:2300.0
상품X의최고가:2400.0의최저가:2400.0
상품Y의최고가:2500.0의최저가:2500.0
상품Z의최고가:2600.0의최저가:2600.0
#고객 정보의 고객 이름
kokyaku_data["고객이름"].head()
0 김 현성
1 김 도윤
2 김 지한
3 김 하윤
4 김 시온
Name: 고객이름, dtype: object
# 매출 이력의 고객 이름
uriage_data["customer_name"].head()
0 김가온
1 김우찬
2 김유찬
3 김재현
4 김강현
Name: customer_name, dtype: object
# 공백이 보이므로, 일단 제거해서 통일시킨다.
kokyaku_data["고객이름"] = kokyaku_data["고객이름"].str.replace(" ", "")
kokyaku_data["고객이름"] = kokyaku_data["고객이름"].str.replace(" ", "")
kokyaku_data["고객이름"].head()
0 김현성
1 김도윤
2 김지한
3 김하윤
4 김시온
Name: 고객이름, dtype: object
⏺고객 대장의 등록일 오류를 수정해보자. 4xxxx처럼 날짜 형식이 아닌 경우가 무엇이었는지 보기.
#숫자로 읽히는 데이터 확인
#고객 정보의 등록일이 숫자인지 아닌지를 str.isdigit()로 판정하고, 숫자로 된 장소를 flg_is_serial에 저장해서 갯수 확인
flg_is_serial = kokyaku_data["등록일"].astype("str").str.isdigit()
flg_is_serial.sum()
22
kokyaku_data["등록일"]
0 2018-01-04 00:00:00
1 42782
2 2018-01-07 00:00:00
3 42872
4 43127
...
195 2017-06-20 00:00:00
196 2018-06-20 00:00:00
197 2017-04-29 00:00:00
198 2019-04-19 00:00:00
199 2019-04-23 00:00:00
Name: 등록일, Length: 200, dtype: object
▶pd.to_timedelta()
함수로 숫자를 날짜로 변환한다. loc()
을 이용해서 데이터 추출하고, 날짜로 변경한다.
fromSerial = pd.to_timedelta(kokyaku_data.loc[flg_is_serial, "등록일"].astype("float"), unit="D") + pd.to_datetime("1900/01/01")
fromSerial
1 2017-02-18
3 2017-05-19
4 2018-01-29
21 2017-07-06
27 2017-06-17
47 2017-01-08
49 2017-07-15
53 2017-04-10
76 2018-03-31
80 2018-01-12
99 2017-06-01
114 2018-06-05
118 2018-01-31
122 2018-04-18
139 2017-05-27
143 2017-03-26
155 2017-01-21
172 2018-03-24
179 2017-01-10
183 2017-07-26
186 2018-07-15
192 2018-06-10
Name: 등록일, dtype: datetime64[ns]
# 날짜로 변환된 데이터도 서식을 통일한다.
fromString = pd.to_datetime(kokyaku_data.loc[~flg_is_serial, "등록일"])
fromString
0 2018-01-04
2 2018-01-07
5 2017-06-20
6 2018-06-11
7 2017-05-19
...
195 2017-06-20
196 2018-06-20
197 2017-04-29
198 2019-04-19
199 2019-04-23
Name: 등록일, Length: 178, dtype: datetime64[ns]
kokyaku_data["등록일"] = pd.concat([fromSerial, fromString])
kokyaku_data
고객이름 | 지역 | 등록일 | |
---|---|---|---|
0 | 김현성 | H시 | 2018-01-04 |
1 | 김도윤 | E시 | 2017-02-18 |
2 | 김지한 | A시 | 2018-01-07 |
3 | 김하윤 | F시 | 2017-05-19 |
4 | 김시온 | E시 | 2018-01-29 |
... | ... | ... | ... |
195 | 김재희 | G시 | 2017-06-20 |
196 | 김도영 | E시 | 2018-06-20 |
197 | 김이안 | F시 | 2017-04-29 |
198 | 김시현 | H시 | 2019-04-19 |
199 | 김서우 | D시 | 2019-04-23 |
200 rows × 3 columns
▶등록일로부터 등록월을 추출해서 집계해보기.
kokyaku_data["등록연월"] = kokyaku_data["등록일"].dt.strftime("%Y%m")
rslt = kokyaku_data.groupby("등록연월").count()["고객이름"]
print(rslt)
print(len(kokyaku_data))
등록연월
201701 15
201702 11
201703 14
201704 15
201705 13
201706 14
201707 17
201801 13
201802 15
201803 17
201804 5
201805 19
201806 13
201807 17
201904 2
Name: 고객이름, dtype: int64
200
# 등록일 컬럼에 숫자 데이터가 남아있는지 확인
flg_is_serial = kokyaku_data["등록일"].astype("str").str.isdigit()
flg_is_serial.sum()
0
# how='left'로, uriage_data를 기준으로 결합한다.
join_data = pd.merge(uriage_data, kokyaku_data, left_on="customer_name", right_on="고객이름", how="left")
join_data = join_data.drop("customer_name", axis=1)
join_data
purchase_date | item_name | item_price | purchase_month | 고객이름 | 지역 | 등록일 | 등록연월 | |
---|---|---|---|---|---|---|---|---|
0 | 2019-06-13 18:02:00 | 상품A | 100.0 | 201906 | 김가온 | C시 | 2017-01-26 | 201701 |
1 | 2019-07-13 13:05:00 | 상품S | 1900.0 | 201907 | 김우찬 | C시 | 2018-04-07 | 201804 |
2 | 2019-05-11 19:42:00 | 상품A | 100.0 | 201905 | 김유찬 | A시 | 2018-06-19 | 201806 |
3 | 2019-02-12 23:40:00 | 상품Z | 2600.0 | 201902 | 김재현 | D시 | 2018-07-22 | 201807 |
4 | 2019-04-22 03:09:00 | 상품A | 100.0 | 201904 | 김강현 | D시 | 2017-06-07 | 201706 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
2994 | 2019-02-15 02:56:00 | 상품Y | 2500.0 | 201902 | 김정민 | B시 | 2017-07-01 | 201707 |
2995 | 2019-06-22 04:03:00 | 상품M | 1300.0 | 201906 | 김재원 | E시 | 2018-03-31 | 201803 |
2996 | 2019-03-29 11:14:00 | 상품Q | 1700.0 | 201903 | 김지율 | B시 | 2017-03-15 | 201703 |
2997 | 2019-07-14 12:56:00 | 상품H | 800.0 | 201907 | 김승주 | E시 | 2018-07-15 | 201807 |
2998 | 2019-07-21 00:31:00 | 상품D | 400.0 | 201907 | 정준기 | B시 | 2017-02-05 | 201702 |
2999 rows × 8 columns
⏺컬럼을 약간 정리한 후에 가공한 데이터를 저장해두자
dump_data = join_data[["purchase_date", "purchase_month", "item_name", "item_price", "고객이름", "지역", "등록일"]]
dump_data
purchase_date | purchase_month | item_name | item_price | 고객이름 | 지역 | 등록일 | |
---|---|---|---|---|---|---|---|
0 | 2019-06-13 18:02:00 | 201906 | 상품A | 100.0 | 김가온 | C시 | 2017-01-26 |
1 | 2019-07-13 13:05:00 | 201907 | 상품S | 1900.0 | 김우찬 | C시 | 2018-04-07 |
2 | 2019-05-11 19:42:00 | 201905 | 상품A | 100.0 | 김유찬 | A시 | 2018-06-19 |
3 | 2019-02-12 23:40:00 | 201902 | 상품Z | 2600.0 | 김재현 | D시 | 2018-07-22 |
4 | 2019-04-22 03:09:00 | 201904 | 상품A | 100.0 | 김강현 | D시 | 2017-06-07 |
... | ... | ... | ... | ... | ... | ... | ... |
2994 | 2019-02-15 02:56:00 | 201902 | 상품Y | 2500.0 | 김정민 | B시 | 2017-07-01 |
2995 | 2019-06-22 04:03:00 | 201906 | 상품M | 1300.0 | 김재원 | E시 | 2018-03-31 |
2996 | 2019-03-29 11:14:00 | 201903 | 상품Q | 1700.0 | 김지율 | B시 | 2017-03-15 |
2997 | 2019-07-14 12:56:00 | 201907 | 상품H | 800.0 | 김승주 | E시 | 2018-07-15 |
2998 | 2019-07-21 00:31:00 | 201907 | 상품D | 400.0 | 정준기 | B시 | 2017-02-05 |
2999 rows × 7 columns
dump_data.to_csv("dump_data.csv", index=False)
import_data = pd.read_csv("dump_data.csv")
import_data
purchase_date | purchase_month | item_name | item_price | 고객이름 | 지역 | 등록일 | |
---|---|---|---|---|---|---|---|
0 | 2019-06-13 18:02:00 | 201906 | 상품A | 100.0 | 김가온 | C시 | 2017-01-26 |
1 | 2019-07-13 13:05:00 | 201907 | 상품S | 1900.0 | 김우찬 | C시 | 2018-04-07 |
2 | 2019-05-11 19:42:00 | 201905 | 상품A | 100.0 | 김유찬 | A시 | 2018-06-19 |
3 | 2019-02-12 23:40:00 | 201902 | 상품Z | 2600.0 | 김재현 | D시 | 2018-07-22 |
4 | 2019-04-22 03:09:00 | 201904 | 상품A | 100.0 | 김강현 | D시 | 2017-06-07 |
... | ... | ... | ... | ... | ... | ... | ... |
2994 | 2019-02-15 02:56:00 | 201902 | 상품Y | 2500.0 | 김정민 | B시 | 2017-07-01 |
2995 | 2019-06-22 04:03:00 | 201906 | 상품M | 1300.0 | 김재원 | E시 | 2018-03-31 |
2996 | 2019-03-29 11:14:00 | 201903 | 상품Q | 1700.0 | 김지율 | B시 | 2017-03-15 |
2997 | 2019-07-14 12:56:00 | 201907 | 상품H | 800.0 | 김승주 | E시 | 2018-07-15 |
2998 | 2019-07-21 00:31:00 | 201907 | 상품D | 400.0 | 정준기 | B시 | 2017-02-05 |
2999 rows × 7 columns
#상품별 집계
byItem = import_data.pivot_table(index="purchase_month", columns="item_name", aggfunc="size", fill_value=0)
byItem
item_name | 상품A | 상품B | 상품C | 상품D | 상품E | 상품F | 상품G | 상품H | 상품I | 상품J | ... | 상품Q | 상품R | 상품S | 상품T | 상품U | 상품V | 상품W | 상품X | 상품Y | 상품Z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
purchase_month | |||||||||||||||||||||
201901 | 18 | 13 | 19 | 17 | 18 | 15 | 11 | 16 | 18 | 17 | ... | 17 | 21 | 20 | 17 | 7 | 22 | 13 | 14 | 10 | 0 |
201902 | 19 | 14 | 26 | 21 | 16 | 14 | 14 | 17 | 12 | 14 | ... | 22 | 22 | 22 | 23 | 19 | 22 | 24 | 16 | 11 | 1 |
201903 | 17 | 21 | 20 | 17 | 9 | 27 | 14 | 18 | 12 | 16 | ... | 23 | 16 | 20 | 12 | 23 | 18 | 16 | 21 | 16 | 0 |
201904 | 17 | 19 | 24 | 20 | 18 | 17 | 14 | 11 | 18 | 13 | ... | 20 | 20 | 16 | 16 | 11 | 15 | 14 | 16 | 20 | 0 |
201905 | 24 | 14 | 16 | 14 | 19 | 18 | 23 | 15 | 16 | 11 | ... | 13 | 22 | 18 | 16 | 16 | 9 | 21 | 16 | 20 | 0 |
201906 | 24 | 12 | 11 | 19 | 13 | 18 | 15 | 13 | 19 | 22 | ... | 15 | 16 | 21 | 12 | 18 | 20 | 17 | 15 | 13 | 0 |
201907 | 20 | 20 | 17 | 17 | 12 | 17 | 19 | 19 | 19 | 23 | ... | 15 | 19 | 23 | 21 | 13 | 28 | 16 | 18 | 12 | 0 |
7 rows × 26 columns
# 구입 연월, 매출 금액 집계
byPrice = import_data.pivot_table(index="purchase_month", columns="item_name", values="item_price", aggfunc="sum", fill_value=0)
byPrice
item_name | 상품A | 상품B | 상품C | 상품D | 상품E | 상품F | 상품G | 상품H | 상품I | 상품J | ... | 상품Q | 상품R | 상품S | 상품T | 상품U | 상품V | 상품W | 상품X | 상품Y | 상품Z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
purchase_month | |||||||||||||||||||||
201901 | 1800 | 2600 | 5700 | 6800 | 9000 | 9000 | 7700 | 12800 | 16200 | 17000 | ... | 28900 | 37800 | 38000 | 34000 | 14700 | 48400 | 29900 | 33600 | 25000 | 0 |
201902 | 1900 | 2800 | 7800 | 8400 | 8000 | 8400 | 9800 | 13600 | 10800 | 14000 | ... | 37400 | 39600 | 41800 | 46000 | 39900 | 48400 | 55200 | 38400 | 27500 | 2600 |
201903 | 1700 | 4200 | 6000 | 6800 | 4500 | 16200 | 9800 | 14400 | 10800 | 16000 | ... | 39100 | 28800 | 38000 | 24000 | 48300 | 39600 | 36800 | 50400 | 40000 | 0 |
201904 | 1700 | 3800 | 7200 | 8000 | 9000 | 10200 | 9800 | 8800 | 16200 | 13000 | ... | 34000 | 36000 | 30400 | 32000 | 23100 | 33000 | 32200 | 38400 | 50000 | 0 |
201905 | 2400 | 2800 | 4800 | 5600 | 9500 | 10800 | 16100 | 12000 | 14400 | 11000 | ... | 22100 | 39600 | 34200 | 32000 | 33600 | 19800 | 48300 | 38400 | 50000 | 0 |
201906 | 2400 | 2400 | 3300 | 7600 | 6500 | 10800 | 10500 | 10400 | 17100 | 22000 | ... | 25500 | 28800 | 39900 | 24000 | 37800 | 44000 | 39100 | 36000 | 32500 | 0 |
201907 | 2000 | 4000 | 5100 | 6800 | 6000 | 10200 | 13300 | 15200 | 17100 | 23000 | ... | 25500 | 34200 | 43700 | 42000 | 27300 | 61600 | 36800 | 43200 | 30000 | 0 |
7 rows × 26 columns
# 구입 연월, 고객별 구입 수 집계 결과
byCustomer = import_data.pivot_table(index="purchase_month", columns="고객이름", aggfunc="size", fill_value=0)
byCustomer
고객이름 | 김가온 | 김강민 | 김강현 | 김건우 | 김건희 | 김경민 | 김규민 | 김규현 | 김다온 | 김대현 | ... | 김현수 | 김현승 | 김현우 | 김현준 | 김현진 | 김호준 | 정도형 | 정영훈 | 정우석 | 정준기 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
purchase_month | |||||||||||||||||||||
201901 | 1 | 2 | 1 | 2 | 5 | 3 | 1 | 1 | 1 | 5 | ... | 3 | 2 | 4 | 2 | 1 | 1 | 4 | 4 | 4 | 3 |
201902 | 3 | 1 | 4 | 3 | 3 | 5 | 2 | 3 | 1 | 5 | ... | 5 | 3 | 7 | 0 | 0 | 1 | 2 | 0 | 2 | 2 |
201903 | 4 | 0 | 3 | 1 | 1 | 2 | 2 | 1 | 1 | 5 | ... | 3 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 3 | 4 |
201904 | 1 | 0 | 3 | 0 | 2 | 1 | 2 | 6 | 2 | 4 | ... | 0 | 4 | 3 | 1 | 1 | 2 | 2 | 4 | 3 | 1 |
201905 | 0 | 0 | 2 | 2 | 1 | 4 | 6 | 3 | 3 | 1 | ... | 2 | 2 | 1 | 4 | 3 | 0 | 0 | 0 | 1 | 2 |
201906 | 5 | 0 | 3 | 0 | 2 | 1 | 6 | 1 | 1 | 1 | ... | 2 | 4 | 1 | 0 | 4 | 2 | 2 | 0 | 0 | 2 |
201907 | 3 | 1 | 3 | 2 | 2 | 1 | 4 | 1 | 4 | 3 | ... | 2 | 1 | 0 | 4 | 0 | 2 | 6 | 1 | 2 | 3 |
7 rows × 199 columns
# 구입 연월, 지역별 판매 수 집계
byRegion = import_data.pivot_table(index="purchase_month", columns="지역", aggfunc="size", fill_value=0)
byRegion
지역 | A시 | B시 | C시 | D시 | E시 | F시 | G시 | H시 |
---|---|---|---|---|---|---|---|---|
purchase_month | ||||||||
201901 | 59 | 55 | 72 | 34 | 49 | 57 | 49 | 42 |
201902 | 71 | 46 | 65 | 48 | 61 | 52 | 43 | 63 |
201903 | 64 | 52 | 57 | 43 | 52 | 59 | 51 | 59 |
201904 | 64 | 48 | 54 | 45 | 48 | 58 | 40 | 52 |
201905 | 57 | 52 | 68 | 48 | 59 | 65 | 35 | 43 |
201906 | 53 | 47 | 61 | 30 | 51 | 51 | 58 | 58 |
201907 | 76 | 53 | 61 | 42 | 54 | 64 | 47 | 54 |
▶import_data는 구매이력이 있는 uriage_data를 기준으로 왼쪽 조인된 결과이기 때문에,
기간 동안 구매이력이 없는 사람을 확인하려면 고객 대장을 기준으로 오른쪽 조인으로 다시 확인해야 된다.
# 집계 기간에 구매이력이 없는 사람
away_data = pd.merge(uriage_data, kokyaku_data, left_on="customer_name", right_on="고객이름", how="right") #고객 대장을 기준으로 합치기
away_data[away_data["purchase_date"].isnull()][["고객이름", "등록일"]]
고객이름 | 등록일 | |
---|---|---|
2999 | 김서우 | 2019-04-23 |