brunch

You can make anything
by writing

C.S.Lewis

by 이영민 Mar 23. 2018

Yelp의 장소 카테고리는 몇 개일까?

in Yelp Dataset Challenge Round 10

지난 포스팅에서 Yelp 데이터(Yelp dataset challenge round 10)를 뜯어봤는데, 이번에는 조금 더 나아가 Yelp에서 제공하는 장소 카테고리는 어떤 것이 있고 몇 개나 있는지 알아보려 한다.


이용할 데이터는 지난번에 json to csv로 변환한 'business.csv' 파일이다. 실험에 사용한 언어는 Python 3.5.2이고 IDE로 jupyter notebook을 활용했다. 그리고 이번 포스팅에서 사용한 코드와 결괏값은 맨 아래에 첨부파일로 넣었다.



목차

1. 파일 불러와서 데이터 속성 확인하기

2. 필요한 속성('categories')만 추출하기

3. 중복 제거하기

4. 카테고리별 개수 카운트하기



1. 파일 불러와서 데이터 속성 확인하기


# business.csv 파일을 list 형태로 불러오기
biz = []
g = open('business.csv', 'r', encoding='utf-8')
rdrg = csv.reader(g)
for line in rdrg:
    biz.append(line)

# business.csv 파일의 속성 확인하기
biz[0]

['attributes.Ambience.divey',  'attributes.RestaurantsDelivery',  'attributes.DogsAllowed',  'postal_code',  'hours.Thursday',  'attributes.HairSpecializesIn.coloring',  'attributes.BestNights.sunday',  'attributes.BYOB',  'attributes.AgesAllowed',  'attributes.Music.video',  'hours.Friday',  'latitude',  'attributes.Alcohol',  'attributes.Ambience.classy',  'attributes.RestaurantsTableService',  'business_id',  'attributes.Ambience.touristy',  'attributes.RestaurantsCounterService',  'attributes.Corkage',  'attributes.RestaurantsGoodForGroups',  'categories', 'name',  'attributes.BusinessAcceptsBitcoin',  'attributes.HappyHour',  'attributes.WheelchairAccessible',  'attributes.Ambience.hipster',  'attributes.BusinessAcceptsCreditCards',  'is_open',  'attributes.DietaryRestrictions.vegetarian',  'attributes.Music.live',  'attributes.Music.background_music',  'neighborhood',  'attributes.BusinessParking.lot',  'attributes.Music.karaoke',  'review_count',  'attributes.GoodForMeal.breakfast',  'attributes.NoiseLevel',  'attributes.HairSpecializesIn.perms',  'state',  'attributes.DriveThru',  'attributes.HasTV',  'attributes.GoodForMeal.dinner',  'attributes.BusinessParking.street',  'address',  'attributes.RestaurantsAttire',  'hours.Sunday',  'attributes.BestNights.tuesday',  'attributes.AcceptsInsurance',  'attributes.BestNights.wednesday',  'hours.Wednesday',  'attributes.HairSpecializesIn.kids',  'attributes.Open24Hours',  'attributes.Ambience.trendy',  'attributes.CoatCheck',  'hours.Monday',  'attributes.HairSpecializesIn.straightperms',  'city',  'attributes.HairSpecializesIn.curly',  'attributes.Music.no_music',  'hours.Tuesday',  'attributes.HairSpecializesIn.africanamerican',  'stars',  'attributes.RestaurantsPriceRange2',  'attributes.Ambience.intimate',  'attributes.GoodForMeal.latenight',  'attributes.GoodForMeal.dessert',  'attributes.BusinessParking.validated',  'attributes.GoodForMeal.lunch',  'attributes.GoodForKids',  'attributes.DietaryRestrictions.soy-free',  'attributes.GoodForMeal.brunch',  'attributes.BusinessParking.valet',  'longitude',  'attributes.DietaryRestrictions.gluten-free',  'attributes.BYOBCorkage',  'attributes.BusinessParking.garage',  'attributes.BestNights.friday',  'hours.Saturday',  'attributes.Music.dj',  'attributes.HairSpecializesIn.extensions',  'attributes.BestNights.saturday',  'attributes.Ambience.casual',  'attributes.BestNights.thursday',  'attributes.BestNights.monday',  'attributes.HairSpecializesIn.asian',  'attributes.DietaryRestrictions.kosher',  'attributes.WiFi',  'attributes.Smoking',  'attributes.DietaryRestrictions.halal',  'attributes.GoodForDancing',  'attributes.ByAppointmentOnly',  'attributes.Caters',  'attributes.RestaurantsReservations',  'attributes.DietaryRestrictions.dairy-free',  'attributes.DietaryRestrictions.vegan',  'attributes.Ambience.romantic',  'attributes.Music.jukebox',  'attributes.Ambience.upscale',  'attributes.RestaurantsTakeOut',  'attributes.BikeParking',  'attributes.OutdoorSeating']


위에 출력된 속성 101개 중 몇 개를 보면, 먼저 'business_id'는 해당 POI의 unique id이며, 'name'은 해당 POI의 상호명을 나타낸다. 'hours.Thursday'처럼 'hours.'와 요일이 결합된 속성은 예상하다시피 월요일(Monday)부터 일요일(Sunday)까지 총 7개가 있다. 이는 요일별 오픈 시간을 명시한 것으로, 사용자 입장에서 아주 유용한 정보다.


'attributes.RestaurantsDelivery'는 배달이 가능한지 여부를 나타내며 True or False를 출력하는데, 대부분의 값이 비어(NaN) 있다. 'attributes.DogsAllowed'는 애완견을 동반할 수 있는지 여부를 나타내며 역시 True or False를 출력하는데, 대부분의 값이 비어 있다. 


101개 속성 중 "위치 정보"를 나타내는 속성은 어떤 것들이 있을까? 

: 출력된 순서대로 보면, 'postal_code', 'latitude',  'state',  'address',  'city',  'longitude' 이렇게 6개가 있다. 이렇게 친절하게 경위도 좌표가 찍혀 있는 데이터는 정말 소중하다. 소중히 다뤄야 한다.


이해를 돕기 위해 101개 속성과 속성별 값 일부를 표로 정리해 보았다(표 1-1, 표 1-2). 참고로 번호(No)는 0부터 시작한다. 


표 1-1. 'business.csv' 데이터 예시_1
표 1-2. 'business.csv' 데이터 예시_2



2. 필요한 속성('categories')만 추출하기


101개 속성 중 지금 우리에게 필요한 것은 20번째 속성인 'categories'이다. 표 1-1에서 'categories' 행을 확인해 보면, 하나의 장소에 여러 개의 카테고리가 포함된 것을 알 수 있다. 이것들을 가져와 보자.

# 'categories'만 추출
cate = []
for i in range(1, len(biz)):  # 제목 줄은 제외하므로 1부터 시작
    cate.append(biz[i][20])

# 데이터 type 확인
type(cate[0])  # str


list인 'cate' 내부값의 type이 string이므로 이를 list 바꿔줘야 한다.

# str to list
cate2 = []
for i in range(0, len(cate)):
    cate2.append([ast.literal_eval(cate[i])])

# 데이터 확인
cate2[0]  # [['Shopping', 'Shopping Centers']]


그리고 'cate2'는 list 안의 list 형태로 돼 있는 것을 알 수 있는데, 이것을 해체해서 하나의 list 만든 다음에 중복 제거를 해 보자.

# list 해체하기
cate3 = []
for i in range(0, len(cate2)):
    for j in range(0, len(cate2[i][0])):
        cate3.append(cate2[i][0][j])

# 데이터 길이 확인
len(cate3)  # 590,290



3. 중복 제거하기


중복 제거 전의 카테고리 개수는 590,290개이다. 별 의미 없는 숫자다. 이제 이것의 중복 제거를 해 보자.

# 중복 제거하기
cate4 = list(set(cate3))

# 데이터 길이 확인
len(cate4)  # 1,240


중복 제거 결과 카테고리의 개수가 1,240개로 줄어들었다. 앞에 10개 데이터를 출력해 보자.

cate4[0:10]

['Property Management',  

'Body Shops',  

'Dance Clubs',  

'Personal Care Services', 

'Motorsport Vehicle Dealers',  

'Home Cleaning',  'Shutters',  

'Roof Inspectors',  

"Children's Museums", 

'Insulation Installation']


위 출력 결과를 보면 순서가 제멋대로인 것을 알 수 있다. 이것을 알파벳순으로 정리하고 다시 출력해 보자.

cate4.sort()
cate4[0:10]  # 10개만 출력

['& Probates',  

'3D Printing',  

'ATV Rentals/Tours',  

'Acai Bowls',  'Accessories',  

'Accountants',  

'Acne Treatment',  

'Active Life',  

'Acupuncture',  

'Addiction Medicine']



4. 카테고리별 개수 카운트하기


앞에서 장소 카테고리 개수가 1,240개라는 것을 확인했는데, 이번에는 각 카테고리가 몇 개씩 있는지 알아보자. 먼저 단어 개수를 카운트하는 함수를 만들었다.

# 단어 개수 카운트하는 함수 만들기
def count_words(word_set, word):
    count = 0
    for w in word_set:
        if word == w:
            count += 1
    return count


그리고 앞에서 중복 제거 전의 'cate3'과  알파벳순으로 정렬한 'cate4'를 이용해서 각 카테고리에 해당하는 장소의 개수를 카운트해 보자.

# 각 카테고리에 해당하는 장소 개수 카운트하기
cate_count = []
for i in range(0, len(cate4)):
    cate4_i = cate4[i]    
    cate_count.append([cate4_i, count_words(cate3, cate4_i)])

# 값 확인
cate_count[0:10]  # 10개만 출력

[['& Probates', 37],  

['3D Printing', 5],  

['ATV Rentals/Tours', 40],  

['Acai Bowls', 37], 

 ['Accessories', 1600],  

['Accountants', 214],  

['Acne Treatment', 11],  

['Active Life', 7427], 

 ['Acupuncture', 503], 

 ['Addiction Medicine', 7]]


자, 여기서 가장 많은 카테고리의 개수를 알고 싶다면? 카테고리 개수를 기준으로 정리해서 상위 10개 값을 뽑아보자.

# 정렬하려면 먼저 tuple로 만들어 줘야 함
# list to tuple 
cate_count_t = []
for i in range(0, len(cate_count)):
    cate_count_t.append(tuple(cate_count[i]))

# 그리고 정렬
cate_count_sorted = sorted(cate_count_t, key=lambda cate_count_t: cate_count_t[1], reverse=True)

# 상위 10개 값 확인
cate_count_sorted[0:10]

[('Restaurants', 51613),  

('Shopping', 24595),  

('Food', 23014),  

('Beauty & Spas', 15139),  

('Home Services', 13202), 

('Health & Medical', 12033),  

('Nightlife', 11364),  

('Bars', 9868),  

('Automotive', 9476),  

('Local Services', 9343)]


위 결과를 보면, 레스토랑('Restaurants')의 개수가 51,613개로 압도적으로 많고 다음으로 쇼핑('Shopping')과 음식('Food') 관련 장소가 많은 것을 알 수 있다. 레스토랑과 음식은 카테고리 내에서 상당히 클러스터링 되어 있을 것 같다. 다음번에 카테고리별 클러스터링도 한번 해 봐야겠다.



이번 포스팅에서 사용한 코드(.ipynb)와 결괏값인 'cate_count_sorted' 파일(.csv)을 첨부파일로 넣었습니다. 필요하시면 가져다 쓰셔도 됩니다.


(제목 배경사진 출처)

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari