후쿠오카 카페 데이터로 보기

Published
Author
실행으로 이어지지 않는 데이터는 쓸모없다.
 
데이터 실무자로 일하면서 느꼈던 여러 가지를 한마디로 요약하면
이 말 한마디가 될 것 같다.
아무리 열심히 데이터 수집을 하고, 가공도 하고, 시각화를 하더라도,
그리고 인사이트를 고민해서 분석 결과를 내더라도 그 결과물을 본 사람이 아무런 행동을 하지 않으면
조금 과격하게 표현하면 "삽질을 한 것이다"
그래서 항상 데이터 업무의 최종 목표,
즉 누구에게 어떤 행동을 유도할 것인지를 항상 사전에 염두에 두는 편이다.
반대로 생각해 보면 이런 액션플랜이 명확한 경우 데이터 과제도 잘 진행된다.
 
이번에 후쿠오카 여행을 계획하고 있는데, 쾌적한 공간에서 맛있는 커피도 마실 수 있는 최상급 카페들을 찾아보고 있었다.
그동안 여행이나 출장을 다녀보면서 느낀 게 구글맵 기준으로 리뷰 수가 일정 수 이상 되고 (너무 리뷰 수가 적으면 조작되었을 가능성이 있으니 모수 확보 차원에서 최소 100개) 평점이 4.4점 이상인 곳은 꽤나 괜찮은 곳들이라는 점이다.
그동안 별생각 없이 구글 맵 앱을 켜서 수기로 카페들을 찾아보았는데, 이번 조금 더 체계적으로 더 많은 카페들을 찾아보기 위한 방법을 고민했다.
방법을 적용해서 후쿠오카 텐진 중심으로부터 반경 2.5km의 카페 900여 곳을 수집했고,
태블로로도 시각화하여 카페들을 쉽게 선별해 볼 수 있도록 했다.
notion image
 
notion image
 
이 과제로 인해서 내가 가볼 만한 최상급 카페 16여 군데가 추려졌다.
나름 구체적인 액션플랜으로 이어진 데이터 과제이라고도 볼 수 있다.
아래는 어떤 방법으로 이 문제를 해결했는데, 중간에 어떤 어려움이 있었고 챗GPT로 어떻게 도움을 받아서 문제를 해결했는지 위주로 설명할 예정이고, 파이썬 코드도 등장할 예정이다.
 

1) 문제 해결의 실마리 Google Place API

찾아보니 특정 위치 기준으로 반경을 설정하고, 주변의 카페든, 음식점이든, 학교든 유형별도 선택해서 장소의 데이터를 뽑아낼 수 있는 API가 있었다.
구글에서 제공하는 Places API이다
notion image
notion image
API를 써서 나오는 결과값 예시는 아래처럼 이 카페의 엽업시간, URL, 사진, 평점, 리뷰 수, 가격수준 등인데 우선 필요한 리뷰 수와 평점, 가격수준 정도를 활용했다.
notion image
notion image
그런데 이 API에 치명적인 문제가 있다.
특정 지점 중심으로 최대 60개 장소만 검색이 된다는 것이다.
이것도 처음에는 20개가 나오고, 몇 번의 추가 단계를 거쳐도 최대 60개만 나오니 반경을 500m든 1km든 크게 해서 검색을 할 수 없다.
이렇게 검색해 봐야 어차피 60개만 나오기 때문이다.
몇 번의 테스트를 해보니 우선 이 한계 안에서 활용을 해볼 수 있는 반경은 300m 수준이다.
텐진 중심지를 기준으로 반경 300m로 검색하면 카페가 30여 개 나오고, 400m로 바꾸는 순간 바로 60개가 찍힌다.
그래서 반경 2.5km의 커다란 면적을 검색하려면 이를 수없이 작은 300m 반경의 원을 그려주고 중간중간에 빠진 부분은 없도록 간격을 설정해 주고 하는 복잡한 과정이 필요하다.
사실 이런 GIS 데이터라든지 공간 데이터에 대해서 비전문가 입장에서는 매우 어려운 작업일 수 있고 여기서 대부분 막힌다.
이때 해결사 챗GPT가 등장했다.
그래서 아래 코드를 기반으로 몇 가지 구체적인 지시사항을 알려줬다니 곧바로 일하기 시작했다.
import requests import time API_KEY = "" def format_res(res): places = res.get('results', []) formatted = [] for place in places: data = { "lat": place["geometry"]["location"]["lat"], "long": place["geometry"]["location"]["lng"], "name": place.get("name", ""), "price_level": int(place.get("price_level", 0)), "rating": float(place.get("rating", 0)), "rating_count": int(place.get("user_ratings_total", 0)), "place_id": place["place_id"], "vicinity": place["vicinity"], } formatted.append(data) return formatted def do_search(place_type, key, location, radius, page_token=None): endpoint = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" params = { "type": place_type, "location": f"{location[0]},{location[1]}", "radius": radius, "key": key } if page_token: params["pagetoken"] = page_token response = requests.get(endpoint, params=params) return response.json() def full_search(place_type, key, location, radius): all_results = [] page_token = None while True: res = do_search(place_type, key, location, radius, page_token) # If there's an error, print it and break if res['status'] != 'OK': print(f"Error for {location}: {res['status']}") break all_results.extend(format_res(res)) page_token = res.get("next_page_token") if not page_token: break # Sleep before next call due to Google's API limitations time.sleep(3) return all_results
notion image
 
