聰明不如鈍筆
총명불여둔필
assignment KindeR

R에서 깔끔하게 XML 파일 데이터 프레임으로 만들기(feat. unnest_longer, unnest_wider)

인터넷을 하다 보면 자료를 XML 형태로 제공하는 사이트를 만날 때가 있습니다.

 

행정안전부에서 운영하는 공공 데이터 통합 제공 시스템 '공공데이터포털'이 대표 사례입니다.

 

이번 포스트에서는 국토교통부에서 공공데이터포털을 통해 제공하는 '아파트 매매 실거래 상세 자료'를 tibble 형태로 바꾸는 연습을 해보겠습니다.

 

노파심에 말씀드리면 R에서 가장 널리 쓰는 데이터 유형은 테이블 형식으로 자료를 정리하는 '데이터 프레임'입니다.

 

tibble은 이 데이터 프레임을 업그레이드한 형태라고 생각하시면 됩니다.

 

XML은 'eXtensible Markup Language'를 줄인 말입니다.

 

일단 마크업 언어(Markup Language)라는 건 여는 태그와 닫는 태그가 따로 있다는 뜻입니다.

 

인터넷 페이지를 만들 때 쓰는 하이퍼 텍스트 마크업 언어(HTML)는 <html>로 열고 </html>로 닫습니다.

 

XML도 같은 방식으로 <...>로 열고 </...>로 닫습니다.

 

미리 약속한 태그를 써야 하는 HTML과 달리 XML은 작성자가 태그를 임의로 지정할 수 있습니다.

 

그래서 '확장이 가능하다'(eXtensible)는 표현을 쓰는 겁니다.

 

말보다 직접 보면 더 이해가 쉬울 수 있습니다. 우리가 이번 포스트에서 쓸 예제 데이터는 이렇게 생겼습니다.

<?xml version="1.0" encoding="UTF-8" standalone="true"?> 
- <response> 
  - <header> 
    <resultCode>00</resultCode> 
    <resultMsg>NORMAL SERVICE.</resultMsg> 
    </header> 
  - <body> 
     - <items> 
       - <item> 
            <거래금액> 34,700</거래금액> 
            <거래유형> </거래유형> 
            <건축년도>2001</건축년도> 
            <년>2015</년> 
            <도로명>만양로</도로명> 
            <도로명건물본번호코드>00019</도로명건물본번호코드> 
            <도로명건물부번호코드>00000</도로명건물부번호코드> 
            <도로명시군구코드>11590</도로명시군구코드>
            <도로명일련번호코드>01</도로명일련번호코드>
            <도로명지상지하코드>0</도로명지상지하코드>
            <도로명코드>3119002</도로명코드>
            <법정동> 노량진동</법정동> 
            <법정동본번코드>0325</법정동본번코드> 
            <법정동부번코드>0000</법정동부번코드> 
            <법정동시군구코드>11590</법정동시군구코드>
            <법정동읍면동코드>10100</법정동읍면동코드>
            <법정동지번코드>1</법정동지번코드>
            <아파트>신동아리버파크</아파트>
            <월>10</월>
            <일>2</일>
            <일련번호>11590-4</일련번호>
            <전용면적>59.67</전용면적>
            <중개사소재지> </중개사소재지>
            <지번>325</지번>
            <지역코드>11590</지역코드>
            <층>6</층>
            <해제사유발생일> </해제사유발생일>
            <해제여부> </해제여부>
         </item>
         (중간 생략)
        </items>
        <numOfRows>10</numOfRows>
        <pageNo>1</pageNo>
        <totalCount>409</totalCount>
    </body>
  </response>

여기서 중간 생략이 들어간 건 실제로는 똑같은 구조로 <item> </item> 사이에 9건이 더 들어 있기 때문입니다.

 

10건이 전부 들어가 있는 데이터는 아래에서 내려받으실 수 있습니다.

seoul_apt_example.xml
0.01MB

XML 파일을 테이블 형태로 확인할 수 있는 가장 빠른 방법은 마이크로소프트(MS) 엑셀을 활용하는 겁니다.

 

엑셀 2016 기준으로 메뉴에서 '데이터' - '외부 데이터 가져오기' - '기타 원본에서'로 따라가시면 'XML 데이터 가져오기' 항목이 있습니다.

 

이를 활용해 XML 파일을 열어 보면 경고 창이 몇 개 뜬 다음 테이블 형태로 변합니다.

 

