Jak wykreślić przykładowe drzewo z randomForest :: getTree ()? [Zamknięte]

62

Każdy otrzymał sugestie dotyczące biblioteki lub kodu dotyczące sposobu wykreślenia kilku przykładowych drzew z:

getTree(rfobj, k, labelVar=TRUE)

(Tak, wiem, że nie powinieneś tego robić operacyjnie, RF to czarna skrzynka itp. Itp. Chcę wizualnie sprawdzić poprawność drzewa, aby zobaczyć, czy jakieś zmienne zachowują się nieintuicyjnie, potrzebuję ulepszenia / połączenia / dyskretyzacji / transformacji, sprawdź jak dobrze działają moje zakodowane czynniki itp.)


Wcześniejsze pytania bez przyzwoitej odpowiedzi:

Tak naprawdę chcę wykreślić przykładowe drzewo . Więc już się ze mną nie sprzeczaj. Nie pytam o varImpPlot(Zmienna ważność wykresu) lub partialPlotlub MDSPlot, lub te inne wykresy , już je mam, ale nie zastępują widoku przykładowego drzewa. Tak, mogę wizualnie sprawdzić wydajność getTree(...,labelVar=TRUE).

(Myślę, że plot.rf.tree()wkład byłby bardzo dobrze przyjęty.)

smci
źródło
6
Nie widzę potrzeby prewencyjnej argumentacji, zwłaszcza jeśli prosisz kogoś, kto zgłosi się do pomocy; nie wychodzi dobrze. W CV obowiązuje zasada etykiety - możesz przeczytać nasze FAQ .
gung - Przywróć Monikę
9
@gung: każde poprzednie pytanie na ten temat rozpadło się na ludzi, którzy twierdzili, że nie jest konieczne, a wręcz heretyckie, wykreślić przykładowe drzewo. Przeczytaj cytowane przeze mnie cytaty. Szukam szkicu, jak kodować drzewo rf.
smci,
3
Widzę odpowiedzi, w których użytkownicy starają się być pomocni i odpowiadają na pytania, wraz z niektórymi komentarzami kwestionującymi przesłankę pomysłu (które, szczerze wierzę, są również zamierzone w duchu pomocy). Z pewnością można uznać, że niektórzy ludzie nie będą się zgadzać bez testów.
gung - Przywróć Monikę
4
Widzę zero odpowiedzi, gdzie ktokolwiek kiedykolwiek planował drzewo, od ponad roku. Szukam konkretnej odpowiedzi na to konkretne pytanie.
smci
1
Można wykreślić pojedyncze drzewo zbudowane za pomocą cforest(w pakiecie imprezowym ). W przeciwnym razie będziesz musiał przekonwertować data.framezwrócony przez randomForest::getTreedo tree-Jak obiektu.
chl

Odpowiedzi:

44

Pierwsze (i najłatwiejsze) rozwiązanie: jeśli nie chcesz trzymać się klasycznego RF, jak zaimplementowano w Andy Liaw's randomForest, możesz wypróbować pakiet imprezowy , który zapewnia inną implementację oryginalnego algorytmu RF (użycie drzew warunkowych i schematu agregacji na podstawie od średniej masy jednostek). Następnie, zgodnie z tym, co napisano w tym poście z pomocą R , możesz wykreślić jednego członka listy drzew. O ile mi wiadomo, wydaje się, że działa płynnie. Poniżej znajduje się wykres jednego drzewa wygenerowanego przez cforest(Species ~ ., data=iris, controls=cforest_control(mtry=2, mincriterion=0)).

wprowadź opis zdjęcia tutaj

Drugie (prawie tak łatwe) Rozwiązanie: Większość technik oparty na drzewie w R ( tree, rpart, TWIX, itd.) Oferuje tree-Jak strukturę do druku / wykreślenie jednego drzewa. Ideą byłoby przekonwertowanie wyniku randomForest::getTreena taki obiekt R, nawet jeśli jest to bezsensowne z statystycznego punktu widzenia. Zasadniczo łatwo jest uzyskać dostęp do struktury drzewa z treeobiektu, jak pokazano poniżej. Należy pamiętać, że będzie się nieco różnić w zależności od rodzaju zadania - regresja vs. klasyfikacja - gdzie w późniejszym przypadku doda prawdopodobieństwa specyficzne dla klasy jako ostatnią kolumnę obj$frame(która jest a data.frame).

> library(tree)
> tr <- tree(Species ~ ., data=iris)
> tr
node), split, n, deviance, yval, (yprob)
      * denotes terminal node

 1) root 150 329.600 setosa ( 0.33333 0.33333 0.33333 )  
   2) Petal.Length < 2.45 50   0.000 setosa ( 1.00000 0.00000 0.00000 ) *
   3) Petal.Length > 2.45 100 138.600 versicolor ( 0.00000 0.50000 0.50000 )  
     6) Petal.Width < 1.75 54  33.320 versicolor ( 0.00000 0.90741 0.09259 )  
      12) Petal.Length < 4.95 48   9.721 versicolor ( 0.00000 0.97917 0.02083 )  
        24) Sepal.Length < 5.15 5   5.004 versicolor ( 0.00000 0.80000 0.20000 ) *
        25) Sepal.Length > 5.15 43   0.000 versicolor ( 0.00000 1.00000 0.00000 ) *
      13) Petal.Length > 4.95 6   7.638 virginica ( 0.00000 0.33333 0.66667 ) *
     7) Petal.Width > 1.75 46   9.635 virginica ( 0.00000 0.02174 0.97826 )  
      14) Petal.Length < 4.95 6   5.407 virginica ( 0.00000 0.16667 0.83333 ) *
      15) Petal.Length > 4.95 40   0.000 virginica ( 0.00000 0.00000 1.00000 ) *
> tr$frame
            var   n        dev       yval splits.cutleft splits.cutright yprob.setosa yprob.versicolor yprob.virginica
1  Petal.Length 150 329.583687     setosa          <2.45           >2.45   0.33333333       0.33333333      0.33333333
2        <leaf>  50   0.000000     setosa                                  1.00000000       0.00000000      0.00000000
3   Petal.Width 100 138.629436 versicolor          <1.75           >1.75   0.00000000       0.50000000      0.50000000
6  Petal.Length  54  33.317509 versicolor          <4.95           >4.95   0.00000000       0.90740741      0.09259259
12 Sepal.Length  48   9.721422 versicolor          <5.15           >5.15   0.00000000       0.97916667      0.02083333
24       <leaf>   5   5.004024 versicolor                                  0.00000000       0.80000000      0.20000000
25       <leaf>  43   0.000000 versicolor                                  0.00000000       1.00000000      0.00000000
13       <leaf>   6   7.638170  virginica                                  0.00000000       0.33333333      0.66666667
7  Petal.Length  46   9.635384  virginica          <4.95           >4.95   0.00000000       0.02173913      0.97826087
14       <leaf>   6   5.406735  virginica                                  0.00000000       0.16666667      0.83333333
15       <leaf>  40   0.000000  virginica                                  0.00000000       0.00000000      1.00000000

Następnie istnieją metody ładnego drukowania i drukowania tych obiektów. Kluczowe funkcje to ogólna tree:::plot.treemetoda (stawiam potrójną, :która pozwala zobaczyć kod w R bezpośrednio), polegając na tree:::treepl(wyświetlanie graficzne) i tree:::treeco(obliczanie współrzędnych węzłów). Funkcje te oczekują obj$framereprezentacji drzewa. Inne subtelne kwestie: (1) argument type = c("proportional", "uniform")w domyślnej metodzie kreślenia tree:::plot.tree, pomaga zarządzać odległością pionową między węzłami ( proportionaloznacza, że ​​jest proporcjonalna do odchylenia, uniformoznacza, że ​​jest ustalona); (2) musisz uzupełnić plot(tr)wezwaniem, aby text(tr)dodać etykiety tekstowe do węzłów i podziałów, co w tym przypadku oznacza, że ​​będziesz musiał także rzucić okiem tree:::text.tree.

