2015-10-31 12 views
17

宿題の割り当て私はXMLファイルをRのデータフレームに変換しようとしています。私は多くの異なることを試みましたが、インターネット上でアイデアを検索しましたが、失敗しました。これまでのコードはこれまで通りです:R:XMLデータをデータフレームに変換する

library(XML) 
url <- 'http://www.ggobi.org/book/data/olive.xml' 
doc <- xmlParse(myUrl) 
root <- xmlRoot(doc) 

dataFrame <- xmlSApply(xmltop, function(x) xmlSApply(x, xmlValue)) 
data.frame(t(dataFrame),row.names=NULL) 

私が得られる出力は、数字の巨大なベクトルのようです。私は、データフレームにデータを整理しようとしていますが、私はそれを得るためにコードを適切に調整する方法がわかりません。

+3

Hadleyが一貫して非常に高品質で便利なRパッケージを作成しているため、xml2を使用します。それはおそらくもっとスムーズに進み、よりよく文書化されるでしょう。 readmeの下の相違点:https://github.com/hadley/xml2 –

+2

十分に公正です。 xml2は、GitHubのページによると私の簡単な経験は、より簡単なインターフェイスを持っています。 RはRのコア開発者によって書かれたもので、奇妙なものでいっぱいですが、Hadleyはしばしばシンプルさとパワーバランスのバランスを取って、すべてをきれいに整えます。 –

答えて

20

冗長XMLパッケージとしてしかしxml2がメモリリークを持っており、レーザー焦点を当てたデータ抽出にあるしないとして、それができない場合があります。私はtrimwsを使用しています。これはであり、最近ではRコアに加えてです。私はprbly今最後のビットをこのようにしてください

library(xml2) 

pg <- read_xml("http://www.ggobi.org/book/data/olive.xml") 

# get all the <record>s 
recs <- xml_find_all(pg, "//record") 

# extract and clean all the columns 
vals <- trimws(xml_text(recs)) 

# extract and clean (if needed) the area names 
labs <- trimws(xml_attr(recs, "label")) 

# mine the column names from the two variable descriptions 
# this XPath construct lets us grab either the <categ…> or <real…> tags 
# and then grabs the 'name' attribute of them 
cols <- xml_attr(xml_find_all(pg, "//data/variables/*[self::categoricalvariable or 
                 self::realvariable]"), "name") 

# this converts each set of <record> columns to a data frame 
# after first converting each row to numeric and assigning 
# names to each column (making it easier to do the matrix to data frame conv) 
dat <- do.call(rbind, lapply(strsplit(vals, "\ +"), 
           function(x) { 
            data.frame(rbind(setNames(as.numeric(x),cols))) 
           })) 

# then assign the area name column to the data frame 
dat$area_name <- labs 

head(dat) 
## region area palmitic palmitoleic stearic oleic linoleic linolenic 
## 1  1 1  1075   75  226 7823  672  NA 
## 2  1 1  1088   73  224 7709  781  31 
## 3  1 1  911   54  246 8113  549  31 
## 4  1 1  966   57  240 7952  619  50 
## 5  1 1  1051   67  259 7771  672  50 
## 6  1 1  911   49  268 7924  678  51 
## arachidic eicosenoic area_name 
## 1  60   29 North-Apulia 
## 2  61   29 North-Apulia 
## 3  63   29 North-Apulia 
## 4  78   35 North-Apulia 
## 5  80   46 North-Apulia 
## 6  70   44 North-Apulia 

UPDATE

:上記

library(tidyverse) 

strsplit(vals, "[[:space:]]+") %>% 
    map_df(~as_data_frame(as.list(setNames(., cols)))) %>% 
    mutate(area_name=labs) 
+0

tidyvereはタイプミスです。 as_data_frameもエクスポートされたオブジェクトではないようです。 – userJT

+1

typoは修正されましたが、実際には "非エクスポート"ステートメントを作成する前に 'tibble'と' dplyr'の両方をチェックしています。 – hrbrmstr

+0

@hrbrmstr本当に良い解決策のように見えます。私は 'xml'の完全なnoobですので、あなたのコードは他の' xml'ファイルに一般化されていますか?たとえば、すべてのXMLに 'record'がありますか? 'recs < - xml_find_all(pg、" // record ")' –

2

ここで私が思いついたのです。同じページにもあるolive oil csv fileと一致します。彼らはXを最初の列名として表示しますが、xmlには表示されませんので、手動で追加しました。

おそらく、セクションに分割してすべての部分を取得したら最終データフレームを組み立てるのが最善でしょう。また、XPathには[.XML*ショートカットを使用し、その他のアクセサー関数には[[を使用することもできます。

library(XML) 
url <- "http://www.ggobi.org/book/data/olive.xml" 

## parse the xml document and get the top-level XML node 
doc <- xmlParse(url) 
top <- xmlRoot(doc) 

## create the data frame 
df <- cbind(
    ## get all the labels for the first column (groups) 
    X = unlist(doc["//record//@label"], use.names = FALSE), 
    read.table(
     ## get all the records as a character vector 
     text = xmlValue(top[["data"]][["records"]]), 
     ## get the column names from 'variables' 
     col.names = xmlSApply(top[["data"]][["variables"]], xmlGetAttr, "name"), 
     ## assign the NA values to 'na' in the records 
     na.strings = "na" 
    ) 
) 

## result 
head(df) 
#    X region area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic 
# 1 North-Apulia  1 1  1075   75  226 7823  672  NA  60   29 
# 2 North-Apulia  1 1  1088   73  224 7709  781  31  61   29 
# 3 North-Apulia  1 1  911   54  246 8113  549  31  63   29 
# 4 North-Apulia  1 1  966   57  240 7952  619  50  78   35 
# 5 North-Apulia  1 1  1051   67  259 7771  672  50  80   46 
# 6 North-Apulia  1 1  911   49  268 7924  678  51  70   44 

## clean up 
free(doc); rm(doc, top); gc() 
4

グレート答え!将来の読者にとって、Rインポートを必要とする複雑なXMLに直面するときはいつでも、XSLT(XMLコンテンツを様々な最終用途のニーズに操作する特殊目的の宣言型プログラミング言語)を使用してXML文書を再構成することを検討してください。それから、単にXMLパッケージのRのxmlToDataFrame()関数を使用します。

Rには、すべてのオペレーティングシステムでCRAN-Rで使用できる専用のXSLTパッケージがありません。記載されているSXLTはLinuxパッケージのようですが、Windowsでは使用できません。未回答の質問herehereを参照してください。私は@hrbrmstr(上記)がGitHub XSLT projectを維持していることを理解しています。それにもかかわらず、ほぼすべての汎用言語は、Java、C#、Python、PHP、Perl、およびVBなどのXSLTプロセッサを維持しています。

以下はオープンソースのPythonのルートです.XSLドキュメントはかなり微妙なので、2つのXSLTが使用されています(もちろん、XSLTのガウスはそれらを1つに組み合わせることができますが、(recursive templateを使用して)

FIRST XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<!-- Identity Transform -->  
<xsl:template match="node()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="record/text()" name="tokenize">   
    <xsl:param name="text" select="."/> 
    <xsl:param name="separator" select="' '"/> 
    <xsl:choose>    
     <xsl:when test="not(contains($text, $separator))">     
      <data> 
       <xsl:value-of select="normalize-space($text)"/> 
      </data>    
     </xsl:when> 
     <xsl:otherwise> 
      <data>     
       <xsl:value-of select="normalize-space(substring-before($text, $separator))"/>     
      </data>     
      <xsl:call-template name="tokenize"> 
       <xsl:with-param name="text" select="substring-after($text, $separator)"/> 
      </xsl:call-template>     
     </xsl:otherwise>    
    </xsl:choose>   
</xsl:template>  

<xsl:template match="description|variables|categoricalvariable|realvariable">   
</xsl:template> 

SECOND XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <!-- Identity Transform -->  
    <xsl:template match="records"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="record"> 
     <record> 
      <area_name><xsl:value-of select="@label"/></area_name> 
      <area><xsl:value-of select="data[1]"/></area> 
      <region><xsl:value-of select="data[2]"/></region> 
      <palmitic><xsl:value-of select="data[3]"/></palmitic> 
      <palmitoleic><xsl:value-of select="data[4]"/></palmitoleic> 
      <stearic><xsl:value-of select="data[5]"/></stearic> 
      <oleic><xsl:value-of select="data[6]"/></oleic> 
      <linoleic><xsl:value-of select="data[7]"/></linoleic> 
      <linolenic><xsl:value-of select="data[8]"/></linolenic> 
      <arachidic><xsl:value-of select="data[9]"/></arachidic> 
      <eicosenoic><xsl:value-of select="data[10]"/></eicosenoic> 
     </record> 
    </xsl:template>   

</xsl:stylesheet> 

(lxmlのモジュールを使用して)Pythonの

余分なスペースは、XML文書に "NA" の後に追加されたとして
import lxml.etree as ET 

cd = os.path.dirname(os.path.abspath(__file__)) 

# FIRST TRANSFORMATION 
dom = ET.parse('http://www.ggobi.org/book/data/olive.xml') 
xslt = ET.parse(os.path.join(cd, 'Olive.xsl')) 
transform = ET.XSLT(xslt) 
newdom = transform(dom) 

tree_out = ET.tostring(newdom, encoding='UTF-8', pretty_print=True, xml_declaration=True) 

xmlfile = open(os.path.join(cd, 'Olive_py.xml'),'wb') 
xmlfile.write(tree_out) 
xmlfile.close()  

# SECOND TRANSFORMATION 
dom = ET.parse(os.path.join(cd, 'Olive_py.xml')) 
xslt = ET.parse(os.path.join(cd, 'Olive2.xsl')) 
transform = ET.XSLT(xslt) 
newdom = transform(dom) 

tree_out = ET.tostring(newdom, encoding='UTF-8', pretty_print=True, xml_declaration=True)  

xmlfile = open(os.path.join(cd, 'Olive_py.xml'),'wb') 
xmlfile.write(tree_out) 
xmlfile.close() 

R

library(XML) 

# LOADING TRANSFORMED XML INTO R DATA FRAME 
doc<-xmlParse("Olive_py.xml") 
xmldf <- xmlToDataFrame(nodes = getNodeSet(doc, "//record")) 
View(xmldf) 

出力

area_name area region palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic 
North-Apulia 1  1  1075  75   226  7823  672   na      60 
North-Apulia 1  1  1088  73   224  7709  781   31   61   29 
North-Apulia 1  1  911   54   246  8113  549   31   63   29 
North-Apulia 1  1  966   57   240  7952  619   50   78   35 
North-Apulia 1  1  1051  67   259  7771  672   50   80   46 
    ... 

は、(非常に最初のレコードに若干のクリーンアップは、必要とされていますしたがって、arachidicおよびeicosenoicは、前方にシフトされました)

+0

今度は 'xslt'パッケージがあります。私は自分でそれを試していないが、それがクロスプラットフォームではないという兆候はない。 https://cran.r-project.org/web/packages/xslt/index.html。 (https://github.com/hrbrmstr/xsltとは無関係) –

+0

@Aurèle - 最後に!メモをありがとうございます。このxml2拡張モジュールをテストします。 – Parfait

関連する問題