응용-매출,가장 많이 팔린 아이템 확인하기
학습목표
- 아이템별 지표 확인하기
- 시간별 지역별 판매 지표 확인하기
import numpy as np
import pandas as pd
# seaborn
import seaborn as sns
COLORS = sns.color_palette()
%matplotlib inline
# 그래프를 노트북 안에 그리기 위해 설정
# matplotlib 한글 폰트 출력코드
import matplotlib.pyplot as plt
import matplotlib
matplotlib.font_manager._rebuild()
from matplotlib import style
from matplotlib import font_manager , rc
import platform
platform.platform() #'Windows-10-10.0.19041-SP0' / 시스템 운영체제 확인 가능
try :
if platform.sys() == 'windows':
# 윈도우인 경우
font_name = fontmanager.FontProperties(fname="c:/windows/Fonts/나눔고딕.ttf").get_name()
rc('font', family=font_name)
else:
#max 인 경우
rc('font', family='AppleGothic')
except:
pass
matplotlib.rcParams['axes.unicode_minus'] = False
# rcParams 폰트 크기를 지정하기
# 그래프에서 마이너스 기호가 표시되도록 하는 설정입니다.
Matplotlib is building the font cache; this may take a moment.
plt.rcParams["font.family"] = 'serif'
print (plt.rcParams['font.family'] ) #현재 설정 된 폰트 ['sans-serif']이다
['serif']
!conda install fonts-nanum*
Collecting package metadata (current_repodata.json): ...working... failed
CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://repo.anaconda.com/pkgs/main/win-64/current_repodata.json>
Elapsed: -
An HTTP error occurred when trying to retrieve this URL.
HTTP errors are often intermittent, and a simple retry will get you on your way.
If your current network has https://www.anaconda.com blocked, please file
a support request with your network engineering team.
'https://repo.anaconda.com/pkgs/main/win-64'
#I was able to get rid of the RuntimeWarning by declaring the font usage with:
#plt.rcParams["font.serif"] = "nanumGothic"
# 참조 -https://github.com/matplotlib/matplotlib/issues/17007
import matplotlib.pyplot as plt
plt.rcParams["font.serif"]= "nanumGothic"
fig, ax = plt.subplots()
ax.set_title('my font')
ax.set_xlabel(r'my font $\alpha\beta\gamma$')
ax.set_yticks([-1,0,1])
plt.show()
데이터 로딩
- 정제된 데이터 사용(OnlineRetailCleanhangle.csv)
dtypes = {
'상품 가격': np.float32,
'고객 아이디': np.int32,
'주문 수량': np.int32
}
# 한글 깨짐 해결 방안
#[거의 해결] 해결책 (3) - Excel에서 인코딩 옵션 변경
# 파일을 우선 Excel에서 열어줍니다.
# 파일 - 다른 이름으로 저장에서 - CSV UTF-8 (쉼표로 분리) 로 변경하여 저장합니다.
retail = pd.read_csv('./OnlineRetailCleanhangle.csv',
dtype=dtypes ,
encoding = 'utf-8')
retail.head()
Unnamed: 0 | 주문 번호 | 아이템 아이디 | 상품 설명 | 주문 수량 | 주문 시각 | 상품 가격 | 고객 아이디 | 고객 거주 지역 | 총 주문 가격 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 12/1/2010 8:26 | 2.55 | 17850 | United Kingdom | 15.30 |
1 | 1 | 536365 | 71053 | WHITE METAL LANTERN | 6 | 12/1/2010 8:26 | 3.39 | 17850 | United Kingdom | 20.34 |
2 | 2 | 536365 | 84406B | CREAM CUPID HEARTS COAT HANGER | 8 | 12/1/2010 8:26 | 2.75 | 17850 | United Kingdom | 22.00 |
3 | 3 | 536365 | 84029G | KNITTED UNION FLAG HOT WATER BOTTLE | 6 | 12/1/2010 8:26 | 3.39 | 17850 | United Kingdom | 20.34 |
4 | 4 | 536365 | 84029E | RED WOOLLY HOTTIE WHITE HEART. | 6 | 12/1/2010 8:26 | 3.39 | 17850 | United Kingdom | 20.34 |
날짜 타입 데이터 변환
- 문자열로 로딩하는 것보다 date/datetime 타입으로 로딩하는 것이 분석에 용이
retail['주문 시각'] = pd.to_datetime(retail['주문 시각'], infer_datetime_format=True)# infer_datetime_format=True 날짜시간 포맷 추정해서 파싱하기
retail.info() #5 주문 시각 397884 non-null datetime64[ns] 바꿔짐
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 397884 entries, 0 to 397883
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Unnamed: 0 397884 non-null int64
1 주문 번호 397884 non-null int64
2 아이템 아이디 397884 non-null object
3 상품 설명 397884 non-null object
4 주문 수량 397884 non-null int32
5 주문 시각 397884 non-null datetime64[ns]
6 상품 가격 397884 non-null float32
7 고객 아이디 397884 non-null int32
8 고객 거주 지역 397884 non-null object
9 총 주문 가격 397884 non-null float64
dtypes: datetime64[ns](1), float32(1), float64(1), int32(2), int64(2), object(3)
memory usage: 25.8+ MB
해당 기간 동안의 매출
- 전체 매출
- 국가별 매출
- 월별 매출
- 요일별 매출
- 시간별 매출
전체 매출
total_revenue = retail['총 주문 가격'].sum()
total_revenue
8911407.904
국가별 매출
rev_by_countries = retail.groupby('고객 거주 지역').sum()['총 주문 가격'].sort_values(ascending=False) # 내림차순 : ascending=False
rev_by_countries
고객 거주 지역
United Kingdom 7.308392e+06
Netherlands 2.854463e+05
EIRE 2.655459e+05
Germany 2.288671e+05
France 2.090240e+05
Australia 1.385213e+05
Spain 6.157711e+04
Switzerland 5.644395e+04
Belgium 4.119634e+04
Sweden 3.837833e+04
Japan 3.741637e+04
Norway 3.616544e+04
Portugal 3.343989e+04
Finland 2.254608e+04
Singapore 2.127929e+04
Channel Islands 2.045044e+04
Denmark 1.895534e+04
Italy 1.748324e+04
Cyprus 1.359038e+04
Austria 1.019868e+04
Poland 7.334650e+03
Israel 7.221690e+03
Greece 4.760520e+03
Iceland 4.310000e+03
Canada 3.666380e+03
USA 3.580390e+03
Malta 2.725590e+03
Unspecified 2.667070e+03
United Arab Emirates 1.902280e+03
Lebanon 1.693880e+03
Lithuania 1.661060e+03
European Community 1.300250e+03
Brazil 1.143600e+03
RSA 1.002310e+03
Czech Republic 8.267400e+02
Bahrain 5.484000e+02
Saudi Arabia 1.459200e+02
Name: 총 주문 가격, dtype: float64
plot = rev_by_countries.plot(kind='bar', color=COLORS[-1], figsize=(20, 10))
plot.set_xlabel('고객 거주 지역', fontsize=25)
plot.set_ylabel('매출', fontsize=25)
plot.set_title('국가별 매출', fontsize=23)
plot.set_xticklabels(labels=rev_by_countries.index, rotation=75, fontsize=13)
[Text(0, 0, 'United Kingdom'),
Text(1, 0, 'Netherlands'),
Text(2, 0, 'EIRE'),
Text(3, 0, 'Germany'),
Text(4, 0, 'France'),
Text(5, 0, 'Australia'),
Text(6, 0, 'Spain'),
Text(7, 0, 'Switzerland'),
Text(8, 0, 'Belgium'),
Text(9, 0, 'Sweden'),
Text(10, 0, 'Japan'),
Text(11, 0, 'Norway'),
Text(12, 0, 'Portugal'),
Text(13, 0, 'Finland'),
Text(14, 0, 'Singapore'),
Text(15, 0, 'Channel Islands'),
Text(16, 0, 'Denmark'),
Text(17, 0, 'Italy'),
Text(18, 0, 'Cyprus'),
Text(19, 0, 'Austria'),
Text(20, 0, 'Poland'),
Text(21, 0, 'Israel'),
Text(22, 0, 'Greece'),
Text(23, 0, 'Iceland'),
Text(24, 0, 'Canada'),
Text(25, 0, 'USA'),
Text(26, 0, 'Malta'),
Text(27, 0, 'Unspecified'),
Text(28, 0, 'United Arab Emirates'),
Text(29, 0, 'Lebanon'),
Text(30, 0, 'Lithuania'),
Text(31, 0, 'European Community'),
Text(32, 0, 'Brazil'),
Text(33, 0, 'RSA'),
Text(34, 0, 'Czech Republic'),
Text(35, 0, 'Bahrain'),
Text(36, 0, 'Saudi Arabia')]
rev_by_countries / total_revenue #비율 확인할 수 있다.
고객 거주 지역
United Kingdom 0.820116
Netherlands 0.032032
EIRE 0.029798
Germany 0.025682
France 0.023456
Australia 0.015544
Spain 0.006910
Switzerland 0.006334
Belgium 0.004623
Sweden 0.004307
Japan 0.004199
Norway 0.004058
Portugal 0.003752
Finland 0.002530
Singapore 0.002388
Channel Islands 0.002295
Denmark 0.002127
Italy 0.001962
Cyprus 0.001525
Austria 0.001144
Poland 0.000823
Israel 0.000810
Greece 0.000534
Iceland 0.000484
Canada 0.000411
USA 0.000402
Malta 0.000306
Unspecified 0.000299
United Arab Emirates 0.000213
Lebanon 0.000190
Lithuania 0.000186
European Community 0.000146
Brazil 0.000128
RSA 0.000112
Czech Republic 0.000093
Bahrain 0.000062
Saudi Arabia 0.000016
Name: 총 주문 가격, dtype: float64
그래프 유틸 함수
def plot_bar(df, xlabel, ylabel, title, color=COLORS[-7], figsize=(20, 10), rotation=45):
plot = df.plot(kind='bar', color=color, figsize=figsize)
plot.set_xlabel(xlabel, fontsize=25)
plot.set_ylabel(ylabel, fontsize=25)
plot.set_title(title, fontsize=30)
plot.set_xticklabels(labels=df.index, rotation=75 , fontsize=13)
plot_bar(rev_by_countries, '고객 거주 지역', '매출', '국가별 매출')
grouped = retail.groupby('고객 거주 지역')
grouped.first()
Unnamed: 0 | 주문 번호 | 아이템 아이디 | 상품 설명 | 주문 수량 | 주문 시각 | 상품 가격 | 고객 아이디 | 총 주문 가격 | |
---|---|---|---|---|---|---|---|---|---|
고객 거주 지역 | |||||||||
Australia | 197 | 536389 | 22941 | CHRISTMAS LIGHTS 10 REINDEER | 6 | 2010-12-01 10:03:00 | 8.50 | 12431 | 51.00 |
Austria | 34293 | 539330 | 37449 | CERAMIC CAKE STAND + HANGING CAKES | 8 | 2010-12-17 09:38:00 | 8.50 | 12370 | 68.00 |
Bahrain | 181140 | 552449 | 22693 | GROW A FLYTRAP OR SUNFLOWER IN TIN | 24 | 2011-05-09 13:49:00 | 1.25 | 12355 | 30.00 |
Belgium | 7279 | 537026 | 84375 | SET OF 20 KIDS COOKIE CUTTERS | 12 | 2010-12-03 16:35:00 | 2.10 | 12395 | 25.20 |
Brazil | 157299 | 550201 | 22423 | REGENCY CAKESTAND 3 TIER | 16 | 2011-04-15 10:25:00 | 10.95 | 12769 | 175.20 |
Canada | 119191 | 546533 | 20886 | BOX OF 9 PEBBLE CANDLES | 12 | 2011-03-14 13:53:00 | 1.95 | 15388 | 23.40 |
Channel Islands | 20000 | 538002 | 22690 | DOORMAT HOME SWEET HOME BLUE | 2 | 2010-12-09 11:48:00 | 7.95 | 14932 | 15.90 |
Cyprus | 29732 | 538826 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 64 | 2010-12-14 12:58:00 | 2.55 | 12370 | 163.20 |
Czech Republic | 103598 | 545072 | 22930 | BAKING MOULD HEART MILK CHOCOLATE | 18 | 2011-02-28 08:43:00 | 2.55 | 12781 | 45.90 |
Denmark | 20017 | 538003 | 22847 | BREAD BIN DINER STYLE IVORY | 8 | 2010-12-09 12:05:00 | 14.95 | 12429 | 119.60 |
EIRE | 1404 | 536540 | 22968 | ROSE COTTAGE KEEPSAKE BOX | 4 | 2010-12-01 14:05:00 | 9.95 | 14911 | 39.80 |
European Community | 168149 | 551013 | 22839 | 3 TIER CAKE TIN GREEN AND CREAM | 1 | 2011-04-26 10:54:00 | 14.95 | 15108 | 14.95 |
Finland | 34083 | 539318 | 84992 | 72 SWEETHEART FAIRY CAKE CASES | 72 | 2010-12-16 19:09:00 | 0.55 | 12348 | 39.60 |
France | 26 | 536370 | 22728 | ALARM CLOCK BAKELIKE PINK | 24 | 2010-12-01 08:45:00 | 3.75 | 12583 | 90.00 |
Germany | 1109 | 536527 | 22809 | SET OF 6 T-LIGHTS SANTA | 6 | 2010-12-01 13:04:00 | 2.95 | 12662 | 17.70 |
Greece | 69007 | 541932 | 22699 | ROSES REGENCY TEACUP AND SAUCER | 24 | 2011-01-24 11:39:00 | 2.55 | 14439 | 61.20 |
Iceland | 14938 | 537626 | 85116 | BLACK CANDELABRA T-LIGHT HOLDER | 12 | 2010-12-07 14:57:00 | 2.10 | 12347 | 25.20 |
Israel | 91580 | 544108 | 22245 | HOOK, 1 HANGER ,MAGIC GARDEN | 24 | 2011-02-16 10:53:00 | 0.85 | 12653 | 20.40 |
Italy | 7214 | 537022 | 22791 | T-LIGHT GLASS FLUTED ANTIQUE | 12 | 2010-12-03 15:45:00 | 1.25 | 12725 | 15.00 |
Japan | 9783 | 537218 | 85016 | SET OF 6 VINTAGE NOTELETS KIT | 6 | 2010-12-05 15:46:00 | 2.55 | 12763 | 15.30 |
Lebanon | 72985 | 542276 | 82551 | LAUNDRY 15C METAL SIGN | 12 | 2011-01-27 10:19:00 | 1.45 | 12764 | 17.40 |
Lithuania | 7986 | 537081 | 22409 | MONEY BOX BISCUITS DESIGN | 12 | 2010-12-05 12:00:00 | 1.25 | 15332 | 15.00 |
Malta | 217684 | 555931 | 22784 | LANTERN CREAM GAZEBO | 3 | 2011-06-08 08:31:00 | 4.95 | 17828 | 14.85 |
Netherlands | 385 | 536403 | 22867 | HAND WARMER BIRD DESIGN | 96 | 2010-12-01 11:27:00 | 1.85 | 12791 | 177.60 |
Norway | 1236 | 536532 | 84692 | BOX OF 24 COCKTAIL PARASOLS | 50 | 2010-12-01 13:24:00 | 0.42 | 12433 | 21.00 |
Poland | 6608 | 536971 | 21733 | RED HANGING HEART T-LIGHT HOLDER | 32 | 2010-12-03 13:40:00 | 2.55 | 12779 | 81.60 |
Portugal | 7134 | 536990 | 21992 | VINTAGE PAISLEY STATIONERY SET | 6 | 2010-12-03 15:14:00 | 2.95 | 12793 | 17.70 |
RSA | 395472 | 571035 | 21238 | RED RETROSPOT CUP | 8 | 2011-10-13 12:50:00 | 0.85 | 12446 | 6.80 |
Saudi Arabia | 100810 | 544838 | 22915 | ASSORTED BOTTLE TOP MAGNETS | 12 | 2011-02-24 10:34:00 | 0.42 | 12565 | 5.04 |
Singapore | 70758 | 542102 | 21519 | GIN & TONIC DIET GREETING CARD | 72 | 2011-01-25 13:26:00 | 0.36 | 12744 | 25.92 |
Spain | 6421 | 536944 | 22383 | LUNCH BAG SUKI DESIGN | 70 | 2010-12-03 12:20:00 | 1.65 | 12557 | 115.50 |
Sweden | 30079 | 538848 | 85232B | SET OF 3 BABUSHKA STACKING TINS | 240 | 2010-12-14 13:28:00 | 4.95 | 17404 | 1188.00 |
Switzerland | 5320 | 536858 | 22326 | ROUND SNACK BOXES SET OF4 WOODLAND | 30 | 2010-12-03 10:36:00 | 2.95 | 13520 | 88.50 |
USA | 164464 | 550644 | 22722 | SET OF 6 SPICE TINS PANTRY DESIGN | 7 | 2011-04-19 16:19:00 | 3.95 | 12733 | 27.65 |
United Arab Emirates | 89570 | 543911 | 21485 | RETROSPOT HEART HOT WATER BOTTLE | 6 | 2011-02-14 12:46:00 | 4.95 | 17829 | 29.70 |
United Kingdom | 0 | 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 2010-12-01 08:26:00 | 2.55 | 17850 | 15.30 |
Unspecified | 152712 | 549687 | 20685 | DOORMAT RED RETROSPOT | 2 | 2011-04-11 13:29:00 | 7.95 | 12363 | 15.90 |
grouped_sr = grouped.size()
grouped_sr
고객 거주 지역
Australia 1182
Austria 398
Bahrain 17
Belgium 2031
Brazil 32
Canada 151
Channel Islands 748
Cyprus 614
Czech Republic 25
Denmark 380
EIRE 7236
European Community 60
Finland 685
France 8341
Germany 9040
Greece 145
Iceland 182
Israel 248
Italy 758
Japan 321
Lebanon 45
Lithuania 35
Malta 112
Netherlands 2359
Norway 1071
Poland 330
Portugal 1462
RSA 57
Saudi Arabia 9
Singapore 222
Spain 2484
Sweden 451
Switzerland 1841
USA 179
United Arab Emirates 68
United Kingdom 354321
Unspecified 244
dtype: int64
grouped_sr.index
Index(['Australia', 'Austria', 'Bahrain', 'Belgium', 'Brazil', 'Canada',
'Channel Islands', 'Cyprus', 'Czech Republic', 'Denmark', 'EIRE',
'European Community', 'Finland', 'France', 'Germany', 'Greece',
'Iceland', 'Israel', 'Italy', 'Japan', 'Lebanon', 'Lithuania', 'Malta',
'Netherlands', 'Norway', 'Poland', 'Portugal', 'RSA', 'Saudi Arabia',
'Singapore', 'Spain', 'Sweden', 'Switzerland', 'USA',
'United Arab Emirates', 'United Kingdom', 'Unspecified'],
dtype='object', name='고객 거주 지역')
grouped_sr.plot(kind='pie',
figsize=(15,25),
textprops={'size':10}
)
plt.title('국가별 매출', size=50)
plt.legend(grouped_sr.index, loc='best')
plt.show()
- grouped_sr.index 가 많다보니 글씨가 잘 안보인다.
grouped_sr.plot.pie(figsize=(17,25),
shadow = True,
textprops = {'fontsize' : 14},
autopct='%1.0f%%') # 파이 조각의 전체 대비 백분율
plt.title('국가별 매출', size=50)
plt.legend(grouped_sr.index, loc='best')
plt.show()
월별 매출
retail['주문 시각'].sort_values(ascending=False)
397883 2011-12-09 12:50:00
397876 2011-12-09 12:50:00
397870 2011-12-09 12:50:00
397871 2011-12-09 12:50:00
397872 2011-12-09 12:50:00
...
3 2010-12-01 08:26:00
1 2010-12-01 08:26:00
5 2010-12-01 08:26:00
6 2010-12-01 08:26:00
0 2010-12-01 08:26:00
Name: 주문 시각, Length: 397884, dtype: datetime64[ns]
def extract_month(date):
month = str(date.month)
if date.month < 10:
month = '0' + month
return str(date.year) + month
rev_by_month = retail.set_index('주문 시각').groupby(extract_month).sum()['총 주문 가격']
rev_by_month
201012 572713.890
201101 569445.040
201102 447137.350
201103 595500.760
201104 469200.361
201105 678594.560
201106 661213.690
201107 600091.011
201108 645343.900
201109 952838.382
201110 1039318.790
201111 1161817.380
201112 518192.790
Name: 총 주문 가격, dtype: float64
plot_bar(rev_by_month, '월별', '매출', '월별 매출')
요일별 매출
rev_by_dow = retail.set_index('주문 시각').groupby(lambda date:date.dayofweek).sum()['총 주문 가격']
rev_by_dow ## dayofweek - [Monday 0 ~ Sunday 6]
0 1367146.411
1 1700634.631
2 1588336.170
3 1976859.070
4 1485917.401
6 792514.221
Name: 총 주문 가격, dtype: float64
DAY_OF_WEEK = np.array(['월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'])
rev_by_dow.index = DAY_OF_WEEK[rev_by_dow.index]
plot_bar(rev_by_dow, '요일', '매출', '요일 별 매출')
시간별 매출
rev_by_hour = retail.set_index('주문 시각').groupby(lambda date:date.hour).sum()['총 주문 가격']
plot_bar(rev_by_hour, '시간', '매출', '시간별 매출')
매출 데이터로부터 insight
- 전체 매출의 82%가 UK에서 발생
- 11년도의 가장 많은 주문이 발생한 달 11월(12월의 전체 데이터가 반영이 되진 않았음)
- 11, 12월의 판매량이 압도(블랙프라이데이, 사이버먼데이, 크리스마스 휴일)
- 일주일중 목요일까지는 성장세를 보이다가, 이후로 하락(토요일에는 주문X)
- 7시를 시작으로 주문이 시작되어 12시까지 증가세, 15시까지 하락을, 15시 이후 부터 급락)
제품별 metrics
- Top 10 판매 제품
- Top 10 매출 제품
top_selling = retail.groupby('아이템 아이디').sum()['주문 수량'].sort_values(ascending=False)[:3]
top_selling
아이템 아이디
23843 80995
23166 77916
84077 54415
Name: 주문 수량, dtype: int32
top_revenue = retail.groupby('아이템 아이디').sum()['총 주문 가격'].sort_values(ascending=False)[:10]
top_revenue
아이템 아이디
23843 168469.60
22423 142592.95
85123A 100603.50
85099B 85220.78
23166 81416.73
POST 77803.96
47566 68844.33
84879 56580.34
M 53779.93
23084 51346.20
Name: 총 주문 가격, dtype: float64
top 3 아이템의 월별 판매량 추이
retail.set_index('주문 시각').groupby(['아이템 아이디', extract_month]).sum()[['주문 수량', '총 주문 가격']].loc[top_selling.index]
주문 수량 | 총 주문 가격 | ||
---|---|---|---|
아이템 아이디 | |||
23843 | 201112 | 80995 | 168469.60 |
23166 | 201101 | 74215 | 77183.60 |
201105 | 792 | 869.04 | |
201106 | 391 | 458.51 | |
201107 | 718 | 826.94 | |
201108 | 405 | 486.09 | |
201109 | 342 | 397.26 | |
201110 | 235 | 283.67 | |
201111 | 631 | 708.11 | |
201112 | 187 | 203.51 | |
84077 | 201012 | 5139 | 1150.47 |
201101 | 1488 | 385.44 | |
201102 | 3457 | 795.17 | |
201103 | 3888 | 943.20 | |
201104 | 10224 | 2281.44 | |
201105 | 4944 | 1249.44 | |
201106 | 1920 | 533.76 | |
201107 | 3600 | 982.56 | |
201108 | 2256 | 654.24 | |
201109 | 3462 | 985.70 | |
201110 | 8174 | 1953.98 | |
201111 | 4500 | 1294.20 | |
201112 | 1363 | 376.65 |
monthly_top3 = retail.set_index('주문 시각').groupby(['아이템 아이디', extract_month]).sum()[['주문 수량', '총 주문 가격']].loc[top_selling.index]
plot_bar(monthly_top3['총 주문 가격'], '아이템/월별', '매출', '탑 3 아이템 매출')