getTreeMetoda od randomForestzwrotów inna konstrukcja, która jest opisana w pomocy online. Typowe wyjście pokazano poniżej, z węzłami końcowymi oznaczonymi statuskodem (-1). (Ponownie, dane wyjściowe będą się różnić w zależności od rodzaju zadania, ale tylko od kolumn statusi prediction).

> library(randomForest)
> rf <- randomForest(Species ~ ., data=iris)
> getTree(rf, 1, labelVar=TRUE)
   left daughter right daughter    split var split point status prediction
1              2              3 Petal.Length        4.75      1       <NA>
2              4              5 Sepal.Length        5.45      1       <NA>
3              6              7  Sepal.Width        3.15      1       <NA>
4              8              9  Petal.Width        0.80      1       <NA>
5             10             11  Sepal.Width        3.60      1       <NA>
6              0              0         <NA>        0.00     -1  virginica
7             12             13  Petal.Width        1.90      1       <NA>
8              0              0         <NA>        0.00     -1     setosa
9             14             15  Petal.Width        1.55      1       <NA>
10             0              0         <NA>        0.00     -1 versicolor
11             0              0         <NA>        0.00     -1     setosa
12            16             17 Petal.Length        5.40      1       <NA>
13             0              0         <NA>        0.00     -1  virginica
14             0              0         <NA>        0.00     -1 versicolor
15             0              0         <NA>        0.00     -1  virginica
16             0              0         <NA>        0.00     -1 versicolor
17             0              0         <NA>        0.00     -1  virginica

Jeśli uda ci się przekształcić powyższą tabelę do jednego generowane przez tree, prawdopodobnie będzie w stanie dostosować tree:::treepl, tree:::treecoi tree:::text.treedo własnych potrzeb, choć nie mam przykład tego podejścia. W szczególności prawdopodobnie chcesz pozbyć się dewiacji, prawdopodobieństw klasowych itp., Które nie mają znaczenia w RF. Wszystko, czego potrzebujesz, to ustawić współrzędne węzłów i podzielić wartości. Możesz fixInNamespace()do tego wykorzystać , ale szczerze mówiąc, nie jestem pewien, czy jest to właściwa droga.

Trzecie (i na pewno sprytne) rozwiązanie: Napisz prawdziwą as.treefunkcję pomocnika, która złagodzi wszystkie powyższe „łaty”. Następnie możesz użyć metod kreślenia R lub, prawdopodobnie lepiej, Klimta (bezpośrednio z R), aby wyświetlić pojedyncze drzewa.

chl
źródło
40

Spóźniłem się cztery lata, ale jeśli naprawdę chcesz trzymać się randomForestpakietu (a istnieją ku temu dobre powody) i chcesz wizualizować drzewo, możesz użyć pakietu reprtree .

Pakiet nie jest bardzo dobrze udokumentowany (możesz znaleźć tutaj dokumenty ), ale wszystko jest dość proste. Aby zainstalować pakiet, zapoznaj się z plikiem initialize.R w repozytorium, więc po prostu uruchom następujące polecenie:

options(repos='http://cran.rstudio.org')
have.packages <- installed.packages()
cran.packages <- c('devtools','plotrix','randomForest','tree')
to.install <- setdiff(cran.packages, have.packages[,1])
if(length(to.install)>0) install.packages(to.install)

library(devtools)
if(!('reprtree' %in% installed.packages())){
  install_github('araastat/reprtree')
}
for(p in c(cran.packages, 'reprtree')) eval(substitute(library(pkg), list(pkg=p)))

Następnie wykonaj model i drzewo:

library(randomForest)
library(reprtree)

model <- randomForest(Species ~ ., data=iris, importance=TRUE, ntree=500, mtry = 2, do.trace=100)

reprtree:::plot.getTree(model)

I proszę bardzo! Piękny i prosty.

drzewo wygenerowane z plot.getTree (model)

Możesz sprawdzić repozytorium github, aby dowiedzieć się o innych metodach w pakiecie. W rzeczywistości, jeśli zaznaczysz plot.getTree.R , zauważysz, że autor używa własnej implementacji, z as.tree()której chl ♦ sugerował, że możesz zbudować siebie w swojej odpowiedzi. Oznacza to, że możesz to zrobić:

tree <- getTree(model, k=1, labelVar=TRUE)
realtree <- reprtree:::as.tree(tree, model)

A następnie potencjalnie użyj realtreez innymi pakietami drzewa, takimi jak drzewo .

jgozal
źródło
Bardzo dziękuję, nadal chętnie przyjmuję odpowiedzi, wydaje się, że jest to obszar, w którym ludzie są niezadowoleni z ofert. Myślę, że nową rzeczą byłoby również wsparcie xgboost.
smci
6
nie ma problemu. Znalezienie biblioteki / paczki zajęło mi wiele godzin, więc pomyślałem, że jeśli nie będzie to dla ciebie przydatne, to inne osoby będą próbowały rysować drzewa, wciąż trzymając się randomForestpaczki.
jgozal
2
Fajne znalezienie. Uwaga: Rysuje reprezentatywne drzewo, w pewnym sensie, drzewo w zespole, które są średnio „najbliższe” do wszystkich innych drzew w zespole
Chris
2
@Chris Funkcja drukuje plot.getTree()pojedyncze drzewo. Funkcja plot.reprtree()w tym pakiecie drukuje reprezentatywne drzewo.
Chun Li
1
wziąłem model z karetki i chcę karmić się w gadzie reprtree:::plot.getTree(mod_rf_1$finalModel), jednak występuje „Błąd w data.frame (var = fr $ var, splits = as.character (gTree [,„ split point ”])): argumenty sugerują różne liczba rzędów: 2631, 0 "
HappyCoding
15

Stworzyłem kilka funkcji do wyodrębnienia reguł drzewa.

#**************************
#return the rules of a tree
#**************************
getConds<-function(tree){
  #store all conditions into a list
  conds<-list()
  #start by the terminal nodes and find previous conditions
  id.leafs<-which(tree$status==-1)
	  j<-0
	  for(i in id.leafs){
		j<-j+1
		prevConds<-prevCond(tree,i)
		conds[[j]]<-prevConds$cond
		while(prevConds$id>1){
		  prevConds<-prevCond(tree,prevConds$id)
		  conds[[j]]<-paste(conds[[j]]," & ",prevConds$cond)
        }
		if(prevConds$id==1){
			conds[[j]]<-paste(conds[[j]]," => ",tree$prediction[i])
    }
    }

  }

  return(conds)
}

#**************************
#find the previous conditions in the tree
#**************************
prevCond<-function(tree,i){
  if(i %in% tree$right_daughter){
		id<-which(tree$right_daughter==i)
		cond<-paste(tree$split_var[id],">",tree$split_point[id])
	  }
	  if(i %in% tree$left_daughter){
    id<-which(tree$left_daughter==i)
		cond<-paste(tree$split_var[id],"<",tree$split_point[id])
  }

  return(list(cond=cond,id=id))
}

#remove spaces in a word
collapse<-function(x){
  x<-sub(" ","_",x)

  return(x)
}


data(iris)
require(randomForest)
mod.rf <- randomForest(Species ~ ., data=iris)
tree<-getTree(mod.rf, k=1, labelVar=TRUE)
#rename the name of the column
colnames(tree)<-sapply(colnames(tree),collapse)
rules<-getConds(tree)
print(rules)
Dalpozz
źródło