241001 TIL #503 AI Tech #41 위도 경도로 거리측정 효율화

김춘복·2024년 9월 30일
0

TIL : Today I Learned

목록 보기
505/543
post-custom-banner

Today I Learned

데이터 전처리 과정에서 오류 발생. 이를 고쳐보려한다.


위도 경도로 거리측정 효율화

기존 코드

가장 가까운 지하철역과 거리 찾기

  • 기존 방식
    subway_info.sort_values(by=['latitude', 'longitude'])로 정렬해서 이진탐색으로 distance를 추정하려했다.
# 가장 가까운 지하철역과 거리 찾기(이진탐색)
def find_nearest_subway(row, subway_sorted):
    min_distance = float('inf')
    nearest_subway_code = None
    
    left, right = 0, len(subway_sorted) - 1
    while left <= right:
        mid = (left + right) // 2
        subway_lat = subway_sorted.iloc[mid]['latitude']
        subway_lon = subway_sorted.iloc[mid]['longitude']
        # 이부분 나중에 Haversine 방식으로 변경!!
        distance = geodesic((row['latitude'], row['longitude']), (subway_lat, subway_lon)).meters
        
        if distance < min_distance:
            min_distance = distance
            nearest_subway_code = subway_sorted.iloc[mid]['subway_code']
        
        if (row['latitude'], row['longitude']) < (subway_lat, subway_lon):
            right = mid - 1
        else:
            left = mid + 1
    
    return nearest_subway_code, min_distance

temp_df['nearest_subway_code'], temp_df['nearest_subway_distance'] = zip(*temp_df.apply(find_nearest_subway, subway_sorted=subway_sorted, axis=1))
temp_df.head()
  • 하지만 위 방법은 두 변수를 정렬했다는 것 부터 이진탐색이 정확하게 성립하지 않고, 논리적으로 허점이 있어서 철회.

1km 내에 지하철역 찾기

  • 기존방식
    이 방식도 논리적 오류로 철회
def find_subways_within_1km(row, subway_sorted):
    subways_within_1km = []
    for _, subway in subway_sorted.iterrows():
        distance = geodesic((row['latitude'], row['longitude']), (subway['latitude'], subway['longitude'])).meters
        if distance <= 1000:
            subways_within_1km.append(subway['subway_code'])
        elif distance > 1000 and len(subways_within_1km) > 0:
            break
    return len(subways_within_1km), subways_within_1km

temp_df['num_subway_within_1km'], temp_df['list_subway_within_1km'] = zip(*temp_df.apply(find_subways_within_1km, subway_sorted=subway_sorted, axis=1))
temp_df.head()

ballTree & Haversine 방식 시도

  • Balltree를 사용해서 Haversine 거리로 측정시도
import numpy as np
import pandas as pd
from sklearn.neighbors import BallTree

# 지구의 평균 반경 (킬로미터 단위)
EARTH_RADIUS_KM = 6371.0

# 아파트의 위도와 경도를 라디안으로 변환
temp_df_rad = np.radians(temp_df[['latitude', 'longitude']].values)

# 지하철 역의 위도와 경도를 라디안으로 변환
subway_sorted_rad = np.radians(subway_sorted[['latitude', 'longitude']].values)

# BallTree 생성 (Haversine 거리 메트릭 사용)
tree = BallTree(subway_sorted_rad, metric='haversine')

# 반경 1km을 라디안으로 변환
radius = 1 / EARTH_RADIUS_KM  # 약 0.000157 라디안

# 가장 가까운 지하철역과 거리 찾기
distances, indices = tree.query(temp_df_rad, k=1)
temp_df['nearest_subway_distance'] = distances.flatten() * EARTH_RADIUS_KM * 1000  # meters
temp_df['nearest_subway_idx'] = subway_sorted['subway_idx'].iloc[indices.flatten()].values

# 반경 1km 내의 지하철역 인덱스 찾기
indices_within_1km = tree.query_radius(temp_df_rad, r=radius)

# 반경 1km 내의 지하철역 개수
temp_df['num_subway_within_1km'] = [len(ind) for ind in indices_within_1km]

# 반경 1km 내의 지하철역 subway_idx 리스트
temp_df['list_subway_idx_within_1km'] = [subway_sorted['subway_idx'].iloc[ind].tolist() for ind in indices_within_1km]

# 반경 1km 내의 Interchange_station이 2 이상인 지하철역 존재 여부
interchange_counts = [subway_sorted['Interchange_station'].iloc[ind].ge(2).sum() for ind in indices_within_1km]
temp_df['has_Interchange_2_or_more'] = [count >= 1 for count in interchange_counts]

# 결과 확인
print(temp_df[['nearest_subway_idx', 'nearest_subway_distance', 'num_subway_within_1km', 
              'list_subway_idx_within_1km', 'has_Interchange_2_or_more']].head())
  • 이 방법은 EARTH_RADIUS_KM = 6371.0 으로 두고 시작한다.
    Haversine 공식은 지구를 완전한 구로 간주하는 반면, Geodesic 방식은 지구의 타원체 형태를 반영하기 때문이다.

  • Haversine 공식은 삼각함수를 기반으로 한 근사 계산을 사용하므로 약간(약 0.5%이하, 10km당 50m이하)의 오차가 발생할 수 있지만 계산이 월등히 빠르다.

  • 아파트와 지하철역의 위도 경도를 np.radians를 통해 라디안으로 변환한 뒤 ballTree를 생성해서 거리를 측정한다.

  • 이전 방법으로는 30분이 넘게 걸려도 완성되지 못한 코드를 10초 이내로 빠르게 해결할 수 있게 되었다. 오차도 그렇게 큰 편이 아니었다.

회고

  • 오늘 했던 코드가 다 무용지물이 되어버렸다. 허사는 됐지만 배운건 많다. 내일은 복구해보자!
profile
Backend Dev / Data Engineer
post-custom-banner

0개의 댓글