1 XML 파일 뷰어1

XML 파일을 데이터프레임으로 변환시키는 첫 번째 단계는 아마도 XML 파일 구조를 파악하는 것부터 시작된다. htmltidy 팩키지에 XML 파일을 살펴볼 수 있는 함수가 다음과 같이 제공된다.

  • highlight_styles: List available HTML/XML highlight styles
  • renderXmlview: Widget render function for use in Shiny
  • tidy_html.response: Tidy or “Pretty Print” HTML/XHTML Documents
  • xml_tree_view: HTML/XML tree viewer
  • xml_view: HTML/XML pretty printer and viewer
  • xmltreeview-shiny: Shiny bindings for xmltreeview
  • xmlviewOutput: Widget output function for use in Shiny

Stackoverflow, “How to parse XML to R data frame”에 나온 예제를 바탕으로 XML파일에서 위도와 경도를 추출하는 사례를 살펴보자. xml2 팩키지의 read_xml() 함수를 사용해서 XML 파일을 R로 불러읽어들인다. 그리고 나서, htmltidy 팩키지 xml_view() 혹은 xml_tree_view() 함수를 사용해서 시각적으로 XML 파일 구조를 파악한다. 그리고 나서 위경도 정보가 담긴 노드를 찾아 추출한다.

xml_find_all() 함수로 위경도 정보가 담긴 노드를 찾아낸다. 그리고 나서 xml_attrs를 추출하게 되면 리스트 객체로 뽑히게 되고 이를 데이터프레임으로 변환시킨다.

library(tidyverse)
library(htmltidy)
library(xml2)

xml_dat <- read_xml("http://forecast.weather.gov/MapClick.php?lat=29.803&lon=-82.411&FcstType=digitalDWML")

# xml_view(xml_dat)
# xml_tree_view(xml_dat)

xml_node <- xml_children(xml_dat)

xml_lnglat <- xml_find_all(xml_node, ".//point") 

geo_df <- map(xml_lnglat, xml_attrs) %>% 
  map_df(~as.list(.))

geo_df
# A tibble: 1 x 2
  latitude longitude
  <chr>    <chr>    
1 29.81    -82.42   

1.1 위경도 추출

위경도를 추출하고자 할 때, xml_find_all() 함수로 point 노드만 추출한다. 그리고 나서 노드를 구성하는 attribute를 xml_attr() 함수로 추출한다.

point <- xml_dat %>% xml_find_all("//point")

point %>% xml_attr("latitude") %>% as.numeric()
[1] 29.81
point %>% xml_attr("longitude") %>% as.numeric()
[1] -82.42

1.2 측정위치

측정위치를 추출한다. 노드 명칭을 알기 때문에 이것을 이용하여 노드를 추출하고 나서 xml_text()로 변환시켜 텍스트를 끌어낸다.

xml_dat %>% xml_find_all("//area-description") %>% xml_text()
[1] "3 Miles S La Crosse FL"

1.3 관측시간

xml_view(xml_dat)를 통해 노드 명칭을 확인했기 때문에 이를 이용하여 시작시점에 대한 값을 추출한다. 그외 온도, 습도, 풍향에 대한 정보를 추출하여 이를 결합시켜 데이터프레임으로 제작한다.

# 측정시작시간
start_time <- xml_dat %>% 
  xml_find_all("//start-valid-time") %>% 
  xml_text()

# 측정종료시간
end_time <- xml_dat %>% 
  xml_find_all("//end-valid-time") %>% 
  xml_text()

# 온도
temperature <- xml_dat %>% 
  xml_find_all("//temperature[@type='hourly']/value") %>% 
  xml_text() %>% 
  as.integer()

# 풍속
wind <- xml_dat %>% 
  xml_find_all("//wind-speed[@type='sustained']/value") %>% 
  xml_text() %>% 
  as.integer()

# 습도
humidity <- xml_dat %>% 
  xml_find_all("//humidity[@type='relative']/value") %>% 
  xml_text() %>% 
  as.integer()

weather_df <- tibble(start_time = start_time,
       temperature = temperature,
       wind = wind,
       humidity = humidity)

