Food_analysis_1_chicken

7 minute read

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에 있어서 빈 값이 있는지의 체크는 다음과 같은 방법으로 할 수 있습니다.

  1. (모든 데이터에 대해 null여부 체크 원하는 경우)df.isnull을 통해 null값인지에 대한 boolean value를 받는 경우

df.isnull()

  1. (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를 다룰지 생각해 봅시다.

요식업계에 대한 지식은 거의 없지만 몇 가지 가설을 생각해 봅시다.

  1. 연령대에 따라 다르지 않을까?

    • 치킨은 아무래도 10~30대에게 더 친숙한 음식이고, 이 중에서 10대보다는 20,30대가 경제력이 있다고 판단되므로 20~30대가 40대 이상보다 주문을 많이 하지 않을까?
  2. 요일에 따라 주문 건수가 달라질까?

    • 금/토에 많이 주문을 하지 않을까?
  3. 스포츠 경기나 중요한 행사 등과 상관이 있지 않을까?

    • 스포츠 경기가 있는 날에 주문을 많이 할 듯하다

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

음?뭔가 이상합니다. 이렇게 적게 나올 리가 없는데..

  1. soccer_frame의 date와 동일한
  2. df에 있는 Date를 찾아서
  3. 그걸로 묶는다

이러한 방식으로, 새로운 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들이 많다는 것을 알 수 있습니다.

의문점

  1. ‘날짜가 나온 횟수’(ex: 2019년 1월 1일에 해당하는 row가 몇 개인지) or ‘날짜가 나온 통화건수의 합계’

(Data row를 기준으로 집계하는 게 좋은지, 통화건수를 기준으로 집계하는 것이 좋은지) 이 둘 중 어느 것이 치킨 주문량에 대해 더 적합한 결과를 나타낼 수 있을까? -> 어떤 사람은 통화건수가 66건이나 되던데, 이는 ‘하루에 했던통화건수’일까 ‘그 사람이 1월달동안 한 전체 통화건수’일까?(이에 대해서는 sk에 별도의 설명이 되어 있지 않다. data를 올려놓을거면 설명을 충실하게 했으면 좋겠다..

-> ‘통화건수’는 해당 info를 가진 사람들의 총 통화건수를 나타내는 것이었습니다! 강남구에 사는 30대 남성’들’이 1월 1일에 치킨을 주문하기 위한 ‘통화건수들의 합’이 바로 통화건수 입니다.

Updated: