-
[2월 3주차-2/19]왜 우리 동네에는 스타벅스가 없을까?Why Not SW CAMP 5기/수업 기록 2025. 2. 20. 09:09
서울에서 스타벅스가 어떤 입지 전략으로 매장 입지를 선택하는지 분석
=> 두 가지 가설을 세워, 이 가설이 맞는지 데이터를 분석을 통해 확인
가설 1. 거주 인구가 많은 지역에 스타벅스 매장이 많이 입지해 있을 것이다!
가설 2. 직장인이 많은 지역에 스타벅스 매장이 많이 입지해 있을 것이다!
1. 데이터 수집
입지전략을 분석하기 위해서는 서울시 내에 출점한 스타벅스들의 위치를 파악.
→ 스타벅스 홈페이지에서 매장들의 정보를 수집(크롤링)
두 가지 가설을 검증하기 위한 인구 통계 데이터 수집.
→ 서울시 열린데이터 광장 OPEN API를 이용하여 인구 통계 데이터 (거주인구 수, 직장인구 수)
1.1 크롤링을 이용한 서울시 스타벅스 매장 목록 데이터 생성
📌 (1_1_Crawling_Starbucks_List.py)
# -*- coding: utf-8 -*- """ Created on Wed Feb 19 04:22:02 2025 @author: tuesv 크롤링을 이용한 서울시 스벅 매장 목록 데이터 생성 """ from selenium import webdriver from bs4 import BeautifulSoup import pandas as pd driver = webdriver.Chrome() url = '<https://www.istarbucks.co.kr/store/store_map.do?disp=locale>' driver.get(url) ## webdriver로 ‘서울’ 버튼 요소를 찾아 클릭 # 1. ‘서울’ 버튼 요소를 찾아 seoul_btn = '#container > div > form > fieldset > div > section > article.find_store_cont > article > article:nth-child(4) > div.loca_step1 > div.loca_step1_cont > ul > li:nth-child(1) > a' # 2. 클릭하기 driver.find_element('css selector', seoul_btn).click() # webdriver로 ‘전체’ 버튼 요소를 찾아 클릭 all_btn = '#mCSB_2_container > ul > li:nth-child(1) > a' driver.find_element('css selector', all_btn).click() # 현재 페이지에 대한 HTML 파서 만들기 html = driver.page_source soup = BeautifulSoup(html, 'html.parser') # 원하는 HTML 태그 찾아오기 starbucks_soup_list = soup.select('li.quickResultLstCon') starbucks_store = starbucks_soup_list[0] ''' <li class="quickResultLstCon" data-code="3762" data-hlytag="null" data-index="0" data-lat="37.501087" data-long="127.043069" data-name="역삼아레나빌딩" data-storecd="1509" style="background:#fff"> <strong data-my_siren_order_store_yn="N" data-name="역삼아레나빌딩" data-store="1509" data-yn="N"> 역삼아레나빌딩 </strong> <p class="result_details"> 서울특별시 강남구 언주로 425 (역삼동) <br/> 1522-3232 </p> <i class="pin_general"> 리저브 매장 2번 </i> </li> ''' # 스타벅스 매장 정보 샘플 확인 name = starbucks_store.select('strong')[0].text.strip() # '역삼아레나빌딩' lat = starbucks_store['data-lat'].strip() # '37.501087' long = starbucks_store['data-long'].strip() # '127.043069' store_type = starbucks_store.select('i')[0]['class'][0][4:] # 'general' address = str(starbucks_store.select('p.result_details')[0]).split(' ')[0].split('>')[1] # '서울특별시 강남구 언주로 425 (역삼동)' tel = str(starbucks_store.select('p.result_details')[0]).split(' ')[1].split('<')[0] # '1522-3232' ### 서울시 스타벅스 매장 목록 데이터 ### # 매장명, 위도, 경도, 매장 타입, 주소, 전화번호 starbucks_list = [] for item in starbucks_soup_list: name = item.select('strong')[0].text.strip() # '역삼아레나빌딩' lat = item['data-lat'].strip() # '37.501087' lng = item['data-long'].strip() # '127.043069' store_type = item.select('i')[0]['class'][0][4:] # 'general' address = str(item.select('p.result_details')[0]).split(' ')[0].split('>')[1] # '서울특별시 강남구 언주로 425 (역삼동)' tel = str(item.select('p.result_details')[0]).split(' ')[1].split('<')[0] # '1522-3232' starbucks_list.append([name, lat, lng, store_type, address, tel]) columns = ['매장명', '위도', '경도', '매장타입', '주소', '전화번호'] seoul_starbucks_df = pd.DataFrame(starbucks_list, columns = columns) seoul_starbucks_df.to_excel('starbucks_location/files/seoul_starbucks_list.xlsx', index = False)1.2 서울열린데이터광장 OPEN API를 활용한 공공데이터 정리
📌 서울열린데이터광장 공공데이터 정리
📌 (1_2_edit_OpenData_Download.py => report.txt / report2.txt)
import pandas as pd sgg_pop_df = pd.read_csv('starbucks_location/files/report.txt', sep='\\t', header=2) columns = { '기간': 'GIGAN', '자치구': 'JACHIGU', '계': 'GYE_1', '계.1': 'GYE_2', '계.2': 'GYE_3', '남자': 'NAMJA_1', '남자.1': 'NAMJA_2', '남자.2': 'NAMJA_3', '여자': 'YEOJA_1', '여자.1': 'YEOJA_2', '여자.2': 'YEOJA_3', '세대': 'SEDAE', '세대당인구': 'SEDAEDANGINGU', '65세이상고령자': 'N_65SEISANGGORYEONGJA' } sgg_pop_df.rename(columns=columns, inplace = True) ### 서울시 동별 인구 데이터 sgg_pop_df_selected = sgg_pop_df[sgg_pop_df['JACHIGU'] != '합계'] columns = ['JACHIGU','GYE_1'] sgg_pop_df_final = sgg_pop_df_selected[columns] sgg_pop_df_final.columns = ['시군구명', '주민등록인구'] sgg_pop_df_final.info() sgg_pop_df_final.to_excel('starbucks_location/files/sgg_pop.xlsx', index = False) ### 서울시 동별 사업체 현황 통계 데이터 sgg_biz_df = pd.read_csv('starbucks_location/files/report2.txt', sep='\\t', header=2) sgg_biz_df_selected = sgg_biz_df[sgg_biz_df['동'] == '소계'] columns = ['자치구','계', '사업체수'] sgg_biz_df_final = sgg_biz_df_selected[columns] sgg_biz_df_final.columns = ['시군구명', '종사자수','사업체수'] sgg_biz_df_final = sgg_biz_df_final.reset_index(drop=True) sgg_biz_df_final.to_excel('starbucks_location/files/sgg_biz.xlsx', index = False)2. 데이터 전처리
2.1 서울시 스타벅스 매장 목록 데이터에 시군구명 칼럼 추가 (2_1_address.py)
import pandas as pd seoul_starbucks = pd.read_excel('starbucks_location/files/seoul_starbucks_list.xlsx', header=0) # 주소 정보에서 시군구명 추출 sgg_name =[] for adress in seoul_starbucks['주소']: sgg = adress.split()[1] sgg_name.append(sgg) seoul_starbucks['시군구명'] = sgg_name seoul_starbucks.to_excel('starbucks_location/files/seoul_starbucks_list.xlsx', index = False)2.2 스타벅스 분석 데이터 만들기 (2_2_data.py)
import pandas as pd # 시군구 목록 데이터 불러오기 seoul_sgg = pd.read_excel('starbucks_location/files/seoul_sgg_list.xlsx') # 시군구별 인구 통계 데이터 불러오기 seoul_pop = pd.read_excel('starbucks_location/files/sgg_pop.xlsx') # 군구별 사업체 수 통계 데이터 seoul_biz = pd.read_excel('starbucks_location/files/sgg_biz.xlsx') # 스타벅스 매장 목록 데이터 불러오기 seoul_starbucks = pd.read_excel('starbucks_location/files/seoul_starbucks_list.xlsx') # 시군구별 스타벅스 매장 수 세기: '시군구명', '매장명' count로 starbucks_sgg_count = seoul_starbucks.pivot_table(index = '시군구명', values='매장명', aggfunc='count').rename(columns={'매장명':'스타벅스_매장수'}) # 서울시 시군구 목록 데이터(seoul_sgg)에 스벅 매장수 데이터를 병합 seoul_sgg = seoul_sgg.merge(starbucks_sgg_count, how = 'left', on = '시군구명') # 서울시 시군구 목록 데이터에 서울시 시군구별 인구 통계 데이터(seoul_pop) 병합 seoul_sgg = seoul_sgg.merge(seoul_pop, how = 'left', on = '시군구명') # 서울시 시군구 목록 데이터에 서울시 시군구별 사업체 수 통계 데이터(seoul_biz) 변합 seoul_sgg = seoul_sgg.merge(seoul_biz, how = 'left', on = '시군구명') # 병합 결과를 엑셀 파일로 저장 seoul_sgg.to_excel('starbucks_location/files/seoul_sgg_stat2.xlsx', index = False)
3. 데이터 시각화
3.1 스타벅스 매장분포 시각화 (3_1_map.py)
import pandas as pd import folium import json seoul_starbucks = pd.read_excel('starbucks_location/files/seoul_starbucks_list.xlsx') # 스타벅스 매장 타입별 위치 starbucks_map = folium.Map(location=[37.573050, 126.979189], zoom_start=11) for idx in seoul_starbucks.index: lat = seoul_starbucks.loc[idx, '위도'] lng = seoul_starbucks.loc[idx, '경도'] store_type = seoul_starbucks.loc[idx, '매장타입'] fillColor = '' if store_type == 'general': fillColor = 'green' size=2 elif store_type == 'reserve': fillColor = 'blue' size=5 elif store_type == 'generalDT': fillColor = 'red' size=5 folium.CircleMarker(location=[lat, lng], color = fillColor, fill= True, fill_color = fillColor, fill_opacity = 1, weight = 1, radius = size).add_to(starbucks_map) starbucks_map2.save('starbucks_map.html')
3.2 시군구별 스타벅스 매장 수 시각화 (3_2_location_visualization.py)
3.2.1 기본 지도 가져와 경계 그리기
import pandas as pd import folium import json seoul_sgg_stat = pd.read_excel('starbucks_location/files/seoul_sgg_stat2.xlsx', thousands=',') sgg_geojson_file_path = 'starbucks_location/maps/seoul_sgg.geojson' seoul_sgg_geo = json.load(open(sgg_geojson_file_path, encoding = 'utf-8')) seoul_sgg_geo['features'][0]['properties'] ''' {'SIG_CD': '11320', 'SIG_KOR_NM': '도봉구', 'SIG_ENG_NM': 'Dobong-gu', 'ESRI_PK': 0, 'SHAPE_AREA': 0.00210990544544, 'SHAPE_LEN': 0.239901251347} ''' # 지도 생성 starbucks_bubble = folium.Map(location=[37.573050, 126.979189], tiles = 'CartoDB positron', zoom_start=11) starbucks_bubble.save('starbucks_bubble.html') # 서울시 시군구 경계 지도 그리기 def style_function(feature): return { 'opacity':0.4, 'weight': 1, 'color': 'black', 'fillOpacity':0, 'dashArray': '5, 5'} folium.GeoJson(seoul_sgg_geo, style_function=style_function).add_to(starbucks_bubble) starbucks_bubble.save('starbucks_bubble_bound.html')3.2.2 평균 매장 수에 따라 크기가 다른 버블 지도로 시각화
# 평균 매장 수 계산 starbucks_mean = seoul_sgg_stat['스타벅스_매장수'].mean() # 평균 매장 수에 따라 크기가 다른 버블 지도로 시각화 for idx in seoul_sgg_stat.index: lat = seoul_sgg_stat.loc[idx, '위도'] lng = seoul_sgg_stat.loc[idx, '경도'] count = seoul_sgg_stat.loc[idx, '스타벅스_매장수'] if count > starbucks_mean: fillColor = 'red' else: fillColor = 'blue' folium.CircleMarker( location = [lat, lng], dolor='#FFFF00', fill_color=fillColor, fill_opacity=0.7, weight = 1.5, radius = count/2).add_to(starbucks_bubble) starbucks_bubble.save('starbucks_bubble_mean.html')
3.2.3 매장 수를 단계구분도로 시각화
sgg_geojson_file_path = 'starbucks_location/maps/seoul_sgg.geojson' seoul_sgg_geo = json.load(open(sgg_geojson_file_path, encoding = 'utf-8')) starbucks_choropleth = folium.Map(location=[37.573050, 126.979189], tiles = 'CartoDB dark_matter', zoom_start=11) folium.Choropleth(geo_data=seoul_sgg_geo, data = seoul_sgg_stat, columns=['시군구명', '스타벅스_매장수'], fill_color = 'YlGn', fill_opacity=0.7, line_opacity=0.5, key_on = 'properties.SIG_KOR_NM').add_to(starbucks_choropleth) starbucks_choropleth.save('starbucks_choropleth.html')
3.3 스타벅스 매장 수와 인구수 비교 (3_3_analysis.py)
import pandas as pd import folium import json # 통계 데이터 불러오기 seoul_sgg_stat = pd.read_excel('starbucks_location/files/seoul_sgg_stat2.xlsx', thousands=',') # 시군구 행정 경계 지도 파일 sgg_geojson_file_path = 'starbucks_location/maps/seoul_sgg.geojson' seoul_sgg_geo = json.load(open(sgg_geojson_file_path, encoding = 'utf-8')) # 주민등록인구수 단계구분도 지도 시각화 starbucks_choropleth = folium.Map(location=[37.573050, 126.979189], tiles = 'CartoDB dark_matter', zoom_start=11) folium.Choropleth(geo_data=seoul_sgg_geo, data = seoul_sgg_stat, columns=['시군구명', '주민등록인구'], fill_color = 'YlGn', fill_opacity=0.7, line_opacity=0.5, key_on = 'properties.SIG_KOR_NM' ).add_to(starbucks_choropleth) starbucks_choropleth.save('starbucks_choropleth_pop.html')
3.4 스타벅스 매장 수와 사업체 수 비교 (3_3_analysis.py)
# 사업체 수 단계구분도 지도 시각화 seoul_sgg_geo2 = json.load(open(sgg_geojson_file_path, encoding = 'utf-8')) starbucks_choropleth = folium.Map(location=[37.573050, 126.979189], tiles = 'CartoDB dark_matter', zoom_start=11) folium.Choropleth(geo_data=seoul_sgg_geo2, data = seoul_sgg_stat, columns=['시군구명', '사업체수'], fill_color = 'YlGn', fill_opacity=0.7, line_opacity=0.5, key_on = 'properties.SIG_KOR_NM' ).add_to(starbucks_choropleth) starbucks_choropleth.save('starbucks_choropleth_corp.html')
'Why Not SW CAMP 5기 > 수업 기록' 카테고리의 다른 글
[2월 3주차-2/20(2)]미세먼지와 날씨 데이터 분석 및 시각화 (0) 2025.02.20 [2월 3주차-2/20(1)]다나와 무선청소기 데이터 분석 및 시각화 🧹📊 (0) 2025.02.20 [2월 3주차-2/18(4)]📊 월별 외국인 관광객 데이터 전처리 및 통합 분석 (0) 2025.02.19 [2월 3주차-2/18(3)]📊 YouTube 채널 랭킹 크롤링 및 데이터 분석 (0) 2025.02.18 [2월 3주차-2/18(2)]MelOn, Bugs, Genie 크롤링 후 Excel 파일로 저장 및 통합 🎵 (1) 2025.02.18