본문 바로가기
R

XML 구조 및 정의, parsing in r , xml to dataframe

by Gom Guard 2019. 2. 26.

XML 정의 

  • XML 은 소프트웨어나 하드웨어에 관계없이 데이터를 저장하거나 전송하기위한 도구, 마크업 언어 입니다.

  • 일반적으로 데이터를 구조화해서 다른 프로그래밍 언어간 전송할 때 자주 사용하기도 하고 최근에는 open API 에서 데이터를 얻을 때 json 이나 xml 형태로 얻는 경우가 많습니다.

  • 기본적인 구조를 살펴보고 R 에서는 xml 파일을 어떻게 다루어야 하는지 살펴볼까요.


XML 구조 설명 

  • XML 에서는 크게 markup(tag), content, element, attribute  총 4가지만 인지하고 있으면 됩니다. 기본적으로 데이터를 저장하는 수단일 뿐이기 때문에 그리 어렵게 정의된 언어는 아니지요.

  • 보다 자세한 것은 위키백과나 W3C 에서 살펴보시면 될 것 같습니다.

    • 위키 : https://ko.wikipedia.org/wiki/XML

    • w3c : https://www.w3schools.com/xml/default.asp


  • html 을 접하신 분은 아시겠지만 markup 이라는 것이 tag 와 동일한 단어입니다. < 로 시작해서 > 로 끝나는 부분을 markup 이라고 합니다. 이 markup 이라는 것은 텍스트에 의미를 부여하거나 구조화 하는 역할을 합니다.

  • content 라고 불리는 내용은 markup 태그를 제외한 텍스트들을 의미합니다. 기본적으로 시작 태그와 종료 태그 사이에 위치합니다.



  • parsing 하는 과정에서 몰라서는 안될 element 와 attribute 입니다. xml 작성 방식에 따라 중요 데이터를 content 에서 parsing 해야할 때도 있고 attribute 에서 parsing 해야할 경우도 있습니다만 개념만 잘 알고 있으면 어떤 xml 이든 어렵지 않죠.

  • element 라는 것은 시작태그와 내용, 종료태그로 이루어지는 한 chunk 를 의미 합니다. 예를 들어 <name>Belgian Waffles </name> 와 같습니다. 이 element 의 내용에는 다른 element 가 들어갈 수도 있습니다. 이 경우, element 내에 다른 element 가 있는 경우 이 element 를 자식 엘리먼트 ( child element ) 라고 칭합니다.

  • attribute 는 태그 내에 있는 정보를 의미합니다. attribute 는 이름 과 값의 한 쌍으로 이루어집니다. 예시에서는 초록색을 띄고 있는 currency='$' 가 해당합니다.



xml parsing in r - 01



  • xml parsing 에 사용할 예제 입니다. 주소는 https://www.w3schools.com/xml/simple.xml 과 같습니다. w3c 에서 제공하는 예제이며 다른 예제 xml 들도 있으니 연습하기 좋습니다.

  • R 에서 xml 을 다루기 위한 여러 패키지가 있지만 저는 xml2 를 사용하여 처리합니다. xml2 패키지에 대해 Hadley Wickham 씨가 작성한 문서를 첨부합니다.

    • https://blog.rstudio.com/2015/04/21/xml2/


library(xml2) library(tidyverse)

# sample xml url

xml_path <- 'https://www.w3schools.com/xml/simple.xml'

# read xml data from url

raw_xml <- read_xml(xml_path)

# food element

food_node <- xml_children(raw_xml)

# xml to data frame

lapply(seq_along(food_node),

function(x){

temp_row <- xml_find_all(food_node[x], './*')

tibble(

idx = x,

key = temp_row %>% xml_name(),

value = temp_row %>% xml_text()

) %>% return()

}

) %>% bind_rows() %>%

spread(key, value) %>%

