티스토리 뷰
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 을 얻을 수 있겠죠?
| ||
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' 카테고리의 다른 글
[ggplot2] fct_reorder, forcats 사용 barchart 값 기준 순서 재정렬 (0) | 2021.07.30 |
---|---|
[R] plot 한글 관련 (0) | 2018.07.24 |
- Total
- Today
- Yesterday
- sql
- 여름휴가추천
- 머신러닝
- 영월여행
- 계곡캠핑
- 여름캠핑
- 반려견캠핑
- 가족여행
- 알고리즘
- 가족캠핑
- 강원도여행
- SeoulTravel
- Oracle
- 강원도캠핑
- 서울근교캠핑
- 백준
- 카카오
- python
- 파이썬
- 커플여행
- 캠핑장추천
- 캠핑초보
- 가평캠핑
- 글램핑
- 가평여행
- Koreancuisine
- 자연힐링
- 영월캠핑
- bukhansannationalpark
- 여름휴가
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |