Ich gebe zu, ein mittelmäßiger C-Code-Interpreter zu sein, und dieser alte Code ist nicht benutzerfreundlich. Das heißt, ich habe den Quellcode durchgesehen und diese Beobachtungen gemacht, was mich ziemlich sicher macht zu sagen: "rpart wählt buchstäblich die erste und beste variable Spalte aus". Da die Spalten 1 und 2 minderwertige Teilungen erzeugen, ist die Blütenblattlänge zuerst eine Teilungsvariable, da diese Spalte in data.frame / matrix vor der Blütenblattbreite steht. Zuletzt zeige ich dies, indem ich die Spaltenreihenfolge so invertiere, dass petal.with zuerst die Split-Variable ist.
In der c-Quelldatei "bsplit.c" im Quellcode für Teil zitiere ich aus Zeile 38:
* test out the variables 1 at at time
me->primary = (pSplit) NULL;
for (i = 0; i < rp.nvar; i++) {
... und so wird in einer for-Schleife von i = 1 bis rp.nvar eine Verlustfunktion aufgerufen, um alle Teilungen durch eine Variable zu scannen. Innerhalb von gini.c wird nach der Zeile 230 für "nicht kategoriale Teilung" die am besten gefundene Teilung gesucht aktualisiert, wenn eine neue Aufteilung besser ist. (Dies könnte auch eine benutzerdefinierte Verlustfunktion sein)
if (temp < best) {
best = temp;
where = i;
direction = lmean < rmean ? LEFT : RIGHT;
}
und letzte Zeile 323 wird die Verbesserung für die beste Aufteilung durch eine Variable berechnet ...
*improve = total_ss - best
... zurück in bsplit.c wird die Verbesserung überprüft, wenn sie größer als zuvor ist, und nur aktualisiert, wenn sie größer ist.
if (improve > rp.iscale)
rp.iscale = improve; /* largest seen so far */
Mein Eindruck dabei ist, dass das erste und beste (von möglichen Unentschieden ausgewählt wird), denn nur wenn ein neuer Haltepunkt eine bessere Punktzahl hat, wird er gespeichert. Dies betrifft sowohl den ersten besten gefundenen Haltepunkt als auch die erste beste gefundene Variable. Haltepunkte scheinen in gini.c nicht einfach von links nach rechts gescannt zu werden, daher kann es schwierig sein, den ersten gefundenen Bindungsbruchpunkt vorherzusagen. Variablen sind jedoch sehr vorhersehbar und werden von der ersten bis zur letzten Spalte gescannt.
Dieses Verhalten unterscheidet sich von der randomForest-Implementierung, bei der in classTree.c die folgende Lösung verwendet wird:
/* Break ties at random: */
if (crit == critmax) {
if (unif_rand() < 1.0 / ntie) {
*bestSplit = j;
critmax = crit;
*splitVar = mvar;
}
ntie++;
}
Zuletzt bestätige ich dieses Verhalten, indem ich die Iris-Spalten umdrehe, sodass zuerst die Blütenblattbreite ausgewählt wird
library(rpart)
data(iris)
iris = iris[,5:1] #flip/flop", invert order of columns columns
obj = rpart(Species~.,data=iris)
print(obj) #now petal width is first split
1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)
2) Petal.Width< 0.8 50 0 setosa (1.00000000 0.00000000 0.00000000) *
3) Petal.Width>=0.8 100 50 versicolor (0.00000000 0.50000000 0.50000000)
6) Petal.Width< 1.75 54 5 versicolor (0.00000000 0.90740741 0.09259259) *
7) Petal.Width>=1.75 46 1 virginica (0.00000000 0.02173913 0.97826087) *
... und wieder zurückblättern
iris = iris[,5:1] #flop/flip", revert order of columns columns
obj = rpart(Species~.,data=iris)
print(obj) #now petal length is first split
1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)
2) Petal.Length< 2.45 50 0 setosa (1.00000000 0.00000000 0.00000000) *
3) Petal.Length>=2.45 100 50 versicolor (0.00000000 0.50000000 0.50000000)
6) Petal.Width< 1.75 54 5 versicolor (0.00000000 0.90740741 0.09259259) *
7) Petal.Width>=1.75 46 1 virginica (0.00000000 0.02173913 0.97826087) *