인프런 커뮤니티 질문&답변

ccow1021님의 프로필 이미지
ccow1021

작성한 질문수

QGIS 파이썬 자동화 (벡터편) Ver.2

5.MERGE QGIS 코드 실습

merge 관련 문의 입니다.

해결된 질문

작성

·

957

·

수정됨

2

- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요!
- 먼저 유사한 질문이 있었는지 검색해보세요.
- 서로 예의를 지키며 존중하는 문화를 만들어가요.
- 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.

 

1.shp 파일1 (필드 gid, 유소년) + shp 파일2 (필드 gid, 유아) 두 파일을 merge 했습니다.

gid 필드 기준으로 유소년 필드와 유아 필드를 열 결합하고 싶은데 행으로만 merge가 됩니다.

스크린샷 2023-05-22 오후 3.20.55.png

python 의 pd.merge(left, right, on = '기준열', how = '조인방식') 방식으로 shp파일을 결합하는 방법을 알고 싶습니다.

 

2. 속성테이블의 NULL 값을 숫자 0으로 바꾸고 모든 값을 정수로 바꾸는 코드를 알고 싶습니다.

pyqgis 쿡북을 보면서 주말내내 고민하다가 우연히 강의를 찾고 수강하게 되었습니다.

도움을 부탁드립니다.

감사합니다.

답변 2

2

UPWISE님의 프로필 이미지
UPWISE
지식공유자

질문 2는 속성테이블의 필드계산기 기능을 활용해야합니다.

필드계산기 또한, QGIS에서 매우 활발히 쓰이지만 본 강의에는 별도로 다루고 있지 않습니다. 필드계산기의 사용 예시는 아래 링크를 참고 부탁드립니다.
https://urbn-ds.tistory.com/29

필드계산기 사용 예시를 보셨다면, 사용자가 원하는 수식을 직접 입력하여 필드 값 (attributes)을 수정하거나 추가할 수 있음을 확인할 수 있습니다. 질문자님께서 질문 주신 내용에 따르면, 우리는 두 가지의 필드계산을 수행해야 합니다.

  1. 특정 필드의 NULL 값을 0으로 변경

  2. 모든 값을 정수로 변경

필드 계산기의 경우, 사용하고자 하는 수식에 따라 그 내용이 크게 바뀌기 때문에 코드 가이드 안에 위 두 가지 계산식을 포함하여 코드를 작성해드리겠습니다.

##필드계산기
# 필드명
fieldName = 'fieldname' #NULL 값이 포함된 필드명으로 수정
# 두 가지 계산식이 적용된 공식
formula = 'to_int(if("fieldname" is null, 0, "fieldname"))'
# 일시 산출물
output_field_re = 'memory:output_field_re'
# 계산기 파라미터
fieldReParams = {'INPUT' : input, 'FIELD_NAME' : fieldName, 'FORMULA' : formula,'OUTPUT' : output_field_re}
# 계산기 실행
fieldRe = processing.run('native:fieldcalculator', fieldReParams)
# 계산기 결과 추가 및 완료 메시지
QgsProject.instance().addMapLayer(fieldRe['OUTPUT'])

위 코드에서 직접 입력이 필요한 내용은 'fieldname' 입니다. 순서를 간단히 말씀드리자면,

  1. NULL 값이 포함된 필드 이름을 지정

  2. formula 부분에서 만약 필드의 속성값이 NULL이라면 0을 반영, 아니라면 기존의 속성값 그대로 반영

  3. to_int 를 통해 필드의 모든 값을 정수형으로 변경

  4. 임시 산출물로 레이어 추가
    입니다.

앞서 말씀드린 것처럼, 필드계산기는 수식을 통해 속성값을 변경할 수 있으며, python에서 이를 원활히 수행하려면 필드계산기에서 사용되는 수식을 숙지할 필요가 있습니다. 만약, 필드계산기를 사용해보신 적이 없거나, 익숙하지 않으시다면, 실제로 필드계산기를 켜보고 다양한 수식을 연습해보며 방법을 익혀두시는 걸 추천드립니다.

저도 코드를 통해서 필드계산기를 사용하는 것은 익숙치 않았는데, 질문자님께서 좋은 질문을 남겨주셔서 추가적인 공부가 되었습니다.

혹시 코드가 제대로 작동하지 않거나 다른 문제가 발생한다면 언제든 답글 부탁드립니다.

감사합니다 :)

-UPWISE 답변-

ccow1021님의 프로필 이미지
ccow1021
질문자

안녕하세요.

질문에 대한 친절하신 답변 너무 감사합니다.
보내주신 코드를 바탕으로 shp 파일 속성테이블의 모든 값을 정수로 바꾸고 null 값은 0으로 바꾸는 시도를 했습니다.

에러가 나서 chatgpt 확인결과

모든 필드 값을 한 번에 변경하는 것은 QGIS에서 직접 지원하지 않는 기능일 수 있습니다. 각 필드는 고유한 데이터 유형을 가질 수 있기 때문에 이들을 일괄적으로 변환하는 것은 데이터 유형 불일치 문제를 일으킬 수 있습니다. 따라서 일반적으로는 개별 필드에 대한 작업을 수행하게 됩니다.

라는 답변을 받고 chatgpt 의 도움을 받아 개별필드 작업으로 완성했습니다.

python과 qgis 를 독학하고 있어서 코드가 지져분하지만 첨부해서 올리겠습니다.

UPWISE 님 답변 정말 너무 감사합니다.

from qgis.core import QgsProject, QgsVectorLayer, QgsField
from qgis import processing

# input
input = '/Users/test/결과저장/sex.shp'
input_layer = QgsVectorLayer(input, "", "ogr")

# output directory
output_dir = '/Users/test'

# 필드 데이터 타입 출력
for field in input_layer.fields():
    fieldName = field.name()
    fieldType = field.typeName()
    print(f'Field name: {fieldName}, Field type: {fieldType}')

for field in input_layer.fields():
    fieldName = field.name()

    # 계산식 생성
    if field.typeName() in ('Integer', 'Real'):  # 숫자 필드만 대상
        formula = f'to_int(if("{fieldName}" is null, 0, "{fieldName}"))'
    else:
        continue  # 숫자 필드가 아닌 경우 건너뜁니다

    # output file
    output = f'{output_dir}/updated_{fieldName}.gpkg'

    # 계산기 파라미터
    fieldReParams = {'INPUT' : input, 'FIELD_NAME' : fieldName, 'FORMULA' : formula,'OUTPUT' : output}

    # 계산기 실행
    fieldRe = processing.run('qgis:fieldcalculator', fieldReParams)

    # 결과를 입력 레이어로 다시 설정
    input = output
    input_layer = QgsVectorLayer(input, "", "ogr")

# 계산기 결과 레이어 로드
output_layer = QgsVectorLayer(output, "Updated Layer", "ogr")

if not output_layer.isValid():
    print("Failed to load the layer!")
else:
    # 계산기 결과 추가 및 완료 메시지
    QgsProject.instance().addMapLayer(output_layer)

2

UPWISE님의 프로필 이미지
UPWISE
지식공유자

안녕하세요. UPWISE 입니다.

우선 제 강의를 수강해주셔서 감사합니다. 질문자님께서 pyqgis 쿡북까지 찾아보실 정도면, 감히 QGIS와 python의 엄청난 고수라고 짐작해봅니다 :)

질문해주신 내용을 한번에 답변 하기에는 내용이 너무 길어서, 질문 1과 2를 나눠서 각각 댓글로 답변 드리겠습니다.

우선, 질문 1의 경우, geometry가 동일한 두 shp 파일의 속성만 결합하고 싶은 경우로 생각됩니다. 이러한 경우, vector layer merge가 아닌, join (필드 값으로 속성 결합) 기능을 활용하셔야 합니다. join의 경우, QGIS에서 기본적으로 사용하는 기능 중 하나이나, 본 강의에는 python 자동화 측면에만 집중되어 있어 해당 내용이 누락되어 있습니다. python을 사용하는 것이 아닌, 수동으로 join을 진행하는 방법은 아래 링크를 참고 부탁드립니다.
https://wikidocs.net/163960

python을 활용하여 join을 진행하는 방법은 아래 코드 가이드를 참고하시면 됩니다.

##조인
# 조인할 두 레이어
input_1 = 'layer_path'
input_2 = 'layer_path'
# 결합 필드명
join_field = 'fieldname' #실제 두 레이어에 동시에 포함되어 있는 필드명을 적용할 것
# 일시 산출물
output_join = 'memory:output_join'
# 조인 파라미터
joinParams = {'INPUT' : input_1, 'FIELD' : join_field, 'INPUT_2' : input_2, 'FIELD_2' : join_field, 'OUTPUT' : output_join}
# 조인 실행
join = processing.run('native:joinattributestable', joinParams)
# 조인 결과 레이어 추가
QgsProject.instance().addMapLayer(join['OUTPUT'])

위 코드에서 직접 입력이 필요한 내용은 'layer_path''fieldname' 부분 입니다.

질문 주신 두 shp 파일을 예시로 들자면, input_1,2에는 shp 파일 1과 2의 경로를 입력하시고,
fieldname엔 gid를 입력하면 됩니다.

확인해보시고, 혹시 문제가 발생하는 부분이 있다면 댓글 부탁드립니다. 감사합니다 :)

-UPWISE 답변-

ccow1021님의 프로필 이미지
ccow1021
질문자

답변 정말 너무 감사합니다.

파일 결합때문에 거의 일주일을 고생했는데 UPWISE 님 답변 덕분에 드디어 해결되었습니다.

정말 너무 감사합니다.

import glob
import os
from qgis.core import QgsProject, QgsVectorLayer, QgsField
from qgis.analysis import QgsNativeAlgorithms
import processing
from processing.core.Processing import Processing

Processing.initialize()
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())

# 조인할 레이어 경로
directory_path = '/Users/test/2022/'
# 결합 필드명
join_field = 'gid'  # 실제 두 레이어에 동시에 포함되어 있는 필드명을 적용할 것
# 필요한 필드
required_fields = ['gid', '유아', '유소년', '초등', '중등', '고등', '20대', '30대', '40대', '50대', '60대', '70대', '80대', '90대']

# Get list of all .shp files in the directory and sort them in ascending order
shp_list = sorted(glob.glob(directory_path + '*.shp'))

join = None
for i in range(len(shp_list)-1):
    input_1 = join['OUTPUT'] if join else shp_list[i]
    input_2 = shp_list[i+1]

    # 일시 산출물
    output_join = 'memory:output_join'
    # 조인 파라미터
    joinParams = {'INPUT' : input_1, 'FIELD' : join_field, 'INPUT_2' : input_2, 'FIELD_2' : join_field, 'OUTPUT' : output_join}
    # 조인 실행
    join = processing.run('native:joinattributestable', joinParams)

    # 필드 삭제
    layer = join['OUTPUT']
    fields = layer.fields()
    to_remove = [field.name() for field in fields if field.name() not in required_fields]
    if to_remove:
        layer.dataProvider().deleteAttributes([fields.indexOf(f) for f in to_remove])
        layer.updateFields()

    # 조인 결과 레이어 추가
    QgsProject.instance().addMapLayer(layer)
UPWISE님의 프로필 이미지
UPWISE
지식공유자

안녕하세요. UPWISE 입니다.

문제가 잘 해결되었다니 정말 다행입니다.

그리고 스스로 찾아내신 해결법도 공유해주셔서 감사합니다. 저도 참고하여 한 번 실습해보도록 하겠습니다!! :)

이후에도 문의사항이 생긴다면 언제든 글 작성해주세요. 저도 많은 공부가 되었습니다 ㅎㅎ

감사합니다!!

-UPWISE 답변-

ccow1021님의 프로필 이미지
ccow1021

작성한 질문수

질문하기