그래서 나온 결과물이 아래였다.
챗GPT는 특정 좌표와 필요한 반경이 있으면, 이를 기반으로 300m 반경의 원들 여러 개를 만들어보고 각 원들의 좌표를 배열 형태로 반환하는 함수(get_circle_centers)를 만들어줬다.
 
def get_circle_centers(coord, desired_radius): if desired_radius <= 300: return [coord] # The distance between circle centers in a hexagonal grid. inter_circle_distance = 300 * math.sqrt(3) # Number of circles needed along the radius. num_circles = math.ceil(desired_radius / inter_circle_distance) circle_centers = [coord] # Start with the central circle. for i in range(1, num_circles + 1): # For each layer, there are 6 * i circles. for j in range(6 * i): theta = 2 * math.pi * j / (6 * i) d_distance = i * inter_circle_distance # distance in meters # Convert meters to latitude. dx = d_distance * math.cos(theta) / 111320 # Convert meters to longitude. dy = d_distance * math.sin(theta) / (111320 * math.cos(math.radians(coord[0]))) new_center = (coord[0] + dx, coord[1] + dy) circle_centers.append(new_center) return circle_centers
 
챗GPT와 코딩을 하면서 깨달은 가장 중요한 점은 최종 검증을 꼭 사람이 하든, 검증을 잘할 수 있는 시스템을 만들든 해야 한다는 것이다.
그래서 아래와 같이 추가 요청을 했다.
1) 도대체 그래서 몇 개의 원이 만들어지는지도 알려줘
2) 그리고 그 결과를 시각화해주는 코드도 짜줘
notion image
notion image
그렇게 몇 번의 시행착오 과정을 거쳐보니 2.5km 반경으로 91개의 원이 생성되고, 원은 아래와 같은 간격을 만들어지도록 하는 코드를 완성하였다.
물론 여전히 원과 원 사이에 간격은 조금 있지만 이 정도면 괜찮은 것 같다.
notion image
notion image
(시각화까지 포함된 최종 코드)
import math def get_circle_centers(coord, desired_radius): if desired_radius <= 300: return [coord] # The distance between circle centers in a hexagonal grid. inter_circle_distance = 300 * math.sqrt(3) # Number of circles needed along the radius. num_circles = math.ceil(desired_radius / inter_circle_distance) circle_centers = [coord] # Start with the central circle. for i in range(1, num_circles + 1): # For each layer, there are 6 * i circles. for j in range(6 * i): theta = 2 * math.pi * j / (6 * i) d_distance = i * inter_circle_distance # distance in meters # Convert meters to latitude. dx = d_distance * math.cos(theta) / 111320 # Convert meters to longitude. dy = d_distance * math.sin(theta) / (111320 * math.cos(math.radians(coord[0]))) new_center = (coord[0] + dx, coord[1] + dy) circle_centers.append(new_center) print(f"Total number of circles: {len(circle_centers)}") return circle_centers import matplotlib.pyplot as plt def visualize_hexagonal_pattern(center, radius): # Get circle centers circle_centers = get_circle_centers(center, radius) fig, ax = plt.subplots(figsize=(10, 10)) # Convert circle radius from meters to lat/long units for visualization circle_radius_lat = 300 / 111320 circle_radius_lon = 300 / (111320 * math.cos(math.radians(center[0]))) # Draw each circle for coord in circle_centers: circle = plt.Circle((coord[1], coord[0]), circle_radius_lon, fill=False) ax.add_artist(circle) # Set plot limits d_limit = radius * 1.1 / 111320 # Add 10% for margins ax.set_xlim(center[1] - d_limit, center[1] + d_limit) ax.set_ylim(center[0] - d_limit, center[0] + d_limit) ax.set_aspect('equal', adjustable='datalim') plt.xlabel("Longitude") plt.ylabel("Latitude") plt.title(f"Hexagonal Tiling with Radius: {radius} meters") plt.grid(True) plt.show() center = (40.7128, -74.0060) # New York visualize_hexagonal_pattern(center, 2500)
 
그렇게 해서 코드가 완성되니, 우선 어떤 좌표를 기준으로 찍을까 고민하다가 유용한 사이트를 발견했다.
여기서 원하는 점을 찍고 반지름을 설정하면 내 원이 어떤 지역을 포함하는지 시각적으로 쉽게 볼 수 있다.
notion image
 
 

Loading Comments...