From c29a600d4689b7bfa84899fcdf1cf32c676d09d2 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 21 Nov 2014 09:48:59 -0800 Subject: [PATCH 01/85] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 172a7ec67..7dfe6293b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Learning about the model: [Introduction to Boosted Trees](http://homes.cs.washin What's New ===== - +* XGBoost wins [HEP meets ML Award in Higgs Boson Challenge](http://atlas.ch/news/2014/machine-learning-wins-the-higgs-challenge.html) * Thanks to Bing Xu, [XGBoost.jl](https://github.com/antinucleon/XGBoost.jl) allows you to use xgboost from Julia * See the updated [demo folder](demo) for feature walkthrough * Thanks to Tong He, the new [R package](R-package) is available From c356a0acc2489c644b67ddefc366fb10818ee7f1 Mon Sep 17 00:00:00 2001 From: Ted Fujimoto Date: Tue, 25 Nov 2014 21:27:50 -0500 Subject: [PATCH 02/85] Remove tools folder --- tools/Makefile | 25 ---- tools/xgcombine_buffer.cpp | 247 ------------------------------------- 2 files changed, 272 deletions(-) delete mode 100644 tools/Makefile delete mode 100644 tools/xgcombine_buffer.cpp diff --git a/tools/Makefile b/tools/Makefile deleted file mode 100644 index b35277aff..000000000 --- a/tools/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -export CC = gcc -export CXX = g++ -export CFLAGS = -Wall -O3 -msse2 -Wno-unknown-pragmas -fopenmp - -# specify tensor path -BIN = xgcombine_buffer -OBJ = -.PHONY: clean all - -all: $(BIN) $(OBJ) -export LDFLAGS= -pthread -lm - -xgcombine_buffer : xgcombine_buffer.cpp - -$(BIN) : - $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $(filter %.cpp %.o %.c, $^) - -$(OBJ) : - $(CXX) -c $(CFLAGS) -o $@ $(firstword $(filter %.cpp %.c, $^) ) - -install: - cp -f -r $(BIN) $(INSTALL_PATH) - -clean: - $(RM) $(OBJ) $(BIN) *~ diff --git a/tools/xgcombine_buffer.cpp b/tools/xgcombine_buffer.cpp deleted file mode 100644 index 84bc996a4..000000000 --- a/tools/xgcombine_buffer.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/*! - * a tool to combine different set of features into binary buffer - * not well organized code, but does it's job - * \author Tianqi Chen: tianqi.tchen@gmail.com - */ -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "../src/io/simple_dmatrix-inl.hpp" -#include "../src/utils/utils.h" - -using namespace xgboost; -using namespace xgboost::io; - -// header in dataset -struct Header{ - FILE *fi; - int tmp_num; - int base; - int num_feat; - // whether it's dense format - bool is_dense; - bool warned; - - Header( void ){ this->warned = false; this->is_dense = false; } - - inline void CheckBase( unsigned findex ){ - if( findex >= (unsigned)num_feat && ! warned ) { - fprintf( stderr, "warning:some feature exceed bound, num_feat=%d\n", num_feat ); - warned = true; - } - } -}; - - -inline int norm( std::vector
&vec, int base = 0 ){ - int n = base; - for( size_t i = 0; i < vec.size(); i ++ ){ - if( vec[i].is_dense ) vec[i].num_feat = 1; - vec[i].base = n; n += vec[i].num_feat; - } - return n; -} - -inline void vclose( std::vector
&vec ){ - for( size_t i = 0; i < vec.size(); i ++ ){ - fclose( vec[i].fi ); - } -} - -inline int readnum( std::vector
&vec ){ - int n = 0; - for( size_t i = 0; i < vec.size(); i ++ ){ - if( !vec[i].is_dense ){ - utils::Assert( fscanf( vec[i].fi, "%d", &vec[i].tmp_num ) == 1, "load num" ); - n += vec[i].tmp_num; - }else{ - n ++; - } - } - return n; -} - -inline void vskip( std::vector
&vec ){ - for( size_t i = 0; i < vec.size(); i ++ ){ - if( !vec[i].is_dense ){ - utils::Assert( fscanf( vec[i].fi, "%*d%*[^\n]\n" ) >= 0, "sparse" ); - }else{ - utils::Assert( fscanf( vec[i].fi, "%*f\n" ) >= 0, "dense" ); - } - } -} - -class DataLoader: public DMatrixSimple { - public: - // whether to do node and edge feature renormalization - int rescale; - int linelimit; - public: - FILE *fp, *fwlist, *fgroup, *fweight; - std::vector
fheader; - DataLoader( void ){ - rescale = 0; - linelimit = -1; - fp = NULL; fwlist = NULL; fgroup = NULL; fweight = NULL; - } - private: - inline void Load( std::vector &feats, std::vector
&vec ){ - SparseBatch::Entry e; - for( size_t i = 0; i < vec.size(); i ++ ){ - if( !vec[i].is_dense ) { - for( int j = 0; j < vec[i].tmp_num; j ++ ){ - utils::Assert( fscanf ( vec[i].fi, "%u:%f", &e.findex, &e.fvalue ) == 2, "Error when load feat" ); - vec[i].CheckBase( e.findex ); - e.findex += vec[i].base; - feats.push_back(e); - } - }else{ - utils::Assert( fscanf ( vec[i].fi, "%f", &e.fvalue ) == 1, "load feat" ); - e.findex = vec[i].base; - feats.push_back(e); - } - } - } - inline void DoRescale( std::vector &vec ){ - double sum = 0.0; - for( size_t i = 0; i < vec.size(); i ++ ){ - sum += vec[i].fvalue * vec[i].fvalue; - } - sum = sqrt( sum ); - for( size_t i = 0; i < vec.size(); i ++ ){ - vec[i].fvalue /= sum; - } - } - public: - // basically we are loading all the data inside - inline void Load( void ){ - this->Clear(); - float label, weight = 0.0f; - - unsigned ngleft = 0, ngacc = 0; - if( fgroup != NULL ){ - info.group_ptr.clear(); - info.group_ptr.push_back(0); - } - - while( fscanf( fp, "%f", &label ) == 1 ){ - if( ngleft == 0 && fgroup != NULL ){ - utils::Assert( fscanf( fgroup, "%u", &ngleft ) == 1, "group" ); - } - if( fweight != NULL ){ - utils::Assert( fscanf( fweight, "%f", &weight ) == 1, "weight" ); - } - - ngleft -= 1; ngacc += 1; - - int pass = 1; - if( fwlist != NULL ){ - utils::Assert( fscanf( fwlist, "%u", &pass ) ==1, "pass" ); - } - if( pass == 0 ){ - vskip( fheader ); ngacc -= 1; - }else{ - const int nfeat = readnum( fheader ); - - std::vector feats; - - // pairs - this->Load( feats, fheader ); - utils::Assert( feats.size() == (unsigned)nfeat, "nfeat" ); - if( rescale != 0 ) this->DoRescale( feats ); - // push back data :) - this->info.labels.push_back( label ); - // push back weight if any - if( fweight != NULL ){ - this->info.weights.push_back( weight ); - } - this->AddRow( feats ); - } - if( ngleft == 0 && fgroup != NULL && ngacc != 0 ){ - info.group_ptr.push_back( info.group_ptr.back() + ngacc ); - utils::Assert( info.group_ptr.back() == info.num_row, "group size must match num rows" ); - ngacc = 0; - } - // linelimit - if( linelimit >= 0 ) { - if( -- linelimit <= 0 ) break; - } - } - if( ngleft == 0 && fgroup != NULL && ngacc != 0 ){ - info.group_ptr.push_back( info.group_ptr.back() + ngacc ); - utils::Assert( info.group_ptr.back() == info.num_row, "group size must match num rows" ); - } - } - -}; - -const char *folder = "features"; - -int main( int argc, char *argv[] ){ - if( argc < 3 ){ - printf("Usage:xgcombine_buffer [options] -f [features] -fd [densefeatures]\n" \ - "options: -rescale -linelimit -fgroup -wlist \n"); - return 0; - } - - DataLoader loader; - time_t start = time( NULL ); - - int mode = 0; - for( int i = 3; i < argc; i ++ ){ - if( !strcmp( argv[i], "-f") ){ - mode = 0; continue; - } - if( !strcmp( argv[i], "-fd") ){ - mode = 2; continue; - } - if( !strcmp( argv[i], "-rescale") ){ - loader.rescale = 1; continue; - } - if( !strcmp( argv[i], "-wlist") ){ - loader.fwlist = utils::FopenCheck( argv[ ++i ], "r" ); continue; - } - if( !strcmp( argv[i], "-fgroup") ){ - loader.fgroup = utils::FopenCheck( argv[ ++i ], "r" ); continue; - } - if( !strcmp( argv[i], "-fweight") ){ - loader.fweight = utils::FopenCheck( argv[ ++i ], "r" ); continue; - } - if( !strcmp( argv[i], "-linelimit") ){ - loader.linelimit = atoi( argv[ ++i ] ); continue; - } - - char name[ 256 ]; - sprintf( name, "%s/%s.%s", folder, argv[1], argv[i] ); - Header h; - h.fi = utils::FopenCheck( name, "r" ); - - if( mode == 2 ){ - h.is_dense = true; h.num_feat = 1; - loader.fheader.push_back( h ); - }else{ - utils::Assert( fscanf( h.fi, "%d", &h.num_feat ) == 1, "num feat" ); - switch( mode ){ - case 0: loader.fheader.push_back( h ); break; - default: ; - } - } - } - loader.fp = utils::FopenCheck( argv[1], "r" ); - - printf("num_features=%d\n", norm( loader.fheader ) ); - printf("start creating buffer...\n"); - loader.Load(); - loader.SaveBinary( argv[2] ); - // close files - fclose( loader.fp ); - if( loader.fwlist != NULL ) fclose( loader.fwlist ); - if( loader.fgroup != NULL ) fclose( loader.fgroup ); - vclose( loader.fheader ); - printf("all generation end, %lu sec used\n", (unsigned long)(time(NULL) - start) ); - return 0; -} From 198489438ffdb3d95b45af1268f412c59d0c1b14 Mon Sep 17 00:00:00 2001 From: Ted Fujimoto Date: Tue, 25 Nov 2014 21:42:13 -0500 Subject: [PATCH 03/85] Added OS X OpenMP instructions --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 7dfe6293b..46ccc9f94 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,18 @@ Build - Alternatively, you can upgrade your compiler to compile multi-thread version * Windows(VS 2010): see [windows](windows) folder - In principle, you put all the cpp files in the Makefile to the project, and build +* OS X: + - For users who want OpenMP support using [Homebrew](http://brew.sh/), run ```brew update``` (ensures that you install gcc-4.9 or above) and ```brew install gcc```. Once it is installed, edit [Makefile](Makefile/) and [tools/Makefile](tools/Makefile) by replacing: + ``` + export CC = gcc + export CXX = g++ + ``` + with + ``` + export CC = gcc-4.9 + export CXX = g++-4.9 + ``` + Then run ```bash build.sh``` normally. Version ====== From baf41d589d2d08a0710e36484bba8990ad6f0158 Mon Sep 17 00:00:00 2001 From: Ted Fujimoto Date: Tue, 25 Nov 2014 22:17:36 -0500 Subject: [PATCH 04/85] Fixed README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46ccc9f94..940cf3d43 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Build * Windows(VS 2010): see [windows](windows) folder - In principle, you put all the cpp files in the Makefile to the project, and build * OS X: - - For users who want OpenMP support using [Homebrew](http://brew.sh/), run ```brew update``` (ensures that you install gcc-4.9 or above) and ```brew install gcc```. Once it is installed, edit [Makefile](Makefile/) and [tools/Makefile](tools/Makefile) by replacing: + - For users who want OpenMP support using [Homebrew](http://brew.sh/), run ```brew update``` (ensures that you install gcc-4.9 or above) and ```brew install gcc```. Once it is installed, edit [Makefile](Makefile/) by replacing: ``` export CC = gcc export CXX = g++ From a50fd27fd338a86fe4e7f730c519f03f1fb39e95 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 12 Dec 2014 05:46:32 -0800 Subject: [PATCH 05/85] Update README.md --- demo/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/README.md b/demo/README.md index bcc356712..2ae9c4dcd 100644 --- a/demo/README.md +++ b/demo/README.md @@ -43,3 +43,4 @@ Basic Examples by Tasks Benchmarks ==== * [Starter script for Kaggle Higgs Boson](kaggle-higgs) +* [Kaggle Tradeshift winning solution](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) by daxiongshu From 646f33d01d4dd656518939326a71217b875b5966 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 12 Dec 2014 05:47:00 -0800 Subject: [PATCH 06/85] Update README.md --- demo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/README.md b/demo/README.md index 2ae9c4dcd..1c6797304 100644 --- a/demo/README.md +++ b/demo/README.md @@ -43,4 +43,4 @@ Basic Examples by Tasks Benchmarks ==== * [Starter script for Kaggle Higgs Boson](kaggle-higgs) -* [Kaggle Tradeshift winning solution](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) by daxiongshu +* [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) From 4369a57270888a4bf4c93d315301135f49ec1c89 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 09:56:55 +0100 Subject: [PATCH 07/85] fix data labels --- R-package/data/agaricus.test.rda | Bin 14684 -> 37713 bytes R-package/data/agaricus.train.rda | Bin 57360 -> 216514 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/R-package/data/agaricus.test.rda b/R-package/data/agaricus.test.rda index 198016d940efdb9aaf3338848869f7c774791b8a..bffe6de21914ccb338731ecb208a76d7e7d581bb 100644 GIT binary patch literal 37713 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 literal 14684 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 diff --git a/R-package/data/agaricus.train.rda b/R-package/data/agaricus.train.rda index 4d56f1a34796a2d38507dbc001a7bfd210da2038..c471d01733ff83f8323147da46a867629c22b938 100644 GIT binary patch literal 216514 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%0Af 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
$z7WnmS54T&N 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 z z^TP;olE!_F@53&!_z+^|4d9xg=&hX6mge$(RYa((*KFJb^y`_7+LTHl)o(46jS^qN zo~vnAi>_8XG8pgzYxwoX*4(V=vlUbi@%{HwP5V+a+X~9)a zt6r^p8vx|=c+jJ?qoMP`VBcf>d}G8cc*VDnTG$9OeqUU);NpnOFglqYygE6d^h4|o z?b)4_(oDuvc}Y0K5ja3s4;9vXYIWVsXt;+iOW&cPNE_)-v^Q~;&_d=B~@MO&kj zO#4J&7Ddy8F2YMU!!e0i{Nn3EYlVFsD&f&YQv>(_&KGUI0REM8t1Foj?08(4i|hFW z`#(Yrb^?<~UXxQAdJlksb}R{z@sCz9mNfh7D88hW%d}#Go8LVLc!e4go7zOnEay>R zBM#^HKoRJP?%dTIDH^Qu3kVe3e$63V;tHGF5;)hi|%3G!zE%LB5YTd zU&FA(=s^D~Wj1p+2Rns}2zDbf`=Ljw{nMnOLo}==7~+lQ-)pHP$<(>C{H+Iz&bihy zmAKT9va^GUZ`0Ar|H#MHku^7YW)^o#HiD`e@XGCn?Y*?R5;&1n;tu-+7CNsAADrMB ziIt1|rl{D608z{L{o#jOovO~UCX?#6c~wubSTW{SqZ<=9q-!l@h4fA+JW2~wUFs)j zsdjF*fdf}nW|mjs-{+E(kX8riZUbS6zriJRwmeQ8@082#r=g+U{(a}cvl6x#cUBMq zO;2Z;uXg*zlZ2oxQ){2o$269=+x=qN6H?V2>@})i0As68yYHkv#FlX*teND@$~D1_ z5!c3GCcqrqg-UbelNPVX&_ZzpY^&t;g3oQMp~8&pG58NaZPjQuHgbkF4)KFZZ@)IX zfzY3tG%}{aTjNj{xyWlxy$7HDMcZ zp{-g*dp(rL%%^0C@AvB`6@syL?Yx{Wup5E;A;ir6`}W@nhS$)f#v`>Z1qzy+4Kh*o zQsM~#x~P5%%;)v4hPN1H=p>Vvu#=w`C6Jv+ZS%j53|Sr>3rk;5e3Gv7RY)j&sIQlf7TN50PBsV24mE72>Khp+~@hMJ%kdx z65ZLKb4Uw7jSgdA>zPLS{@t?R<$wFLP1=||+h*|$^^D8-`OMfFb;H^S!W4L7+PL_M zqdK>tSr*qiH^+QaiN{70S;WXZWjbHEB*Eu?xWDI8$j;+CqK)|#*XBiv)%9bC#w{f6 zG8s9fph>O8wktsUwMa%7Mq0R{ZMQJ0P<^MsX+wEy{^i|QXLGgTzGybL$UZywLNt!M zccaz=3%G`TS~+=hEh|78jhSzrhfuv^Z;ffvaAHVkem*<=*@feboi@>g9sP<0&G*zC z>bV}Ip>u1jqjVA&d$)FCmr}Be5BN!3E(6CH}P8bAd-r7mUhykq*D zJ|8J^_xo3bIL4A3bRxjm8GSi|L!}j}G^wsu_&XC&^p9N?mx$YHQNYzKcpk=_y@5a^ z11j$NJxH-3+rDk1fJg+)W~lfSCcO&A7A_yiq!zD_mx)%z!0Zp$+dU>W6I}BwTWx(} zf}GlW+WB{{;ktkvxYvRg<<+anolds|J4Y^scCr@N1=AQXpyv|9w#C^A*nPL0cDGa% zD^=o!^MB8p^<+9wx%EXXy*795M-$|6_vO1nwpfdeZjiTV=NNEsvs$lQ{9xceAUbh( zTp}m&GP0ERCHLmG>SeC*dZ})Gx!aa02cqQWKd|!VRB^|ItG@xq5MyXnh2f)K>EYR= zJ7`i`=%Xa$)4+WWfDl&tHSJ$KR8{{yt~{h&5^!^$^w>p0o8f-uC*_r#zBzJ1!* zgKKiHQ(?-9t~;z&G~-x1+AaMX9!O9r^f8{xjTzIz)tdUViY+y6lxF-b%)yN5eUtZW zm6wrf+5rjaGdN58Ivh_SaNapQmn-Yoz`F7KGtR3j8gL1g2Eh&f>0&vE(F~G2Q_SCz zpLi{ggxIlLS+&TMRvrk*(5ZoP0BraftlBXl8)&ES+~Ya4Eo?mWNuC6tJ&M^bhFh{S zt*#_f|8J6)cxhPjDFqP7f1;yXEWBQFSHKpJDB-mYn?=`pR#(z+>O-CWQ?-!combzW z3kHny`dFXsHSk{N6iB~==0(_jftSp_G$f?B0JsJh;L)ZGV=;SXN%WJR`@I1!>>lLG zGyu&T-bG6z3ja1jN5;Bv!C_fcCseg0noSpdhmfUo=MN?$hu6kSF$2{(^Tg^|Hu*$#moXFuvdU{#ST z&uk^!w{F*JdU_q&qtU;Xd@X*b7UflR#F=8YFwltiqxUPko))To5wOCMSf12|GXCoz z2|1z`na#0;dy*(UM1R^ZS=sOaCx zkzPah=DJL%zkk{K!*3u#!w=L^LeV40E{%=2l7_vqJ}yvS-*jXEgb4G{z(wLnb0zWq znF0qqSFY*H`cwg~B+sE$QM*z6L(R);@(8ifyxGQaZY-~0q%mxcCLcJyog*6emiS4N z0vKlkoPoUXn@km@yR(SIeA>Kt%kbV>{cb-9Y4TT7(q!%$oby31zvCQwU}KE}m)Xjo zoq-v4f0x+P+;~PBO05XE5E{n@0M6`!-e$zQSfv-2K?tpBQibzrv26YofkxVW})yB(SEq-Fj2m zl^}+d$CP+y$s5T1Z19Bm3evj<(bor~*Jl#P$q+CkF#8GlcuqFW6^(3YUyJiG&Kk@Z z+(OR_OseRvUVP+NjCo^cZ+!xt9Wz~paSCyI*Mq5!vg_EGBWf2$kC`P1=WCt^*QNEG zP;N3-#QZG4Zcs8TLS07v{gtjD`8Mrxn+E2J!#arIK#^#;(0_5F{LWCKVYm00oWPtKH3ZVM+8vGfOOQI5yCos#Z6U4^>c9 z?cSt~!L#z^9#Tx@VfMXXS{8gxeQcfJ9GE+?_=?-clY*>EiudoRmu}>qtX^FM0~P`r zk;$t{d6CwZuJ|zhT)IAix3&GSw9e{g&PE1 z|7BFka=U;1RQ~c{JucNC#>Mkkkx$k3vRiS#GvS*9fcNuPuDYs{T zX*xL6ytUN>TY|@@g}047mNshx0&#;4$9Z!(XL#J5vij&&l*0ptDSxnE(E2XKWxKp9 zjAv6+2_VjS7H?GBBDC@nI)=*i9Ols7s`mYzth!~5aIF|Uw7E`YK){$@K)x`prLK5N z4W?7*f6Si*r*=l3ZbjCDk_rY*ePUN1^{I{uyLLSr1^0hM0!hKVGFAr zNP=ciOI^f5PgH4}cMaHrK45KHV9#+xGq6cfCi6>oXMwl7@>|3qvMb)7$%;PV-z@%r zKnP?J2Fc&Ysr9r0#?1(HuT}kPLl~fi?t!9=!zGC;)emG>*J=(qlvMm~6V)0XWm<1g z6jwPaa+nMWBCo#{xR@I+sM`HDg1Z(8^9ztBok&y2!0|&_dj(|4L2tEqJDJn{hiN;t z25g{C+v~5I?(l%>)p2`00vyqOW&B|73N1)beI9z$kg(YfmQ8Ok#ke@8g^2vnL(+cy zwn$2MN|0bs3Pq0Q>m#{7pOf7Tgy&!;;+v!=xvb{VO&9AHxm4V56Ak*(%!*TtG~L#o zB7t*9I|!=HJM4{f1UDi@+811rHl7CD9=lrX;D>Q0eZO*_Re8171mE7|NK-WmX-E9FiXD-(qL6}(#a@%G3nb69`iv^=t+%Y*X?G-Uv|q1 zI1)5zi-Pxj%^YdB9c;7SsUnWK`FdQvskO&n)V_tGFuJ&0n+db2<8Nu~-#S}`5*N2y z+z#7$(Q3fIg+O;>JT5hgBa8pT?6MK=;Rv!Chv<5`;BN|&JoKPa7@MC}pk zNCU1>Ek!W4&D#*s-2cjb4d=d}7k#^QVxJS2{oMr3CZR?jXek(C;^NHY%uC%m-03hR zswp_e+qt<;??OBO`fb2kqRaz^eo6ARUZF-qg>?UAx^c(3or~phY8SAzF*#AJp}Sdq z(%eWux~y*Q2ppjp2GE0Pis`)0M$b#lG>JWx2?$q?F?mysHIDYsFJxWDM0nFm?-N&@ z_xsUqF0uMFNZW;Jcnxq)jQ9ApkyLLD&3S&p{IW;TLaE`K87p=PR&y;u+%lSHtQUC( zk>{3L$iB!<=8?aK#?9lGTadr1Rpe3`;5Z zET;8!x3KD(R0srxs{CBupM~_m5V6zN4IurGdG?GtCn98 zA~?aZWM|ZJ=h!zh!Tt^S@u*&h^7qch1lz(`$!^wkh(u|K?4d3rT>L9)%Jd0 z!S$BL7Lz?~^`l%jShj+wphudspIS;ce5LFm`Nt7?I$a10Qu`2};L!dY>q$d! z=gbr?P_3A}0HzmOG|OwNkF_IF_Y$*TStl0-RPze&D0CSKWhFZkHA!bHIgR-bFGw0% zeK>Qs;Yi?31NB74&-c_FDZ4?J!l%39W~N+McSnRYD&{gL(@6bZvUIPqpst7AWDF4H zP@hNKebtjHFz|tejI&qEKsr`X1d@N6mXg8UJsC$Qj)=-C@GHIEX@rqi7m)cNsj*EJ zyMz4{Rm&y#nO#J4#a2YdYfR%dMP$!Xrvjut=xh%}u}3ce1J$}DUWxl;0f=GQ8U z0>4wKAODBEWo^aR0A=tZD_{GWgHpO%Q7lM@5V*7he5gc-L8Lw~o?bu%Yqlo3`!hl( z*F4%7G{smk<~($*HO$;PmBqHBD1gZsXfeaB06-HVIz6Sfb6QWL#lQoiwBBIYJ~AjWp456d>DuZYq(yk)RpQv8SnjxeZvd+G0Tz8xG^z{fl$x zSP_;~L7?>3j!UO5u{d9{^toaUVfbZN)r9B^(k2Ec<0^Tcqy=weK^s82*Q1B%#9w=v zv_~XBu4d~;j4XjWN*s@J=PT`7#M?a!cMjxgSlaIcZX=jfnHFj@>S4noB_=C^vgnKhm%lIg##G2`n_gd%?T7qxzWT$#M_bk)sv3?p zXTe)eGFrDj#%|g;3;VFCguiP$^K|N3x-1Y=_E;HuYs{Uix_7o3&p6 zy^(wPyRYL+eOYIcuuNv=g`7&gYSycAR^4+SMOFnFoWo0@=lz5?ZjxA-kAs}t=pU2b zu0_w5gA{#kIv4^FjZHv^$X%SSvN|hpkXne3RG6v$y1G;ydl1CfZ=<3`8+fX^~DL#Rr3$yE) zCmr2+MUR~+ik%UqRvizecH_DO5mqKs3P*iALWevXz~bqiTqDq<@=7?9aqc4)*8i4% zTimDR_|M(UWy1TxLT4%x>j%NSI)8mF%Hgr;^>982DvIDVuZ@6Xz!dTGY2iJ_^X$uFj>2}8S6Pp zupA5cveoG5>xg^O$JF;NH{4rn-LJ%mpbNwWy>asY!WMFGzhEl+A44}JP=k~F&g4ZK z+}#?nqweMHz%CQ7ofdWHWZMb6jBxN$_$Q?bLqybQE6N4GBEf=}H^l{J50eamF&JN- z3Z77}CZYd4n4s6>HzAgDp~{P{%MfZPjAgSrH^;7;DJfPHW>k$gc_S+b7bY9kgj^~I zm}~V5l!h|gy4AQx6YC<(p9F~WsHK0BZPyIC>S1-e}dPj zA~JbM=1iSW87pW(pK4{YfOb%cZq6xk7bcJVcn#U)72kcBny3Oggu)lcwD=vbsv7zN zt%#G&S-nno19qbZ_iOo%1!EnTKkBG4>M+(M zn$6NwqB%PiNRAdg_XBr%|NdaZYtD|+*C+ZFicasUoe_dWDIkq<8hW}^awaM$h@enM zR$MWX&%NzF_cEoqbK!=6C()JECLS=;gqr;#8%>y%t92Hf zSz_qeIt9mmdY#ddlwH7hDTqH6U+(p%LGJJP%OeJ?0i7$OEkK%IrtV0Ag*^%E^X0@! zRVlPNzCUuN0t8d_OrNid(qPvl=`|n@Rft~}rJ$GCvz4^uHg4Co@EHE#knXRW%Sb0! zColKmUFoMJ3B(?y9RZhmDXQoJ31Np0N^0>HO3AjIOm3^4z{3Oz5j^T7A|)Lq|J3LY zD7CC$&z7vPo~^rd;<(*KKh!cuMharBtHd+PZ)%CuZ|NF zq={rPFLH+skoG!0f}gL%=H~A$TDp*#ahYOVlUt1!hhGJy>!ojOSVv3$2@)dW+b$!Bgs!W%MbQg$|R#_N2ZZ%@ae&16rC_tUp@}5 zUvWT6j-~&x7^psyaL$vI1z5uE1HLHEnoG^pSEm)IUA!RgXT}}Q!+_{+c$Rf*j6M~B zA|luFaV5CVj_FnmltaYkgJ|0#w{`ro#U3i#{DYw|^yedV`{#sP|ca=-x6 zt7i5~ywTIuX^PrvV=1ANhni}C^k-@7Is1%njm!0w9C$wZ_>`2dXZc!z`27!%JjSl; zd;RLQV41YR{XhZZ$by%c#VG8kWg7)qdENsaVbK$rA$t%ik@$`Fb@=hok07!c9JxC- zT|s|DcLeuHBIOo|e3}%?uBrh^(G;Q8Hk-khAAj<_=ysNl*8 zYgMl-Is8aC8%8<*?u>pDkf#k^<_3q5C{=`v4;$_XJrAcf1yZMXhS93&waaIZe{0 zUAj}@`oYnz&XRUXNX&9 )|Z>W&}Yhk#VWYLh`^OxZd|ERTH>*dMF*LG`h##XP-r z^`TqpJxFi|eCFZ7Y}*b52|a$k&w$a465Gy8P~@)qz)W`xuevXpEcWN?FbA^uC>=;> zxt@y){Ys11IJYq7`vVX*-Ik-nK~ZZ?{;nwhc`Q8Ucd)2P@Z=-KMG{1jZPSX%kO}=Ih+My_sS01!p~CzJ zh=T;-Cx1=%bt}iTZ4lpu1Zm)5GN;Xv8dYp5(hR^`h{Fq|`+0H8dMnP_HzAXUS1?w< zz1dfi5tZ%*hSM)yqbW8s<54q(;FGh~(B`L>VyK;Oo=EpbgvwhZAzX zpcVd4DN_0GR^N_p8Hr()9?6&yDSG@#>M8dkDY&H@jQ><$64`JhoV& z6)^Iv^20eo>??$S2M3;6_&Nm{f8MVuP|5ojR3k%q*0=~lSXqTFMI(&|$7(DfJ&3ij z9azTFr41g4DCri0UaJn34RBt+q}diHmH42yNw_UU?*9;bjb-%>pN9r>6{gYQl%6t1 zd1meuG|9lgKd*T){b{24l_BK|2L>4vfCe-6LOj%y39Wyec>CCHfv)2tq&n^_O$j-B z|G1Kk4>YO+YoI8c?ttrVqb)Jf=c$vu4@K=WC|Qaz_k=$-a{=^};k-!yhMqNjI6zBG z2#6_>8*huRK%4i~hz2uo+odnqVhD;3fP3=3FjI8o(A#>doEcgFjTyap=T-P z7<2aP+G_x__E0n5wvnWn{Jhzwnw}AKu6v<4eR{sxc0q_~tpZVy@X7@og<|78n`J(V zK?F$`gUa$Q6am+VOK>Yd+}gv(n+DP1Z!pWg1a3fY=%s0iNhWbqypfAM z?lI0Iz~&$Ma$~qE2<`SKo2o%;ns5gJ_s|aoWXj?D=s$sr10)JQamAxs-)5|Ll@SQn zUSiV~(+aA(NaFiK2KaUV63h5U4R;+J=^y+hpRF{%Y1q$9NiqZNzn*|+?5Pz#Bj>Ly z@b%~vhGq=9s$ajHN&Vl3Eu?xJ3>C?iHXmlx7vN_P=7!y7di0H%9`V+BhVROqY{{a! zD;95Df(R=Z%qN^qEIquxeC9Mip-wv43D>ba>VseEY%OZ?K2eNoFU0{~rdLWS<&5EX zQpdT$o9@_t3<$Rs&`kDXGSWG9&UE~il{JQ{-v_}gf5*yi9d%Ro2Xo=#O22VKKIgJ> zjKIogIRR%npk4Q-JTFDcy|#@l24d*`3+S%a_W6S?nG5e=;l}aOxMJfnY7J@EO87@o z!nO!){@rgZvgg=oBF*kFVL?JanB3`b3~)gV3uOXKqZy->0YR=Jk0r-HOiNWRm=#y=KZ;(YP+s8gt-{`7 z|M41r|MD-$)r!|gma|$0YEx>^RtZanE0ZPwy&Mp6f9}1<8Fg;coVwXiXzlgE#LLbz zV(2!bsszd5r^%S2P3Dkjs+jO>3QiPL0o2QC$@5UZN4fMwEL0dl-n3>TK-gTw2SBs? z{~+HiGuM#z#X;&}9{2rX;&xMUl38ve-4+O8O$Xr)66{@E9Zi2Ah4}zyu4zcpTwa|wFo=w; z>+aWj*wd1U4>NS>k6<;!wQVe>N8@=1bKgFlJIvlvze;IE4=amYF3g2GH(0>twREqv zqZTwJGcdAPg7D*aS1wlsUaQW8>Sfr7$O>CcZ%f6zI#>mb)bUnb1vVc*ct^}v`;nc{ zs$9F893#JBil}vXKU~7MVFiYn1K^^?N;O?Hhl9Sdf9}@p3xE;4yAh~@3 z>9IME!8$V|q)VWMHkAn+XpDj9c7}* zH~Gu9e%ZrglZ2+RAf==}9_Nnp>_v}w-q%lcSz8pXhaR}w=E56m*2$c{yCN#gc{6P!H*&Rb1^ z9qQX}{>&5Osblc_zrndzN!F>2&vN>*-EaTFrc+`ln(1e=!O^_hc-)-#4Zrw4s4H;rMwCllP z2`{XNK;Hz_J+2Rnl5}*7qEKj=>ps1Az27Qt=NGtiIM9PgyXHhRUCgIzLl-xg*M^cv zaJ}8+9OfdMO3Em6d*u+Exd-{WXCC5#=mu**{rw9+TZ)+S?U6}=?^mW8GD1VJGncnM zO?ckFl2xW7SI~98mDq^3Z;$O16#D$mF;slhE$6Z|((9J;;X(btn%yd>{u?))P^)I; zP)3&D?nn-Z26K2q3x^b8m@VNb1YzYsF4SZNa!Arv>C*){m9(2fhRjMVxN8C15gS*{ zo#&yrJqPkDv7|u#*OOy4b+%ArKXuS52YllZ=>Y?E%6DSmu1Z3?eWHbn~JEf)0v?C>c|6Qe)smTK} zrpVpXmb|HOTWxn%{Y6B|0Ya7qL_QGPN#qk|x12fdPV4lW#Fhy4d0AM~PrAqe001u6 dhX4Qp>c0c4mux*>fm=Q>4T}N*00001Sz5;`QgHwP From 1d64cd88968f5672758239fc05d638795727856d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 10:24:08 +0100 Subject: [PATCH 08/85] Add new dependency --- R-package/DESCRIPTION | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 253ee4832..bd46615c0 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -21,4 +21,6 @@ Depends: R (>= 2.10) Imports: Matrix (>= 1.1-0), - methods + methods, + data.table (>= 1.9), + magrittr (>= 1.5) \ No newline at end of file From 8c17a86b3859e07f8df427158fa9fff7516032fe Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 10:24:43 +0100 Subject: [PATCH 09/85] Update Namespace with new function --- R-package/NAMESPACE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index a8dc5c7c3..1e6031c9f 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -1,4 +1,4 @@ -# Generated by roxygen2 (4.0.1): do not edit by hand +# Generated by roxygen2 (4.1.0): do not edit by hand export(getinfo) export(setinfo) @@ -7,6 +7,7 @@ export(xgb.DMatrix) export(xgb.DMatrix.save) export(xgb.cv) export(xgb.dump) +export(xgb.importance) export(xgb.load) export(xgb.save) export(xgb.train) From e63c79d6c62e0b4fa23e4f9d53ef167ecd78174b Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 10:45:47 +0100 Subject: [PATCH 10/85] new function cv.importance + documentation --- R-package/R/xgb.importance.R | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 R-package/R/xgb.importance.R diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R new file mode 100644 index 000000000..690795ff4 --- /dev/null +++ b/R-package/R/xgb.importance.R @@ -0,0 +1,54 @@ +#' Show importance of features in a model +#' +#' Read a xgboost model in text file format. Return a data.table of the features with their weight. +#' +#' @importFrom data.table data.table +#' @importFrom magrittr %>% +#' @importFrom data.table := +#' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix. +#' @param filename_dump the name of the text file. +#' +#' @examples +#' data(agaricus.train, package='xgboost') +#' data(agaricus.test, package='xgboost') +#' +#' #Both dataset are list with two items, a sparse matrix and labels (outcome column which will be learned). +#' #Each column of the sparse Matrix is a feature in one hot encoding format. +#' train <- agaricus.train +#' test <- agaricus.test +#' +#' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, +#' eta = 1, nround = 2,objective = "binary:logistic") +#' xgb.dump(bst, 'xgb.model.dump') +#' +#' #agaricus.test$data@@Dimnames[[2]] represents the column name of the sparse matrix. +#' xgb.importance(agaricus.test$data@@Dimnames[[2]], 'xgb.model.dump') +#' +#' @export +xgb.importance <- function(feature_names, filename_dump){ + text <- readLines(filename_dump) + if(text[2] == "bias:"){ + result <- linearDump(feature_names, text) + } else { + result <- treeDump(feature_names, text) + } + result +} + +treeDump <- function(feature_names, text){ + result <- c() + for(line in text){ + m <- regexec("\\[f.*\\]", line) + p <- regmatches(line, m) + if (length(p[[1]]) > 0) { + splits <- as.numeric(strsplit(sub("\\]", "", sub("\\[f", "", p[[1]])), "<")[[1]]) + result <- c(result, feature_names[splits[1]+ 1]) + } + } + #1. Reduce, 2. %, 3. remove temp col, 4. reorder - bigger top + data.table(Feature = result)[,.N, by = Feature][, Weight:= N /sum(N)][order(-rank(Weight))][,-2,with=F] +} + +linearDump <- function(feature_names, text){ + which(text == "weight:") %>% {a=.+1;text[a:length(text)]} %>% as.numeric %>% data.table(Feature = feature_names, Weight = .) +} \ No newline at end of file From ce83611a72088ee94647614295597bb1c1a69c89 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 10:46:31 +0100 Subject: [PATCH 11/85] generated documentation with ROxygen2 --- R-package/NAMESPACE | 3 ++ R-package/man/agaricus.test.Rd | 3 +- R-package/man/agaricus.train.Rd | 3 +- R-package/man/getinfo.Rd | 7 +++-- R-package/man/predict-xgb.Booster-method.Rd | 7 +++-- R-package/man/setinfo.Rd | 7 +++-- R-package/man/slice.Rd | 7 +++-- R-package/man/xgb.DMatrix.Rd | 3 +- R-package/man/xgb.DMatrix.save.Rd | 3 +- R-package/man/xgb.cv.Rd | 8 +++-- R-package/man/xgb.dump.Rd | 3 +- R-package/man/xgb.importance.Rd | 33 +++++++++++++++++++++ R-package/man/xgb.load.Rd | 3 +- R-package/man/xgb.save.Rd | 3 +- R-package/man/xgb.train.Rd | 3 +- R-package/man/xgboost.Rd | 7 +++-- 16 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 R-package/man/xgb.importance.Rd diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 1e6031c9f..e4784bbf5 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -16,3 +16,6 @@ exportMethods(predict) import(methods) importClassesFrom(Matrix,dgCMatrix) importClassesFrom(Matrix,dgeMatrix) +importFrom(data.table,":=") +importFrom(data.table,data.table) +importFrom(magrittr,"%>%") diff --git a/R-package/man/agaricus.test.Rd b/R-package/man/agaricus.test.Rd index 3cf87c9f8..8fee4e2ee 100644 --- a/R-package/man/agaricus.test.Rd +++ b/R-package/man/agaricus.test.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgboost.R \docType{data} \name{agaricus.test} \alias{agaricus.test} diff --git a/R-package/man/agaricus.train.Rd b/R-package/man/agaricus.train.Rd index dd9f6e9a3..02b55423a 100644 --- a/R-package/man/agaricus.train.Rd +++ b/R-package/man/agaricus.train.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgboost.R \docType{data} \name{agaricus.train} \alias{agaricus.train} diff --git a/R-package/man/getinfo.Rd b/R-package/man/getinfo.Rd index 23e3adc84..37e0ad0be 100644 --- a/R-package/man/getinfo.Rd +++ b/R-package/man/getinfo.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/getinfo.xgb.DMatrix.R \docType{methods} \name{getinfo} \alias{getinfo} @@ -12,9 +13,9 @@ getinfo(object, ...) \arguments{ \item{object}{Object of class "xgb.DMatrix"} -\item{name}{the name of the field to get} - \item{...}{other parameters} + +\item{name}{the name of the field to get} } \description{ Get information of an xgb.DMatrix object diff --git a/R-package/man/predict-xgb.Booster-method.Rd b/R-package/man/predict-xgb.Booster-method.Rd index 36d6327b1..afa0c70a5 100644 --- a/R-package/man/predict-xgb.Booster-method.Rd +++ b/R-package/man/predict-xgb.Booster-method.Rd @@ -1,11 +1,12 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/predict.xgb.Booster.R \docType{methods} \name{predict,xgb.Booster-method} \alias{predict,xgb.Booster-method} \title{Predict method for eXtreme Gradient Boosting model} \usage{ -\S4method{predict}{xgb.Booster}(object, newdata, outputmargin = FALSE, - ntreelimit = NULL) +\S4method{predict}{xgb.Booster}(object, newdata, missing = NULL, + outputmargin = FALSE, ntreelimit = NULL) } \arguments{ \item{object}{Object of class "xgb.Boost"} diff --git a/R-package/man/setinfo.Rd b/R-package/man/setinfo.Rd index 7ea992110..4ed262b46 100644 --- a/R-package/man/setinfo.Rd +++ b/R-package/man/setinfo.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/setinfo.xgb.DMatrix.R \docType{methods} \name{setinfo} \alias{setinfo} @@ -12,11 +13,11 @@ setinfo(object, ...) \arguments{ \item{object}{Object of class "xgb.DMatrix"} +\item{...}{other parameters} + \item{name}{the name of the field to get} \item{info}{the specific field of information to set} - -\item{...}{other parameters} } \description{ Set information of an xgb.DMatrix object diff --git a/R-package/man/slice.Rd b/R-package/man/slice.Rd index a749aa8ff..a7812e886 100644 --- a/R-package/man/slice.Rd +++ b/R-package/man/slice.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/slice.xgb.DMatrix.R \docType{methods} \name{slice} \alias{slice} @@ -13,9 +14,9 @@ slice(object, ...) \arguments{ \item{object}{Object of class "xgb.DMatrix"} -\item{idxset}{a integer vector of indices of rows needed} - \item{...}{other parameters} + +\item{idxset}{a integer vector of indices of rows needed} } \description{ Get a new DMatrix containing the specified rows of diff --git a/R-package/man/xgb.DMatrix.Rd b/R-package/man/xgb.DMatrix.Rd index 227fb515f..86000220f 100644 --- a/R-package/man/xgb.DMatrix.Rd +++ b/R-package/man/xgb.DMatrix.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.DMatrix.R \name{xgb.DMatrix} \alias{xgb.DMatrix} \title{Contruct xgb.DMatrix object} diff --git a/R-package/man/xgb.DMatrix.save.Rd b/R-package/man/xgb.DMatrix.save.Rd index 803de912b..6bbc277b3 100644 --- a/R-package/man/xgb.DMatrix.save.Rd +++ b/R-package/man/xgb.DMatrix.save.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.DMatrix.save.R \name{xgb.DMatrix.save} \alias{xgb.DMatrix.save} \title{Save xgb.DMatrix object to binary file} diff --git a/R-package/man/xgb.cv.Rd b/R-package/man/xgb.cv.Rd index 050d8c4f8..b9c600c0e 100644 --- a/R-package/man/xgb.cv.Rd +++ b/R-package/man/xgb.cv.Rd @@ -1,10 +1,12 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.cv.R \name{xgb.cv} \alias{xgb.cv} \title{Cross Validation} \usage{ -xgb.cv(params = list(), data, nrounds, nfold, label = NULL, showsd = TRUE, - metrics = list(), obj = NULL, feval = NULL, ...) +xgb.cv(params = list(), data, nrounds, nfold, label = NULL, + missing = NULL, showsd = TRUE, metrics = list(), obj = NULL, + feval = NULL, ...) } \arguments{ \item{params}{the list of parameters. Commonly used ones are: diff --git a/R-package/man/xgb.dump.Rd b/R-package/man/xgb.dump.Rd index 9571855ff..9be2696b9 100644 --- a/R-package/man/xgb.dump.Rd +++ b/R-package/man/xgb.dump.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.dump.R \name{xgb.dump} \alias{xgb.dump} \title{Save xgboost model to text file} diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd new file mode 100644 index 000000000..9609fe82f --- /dev/null +++ b/R-package/man/xgb.importance.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.importance.R +\name{xgb.importance} +\alias{xgb.importance} +\title{Show importance of features in a model} +\usage{ +xgb.importance(feature_names, filename_dump) +} +\arguments{ +\item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix.} + +\item{filename_dump}{the name of the text file.} +} +\description{ +Read a xgboost model in text file format. Return a data.table of the features with their weight. +} +\examples{ +data(agaricus.train, package='xgboost') +data(agaricus.test, package='xgboost') + +#Both dataset are list with two items, a sparse matrix and labels (outcome column which will be learned). +#Each column of the sparse Matrix is a feature in one hot encoding format. +train <- agaricus.train +test <- agaricus.test + +bst <- xgboost(data = train$data, label = train$label, max.depth = 2, + eta = 1, nround = 2,objective = "binary:logistic") +xgb.dump(bst, 'xgb.model.dump') + +#agaricus.test$data@Dimnames[[2]] represents the column name of the sparse matrix. +xgb.importance(agaricus.test$data@Dimnames[[2]], 'xgb.model.dump') +} + diff --git a/R-package/man/xgb.load.Rd b/R-package/man/xgb.load.Rd index d2c5d94b6..433b38c79 100644 --- a/R-package/man/xgb.load.Rd +++ b/R-package/man/xgb.load.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.load.R \name{xgb.load} \alias{xgb.load} \title{Load xgboost model from binary file} diff --git a/R-package/man/xgb.save.Rd b/R-package/man/xgb.save.Rd index 0ccdf13da..ded444446 100644 --- a/R-package/man/xgb.save.Rd +++ b/R-package/man/xgb.save.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.save.R \name{xgb.save} \alias{xgb.save} \title{Save xgboost model to binary file} diff --git a/R-package/man/xgb.train.Rd b/R-package/man/xgb.train.Rd index a05e2eeb9..58ef94135 100644 --- a/R-package/man/xgb.train.Rd +++ b/R-package/man/xgb.train.Rd @@ -1,4 +1,5 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.train.R \name{xgb.train} \alias{xgb.train} \title{eXtreme Gradient Boosting Training} diff --git a/R-package/man/xgboost.Rd b/R-package/man/xgboost.Rd index d5abe927d..d85ee6d4a 100644 --- a/R-package/man/xgboost.Rd +++ b/R-package/man/xgboost.Rd @@ -1,10 +1,11 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgboost.R \name{xgboost} \alias{xgboost} \title{eXtreme Gradient Boosting (Tree) library} \usage{ -xgboost(data = NULL, label = NULL, params = list(), nrounds, - verbose = 1, ...) +xgboost(data = NULL, label = NULL, missing = NULL, params = list(), + nrounds, verbose = 1, ...) } \arguments{ \item{data}{takes \code{matrix}, \code{dgCMatrix}, local data file or From 46862e561beb74d8717da43a09296755f6ee507b Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 10:47:02 +0100 Subject: [PATCH 12/85] Add .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a0bd1b6f6..d454c6d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ Debug *dump *save *csv +.Rproj.user From 151285300b52e3f932302c0e8071a2000bdb0a20 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 11:02:48 +0100 Subject: [PATCH 13/85] change version number + date --- R-package/DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index bd46615c0..ce0ab9081 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,8 +1,8 @@ Package: xgboost Type: Package Title: eXtreme Gradient Boosting -Version: 0.3-2 -Date: 2014-08-23 +Version: 0.3-3 +Date: 2014-12-28 Author: Tianqi Chen , Tong He Maintainer: Tong He Description: This package is a R wrapper of xgboost, which is short for eXtreme From 2154a160a37ec50edc099c9e00ddab4b42c38e43 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 11:18:26 +0100 Subject: [PATCH 14/85] refactoring of validation to improve source code readability. --- R-package/R/xgb.importance.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 690795ff4..88af8387a 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -41,14 +41,14 @@ treeDump <- function(feature_names, text){ m <- regexec("\\[f.*\\]", line) p <- regmatches(line, m) if (length(p[[1]]) > 0) { - splits <- as.numeric(strsplit(sub("\\]", "", sub("\\[f", "", p[[1]])), "<")[[1]]) + splits <- sub("\\]", "", sub("\\[f", "", p[[1]])) %>% strsplit("<")[[1]] %>% as.numeric result <- c(result, feature_names[splits[1]+ 1]) } } - #1. Reduce, 2. %, 3. remove temp col, 4. reorder - bigger top + #1. Reduce, 2. %, 3. reorder - bigger top, 4. remove temp col data.table(Feature = result)[,.N, by = Feature][, Weight:= N /sum(N)][order(-rank(Weight))][,-2,with=F] } linearDump <- function(feature_names, text){ - which(text == "weight:") %>% {a=.+1;text[a:length(text)]} %>% as.numeric %>% data.table(Feature = feature_names, Weight = .) + which(text == "weight:") %>% {a=.+1; text[a:length(text)]} %>% as.numeric %>% data.table(Feature = feature_names, Weight = .) } \ No newline at end of file From 84fb89af708463cfbac7dc3363c0b345238022fa Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 11:30:55 +0100 Subject: [PATCH 15/85] fix small bug introduced in refactoring --- R-package/R/xgb.importance.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 88af8387a..f9bd40705 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -38,10 +38,9 @@ xgb.importance <- function(feature_names, filename_dump){ treeDump <- function(feature_names, text){ result <- c() for(line in text){ - m <- regexec("\\[f.*\\]", line) - p <- regmatches(line, m) + p <- regexec("\\[f.*\\]", line) %>% regmatches(line, .) if (length(p[[1]]) > 0) { - splits <- sub("\\]", "", sub("\\[f", "", p[[1]])) %>% strsplit("<")[[1]] %>% as.numeric + splits <- sub("\\[f", "", p[[1]]) %>% sub("\\]", "", .) %>% strsplit("<") %>% .[[1]] %>% as.numeric result <- c(result, feature_names[splits[1]+ 1]) } } @@ -50,5 +49,5 @@ treeDump <- function(feature_names, text){ } linearDump <- function(feature_names, text){ - which(text == "weight:") %>% {a=.+1; text[a:length(text)]} %>% as.numeric %>% data.table(Feature = feature_names, Weight = .) + which(text == "weight:") %>% {a=.+1;text[a:length(text)]} %>% as.numeric %>% data.table(Feature = feature_names, Weight = .) } \ No newline at end of file From 99af2c8ffd39bfc8e696646d6b24df2ab0a4fc99 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 28 Dec 2014 11:33:14 +0100 Subject: [PATCH 16/85] Documentation of the function --- R-package/R/xgb.importance.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index f9bd40705..b971c58ff 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -1,7 +1,10 @@ #' Show importance of features in a model #' -#' Read a xgboost model in text file format. Return a data.table of the features with their weight. +#' Read a xgboost model in text file format. +#' Can be tree or linear model (text dump of linear model are only supported in dev version of Xgboost for now). #' +#' Return a data.table of the features with their weight. +#' #' #' @importFrom data.table data.table #' @importFrom magrittr %>% #' @importFrom data.table := From 6b96737811cb7f9837c6c1fac9a4a80709c5dd2e Mon Sep 17 00:00:00 2001 From: tqchen Date: Sun, 28 Dec 2014 17:45:37 -0800 Subject: [PATCH 17/85] add dump statistics --- R-package/R/xgb.dump.R | 8 ++++++-- R-package/src/xgboost_R.cpp | 6 ++++-- R-package/src/xgboost_R.h | 3 ++- wrapper/xgboost.py | 12 ++++++++---- wrapper/xgboost_wrapper.cpp | 4 ++-- wrapper/xgboost_wrapper.h | 3 ++- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index c11101927..b5e66604c 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -10,6 +10,10 @@ #' See demo/ for walkthrough example in R, and #' \url{https://github.com/tqchen/xgboost/blob/master/demo/data/featmap.txt} #' for example Format. +#' @param with.stats whether dump statistics of splits +#' When this option is on, the model dump comes with two additional statistics: +#' gain is the approximate loss function gain we get in each split; +#' cover is the sum of second order gradient in each node. #' #' @examples #' data(agaricus.train, package='xgboost') @@ -21,13 +25,13 @@ #' xgb.dump(bst, 'xgb.model.dump') #' @export #' -xgb.dump <- function(model, fname, fmap = "") { +xgb.dump <- function(model, fname, fmap = "", with.stats=FALSE) { if (class(model) != "xgb.Booster") { stop("xgb.dump: first argument must be type xgb.Booster") } if (typeof(fname) != "character") { stop("xgb.dump: second argument must be type character") } - .Call("XGBoosterDumpModel_R", model, fname, fmap, PACKAGE = "xgboost") + .Call("XGBoosterDumpModel_R", model, fname, fmap, as.integer(with.stats), PACKAGE = "xgboost") return(TRUE) } diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 5f3b2dde4..7cab221fb 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -241,10 +241,10 @@ extern "C" { for (int i = 0; i < len; ++i) { vec_sptr.push_back(vec_names[i].c_str()); } + _WrapperEnd(); return mkString(XGBoosterEvalOneIter(R_ExternalPtrAddr(handle), asInteger(iter), BeginPtr(vec_dmats), BeginPtr(vec_sptr), len)); - _WrapperEnd(); } SEXP XGBoosterPredict_R(SEXP handle, SEXP dmat, SEXP output_margin, SEXP ntree_limit) { _WrapperBegin(); @@ -272,11 +272,13 @@ extern "C" { XGBoosterSaveModel(R_ExternalPtrAddr(handle), CHAR(asChar(fname))); _WrapperEnd(); } - void XGBoosterDumpModel_R(SEXP handle, SEXP fname, SEXP fmap) { + void XGBoosterDumpModel_R(SEXP handle, SEXP fname, + SEXP fmap, SEXP with_stats) { _WrapperBegin(); bst_ulong olen; const char **res = XGBoosterDumpModel(R_ExternalPtrAddr(handle), CHAR(asChar(fmap)), + asInteger(with_stats), &olen); FILE *fo = utils::FopenCheck(CHAR(asChar(fname)), "w"); for (size_t i = 0; i < olen; ++i) { diff --git a/R-package/src/xgboost_R.h b/R-package/src/xgboost_R.h index 9453bf061..04c16ab3e 100644 --- a/R-package/src/xgboost_R.h +++ b/R-package/src/xgboost_R.h @@ -132,7 +132,8 @@ extern "C" { * \param handle handle * \param fname file name of model that can be dumped into * \param fmap name to fmap can be empty string + * \param with_stats whether dump statistics of splits */ - void XGBoosterDumpModel_R(SEXP handle, SEXP fname, SEXP fmap); + void XGBoosterDumpModel_R(SEXP handle, SEXP fname, SEXP fmap, SEXP with_stats); } #endif // XGBOOST_WRAPPER_R_H_ diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 21a1cd18a..8d87dc8db 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -368,13 +368,15 @@ class Booster: None """ xglib.XGBoosterLoadModel( self.handle, ctypes.c_char_p(fname.encode('utf-8')) ) - def dump_model(self, fo, fmap=''): + def dump_model(self, fo, fmap='', with_stats = False): """dump model into text file Args: fo: string file name to be dumped fmap: string, optional file name of feature map names + with_stats: bool, optional + whether output statistics of the split Returns: None """ @@ -383,16 +385,18 @@ class Booster: need_close = True else: need_close = False - ret = self.get_dump(fmap) + ret = self.get_dump(fmap, with_stats) for i in range(len(ret)): fo.write('booster[%d]:\n' %i) fo.write( ret[i] ) if need_close: fo.close() - def get_dump(self, fmap=''): + def get_dump(self, fmap='', with_stats=False): """get dump of model as list of strings """ length = ctypes.c_ulong() - sarr = xglib.XGBoosterDumpModel(self.handle, ctypes.c_char_p(fmap.encode('utf-8')), ctypes.byref(length)) + sarr = xglib.XGBoosterDumpModel(self.handle, + ctypes.c_char_p(fmap.encode('utf-8')), + int(with_stats), ctypes.byref(length)) res = [] for i in range(length.value): res.append( str(sarr[i]) ) diff --git a/wrapper/xgboost_wrapper.cpp b/wrapper/xgboost_wrapper.cpp index b89af30e8..5ceb5d79d 100644 --- a/wrapper/xgboost_wrapper.cpp +++ b/wrapper/xgboost_wrapper.cpp @@ -293,11 +293,11 @@ extern "C"{ void XGBoosterSaveModel(const void *handle, const char *fname) { static_cast(handle)->SaveModel(fname); } - const char** XGBoosterDumpModel(void *handle, const char *fmap, bst_ulong *len){ + const char** XGBoosterDumpModel(void *handle, const char *fmap, int with_stats, bst_ulong *len){ utils::FeatMap featmap; if (strlen(fmap) != 0) { featmap.LoadText(fmap); } - return static_cast(handle)->GetModelDump(featmap, false, len); + return static_cast(handle)->GetModelDump(featmap, with_stats != 0, len); } } diff --git a/wrapper/xgboost_wrapper.h b/wrapper/xgboost_wrapper.h index 2ae70f026..2555289b1 100644 --- a/wrapper/xgboost_wrapper.h +++ b/wrapper/xgboost_wrapper.h @@ -200,11 +200,12 @@ extern "C" { * \brief dump model, return array of strings representing model dump * \param handle handle * \param fmap name to fmap can be empty string + * \param with_stats whether to dump with statistics * \param out_len length of output array * \return char *data[], representing dump of each model */ XGB_DLL const char **XGBoosterDumpModel(void *handle, const char *fmap, - bst_ulong *out_len); + int with_stats, bst_ulong *out_len); #ifdef __cplusplus } #endif From 755be4b84667d4b6a534f126280c94033ff31048 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 29 Dec 2014 10:31:17 +0100 Subject: [PATCH 18/85] Add variable type checks --- R-package/R/xgb.importance.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index b971c58ff..3b99fbb00 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -29,6 +29,12 @@ #' #' @export xgb.importance <- function(feature_names, filename_dump){ + if (class(feature_names) != "character") { + stop("feature_names: Has to be a vector of character. See help to see where to get it.") + } + if (class(filename_dump) != "character" & file.exists(filename_dump)) { + stop("filename_dump: Has to be a path to the model dump file.") + } text <- readLines(filename_dump) if(text[2] == "bias:"){ result <- linearDump(feature_names, text) From 9b6a14a99d685f0c313197f1b65c945d22627d7d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 29 Dec 2014 23:56:31 +0100 Subject: [PATCH 19/85] regeneration of documentation --- R-package/man/xgb.dump.Rd | 17 +++++++++++------ R-package/man/xgb.importance.Rd | 19 ++++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/R-package/man/xgb.dump.Rd b/R-package/man/xgb.dump.Rd index 9be2696b9..bcecc6abd 100644 --- a/R-package/man/xgb.dump.Rd +++ b/R-package/man/xgb.dump.Rd @@ -4,7 +4,7 @@ \alias{xgb.dump} \title{Save xgboost model to text file} \usage{ -xgb.dump(model, fname, fmap = "") +xgb.dump(model, fname, fmap = "", with.stats = FALSE) } \arguments{ \item{model}{the model object.} @@ -12,11 +12,16 @@ xgb.dump(model, fname, fmap = "") \item{fname}{the name of the binary file.} \item{fmap}{feature map file representing the type of feature. - Detailed description could be found at - \url{https://github.com/tqchen/xgboost/wiki/Binary-Classification#dump-model}. - See demo/ for walkthrough example in R, and - \url{https://github.com/tqchen/xgboost/blob/master/demo/data/featmap.txt} - for example Format.} +Detailed description could be found at +\url{https://github.com/tqchen/xgboost/wiki/Binary-Classification#dump-model}. +See demo/ for walkthrough example in R, and +\url{https://github.com/tqchen/xgboost/blob/master/demo/data/featmap.txt} +for example Format.} + +\item{with.stats}{whether dump statistics of splits + When this option is on, the model dump comes with two additional statistics: + gain is the approximate loss function gain we get in each split; + cover is the sum of second order gradient in each node.} } \description{ Save a xgboost model to text file. Could be parsed later. diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index 9609fe82f..7d02315f9 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -4,30 +4,35 @@ \alias{xgb.importance} \title{Show importance of features in a model} \usage{ -xgb.importance(feature_names, filename_dump) +xgb.importance(feature_names = NULL, filename_dump = NULL) } \arguments{ -\item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix.} +\item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} -\item{filename_dump}{the name of the text file.} +\item{filename_dump}{the path to the text file storing the model.} } \description{ -Read a xgboost model in text file format. Return a data.table of the features with their weight. +Read a xgboost model in text file format. +Can be tree or linear model (text dump of linear model are only supported in dev version of Xgboost for now). +} +\details{ +Return a data.table of the features with their weight. +#' } \examples{ data(agaricus.train, package='xgboost') data(agaricus.test, package='xgboost') -#Both dataset are list with two items, a sparse matrix and labels (outcome column which will be learned). +#Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). #Each column of the sparse Matrix is a feature in one hot encoding format. train <- agaricus.train test <- agaricus.test bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2,objective = "binary:logistic") -xgb.dump(bst, 'xgb.model.dump') +xgb.dump(bst, 'xgb.model.dump', with.stats = T) -#agaricus.test$data@Dimnames[[2]] represents the column name of the sparse matrix. +#agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. xgb.importance(agaricus.test$data@Dimnames[[2]], 'xgb.model.dump') } From dba1ce7050bdb3aee6c92edddcb976be997d1c1d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 29 Dec 2014 23:57:01 +0100 Subject: [PATCH 20/85] new dependency over stringr --- R-package/DESCRIPTION | 3 ++- R-package/NAMESPACE | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index ce0ab9081..6f73766fa 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -23,4 +23,5 @@ Imports: Matrix (>= 1.1-0), methods, data.table (>= 1.9), - magrittr (>= 1.5) \ No newline at end of file + magrittr (>= 1.5), + stringr \ No newline at end of file diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index e4784bbf5..1714d2044 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -19,3 +19,4 @@ importClassesFrom(Matrix,dgeMatrix) importFrom(data.table,":=") importFrom(data.table,data.table) importFrom(magrittr,"%>%") +importFrom(stringr,str_extract) From 263f7fa69d362201cdf4eafd6ecdb4a7a8669ad0 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 29 Dec 2014 23:57:41 +0100 Subject: [PATCH 21/85] Take gain into account to discover most important variables --- R-package/R/xgb.importance.R | 39 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 3b99fbb00..51221b71b 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -8,34 +8,35 @@ #' @importFrom data.table data.table #' @importFrom magrittr %>% #' @importFrom data.table := -#' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix. -#' @param filename_dump the name of the text file. +#' @importFrom stringr str_extract +#' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. +#' @param filename_dump the path to the text file storing the model. #' #' @examples #' data(agaricus.train, package='xgboost') #' data(agaricus.test, package='xgboost') #' -#' #Both dataset are list with two items, a sparse matrix and labels (outcome column which will be learned). +#' #Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). #' #Each column of the sparse Matrix is a feature in one hot encoding format. #' train <- agaricus.train #' test <- agaricus.test #' #' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, #' eta = 1, nround = 2,objective = "binary:logistic") -#' xgb.dump(bst, 'xgb.model.dump') +#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) #' -#' #agaricus.test$data@@Dimnames[[2]] represents the column name of the sparse matrix. +#' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. #' xgb.importance(agaricus.test$data@@Dimnames[[2]], 'xgb.model.dump') #' #' @export -xgb.importance <- function(feature_names, filename_dump){ - if (class(feature_names) != "character") { - stop("feature_names: Has to be a vector of character. See help to see where to get it.") +xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ + if (!class(feature_names) %in% c("character", "NULL")) { + stop("feature_names: Has to be a vector of character or NULL if model dump already contain feature name. See help to see where to get it.") } - if (class(filename_dump) != "character" & file.exists(filename_dump)) { + if (class(filename_dump) != "character" & file.exists(filename_dump)) { stop("filename_dump: Has to be a path to the model dump file.") } - text <- readLines(filename_dump) + text <- readLines(filename_dump) if(text[2] == "bias:"){ result <- linearDump(feature_names, text) } else { @@ -44,17 +45,21 @@ xgb.importance <- function(feature_names, filename_dump){ result } -treeDump <- function(feature_names, text){ - result <- c() +treeDump <- function(feature_names, text){ + featureVec <- c() + gainVec <- c() for(line in text){ - p <- regexec("\\[f.*\\]", line) %>% regmatches(line, .) - if (length(p[[1]]) > 0) { - splits <- sub("\\[f", "", p[[1]]) %>% sub("\\]", "", .) %>% strsplit("<") %>% .[[1]] %>% as.numeric - result <- c(result, feature_names[splits[1]+ 1]) + p <- str_extract(line, "\\[f.*<") + if (!is.na(p)) { + featureVec <- substr(p, 3, nchar(p)-1) %>% c(featureVec) + gainVec <- str_extract(line, "gain.*,") %>% substr(x = ., 6, nchar(.)-1) %>% as.numeric %>% c(gainVec) } } + if(!is.null(feature_names)) { + featureVec %<>% as.numeric %>% {c =.+1; feature_names[c]} #+1 because in R indexing start with 1 instead of 0. + } #1. Reduce, 2. %, 3. reorder - bigger top, 4. remove temp col - data.table(Feature = result)[,.N, by = Feature][, Weight:= N /sum(N)][order(-rank(Weight))][,-2,with=F] + data.table(Feature = featureVec, Weight = gainVec)[,sum(Weight), by = Feature][, Weight:= V1 /sum(V1)][order(-rank(Weight))][,-2,with=F] } linearDump <- function(feature_names, text){ From 78813d8f78d0f4b17b23e670f4cae8e3625685f7 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 00:12:01 +0100 Subject: [PATCH 22/85] wording --- R-package/R/xgb.importance.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 51221b71b..0aa13afff 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -31,7 +31,7 @@ #' @export xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { - stop("feature_names: Has to be a vector of character or NULL if model dump already contain feature name. See help to see where to get it.") + stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } if (class(filename_dump) != "character" & file.exists(filename_dump)) { stop("filename_dump: Has to be a path to the model dump file.") From 3694772bde99af6b7df88e849ee5e09acea83c12 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 12:16:13 +0100 Subject: [PATCH 23/85] Add a new Weight and Gain column. Update documentation. --- R-package/R/xgb.importance.R | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 0aa13afff..8d37a9c15 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -3,7 +3,7 @@ #' Read a xgboost model in text file format. #' Can be tree or linear model (text dump of linear model are only supported in dev version of Xgboost for now). #' -#' Return a data.table of the features with their weight. +#' Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model)in the model. #' #' #' @importFrom data.table data.table #' @importFrom magrittr %>% @@ -12,6 +12,19 @@ #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. #' +#' @details +#' This is the function to understand the model trained (and through your model, your data). +#' Results are returned for both linear and tree models. +#' +#' \code{data.table} is returned by the function. +#' There are 3 columns : +#' \itemize{ +#' \item \code{Features} name of the features as provided in \code{feature_names} or already present in the model dump. +#' \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training. +#' \item \code{Weight} percentage representing the relative number of times a feature have been taken into trees. \code{Gain} should be prefered to search the most important feature. For boosted linear model, this column has no meaning. +#' } +#' +#' #' @examples #' data(agaricus.train, package='xgboost') #' data(agaricus.test, package='xgboost') @@ -45,7 +58,7 @@ xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ result } -treeDump <- function(feature_names, text){ +treeDump <- function(feature_names, text){ featureVec <- c() gainVec <- c() for(line in text){ @@ -59,7 +72,7 @@ treeDump <- function(feature_names, text){ featureVec %<>% as.numeric %>% {c =.+1; feature_names[c]} #+1 because in R indexing start with 1 instead of 0. } #1. Reduce, 2. %, 3. reorder - bigger top, 4. remove temp col - data.table(Feature = featureVec, Weight = gainVec)[,sum(Weight), by = Feature][, Weight:= V1 /sum(V1)][order(-rank(Weight))][,-2,with=F] + data.table(Feature = featureVec, Weight = gainVec)[,list(sum(Weight), .N), by = Feature][, Gain:= V1 /sum(V1)][,Weight:= N / sum(N)][order(-rank(Gain))][,-c(2,3), with = F] } linearDump <- function(feature_names, text){ From c754fd4ad0559d6bee25e4d4ee995e613afcc754 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 12:32:21 +0100 Subject: [PATCH 24/85] documentation wording --- R-package/man/xgb.importance.Rd | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index 7d02315f9..883819993 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -9,15 +9,25 @@ xgb.importance(feature_names = NULL, filename_dump = NULL) \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} -\item{filename_dump}{the path to the text file storing the model.} +\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} } \description{ -Read a xgboost model in text file format. -Can be tree or linear model (text dump of linear model are only supported in dev version of Xgboost for now). +Read a xgboost model text dump. +Can be tree or linear model (text dump of linear model are only supported in dev version of \code{Xgboost} for now). +Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model) in the model. } \details{ -Return a data.table of the features with their weight. -#' +This is the function to understand the model trained (and through your model, your data). + +Results are returned for both linear and tree models. + +\code{data.table} is returned by the function. +There are 3 columns : +\itemize{ + \item \code{Features} name of the features as provided in \code{feature_names} or already present in the model dump. + \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training. + \item \code{Weight} percentage representing the relative number of times a feature have been taken into trees. \code{Gain} should be prefered to search the most important feature. For boosted linear model, this column has no meaning. +} } \examples{ data(agaricus.train, package='xgboost') From c6f76fab561b7ada7a545b698f13b0bfd089d6c1 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 12:32:58 +0100 Subject: [PATCH 25/85] add new Gain and Weight columns. documentation updated. --- R-package/R/xgb.importance.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 8d37a9c15..b2e60bed7 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -1,19 +1,19 @@ #' Show importance of features in a model #' -#' Read a xgboost model in text file format. -#' Can be tree or linear model (text dump of linear model are only supported in dev version of Xgboost for now). +#' Read a xgboost model text dump. +#' Can be tree or linear model (text dump of linear model are only supported in dev version of \code{Xgboost} for now). +#' Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' -#' Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model)in the model. -#' #' #' @importFrom data.table data.table #' @importFrom magrittr %>% #' @importFrom data.table := #' @importFrom stringr str_extract #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. -#' @param filename_dump the path to the text file storing the model. +#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). #' #' @details #' This is the function to understand the model trained (and through your model, your data). +#' #' Results are returned for both linear and tree models. #' #' \code{data.table} is returned by the function. @@ -72,7 +72,7 @@ treeDump <- function(feature_names, text){ featureVec %<>% as.numeric %>% {c =.+1; feature_names[c]} #+1 because in R indexing start with 1 instead of 0. } #1. Reduce, 2. %, 3. reorder - bigger top, 4. remove temp col - data.table(Feature = featureVec, Weight = gainVec)[,list(sum(Weight), .N), by = Feature][, Gain:= V1 /sum(V1)][,Weight:= N / sum(N)][order(-rank(Gain))][,-c(2,3), with = F] + data.table(Feature = featureVec, Weight = gainVec)[,list(sum(Weight), .N), by = Feature][, Gain:= V1/sum(V1)][,Weight:= N/sum(N)][order(-rank(Gain))][,-c(2,3), with = F] } linearDump <- function(feature_names, text){ From d8eb978f986f89d711535b97f5e82e1881ef34d1 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 15:00:52 +0100 Subject: [PATCH 26/85] Update readme with new win on Kaggle --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 940cf3d43..d6bd25f95 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Learning about the model: [Introduction to Boosted Trees](http://homes.cs.washin What's New ===== +* XGBoost wins [Tradeshift Text Classification](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) * XGBoost wins [HEP meets ML Award in Higgs Boson Challenge](http://atlas.ch/news/2014/machine-learning-wins-the-higgs-challenge.html) * Thanks to Bing Xu, [XGBoost.jl](https://github.com/antinucleon/XGBoost.jl) allows you to use xgboost from Julia * See the updated [demo folder](demo) for feature walkthrough From 345b93fcfa80a2a8627fba9d950445d466847d8b Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 15:03:21 +0100 Subject: [PATCH 27/85] fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6bd25f95..6bc92ed04 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Learning about the model: [Introduction to Boosted Trees](http://homes.cs.washin What's New ===== -* XGBoost wins [Tradeshift Text Classification](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* XGBoost wins [Tradeshift Text Classification](https://kaggle2.blob.core.windows.net/forum-message-attachments/60041/1813/TradeshiftTextClassification.pdf?sv=2012-02-12&se=2015-01-02T13%3A55%3A16Z&sr=b&sp=r&sig=5MHvyjCLESLexYcvbSRFumGQXCS7MVmfdBIY3y01tMk%3D) * XGBoost wins [HEP meets ML Award in Higgs Boson Challenge](http://atlas.ch/news/2014/machine-learning-wins-the-higgs-challenge.html) * Thanks to Bing Xu, [XGBoost.jl](https://github.com/antinucleon/XGBoost.jl) allows you to use xgboost from Julia * See the updated [demo folder](demo) for feature walkthrough From 45a006f3670790ed4bdb8a3a8b234ca41832ae6d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:04:43 +0100 Subject: [PATCH 28/85] R demo code README --- R-package/demo/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/R-package/demo/README.md b/R-package/demo/README.md index 667eb143d..dd0261d4e 100644 --- a/R-package/demo/README.md +++ b/R-package/demo/README.md @@ -10,6 +10,11 @@ XGBoost R Feature Walkthrough Benchmarks ==== * [Starter script for Kaggle Higgs Boson](../../demo/kaggle-higgs) + +R language Demo +==== +R demo are contained in the [R package source code](https://github.com/pommedeterresautee/xgboost/tree/master/R-package/demo) as usually done in R. + Notes ==== From 31ed2813bd18d7790a2eaf0b2df7934ca9646517 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:05:12 +0100 Subject: [PATCH 29/85] Spell --- R-package/demo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/demo/README.md b/R-package/demo/README.md index dd0261d4e..5f2625b9f 100644 --- a/R-package/demo/README.md +++ b/R-package/demo/README.md @@ -13,7 +13,7 @@ Benchmarks R language Demo ==== -R demo are contained in the [R package source code](https://github.com/pommedeterresautee/xgboost/tree/master/R-package/demo) as usually done in R. +R demo code are contained in the [R package source code](https://github.com/pommedeterresautee/xgboost/tree/master/R-package/demo) as usually done in R. Notes From af31397ec2d367d9d21f6747c974e9205ea2d73e Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:22:24 +0100 Subject: [PATCH 30/85] Missing parameter documentation --- R-package/R/xgb.cv.R | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index c5f119095..02870b772 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -36,6 +36,8 @@ #' @param feval custimized evaluation function. Returns #' \code{list(metric='metric-name', value='metric-value')} with given #' prediction and dtrain, +#' @param missing Missing is only used when input is dense matrix, pick a float +# value that represents missing value. Sometime a data use 0 or other extreme value to represents missing values. #' @param ... other parameters to pass to \code{params}. #' #' @details @@ -73,7 +75,7 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = } folds <- xgb.cv.mknfold(dtrain, nfold, params) - history <- list() + history <- c() for (i in 1:nrounds) { msg <- list() for (k in 1:nfold) { @@ -83,8 +85,12 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = "\t")[[1]] } ret <- xgb.cv.aggcv(msg, showsd) - history <- append(history, ret) + history <- c(history, ret) cat(paste(ret, "\n", sep="")) } - return (TRUE) + return (history) +} + +xgb.cv.strip.numeric <- function(x) { + as.numeric(strsplit(regmatches(x, regexec("test-(.*):(.*)$", x))[[1]][3], "\\+")[[1]]) } From e64cb99f8998cdab0ea76cc9a2471a0c3a23c1b4 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:22:50 +0100 Subject: [PATCH 31/85] Missing parameter documentation Fix data documentation --- R-package/R/xgboost.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R-package/R/xgboost.R b/R-package/R/xgboost.R index 9bfd4108f..02a554f68 100644 --- a/R-package/R/xgboost.R +++ b/R-package/R/xgboost.R @@ -24,6 +24,8 @@ #' @param verbose If 0, xgboost will stay silent. If 1, xgboost will print #' information of performance. If 2, xgboost will print information of both #' performance and construction progress information +#' @param missing Missing is only used when input is dense matrix, pick a float +# value that represents missing value. Sometime a data use 0 or other extreme value to represents missing values. #' @param ... other parameters to pass to \code{params}. #' #' @details @@ -74,7 +76,7 @@ xgboost <- function(data = NULL, label = NULL, missing = NULL, params = list(), #' #' \itemize{ #' \item \code{label} the label for each record -#' \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 127 columns. +#' \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 126 columns. #' } #' #' @references @@ -101,7 +103,7 @@ NULL #' #' \itemize{ #' \item \code{label} the label for each record -#' \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 127 columns. +#' \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 126 columns. #' } #' #' @references @@ -116,5 +118,5 @@ NULL #' @name agaricus.test #' @usage data(agaricus.test) #' @format A list containing a label vector, and a dgCMatrix object with 1611 -#' rows and 127 variables +#' rows and 126 variables NULL From 2364e914bd6c938e8a92cd42337f1d60843a850d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:24:16 +0100 Subject: [PATCH 32/85] Documentation regenerated with fixes --- R-package/man/agaricus.test.Rd | 4 ++-- R-package/man/agaricus.train.Rd | 2 +- R-package/man/xgb.cv.Rd | 2 ++ R-package/man/xgboost.Rd | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/R-package/man/agaricus.test.Rd b/R-package/man/agaricus.test.Rd index 8fee4e2ee..556425379 100644 --- a/R-package/man/agaricus.test.Rd +++ b/R-package/man/agaricus.test.Rd @@ -5,7 +5,7 @@ \alias{agaricus.test} \title{Test part from Mushroom Data Set} \format{A list containing a label vector, and a dgCMatrix object with 1611 -rows and 127 variables} +rows and 126 variables} \usage{ data(agaricus.test) } @@ -18,7 +18,7 @@ This data set includes the following fields: \itemize{ \item \code{label} the label for each record - \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 127 columns. + \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 126 columns. } } \references{ diff --git a/R-package/man/agaricus.train.Rd b/R-package/man/agaricus.train.Rd index 02b55423a..879b3d5df 100644 --- a/R-package/man/agaricus.train.Rd +++ b/R-package/man/agaricus.train.Rd @@ -18,7 +18,7 @@ This data set includes the following fields: \itemize{ \item \code{label} the label for each record - \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 127 columns. + \item \code{data} a sparse Matrix of \code{dgCMatrix} class, with 126 columns. } } \references{ diff --git a/R-package/man/xgb.cv.Rd b/R-package/man/xgb.cv.Rd index b9c600c0e..271182625 100644 --- a/R-package/man/xgb.cv.Rd +++ b/R-package/man/xgb.cv.Rd @@ -32,6 +32,8 @@ xgb.cv(params = list(), data, nrounds, nfold, label = NULL, \item{label}{option field, when data is Matrix} +\item{missing}{Missing is only used when input is dense matrix, pick a float} + \item{showsd}{boolean, whether show standard deviation of cross validation} \item{metrics,}{list of evaluation metrics to be used in corss validation, diff --git a/R-package/man/xgboost.Rd b/R-package/man/xgboost.Rd index d85ee6d4a..21b1ad220 100644 --- a/R-package/man/xgboost.Rd +++ b/R-package/man/xgboost.Rd @@ -13,6 +13,8 @@ xgboost(data = NULL, label = NULL, missing = NULL, params = list(), \item{label}{the response variable. User should not set this field,} +\item{missing}{Missing is only used when input is dense matrix, pick a float} + \item{params}{the list of parameters. Commonly used ones are: \itemize{ \item \code{objective} objective function, common ones are From 8e74bcdd05c674dd4c9d20679b288eb53afa36a7 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:29:13 +0100 Subject: [PATCH 33/85] remove unneeded text... --- R-package/demo/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/R-package/demo/README.md b/R-package/demo/README.md index 5f2625b9f..667eb143d 100644 --- a/R-package/demo/README.md +++ b/R-package/demo/README.md @@ -10,11 +10,6 @@ XGBoost R Feature Walkthrough Benchmarks ==== * [Starter script for Kaggle Higgs Boson](../../demo/kaggle-higgs) - -R language Demo -==== -R demo code are contained in the [R package source code](https://github.com/pommedeterresautee/xgboost/tree/master/R-package/demo) as usually done in R. - Notes ==== From 7558a9450785d49307b7c15f36ef69326ec4f966 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 30 Dec 2014 16:38:56 +0100 Subject: [PATCH 34/85] Update wlkthrough R demo code to include variable importance. --- R-package/demo/basic_walkthrough.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R-package/demo/basic_walkthrough.R b/R-package/demo/basic_walkthrough.R index 59f5cd72e..7e6914b31 100644 --- a/R-package/demo/basic_walkthrough.R +++ b/R-package/demo/basic_walkthrough.R @@ -88,6 +88,9 @@ pred <- predict(bst, dtest) err <- as.numeric(sum(as.integer(pred > 0.5) != label))/length(label) print(paste("test-error=", err)) -# Finally, you can dump the tree you learned using xgb.dump into a text file -xgb.dump(bst, "dump.raw.txt") +# You can dump the tree you learned using xgb.dump into a text file +xgb.dump(bst, "dump.raw.txt", with.stats = T) +# Finally, you can check which features are the most important. +print("Most important features (look at column Gain):") +print(xgb.importance(feature_names = train$data@Dimnames[[2]], filename_dump = "dump.raw.txt")) From 79731f48b6f86dbd47c7c9958a637f99ded60593 Mon Sep 17 00:00:00 2001 From: Martial Hue Date: Tue, 30 Dec 2014 17:50:24 +0100 Subject: [PATCH 35/85] Fixed minor typos. --- demo/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/README.md b/demo/README.md index 1c6797304..c1f9a210c 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,8 +1,8 @@ XGBoost Examples ==== -This folder contains the all example codes using xgboost. +This folder contains all the code examples using xgboost. -* Contribution of exampls, benchmarks is more than welcomed! +* Contribution of examples, benchmarks is more than welcome! * If you like to share how you use xgboost to solve your problem, send a pull request:) Features Walkthrough @@ -12,7 +12,7 @@ This is a list of short codes introducing different functionalities of xgboost a [python](guide-python/basic_walkthrough.py) [R](../R-package/demo/basic_walkthrough.R) [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/basic_walkthrough.jl) -* Cutomize loss function, and evaluation metric +* Customize loss function, and evaluation metric [python](guide-python/custom_objective.py) [R](../R-package/demo/custom_objective.R) [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/custom_objective.jl) From 97fd9b47d4126a8ef4cf57583a20b3e51f4fd340 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 00:39:13 +0100 Subject: [PATCH 36/85] Add new demo --- R-package/demo/00Index | 1 + R-package/demo/README.md | 1 + R-package/demo/create_sparse_matrix.R | 65 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 R-package/demo/create_sparse_matrix.R diff --git a/R-package/demo/00Index b/R-package/demo/00Index index af7dfccd0..7954c3165 100644 --- a/R-package/demo/00Index +++ b/R-package/demo/00Index @@ -4,3 +4,4 @@ boost_from_prediction Boosting from existing prediction predict_first_ntree Predicting using first n trees generalized_linear_model Generalized Linear Model cross_validation Cross validation +create_sparse_matrix \ No newline at end of file diff --git a/R-package/demo/README.md b/R-package/demo/README.md index 667eb143d..e646cf0c9 100644 --- a/R-package/demo/README.md +++ b/R-package/demo/README.md @@ -6,6 +6,7 @@ XGBoost R Feature Walkthrough * [Predicting using first n trees](predict_first_ntree.R) * [Generalized Linear Model](generalized_linear_model.R) * [Cross validation](cross_validation.R) +* [Create a sparse matrix from a dense one](create_sparse_matrix.R) Benchmarks ==== diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R new file mode 100644 index 000000000..b697def97 --- /dev/null +++ b/R-package/demo/create_sparse_matrix.R @@ -0,0 +1,65 @@ +require(xgboost) +require(Matrix) +require(data.table) +require(vcd) #Available in Cran. Used for its dataset with categorical values. + +# According to its documentation, Xgboost works only on numbers. +# Sometimes the dataset we have to work on have categorical data. +# A categorical variable is one which have a fixed number of values. By exemple, if for each observation a variable called "Colour" can have only "red", "blue" or "green" as value, it is a categorical variable. +# +# In R, categorical variable is called Factor. +# Type ?factor in console for more information. +# +# In this demo we will see how to transform a dense dataframe with categorical variables to a sparse matrix before analyzing it in Xgboost. +# The method we are going to see is usually called "one hot encoding". + +#load Arthritis dataset in memory. +data(Arthritis) + +# create a copy of the dataset with data.table package (data.table is 100% compliant with R dataframe but its syntax is a lot more consistent and its performance are really good). +df <- data.table(Arthritis, keep.rownames = F) + +# Let's have a look to the data.table +cat("Print the dataset\n") +print(df) + +# 2 columns have factor type, one has ordinal type (ordinal variable is a categorical variable with values wich can be ordered, here: None > Some > Marked). +cat("Structure of the dataset\n") +str(df) + +# We remove the Age column which has no interest for the purpose of this demo. +df[,Age:= NULL] + +# List the different values for the column Treatment: Placebo, Treated. +cat("Values of the categorical feature Treatment\n") +print(levels(df[,Treatment])) + +# Next step, we will transform the categorical data to dummy variables. +# This method is also called dummy encoding. +# The purpose is to transform each value of each categorical feature in one binary feature. +# +# For example, the column Treatment will be replaced by two columns, Placebo, and Treated. Each of them will be binary, meaning that it will contain the value 1 in the new column Placebo and 0 in the new column Treated, for observations which had the value Placebo in column Treatment before the transformation. +# +# Formulae Improved~.-1 means transform all categorical features but column Improved to binary values. +# Column Improved is excluded because it will be our output column, the one we want to predict. +sparse_matrix = sparse.model.matrix(Improved~.-1, data = df) + +cat("Encoding of the sparse Matrix\n") +print(sparse_matrix) + +# Create the output vector (not sparse) +# 1. Set, for all rows, field in Y column to 0; +# 2. set Y to 1 when Improved == Marked; +# 3. Return Y column +output_vector = df[,Y:=0][Improved == "Marked",Y:=1][,Y] + +# Following is the same process as other demo +cat("Learning...\n") +bst <- xgboost(data = sparse_matrix, label = output_vector, max.depth = 3, + eta = 1, nround = 2,objective = "binary:logistic") +xgb.dump(bst, 'xgb.model.dump', with.stats = T) + +# sparse_matrix@Dimnames[[2]] represents the column names of the sparse matrix. +importance = xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') +print(importance) +# According to the matrix below, the most important feature in this dataset to predict if the treatment will work is having received a Placebo or not. From 006578e2e62bcd625dc397e239b242a0a348921a Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 00:46:12 +0100 Subject: [PATCH 37/85] fix demo index --- R-package/demo/00Index | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/demo/00Index b/R-package/demo/00Index index 7954c3165..345d7ca4f 100644 --- a/R-package/demo/00Index +++ b/R-package/demo/00Index @@ -4,4 +4,4 @@ boost_from_prediction Boosting from existing prediction predict_first_ntree Predicting using first n trees generalized_linear_model Generalized Linear Model cross_validation Cross validation -create_sparse_matrix \ No newline at end of file +create_sparse_matrix From c3d8f21df339564e5b43bb5f42c41d12c46d8eec Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 00:52:53 +0100 Subject: [PATCH 38/85] change assignation sign --- R-package/demo/create_sparse_matrix.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index b697def97..a333f3ac0 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -60,6 +60,6 @@ bst <- xgboost(data = sparse_matrix, label = output_vector, max.depth = 3, xgb.dump(bst, 'xgb.model.dump', with.stats = T) # sparse_matrix@Dimnames[[2]] represents the column names of the sparse matrix. -importance = xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') +importance <- xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') print(importance) # According to the matrix below, the most important feature in this dataset to predict if the treatment will work is having received a Placebo or not. From 4cc3790b76df514dc03a91ce46578a42fe02863a Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 10:36:10 +0100 Subject: [PATCH 39/85] Improve explanation, add new concepts. --- R-package/demo/create_sparse_matrix.R | 40 +++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index a333f3ac0..b89ab80fa 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -27,20 +27,28 @@ print(df) cat("Structure of the dataset\n") str(df) -# We remove the Age column which has no interest for the purpose of this demo. -df[,Age:= NULL] +# Let's add some new categorical features to see if it helps. Of course these feature are highly correlated to the Age feature. Usually it's not a good thing in ML, but Tree algorithms (including boosted trees) are able to select the best features, even in case of highly correlated features. + +# For the first feature we create groups of age by rounding the real age. Note that we transform it to factor (categorical data) so the algorithm treat them as independant values. +df[,AgeDiscret:= as.factor(round(Age/10,0))] + +# Here is an even stronger simplification of the real age with an arbitrary split at 30 years old. I choose this value based on nothing. We will see later if simplifying the information based on arbitrary values is a good strategy (I am sure you already have an idea of how well it will work!). +df[,AgeCat:= as.factor(ifelse(Age > 30, "Old", "Young"))] + +# We remove ID as there is nothing to learn from this feature (it will just add some noise as the dataset is small). +df[,ID:=NULL] # List the different values for the column Treatment: Placebo, Treated. cat("Values of the categorical feature Treatment\n") print(levels(df[,Treatment])) # Next step, we will transform the categorical data to dummy variables. -# This method is also called dummy encoding. +# This method is also called one hot encoding. # The purpose is to transform each value of each categorical feature in one binary feature. # -# For example, the column Treatment will be replaced by two columns, Placebo, and Treated. Each of them will be binary, meaning that it will contain the value 1 in the new column Placebo and 0 in the new column Treated, for observations which had the value Placebo in column Treatment before the transformation. +# Let's take, the column Treatment will be replaced by two columns, Placebo, and Treated. Each of them will be binary. For example an observation which had the value Placebo in column Treatment before the transformation will have, after the transformation, the value 1 in the new column Placebo and the value 0 in the new column Treated. # -# Formulae Improved~.-1 means transform all categorical features but column Improved to binary values. +# Formulae Improved~.-1 used below means transform all categorical features but column Improved to binary values. # Column Improved is excluded because it will be our output column, the one we want to predict. sparse_matrix = sparse.model.matrix(Improved~.-1, data = df) @@ -55,11 +63,27 @@ output_vector = df[,Y:=0][Improved == "Marked",Y:=1][,Y] # Following is the same process as other demo cat("Learning...\n") -bst <- xgboost(data = sparse_matrix, label = output_vector, max.depth = 3, - eta = 1, nround = 2,objective = "binary:logistic") +bst <- xgboost(data = sparse_matrix, label = output_vector, max.depth = 9, + eta = 1, nround = 10,objective = "binary:logistic") xgb.dump(bst, 'xgb.model.dump', with.stats = T) # sparse_matrix@Dimnames[[2]] represents the column names of the sparse matrix. importance <- xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') print(importance) -# According to the matrix below, the most important feature in this dataset to predict if the treatment will work is having received a Placebo or not. +# According to the matrix below, the most important feature in this dataset to predict if the treatment will work is the Age. The second most important feature is having received a placebo or not. The sex is third. Then we see our generated features (AgeDiscret). We can see that there contribution is very low. + +# Does these results make sense? +# Let's check some Chi2 between each of these features and the outcome. + +print(chisq.test(df$Age, df$Y)) +# Pearson correlation between Age and illness disapearing is 35 + +print(chisq.test(df$AgeDiscret, df$Y)) +# Our first simplification of Age gives a Pearson correlation of 8. + +print(chisq.test(df$AgeCat, df$Y)) +# The perfectly random split I did between young and old at 30 years old have a low correlation of 2. + +# As you can see, destroying information by simplying it won't improve your model. Chi2 just demonstrates that. +# It's even worse when you add some arbitrary rules. +# However, even if we have added some not useful new features highly correlated with other features, the boosting tree algorithm have been able to choose the best one, which in this case is the Age. \ No newline at end of file From 9998575c323a8d27eead69db14d1d0b9b76febe6 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 10:47:57 +0100 Subject: [PATCH 40/85] Small text improvement --- R-package/demo/create_sparse_matrix.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index b89ab80fa..0e8c5efb6 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -82,8 +82,8 @@ print(chisq.test(df$AgeDiscret, df$Y)) # Our first simplification of Age gives a Pearson correlation of 8. print(chisq.test(df$AgeCat, df$Y)) -# The perfectly random split I did between young and old at 30 years old have a low correlation of 2. +# The perfectly random split I did between young and old at 30 years old have a low correlation of 2. It's a result we may expect as may be in my mind > 30 years is being old (I am 32 and starting feeling old, this may explain that), but for the illness we are studying, the age to be vulnerable is not the same. Don't let your "gut" lower the quality of your model. In "data science", there is science :-) -# As you can see, destroying information by simplying it won't improve your model. Chi2 just demonstrates that. -# It's even worse when you add some arbitrary rules. -# However, even if we have added some not useful new features highly correlated with other features, the boosting tree algorithm have been able to choose the best one, which in this case is the Age. \ No newline at end of file +# As you can see, in general destroying information by simplying it won't improve your model. Chi2 just demonstrates that. But in more complex cases, creating a new feature which makes link with the outcome more obvious may help the algorithm and improve the model. The case studied here is not enough complex to show that. Check Kaggle forum for some challenging datasets. +# However it's almost always worse when you add some arbitrary rules. +# Moreover, you can notice that even if we have added some not useful new features highly correlated with other features, the boosting tree algorithm have been able to choose the best one, which in this case is the Age. Linear model may not be that strong in these scenario. \ No newline at end of file From 4f0ae53974082734463bc7b81f3ece60f4f85468 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 10:49:05 +0100 Subject: [PATCH 41/85] text change --- R-package/demo/create_sparse_matrix.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index 0e8c5efb6..cf0fcac4d 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -84,6 +84,6 @@ print(chisq.test(df$AgeDiscret, df$Y)) print(chisq.test(df$AgeCat, df$Y)) # The perfectly random split I did between young and old at 30 years old have a low correlation of 2. It's a result we may expect as may be in my mind > 30 years is being old (I am 32 and starting feeling old, this may explain that), but for the illness we are studying, the age to be vulnerable is not the same. Don't let your "gut" lower the quality of your model. In "data science", there is science :-) -# As you can see, in general destroying information by simplying it won't improve your model. Chi2 just demonstrates that. But in more complex cases, creating a new feature which makes link with the outcome more obvious may help the algorithm and improve the model. The case studied here is not enough complex to show that. Check Kaggle forum for some challenging datasets. +# As you can see, in general destroying information by simplying it won't improve your model. Chi2 just demonstrates that. But in more complex cases, creating a new feature based on existing one which makes link with the outcome more obvious may help the algorithm and improve the model. The case studied here is not enough complex to show that. Check Kaggle forum for some challenging datasets. # However it's almost always worse when you add some arbitrary rules. # Moreover, you can notice that even if we have added some not useful new features highly correlated with other features, the boosting tree algorithm have been able to choose the best one, which in this case is the Age. Linear model may not be that strong in these scenario. \ No newline at end of file From d07be2bb963fc66cf3a3816f0c3d0de8678c2374 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 31 Dec 2014 11:03:51 +0100 Subject: [PATCH 42/85] Username parameter is deprecated in install_function (see doc of the package for more information). --- R-package/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/README.md b/R-package/README.md index c0ca87195..ca65df9a3 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -6,7 +6,7 @@ For up-to-date version(which is recommended), please install from github. Window ```r require(devtools) -install_github('xgboost','tqchen',subdir='R-package') +install_github('tqchen/xgboost',subdir='R-package') ``` For stable version on CRAN, please run From 901904b5357750c6cebaf13f7eb9162bcdd01029 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 13:50:05 +0100 Subject: [PATCH 43/85] linear text dump model --- src/gbm/gblinear-inl.hpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/gbm/gblinear-inl.hpp b/src/gbm/gblinear-inl.hpp index 624f15c28..473914b6e 100644 --- a/src/gbm/gblinear-inl.hpp +++ b/src/gbm/gblinear-inl.hpp @@ -8,6 +8,7 @@ */ #include #include +#include #include #include "./gbm.h" #include "../tree/updater.h" @@ -134,11 +135,24 @@ class GBLinear : public IGradBooster { } } } - virtual std::vector DumpModel(const utils::FeatMap& fmap, int option) { - utils::Error("gblinear does not support dump model"); - return std::vector(); - } + virtual std::vector DumpModel(const utils::FeatMap& fmap, int option) { + std::stringstream fo(""); + fo << "bias:\n"; + for (int i = 0; i < model.param.num_output_group; ++i) { + fo << model.bias()[i] << std::endl; + } + fo << "weight:\n"; + for (int i = 0; i < model.param.num_output_group; ++i) { + for (int j = 0; j v; + v.push_back(fo.str()); + return v; + } + protected: inline void Pred(const RowBatch::Inst &inst, float *preds) { for (int gid = 0; gid < model.param.num_output_group; ++gid) { From 5e5500d6d3234f1d4ff8d61275df2afd7fbf894a Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 13:50:28 +0100 Subject: [PATCH 44/85] rewording --- R-package/demo/create_sparse_matrix.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index cf0fcac4d..4060d1c48 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -70,7 +70,7 @@ xgb.dump(bst, 'xgb.model.dump', with.stats = T) # sparse_matrix@Dimnames[[2]] represents the column names of the sparse matrix. importance <- xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') print(importance) -# According to the matrix below, the most important feature in this dataset to predict if the treatment will work is the Age. The second most important feature is having received a placebo or not. The sex is third. Then we see our generated features (AgeDiscret). We can see that there contribution is very low. +# According to the matrix below, the most important feature in this dataset to predict if the treatment will work is the Age. The second most important feature is having received a placebo or not. The sex is third. Then we see our generated features (AgeDiscret). We can see that their contribution is very low (Gain column). # Does these results make sense? # Let's check some Chi2 between each of these features and the outcome. From 34aaeff3d9199c2fcf1b281e818a39e0cf185825 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 14:57:48 +0100 Subject: [PATCH 45/85] small documentation change --- R-package/R/xgb.importance.R | 3 ++- R-package/man/xgb.importance.Rd | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index b2e60bed7..2071680d3 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -2,7 +2,6 @@ #' #' Read a xgboost model text dump. #' Can be tree or linear model (text dump of linear model are only supported in dev version of \code{Xgboost} for now). -#' Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' #' @importFrom data.table data.table #' @importFrom magrittr %>% @@ -11,6 +10,8 @@ #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). #' +#' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +#' #' @details #' This is the function to understand the model trained (and through your model, your data). #' diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index 883819993..a7a71cefc 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -11,10 +11,12 @@ xgb.importance(feature_names = NULL, filename_dump = NULL) \item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} } +\value{ +A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +} \description{ Read a xgboost model text dump. Can be tree or linear model (text dump of linear model are only supported in dev version of \code{Xgboost} for now). -Return a data.table of the features used in the model with their average gain (and their weight for boosted tree model) in the model. } \details{ This is the function to understand the model trained (and through your model, your data). From a524a51a06f979e5ea4d6a14e5c6368c88549dd0 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 16:05:43 +0100 Subject: [PATCH 46/85] return history as data.table for cross validation + documentation --- R-package/NAMESPACE | 1 + R-package/R/xgb.cv.R | 23 ++++++++++++++++++----- R-package/man/xgb.cv.Rd | 3 +++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 1714d2044..7e0bfa8ac 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -18,5 +18,6 @@ importClassesFrom(Matrix,dgCMatrix) importClassesFrom(Matrix,dgeMatrix) importFrom(data.table,":=") importFrom(data.table,data.table) +importFrom(data.table,rbindlist) importFrom(magrittr,"%>%") importFrom(stringr,str_extract) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index 02870b772..3a9fd9b86 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -1,7 +1,12 @@ #' Cross Validation #' #' The cross valudation function of xgboost -#' +#' +#' @importFrom data.table data.table +#' @importFrom magrittr %>% +#' @importFrom data.table := +#' @importFrom data.table rbindlist +#' @importFrom stringr str_extract #' @param params the list of parameters. Commonly used ones are: #' \itemize{ #' \item \code{objective} objective function, common ones are @@ -40,6 +45,8 @@ # value that represents missing value. Sometime a data use 0 or other extreme value to represents missing values. #' @param ... other parameters to pass to \code{params}. #' +#' @return a \code{data.table} with each mean and standard deviation stat for training set and test set. +#' #' @details #' This is the cross validation function for xgboost #' @@ -88,9 +95,15 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = history <- c(history, ret) cat(paste(ret, "\n", sep="")) } - return (history) + + dt <- data.table(train_rmse_mean=numeric(), train_rmse_std=numeric(), train_auc_mean=numeric(), train_auc_std=numeric(), test_rmse_mean=numeric(), test_rmse_std=numeric(), test_auc_mean=numeric(), test_auc_std=numeric()) + + split = str_split(string = history, pattern = "\t") + for(line in split){ + dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d.\\d*") %>% unlist %>% as.list %>% {vec <- .;rbindlist(list(dt, vec), use.names = F, fill = F)} + } + dt } -xgb.cv.strip.numeric <- function(x) { - as.numeric(strsplit(regmatches(x, regexec("test-(.*):(.*)$", x))[[1]][3], "\\+")[[1]]) -} + + diff --git a/R-package/man/xgb.cv.Rd b/R-package/man/xgb.cv.Rd index 271182625..19f04ee79 100644 --- a/R-package/man/xgb.cv.Rd +++ b/R-package/man/xgb.cv.Rd @@ -56,6 +56,9 @@ prediction and dtrain,} \item{...}{other parameters to pass to \code{params}.} } +\value{ +a \code{data.table} with each mean and standard deviation stat for training set and test set. +} \description{ The cross valudation function of xgboost } From 8bbe45eed26f3a8fd87b574dd7c5eb4fce6c3fc1 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 16:09:03 +0100 Subject: [PATCH 47/85] fix some missing imports --- R-package/NAMESPACE | 2 ++ R-package/R/xgb.cv.R | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 7e0bfa8ac..5c9e19932 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -21,3 +21,5 @@ importFrom(data.table,data.table) importFrom(data.table,rbindlist) importFrom(magrittr,"%>%") importFrom(stringr,str_extract) +importFrom(stringr,str_extract_all) +importFrom(stringr,str_split) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index 3a9fd9b86..a5be567bc 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -6,7 +6,9 @@ #' @importFrom magrittr %>% #' @importFrom data.table := #' @importFrom data.table rbindlist -#' @importFrom stringr str_extract +#' @importFrom stringr str_extract_all +#' @importFrom stringr str_split +#' #' @param params the list of parameters. Commonly used ones are: #' \itemize{ #' \item \code{objective} objective function, common ones are From 4d0d65837d14b5b688a1e88509df2129513a5b91 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 1 Jan 2015 22:43:23 +0100 Subject: [PATCH 48/85] parse history first line to guess which columns are required --- R-package/NAMESPACE | 4 ++++ R-package/R/xgb.cv.R | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 5c9e19932..bd12fc7ec 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -17,9 +17,13 @@ import(methods) importClassesFrom(Matrix,dgCMatrix) importClassesFrom(Matrix,dgeMatrix) importFrom(data.table,":=") +importFrom(data.table,as.data.table) importFrom(data.table,data.table) importFrom(data.table,rbindlist) importFrom(magrittr,"%>%") importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) +importFrom(stringr,str_match) +importFrom(stringr,str_replace) +importFrom(stringr,str_replace_all) importFrom(stringr,str_split) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index a5be567bc..c2e73e202 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -3,11 +3,15 @@ #' The cross valudation function of xgboost #' #' @importFrom data.table data.table +#' @importFrom data.table as.data.table #' @importFrom magrittr %>% #' @importFrom data.table := #' @importFrom data.table rbindlist #' @importFrom stringr str_extract_all #' @importFrom stringr str_split +#' @importFrom stringr str_replace_all +#' @importFrom stringr str_replace +#' @importFrom stringr str_match #' #' @param params the list of parameters. Commonly used ones are: #' \itemize{ @@ -98,14 +102,20 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = cat(paste(ret, "\n", sep="")) } - dt <- data.table(train_rmse_mean=numeric(), train_rmse_std=numeric(), train_auc_mean=numeric(), train_auc_std=numeric(), test_rmse_mean=numeric(), test_rmse_std=numeric(), test_auc_mean=numeric(), test_auc_std=numeric()) + colnames <- str_split(string = history[1], pattern = "\t")[[1]] %>% .[2:length(.)] %>% str_extract(".*:") %>% str_replace(":","") %>% str_replace_all("-", ".") + + colnamesMean <- paste(colnames, "mean") + colnamesStd <- paste(colnames, "std") + colnames <- c() + for(i in 1:length(colnamesMean)) colnames <- c(colnames, colnamesMean[i], colnamesStd[i]) + + type <- rep(x = "numeric", times = length(colnames)) + + dt <- read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table split = str_split(string = history, pattern = "\t") for(line in split){ dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d.\\d*") %>% unlist %>% as.list %>% {vec <- .;rbindlist(list(dt, vec), use.names = F, fill = F)} } dt -} - - - +} \ No newline at end of file From cdea1685e581c081b72a7107bb6fb899fa6c5c2f Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 2 Jan 2015 11:21:53 +0100 Subject: [PATCH 49/85] Add a new verbose parameter to print progress during the process (set to true by default to not change behavior of existing code) + source code refactoring --- R-package/NAMESPACE | 1 - R-package/R/xgb.cv.R | 26 ++++++++++++-------------- R-package/man/xgb.cv.Rd | 9 ++++++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index bd12fc7ec..6e74d9ac2 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -25,5 +25,4 @@ importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_match) importFrom(stringr,str_replace) -importFrom(stringr,str_replace_all) importFrom(stringr,str_split) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index c2e73e202..7256980c6 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -8,8 +8,8 @@ #' @importFrom data.table := #' @importFrom data.table rbindlist #' @importFrom stringr str_extract_all +#' @importFrom stringr str_extract #' @importFrom stringr str_split -#' @importFrom stringr str_replace_all #' @importFrom stringr str_replace #' @importFrom stringr str_match #' @@ -31,7 +31,7 @@ #' @param nrounds the max number of iterations #' @param nfold number of folds used #' @param label option field, when data is Matrix -#' @param showsd boolean, whether show standard deviation of cross validation +#' @param showsd \code{boolean}, whether show standard deviation of cross validation #' @param metrics, list of evaluation metrics to be used in corss validation, #' when it is not specified, the evaluation metric is chosen according to objective function. #' Possible options are: @@ -49,9 +49,10 @@ #' prediction and dtrain, #' @param missing Missing is only used when input is dense matrix, pick a float # value that represents missing value. Sometime a data use 0 or other extreme value to represents missing values. +#' @param verbose \code{boolean}, print the statistics during the process. #' @param ... other parameters to pass to \code{params}. #' -#' @return a \code{data.table} with each mean and standard deviation stat for training set and test set. +#' @return A \code{data.table} with each mean and standard deviation stat for training set and test set. #' #' @details #' This is the cross validation function for xgboost @@ -66,10 +67,11 @@ #' dtrain <- xgb.DMatrix(agaricus.train$data, label = agaricus.train$label) #' history <- xgb.cv(data = dtrain, nround=3, nfold = 5, metrics=list("rmse","auc"), #' "max.depth"=3, "eta"=1, "objective"="binary:logistic") +#' print(history) #' @export #' xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = NULL, - showsd = TRUE, metrics=list(), obj = NULL, feval = NULL, ...) { + showsd = TRUE, metrics=list(), obj = NULL, feval = NULL, verbose = T,...) { if (typeof(params) != "list") { stop("xgb.cv: first argument params must be list") } @@ -94,28 +96,24 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = for (k in 1:nfold) { fd <- folds[[k]] succ <- xgb.iter.update(fd$booster, fd$dtrain, i - 1, obj) - msg[[k]] <- strsplit(xgb.iter.eval(fd$booster, fd$watchlist, i - 1, feval), - "\t")[[1]] + msg[[k]] <- xgb.iter.eval(fd$booster, fd$watchlist, i - 1, feval) %>% str_split("\t") %>% .[[1]] } ret <- xgb.cv.aggcv(msg, showsd) history <- c(history, ret) - cat(paste(ret, "\n", sep="")) + if(verbose) paste(ret, "\n", sep="") %>% cat } - colnames <- str_split(string = history[1], pattern = "\t")[[1]] %>% .[2:length(.)] %>% str_extract(".*:") %>% str_replace(":","") %>% str_replace_all("-", ".") - + colnames <- str_split(string = history[1], pattern = "\t")[[1]] %>% .[2:length(.)] %>% str_extract(".*:") %>% str_replace(":","") %>% str_replace("-", ".") colnamesMean <- paste(colnames, "mean") colnamesStd <- paste(colnames, "std") + colnames <- c() for(i in 1:length(colnamesMean)) colnames <- c(colnames, colnamesMean[i], colnamesStd[i]) type <- rep(x = "numeric", times = length(colnames)) - dt <- read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table - split = str_split(string = history, pattern = "\t") - for(line in split){ - dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d.\\d*") %>% unlist %>% as.list %>% {vec <- .;rbindlist(list(dt, vec), use.names = F, fill = F)} - } + + for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d.\\d*") %>% unlist %>% as.list %>% {vec <- .; rbindlist(list(dt, vec), use.names = F, fill = F)} dt } \ No newline at end of file diff --git a/R-package/man/xgb.cv.Rd b/R-package/man/xgb.cv.Rd index 19f04ee79..7ba5eb727 100644 --- a/R-package/man/xgb.cv.Rd +++ b/R-package/man/xgb.cv.Rd @@ -6,7 +6,7 @@ \usage{ xgb.cv(params = list(), data, nrounds, nfold, label = NULL, missing = NULL, showsd = TRUE, metrics = list(), obj = NULL, - feval = NULL, ...) + feval = NULL, verbose = T, ...) } \arguments{ \item{params}{the list of parameters. Commonly used ones are: @@ -34,7 +34,7 @@ xgb.cv(params = list(), data, nrounds, nfold, label = NULL, \item{missing}{Missing is only used when input is dense matrix, pick a float} -\item{showsd}{boolean, whether show standard deviation of cross validation} +\item{showsd}{\code{boolean}, whether show standard deviation of cross validation} \item{metrics,}{list of evaluation metrics to be used in corss validation, when it is not specified, the evaluation metric is chosen according to objective function. @@ -54,10 +54,12 @@ gradient with given prediction and dtrain,} \code{list(metric='metric-name', value='metric-value')} with given prediction and dtrain,} +\item{verbose}{\code{boolean}, print the statistics during the process.} + \item{...}{other parameters to pass to \code{params}.} } \value{ -a \code{data.table} with each mean and standard deviation stat for training set and test set. +A \code{data.table} with each mean and standard deviation stat for training set and test set. } \description{ The cross valudation function of xgboost @@ -75,5 +77,6 @@ data(agaricus.train, package='xgboost') dtrain <- xgb.DMatrix(agaricus.train$data, label = agaricus.train$label) history <- xgb.cv(data = dtrain, nround=3, nfold = 5, metrics=list("rmse","auc"), "max.depth"=3, "eta"=1, "objective"="binary:logistic") +print(history) } From cfe5015e5485eaabc6877087f57d9a88d10edaa4 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 11:21:03 +0100 Subject: [PATCH 50/85] small fix in parsing --- R-package/R/xgb.cv.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index 7256980c6..eb14b2e1e 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -114,6 +114,6 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = dt <- read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table split = str_split(string = history, pattern = "\t") - for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d.\\d*") %>% unlist %>% as.list %>% {vec <- .; rbindlist(list(dt, vec), use.names = F, fill = F)} + for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d*\\.*\\d*") %>% unlist %>% as.list %>% {vec <- .; rbindlist(list(dt, vec), use.names = F, fill = F)} dt } \ No newline at end of file From 8b45ef07ca9b2f1b593b8f93279fe20f15f283d8 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 11:21:39 +0100 Subject: [PATCH 51/85] build data.table from raw model data --- R-package/R/xgb.plot.tree.R | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 R-package/R/xgb.plot.tree.R diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R new file mode 100644 index 000000000..3f60d598a --- /dev/null +++ b/R-package/R/xgb.plot.tree.R @@ -0,0 +1,24 @@ +require(DiagrammeR) +require(stringr) +require(data.table) +require(magrittr) +text <- readLines('xgb.model.dump') %>% str_trim(side = "both") +position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) + +extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2]) %>% unlist %>% as.numeric + +#for(i in 1:(length(position)-1)){ +i=1 + cat(paste("\n",i,"\n")) + tree <- text[(position[i]+1):(position[i+1]-1)] + paste(tree, collapse = "\n") %>% cat +branch <- str_match(tree, "leaf") %>% is.na %>% tree[.] +id <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% as.numeric +feature <- str_extract(branch, "\\[.*\\]") +yes <- extract(branch, "yes=\\d*") +no <- extract(branch, "no=\\d*") +missing <- extract(branch, "missing=\\d+") +gain <- extract(branch, "gain=\\d*\\.*\\d*") +cover <- extract(branch, "cover=\\d*\\.*\\d*") +dt <- data.table(ID = id, Feature = feature, Yes = yes, No = no, Missing = missing, Gain = gain, Cover = cover) +#} From 2925236fab537fd41074f0c90aa1968bcf8706d0 Mon Sep 17 00:00:00 2001 From: tqchen Date: Sun, 4 Jan 2015 02:35:24 -0800 Subject: [PATCH 52/85] change dump stats --- src/tree/model.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tree/model.h b/src/tree/model.h index 8049a1608..aa9ad2794 100644 --- a/src/tree/model.h +++ b/src/tree/model.h @@ -437,7 +437,6 @@ class TreeModel { << ",missing=" << nodes[nid].cdefault(); } if (with_stats) { - fo << ' '; stat(nid).Print(fo, false); } fo << '\n'; @@ -460,9 +459,9 @@ struct RTreeNodeStat { /*! \brief print information of current stats to fo */ inline void Print(std::stringstream &fo, bool is_leaf) const { if (!is_leaf) { - fo << "gain=" << loss_chg << ",cover=" << sum_hess; + fo << ",gain=" << loss_chg << ",cover=" << sum_hess; } else { - fo << "cover=" << sum_hess; + fo << ",cover=" << sum_hess; } } }; From 33bb1685742b40e7b34e4408a6228f88a88a6c81 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 17:23:53 +0100 Subject: [PATCH 53/85] basis to plot --- R-package/R/xgb.plot.tree.R | 43 ++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 3f60d598a..1a6791055 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -5,20 +5,43 @@ require(magrittr) text <- readLines('xgb.model.dump') %>% str_trim(side = "both") position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) -extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2]) %>% unlist %>% as.numeric +extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist #for(i in 1:(length(position)-1)){ i=1 cat(paste("\n",i,"\n")) tree <- text[(position[i]+1):(position[i+1]-1)] paste(tree, collapse = "\n") %>% cat -branch <- str_match(tree, "leaf") %>% is.na %>% tree[.] -id <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% as.numeric -feature <- str_extract(branch, "\\[.*\\]") -yes <- extract(branch, "yes=\\d*") -no <- extract(branch, "no=\\d*") -missing <- extract(branch, "missing=\\d+") -gain <- extract(branch, "gain=\\d*\\.*\\d*") -cover <- extract(branch, "cover=\\d*\\.*\\d*") -dt <- data.table(ID = id, Feature = feature, Yes = yes, No = no, Missing = missing, Gain = gain, Cover = cover) +notLeaf <- str_match(tree, "leaf") %>% is.na +leaf <- notLeaf %>% not %>% tree[.] +branch <- notLeaf %>% tree[.] +idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% as.numeric +idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% as.numeric +featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") #%>% as.numeric +featureLeaf <- rep("Leaf", length(leaf)) +yesBranch <- extract(branch, "yes=\\d*") +yesLeaf <- rep(NA, length(leaf)) +noBranch <- extract(branch, "no=\\d*") +noLeaf <- rep(NA, length(leaf)) +missingBranch <- extract(branch, "missing=\\d+") +missingLeaf <- rep(NA, length(leaf)) +qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") +qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") +coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") +coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") +dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + +set(dt, j = "YesFeature", value = ifelse(is.na(dt[,Yes]),NA,dt[ID == dt[,Yes], ID])) +set(dt, j = "NoFeature", value = ifelse(is.na(dt[,No]),NA,dt[ID == dt[,No], ID])) +dtBranch <- dt[Feature!="Leaf"] + +yesPath <- paste(dtBranch[,ID], "-->", dtBranch[,Yes], sep = "") +noPath <- paste(dtBranch[,ID], "-->", dtBranch[,No], sep = "") +missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") +yesPathStyle <- paste("style ", dtBranch[,Yes], " fill:#A2EB86, stroke:#04C4AB, stroke-width:2px", sep = "") +noPathStyle <- paste("style ", dtBranch[,No], " fill:#FFA070, stroke:#FF5E5E, stroke-width:2px", sep = "") + +path <- c(yesPath, noPath, yesPathStyle, noPathStyle) %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "",sep = ";") + +DiagrammeR(path, height = 400) #} From f6290ad792e4f398ab721e330d9d9f219166da43 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 21:56:41 +0100 Subject: [PATCH 54/85] plot all trees --- R-package/R/xgb.plot.tree.R | 54 ++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 1a6791055..35579b9cd 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -7,41 +7,51 @@ position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length( extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist -#for(i in 1:(length(position)-1)){ -i=1 - cat(paste("\n",i,"\n")) +addTreeId <- function(x, i) paste(i,x,sep = "-") + +allTrees <- data.table() + +for(i in 1:(length(position)-1)){ + tree <- text[(position[i]+1):(position[i+1]-1)] - paste(tree, collapse = "\n") %>% cat + notLeaf <- str_match(tree, "leaf") %>% is.na leaf <- notLeaf %>% not %>% tree[.] branch <- notLeaf %>% tree[.] -idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% as.numeric -idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% as.numeric -featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") #%>% as.numeric +idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) +idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) +featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric featureLeaf <- rep("Leaf", length(leaf)) -yesBranch <- extract(branch, "yes=\\d*") -yesLeaf <- rep(NA, length(leaf)) -noBranch <- extract(branch, "no=\\d*") +splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") +splitLeaf <- rep(NA, length(leaf)) +yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) +yesLeaf <- rep(NA, length(leaf)) +noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) noLeaf <- rep(NA, length(leaf)) -missingBranch <- extract(branch, "missing=\\d+") +missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) missingLeaf <- rep(NA, length(leaf)) qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") -dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] +dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] -set(dt, j = "YesFeature", value = ifelse(is.na(dt[,Yes]),NA,dt[ID == dt[,Yes], ID])) -set(dt, j = "NoFeature", value = ifelse(is.na(dt[,No]),NA,dt[ID == dt[,No], ID])) -dtBranch <- dt[Feature!="Leaf"] +set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) +set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) -yesPath <- paste(dtBranch[,ID], "-->", dtBranch[,Yes], sep = "") -noPath <- paste(dtBranch[,ID], "-->", dtBranch[,No], sep = "") -missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") -yesPathStyle <- paste("style ", dtBranch[,Yes], " fill:#A2EB86, stroke:#04C4AB, stroke-width:2px", sep = "") -noPathStyle <- paste("style ", dtBranch[,No], " fill:#FFA070, stroke:#FF5E5E, stroke-width:2px", sep = "") +dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] -path <- c(yesPath, noPath, yesPathStyle, noPathStyle) %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "",sep = ";") +dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|> ", Split, "|", No, "[", NoFeature, "]", sep = "")] -DiagrammeR(path, height = 400) +#missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") + +dt[Feature!="Leaf", yesPathStyle := paste("style ", Yes, " fill:#A2EB86, stroke:#04C4AB, stroke-width:2px", sep = "")] + +dt[Feature!="Leaf", noPathStyle := paste("style ", No, " fill:#FFA070, stroke:#FF5E5E, stroke-width:2px", sep = "")] + +allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) +} +path <- dt[Feature!="Leaf", c(yesPath, noPath, yesPathStyle, noPathStyle)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "",sep = ";") + +DiagrammeR(path, height =700) #} From ffbd78fce4de0e5088ec86a9490af7c137ef1b22 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 22:40:31 +0100 Subject: [PATCH 55/85] use style CSS class instead of q style per item --- R-package/R/xgb.plot.tree.R | 79 +++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 35579b9cd..788154afb 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -12,46 +12,49 @@ addTreeId <- function(x, i) paste(i,x,sep = "-") allTrees <- data.table() for(i in 1:(length(position)-1)){ - + tree <- text[(position[i]+1):(position[i+1]-1)] - -notLeaf <- str_match(tree, "leaf") %>% is.na -leaf <- notLeaf %>% not %>% tree[.] -branch <- notLeaf %>% tree[.] -idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) -idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) -featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric -featureLeaf <- rep("Leaf", length(leaf)) -splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") -splitLeaf <- rep(NA, length(leaf)) -yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) -yesLeaf <- rep(NA, length(leaf)) -noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) -noLeaf <- rep(NA, length(leaf)) -missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) -missingLeaf <- rep(NA, length(leaf)) -qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") -qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") -coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") -coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") -dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] - -set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) -set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) - -dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] - -dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|> ", Split, "|", No, "[", NoFeature, "]", sep = "")] - -#missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") - -dt[Feature!="Leaf", yesPathStyle := paste("style ", Yes, " fill:#A2EB86, stroke:#04C4AB, stroke-width:2px", sep = "")] - -dt[Feature!="Leaf", noPathStyle := paste("style ", No, " fill:#FFA070, stroke:#FF5E5E, stroke-width:2px", sep = "")] - -allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) + + notLeaf <- str_match(tree, "leaf") %>% is.na + leaf <- notLeaf %>% not %>% tree[.] + branch <- notLeaf %>% tree[.] + idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric + featureLeaf <- rep("Leaf", length(leaf)) + splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") + splitLeaf <- rep(NA, length(leaf)) + yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) + yesLeaf <- rep(NA, length(leaf)) + noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) + noLeaf <- rep(NA, length(leaf)) + missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) + missingLeaf <- rep(NA, length(leaf)) + qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") + qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") + coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") + coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") + dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) + + dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] + + dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|> ", Split, "|", No, "[", NoFeature, "]", sep = "")] + + #missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") + + allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } -path <- dt[Feature!="Leaf", c(yesPath, noPath, yesPathStyle, noPathStyle)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "",sep = ";") + +styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px;" + +yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode;", sep = "") + +no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode;", sep = "") + +path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(";", styles, yes, no, collapse = ";", sep = "") DiagrammeR(path, height =700) #} From b9799c6ac489687761ae76655ea39183c0018c74 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 4 Jan 2015 22:42:17 +0100 Subject: [PATCH 56/85] refactor plot function --- R-package/R/xgb.plot.tree.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 788154afb..4031b6055 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -48,13 +48,13 @@ for(i in 1:(length(position)-1)){ allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } -styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px;" +styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" -yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode;", sep = "") +yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode", sep = "") -no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode;", sep = "") +no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode", sep = "") -path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(";", styles, yes, no, collapse = ";", sep = "") +path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(styles, yes, no, sep = ";") DiagrammeR(path, height =700) #} From 3d068b4e1ade70768f223d5993835932cd1091bb Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 5 Jan 2015 19:26:09 +0100 Subject: [PATCH 57/85] new documentation new import --- R-package/NAMESPACE | 4 ++++ R-package/man/xgb.plot.tree.Rd | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 R-package/man/xgb.plot.tree.Rd diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 6e74d9ac2..a20057e25 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -9,6 +9,7 @@ export(xgb.cv) export(xgb.dump) export(xgb.importance) export(xgb.load) +export(xgb.plot.tree) export(xgb.save) export(xgb.train) export(xgboost) @@ -16,13 +17,16 @@ exportMethods(predict) import(methods) importClassesFrom(Matrix,dgCMatrix) importClassesFrom(Matrix,dgeMatrix) +importFrom(DiagrammeR,DiagrammeR) importFrom(data.table,":=") importFrom(data.table,as.data.table) importFrom(data.table,data.table) importFrom(data.table,rbindlist) +importFrom(data.table,set) importFrom(magrittr,"%>%") importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_match) importFrom(stringr,str_replace) importFrom(stringr,str_split) +importFrom(stringr,str_trim) diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd new file mode 100644 index 000000000..08f8b9c94 --- /dev/null +++ b/R-package/man/xgb.plot.tree.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.plot.tree.R +\name{xgb.plot.tree} +\alias{xgb.plot.tree} +\title{Plot a boosted tree model} +\usage{ +xgb.plot.tree(feature_names = NULL, filename_dump = NULL) +} +\arguments{ +\item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} + +\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} +} +\value{ +A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +} +\description{ +Read a xgboost model text dump. +Only works for boosted tree model (not linear model). +} +\details{ +This is the function to plot the trees growned. +It uses Mermaid JS library for that purpose. +Performance can be low for huge models. +} +\examples{ +data(agaricus.train, package='xgboost') + +#Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). +#Each column of the sparse Matrix is a feature in one hot encoding format. +train <- agaricus.train + +bst <- xgboost(data = train$data, label = train$label, max.depth = 2, + eta = 1, nround = 2,objective = "binary:logistic") +xgb.dump(bst, 'xgb.model.dump', with.stats = T) + +#agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. +xgb.plot.tree(agaricus.train$data@Dimnames[[2]], 'xgb.model.dump') +} + From f793df671bdbb35b9a17a2a9a7c1b3a7018b6537 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 5 Jan 2015 19:26:26 +0100 Subject: [PATCH 58/85] Change code to look like a function --- R-package/R/xgb.plot.tree.R | 153 ++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 52 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 4031b6055..7770bff1a 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -1,60 +1,109 @@ -require(DiagrammeR) -require(stringr) -require(data.table) -require(magrittr) -text <- readLines('xgb.model.dump') %>% str_trim(side = "both") -position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) - -extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist - -addTreeId <- function(x, i) paste(i,x,sep = "-") - -allTrees <- data.table() - -for(i in 1:(length(position)-1)){ +#' Plot a boosted tree model +#' +#' Read a xgboost model text dump. +#' Only works for boosted tree model (not linear model). +#' +#' @importFrom data.table data.table +#' @importFrom data.table set +#' @importFrom data.table rbindlist +#' @importFrom magrittr %>% +#' @importFrom data.table := +#' @importFrom stringr str_extract +#' @importFrom stringr str_split +#' @importFrom stringr str_extract +#' @importFrom stringr str_trim +#' @importFrom DiagrammeR DiagrammeR +#' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. +#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). +#' +#' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +#' +#' @details +#' This is the function to plot the trees growned. +#' It uses Mermaid JS library for that purpose. +#' Performance can be low for huge models. +#' +#' +#' @examples +#' data(agaricus.train, package='xgboost') +#' +#' #Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). +#' #Each column of the sparse Matrix is a feature in one hot encoding format. +#' train <- agaricus.train +#' +#' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, +#' eta = 1, nround = 2,objective = "binary:logistic") +#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) +#' +#' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. +#' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') +#' +#' @export +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL){ - tree <- text[(position[i]+1):(position[i+1]-1)] + if (!class(feature_names) %in% c("character", "NULL")) { + stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") + } + if (class(filename_dump) != "character" & file.exists(filename_dump)) { + stop("filename_dump: Has to be a path to the model dump file.") + } - notLeaf <- str_match(tree, "leaf") %>% is.na - leaf <- notLeaf %>% not %>% tree[.] - branch <- notLeaf %>% tree[.] - idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) - idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) - featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric - featureLeaf <- rep("Leaf", length(leaf)) - splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") - splitLeaf <- rep(NA, length(leaf)) - yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) - yesLeaf <- rep(NA, length(leaf)) - noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) - noLeaf <- rep(NA, length(leaf)) - missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) - missingLeaf <- rep(NA, length(leaf)) - qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") - qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") - coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") - coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") - dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + text <- readLines(filename_dump) %>% str_trim(side = "both") + position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) + extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist - dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] + addTreeId <- function(x, i) paste(i,x,sep = "-") - dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|> ", Split, "|", No, "[", NoFeature, "]", sep = "")] + allTrees <- data.table() - #missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") + for(i in 1:(length(position)-1)){ + + tree <- text[(position[i]+1):(position[i+1]-1)] + + notLeaf <- str_match(tree, "leaf") %>% is.na + leaf <- notLeaf %>% not %>% tree[.] + branch <- notLeaf %>% tree[.] + idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric + if(!is.null(feature_names)){ + featureBranch <- feature_names[featureBranch + 1] + } + featureLeaf <- rep("Leaf", length(leaf)) + splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") + splitLeaf <- rep(NA, length(leaf)) + yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) + yesLeaf <- rep(NA, length(leaf)) + noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) + noLeaf <- rep(NA, length(leaf)) + missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) + missingLeaf <- rep(NA, length(leaf)) + qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") + qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") + coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") + coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") + dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) + + dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] + + dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|>= ", Split, "|", No, "[", NoFeature, "]", sep = "")] + + #missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") + + allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) + } - allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) + styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" + + yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode", sep = "") + + no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode", sep = "") + + path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(styles, yes, no, sep = ";") + + DiagrammeR(path) } - -styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" - -yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode", sep = "") - -no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode", sep = "") - -path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(styles, yes, no, sep = ";") - -DiagrammeR(path, height =700) -#} From c64bfad5bbcd5d8ea32fb572ab530379c6b9e00d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Mon, 5 Jan 2015 19:35:33 +0100 Subject: [PATCH 59/85] fix import issue --- R-package/NAMESPACE | 2 ++ R-package/R/xgb.plot.tree.R | 2 ++ 2 files changed, 4 insertions(+) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index a20057e25..f2baee12b 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -24,6 +24,8 @@ importFrom(data.table,data.table) importFrom(data.table,rbindlist) importFrom(data.table,set) importFrom(magrittr,"%>%") +importFrom(magrittr,add) +importFrom(magrittr,not) importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_match) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 7770bff1a..7eb267298 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -7,6 +7,8 @@ #' @importFrom data.table set #' @importFrom data.table rbindlist #' @importFrom magrittr %>% +#' @importFrom magrittr not +#' @importFrom magrittr add #' @importFrom data.table := #' @importFrom stringr str_extract #' @importFrom stringr str_split From a6c588f90d1e115518cac9492e35068d62ebea6d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 6 Jan 2015 13:59:14 +0100 Subject: [PATCH 60/85] fix arg check --- R-package/R/xgb.importance.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 2071680d3..d5860e8a4 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -47,7 +47,7 @@ xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } - if (class(filename_dump) != "character" & file.exists(filename_dump)) { + if (class(filename_dump) != "character" || !file.exists(filename_dump)) { stop("filename_dump: Has to be a path to the model dump file.") } text <- readLines(filename_dump) From 94d070da601379a41d32e10ddb37c9c8e1e42bdc Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 6 Jan 2015 13:59:29 +0100 Subject: [PATCH 61/85] add limit number of trees option --- R-package/R/xgb.plot.tree.R | 12 +++++++++--- R-package/man/xgb.plot.tree.Rd | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 7eb267298..4863fd7ca 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -17,6 +17,7 @@ #' @importFrom DiagrammeR DiagrammeR #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). +#' @param n_first_tree limit the plot to the n first trees. #' #' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' @@ -41,25 +42,30 @@ #' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') #' #' @export -xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL){ +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } - if (class(filename_dump) != "character" & file.exists(filename_dump)) { + if (class(filename_dump) != "character" || !file.exists(filename_dump)) { stop("filename_dump: Has to be a path to the model dump file.") } + if (!class(n_first_tree) %in% c("numeric", "NULL") | length(n_first_tree) > 1) { + stop("n_first_tree: Has to be a numeric vector of size 1.") + } text <- readLines(filename_dump) %>% str_trim(side = "both") position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist + n_round <- min(length(position) - 1, n_first_tree) + addTreeId <- function(x, i) paste(i,x,sep = "-") allTrees <- data.table() - for(i in 1:(length(position)-1)){ + for(i in 1:n_round){ tree <- text[(position[i]+1):(position[i+1]-1)] diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index 08f8b9c94..eeec2f111 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -4,12 +4,15 @@ \alias{xgb.plot.tree} \title{Plot a boosted tree model} \usage{ -xgb.plot.tree(feature_names = NULL, filename_dump = NULL) +xgb.plot.tree(feature_names = NULL, filename_dump = NULL, + n_first_tree = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} \item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} + +\item{n_first_tree}{limit the plot to the n first trees.} } \value{ A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. From 3dd202a19eb318a16bfa0b98fd0f70fb78a927b4 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Tue, 6 Jan 2015 18:18:55 +0100 Subject: [PATCH 62/85] Add stat indicators in plot --- R-package/NAMESPACE | 1 + R-package/R/xgb.plot.tree.R | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index f2baee12b..f68eafbc5 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -20,6 +20,7 @@ importClassesFrom(Matrix,dgeMatrix) importFrom(DiagrammeR,DiagrammeR) importFrom(data.table,":=") importFrom(data.table,as.data.table) +importFrom(data.table,copy) importFrom(data.table,data.table) importFrom(data.table,rbindlist) importFrom(data.table,set) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 4863fd7ca..a263fe989 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -6,10 +6,11 @@ #' @importFrom data.table data.table #' @importFrom data.table set #' @importFrom data.table rbindlist +#' @importFrom data.table := +#' @importFrom data.table copy #' @importFrom magrittr %>% #' @importFrom magrittr not #' @importFrom magrittr add -#' @importFrom data.table := #' @importFrom stringr str_extract #' @importFrom stringr str_split #' @importFrom stringr str_extract @@ -93,10 +94,11 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = dt[ID == dt[,Yes], Feature]) - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = dt[ID == dt[,No], Feature]) + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = merge(copy(dt)[,ID:=Yes][, .(ID)], dt[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) - dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] + set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = merge(copy(dt)[,ID:=No][, .(ID)], dt[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) + + dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "
Cover: ", Cover, "
Gain: ", Quality, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|>= ", Split, "|", No, "[", NoFeature, "]", sep = "")] @@ -112,6 +114,5 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode", sep = "") path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(styles, yes, no, sep = ";") - DiagrammeR(path) } From 9e20893d352cf5578dd45b6e22082bf0f99208a4 Mon Sep 17 00:00:00 2001 From: pommedeterresautee Date: Tue, 6 Jan 2015 23:57:33 +0100 Subject: [PATCH 63/85] Change in aesthetic Improve documentation --- R-package/R/xgb.plot.tree.R | 23 +++++++++++++++-------- R-package/man/xgb.plot.tree.Rd | 15 +++++++++++---- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index a263fe989..f3aa1fe65 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -17,17 +17,24 @@ #' @importFrom stringr str_trim #' @importFrom DiagrammeR DiagrammeR #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. -#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). -#' @param n_first_tree limit the plot to the n first trees. +#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). +#' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. #' #' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' #' @details -#' This is the function to plot the trees growned. +#' +#' The content of each node is organised that way: +#' +#' \itemize{ +#' \item{\code{feature} value}{ ;} +#' \item{\code{cover}}{: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ;} +#' \item{\code{gain}}{: metric the importance of the node in the model.} +#' } +#' +#' Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. #' It uses Mermaid JS library for that purpose. -#' Performance can be low for huge models. -#' -#' +#' #' @examples #' data(agaricus.train, package='xgboost') #' @@ -98,9 +105,9 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = merge(copy(dt)[,ID:=No][, .(ID)], dt[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) - dt[Feature!="Leaf" ,yesPath:= paste(ID,"[", Feature, "
Cover: ", Cover, "
Gain: ", Quality, "]-->|< ", Split, "|", Yes, "[", YesFeature, "]", sep = "")] + dt[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", YesFeature, "]", sep = "")] - dt[Feature!="Leaf" ,noPath:= paste(ID,"[", Feature, "]-->|>= ", Split, "|", No, "[", NoFeature, "]", sep = "")] + dt[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", NoFeature, "]", sep = "")] #missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index eeec2f111..099092cc7 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -10,9 +10,9 @@ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} -\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} +\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} -\item{n_first_tree}{limit the plot to the n first trees.} +\item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} } \value{ A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. @@ -22,9 +22,16 @@ Read a xgboost model text dump. Only works for boosted tree model (not linear model). } \details{ -This is the function to plot the trees growned. +The content of each node is organised that way: + +\itemize{ + \item{\code{feature} value}{ ;} + \item{\code{cover}}{: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ;} + \item{\code{gain}}{: metric the importance of the node in the model.} +} + +Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. It uses Mermaid JS library for that purpose. -Performance can be low for huge models. } \examples{ data(agaricus.train, package='xgboost') From cce26756bfa219c550035b0ca43fbb2d84ce5577 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 7 Jan 2015 17:05:34 +0100 Subject: [PATCH 64/85] add style option --- R-package/R/xgb.plot.tree.R | 17 ++++++++++++----- R-package/man/xgb.plot.tree.Rd | 10 ++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index f3aa1fe65..9582a7fe7 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -19,6 +19,7 @@ #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). #' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. +#' @param style a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information. #' #' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' @@ -27,9 +28,9 @@ #' The content of each node is organised that way: #' #' \itemize{ -#' \item{\code{feature} value}{ ;} -#' \item{\code{cover}}{: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ;} -#' \item{\code{gain}}{: metric the importance of the node in the model.} +#' \item \code{feature} value ; +#' \item \code{cover}: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ; +#' \item \code{gain}: metric the importance of the node in the model. #' } #' #' Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. @@ -50,7 +51,7 @@ #' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') #' #' @export -xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL){ +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL, styles = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") @@ -62,6 +63,10 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr stop("n_first_tree: Has to be a numeric vector of size 1.") } + if (!class(styles) %in% c("character", "NULL") | length(styles) > 1) { + stop("style: Has to be a character vector of size 1.") + } + text <- readLines(filename_dump) %>% str_trim(side = "both") position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) @@ -114,7 +119,9 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } - styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" + if(is.null(styles)){ + styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" + } yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode", sep = "") diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index 099092cc7..21c1ab380 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -5,7 +5,7 @@ \title{Plot a boosted tree model} \usage{ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, - n_first_tree = NULL) + n_first_tree = NULL, styles = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} @@ -13,6 +13,8 @@ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, \item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} \item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} + +\item{style}{a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information.} } \value{ A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. @@ -25,9 +27,9 @@ Only works for boosted tree model (not linear model). The content of each node is organised that way: \itemize{ - \item{\code{feature} value}{ ;} - \item{\code{cover}}{: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ;} - \item{\code{gain}}{: metric the importance of the node in the model.} + \item \code{feature} value ; + \item \code{cover}: the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be ; + \item \code{gain}: metric the importance of the node in the model. } Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. From e380e4facf240a460cdbca729ef5c0b7a3cbddb7 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 7 Jan 2015 17:09:56 +0100 Subject: [PATCH 65/85] refactoring for perf --- R-package/R/xgb.plot.tree.R | 25 ++++++++++++------------- R-package/man/xgb.plot.tree.Rd | 5 +++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 9582a7fe7..1efcbf813 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -1,7 +1,7 @@ #' Plot a boosted tree model #' #' Read a xgboost model text dump. -#' Only works for boosted tree model (not linear model). +#' Plotting only works for boosted tree model (not linear model). #' #' @importFrom data.table data.table #' @importFrom data.table set @@ -33,7 +33,7 @@ #' \item \code{gain}: metric the importance of the node in the model. #' } #' -#' Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. +#' Each branch finishes with a leaf. For each leaf, only the \code{cover} is indicated. #' It uses Mermaid JS library for that purpose. #' #' @examples @@ -105,20 +105,19 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] - - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "YesFeature", value = merge(copy(dt)[,ID:=Yes][, .(ID)], dt[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) - - set(dt, i = which(dt[,Feature]!= "Leaf"), j = "NoFeature", value = merge(copy(dt)[,ID:=No][, .(ID)], dt[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) - - dt[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", YesFeature, "]", sep = "")] - - dt[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", NoFeature, "]", sep = "")] - - #missingPath <- paste(dtBranch[,ID], "-->|Missing|", dtBranch[,Missing], sep = "") - + allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "YesFeature", value = merge(copy(allTrees)[,ID:=Yes][, .(ID)], allTrees[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "NoFeature", value = merge(copy(allTrees)[,ID:=No][, .(ID)], allTrees[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) + + allTrees[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", YesFeature, "]", sep = "")] + + allTrees[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", NoFeature, "]", sep = "")] + + if(is.null(styles)){ styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" } diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index 21c1ab380..17ef49ced 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -21,9 +21,10 @@ A \code{data.table} of the features used in the model with their average gain (a } \description{ Read a xgboost model text dump. -Only works for boosted tree model (not linear model). } \details{ +Plotting only works for boosted tree model (not linear model). + The content of each node is organised that way: \itemize{ @@ -32,7 +33,7 @@ The content of each node is organised that way: \item \code{gain}: metric the importance of the node in the model. } -Each branch finished with a leaf. For each leaf, only the \code{cover} is indicated. +Each branch finishes with a leaf. For each leaf, only the \code{cover} is indicated. It uses Mermaid JS library for that purpose. } \examples{ From d532f04394ccae1161aa2a586e56b3e7df10ac3c Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 7 Jan 2015 17:47:50 +0100 Subject: [PATCH 66/85] add new function to read model and use it in the plot function --- R-package/NAMESPACE | 1 + R-package/R/xgb.model.dt.tree.R | 109 +++++++++++++++++++++++++++++ R-package/R/xgb.plot.tree.R | 60 ++-------------- R-package/man/xgb.model.dt.tree.Rd | 54 ++++++++++++++ R-package/man/xgb.plot.tree.Rd | 9 ++- 5 files changed, 173 insertions(+), 60 deletions(-) create mode 100644 R-package/R/xgb.model.dt.tree.R create mode 100644 R-package/man/xgb.model.dt.tree.Rd diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index f68eafbc5..23de90d28 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -9,6 +9,7 @@ export(xgb.cv) export(xgb.dump) export(xgb.importance) export(xgb.load) +export(xgb.model.dt.tree) export(xgb.plot.tree) export(xgb.save) export(xgb.train) diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R new file mode 100644 index 000000000..2a65c30f7 --- /dev/null +++ b/R-package/R/xgb.model.dt.tree.R @@ -0,0 +1,109 @@ +#' Convert tree model dump to data.table +#' +#' Read a tree model text dump and return a data.table. +#' +#' @importFrom data.table data.table +#' @importFrom data.table set +#' @importFrom data.table rbindlist +#' @importFrom data.table := +#' @importFrom magrittr %>% +#' @importFrom magrittr not +#' @importFrom magrittr add +#' @importFrom stringr str_extract +#' @importFrom stringr str_split +#' @importFrom stringr str_extract +#' @importFrom stringr str_trim +#' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. +#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). +#' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. +#' +#' @return A \code{data.table} of the features used in the model with their gain, cover and few other thing. +#' +#' @details +#' General function to convert a text dump of tree model to a Matrix. The purpose is to help user to explore the model and get a better understanding of it. +#' +#' The content of the \code{data.table} is organised that way: +#' +#' \itemize{ +#' \item \code{ID}: unique identifier of a node ; +#' \item \code{Feature}: feature used in the tree to operate a split. When Leaf is indicated, it is the end of a branch ; +#' \item \code{Split}: value of the chosen feature where is operated the split ; +#' \item \code{Yes}: ID of the feature for the next node in the branch when the split condition is met ; +#' \item \code{No}: ID of the feature for the next node in the branch when the split condition is not met ; +#' \item \code{Missing}: ID of the feature for the next node in the branch for observation where the feature used for the split are not provided ; +#' \item \code{Quality}: it's the gain related to the split in this specific node ; +#' \item \code{Cover}: metric to measure the number of observation affected by the split ; +#' \item \code{Tree}: ID of the tree. It is included in the main ID ; +#' } +#' +#' @examples +#' data(agaricus.train, package='xgboost') +#' +#' #Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). +#' #Each column of the sparse Matrix is a feature in one hot encoding format. +#' train <- agaricus.train +#' +#' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, +#' eta = 1, nround = 2,objective = "binary:logistic") +#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) +#' +#' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. +#' xgb.model.dt.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') +#' +#' @export +xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL){ + + if (!class(feature_names) %in% c("character", "NULL")) { + stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") + } + if (class(filename_dump) != "character" || !file.exists(filename_dump)) { + stop("filename_dump: Has to be a path to the model dump file.") + } + if (!class(n_first_tree) %in% c("numeric", "NULL") | length(n_first_tree) > 1) { + stop("n_first_tree: Has to be a numeric vector of size 1.") + } + + text <- readLines(filename_dump) %>% str_trim(side = "both") + position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) + + extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist + + n_round <- min(length(position) - 1, n_first_tree) + + addTreeId <- function(x, i) paste(i,x,sep = "-") + + allTrees <- data.table() + + for(i in 1:n_round){ + + tree <- text[(position[i]+1):(position[i+1]-1)] + + notLeaf <- str_match(tree, "leaf") %>% is.na + leaf <- notLeaf %>% not %>% tree[.] + branch <- notLeaf %>% tree[.] + idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric + if(!is.null(feature_names)){ + featureBranch <- feature_names[featureBranch + 1] + } + featureLeaf <- rep("Leaf", length(leaf)) + splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") + splitLeaf <- rep(NA, length(leaf)) + yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) + yesLeaf <- rep(NA, length(leaf)) + noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) + noLeaf <- rep(NA, length(leaf)) + missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) + missingLeaf <- rep(NA, length(leaf)) + qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") + qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") + coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") + coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") + dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + + allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) + } + + allTrees +} diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 1efcbf813..b980671b0 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -1,6 +1,6 @@ #' Plot a boosted tree model #' -#' Read a xgboost model text dump. +#' Read a tree model text dump. #' Plotting only works for boosted tree model (not linear model). #' #' @importFrom data.table data.table @@ -21,7 +21,7 @@ #' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. #' @param style a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information. #' -#' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +#' @return A \code{DiagrammeR} of the model. #' #' @details #' @@ -34,7 +34,7 @@ #' } #' #' Each branch finishes with a leaf. For each leaf, only the \code{cover} is indicated. -#' It uses Mermaid JS library for that purpose. +#' It uses \href{https://github.com/knsv/mermaid/}{Mermaid} library for that purpose. #' #' @examples #' data(agaricus.train, package='xgboost') @@ -51,63 +51,13 @@ #' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') #' #' @export -xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL, styles = NULL){ - - if (!class(feature_names) %in% c("character", "NULL")) { - stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") - } - if (class(filename_dump) != "character" || !file.exists(filename_dump)) { - stop("filename_dump: Has to be a path to the model dump file.") - } - if (!class(n_first_tree) %in% c("numeric", "NULL") | length(n_first_tree) > 1) { - stop("n_first_tree: Has to be a numeric vector of size 1.") - } +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL, styles = NULL){ if (!class(styles) %in% c("character", "NULL") | length(styles) > 1) { stop("style: Has to be a character vector of size 1.") } - - text <- readLines(filename_dump) %>% str_trim(side = "both") - position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) - - extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist - - n_round <- min(length(position) - 1, n_first_tree) - - addTreeId <- function(x, i) paste(i,x,sep = "-") - - allTrees <- data.table() - - for(i in 1:n_round){ - tree <- text[(position[i]+1):(position[i+1]-1)] - - notLeaf <- str_match(tree, "leaf") %>% is.na - leaf <- notLeaf %>% not %>% tree[.] - branch <- notLeaf %>% tree[.] - idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) - idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) - featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric - if(!is.null(feature_names)){ - featureBranch <- feature_names[featureBranch + 1] - } - featureLeaf <- rep("Leaf", length(leaf)) - splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") - splitLeaf <- rep(NA, length(leaf)) - yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) - yesLeaf <- rep(NA, length(leaf)) - noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) - noLeaf <- rep(NA, length(leaf)) - missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) - missingLeaf <- rep(NA, length(leaf)) - qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") - qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") - coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") - coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") - dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] - - allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) - } + allTrees <- xgb.model.dt.tree(feature_names, filename_dump, n_first_tree) set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "YesFeature", value = merge(copy(allTrees)[,ID:=Yes][, .(ID)], allTrees[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) diff --git a/R-package/man/xgb.model.dt.tree.Rd b/R-package/man/xgb.model.dt.tree.Rd new file mode 100644 index 000000000..8c46ffe4f --- /dev/null +++ b/R-package/man/xgb.model.dt.tree.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2 (4.1.0): do not edit by hand +% Please edit documentation in R/xgb.model.dt.tree.R +\name{xgb.model.dt.tree} +\alias{xgb.model.dt.tree} +\title{Convert tree model dump to data.table} +\usage{ +xgb.model.dt.tree(feature_names = NULL, filename_dump = NULL, + n_first_tree = NULL) +} +\arguments{ +\item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} + +\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} + +\item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} +} +\value{ +A \code{data.table} of the features used in the model with their gain, cover and few other thing. +} +\description{ +Read a tree model text dump and return a data.table. +} +\details{ +General function to convert a text dump of tree model to a Matrix. The purpose is to help user to explore the model and get a better understanding of it. + +The content of the \code{data.table} is organised that way: + +\itemize{ +\item \code{ID}: unique identifier of a node ; + \item \code{Feature}: feature used in the tree to operate a split. When Leaf is indicated, it is the end of a branch ; + \item \code{Split}: value of the chosen feature where is operated the split ; + \item \code{Yes}: ID of the feature for the next node in the branch when the split condition is met ; + \item \code{No}: ID of the feature for the next node in the branch when the split condition is not met ; + \item \code{Missing}: ID of the feature for the next node in the branch for observation where the feature used for the split are not provided ; + \item \code{Quality}: it's the gain related to the split in this specific node ; + \item \code{Cover}: metric to measure the number of observation affected by the split ; + \item \code{Tree}: ID of the tree. It is included in the main ID ; +} +} +\examples{ +data(agaricus.train, package='xgboost') + +#Both dataset are list with two items, a sparse matrix and labels (labels = outcome column which will be learned). +#Each column of the sparse Matrix is a feature in one hot encoding format. +train <- agaricus.train + +bst <- xgboost(data = train$data, label = train$label, max.depth = 2, + eta = 1, nround = 2,objective = "binary:logistic") +xgb.dump(bst, 'xgb.model.dump', with.stats = T) + +#agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. +xgb.model.dt.tree(agaricus.train$data@Dimnames[[2]], 'xgb.model.dump') +} + diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index 17ef49ced..ba65cdd7c 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -17,14 +17,13 @@ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, \item{style}{a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information.} } \value{ -A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. +A \code{DiagrammeR} of the model. } \description{ -Read a xgboost model text dump. +Read a tree model text dump. +Plotting only works for boosted tree model (not linear model). } \details{ -Plotting only works for boosted tree model (not linear model). - The content of each node is organised that way: \itemize{ @@ -34,7 +33,7 @@ The content of each node is organised that way: } Each branch finishes with a leaf. For each leaf, only the \code{cover} is indicated. -It uses Mermaid JS library for that purpose. +It uses \href{https://github.com/knsv/mermaid/}{Mermaid} library for that purpose. } \examples{ data(agaricus.train, package='xgboost') From 3d0bbae2c2d84ff3835b1540e291fc80b857200d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Wed, 7 Jan 2015 18:18:52 +0100 Subject: [PATCH 67/85] refactoring of importance function --- R-package/R/xgb.importance.R | 23 ++++++----------------- R-package/R/xgb.model.dt.tree.R | 18 ++++++++++++++---- R-package/man/xgb.importance.Rd | 3 ++- R-package/man/xgb.model.dt.tree.Rd | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index d5860e8a4..eaaad9ab8 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -6,7 +6,6 @@ #' @importFrom data.table data.table #' @importFrom magrittr %>% #' @importFrom data.table := -#' @importFrom stringr str_extract #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). #' @@ -21,7 +20,8 @@ #' There are 3 columns : #' \itemize{ #' \item \code{Features} name of the features as provided in \code{feature_names} or already present in the model dump. -#' \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training. +#' \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means important feature to predict the \code{label} used for the training ; +#' \item \code{Cover} metric of the number of observation related to this feature (only available for tree models) ; #' \item \code{Weight} percentage representing the relative number of times a feature have been taken into trees. \code{Gain} should be prefered to search the most important feature. For boosted linear model, this column has no meaning. #' } #' @@ -59,21 +59,10 @@ xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ result } -treeDump <- function(feature_names, text){ - featureVec <- c() - gainVec <- c() - for(line in text){ - p <- str_extract(line, "\\[f.*<") - if (!is.na(p)) { - featureVec <- substr(p, 3, nchar(p)-1) %>% c(featureVec) - gainVec <- str_extract(line, "gain.*,") %>% substr(x = ., 6, nchar(.)-1) %>% as.numeric %>% c(gainVec) - } - } - if(!is.null(feature_names)) { - featureVec %<>% as.numeric %>% {c =.+1; feature_names[c]} #+1 because in R indexing start with 1 instead of 0. - } - #1. Reduce, 2. %, 3. reorder - bigger top, 4. remove temp col - data.table(Feature = featureVec, Weight = gainVec)[,list(sum(Weight), .N), by = Feature][, Gain:= V1/sum(V1)][,Weight:= N/sum(N)][order(-rank(Gain))][,-c(2,3), with = F] +treeDump <- function(feature_names, text){ + result <- xgb.model.dt.tree(feature_names = feature_names, text = text)[Feature!="Leaf",][,.(sum(Quality), sum(Cover), .N),by = Feature][,V1:=V1/sum(V1)][,V2:=V2/sum(V2)][,N:=N/sum(N)][order(-rank(V1))] + setnames(result, c("Feature", "Gain", "Cover", "Frequence")) + result } linearDump <- function(feature_names, text){ diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 2a65c30f7..1fc104cce 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -51,19 +51,29 @@ #' xgb.model.dt.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') #' #' @export -xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL){ +xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, text = NULL, n_first_tree = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } - if (class(filename_dump) != "character" || !file.exists(filename_dump)) { - stop("filename_dump: Has to be a path to the model dump file.") + if (!class(filename_dump) %in% c("character", "NULL")) { + stop("filename_dump: Has to be a character vector representing the path to the model dump file.") + } else if (class(filename_dump) == "character" && !file.exists(filename_dump)) { + stop("filename_dump: path to the model doesn't exist.") + } else if(is.null(filename_dump) & is.null(text)){ + stop("filename_dump: no path and no string version of the model dump have been provided.") + } + if (!class(text) %in% c("character", "NULL")) { + stop("text: Has to be a vector of character or NULL if a path to the model dump has already been provided.") } if (!class(n_first_tree) %in% c("numeric", "NULL") | length(n_first_tree) > 1) { stop("n_first_tree: Has to be a numeric vector of size 1.") } - text <- readLines(filename_dump) %>% str_trim(side = "both") + if(is.null(text)){ + text <- readLines(filename_dump) %>% str_trim(side = "both") + } + position <- str_match(text, "booster") %>% is.na %>% not %>% which %>% c(length(text)+1) extract <- function(x, pattern) str_extract(x, pattern) %>% str_split("=") %>% lapply(function(x) x[2] %>% as.numeric) %>% unlist diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index a7a71cefc..8aa58cddd 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -27,7 +27,8 @@ Results are returned for both linear and tree models. There are 3 columns : \itemize{ \item \code{Features} name of the features as provided in \code{feature_names} or already present in the model dump. - \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training. + \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training ; + \item \code{Cover} metric of the number of observation related to this feature (only available for tree models) ; \item \code{Weight} percentage representing the relative number of times a feature have been taken into trees. \code{Gain} should be prefered to search the most important feature. For boosted linear model, this column has no meaning. } } diff --git a/R-package/man/xgb.model.dt.tree.Rd b/R-package/man/xgb.model.dt.tree.Rd index 8c46ffe4f..2bc48c4d0 100644 --- a/R-package/man/xgb.model.dt.tree.Rd +++ b/R-package/man/xgb.model.dt.tree.Rd @@ -4,7 +4,7 @@ \alias{xgb.model.dt.tree} \title{Convert tree model dump to data.table} \usage{ -xgb.model.dt.tree(feature_names = NULL, filename_dump = NULL, +xgb.model.dt.tree(feature_names = NULL, filename_dump = NULL, text = NULL, n_first_tree = NULL) } \arguments{ From 6fd8bbe71a5d686d4a6a6b7a5cc7fb4e38117fbe Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Thu, 8 Jan 2015 23:47:00 +0100 Subject: [PATCH 68/85] C part export a model dump string --- R-package/R/xgb.dump.R | 4 +++- R-package/src/xgboost_R.cpp | 17 +++++++++-------- R-package/src/xgboost_R.h | 5 ++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index b5e66604c..9a6e0ddd0 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -32,6 +32,8 @@ xgb.dump <- function(model, fname, fmap = "", with.stats=FALSE) { if (typeof(fname) != "character") { stop("xgb.dump: second argument must be type character") } - .Call("XGBoosterDumpModel_R", model, fname, fmap, as.integer(with.stats), PACKAGE = "xgboost") + result <- .Call("XGBoosterDumpModel_R", model, fmap, as.integer(with.stats), PACKAGE = "xgboost") + writeLines(result, fname) + #unlist(str_split(a, "\n"))=="" return(TRUE) } diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 7cab221fb..c8fe3e23a 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -272,20 +272,21 @@ extern "C" { XGBoosterSaveModel(R_ExternalPtrAddr(handle), CHAR(asChar(fname))); _WrapperEnd(); } - void XGBoosterDumpModel_R(SEXP handle, SEXP fname, - SEXP fmap, SEXP with_stats) { + SEXP XGBoosterDumpModel_R(SEXP handle, SEXP fmap, SEXP with_stats) { _WrapperBegin(); bst_ulong olen; const char **res = XGBoosterDumpModel(R_ExternalPtrAddr(handle), CHAR(asChar(fmap)), asInteger(with_stats), &olen); - FILE *fo = utils::FopenCheck(CHAR(asChar(fname)), "w"); - for (size_t i = 0; i < olen; ++i) { - fprintf(fo, "booster[%u]:\n", static_cast(i)); - fprintf(fo, "%s", res[i]); + SEXP out = PROTECT(allocVector(STRSXP, olen)); + char buffer [2000]; + for (size_t i = 0; i < olen; ++i) { + sprintf (buffer, "booster[%u]:\n%s", static_cast(i), res[i]); + SET_STRING_ELT(out, i, mkChar(buffer)); } - fclose(fo); _WrapperEnd(); + UNPROTECT(1); + return out; } -} +} \ No newline at end of file diff --git a/R-package/src/xgboost_R.h b/R-package/src/xgboost_R.h index 04c16ab3e..766152699 100644 --- a/R-package/src/xgboost_R.h +++ b/R-package/src/xgboost_R.h @@ -128,12 +128,11 @@ extern "C" { */ void XGBoosterSaveModel_R(SEXP handle, SEXP fname); /*! - * \brief dump model into text file + * \brief dump model into a string * \param handle handle - * \param fname file name of model that can be dumped into * \param fmap name to fmap can be empty string * \param with_stats whether dump statistics of splits */ - void XGBoosterDumpModel_R(SEXP handle, SEXP fname, SEXP fmap, SEXP with_stats); + SEXP XGBoosterDumpModel_R(SEXP handle, SEXP fmap, SEXP with_stats); } #endif // XGBOOST_WRAPPER_R_H_ From 3e1eea0eea6ad4be75408624bf71469a3492db84 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 00:14:01 +0100 Subject: [PATCH 69/85] refactor dump function to adapt to the new possibilities of exporting a String --- R-package/NAMESPACE | 1 + R-package/R/xgb.dump.R | 20 ++++++++++++++------ R-package/man/xgb.dump.Rd | 7 +++++-- R-package/man/xgb.importance.Rd | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 23de90d28..a36e066ef 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -32,5 +32,6 @@ importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_match) importFrom(stringr,str_replace) +importFrom(stringr,str_replace_all) importFrom(stringr,str_split) importFrom(stringr,str_trim) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index 9a6e0ddd0..0d7e79f31 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -2,8 +2,11 @@ #' #' Save a xgboost model to text file. Could be parsed later. #' +#' @importFrom magrittr %>% +#' @importFrom stringr str_split +#' @importFrom stringr str_replace_all #' @param model the model object. -#' @param fname the name of the binary file. +#' @param fname the name of the text file where to save the model. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector. #' @param fmap feature map file representing the type of feature. #' Detailed description could be found at #' \url{https://github.com/tqchen/xgboost/wiki/Binary-Classification#dump-model}. @@ -15,6 +18,9 @@ #' gain is the approximate loss function gain we get in each split; #' cover is the sum of second order gradient in each node. #' +#' @return +#' if fname is not provided or set to \code{NULL} the function will return the model as a \code{character} vector. Otherwise it will return \code{TRUE}. +#' #' @examples #' data(agaricus.train, package='xgboost') #' data(agaricus.test, package='xgboost') @@ -25,15 +31,17 @@ #' xgb.dump(bst, 'xgb.model.dump') #' @export #' -xgb.dump <- function(model, fname, fmap = "", with.stats=FALSE) { +xgb.dump <- function(model, fname = NULL, fmap = "", with.stats=FALSE) { if (class(model) != "xgb.Booster") { stop("xgb.dump: first argument must be type xgb.Booster") } - if (typeof(fname) != "character") { - stop("xgb.dump: second argument must be type character") + if (!class(fname) %in% c("character", "NULL")) { + stop("xgb.dump: second argument must be type character if provided") } result <- .Call("XGBoosterDumpModel_R", model, fmap, as.integer(with.stats), PACKAGE = "xgboost") + + if(is.null(fname)) return(str_split(result, "\n") %>% unlist %>% str_replace_all("\t"," ") %>% Filter(function(x) x != "", .)) + writeLines(result, fname) - #unlist(str_split(a, "\n"))=="" - return(TRUE) + TRUE } diff --git a/R-package/man/xgb.dump.Rd b/R-package/man/xgb.dump.Rd index bcecc6abd..e779f32b9 100644 --- a/R-package/man/xgb.dump.Rd +++ b/R-package/man/xgb.dump.Rd @@ -4,12 +4,12 @@ \alias{xgb.dump} \title{Save xgboost model to text file} \usage{ -xgb.dump(model, fname, fmap = "", with.stats = FALSE) +xgb.dump(model, fname = NULL, fmap = "", with.stats = FALSE) } \arguments{ \item{model}{the model object.} -\item{fname}{the name of the binary file.} +\item{fname}{the name of the text file where to save the model. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector.} \item{fmap}{feature map file representing the type of feature. Detailed description could be found at @@ -23,6 +23,9 @@ for example Format.} gain is the approximate loss function gain we get in each split; cover is the sum of second order gradient in each node.} } +\value{ +if fname is not provided or set to \code{NULL} the function will return the model as a \code{character} vector. Otherwise it will return \code{TRUE}. +} \description{ Save a xgboost model to text file. Could be parsed later. } diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index 8aa58cddd..78be4b91b 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -27,7 +27,7 @@ Results are returned for both linear and tree models. There are 3 columns : \itemize{ \item \code{Features} name of the features as provided in \code{feature_names} or already present in the model dump. - \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means most important feature regarding the \code{label} used for the training ; + \item \code{Gain} contribution of each feature to the model. For boosted tree model, each gain of each feature of each tree is taken into account, then average per feature to give a vision of the entire model. Highest percentage means important feature to predict the \code{label} used for the training ; \item \code{Cover} metric of the number of observation related to this feature (only available for tree models) ; \item \code{Weight} percentage representing the relative number of times a feature have been taken into trees. \code{Gain} should be prefered to search the most important feature. For boosted linear model, this column has no meaning. } From 10f755e055133f35c0b46e913d44d31577eafc63 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 11:06:56 +0100 Subject: [PATCH 70/85] only replace tabulation which begins a line (avoid wrong replacement in feature name) --- R-package/R/xgb.dump.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index 0d7e79f31..7049257f8 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -40,8 +40,10 @@ xgb.dump <- function(model, fname = NULL, fmap = "", with.stats=FALSE) { } result <- .Call("XGBoosterDumpModel_R", model, fmap, as.integer(with.stats), PACKAGE = "xgboost") - if(is.null(fname)) return(str_split(result, "\n") %>% unlist %>% str_replace_all("\t"," ") %>% Filter(function(x) x != "", .)) - - writeLines(result, fname) - TRUE -} + if(is.null(fname)) { + return(str_split(result, "\n") %>% unlist %>% str_replace("^\t+","") %>% Filter(function(x) x != "", .)) + } else { + writeLines(result, fname) + return(TRUE) + } +} \ No newline at end of file From 9d6eecf34e88e331c92fd89ef61f042d6cdd9a50 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 11:07:53 +0100 Subject: [PATCH 71/85] small change in import lib --- R-package/NAMESPACE | 1 - R-package/R/xgb.dump.R | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index a36e066ef..23de90d28 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -32,6 +32,5 @@ importFrom(stringr,str_extract) importFrom(stringr,str_extract_all) importFrom(stringr,str_match) importFrom(stringr,str_replace) -importFrom(stringr,str_replace_all) importFrom(stringr,str_split) importFrom(stringr,str_trim) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index 7049257f8..ceb68c1a3 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -4,7 +4,7 @@ #' #' @importFrom magrittr %>% #' @importFrom stringr str_split -#' @importFrom stringr str_replace_all +#' @importFrom stringr str_replace #' @param model the model object. #' @param fname the name of the text file where to save the model. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector. #' @param fmap feature map file representing the type of feature. From 31d0e8f65d42c6e65ef0a0eb89b4353a582ef69d Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 11:14:08 +0100 Subject: [PATCH 72/85] better doc of dump function --- R-package/R/xgb.dump.R | 10 +++++++--- R-package/man/xgb.dump.Rd | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index ceb68c1a3..f73850883 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -6,7 +6,7 @@ #' @importFrom stringr str_split #' @importFrom stringr str_replace #' @param model the model object. -#' @param fname the name of the text file where to save the model. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector. +#' @param fname the name of the text file where to save the model text dump. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector. #' @param fmap feature map file representing the type of feature. #' Detailed description could be found at #' \url{https://github.com/tqchen/xgboost/wiki/Binary-Classification#dump-model}. @@ -28,15 +28,19 @@ #' test <- agaricus.test #' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, #' eta = 1, nround = 2,objective = "binary:logistic") +#' # save the model in file 'xgb.model.dump' #' xgb.dump(bst, 'xgb.model.dump') +#' +#' # print the model without saving it to a file +#' print(xgb.dump(bst)) #' @export #' -xgb.dump <- function(model, fname = NULL, fmap = "", with.stats=FALSE) { +xgb.dump <- function(model = NULL, fname = NULL, fmap = "", with.stats=FALSE) { if (class(model) != "xgb.Booster") { stop("xgb.dump: first argument must be type xgb.Booster") } if (!class(fname) %in% c("character", "NULL")) { - stop("xgb.dump: second argument must be type character if provided") + stop("xgb.dump: second argument must be type character when provided") } result <- .Call("XGBoosterDumpModel_R", model, fmap, as.integer(with.stats), PACKAGE = "xgboost") diff --git a/R-package/man/xgb.dump.Rd b/R-package/man/xgb.dump.Rd index e779f32b9..6dad9ed7b 100644 --- a/R-package/man/xgb.dump.Rd +++ b/R-package/man/xgb.dump.Rd @@ -4,12 +4,12 @@ \alias{xgb.dump} \title{Save xgboost model to text file} \usage{ -xgb.dump(model, fname = NULL, fmap = "", with.stats = FALSE) +xgb.dump(model = NULL, fname = NULL, fmap = "", with.stats = FALSE) } \arguments{ \item{model}{the model object.} -\item{fname}{the name of the text file where to save the model. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector.} +\item{fname}{the name of the text file where to save the model text dump. If not provided or set to \code{NULL} the function will return the model as a \code{character} vector.} \item{fmap}{feature map file representing the type of feature. Detailed description could be found at @@ -36,6 +36,10 @@ train <- agaricus.train test <- agaricus.test bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2,objective = "binary:logistic") +# save the model in file 'xgb.model.dump' xgb.dump(bst, 'xgb.model.dump') + +# print the model without saving it to a file +print(xgb.dump(bst)) } From d96bd15b7deeb61d4719f013ed1b34feaa114691 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 11:52:40 +0100 Subject: [PATCH 73/85] small fix in the C dump code --- R-package/src/xgboost_R.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index c8fe3e23a..80f77c02b 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -281,7 +281,8 @@ extern "C" { &olen); SEXP out = PROTECT(allocVector(STRSXP, olen)); char buffer [2000]; - for (size_t i = 0; i < olen; ++i) { + for (size_t i = 0; i < olen; ++i) { + memset(buffer, 0, sizeof buffer); sprintf (buffer, "booster[%u]:\n%s", static_cast(i), res[i]); SET_STRING_ELT(out, i, mkChar(buffer)); } From b656ca1554c3024ddb93fc900103360305190784 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 11:54:23 +0100 Subject: [PATCH 74/85] reindent --- R-package/src/xgboost_R.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 80f77c02b..9320547df 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -276,9 +276,9 @@ extern "C" { _WrapperBegin(); bst_ulong olen; const char **res = XGBoosterDumpModel(R_ExternalPtrAddr(handle), - CHAR(asChar(fmap)), - asInteger(with_stats), - &olen); + CHAR(asChar(fmap)), + asInteger(with_stats), + &olen); SEXP out = PROTECT(allocVector(STRSXP, olen)); char buffer [2000]; for (size_t i = 0; i < olen; ++i) { @@ -287,7 +287,7 @@ extern "C" { SET_STRING_ELT(out, i, mkChar(buffer)); } _WrapperEnd(); - UNPROTECT(1); - return out; + UNPROTECT(1); + return out; } } \ No newline at end of file From 51935851bdf1a15630c6e4053b523753e04cf6e5 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 18:24:12 +0100 Subject: [PATCH 75/85] fix plenty of small bugs --- R-package/NAMESPACE | 1 + R-package/R/xgb.dump.R | 2 +- R-package/R/xgb.importance.R | 3 ++- R-package/R/xgb.model.dt.tree.R | 43 ++++++++++++++++++++++++++++----- R-package/R/xgb.plot.tree.R | 8 ++---- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 23de90d28..d29ad7a18 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -25,6 +25,7 @@ importFrom(data.table,copy) importFrom(data.table,data.table) importFrom(data.table,rbindlist) importFrom(data.table,set) +importFrom(data.table,setnames) importFrom(magrittr,"%>%") importFrom(magrittr,add) importFrom(magrittr,not) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index f73850883..61bfe412e 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -47,7 +47,7 @@ xgb.dump <- function(model = NULL, fname = NULL, fmap = "", with.stats=FALSE) { if(is.null(fname)) { return(str_split(result, "\n") %>% unlist %>% str_replace("^\t+","") %>% Filter(function(x) x != "", .)) } else { - writeLines(result, fname) + result %>% str_split("\n") %>% unlist %>% Filter(function(x) x != "", .) %>% writeLines(fname) return(TRUE) } } \ No newline at end of file diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index eaaad9ab8..189ee03b4 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -4,8 +4,9 @@ #' Can be tree or linear model (text dump of linear model are only supported in dev version of \code{Xgboost} for now). #' #' @importFrom data.table data.table -#' @importFrom magrittr %>% +#' @importFrom data.table setnames #' @importFrom data.table := +#' @importFrom magrittr %>% #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). #' diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 1fc104cce..3e0723c61 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -5,6 +5,7 @@ #' @importFrom data.table data.table #' @importFrom data.table set #' @importFrom data.table rbindlist +#' @importFrom data.table copy #' @importFrom data.table := #' @importFrom magrittr %>% #' @importFrom magrittr not @@ -88,11 +89,13 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, text = tree <- text[(position[i]+1):(position[i+1]-1)] + treeID <- i-1 + notLeaf <- str_match(tree, "leaf") %>% is.na leaf <- notLeaf %>% not %>% tree[.] branch <- notLeaf %>% tree[.] - idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) - idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(i) + idBranch <- str_extract(branch, "\\d*:") %>% str_replace(":", "") %>% addTreeId(treeID) + idLeaf <- str_extract(leaf, "\\d*:") %>% str_replace(":", "") %>% addTreeId(treeID) featureBranch <- str_extract(branch, "f\\d*<") %>% str_replace("<", "") %>% str_replace("f", "") %>% as.numeric if(!is.null(feature_names)){ featureBranch <- feature_names[featureBranch + 1] @@ -100,20 +103,48 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, text = featureLeaf <- rep("Leaf", length(leaf)) splitBranch <- str_extract(branch, "<\\d*\\.*\\d*\\]") %>% str_replace("<", "") %>% str_replace("\\]", "") splitLeaf <- rep(NA, length(leaf)) - yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(i) + yesBranch <- extract(branch, "yes=\\d*") %>% addTreeId(treeID) yesLeaf <- rep(NA, length(leaf)) - noBranch <- extract(branch, "no=\\d*") %>% addTreeId(i) + noBranch <- extract(branch, "no=\\d*") %>% addTreeId(treeID) noLeaf <- rep(NA, length(leaf)) - missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(i) + missingBranch <- extract(branch, "missing=\\d+") %>% addTreeId(treeID) missingLeaf <- rep(NA, length(leaf)) qualityBranch <- extract(branch, "gain=\\d*\\.*\\d*") qualityLeaf <- extract(leaf, "leaf=\\-*\\d*\\.*\\d*") coverBranch <- extract(branch, "cover=\\d*\\.*\\d*") coverLeaf <- extract(leaf, "cover=\\d*\\.*\\d*") - dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=i] + dt <- data.table(ID = c(idBranch, idLeaf), Feature = c(featureBranch, featureLeaf), Split = c(splitBranch, splitLeaf), Yes = c(yesBranch, yesLeaf), No = c(noBranch, noLeaf), Missing = c(missingBranch, missingLeaf), Quality = c(qualityBranch, qualityLeaf), Cover = c(coverBranch, coverLeaf))[order(ID)][,Tree:=treeID] allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } + yes <- allTrees[!is.na(Yes),Yes] + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "Yes.Feature", + value = allTrees[ID == yes,Feature]) + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "Yes.Cover", + value = allTrees[ID == yes,Cover]) + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "Yes.Quality", + value = allTrees[ID == yes,Quality]) + + no <- allTrees[!is.na(No),No] + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "No.Feature", + value = allTrees[ID == no,Feature]) + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "No.Cover", + value = allTrees[ID == no,Cover]) + + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + j = "No.Quality", + value = allTrees[ID == no,Quality]) + allTrees } diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index b980671b0..1a8a04e8a 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -59,13 +59,9 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tr allTrees <- xgb.model.dt.tree(feature_names, filename_dump, n_first_tree) - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "YesFeature", value = merge(copy(allTrees)[,ID:=Yes][, .(ID)], allTrees[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) + allTrees[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", Yes.Feature, "]", sep = "")] - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "NoFeature", value = merge(copy(allTrees)[,ID:=No][, .(ID)], allTrees[,.(ID, Feature, Quality, Cover)], by = "ID")[,paste(Feature, "
Cover: ", Cover, sep = "")]) - - allTrees[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", YesFeature, "]", sep = "")] - - allTrees[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", NoFeature, "]", sep = "")] + allTrees[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", No.Feature, "]", sep = "")] if(is.null(styles)){ From a3493934d1e964240b8ff428d14c52a1d76abfa3 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 18:26:56 +0100 Subject: [PATCH 76/85] documentation example change --- R-package/R/xgb.dump.R | 2 +- R-package/man/xgb.dump.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index 61bfe412e..3df8c9605 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -29,7 +29,7 @@ #' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, #' eta = 1, nround = 2,objective = "binary:logistic") #' # save the model in file 'xgb.model.dump' -#' xgb.dump(bst, 'xgb.model.dump') +#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) #' #' # print the model without saving it to a file #' print(xgb.dump(bst)) diff --git a/R-package/man/xgb.dump.Rd b/R-package/man/xgb.dump.Rd index 6dad9ed7b..473227357 100644 --- a/R-package/man/xgb.dump.Rd +++ b/R-package/man/xgb.dump.Rd @@ -37,7 +37,7 @@ test <- agaricus.test bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2,objective = "binary:logistic") # save the model in file 'xgb.model.dump' -xgb.dump(bst, 'xgb.model.dump') +xgb.dump(bst, 'xgb.model.dump', with.stats = T) # print the model without saving it to a file print(xgb.dump(bst)) From 99b4ead9370f91a6d45eae8a7c2944678896f128 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 18:28:10 +0100 Subject: [PATCH 77/85] add new dependency on DiagrammeR --- R-package/DESCRIPTION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 6f73766fa..cc1c22087 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -24,4 +24,5 @@ Imports: methods, data.table (>= 1.9), magrittr (>= 1.5), - stringr \ No newline at end of file + stringr, + DiagrammeR \ No newline at end of file From 359889e3d66a6cb7a6eada5b8be0337132e59fca Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Fri, 9 Jan 2015 19:03:47 +0100 Subject: [PATCH 78/85] fix a small bug in CV function --- R-package/R/xgb.cv.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index eb14b2e1e..b071f08a7 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -112,8 +112,8 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = type <- rep(x = "numeric", times = length(colnames)) dt <- read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table - split = str_split(string = history, pattern = "\t") + split <- str_split(string = history, pattern = "\t") - for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d*\\.*\\d*") %>% unlist %>% as.list %>% {vec <- .; rbindlist(list(dt, vec), use.names = F, fill = F)} + for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d*\\.+\\d*") %>% unlist %>% as.list %>% {vec <- .; rbindlist(list(dt, vec), use.names = F, fill = F)} dt } \ No newline at end of file From 70df2276890d08240391adee07408f9495c78db5 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 11 Jan 2015 01:04:54 +0100 Subject: [PATCH 79/85] dump function is now memory safe --- R-package/src/xgboost_R.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 9320547df..5a8ddbf52 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "xgboost_R.h" #include "wrapper/xgboost_wrapper.h" #include "src/utils/utils.h" @@ -280,11 +281,10 @@ extern "C" { asInteger(with_stats), &olen); SEXP out = PROTECT(allocVector(STRSXP, olen)); - char buffer [2000]; for (size_t i = 0; i < olen; ++i) { - memset(buffer, 0, sizeof buffer); - sprintf (buffer, "booster[%u]:\n%s", static_cast(i), res[i]); - SET_STRING_ELT(out, i, mkChar(buffer)); + stringstream stream; + stream << "booster["< Date: Sun, 11 Jan 2015 03:06:41 +0100 Subject: [PATCH 80/85] add new parameters to several functions avoid the need of a text dump --- R-package/R/xgb.dump.R | 10 +++++++--- R-package/R/xgb.importance.R | 21 ++++++++++++++++----- R-package/R/xgb.plot.tree.R | 22 ++++++++++++++++------ R-package/man/xgb.importance.Rd | 7 ++++--- R-package/man/xgb.plot.tree.Rd | 9 +++++---- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/R-package/R/xgb.dump.R b/R-package/R/xgb.dump.R index 3df8c9605..b6c829663 100644 --- a/R-package/R/xgb.dump.R +++ b/R-package/R/xgb.dump.R @@ -37,11 +37,15 @@ #' xgb.dump <- function(model = NULL, fname = NULL, fmap = "", with.stats=FALSE) { if (class(model) != "xgb.Booster") { - stop("xgb.dump: first argument must be type xgb.Booster") + stop("model: argument must be type xgb.Booster") } - if (!class(fname) %in% c("character", "NULL")) { - stop("xgb.dump: second argument must be type character when provided") + if (!(class(fname) %in% c("character", "NULL") && length(fname) <= 1)) { + stop("fname: argument must be type character (when provided)") } + if (!(class(fmap) %in% c("character", "NULL") && length(fname) <= 1)) { + stop("fmap: argument must be type character (when provided)") + } + result <- .Call("XGBoosterDumpModel_R", model, fmap, as.integer(with.stats), PACKAGE = "xgboost") if(is.null(fname)) { diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 189ee03b4..7dd3a8ca3 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -9,6 +9,7 @@ #' @importFrom magrittr %>% #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}). +#' @param model generated by the \code{xgb.train} function. Avoid the creation of a dump file. #' #' @return A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. #' @@ -38,20 +39,30 @@ #' #' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, #' eta = 1, nround = 2,objective = "binary:logistic") -#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) #' #' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. -#' xgb.importance(agaricus.test$data@@Dimnames[[2]], 'xgb.model.dump') +#' xgb.importance(agaricus.test$data@@Dimnames[[2]], model = bst) #' #' @export -xgb.importance <- function(feature_names = NULL, filename_dump = NULL){ +xgb.importance <- function(feature_names = NULL, filename_dump = NULL, model = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } - if (class(filename_dump) != "character" || !file.exists(filename_dump)) { + + if (!(class(filename_dump) %in% c("character", "NULL") && length(filename_dump) <= 1)) { stop("filename_dump: Has to be a path to the model dump file.") } - text <- readLines(filename_dump) + + if (!class(model) %in% c("xgb.Booster", "NULL")) { + stop("model: Has to be an object of class xgb.Booster model generaged by the xgb.train function.") + } + + if(is.null(model)){ + text <- readLines(filename_dump) + } else { + text <- xgb.dump(model = model, with.stats = T) + } + if(text[2] == "bias:"){ result <- linearDump(feature_names, text) } else { diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 1a8a04e8a..7fb23c88a 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -17,7 +17,8 @@ #' @importFrom stringr str_trim #' @importFrom DiagrammeR DiagrammeR #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. -#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). +#' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). Possible to provide a model directly (see \code{model} argument). +#' @param model generated by the \code{xgb.train} function. Avoid the creation of a dump file. #' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. #' @param style a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information. #' @@ -45,19 +46,28 @@ #' #' bst <- xgboost(data = train$data, label = train$label, max.depth = 2, #' eta = 1, nround = 2,objective = "binary:logistic") -#' xgb.dump(bst, 'xgb.model.dump', with.stats = T) #' #' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. -#' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') +#' xgb.plot.tree(agaricus.train$data@@Dimnames[[2]], model = bst) #' #' @export -xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, n_first_tree = NULL, styles = NULL){ +#' +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, model = NULL, n_first_tree = NULL, styles = NULL){ - if (!class(styles) %in% c("character", "NULL") | length(styles) > 1) { + if (!(class(styles) %in% c("character", "NULL") && length(styles) == 1)) { stop("style: Has to be a character vector of size 1.") } + + if (!class(model) %in% c("xgb.Booster", "NULL")) { + stop("model: Has to be an object of class xgb.Booster model generaged by the xgb.train function.") + } - allTrees <- xgb.model.dt.tree(feature_names, filename_dump, n_first_tree) + if(is.null(model)){ + allTrees <- xgb.model.dt.tree(feature_names = feature_names, filename_dump = filename_dump, n_first_tree = n_first_tree) + } else { + text = xgb.dump(model = model, with.stats = T) + allTrees <- xgb.model.dt.tree(feature_names = feature_names, text = text, n_first_tree = n_first_tree) + } allTrees[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", Yes.Feature, "]", sep = "")] diff --git a/R-package/man/xgb.importance.Rd b/R-package/man/xgb.importance.Rd index 78be4b91b..c173b1e8e 100644 --- a/R-package/man/xgb.importance.Rd +++ b/R-package/man/xgb.importance.Rd @@ -4,12 +4,14 @@ \alias{xgb.importance} \title{Show importance of features in a model} \usage{ -xgb.importance(feature_names = NULL, filename_dump = NULL) +xgb.importance(feature_names = NULL, filename_dump = NULL, model = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} \item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (\code{with.stats = T} in function \code{xgb.dump}).} + +\item{model}{generated by the \code{xgb.train} function. Avoid the creation of a dump file.} } \value{ A \code{data.table} of the features used in the model with their average gain (and their weight for boosted tree model) in the model. @@ -43,9 +45,8 @@ test <- agaricus.test bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2,objective = "binary:logistic") -xgb.dump(bst, 'xgb.model.dump', with.stats = T) #agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. -xgb.importance(agaricus.test$data@Dimnames[[2]], 'xgb.model.dump') +xgb.importance(agaricus.test$data@Dimnames[[2]], model = bst) } diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index ba65cdd7c..c1b8418cd 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -4,13 +4,15 @@ \alias{xgb.plot.tree} \title{Plot a boosted tree model} \usage{ -xgb.plot.tree(feature_names = NULL, filename_dump = NULL, +xgb.plot.tree(feature_names = NULL, filename_dump = NULL, model = NULL, n_first_tree = NULL, styles = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} -\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} +\item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). Possible to provide a model directly (see \code{model} argument).} + +\item{model}{generated by the \code{xgb.train} function. Avoid the creation of a dump file.} \item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} @@ -44,9 +46,8 @@ train <- agaricus.train bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2,objective = "binary:logistic") -xgb.dump(bst, 'xgb.model.dump', with.stats = T) #agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. -xgb.plot.tree(agaricus.train$data@Dimnames[[2]], 'xgb.model.dump') +xgb.plot.tree(agaricus.train$data@Dimnames[[2]], model = bst) } From 31a3b38ef85dd19daacd68429aa885546925ff25 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 11 Jan 2015 09:40:55 +0100 Subject: [PATCH 81/85] add new parameters model to avoid the use of dump file for functions plot, dt.tree, importance add new size parameter for plot function --- R-package/R/xgb.importance.R | 4 ++-- R-package/R/xgb.model.dt.tree.R | 28 +++++++++++++++++++--------- R-package/R/xgb.plot.tree.R | 19 ++++++++++--------- R-package/man/xgb.model.dt.tree.Rd | 10 +++++++--- R-package/man/xgb.plot.tree.Rd | 4 ++-- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 7dd3a8ca3..174d92704 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -64,9 +64,9 @@ xgb.importance <- function(feature_names = NULL, filename_dump = NULL, model = N } if(text[2] == "bias:"){ - result <- linearDump(feature_names, text) + result <- readLines(filename_dump) %>% linearDump(feature_names, .) } else { - result <- treeDump(feature_names, text) + result <- treeDump(feature_names, text = text) } result } diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 3e0723c61..5ad6c6b3d 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -16,6 +16,8 @@ #' @importFrom stringr str_trim #' @param feature_names names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}. #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). +#' @param model dump generated by the \code{xgb.train} function. Avoid the creation of a dump file. +#' @param text dump generated by the \code{xgb.dump} function. Avoid the creation of a dump file. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). #' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. #' #' @return A \code{data.table} of the features used in the model with their gain, cover and few other thing. @@ -49,29 +51,37 @@ #' xgb.dump(bst, 'xgb.model.dump', with.stats = T) #' #' #agaricus.test$data@@Dimnames[[2]] represents the column names of the sparse matrix. -#' xgb.model.dt.tree(agaricus.train$data@@Dimnames[[2]], 'xgb.model.dump') +#' xgb.model.dt.tree(agaricus.train$data@@Dimnames[[2]], filename_dump = 'xgb.model.dump') #' #' @export -xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, text = NULL, n_first_tree = NULL){ +xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, model = NULL, text = NULL, n_first_tree = NULL){ if (!class(feature_names) %in% c("character", "NULL")) { stop("feature_names: Has to be a vector of character or NULL if the model dump already contains feature name. Look at this function documentation to see where to get feature names.") } - if (!class(filename_dump) %in% c("character", "NULL")) { - stop("filename_dump: Has to be a character vector representing the path to the model dump file.") - } else if (class(filename_dump) == "character" && !file.exists(filename_dump)) { + if (!(class(filename_dump) %in% c("character", "NULL") && length(filename_dump) <= 1)) { + stop("filename_dump: Has to be a character vector of size 1 representing the path to the model dump file.") + } else if (!is.null(filename_dump) && !file.exists(filename_dump)) { stop("filename_dump: path to the model doesn't exist.") - } else if(is.null(filename_dump) & is.null(text)){ - stop("filename_dump: no path and no string version of the model dump have been provided.") + } else if(is.null(filename_dump) && is.null(model) && is.null(text)){ + stop("filename_dump & model & text: no path to dump model, no model, no text dump, have been provided.") } - if (!class(text) %in% c("character", "NULL")) { + + if (!class(model) %in% c("xgb.Booster", "NULL")) { + stop("model: Has to be an object of class xgb.Booster model generaged by the xgb.train function.") + } + + if (!class(text) %in% c("character", "NULL")) { stop("text: Has to be a vector of character or NULL if a path to the model dump has already been provided.") } + if (!class(n_first_tree) %in% c("numeric", "NULL") | length(n_first_tree) > 1) { stop("n_first_tree: Has to be a numeric vector of size 1.") } - if(is.null(text)){ + if(!is.null(model)){ + text = xgb.dump(model = model, with.stats = T) + } else if(!is.null(filename_dump)){ text <- readLines(filename_dump) %>% str_trim(side = "both") } diff --git a/R-package/R/xgb.plot.tree.R b/R-package/R/xgb.plot.tree.R index 7fb23c88a..01261fab3 100644 --- a/R-package/R/xgb.plot.tree.R +++ b/R-package/R/xgb.plot.tree.R @@ -20,7 +20,9 @@ #' @param filename_dump the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}). Possible to provide a model directly (see \code{model} argument). #' @param model generated by the \code{xgb.train} function. Avoid the creation of a dump file. #' @param n_first_tree limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models. -#' @param style a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information. +#' @param CSSstyle a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information. +#' @param width the width of the diagram in pixels. +#' @param height the height of the diagram in pixels. #' #' @return A \code{DiagrammeR} of the model. #' @@ -52,9 +54,9 @@ #' #' @export #' -xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, model = NULL, n_first_tree = NULL, styles = NULL){ +xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, model = NULL, n_first_tree = NULL, CSSstyle = NULL, width = NULL, height = NULL){ - if (!(class(styles) %in% c("character", "NULL") && length(styles) == 1)) { + if (!(class(CSSstyle) %in% c("character", "NULL") && length(CSSstyle) <= 1)) { stop("style: Has to be a character vector of size 1.") } @@ -65,8 +67,7 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, model = NU if(is.null(model)){ allTrees <- xgb.model.dt.tree(feature_names = feature_names, filename_dump = filename_dump, n_first_tree = n_first_tree) } else { - text = xgb.dump(model = model, with.stats = T) - allTrees <- xgb.model.dt.tree(feature_names = feature_names, text = text, n_first_tree = n_first_tree) + allTrees <- xgb.model.dt.tree(feature_names = feature_names, model = model, n_first_tree = n_first_tree) } allTrees[Feature!="Leaf" ,yesPath:= paste(ID,"(", Feature, "
Cover: ", Cover, "
Gain: ", Quality, ")-->|< ", Split, "|", Yes, ">", Yes.Feature, "]", sep = "")] @@ -74,14 +75,14 @@ xgb.plot.tree <- function(feature_names = NULL, filename_dump = NULL, model = NU allTrees[Feature!="Leaf" ,noPath:= paste(ID,"(", Feature, ")-->|>= ", Split, "|", No, ">", No.Feature, "]", sep = "")] - if(is.null(styles)){ - styles <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" + if(is.null(CSSstyle)){ + CSSstyle <- "classDef greenNode fill:#A2EB86, stroke:#04C4AB, stroke-width:2px;classDef redNode fill:#FFA070, stroke:#FF5E5E, stroke-width:2px" } yes <- allTrees[Feature!="Leaf", c(Yes)] %>% paste(collapse = ",") %>% paste("class ", ., " greenNode", sep = "") no <- allTrees[Feature!="Leaf", c(No)] %>% paste(collapse = ",") %>% paste("class ", ., " redNode", sep = "") - path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(styles, yes, no, sep = ";") - DiagrammeR(path) + path <- allTrees[Feature!="Leaf", c(yesPath, noPath)] %>% .[order(.)] %>% paste(sep = "", collapse = ";") %>% paste("graph LR", .,collapse = "", sep = ";") %>% paste(CSSstyle, yes, no, sep = ";") + DiagrammeR(path, width, height) } diff --git a/R-package/man/xgb.model.dt.tree.Rd b/R-package/man/xgb.model.dt.tree.Rd index 2bc48c4d0..fb5bd94bd 100644 --- a/R-package/man/xgb.model.dt.tree.Rd +++ b/R-package/man/xgb.model.dt.tree.Rd @@ -4,14 +4,18 @@ \alias{xgb.model.dt.tree} \title{Convert tree model dump to data.table} \usage{ -xgb.model.dt.tree(feature_names = NULL, filename_dump = NULL, text = NULL, - n_first_tree = NULL) +xgb.model.dt.tree(feature_names = NULL, filename_dump = NULL, + model = NULL, text = NULL, n_first_tree = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} \item{filename_dump}{the path to the text file storing the model. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} +\item{model}{dump generated by the \code{xgb.train} function. Avoid the creation of a dump file.} + +\item{text}{dump generated by the \code{xgb.dump} function. Avoid the creation of a dump file. Model dump must include the gain per feature and per tree (parameter \code{with.stats = T} in function \code{xgb.dump}).} + \item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} } \value{ @@ -49,6 +53,6 @@ bst <- xgboost(data = train$data, label = train$label, max.depth = 2, xgb.dump(bst, 'xgb.model.dump', with.stats = T) #agaricus.test$data@Dimnames[[2]] represents the column names of the sparse matrix. -xgb.model.dt.tree(agaricus.train$data@Dimnames[[2]], 'xgb.model.dump') +xgb.model.dt.tree(agaricus.train$data@Dimnames[[2]], filename_dump = 'xgb.model.dump') } diff --git a/R-package/man/xgb.plot.tree.Rd b/R-package/man/xgb.plot.tree.Rd index c1b8418cd..ce69d4431 100644 --- a/R-package/man/xgb.plot.tree.Rd +++ b/R-package/man/xgb.plot.tree.Rd @@ -5,7 +5,7 @@ \title{Plot a boosted tree model} \usage{ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, model = NULL, - n_first_tree = NULL, styles = NULL) + n_first_tree = NULL, CSSstyle = NULL) } \arguments{ \item{feature_names}{names of each feature as a character vector. Can be extracted from a sparse matrix (see example). If model dump already contains feature names, this argument should be \code{NULL}.} @@ -16,7 +16,7 @@ xgb.plot.tree(feature_names = NULL, filename_dump = NULL, model = NULL, \item{n_first_tree}{limit the plot to the n first trees. If \code{NULL}, all trees of the model are plotted. Performance can be low for huge models.} -\item{style}{a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information.} +\item{CSSstyle}{a \code{character} vector storing a css style to customize the appearance of nodes. Look at the \href{https://github.com/knsv/mermaid/wiki}{Mermaid wiki} for more information.} } \value{ A \code{DiagrammeR} of the model. From d441a9d382ffecfe04e6c004d0bc1848e8a48f16 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 11 Jan 2015 23:37:02 +0100 Subject: [PATCH 82/85] avoid error when a tree is just made of one leaf --- R-package/R/xgb.model.dt.tree.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 5ad6c6b3d..3cabcf1a9 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -99,6 +99,9 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, model tree <- text[(position[i]+1):(position[i+1]-1)] + # avoid tree made of a leaf only (no split) + if(length(tree) <2) break + treeID <- i-1 notLeaf <- str_match(tree, "leaf") %>% is.na @@ -129,7 +132,7 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, model } yes <- allTrees[!is.na(Yes),Yes] - + set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), j = "Yes.Feature", value = allTrees[ID == yes,Feature]) From 48c1911bc41505b2feeda438726aa8fd56e9e754 Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 11 Jan 2015 23:39:24 +0100 Subject: [PATCH 83/85] fix error --- R-package/R/xgb.model.dt.tree.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 3cabcf1a9..87b9f3a99 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -100,7 +100,7 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, model tree <- text[(position[i]+1):(position[i+1]-1)] # avoid tree made of a leaf only (no split) - if(length(tree) <2) break + if(length(tree) <2) next treeID <- i-1 From bbbc6be58e454ccf78df1c25a263982fbdd978ee Mon Sep 17 00:00:00 2001 From: Tong He Date: Tue, 13 Jan 2015 15:38:50 -0800 Subject: [PATCH 84/85] Add `vcd` to the dependencies --- R-package/DESCRIPTION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index cc1c22087..7fec935f6 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -25,4 +25,5 @@ Imports: data.table (>= 1.9), magrittr (>= 1.5), stringr, - DiagrammeR \ No newline at end of file + DiagrammeR, + vcd From d84d27ae3df47eda4f427bc3134bf52da11fa42c Mon Sep 17 00:00:00 2001 From: El Potaeto Date: Sun, 18 Jan 2015 00:35:38 +0100 Subject: [PATCH 85/85] refactoring --- R-package/R/xgb.importance.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R-package/R/xgb.importance.R b/R-package/R/xgb.importance.R index 174d92704..69715d3cb 100644 --- a/R-package/R/xgb.importance.R +++ b/R-package/R/xgb.importance.R @@ -72,8 +72,8 @@ xgb.importance <- function(feature_names = NULL, filename_dump = NULL, model = N } treeDump <- function(feature_names, text){ - result <- xgb.model.dt.tree(feature_names = feature_names, text = text)[Feature!="Leaf",][,.(sum(Quality), sum(Cover), .N),by = Feature][,V1:=V1/sum(V1)][,V2:=V2/sum(V2)][,N:=N/sum(N)][order(-rank(V1))] - setnames(result, c("Feature", "Gain", "Cover", "Frequence")) + result <- xgb.model.dt.tree(feature_names = feature_names, text = text)[Feature!="Leaf",.(Gain = sum(Quality), Cover = sum(Cover), Frequence = .N), by = Feature][,`:=`(Gain=Gain/sum(Gain),Cover=Cover/sum(Cover),Frequence=Frequence/sum(Frequence))][order(-Gain)] + result }