weather_df
# A tibble: 168 x 4
   start_time                temperature  wind humidity
   <chr>                           <int> <int>    <int>
 1 2020-11-21T04:00:00-05:00          61     6       97
 2 2020-11-21T05:00:00-05:00          62     6       93
 3 2020-11-21T06:00:00-05:00          62     6       94
 4 2020-11-21T07:00:00-05:00          62     6       95
 5 2020-11-21T08:00:00-05:00          62     7       97
 6 2020-11-21T09:00:00-05:00          66     7       90
 7 2020-11-21T10:00:00-05:00          70     8       81
 8 2020-11-21T11:00:00-05:00          74     8       71
 9 2020-11-21T12:00:00-05:00          75     9       71
10 2020-11-21T13:00:00-05:00          76     9       69
# ... with 158 more rows

2 XML 아침메뉴23

https://www.w3schools.com/ 웹사이트에 있는 XML 예제를 가지고 아침메뉴 식단을 데이터프레임으로 변환시킨다.

2.1 XML 구조 파악

xml 파일을 불러 읽어들이고 xml_view() 함수로 데이터 변환 전략을 머리에 담아 둔다.

food_xml <- read_xml("https://www.w3schools.com/xml/simple.xml")

xml_view(food_xml)

2.2 노드 위치 식별

xml_children() 함수로 자식노드를 확인한다. xml_path() 동사를 통해 위치를 파악한다. xml_find_all() 함수 xpath 와 결합시켜 추출할 노드를 식별한다.

xml_children(food_xml)
{xml_nodeset (5)}
[1] <food>\n  <name>Belgian Waffles</name>\n  <price>$5.95</price>\n  <descri ...
[2] <food>\n  <name>Strawberry Belgian Waffles</name>\n  <price>$7.95</price> ...
[3] <food>\n  <name>Berry-Berry Belgian Waffles</name>\n  <price>$8.95</price ...
[4] <food>\n  <name>French Toast</name>\n  <price>$4.50</price>\n  <descripti ...
[5] <food>\n  <name>Homestyle Breakfast</name>\n  <price>$6.95</price>\n  <de ...
xml_path(food_xml)
[1] "/breakfast_menu"
food_node <- xml_find_all(food_xml, ".//food")

xml_path(food_node)
[1] "/breakfast_menu/food[1]" "/breakfast_menu/food[2]"
[3] "/breakfast_menu/food[3]" "/breakfast_menu/food[4]"
[5] "/breakfast_menu/food[5]"
xml_find_all(food_node, "/breakfast_menu/food[*]")
{xml_nodeset (5)}
[1] <food>\n  <name>Belgian Waffles</name>\n  <price>$5.95</price>\n  <descri ...
[2] <food>\n  <name>Strawberry Belgian Waffles</name>\n  <price>$7.95</price> ...
[3] <food>\n  <name>Berry-Berry Belgian Waffles</name>\n  <price>$8.95</price ...
[4] <food>\n  <name>French Toast</name>\n  <price>$4.50</price>\n  <descripti ...
[5] <food>\n  <name>Homestyle Breakfast</name>\n  <price>$6.95</price>\n  <de ...

2.3 노드 → 데이터프레임

Stackoverflow, “Parsing large XML to dataframe in R” 을 참고하여 먼저 데이터프레임을 만든다. 그리고 나서 각 행별로 노드값을 추출하여 xml_text() 함수로 추출한 결과를 채워넣고 데이터프레임의 변수명을 넣어 완성한다.

# 데이터프레임 생성
obs_length <- length(food_node)
var_length <- length(xml_children(food_node[[1]]))

food_df <- data.frame(matrix(NA, nrow=obs_length, ncol=var_length))

# 데이터프레임에 행순으로 차고차곡 채워넣기
for (j in 1:obs_length) {
  food_df[j, ] <- xml_text(xml_children(food_node[[j]]), trim=TRUE)
}

food_df <- food_df %>% 
  set_names(names(as_list(food_node[[1]])))

food_df
                         name price
1             Belgian Waffles $5.95
2  Strawberry Belgian Waffles $7.95
3 Berry-Berry Belgian Waffles $8.95
4                French Toast $4.50
5         Homestyle Breakfast $6.95
                                                                          description
1                   Two of our famous Belgian Waffles with plenty of real maple syrup
2                   Light Belgian waffles covered with strawberries and whipped cream
3 Light Belgian waffles covered with an assortment of fresh berries and whipped cream
4                                 Thick slices made from our homemade sourdough bread
5                 Two eggs, bacon or sausage, toast, and our ever-popular hash browns
  calories
1      650
2      900
3      900
4      600
5      950
 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com