Food_analysis_1_chicken
Food analysis in Seoul_ using SK data hub
SK data hub는 SK Telecom에서 제공하는 여러 데이터가 모여있는 곳입니다. 여기서 ‘1월 서울시 치킨 판매업종 이용 통화량’을 분석해 보았습니다. 주의해야 할 점은 SK 텔레콤의 통화량 데이터와 업종 데이터를 기반으로 추출한 데이터이기에 전체 치킨집 서비스 이용 현황이 반영되어 있지 않음을 감안하여 데이터를 이용해야 한다는 점입니다. 그러나 모집단에 대한 일종의 표본집단으로 생각해 본다면 이 역시 유의미한 값들을 도출 가능하리라 봅니다.
//
같이 데이터 분석 스터디를 진행하시는 분의 블로그에서 아이디어를 얻었습니다. 원글은 R이며 저는 python으로 진행할 것입니다. 블로그의 주소는 다음.과 같습니다.
데이터는 다음.의 주소에서 다운받으실 수 있습니다.
STEP 1. Data Preprocessing(데이터 전처리)
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv("C:/Users/user/Desktop/janeshin/skdata/CALL_CHICKEN_01MONTH.csv")
df.head(20)
기준일 | 요일 | 성별 | 연령대 | 시도 | 시군구 | 읍면동 | 업종 | 통화건수 | |
---|---|---|---|---|---|---|---|---|---|
0 | 20190101 | 화 | 여 | 20대 | 서울특별시 | 강남구 | 도곡동 | 치킨 | 5 |
1 | 20190101 | 화 | 남 | 30대 | 서울특별시 | 강남구 | 삼성동 | 치킨 | 36 |
2 | 20190101 | 화 | 여 | 10대 | 서울특별시 | 강남구 | 대치동 | 치킨 | 5 |
3 | 20190101 | 화 | 여 | 40대 | 서울특별시 | 강남구 | 대치동 | 치킨 | 5 |
4 | 20190101 | 화 | 여 | 40대 | 서울특별시 | 강남구 | 일원동 | 치킨 | 5 |
5 | 20190101 | 화 | 남 | 10대 | 서울특별시 | 강남구 | 논현동 | 치킨 | 5 |
6 | 20190101 | 화 | 여 | 30대 | 서울특별시 | 강남구 | 일원동 | 치킨 | 5 |
7 | 20190101 | 화 | 남 | 50대 | 서울특별시 | 강남구 | 대치동 | 치킨 | 5 |
8 | 20190101 | 화 | 남 | 30대 | 서울특별시 | 강남구 | 수서동 | 치킨 | 5 |
9 | 20190101 | 화 | 남 | 20대 | 서울특별시 | 강남구 | 논현동 | 치킨 | 18 |
10 | 20190101 | 화 | 남 | 30대 | 서울특별시 | 강남구 | 역삼동 | 치킨 | 28 |
11 | 20190101 | 화 | 여 | 30대 | 서울특별시 | 강남구 | 역삼동 | 치킨 | 29 |
12 | 20190101 | 화 | 여 | 60대이상 | 서울특별시 | 강남구 | 논현동 | 치킨 | 5 |
13 | 20190101 | 화 | 남 | 40대 | 서울특별시 | 강남구 | 자곡동 | 치킨 | 5 |
14 | 20190101 | 화 | 여 | 10대 | 서울특별시 | 강남구 | 논현동 | 치킨 | 5 |
15 | 20190101 | 화 | 여 | 20대 | 서울특별시 | 강남구 | 삼성동 | 치킨 | 12 |
16 | 20190101 | 화 | 남 | 60대이상 | 서울특별시 | 강남구 | 논현동 | 치킨 | 5 |
17 | 20190101 | 화 | 여 | 40대 | 서울특별시 | 강남구 | 수서동 | 치킨 | 5 |
18 | 20190101 | 화 | 여 | 40대 | 서울특별시 | 강남구 | 세곡동 | 치킨 | 5 |
19 | 20190101 | 화 | 남 | 40대 | 서울특별시 | 강남구 | 역삼동 | 치킨 | 37 |
df['기준일'].unique()
array([20190101, 20190102, 20190103, 20190104, 20190105, 20190106,
20190107, 20190108, 20190109, 20190110, 20190111, 20190112,
20190113, 20190114, 20190115, 20190116, 20190117, 20190118,
20190119, 20190120, 20190121, 20190122, 20190123, 20190124,
20190125, 20190126, 20190127, 20190128, 20190129, 20190130,
20190131], dtype=int64)
모든 날짜에 대해 데이터가 존재한다는 점을 확인할 수 있습니다.
빈 값이 있는지 체크해봅시다.
dataframe에 있어서 빈 값이 있는지의 체크는 다음과 같은 방법으로 할 수 있습니다.
- (모든 데이터에 대해 null여부 체크 원하는 경우)df.isnull을 통해 null값인지에 대한 boolean value를 받는 경우
df.isnull()
- (null인 값의 개수를 원하는 경우)
df.isnull.sum() :각 column에 대한 null개수
아래의 링크.를 참조하면 됩니다.
df.isnull().sum()
기준일 0
요일 0
성별 0
연령대 0
시도 0
시군구 0
읍면동 0
업종 0
통화건수 0
dtype: int64
df.isnull().sum().sum()
0
각 행들을 살펴봅시다.
df['기준일'].value_counts()
20190125 1147
20190112 1060
20190111 1056
20190118 1049
20190126 1046
20190105 1022
20190116 1018
20190122 1015
20190119 1014
20190104 1003
20190127 995
20190120 975
20190113 974
20190101 965
20190106 952
20190115 944
20190108 939
20190123 938
20190124 938
20190103 935
20190110 935
20190131 929
20190109 923
20190117 918
20190130 907
20190107 890
20190121 885
20190102 884
20190129 876
20190128 860
20190114 858
Name: 기준일, dtype: int64
2019년 1월 25일에 무슨 일이 있었길래 이렇게 치킨이 많이 팔렸지?하고 검색해보니 아시안컵 8강 이었군요. (ㅠㅠ) 1월 12일도 마찬가지로 키르기스스탄과의 경기였고요. 축구 +주말(금,토,일) 외의 여러 요소들이 영향을 끼쳤다고 보입니다.
df['시군구'].value_counts()
강남구 2381
강서구 1916
중구 1717
영등포구 1688
마포구 1552
서대문구 1476
서초구 1415
구로구 1396
성북구 1377
용산구 1352
강동구 1295
송파구 1162
동작구 1135
동대문구 1084
은평구 1013
노원구 993
중랑구 914
종로구 906
강북구 868
성동구 860
양천구 839
금천구 831
광진구 678
관악구 560
도봉구 442
Name: 시군구, dtype: int64
신기한 점은, 이 결과가 서울시 인구 순위와는 비례하지 않는다는 것입니다. 중구의 경우 서울시 전체에서 인구가 가장 적은데 저 순위에는 높군요. 중구는 낮에 유입된 인구와 밤에 빠져나가는 인구(퇴근 등) 의 차이가 큰데
서울시 인구 순위는 실제 거주민만을 대상으로 하고 있어서 그런지 치킨 주문 수와는 비례하지 않습니다. 중구에 실제 거주하는 사람들만이 치킨을 시켜 먹는건 아닐 테니까요.
+(추가) 위와 같이 Data row수를 기준으로 집계하는 것이 아닌, 통화량을 기준으로 하면 다른 결과가 있을 것이라는 코멘트가 있었습니다. 그래서 통화량 기준으로 해 봅시다.
groupedByDistrict = df['통화건수'].groupby(df['시군구'])
groupedByDistrict.sum()
시군구
강남구 27901
강동구 15933
강북구 19005
강서구 39391
관악구 3071
광진구 3411
구로구 21915
금천구 6962
노원구 21926
도봉구 2283
동대문구 10205
동작구 27842
마포구 12293
서대문구 20267
서초구 24069
성동구 19323
성북구 12633
송파구 22467
양천구 5653
영등포구 15910
용산구 9717
은평구 14454
종로구 7409
중구 18622
중랑구 12671
Name: 통화건수, dtype: int64
Data row수를 기준으로 집계하는 것보다, 지역별 통화건수의 sum을 계산하는 게 더 구별 인구 순위와 비례하는 결과가 나왔네요.
도대체 왜 이런 결과가 나왔을까??하는 생각에 데이터를 좀더 자세히 뜯어 보니 ‘통화 건수’에 대해 잘못된 해석을 한 것 같습니다. 한 row의 통화 건수 = 1사람 인 줄 알았는데
ex) 20190101 화 남 40대 서울특별시 강남구 역삼동 치킨 37
이면 저 37은 ‘‘19년 1월 1일(화)에 서울시 강남구 역삼동에 사는 40대의 남성 ‘들’이 치킨을 주문한 횟수’’ 라는 것입니다. 왜냐면 1월 1일의 데이터만 봐도 똑같은 data가 없거든요. #### 아래 코드를 보면 어떠한 data도 똑같지 않다는 점을 알 수 있습니다.
df.duplicated().sum() # 어떤 data도 똑같지 않습니다
0
STEP 2. 어떻게 분석을 해볼까?Domain Knowledge
데이터에 대해 어느 정도 살펴보았으니 이제는 어떠한 식으로 이 data를 다룰지 생각해 봅시다.
요식업계에 대한 지식은 거의 없지만 몇 가지 가설을 생각해 봅시다.
-
연령대에 따라 다르지 않을까?
- 치킨은 아무래도 10~30대에게 더 친숙한 음식이고, 이 중에서 10대보다는 20,30대가 경제력이 있다고 판단되므로 20~30대가 40대 이상보다 주문을 많이 하지 않을까?
-
요일에 따라 주문 건수가 달라질까?
- 금/토에 많이 주문을 하지 않을까?
-
스포츠 경기나 중요한 행사 등과 상관이 있지 않을까?
- 스포츠 경기가 있는 날에 주문을 많이 할 듯하다
groupby()로 그룹별 집계하기
파이썬의 groupby() 연산자를 사용하여 집단 및 그룹별로 데이터를 집계, 요약해 보겠습니다.
전체 데이터를 그룹 별로 split하고 -> 각 그룹별로 집계함수를 apply한후 -> 그룹별 집계 결과를 하나로 합치는 combine단계를 거치게 됩니다. (split->apply function ->combine)
더 자세한 내용은 https://rfriend.tistory.com/383 를 참조하시기 바랍니다.
df['통화건수'].describe() #descriptive statics
count 29850.000000
mean 13.243987
std 17.464655
min 5.000000
25% 5.000000
50% 5.000000
75% 14.000000
max 242.000000
Name: 통화건수, dtype: float64
이제는 ‘연령대’ 그룹 별로 ‘통화건수’ 변수에 대해 groupby집계를 해 보겠습니다. (연령대별로 얼마나 통화를 많이 했는지 알고 싶으므로) 집단별 크기는 grouped.size() 집단별 합계는 grouped.sum() 집단별 평균은 grouped.mean()을 사용합니다
groupedByAge = df['통화건수'].groupby(df['연령대'])
groupedByAge.sum()
연령대
10대 26681
20대 65140
30대 95924
40대 122028
50대 56655
60대이상 28905
Name: 통화건수, dtype: int64
groupedByAge.mean()
연령대
10대 7.234544
20대 11.438104
30대 15.684107
40대 20.057199
50대 12.487326
60대이상 7.749330
Name: 통화건수, dtype: float64
검증해 본 결과 가설과는 다르게, 40대가 가장 많은 구매를 한 것으로 드러났습니다.
이제는 요일에 따른 주문 건수를 살펴보도록 하겠습니다.
groupedByDay = df['통화건수'].groupby(df['요일'])
groupedByDay.sum().sort_values(ascending = False) # sort_values를 사용해 내림차순으로 정리
요일
토 64817
금 63117
화 59152
일 58460
목 55768
수 55211
월 38808
Name: 통화건수, dtype: int64
토요일과 금요일의 주문량이 다른 날들에 비해 많다는 것을 알 수 있습니다. 마지막으로, 앞에서 살펴본 것에 따르면 스포츠 경기의 전날에 치킨 주문량이 많았던 경우가 있습니다. 이제는 이를 바탕으로 스포츠 경기의 스케줄과 치킨 주문량 사이의 관계에 대해서 살펴 보겠습니다.
2019 아시안컵 대한민국 일정은 다음과 같습니다.
- 1차전(대한민국:필리핀)-> 190107 오후 10:30
- 2차전(대한민국:키르기스스탄)->190112 오전 1시
- 3차전(대한민국:중국)->190116 오후 10:30
- 16강(대한민국:바레인)->190122 오후 10시
- 8강(대한민국:카타르)->190125 오후 10시
이제 이 데이터들을 갖고 dataframe을 만들어보겠습니다.
soccer = {'Date':['20190107','20190112','20190116','20190122','20190125'],
'Country':['Korea:Philippines','Korea:Kyrgyzstan','Korea:China','Korea:Bahrain','Korea:Qatar']
,'Name':['1','2','3','16','8']}
soccer_frame =pd.DataFrame(soccer)
soccer_frame.head(5)
Date | Country | Name | |
---|---|---|---|
0 | 20190107 | Korea:Philippines | 1 |
1 | 20190112 | Korea:Kyrgyzstan | 2 |
2 | 20190116 | Korea:China | 3 |
3 | 20190122 | Korea:Bahrain | 16 |
4 | 20190125 | Korea:Qatar | 8 |
이제 해당 Date에 대한 치킨 주문량을 살펴보도록 하겠습니다. 두 개의 다른 dataframe을 사용합니다.
groupedBysoccer = df['통화건수'].groupby(soccer_frame['Date'])
groupedBysoccer.sum() #error
Date
20190107 5
20190112 36
20190116 5
20190122 5
20190125 5
Name: 통화건수, dtype: int64
음?뭔가 이상합니다. 이렇게 적게 나올 리가 없는데..
- soccer_frame의 date와 동일한
- df에 있는 Date를 찾아서
- 그걸로 묶는다
이러한 방식으로, 새로운 dataframe을 만들도록 하겠습니다. 여기서는 rows들을 multiple values present in an iterable or list들을 참조해 필터링 할 것입니다.
여기서는 soccer_frame의 date와 동일한 df에 있는 date를 찾고 싶은 것입니다. 이는 soccer_frame의 date와 동일한 row만을 필터링 할 수 있다는 의미입니다.
자세한 자료는 다음.을 참조하시면 됩니다.
years = soccer_frame['Date']
df['기준일'].isin(years)
soccerY = df[df['기준일'].isin(years)]
soccerY.head()
기준일 | 요일 | 성별 | 연령대 | 시도 | 시군구 | 읍면동 | 업종 | 통화건수 | |
---|---|---|---|---|---|---|---|---|---|
5761 | 20190107 | 월 | 남 | 30대 | 서울특별시 | 강남구 | 삼성동 | 치킨 | 23 |
5762 | 20190107 | 월 | 여 | 60대이상 | 서울특별시 | 강남구 | 역삼동 | 치킨 | 6 |
5763 | 20190107 | 월 | 여 | 50대 | 서울특별시 | 강남구 | 수서동 | 치킨 | 5 |
5764 | 20190107 | 월 | 남 | 20대 | 서울특별시 | 강남구 | 대치동 | 치킨 | 5 |
5765 | 20190107 | 월 | 여 | 40대 | 서울특별시 | 강남구 | 일원동 | 치킨 | 5 |
잘 걸러진 것을 확인할 수 있습니다.
groupedBysoccerY = soccerY['통화건수'].groupby(soccerY['기준일'])
groupedBysoccerY.mean()
# 통화건수 대로 더한거라 앞 값과 차이가 있을 수 있습니다.
기준일
20190107 11.776404
20190112 15.460377
20190116 13.851670
20190122 13.829557
20190125 17.166521
Name: 통화건수, dtype: float64
df['통화건수'].mean()
13.243986599664991
평균보다 높은 Date들이 많다는 것을 알 수 있습니다.
의문점
- ‘날짜가 나온 횟수’(ex: 2019년 1월 1일에 해당하는 row가 몇 개인지) or ‘날짜가 나온 통화건수의 합계’
(Data row를 기준으로 집계하는 게 좋은지, 통화건수를 기준으로 집계하는 것이 좋은지) 이 둘 중 어느 것이 치킨 주문량에 대해 더 적합한 결과를 나타낼 수 있을까? -> 어떤 사람은 통화건수가 66건이나 되던데, 이는 ‘하루에 했던통화건수’일까 ‘그 사람이 1월달동안 한 전체 통화건수’일까?(이에 대해서는 sk에 별도의 설명이 되어 있지 않다. data를 올려놓을거면 설명을 충실하게 했으면 좋겠다..
-> ‘통화건수’는 해당 info를 가진 사람들의 총 통화건수를 나타내는 것이었습니다! 강남구에 사는 30대 남성’들’이 1월 1일에 치킨을 주문하기 위한 ‘통화건수들의 합’이 바로 통화건수 입니다.