Primeira (e mais fácil) solução: se você não gosta de usar a RF clássica, como implementado no Andy Liaw randomForest
, pode experimentar o pacote de terceiros que fornece uma implementação diferente do algoritmo RF ™ original (uso de árvores condicionais e esquema de agregação baseado em unidades de peso médio). Então, conforme relatado neste post de ajuda R , você pode plotar um único membro da lista de árvores. Parece funcionar sem problemas, até onde eu sei. Abaixo está um gráfico de uma árvore gerada por cforest(Species ~ ., data=iris, controls=cforest_control(mtry=2, mincriterion=0))
.
Segundo (quase tão fácil) solução: A maioria das técnicas baseadas em árvores em R ( tree
, rpart
, TWIX
, etc.) oferece uma tree
estrutura -como para impressão / plotagem uma única árvore. A idéia seria converter a saída de randomForest::getTree
um objeto desse tipo R, mesmo que isso não faça sentido do ponto de vista estatístico. Basicamente, é fácil acessar a estrutura em árvore a partir de um tree
objeto, como mostrado abaixo. Observe que ela diferirá um pouco, dependendo do tipo de tarefa - regressão versus classificação - onde, no caso posterior, adicionará probabilidades específicas de classe como a última coluna da obj$frame
(que é 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
Depois, existem métodos para imprimir e plotar esses objetos de maneira bonita. As funções das teclas são um tree:::plot.tree
método genérico (coloquei um triplo :
que permite visualizar o código em R diretamente) contando com tree:::treepl
(exibição gráfica) e tree:::treeco
(coordenadas dos nós de cálculo). Essas funções esperam a obj$frame
representação da árvore. Outras questões sutis: (1) o argumento type = c("proportional", "uniform")
no método de plotagem padrão tree:::plot.tree
ajuda a gerenciar a distância vertical entre os nós ( proportional
significa que é proporcional ao desvio, uniform
significa que é fixo); (2) você precisa complementar plot(tr)
com uma chamada para text(tr)
adicionar rótulos de texto a nós e divisões, o que, neste caso, significa que você também precisará dar uma olhada tree:::text.tree
.
O getTree
método from randomForest
retorna uma estrutura diferente, documentada na ajuda online. Uma saída típica é mostrada abaixo, com os nós do terminal indicados pelo status
código (-1). (Novamente, a saída será diferente dependendo do tipo de tarefa, mas apenas nas colunas status
e 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
Se você pode conseguir converter a tabela acima para o gerado por tree
, provavelmente você vai ser capaz de personalizar tree:::treepl
, tree:::treeco
e tree:::text.tree
para atender às suas necessidades, embora eu não tenho um exemplo desta abordagem. Em particular, você provavelmente deseja se livrar do uso de desvio, probabilidades de classe etc. que não são significativas na RF. Tudo o que você deseja é configurar as coordenadas dos nós e dividir os valores. Você pode usar fixInNamespace()
isso, mas, para ser sincero, não tenho certeza se esse é o caminho certo a seguir.
Terceira (e certamente inteligente) solução: Escreva uma verdadeira as.tree
função auxiliar que alivie todos os "patches" acima. Você poderia usar os métodos de plotagem de R ou, provavelmente melhor, Klimt (diretamente de R) para exibir árvores individuais.