본문 바로가기
프로젝트/(세미)강남구 지역 상권 기반 시간대별 편의점 매출 예측

좌표 데이터(.shp) -4 영역 내 좌표 개수 세기 / 해당 좌표 출력하기

by 규딩코딩 2023. 9. 10.

* 사담

- 개발자들은 코딩하는 시간보다 생각하고 머리에서 정리하는 시간을 더 많이 사용해야하고, 다수의 소위 성공한 개발자들이 이를 공감하고 있다는 내용을 많이 들었다. 처음에는 이 말의 의미를 제대로 이해하지는 못하고 어렴풋이 생각만 했던 것 같다.

- 하지만 이번 글에서 쓰는 코드를 만들기 위해 정말 생각8 : 코딩2 정도의 시간을 할애하면서 위의 말 뜻을 다소 이해할 수 있게 되었다. 우리는 컴퓨터와 일하지 않는다. 파이썬 언어를 사용하지만, 실제로 업무를 진행할 때는 사람과 사람이 "우리 무엇을 합시다(만듭시다)" 논의를 하고, 구현하는 것을 위해 파이썬 등의 컴퓨터 언어를 사용하는 것이다.

- 인간의 말 즉 나의 생각, 내가 원하는 바를 파이썬의 언어로 구현하는 것 또한 쉬운 일이 아니었다(그러니 chat-gpt는 정말 어마어마한 기능인 듯..). 아무튼 다시 한 번 나의 능력이 아직 부족하다는 것을 많이 깨닫게 된 기회였고, 좋게 말하면 단순히 파이썬 기능을 배운 것 이상으로 많은 깨달음을 얻을 수 있었던 작업이었다.

반응형

 

0. 요청 이해하기

- 대중교통 데이터는 연도별/분기별/상권별 + 상권별 버스 정류장의 개수/상권별 각 시간대별 승하차 승객수(6개)/상권별 지하철 역의 개수/상권별 각 시간대별 승하차 승객수(6개) 크게 4가지의 컬럼 종류를 만들어야 했다.

 

- 대중교통 데이터 전처리에는 나와 다른 팀원 한 명이 같이 수행하기로 결정되었고, 내 쪽에서 상권 영역별 데이터를 더 잘 알고 있었기 때문에 1. 상권영역별 버스정류장과 지하철 역의 개수를 세고, 2. 해당 좌표에 해당하는 버스 정류장 ARS 번호(고유 식별 번호)와 지하철 역 이름을 컬럼에 반환하여 만들어주기를 요청받았다.

- 다음에 이어서 팀원께서 그 데이터를 확인하고 해당하는 좌표들의 합을 구해 최종 대중교통 데이터를 만들어주시기로 했다.

 

1. 영역 내 위치한 좌표 개수 세기

1-1 버스 데이터 특성 이해하기

- 버스는 시기별로 노선의 신설/폐지, 정류장의 신설/폐지로 인하여 데이터가 다를 것이다.
- 실제로 이 버스 최종 데이터를 만들기 전 연도별로 데이터 수가 조금씩 달랐다.
- 모든 경우의수를 고려하여 코드를 짜기에는 데이터가 꼬일 문제도 있으므로, 연도마다 데이터프레임을 만들어주려고 한다.

# BUS 데이터 프레임 손보기

# 연도가 2020인 데이터만 필터링
BUS20 = BUS_GN[BUS_GN['연도'] == 2020]

# 필요한 칼럼들 선택
BUS20 = BUS20[['연도', '분기', '버스정류장ARS번호', '00~06_승하차_승객수', '06~11_승하차_승객수', 
               '11~14_승하차_승객수', '14~17_승하차_승객수', '17~21_승하차_승객수', '21~24_승하차_승객수', '버스정류장_위경도_값']]

# 연도가 2021인 데이터만 필터링
BUS21 = BUS_GN[BUS_GN['연도'] == 2021]

# 필요한 칼럼들 선택
BUS21 = BUS21[['연도', '분기', '버스정류장ARS번호', '00~06_승하차_승객수', '06~11_승하차_승객수', 
               '11~14_승하차_승객수', '14~17_승하차_승객수', '17~21_승하차_승객수', '21~24_승하차_승객수', '버스정류장_위경도_값']]

# 연도가 2022인 데이터만 필터링
BUS22 = BUS_GN[BUS_GN['연도'] == 2022]

# 필요한 칼럼들 선택
BUS22 = BUS22[['연도', '분기', '버스정류장ARS번호', '00~06_승하차_승객수', '06~11_승하차_승객수', 
               '11~14_승하차_승객수', '14~17_승하차_승객수', '17~21_승하차_승객수', '21~24_승하차_승객수', '버스정류장_위경도_값']]
# 확인되는 것처럼 행 개수가 다 다르다.
print(BUS20)
print(BUS21)
print(BUS22)

 

- 실제로 데이터의 개수가 연도별로 상이했다.

2020년 : [2033 rows x 10 columns],

2021년 : [2200 rows x 10 columns],

2022년 : [1992 rows x 10 columns]

 

1-2 버스 정류장 좌표 DF 만들기

- 좌표를 세는 목적이므로, 중복을 제거해주었다.

- 이전 글에서 보았듯이 폴리곤 좌표들은 EPSG : 5181 좌표계를 사용하므로, 이전에 맞춰놓았던 위경도 좌표를 다시 변환해준다.

- 이 내용은 이후에 코드를 만들면서 알게된 점이지만, 위경도데이터처럼 합치면 데이터 타입 오류 등 번거로움이 많으므로 각 x,y좌표를 따로 칼럼을 만들어준다.

# 2020년 버스 좌표
BUS20_whkvy = BUS20[['연도','버스정류장ARS번호', '버스정류장_위경도_값']].drop_duplicates()

# 2021년 버스 좌표
BUS21_whkvy = BUS21[['연도','버스정류장ARS번호', '버스정류장_위경도_값']].drop_duplicates()

# 2022년 버스 좌표
BUS22_whkvy = BUS22[['연도','버스정류장ARS번호', '버스정류장_위경도_값']].drop_duplicates()

# WGS84 => EPSG: 5181 변환 함수
import pandas as pd
import pyproj

def wgs84_to_epsg_5181(lat, lon):
    wgs84 = pyproj.Proj(proj='latlong', datum='WGS84')
    epsg_5181 = pyproj.Proj(init='epsg:5181')  # EPSG:5181 좌표계를 사용합니다.
    x, y = pyproj.transform(wgs84, epsg_5181, lon, lat)
    return x, y

# 위경도 데이터 split 함수
def transform_dataframe(df):
    df[['Latitude', 'Longitude']] = df['버스정류장_위경도_값'].str.split(',', expand=True).astype(float)
    df[['EPSG_X', 'EPSG_Y']] = df.apply(lambda row: pd.Series(wgs84_to_epsg_5181(row['Latitude'], row['Longitude'])), axis=1)
    df.drop(['Latitude', 'Longitude'], axis=1, inplace=True)
    return df

# 각 데이터프레임에 변환 함수 적용
BUS20_whkvy = transform_dataframe(BUS20_whkvy)
BUS21_whkvy = transform_dataframe(BUS21_whkvy)
BUS22_whkvy = transform_dataframe(BUS22_whkvy)

결과예시 2020년

 

1-3 지하철 데이터 이해하기

- 지하철은 다행히 시기에 따라 데이터 범주가 추가되거나 폐지되는 일은 없었지만,

- 현재 월별로 되어있어 다른 데이터와의 merge가 용이하지 않으므로, 다른 데이터처럼 연도 분기로 행을 맞춰주자.

 

1-4 지하철 데이터 좌표 DF 만들기

# SUB 데이터 프레임 손보기
# 먼저 지하철 temp DF를 추출(월 컬럼을 빼주어야함)
SUB_GN_temp = SUB_GN[['연도', '분기', '지하철역', '00-06시', '06-11시', '11-14시', '14-17시', '17-21시', '21-24시', '지하철역_위경도_값']]
SUB_GN_temp

 

- 이번 groupby를 위해 월 컬럼을 제거했고, 연도, 분기가 같은 지하철역을 합쳐 분기별 데이터를 만들어 주었다.

- 기존 버스데이터를 합칠 때는 좌표도 같이 합쳐져서 난처했는데, 이번에는 공부하면서 위경도 좌표는 좌표로서 처음 값으로 재설정해주는 코드가 있었다.

# 연도, 분기, 지하철역을 기준으로 그룹화하고 시간대별 승하차 승객수를 합산
SUB_GN_temp_sum = SUB_GN_temp.groupby(['연도', '분기', '지하철역']).sum().reset_index()

# 위경도는 좌표이므로 더해지거나 문자열이 연달아 출력되는 등 불상사를 피하여 처음 값으로 재설정하는 코드
SUB_GN_temp_sum['지하철역_위경도_값'] = SUB_GN_temp.groupby(['연도', '분기', '지하철역'])['지하철역_위경도_값'].first().reset_index()['지하철역_위경도_값']
SUB_GN_temp_sum

 

- 버스 데이터로 인하여 상권별 폴리곤데이터가 들어있는 총합본 데이터도 연도별로 찢어질 것이므로, 일단 지하철도 연도별로 필터링해주자.

- 버스와 동일한 방법으로 중복값을 제거하여 좌표 데이터로 만들어주고, 좌표계를 변환하여 각 x, y 컬럼을 다음 코드를 이용하여 만들어주자.

# 연도가 2020인 데이터만 필터링
SUB20 = SUB_GN_temp_sum[SUB_GN_temp_sum['연도'] == 2020].reset_index(drop=True)

# 연도가 2021인 데이터만 필터링
SUB21 = SUB_GN_temp_sum[SUB_GN_temp_sum['연도'] == 2021].reset_index(drop=True)

# 연도가 2022인 데이터만 필터링
SUB22 = SUB_GN_temp_sum[SUB_GN_temp_sum['연도'] == 2022].reset_index(drop=True)

# 지하철 좌표 데이터만 추출
SUB20_whkvy = SUB20[['연도', '지하철역', '지하철역_위경도_값']].drop_duplicates()
SUB21_whkvy = SUB21[['연도', '지하철역', '지하철역_위경도_값']].drop_duplicates()
SUB22_whkvy = SUB22[['연도', '지하철역', '지하철역_위경도_값']].drop_duplicates()

# WGS84 => EPSG: 5181
def wgs84_to_epsg_5181(lat, lon):
    wgs84 = pyproj.Proj(proj='latlong', datum='WGS84')
    epsg_5181 = pyproj.Proj(init='epsg:5181')  # EPSG:5181 좌표계를 사용합니다.
    x, y = pyproj.transform(wgs84, epsg_5181, lon, lat)
    return x, y

# 위경도 데이터 split 함수
def transform_dataframe(df):
    df[['Latitude', 'Longitude']] = df['지하철역_위경도_값'].str.split(',', expand=True).astype(float)
    df[['EPSG_X', 'EPSG_Y']] = df.apply(lambda row: pd.Series(wgs84_to_epsg_5181(row['Latitude'], row['Longitude'])), axis=1)
    df.drop(['Latitude', 'Longitude'], axis=1, inplace=True)
    return df

# 각 데이터프레임에 변환 함수 적용
SUB20_whkvy = transform_dataframe(SUB20_whkvy)
SUB21_whkvy = transform_dataframe(SUB21_whkvy)
SUB22_whkvy = transform_dataframe(SUB22_whkvy)

1-5 총합본 DF 만들기

ALL_filter = df_all[['기준_년_코드', '기준_분기_코드', '상권_코드', '폴리곤_좌표']]

# 2020년 통합본
ALL_filter20 = ALL_filter[ALL_filter['기준_년_코드'] == 2020]

# 2021년 통합본
ALL_filter21 = ALL_filter[ALL_filter['기준_년_코드'] == 2021]

