Отображать больше атрибутов в дереве решений

В настоящее время я просматриваю дерево решений, используя следующий код. Есть ли способ экспортировать некоторые вычисленные поля в качестве вывода тоже?

Например, можно ли отображать сумму входного атрибута при каждом node, то есть сумме функции 1 из массива данных 'X' в листах дерева.

from sklearn import datasets

iris = datasets.load_iris()
X = iris.data[:]  
y = iris.target
#%%
from sklearn.tree import DecisionTreeClassifier
alg=DecisionTreeClassifier( max_depth=5,min_samples_leaf=2, max_leaf_nodes = 10)
alg.fit(X,y)

#%%
## View tree
import graphviz
from sklearn import tree
dot_data = tree.export_graphviz(alg,out_file=None, node_ids = True, proportion = True, class_names = True, filled = True, rounded = True)
graph = graphviz.Source(dot_data)
graph

введите описание изображения здесь

Ответ 1

Существует много дискуссий о деревьях решений в scikit-learn на странице github. Есть ответы на этот вопрос SO и эта страница с документами scikit-learn, которые предоставляют рамки, чтобы вы начали. Со всеми ссылками в стороне, вот некоторые функции, которые позволяют пользователю решать вопрос в обобщаемом виде. Функции могут быть легко изменены, так как я не знаю, имеете ли вы все листья или каждый лист в отдельности. Мой подход - последний.

Первая функция использует apply как дешевый способ найти индексы листовых узлов. Нет необходимости добиваться того, о чем вы просите, но я включил его в качестве удобства, так как вы упомянули, что хотите исследовать листовые узлы, а листовые индексы node могут быть неизвестны априори.

def find_leaves(X, clf):
    """A cheap function to find leaves of a DecisionTreeClassifier
    clf must be a fitted DecisionTreeClassifier
    """
    return set(clf.apply(X))

Результат на примере:

find_leaves(X, alg)
{1, 7, 8, 9, 10, 11, 12}

Следующая функция вернет массив значений, которые удовлетворяют условиям node и feature, где node - это индекс node из дерева, для которого вы хотите значения, и feature является столбца (или функции), который вы хотите от X.

def node_feature_values(X, clf, node=0, feature=0, require_leaf=False):
    """this function will return an array of values 
    from the input array X. Array values will be limited to
     1. samples that passed through <node> 
     2. and from the feature <feature>.

    clf must be a fitted DecisionTreeClassifier
    """
    leaf_ids = find_leaves(X, clf)
    if (require_leaf and
        node not in leaf_ids):
        print("<require_leaf> is set, "
                "select one of these nodes:\n{}".format(leaf_ids))
        return

    # a sparse array that contains node assignment by sample
    node_indicator = clf.decision_path(X)
    node_array = node_indicator.toarray()

    # which samples at least passed through the node
    samples_in_node_mask = node_array[:,node]==1

    return X[samples_in_node_mask, feature]

Применяется к примеру:

values_arr = node_feature_values(X, alg, node=12, feature=0, require_leaf=True)

array([6.3, 5.8, 7.1, 6.3, 6.5, 7.6, 7.3, 6.7, 7.2, 6.5, 6.4, 6.8, 5.7,
       5.8, 6.4, 6.5, 7.7, 7.7, 6.9, 5.6, 7.7, 6.3, 6.7, 7.2, 6.1, 6.4,
       7.4, 7.9, 6.4, 7.7, 6.3, 6.4, 6.9, 6.7, 6.9, 5.8, 6.8, 6.7, 6.7,
       6.3, 6.5, 6.2, 5.9])

Теперь пользователь может выполнить любую математическую операцию в подмножестве выборок для данной функции.

то есть. сумма признака 1 из массива данных 'X' в листах дерева.

print("There are {} total samples in this node, "
      "{}% of the total".format(len(values_arr), len(values_arr) / float(len(X))*100))
print("Feature Sum: {}".format(values_arr.sum()))

There are 43 total samples in this node,28.666666666666668% of the total
Feature Sum: 286.69999999999993

Обновление
После повторного чтения вопроса это единственное решение, которое я могу быстро собрать, не требуя изменения исходного кода scikit для export.py, Код ниже по-прежнему зависит от ранее определенных функций. Этот код изменяет строку dot через pydot и networkx.

# Load the data from `dot_data` variable, which you defined.
import pydot
dot_graph = pydot.graph_from_dot_data(dot_data)[0]

import networkx as nx
MG = nx.nx_pydot.from_pydot(dot_graph)

# Select a `feature` and edit the `dot` string in `networkx`.
feature = 0
for n in find_leaves(X, alg):
    nfv = node_feature_values(X, alg, node=n, feature=feature)
    MG.node[str(n)]['label'] = MG.node[str(n)]['label'] + "\nfeature_{} sum: {}".format(feature, nfv.sum())

# Export the `networkx` graph then plot using `graphviz.Source()`
new_dot_data = nx.nx_pydot.to_pydot(MG)
graph = graphviz.Source(new_dot_data.create_dot())
graph

настраиваемый граф дерева решений

Обратите внимание, что у всех листов есть сумма значений от X для функции 0. Я думаю, что лучший способ выполнить то, что вы просите, - это изменить tree.py и/или export.py, чтобы поддерживать эту функцию.