select(food_node %>% xml_children() %>% xml_name %>% unique())


  • xml 을 파일로 갖고 있든 url 로 갖고 있든 관계 없이 read_xml 함수를 사용해서 읽어줍니다. 읽고 나면 raw_xml 에는 다음과 같은 값이 저장됩니다.



  • 결과를 보면 가장 큰 엘리먼트로 breakfast_menu, 그리고 그 안에 food 태그가 있고 그 안에 우리가 필요로 할만한 element 들이 싸여 있는 것을 알 수 있습니다. 이 내부의 element 들에 접근하기 위해서 먼저 xml_children 함수를 사용합니다. xml_children 함수를 사용하고 난 뒤의 결과는 다음과 같습니다.



  • raw_xml 과 달리 <breakfast_menu> 표시가 없어지고 {xml_document} 에서 {xml_nodeset (5)} 로 변경된 값이 출력되는 것을 알 수 있습니다. 이 상태에서 한 번 더 xml_children 을 사용한다면 어떻게 될까요.



  • food 태그 안에 있는 모든 노드들이 출력되는 것을 확인할 수 있습니다. 하지만 이렇게 할 경우 까다로운 일이 생길 수 있습니다. 각 food 별로 하나씩의 name, price, description, calories 가 있어야 하는 데 모두 합쳐져서 data frame 으로 변경하기가 까다롭다는 점입니다.
  • 지금 예제의 경우에는 20개 밖에 안되고 각각 4개씩 빠짐없이 갖춰져 있어서 해결할 수 있어보이지만 실무에서 사용하는 데이터 들은 이렇게 정갈하지 않죠. 그래서 xml_children 함수를 바로 사용하지 않고 각 값별로 사용하여 해결합니다.
  • xml_children 함수를 사용하는 방법도 있지만 예제를 보다 풍성하게 하기위해 xml_find_all 함수를 사용해볼까요. xml_find_all 함수는 두개의 파라메터를 사용합니다. 첫번째는 xml 데이터 이고 두번째는 찾으려는 태그의 경로입니다. './*' 의 경우는 현재 element 에서 한 단계 안쪽에 있는 모든 element 를 찾는 경로입니다.
  • food_node 1 번 인덱스의 값을 가지고 xml_find_all 함수를 실행해볼까요?



  • 각 food element 별로 한 줄씩 만들어서 합쳐주면 우리가 원하는 형태의 data frame 을 얻을 수 있겠죠?


# xml to data frame


lapply(seq_along(food_node),

function(x){

temp_row <- xml_find_all(food_node[x], './*')

tibble(

idx = x,

key = temp_row %>% xml_name(),

value = temp_row %>% xml_text()

) %>% return()

}

) %>% bind_rows() %>%


spread(key, value) %>%

select(food_node %>% xml_children() %>% xml_name %>% unique())


  • tibble 내에 있는 xml_name 함수는 여러 엘리먼트의 태그명을 추출하는 함수 입니다.
  • xml_text 함수는 엘리먼트 중 content 를 추출하는 함수입니다.
  • 맨 마지막의 select 구문은 idx 열을 제외함과 동시에 열의 순서를 맞춰주기 위해 사용했습니다.

  • w3c 에서 제공하는 것 같은 내용이 content 에 있는 예제들은 이렇게 parsing 하면 됩니다. 하지만 모든 xml 이 내용을 content 에 담고 있지는 않습니다.


xml parsing in r - 02

  • https://community.rstudio.com/t/generate-a-data-frame-from-many-xml-files/10214

  • rstudio 커뮤니티에 올라온 xml to dataframe 글을 살펴보면 해당 xml 데이터가 위의 데이터와는 조금 다른 것을 확인할 수 있습니다.



  • VALDISTRIKT 엘리먼트의 자식 엘리먼트들을 보면 시작태그와 내용, 종료태그로 이루어져있지 않고 합쳐져 있는 것을 알 수 있습니다. 이런 경우에는 해당 내용들을 attribute 형태로 작성하여 정보를 제공합니다. 이렇게 parsing 할 정보들이 content 가 아닌 attribute 형태로 작성된 경우에는 다른 방식으로 parsing 합니다.


raw_xml_2 <-

'<VALDISTRIKT KOD="01140212" NAMN="Smedby Södra" RÖSTER="1201" RÖSTER_FGVAL="1186" TID_RAPPORT="20140914230336" MODNR="117144935">

<GILTIGA PARTI="M" RÖSTER="227" RÖSTER_FGVAL="336" PROCENT="18,9" PROCENT_FGVAL="28,3" PROCENT_ÄNDRING="-9,4"/>

<GILTIGA PARTI="C" RÖSTER="35" RÖSTER_FGVAL="17" PROCENT="2,9" PROCENT_FGVAL="1,4" PROCENT_ÄNDRING="+1,5"/>

<GILTIGA PARTI="FP" RÖSTER="43" RÖSTER_FGVAL="61" PROCENT="3,6" PROCENT_FGVAL="5,1" PROCENT_ÄNDRING="-1,6"/>

<ÖVRIGA_GILTIGA RÖSTER="20" RÖSTER_FGVAL="10" PROCENT="1,7" PROCENT_FGVAL="0,8" PROCENT_ÄNDRING="+0,8"/>

<OGILTIGA TEXT="BLANK" RÖSTER="12" RÖSTER_FGVAL="13" PROCENT="1,0" PROCENT_FGVAL="1,1" PROCENT_ÄNDRING="-0,1"/>

<OGILTIGA TEXT="OG" RÖSTER="13" RÖSTER_FGVAL="1" PROCENT="1,1" PROCENT_FGVAL="0,1" PROCENT_ÄNDRING="+1,0"/>

<VALDELTAGANDE RÖSTBERÄTTIGADE="1551" RÖSTBERÄTTIGADE_KLARA_VALDISTRIKT_FGVAL="1546" SUMMA_RÖSTER="1226" SUMMA_RÖSTER_FGVAL="1200" PROCENT="79,0" PROCENT_FGVAL="77,6" PROCENT_ÄNDRING="+1,4"/>

</VALDISTRIKT>' %>% read_xml()

xml_find_all(raw_xml_2, '//VALDISTRIKT/*') %>%

map(xml_attrs) %>%

map_df(~as.list(.)) %>%

mutate(TYPE = xml_find_all(raw_xml_2, '//VALDISTRIKT/*') %>% xml_name)


  • raw_xml_2 는 xml 텍스트를 read_xml 함수를 사용해 읽어온 값을 저장하는 변수입니다.

  • VALDISTRIKT 엘리먼트 내의 값들을 필요로하는 상황이기 때문에 xml_find_all(raw_xml_2, '//VALDISTRIKT/*') 함수를 사용하여 자식엘리먼트들을 찾아냅니다.

  • 이후 xml_attrs 함수를 사용하여 각 엘리먼트의 attribute 들을 추출합니다.



  • <GILTIGA PARTI="M" RÖSTER="227" RÖSTER_FGVAL="336" PROCENT="18,9" PROCENT_FGVAL="28,3" PROCENT_ÄNDRING="-9,4"/>

  • 첫 번째 자식 엘리먼트들의 attribute 의 이름과 값들이 잘 추출된 것을 확인할 수 있습니다.

  • 이후 map_df(~as.list()) 를 통해 data frame 으로 만들어 주면 간단합니다.


  • xml to dataframe 은 데이터를 다루다 보면 꼭 한번씩은 진행해야 하는 process 중 하나입니다.







부족한 블로그에 방문해 주셔서 감사합니다.

잘못된 부분이나 질문이 있으시면 

댓글로 말씀해주세요.


금방 확인하고 피드백 드리겠습니다.


좋은 하루 되세요. ^^








'R' 카테고리의 다른 글

XML 구조 및 정의, parsing in r , xml to dataframe  (2) 2019.02.26
[R] plot 한글 관련  (0) 2018.07.24

댓글2