XML
파일 뷰어1XML
파일을 데이터프레임으로 변환시키는 첫 번째 단계는 아마도 XML
파일 구조를 파악하는 것부터 시작된다. htmltidy
팩키지에 XML 파일을 살펴볼 수 있는 함수가 다음과 같이 제공된다.
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)
read_xml("http://forecast.weather.gov/MapClick.php?lat=29.803&lon=-82.411&FcstType=digitalDWML")
xml_dat <-
# xml_view(xml_dat)
# xml_tree_view(xml_dat)
xml_children(xml_dat)
xml_node <-
xml_find_all(xml_node, ".//point")
xml_lnglat <-
map(xml_lnglat, xml_attrs) %>%
geo_df <- map_df(~as.list(.))
geo_df
# A tibble: 1 x 2
latitude longitude
<chr> <chr>
1 29.81 -82.42
위경도를 추출하고자 할 때, xml_find_all()
함수로 point
노드만 추출한다. 그리고 나서 노드를 구성하는 attribute를 xml_attr()
함수로 추출한다.
xml_dat %>% xml_find_all("//point")
point <-
%>% xml_attr("latitude") %>% as.numeric() point
[1] 29.81
%>% xml_attr("longitude") %>% as.numeric() point
[1] -82.42
측정위치를 추출한다. 노드 명칭을 알기 때문에 이것을 이용하여 노드를 추출하고 나서 xml_text()
로 변환시켜 텍스트를 끌어낸다.
%>% xml_find_all("//area-description") %>% xml_text() xml_dat
[1] "3 Miles S La Crosse FL"
xml_view(xml_dat)
를 통해 노드 명칭을 확인했기 때문에 이를 이용하여 시작시점에 대한 값을 추출한다. 그외 온도, 습도, 풍향에 대한 정보를 추출하여 이를 결합시켜 데이터프레임으로 제작한다.
# 측정시작시간
xml_dat %>%
start_time <- xml_find_all("//start-valid-time") %>%
xml_text()
# 측정종료시간
xml_dat %>%
end_time <- xml_find_all("//end-valid-time") %>%
xml_text()
# 온도
xml_dat %>%
temperature <- xml_find_all("//temperature[@type='hourly']/value") %>%
xml_text() %>%
as.integer()
# 풍속
xml_dat %>%
wind <- xml_find_all("//wind-speed[@type='sustained']/value") %>%
xml_text() %>%
as.integer()
# 습도
xml_dat %>%
humidity <- xml_find_all("//humidity[@type='relative']/value") %>%
xml_text() %>%
as.integer()
tibble(start_time = start_time,
weather_df <-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
https://www.w3schools.com/ 웹사이트에 있는 XML 예제를 가지고 아침메뉴 식단을 데이터프레임으로 변환시킨다.
xml
파일을 불러 읽어들이고 xml_view()
함수로 데이터 변환 전략을 머리에 담아 둔다.
read_xml("https://www.w3schools.com/xml/simple.xml")
food_xml <-
xml_view(food_xml)
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"
xml_find_all(food_xml, ".//food")
food_node <-
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 ...
Stackoverflow, “Parsing large XML to dataframe in R” 을 참고하여 먼저 데이터프레임을 만든다. 그리고 나서 각 행별로 노드값을 추출하여 xml_text()
함수로 추출한 결과를 채워넣고 데이터프레임의 변수명을 넣어 완성한다.
# 데이터프레임 생성
length(food_node)
obs_length <- length(xml_children(food_node[[1]]))
var_length <-
data.frame(matrix(NA, nrow=obs_length, ncol=var_length))
food_df <-
# 데이터프레임에 행순으로 차고차곡 채워넣기
for (j in 1:obs_length) {
xml_text(xml_children(food_node[[j]]), trim=TRUE)
food_df[j, ] <-
}
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