작은 XML 파일을 하나 내려받았을 때는 이렇게 엑셀을 이용하면 간단하게 끝납니다.

 

그런데 보통 XML로 자료를 제공받을 때는 여러 가지 변수를 조합해서 자료를 받은 다음 이를 다시 합쳐야 하는 일이 많습니다.

 

예를 들어 2021년 5월 서울시 아파트 거래 내역을 확인하려면 25개 자치구별로 데이터를 전부 불러와야 합니다.

 

이럴 때는 파일을 하나 하나 불러와서 합치는 것보다 코드로 처리하는 게 훨씬 효율적일 겁니다.

 

그러면 이제 본격적으로 R에서 XML 파일을 구문 분석(파싱·parsing)하는 방법을 알아보겠습니다.

 

제일 먼저 할 일은 늘 그렇듯 tidyverse 패키지 (설치하고) 불러오기.

#install.package('tidyverse')
library('tidyverse')
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.3     v purrr   0.3.4
## v tibble  3.1.4     v dplyr   1.0.6
## v tidyr   1.1.3     v stringr 1.4.0
## v readr   1.4.0     v forcats 0.5.0
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

 

이어서 tidyverse 생태계에서 XML 처리를 담당하는 xml2 패키지도 (설치하고) 불러옵니다.

#install.packages('xml2')
library('xml2')

 

계속해서 read_xml() 함수로 예제 데이터를 불러와 apt_xml 객체에 넣어둡니다.

read_xml('seoul_apt_example.xml') -> apt_xml

 

apt_xml을 열어보면 rvest 패키지로 HTML 문서를 불러왔을 때와 비슷한 구조라는 사실을 확인할 수 있습니다.

apt_xml
## {xml_document}
## <response>
## [1] <header>\n  <resultCode>00</resultCode>\n  <resultMsg>NORMAL SERVICE.</re ...
## [2] <body>\n  <items>\n    <item>\n      <거래금액>    34,700</거래금액>\n      <거래유형 ...

 

일반적으로는 객체에 as_tibble() 함수를 적용하면 tibble로 바꿀 수 있지만 이번에는 에러 메시지가 나옵니다.

apt_xml %>% 
  as_tibble()
## Error in as.data.frame.default(value, stringsAsFactors = FALSE): cannot coerce class 'c("xml_document", "xml_node")' to a data.frame

파이프(%>%) 기호가 이해가 가지 않는 분도 계실 겁니다.

 

그런 분께서는 '최대한 친절하게 쓴 R로 데이터 뽑아내기(feat. dplyr)' 포스트를 읽어 보시면 도움이 될 수 있습니다.

 

다시 코드로 돌아가 중간에 as_list() 함수를 한 번 넣어주면 일단 tibble 변환이 가능합니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble()
## # A tibble: 2 x 1
##   response        
##   <named list>    
## 1 <named list [2]>
## 2 <named list [4]>

이런 데이터 형태를 중첩(nested) tibble이라고 부릅니다. 문자 그대로 tibble 셀(cell) 안에 데이터를 중첩했다는 뜻입니다.

 

중첩 tibble은 unnest() 함수로 일단 풀면 '속살'을 확인해 볼 수 있습니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest(response)
## # A tibble: 6 x 1
##   response         
##   <named list>     
## 1 <list [1]>       
## 2 <list [1]>       
## 3 <named list [10]>
## 4 <list [1]>       
## 5 <list [1]>       
## 6 <list [1]>

 

그래도 또 list가 나옵니다. unnest() 함수를 한 번 더 적용해 봅니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest(response) %>% 
  unnest(response)
## # A tibble: 15 x 1
##    response         
##    <named list>     
##  1 <chr [1]>        
##  2 <chr [1]>        
##  3 <named list [28]>
##  4 <named list [27]>
##  5 <named list [27]>
##  6 <named list [27]>
##  7 <named list [28]>
##  8 <named list [28]>
##  9 <named list [28]>
## 10 <named list [28]>
## 11 <named list [28]>
## 12 <named list [28]>
## 13 <chr [1]>        
## 14 <chr [1]>        
## 15 <chr [1]>

 

도돌이표가 이어질 듯하니 이쯤에서 unnest_longer() 함수로 열어보겠습니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest_longer(response)
## # A tibble: 6 x 2
##   response          response_id
##   <named list>      <chr>      
## 1 <list [1]>        resultCode 
## 2 <list [1]>        resultMsg  
## 3 <named list [10]> items      
## 4 <list [1]>        numOfRows  
## 5 <list [1]>        pageNo     
## 6 <list [1]>        totalCount

