커피 프랜차이즈의 입점전략

주홍땅·2025년 7월 20일

데이터 분석

목록 보기
5/5
post-thumbnail

📌 최초 논제

  • 기사 원문
  • 스타벅스 매장 근처에 다른 커피 매장들이 얼마나 있을까?

📌 스타벅스 매장 위치 크롤링

: 스타벅스 홈페이지에서 매장 찾기 -> 지역 검색 -> 서울시로 스타벅스 매장 위치 크롤링

import re
import time
import pandas as pd
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup

def fetch_starbucks():
	starbucks_url = "https://www.starbucks.co.kr/index.do"
    driver = webdriver.Chrome()
    driver.maximize_window() # 화면 최대화
    driver.get(starbucks_url)
    time.sleep(1)
    
    # 여러 액션을 연결하여 사용
    action = ActionChains(driver)
    first_tag = driver.find_element(By.CSS_SELECTOR, "#gnb > div > nav > div > ul > li.gnb_nav03 > h2 > a")
  	second_tag = driver.find_element(By.CSS_SELECTOR, "#gnb > div > nav > div > ul > li.gnb_nav03 > div > div > div > ul:nth-child(1) > li:nth-child(3) > a")
    action.move_to_element(first_tag).move_to_element(second_tag).click().perform()
    
    seoul_tag = WebDriverWait(driver,10).until(
    	EC.element_to_be_clickable(
     	 (By.CSS_SELECTOR, "#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")
    	)
  	)
  	seoul_tag.click()
    
    WebDriverWait(driver,5).until(EC.presence_of_all_elements_located((By.CLASS_NAME,"set_gugun_cd_btn")))
  	gu_elements = driver.find_elements(By.CLASS_NAME, "set_gugun_cd_btn")
  	WebDriverWait(driver, 5).until(
    	EC.element_to_be_clickable(
      		(By.CSS_SELECTOR, "#mCSB_2_container > ul > li:nth-child(1) > a")
	  )
	)
	gu_elements[0].click()
    
    WebDriverWait(driver, 5).until(EC.presence_of_all_elements_located((By.CLASS_NAME,"quickResultLstCon")))
    
    req = driver.page_source
  	soup = BeautifulSoup(req, "html.parser")
  	stores = soup.find("ul","quickSearchResultBoxSidoGugun").find_all("li")
    
    store_list = []
  	addr_list = []
  	lat_list = []
  	lng_list = []
    
    for store in stores:
    	store_name = store.find("strong").text
        store_addr = store.find("p").text
    	# 정규 표현식 개념
    	store_addr = re.sub(r"\d{4}~\d{4}$", "", store_addr).strip()
    	# (숫자면서 4자리 - 숫자면서 4자리)을 제거한 뒤 앞뒤 공백 지우기
    	store_lat = store["data-lat"]
    	store_lng = store["data-long"]
   
    	store_list.append(store_name)
    	addr_list.append(store_addr)
    	lat_list.append(store_lat)
    	lng_list.append(store_lng)
  
    df = pd.DataFrame({
        "store" : store_list,
        "addr" : addr_list,
        "lat" : lat_list,
        "lng" : lng_list
      })
      
    time.sleep(3)
    driver.quit()
    return df
    
starbucks_df = fetch_starbucks()
starbucks_df.to_csv("starbucks_seoul.csv", index=False, encoding="utf-8-sig")
print("데이터가 starbucks_seoul.csv 파일로 저장되었습니다.")

📌 데이터 소스
: 공공데이터포털에서 서울에 있는 소상공인 시장진흥공단 상가(상권) 정보 데이터를 받아왔음


📌 데이터 전처리

  • 스타벅스 데이터 불러오기
import pandas as pd
df_starbucks = pd.read_csv("/content/drive/MyDrive/컴퓨터비전/3. 데이터 분석/data/starbucks_seoul.csv")
  • 컬럼명 변경
df_starbucks = df_starbucks.set_axis(["지점명", "지점주소", "지점위도", "지점경도"], axis=1).reset_index(drop=True)
  • 소상공인 상가 위치 데이터 불러오기
df = pd.read_csv("/content/drive/MyDrive/컴퓨터비전/3. 데이터 분석/data/소상공인시장진흥공단_상가(상권)정보_서울_202503.csv", low_memory=False)
  • 동일한 매장 종류에 여러개의 매장명이 사용되어 이를 합침
shop = ["이디야", "이디아", '이디야커피', "이디아커피", "올리브영", "컴포즈커피", "빽다방", "백다방", "메가커피", "메가엠지씨", "메가MGC"]
df_shop = df.copy()

# 이디야|이디아|올리브영|컴포즈커피|빽다방|메가커피|메가엠지씨|메가MGC
# extract(): 특정 문자열을 포함하고 있으면 그 문자열을 반환하고, 포함하지 않으면 NaN을 반환

df_shop["상호명"] = df_shop["상호명"].str.extract("({})".format("|".join(shop)))
df_shop = df_shop.dropna(subset=["상호명"]).iloc[:, [0,1,14,37,38]].reset_index(drop=True)

df_shop["상호명"] = df_shop["상호명"].str.replace("메가엠지씨", "메가커피", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("메가MGC", "메가커피", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디아", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디야커피", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("이디아커피", "이디야", regex=False)
df_shop["상호명"] = df_shop["상호명"].str.replace("백다방", "빽다방", regex=False)

print(df_shop.shape)
print(df_starbucks.shape)
  • 두 지점 간의 거리 계산을 위해 Cartesian Product로 merge 시킴
df_cross = df_shop.merge(df_starbucks, how="cross")

📌 하버사인 공식
Haversine 공식은 구체(구면) 위의 두 점(위도와 경도로 표시됨) 사이의 최단 거리(대원 거리, great-circle distance)를 계산하는 방식

pip install haversine
from haversine import haversine
  • 두 지점 사이의 거리 파생변수 생성 (미터 단위)
df_cross["거리"] = df_cross.apply(lambda x : haversine([x["위도"], x["경도"]], [x["지점위도"], x["지점경도"]], unit="m"), axis=1)
  • 개별 매장들과 스타벅스와의 최소거리
df_dis= df_cross.groupby(["상가업소번호","상호명"])["거리"].min().reset_index()
  • 각 프랜차이즈별 스타벅스와의 평균거리
df_dis.groupby("상호명")["거리"].mean()
# agg() : 다중 집계작업을 간단하게 해주는 함수
df_dis.groupby(["상호명"])["거리"].agg(["mean","count"])
  • 거리를 입력하면 프렌차이즈 별 스타벅스와의 평균거리와 매장개수를 출력하는 함수
def distance(x):
    dis = df_dis['거리'] <= x
    return df_dis[dis].groupby(['상호명'])['거리'].agg(['mean', 'count'])

distance(100)

📌 차트 시각화

pip install pandasecharts
df_100 = distance(100).reset_index()
import IPython
from pandasecharts import echart

df_100.echart.pie(x='상호명', y='count', figsize=(600, 400),
                  radius=['20%', '60%'], label_opts={'position':'outer'},
                  title='커피 프렌차이즈의 입점전략은 과연 스타벅스 옆인가?',
                  legend_opts={'pos_right':'0%', 'orient':'vertical'},
                  subtitle='100m 이내 매장수', init_opts={'bg_color': 'white'}).render()

IPython.display.HTML(filename='render.html')

from pyecharts.charts import Timeline, Grid

tl = Timeline({'width':'600px', 'height':'400px'})

for i in [1000, 100, 50, 30]:
    df_d = distance(i).reset_index()
    pie1 = df_d.echart.pie(x='상호명', y='count', figsize=(600, 400),
                    radius=['20%', '60%'], label_opts={'position':'outer'},
                    title='커피 프렌차이즈의 입점전략은 과연 스타벅스 옆인가?',
                    legend_opts={'pos_right':'0%', 'orient':'vertical'},
                    subtitle='{}m 이내 매장수'.format(i), init_opts={'bg_color': 'white'})
    tl.add(pie1, '{}m'.format(i)).render()
IPython.display.HTML(filename='render.html')

profile
공부 기록 글입니다.

0개의 댓글