# 2022년 통합본
ALL_filter22 = ALL_filter[ALL_filter['기준_년_코드'] == 2022]

예시 2020년도

 

1-6 영역 내 버스 정류장 좌표 세기(예시 2020년도)

 [loads(poly) for poly in ALL_filter22['폴리곤_좌표']]

- 이 코드는 파이썬의 리스트 컴프리헨션을 사용하여 ALL_filter22 데이터프레임의 '폴리곤_좌표' 열에 있는 여러 다각형 좌표 데이터를 불러오는 작업을 수행한다.

poly는 '폴리곤_좌표' 열에 있는 하나의 다각형 좌표 데이터를 나타낸다.

loads( ) 함수는 좌표 데이터를 파싱하여 실제 다각형 객체로 변환하는 함수이다. 이 함수는 일반적으로 지리 정보 시스템 (GIS)에서 사용된다.

다시말해 ALL_filter22['폴리곤_좌표'] 열에 있는 각각의 다각형 좌표 데이터를 순회하면서, loads(  ) 함수를 사용하여 이를 실제 다각형 객체로 변환하고 그 결과를 리스트로 반환하는 코드이다.

 

- [Point(x, y) for x, y in zip(BUS22_whkvy['EPSG_X'], BUS22_whkvy['EPSG_Y'])]

- 이 코드는 BUS22_whkvy 데이터프레임의 'EPSG_X'와 'EPSG_Y' 열에 있는 좌표 데이터를 사용하여 Point 객체를 생성하는 작업을 수행한다.

zip(BUS22_whkvy['EPSG_X'], BUS22_whkvy['EPSG_Y'])는 'EPSG_X'와 'EPSG_Y' 열에서 각각 한 쌍씩 값을 가져와서 묶어주는 역할을 한다. 예를 들어, 'EPSG_X' 열에 [x1, x2, x3]이 있고 'EPSG_Y' 열에 [y1, y2, y3]이 있다면, zip 함수를 사용하면 [(x1, y1), (x2, y2), (x3, y3)]와 같은 리스트를 생성하는 것이다. 이후 리스트 컴프리헨션을 사용하여 각 쌍을 이용해 Point(x, y) 객체를 생성하고, 각 좌표 쌍이 Point 객체로 변환되어 리스트로 반환된다.

 

★ polygon.contains(point)는 지리 공간 데이터 처리에서 사용되는 메소드

이 메소드는 어떤 다각형(polygon)이 주어진 점(point)을 포함하는지 여부를 판단합니다. 예를 들어, 만약 polygon이 어떤 지리적 형태를 나타내고 있고, point가 그 안에 있는지를 확인하려면 이 메소드를 사용할 수 있다. polygon이 point를 포함하면 이 메소드는 True를 반환하고, 그렇지 않으면 False를 반환함.

from shapely.geometry import Polygon, Point
from shapely.wkt import loads

# ALL_filter20의 폴리곤을 Polygon 객체로 변환
all_filter_polygons = [loads(poly) for poly in ALL_filter20['폴리곤_좌표']]

# 각 폴리곤에 포함된 좌표의 수를 저장할 리스트
num_points_in_polygon = []

# 각 폴리곤에 대해 작업 수행
for polygon in all_filter_polygons:
    num_points = 0
    
    # BUS20_whkvy의 좌표를 이용하여 Point 객체 생성
    bus20_points = [Point(x, y) for x, y in zip(BUS20_whkvy['EPSG_X'], BUS20_whkvy['EPSG_Y'])]
    
    # 해당 폴리곤에 몇 개의 좌표가 포함되는지 계산
    for point in bus20_points:
        if polygon.contains(point):
            num_points += 1
    
    num_points_in_polygon.append(num_points)

# 결과를 ALL_filter20에 추가
ALL_filter20['포함된_버정_수'] = num_points_in_polygon

 

1-7 영역 내 지하철 역 좌표 세기(예시 2020년도)

 

from shapely.geometry import Polygon, Point
from shapely.wkt import loads

# ALL_filter20의 폴리곤을 Polygon 객체로 변환
all_filter_polygons = [loads(poly) for poly in ALL_filter20['폴리곤_좌표']]

# 각 폴리곤에 포함된 좌표의 수를 저장할 리스트
num_points_in_polygon = []

# 각 폴리곤에 대해 작업 수행
for polygon in all_filter_polygons:
    num_points = 0
    
    # SUB20_whkvy의 좌표를 이용하여 Point 객체 생성
    sub20_points = [Point(x, y) for x, y in zip(SUB20_whkvy['EPSG_X'], SUB20_whkvy['EPSG_Y'])]
    
    # 해당 폴리곤에 몇 개의 좌표가 포함되는지 계산
    for point in sub20_points:
        if polygon.contains(point):
            num_points += 1
    
    num_points_in_polygon.append(num_points)

# 결과를 ALL_filter20에 추가
ALL_filter20['포함된_역_수'] = num_points_in_polygon
ALL_filter20

 

- 두 가지 코드가 모두 실행되면 다음과 같이 데이터프레임이 결과로 나오게 된다.

 

2. 영역내 위치한 좌표를 출력하기

2-1 버스 정류장 ARS 번호 출력(예시 2020년도)

- ars_number = BUS22_whkvy.iloc[index]['버스정류장ARS번호'] 

만약 폴리곤이 점을 포함하고 있다면(위의 if문), BUS22_whkvy 데이터프레임에서 해당 인덱스에 해당하는 버스 정류장의 ARS 번호를 가져옵니다.

from shapely.geometry import Polygon, Point
from shapely.wkt import loads

# ALL_filter20의 폴리곤을 Polygon 객체로 변환
all_filter_polygons = [loads(poly) for poly in ALL_filter20['폴리곤_좌표']]

# 역들을 저장할 리스트
bus_stops = []

# 각 폴리곤에 대해 작업 수행
for polygon in all_filter_polygons:
    polygon_bus_stops = []
    
    # BUS20_whkvy의 좌표를 이용하여 Point 객체 생성
    bus20_points = [Point(x, y) for x, y in zip(BUS20_whkvy['EPSG_X'], BUS20_whkvy['EPSG_Y'])]
    
    # 해당 폴리곤에 포함된 정류장의 ARS 번호를 저장
    for index, point in enumerate(bus20_points):
        if polygon.contains(point):
            ars_number = BUS20_whkvy.iloc[index]['버스정류장ARS번호']
            polygon_bus_stops.append(ars_number)
    
    bus_stops.append(polygon_bus_stops)

# 결과를 ALL_filter20에 추가
ALL_filter20['버스정류장들'] = bus_stops

 

2-2 지하철 역명 출력(예시 2020년도)

from shapely.geometry import Polygon, Point
from shapely.wkt import loads

# ALL_filter20의 폴리곤을 Polygon 객체로 변환
all_filter_polygons = [loads(poly) for poly in ALL_filter20['폴리곤_좌표']]

# 역들을 저장할 리스트
sub_stations = []

# 각 폴리곤에 대해 작업 수행
for polygon in all_filter_polygons:
    polygon_sub_stations = []
    
    # SUB20_whkvy의 좌표를 이용하여 Point 객체 생성
    sub20_points = [Point(x, y) for x, y in zip(SUB20_whkvy['EPSG_X'], SUB20_whkvy['EPSG_Y'])]
    
    # 해당 폴리곤에 포함된 지하철역 이름을 저장
    for index, point in enumerate(sub20_points):
        if polygon.contains(point):
            sub_name = SUB20_whkvy.iloc[index]['지하철역']
            polygon_sub_stations.append(sub_name)
    
    sub_stations.append(polygon_sub_stations)

# 결과를 ALL_filter20에 추가
ALL_filter20['지하철역들'] = sub_stations
ALL_filter20

 

- 두 가지 코드가 모두 실행되면 다음과 같이 데이터프레임이 결과로 나오게 된다.

 

 

 

 

반응형