이언배 연구노트

[QGIS + Python] 서울 생활인구 이동 데이터 탐색 본문

QGIS

[QGIS + Python] 서울 생활인구 이동 데이터 탐색

이언배 2025. 1. 14. 11:32

누가 어딜 얼만큼 방문하는가.

누가 방문하는 곳에 NDTP의 비율이 높은가.

일단, 데이터부터 물어오자.

 

빅데이터캠퍼스에 가서 50m 단위 데이터를 얻을 수도 있지만, 몇가지 이슈가 있다.

1. 50m 단위의 이동 데이터가 생각보다 정확하지 않다는 점

2. 연령대, 시간대, 방문목적 별로 분류가 되어있지만, 쟤들을 한번에 엮은 table은 없다는 점 (그러니까, 방문목적에 따른 시간대별, 연령대별 시간대별 등이 없다)

3. 종로구 데이터로 이것저것 해봤는데 별 의미가 없더라

4. 내 연구의 공간적 단위를 50m로 하는 게 의미가 있나? 없을 수도 있겠다.

5. 가기 귀찮다.

 

그렇다면, 대체할 수 있는 데이터를 서울 열린데이터 광장에서 데이터를 물어와보자.

서울 생활이동> 서울빅데이터 | 서울열린데이터광장

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

가장 작은 단위는 행정동인가. 섭섭하다.

하지만 행정동이 가장 활용할 수 있는 데이터 (상권, 거주인구, 경제적 수준) 가 많겠지.

행정동이 내 연구에서 적합한 단위인가? -- 이건 데이터를 살펴보면서 무엇이 적합한지 확인해보는 게 맞는 것 같다.

 

일단 데이터 설명서의 핵심적인 내용들을 보자

그러니까 KT만 보는 게 아니고, 얼추 보정을 한다
빅데이터캠퍼스에 가면 더 세부적인 '교통폴리곤' (Census 같은) 데이터를 얻을 수 있고, 거주지(H), 근무지(W), 그외(E) 를 구분하는 기준은 당월기준 체류시간이다.
교통폴리곤이 4~5개 모이면 행정동 하나가 나온다. 기준은 기지국이다
좋아 이정도면 훌륭하다

나는 2023년 11월을 기준으로 할테고, 데이터를 내려받아보면

겁1나 귀찮지만 시간대별로 나뉘어져있다. 이 단계에서부터 전처리가 필요하고, 이건 파이썬으로 하는 게 낫다.

위 데이터의 시간 단위는 도착시간을 기준으로 작성되어있고, 나는 17시부터 24시까지 데이터를 사용해보기로 했다. (18시를 쓸지, 17시를 쓸지는 모르겠지만, 일단 17시로 해놓고 나중에 자료를 제외하는 게 더 낫다).

 

요일은? 주중, 주말 다 써도 될 것 같다. 시간이 오후 18시부터니까. 

아니, 아니면 주중은 18시부터 24시로 하고, 주중은 전부 쓰는 거 어때? 어차피 이동 유형이 있잖아?

일리가 있어. 하지만 Third Place 에 학원이랑 헬스클럽도 있잖아? 그건 주말에 쉴텐데, 이걸 넣는 건 좀 위험해.

 

도착시간은? 다 써.

 

출발 행정동 코드는? 혹시 이게 나중에 경제적 요인으로 사용될 수 있을까? 일단 살리자.

 

성별, 나이는? 다 쓴다

이동 유형은? 끝이 E로 끝나는 녀석들만 살린다.

 

평균 이동 시간은 중요하지 않고,

이동인구(합)이 우리의 목표다.

 

그리고, 1차적으로 찍어보고 싶은 건, "나이" 다.

아무래도 Digital platform 의 사용 여부와 연관성을 보고 싶으니, 나이대별 핫스팟과 NDTP, DTP의 비율이 연관이 있는지가 궁금한거야 나는.

 

나이를 10대부터 70대까지로 나눠서 grouping 해보고, 그걸 QGIS에 얹는 걸 우선 목표로 하자.

 

개열받는 포인트는 이동인구(합) 탭이 "string" 형태이고, Null 대신 "*"를 넣어놓았다.

이 진짜 이...

 

###################################원하는 시간대가 아닌 데이터는 싹 지웠다
filenames = glob('../Data/생활이동_행정동_202311/*.csv')
dfs = []
for filename in filenames:
    df = pd.read_csv(filename, encoding = 'cp949')
    dfs.append(df)
df_move = pd.concat(dfs)
df_move.head(3)

###################################원하는 대상이 아닌 column 과 row는 싹 지웠다
df_move2 = df_move[['대상연월', '도착시간', '출발 행정동 코드', '도착 행정동 코드', '성별', '나이', '이동유형', '이동인구(합)']]
moving_patterns = ['HE', 'WE', 'EE']
df_move3 = df_move2[(df_move2['이동유형'].isin(moving_patterns)) & (df_move2['이동인구(합)'] != '*')] #오직 entertain 목적의 이동만 보고, "*"로 기록되지 않은 데이터는 날린다
df_move3['이동인구(합)'] = df_move3['이동인구(합)'].astype(float) #float 형태로 보자
print(len(df_move3))

###################################무식해보인다 하지 말라. 가장 쉽고 빠르다.
df_10 = df_move3[(df_move3['나이'] == 10) | (df_move3['나이'] == 15)]
df_20 = df_move3[(df_move3['나이'] == 20) | (df_move3['나이'] == 25)]
df_30 = df_move3[(df_move3['나이'] == 30) | (df_move3['나이'] == 35)]
df_40 = df_move3[(df_move3['나이'] == 40) | (df_move3['나이'] == 45)]
df_50 = df_move3[(df_move3['나이'] == 50) | (df_move3['나이'] == 55)]
df_60 = df_move3[(df_move3['나이'] == 60) | (df_move3['나이'] == 65)]
df_70 = df_move3[(df_move3['나이'] >= 70)]

df_10_sum = df_10[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '10s_inflow'})
df_20_sum = df_20[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '20s_inflow'})
df_30_sum = df_30[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '30s_inflow'})
df_40_sum = df_40[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '40s_inflow'})
df_50_sum = df_50[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '50s_inflow'})
df_60_sum = df_60[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '60s_inflow'})
df_70_sum = df_70[['도착 행정동 코드', '이동인구(합)']].groupby(['도착 행정동 코드']).sum().reset_index().rename(columns = {'도착 행정동 코드': 'hjd_cd', '이동인구(합)': '70s_inflow'})

df_inflows = df_10_sum.merge(df_20_sum, on = 'hjd_cd').merge(df_30_sum, on = 'hjd_cd').merge(df_40_sum, on = 'hjd_cd').merge(df_50_sum, on = 'hjd_cd').merge(df_60_sum, on = 'hjd_cd').merge(df_70_sum, on = 'hjd_cd')

################################## 비율도 야무지게 추가해준다
df_inflows['total'] = df_inflows['10s_inflow'] + df_inflows['20s_inflow'] + df_inflows['30s_inflow'] + df_inflows['40s_inflow'] + df_inflows['50s_inflow'] + df_inflows['60s_inflow'] + df_inflows['70s_inflow']
df_inflows['10s_r'] = df_inflows['10s_inflow']/df_inflows['total']
df_inflows['20s_r'] = df_inflows['20s_inflow']/df_inflows['total']
df_inflows['30s_r'] = df_inflows['30s_inflow']/df_inflows['total']
df_inflows['40s_r'] = df_inflows['40s_inflow']/df_inflows['total']
df_inflows['50s_r'] = df_inflows['50s_inflow']/df_inflows['total']
df_inflows['60s_r'] = df_inflows['60s_inflow']/df_inflows['total']
df_inflows['70s_r'] = df_inflows['70s_inflow']/df_inflows['total']

 

그리고 QGIS에서

Ctrl + Shift + T 로 csv레이어를 추가해준 뒤

행정동을 중심으로 join 해준다.

그런데 또 개열받는 포인트는 이번에 행정동이 int여서 안붙으니까, str으로 바꿔줘야 한다는 거다.

근데 왜 안붙을까...

행정동 코드가 다르니까 안붙지...

제발 코드 통일 좀 해라 나쁜놈들아 하 진짜 한두번도 아니고

 

브이월드 공간정보 다운로드

 

브이월드

국가가 보유하고 있는 공개 가능한 공간정보를 모든 국민이 자유롭게 활용할 수 있도록 다양한 방법을 제공합니다.

www.vworld.kr

 

여기에서 행정동 데이터를 다시 다운받아서 온다..

안맞는다...

왜 또 이놈의 shp에서는 끝에 0을 하나 더 붙여가지고 말이야...

우리가 가진 행정동 코드와 일치시켜주기 위해 새로운 필드를 만들어준다.

 

드디어 붙였다...흑흑 저 동들은 왜 안붙었을까...

알게 뭐야... 아니야 이따가 확인해보자...

 

(--> 확인해보니, 이유는 모르겠지만 아예 생활이동 데이터 집계에서 빠져있다. 다른 코드를 쓰는지 뭔지...)

 

이게 방문 인구가 많은 동을 20%씩 끊은 quantile 결과다

728x90