재미있는 결과가 나왔습니다. response_id 열에 나온 값이 어디서 어디 많이 본 것 같지 않으신가요?

 

우리에게 필요한 데이터는 items에 들어 있는 걸 아니까 그 부분만 골라낸 뒤 다시 열어봅니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest_longer(response) %>% 
  filter(response_id == 'items') %>% 
  unnest(response)
## # A tibble: 10 x 2
##    response          response_id
##    <named list>      <chr>      
##  1 <named list [28]> items      
##  2 <named list [27]> items      
##  3 <named list [27]> items      
##  4 <named list [27]> items      
##  5 <named list [28]> items      
##  6 <named list [28]> items      
##  7 <named list [28]> items      
##  8 <named list [28]> items      
##  9 <named list [28]> items      
## 10 <named list [28]> items

이 부분도 이해가 가지 않으시면 '최대한 친절하게 쓴 R로 데이터 뽑아내기(feat. dplyr)' 포스트를 참고하시면 좋습니다.

 

이 결과를 자세히 보시면 2~4행만 리스트 길이가 다르다는 걸 알 수 있습니다.

 

이번에는 unnest_wider() 함수를 써서 옆으로 벌려 봅니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest_longer(response) %>% 
  filter(response_id == 'items') %>% 
  unnest(response) %>% 
  unnest_wider(response)
## # A tibble: 10 x 29
##    거래금액   거래유형   건축년도   년    도로명 도로명건물본번~ 도로명건물부번~
##    <list>     <list>     <list>     <lis> <list> <list>          <list>         
##  1 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  2 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  3 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  4 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  5 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  6 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  7 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  8 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
##  9 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
## 10 <list [1]> <list [1]> <list [1]> <lis~ <list~ <list [1]>      <list [1]>     
## # ... with 22 more variables: 도로명시군구코드 <list>,
## #   도로명일련번호코드 <list>, 도로명지상지하코드 <list>, 도로명코드 <list>,
## #   법정동 <list>, 법정동본번코드 <list>, 법정동부번코드 <list>,
## #   법정동시군구코드 <list>, 법정동읍면동코드 <list>, 법정동지번코드 <list>,
## #   아파트 <list>, 월 <list>, 일 <list>, 일련번호 <list>, 전용면적 <list>,
## #   중개사소재지 <list>, 지번 <list>, 지역코드 <list>, 층 <list>,
## #   해제사유발생일 <list>, 해제여부 <list>, response_id <chr>

네, 우리가 원하는 결과와 비슷한 형태가 나왔습니다.

 

나머지 열을 전부 열여주면 될 것 같습니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest_longer(response) %>% 
  filter(response_id == 'items') %>% 
  unnest(response) %>% 
  unnest_wider(response) %>% 
  unnest(everything())
## # A tibble: 10 x 29
##    거래금액  거래유형  건축년도  년       도로명 도로명건물본번~ 도로명건물부번~
##    <list>    <list>    <list>    <list>   <list> <list>          <list>         
##  1 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  2 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  3 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  4 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  5 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  6 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  7 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  8 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
##  9 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
## 10 <chr [1]> <chr [1]> <chr [1]> <chr [1~ <chr ~ <chr [1]>       <chr [1]>      
## # ... with 22 more variables: 도로명시군구코드 <list>,
## #   도로명일련번호코드 <list>, 도로명지상지하코드 <list>, 도로명코드 <list>,
## #   법정동 <list>, 법정동본번코드 <list>, 법정동부번코드 <list>,
## #   법정동시군구코드 <list>, 법정동읍면동코드 <list>, 법정동지번코드 <list>,
## #   아파트 <list>, 월 <list>, 일 <list>, 일련번호 <list>, 전용면적 <list>,
## #   중개사소재지 <list>, 지번 <list>, 지역코드 <list>, 층 <list>,
## #   해제사유발생일 <list>, 해제여부 <list>, response_id <chr>

안타깝게도 이번에도 '꽝'입니다.

 

이번에는 반드시 긁을 것이란 생각으로 한 번만 더 열여 보고 이 데이터를 apt_df 객체에 넣어둡니다.

apt_xml %>% 
  as_list() %>% 
  as_tibble() %>% 
  unnest_longer(response) %>% 
  filter(response_id == 'items') %>% 
  unnest(response) %>% 
  unnest_wider(response) %>% 
  unnest(everything()) %>% 
  unnest(everything()) -> apt_df
apt_df
## # A tibble: 10 x 29
##    거래금액     거래유형 건축년도 년    도로명   도로명건물본번~ 도로명건물부번~
##    <chr>        <chr>    <chr>    <chr> <chr>    <chr>           <chr>          
##  1 "    34,700" " "      2001     2015  만양로   00019           00000          
##  2 "    16,631" " "      2015     2015  노량진로 00140           00000          
##  3 "    15,334" " "      2015     2015  노량진로 00140           00000          
##  4 "    14,733" " "      2015     2015  노량진로 00140           00000          
##  5 "    47,000" " "      2001     2015  만양로   00019           00000          
##  6 "    33,400" " "      1997     2015  만양로8~ 00050           00000          
##  7 "    48,000" " "      2001     2015  만양로   00019           00000          
##  8 "    52,000" " "      1997     2015  만양로8~ 00050           00000          
##  9 "    42,000" " "      2001     2015  만양로   00084           00000          
## 10 "    48,000" " "      2001     2015  만양로   00019           00000          
## # ... with 22 more variables: 도로명시군구코드 <chr>, 도로명일련번호코드 <chr>,
## #   도로명지상지하코드 <chr>, 도로명코드 <chr>, 법정동 <chr>,
## #   법정동본번코드 <chr>, 법정동부번코드 <chr>, 법정동시군구코드 <chr>,
## #   법정동읍면동코드 <chr>, 법정동지번코드 <chr>, 아파트 <chr>, 월 <chr>,
## #   일 <chr>, 일련번호 <chr>, 전용면적 <chr>, 중개사소재지 <chr>, 지번 <chr>,
## #   지역코드 <chr>, 층 <chr>, 해제사유발생일 <chr>, 해제여부 <chr>,
## #   response_id <chr>

야호, 드디어 목적지에 도착했습니다.

 

이 작업이 이렇게 오래 걸린 이유는 돌다리도 두드려 보는 마음가짐으로 조심스레 전진했기 때문입니다.

 

이럴 거면 그냥 처음에 MS 엑셀로 열어보는 게 훨씬 간단하다고 생각하신 분도 계실 겁니다.

 

그런데 답을 알고 나면 파일이 아무리 많아도 이를 한 번에 처리할 수 있습니다.

 

이때 등장하는 개념이 바로 '사용자 정의 함수'입니다.

 

코딩에서 (사용자 정의) 함수는 '반복해서 사용할 수 있는 코드 덩어리'라고 이해하시면 됩니다.

 

지금까지 우리가 작업한 내용을 'xml_to_df'라는 함수로 만들어 보겠습니다.

xml_to_df <- function(file){
  xml_df <- read_xml(file) %>%  
    as_list() %>% 
    as_tibble() %>% 
    unnest_longer(response) %>% 
    filter(response_id == 'items') %>% 
    unnest(response) %>% 
    unnest_wider(response) %>% 
    unnest(cols = everything()) %>% 
    unnest(cols = everything())
  return(xml_df)
  }

정말 이 코드를 반복해서 쓸 수 있는지 한 번 해볼까요?

 

'아파트 매매 실거래 상세 자료'에서 예제 파일을 하나 더 만들어 내려 받습니다.

seoul_apt_example_2.xml
0.01MB

그다음 xml_to_df('seoul_apt_example_2.xml')이라고 입력하시면 그냥 한 번에 결과가 나옵니다.

xml_to_df('seoul_apt_example_2.xml')
## # A tibble: 10 x 29
##    거래금액     거래유형 건축년도 년    도로명   도로명건물본번~ 도로명건물부번~
##    <chr>        <chr>    <chr>    <chr> <chr>    <chr>           <chr>          
##  1 "    82,500" " "      2008     2015  사직로8~ 00004           00000          
##  2 "    60,000" " "      1981     2015  세종대~  00047           00000          
##  3 "   130,000" " "      2004     2015  경희궁2~ 00005           00005          
##  4 "   105,000" " "      2004     2015  사직로8~ 00024           00000          
##  5 "   120,000" " "      2003     2015  사직로8~ 00020           00000          
##  6 "    17,000" " "      2014     2015  대학로   00047           00000          
##  7 "    17,000" " "      2014     2015  대학로   00047           00000          
##  8 "    57,000" " "      2006     2015  혜화로3~ 00030           00000          
##  9 "    44,000" " "      1995     2015  창경궁로 00265           00000          
## 10 "    52,000" " "      1995     2015  창경궁로 00265           00000          
## # ... with 22 more variables: 도로명시군구코드 <chr>, 도로명일련번호코드 <chr>,
## #   도로명지상지하코드 <chr>, 도로명코드 <chr>, 법정동 <chr>,
## #   법정동본번코드 <chr>, 법정동부번코드 <chr>, 법정동시군구코드 <chr>,
## #   법정동읍면동코드 <chr>, 법정동지번코드 <chr>, 아파트 <chr>, 월 <chr>,
## #   일 <chr>, 일련번호 <chr>, 전용면적 <chr>, 중개사소재지 <chr>, 지번 <chr>,
## #   지역코드 <chr>, 층 <chr>, 해제사유발생일 <chr>, 해제여부 <chr>,
## #   response_id <chr>

 

당연히 한꺼번에 여러 파일을 읽는 것도 가능합니다.

 

tidyverse 생태계에서 이런 작업을 담당하는 함수는 map()입니다.

 

먼저 예제 파일 두 개를 묶어서 files라는 객체를 만들어 놓은 다음,

c('seoul_apt_example.xml',
  'seoul_apt_example_2.xml') -> files

 

map() 함수 문법에 맞게 코드를 써보면 한 번에 결과가 나오는 걸 알 수 있습니다.

map(files, ~xml_to_df(.x))
## [[1]]
## # A tibble: 10 x 29
##    거래금액     거래유형 건축년도 년    도로명   도로명건물본번~ 도로명건물부번~
##    <chr>        <chr>    <chr>    <chr> <chr>    <chr>           <chr>          
##  1 "    34,700" " "      2001     2015  만양로   00019           00000          
##  2 "    16,631" " "      2015     2015  노량진로 00140           00000          
##  3 "    15,334" " "      2015     2015  노량진로 00140           00000          
##  4 "    14,733" " "      2015     2015  노량진로 00140           00000          
##  5 "    47,000" " "      2001     2015  만양로   00019           00000          
##  6 "    33,400" " "      1997     2015  만양로8~ 00050           00000          
##  7 "    48,000" " "      2001     2015  만양로   00019           00000          
##  8 "    52,000" " "      1997     2015  만양로8~ 00050           00000          
##  9 "    42,000" " "      2001     2015  만양로   00084           00000          
## 10 "    48,000" " "      2001     2015  만양로   00019           00000          
## # ... with 22 more variables: 도로명시군구코드 <chr>, 도로명일련번호코드 <chr>,
## #   도로명지상지하코드 <chr>, 도로명코드 <chr>, 법정동 <chr>,
## #   법정동본번코드 <chr>, 법정동부번코드 <chr>, 법정동시군구코드 <chr>,
## #   법정동읍면동코드 <chr>, 법정동지번코드 <chr>, 아파트 <chr>, 월 <chr>,
## #   일 <chr>, 일련번호 <chr>, 전용면적 <chr>, 중개사소재지 <chr>, 지번 <chr>,
## #   지역코드 <chr>, 층 <chr>, 해제사유발생일 <chr>, 해제여부 <chr>,
## #   response_id <chr>
## 
## [[2]]
## # A tibble: 10 x 29
##    거래금액     거래유형 건축년도 년    도로명   도로명건물본번~ 도로명건물부번~
##    <chr>        <chr>    <chr>    <chr> <chr>    <chr>           <chr>          
##  1 "    82,500" " "      2008     2015  사직로8~ 00004           00000          
##  2 "    60,000" " "      1981     2015  세종대~  00047           00000          
##  3 "   130,000" " "      2004     2015  경희궁2~ 00005           00005          
##  4 "   105,000" " "      2004     2015  사직로8~ 00024           00000          
##  5 "   120,000" " "      2003     2015  사직로8~ 00020           00000          
##  6 "    17,000" " "      2014     2015  대학로   00047           00000          
##  7 "    17,000" " "      2014     2015  대학로   00047           00000          
##  8 "    57,000" " "      2006     2015  혜화로3~ 00030           00000          
##  9 "    44,000" " "      1995     2015  창경궁로 00265           00000          
## 10 "    52,000" " "      1995     2015  창경궁로 00265           00000          
## # ... with 22 more variables: 도로명시군구코드 <chr>, 도로명일련번호코드 <chr>,
## #   도로명지상지하코드 <chr>, 도로명코드 <chr>, 법정동 <chr>,
## #   법정동본번코드 <chr>, 법정동부번코드 <chr>, 법정동시군구코드 <chr>,
## #   법정동읍면동코드 <chr>, 법정동지번코드 <chr>, 아파트 <chr>, 월 <chr>,
## #   일 <chr>, 일련번호 <chr>, 전용면적 <chr>, 중개사소재지 <chr>, 지번 <chr>,
## #   지역코드 <chr>, 층 <chr>, 해제사유발생일 <chr>, 해제여부 <chr>,
## #   response_id <chr>

이런 반복 작업에 대해 조금 더 알고 싶으신 분은 'R에서 깔끔하게 반복 작업 처리하기(feat. purrr)' 포스트가 도움이 될지 모릅니다.

 

아, 위에 쓴 코드에 bind_rows() 함수를 적용하면 아예 tibble 하나로 만들 수 있습니다.

mmap(files, ~xml_to_df(.x)) %>% 
  bind_rows()
## # A tibble: 20 x 29
##    거래금액     거래유형 건축년도 년    도로명   도로명건물본번~ 도로명건물부번~
##    <chr>        <chr>    <chr>    <chr> <chr>    <chr>           <chr>          
##  1 "    34,700" " "      2001     2015  만양로   00019           00000          
##  2 "    16,631" " "      2015     2015  노량진로 00140           00000          
##  3 "    15,334" " "      2015     2015  노량진로 00140           00000          
##  4 "    14,733" " "      2015     2015  노량진로 00140           00000          
##  5 "    47,000" " "      2001     2015  만양로   00019           00000          
##  6 "    33,400" " "      1997     2015  만양로8~ 00050           00000          
##  7 "    48,000" " "      2001     2015  만양로   00019           00000          
##  8 "    52,000" " "      1997     2015  만양로8~ 00050           00000          
##  9 "    42,000" " "      2001     2015  만양로   00084           00000          
## 10 "    48,000" " "      2001     2015  만양로   00019           00000          
## 11 "    82,500" " "      2008     2015  사직로8~ 00004           00000          
## 12 "    60,000" " "      1981     2015  세종대~  00047           00000          
## 13 "   130,000" " "      2004     2015  경희궁2~ 00005           00005          
## 14 "   105,000" " "      2004     2015  사직로8~ 00024           00000          
## 15 "   120,000" " "      2003     2015  사직로8~ 00020           00000          
## 16 "    17,000" " "      2014     2015  대학로   00047           00000          
## 17 "    17,000" " "      2014     2015  대학로   00047           00000          
## 18 "    57,000" " "      2006     2015  혜화로3~ 00030           00000          
## 19 "    44,000" " "      1995     2015  창경궁로 00265           00000          
## 20 "    52,000" " "      1995     2015  창경궁로 00265           00000          
## # ... with 22 more variables: 도로명시군구코드 <chr>, 도로명일련번호코드 <chr>,
## #   도로명지상지하코드 <chr>, 도로명코드 <chr>, 법정동 <chr>,
## #   법정동본번코드 <chr>, 법정동부번코드 <chr>, 법정동시군구코드 <chr>,
## #   법정동읍면동코드 <chr>, 법정동지번코드 <chr>, 아파트 <chr>, 월 <chr>,
## #   일 <chr>, 일련번호 <chr>, 전용면적 <chr>, 중개사소재지 <chr>, 지번 <chr>,
## #   지역코드 <chr>, 층 <chr>, 해제사유발생일 <chr>, 해제여부 <chr>,
## #   response_id <chr>

 

이번에는 파일을 내려받는 방식을 택했지만 인터넷 주소(URL) 역시 얼마든 같은 방법으로 처리할 수 있습니다.

 

또 XML 형식뿐 아니라 JSON(JavaScript Object Notation) 형식도 거의 똑같은 방법으로 tibble로 바꾸는 게 가능합니다.

 

처음에 파일을 불러올 때 xml2 패키지 대신 jsonlite 패키지를 쓴다는 차이가 있을 뿐입니다.

 

unnest_longer(), unneset_wider() 함수를 잘 조합하셔서 인터넷에서 만난 낯선 친구하고도 깔끔한 사이가 되시기를 기원합니다.

 

그럼 모두들 Happy Tidyversing -_-)/

 

댓글,

KindeR | 카테고리 다른 글 더 보기