Результат rpart - это корень, но данные показывают информационное усиление

У меня есть набор данных со скоростью менее 3% (т.е. около 700 записей с данными класса 1 и 27000 с классом 0).

ID          V1  V2      V3  V5      V6  Target
SDataID3    161 ONE     1   FOUR    0   0
SDataID4    11  TWO     2   THREE   2   1
SDataID5    32  TWO     2   FOUR    2   0
SDataID7    13  ONE     1   THREE   2   0
SDataID8    194 TWO     2   FOUR    0   0
SDataID10   63  THREE   3   FOUR    0   1
SDataID11   89  ONE     1   FOUR    0   0
SDataID13   78  TWO     2   FOUR    0   0
SDataID14   87  TWO     2   THREE   1   0
SDataID15   81  ONE     1   THREE   0   0
SDataID16   63  ONE     3   FOUR    0   0
SDataID17   198 ONE     3   THREE   0   0
SDataID18   9   TWO     3   THREE   0   0
SDataID19   196 ONE     2   THREE   2   0
SDataID20   189 TWO     2   ONE     1   0
SDataID21   116 THREE   3   TWO     0   0
SDataID24   104 ONE     1   FOUR    0   0
SDataID25   5   ONE     2   ONE     3   0
SDataID28   173 TWO     3   FOUR    0   0
SDataID29   5   ONE     3   ONE     3   0
SDataID31   87  ONE     3   FOUR    3   0
SDataID32   5   ONE     2   THREE   1   0
SDataID34   45  ONE     1   FOUR    0   0
SDataID35   19  TWO     2   THREE   0   0
SDataID37   133 TWO     2   FOUR    0   0
SDataID38   8   ONE     1   THREE   0   0
SDataID39   42  ONE     1   THREE   0   0
SDataID43   45  ONE     1   THREE   1   0
SDataID44   45  ONE     1   FOUR    0   0
SDataID45   176 ONE     1   FOUR    0   0
SDataID46   63  ONE     1   THREE   3   0

Я пытаюсь найти split, используя дерево решений. Но результат дерева - только 1 корень.

> library(rpart)
> tree <- rpart(Target ~ ., data=subset(train, select=c( -Record.ID) ),method="class")
> printcp(tree)

Classification tree:
rpart(formula = Target ~ ., data = subset(train, select = c(-Record.ID)), method = "class")

Variables actually used in tree construction:
character(0)

Root node error: 749/18239 = 0.041066

n= 18239 

  CP nsplit rel error xerror xstd
1  0      0         1      0    0

После прочтения большинства ресурсов на StackOverflow я ослабил/изменил параметры управления, которые дали мне желаемое дерево решений.

> tree <- rpart(Target ~ ., data=subset(train, select=c( -Record.ID) ),method="class" ,control =rpart.control(minsplit = 1,minbucket=2, cp=0.00002))
> printcp(tree)

Classification tree:
rpart(formula = Target ~ ., data = subset(train, select = c(-Record.ID)), 
    method = "class", control = rpart.control(minsplit = 1, minbucket = 2, 
        cp = 2e-05))

Variables actually used in tree construction:
[1] V5         V2                     V1          
[4] V3         V6

Root node error: 749/18239 = 0.041066

n= 18239 

          CP nsplit rel error xerror     xstd
1 0.00024275      0   1.00000 1.0000 0.035781
2 0.00019073     20   0.99466 1.0267 0.036235
3 0.00016689     34   0.99199 1.0307 0.036302
4 0.00014835     54   0.98798 1.0334 0.036347
5 0.00002000     63   0.98665 1.0427 0.036504

Когда я обрезал дерево, это привело к дереву с единственным node.

> pruned.tree <- prune(tree, cp = tree$cptable[which.min(tree$cptable[,"xerror"]),"CP"])
> printcp(pruned.tree)

Classification tree:
rpart(formula = Target ~ ., data = subset(train, select = c(-Record.ID)), 
    method = "class", control = rpart.control(minsplit = 1, minbucket = 2, 
        cp = 2e-05))

Variables actually used in tree construction:
character(0)

Root node error: 749/18239 = 0.041066

n= 18239 

          CP nsplit rel error xerror     xstd
1 0.00024275      0         1      1 0.035781

Дерево не должно выдавать только root node, потому что математически, при заданном node (пример предоставлен) мы получаем Информационный выигрыш. Я не знаю, допустил ли я ошибку при обрезке или возникла проблема с rpart при обработке набора данных с низкой частотой событий?

NODE    p       1-p     Entropy         Weights         Ent*Weight      # Obs
Node 1  0.032   0.968   0.204324671     0.351398601     0.071799404     10653
Node 2  0.05    0.95    0.286396957     0.648601399     0.185757467     19663

Sum(Ent*wght)       0.257556871 
Information gain    0.742443129 

Ответ 1

Представленные вами данные не отражают соотношение двух целевых классов, поэтому я изменил данные, чтобы лучше отразить это (см. раздел "Данные" ):

> prop.table(table(train$Target))

         0          1 
0.96707581 0.03292419 

> 700/27700
[1] 0.02527076

Отношение теперь относительно близко...

library(rpart)
tree <- rpart(Target ~ ., data=train, method="class")
printcp(tree)

Результаты в:

Classification tree:
rpart(formula = Target ~ ., data = train, method = "class")

Variables actually used in tree construction:
character(0)

Root node error: 912/27700 = 0.032924

n= 27700 

  CP nsplit rel error xerror xstd
1  0      0         1      0    0

Теперь причина, по которой вы видите только root node для вашей первой модели, вероятно, связана с тем, что у вас чрезвычайно несбалансированные целевые классы, и поэтому ваши независимые переменные не могут предоставить достаточную информацию для роста дерево. У моих выборочных данных 3,3%, но у вас всего около 2,5%!

Как вы уже упоминали, существует способ заставить rpart вырастить дерево. То есть переопределить параметр сложности по умолчанию (cp). Мера сложности - это комбинация размера дерева и того, насколько дерево разделяет целевые классы. Из ?rpart.control "Любой раскол, который не уменьшает общее отсутствие соответствия коэффициентом cp, не предпринимается". Это означает, что ваша модель в данный момент не имеет раскола за пределами корня node, что снижает уровень сложности, достаточный для rpart, чтобы принять во внимание. Мы можем уменьшить этот порог того, что считается "достаточным", либо установив низкий, либо отрицательный cp (отрицательный cp в основном заставляет дерево расти до его полного размера).

tree <- rpart(Target ~ ., data=train, method="class" ,parms = list(split = 'information'), 
              control =rpart.control(minsplit = 1,minbucket=2, cp=0.00002))
printcp(tree)

Результаты в:

Classification tree:
rpart(formula = Target ~ ., data = train, method = "class", parms = list(split = "information"), 
    control = rpart.control(minsplit = 1, minbucket = 2, cp = 2e-05))

Variables actually used in tree construction:
[1] ID V1 V2 V3 V5 V6

Root node error: 912/27700 = 0.032924

n= 27700 

           CP nsplit rel error xerror     xstd
1  4.1118e-04      0   1.00000 1.0000 0.032564
2  3.6550e-04     30   0.98355 1.0285 0.033009
3  3.2489e-04     45   0.97807 1.0702 0.033647
4  3.1328e-04    106   0.95504 1.0877 0.033911
5  2.7412e-04    116   0.95175 1.1031 0.034141
6  2.5304e-04    132   0.94737 1.1217 0.034417
7  2.1930e-04    149   0.94298 1.1458 0.034771
8  1.9936e-04    159   0.94079 1.1502 0.034835
9  1.8275e-04    181   0.93640 1.1645 0.035041
10 1.6447e-04    193   0.93421 1.1864 0.035356
11 1.5664e-04    233   0.92654 1.1853 0.035341
12 1.3706e-04    320   0.91228 1.2083 0.035668
13 1.2183e-04    344   0.90899 1.2127 0.035730
14 9.9681e-05    353   0.90789 1.2237 0.035885
15 2.0000e-05    364   0.90680 1.2259 0.035915

Как вы можете видеть, дерево выросло до размера, что снижает уровень сложности минимум на cp. Следует отметить две вещи:

  • В нуле nsplit значение cp уже равно 0,0004, где по умолчанию cp в rpart установлено значение 0,01.
  • Начиная с nsplit == 0, ошибка перекрестной проверки (xerror) увеличивается по мере увеличения количества разделов.

Оба из них указывают на то, что ваша модель переопределяет данные в nsplit == 0 и выше, поскольку добавление в вашу модель независимых переменных не добавляет достаточной информации (недостаточное сокращение CP), чтобы уменьшить ошибку перекрестной проверки. С учетом сказанного, ваша корневая модель node является лучшей моделью в этом случае, что объясняет, почему ваша исходная модель имеет только корень node.

pruned.tree <- prune(tree, cp = tree$cptable[which.min(tree$cptable[,"xerror"]),"CP"])
printcp(pruned.tree)

Результаты в:

Classification tree:
rpart(formula = Target ~ ., data = train, method = "class", parms = list(split = "information"), 
    control = rpart.control(minsplit = 1, minbucket = 2, cp = 2e-05))

Variables actually used in tree construction:
character(0)

Root node error: 912/27700 = 0.032924

n= 27700 

          CP nsplit rel error xerror     xstd
1 0.00041118      0         1      1 0.032564

Что касается части обрезки, теперь становится яснее, почему ваше обрезанное дерево является корневым деревом node, так как дерево, выходящее за рамки 0, имеет большую ошибку перекрестной проверки. Взятие дерева с минимальным значением xerror оставило бы вас с корнем node, как и ожидалось.

Информационный выигрыш в основном говорит вам, сколько "информации" добавляется для каждого раскола. Так что технически, каждый раскол имеет некоторую степень получения информации, поскольку вы добавляете в свою модель больше переменных (коэффициент полезного действия всегда неотрицателен). То, о чем вы должны думать, заключается в том, уменьшает ли этот дополнительный выигрыш (или нет коэффициента усиления) ошибки, достаточные для того, чтобы вы могли использовать более сложную модель. Следовательно, компромисс между смещением и дисперсией.

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

Данные:

Обратите внимание, что я перетасовываю строки для каждого столбца и образца вместо выборки индексов строк. Это связано с тем, что предоставленные вами данные, вероятно, не являются случайным образцом вашего исходного набора данных (вероятно, предвзятым), поэтому я в основном беспорядочно создаю новые наблюдения с комбинациями существующих строк, которые, надеюсь, уменьшат это смещение.

init_train = structure(list(ID = structure(c(16L, 24L, 29L, 30L, 31L, 1L, 
2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 
17L, 18L, 19L, 20L, 21L, 22L, 23L, 25L, 26L, 27L, 28L), .Label = c("SDataID10", 
"SDataID11", "SDataID13", "SDataID14", "SDataID15", "SDataID16", 
"SDataID17", "SDataID18", "SDataID19", "SDataID20", "SDataID21", 
"SDataID24", "SDataID25", "SDataID28", "SDataID29", "SDataID3", 
"SDataID31", "SDataID32", "SDataID34", "SDataID35", "SDataID37", 
"SDataID38", "SDataID39", "SDataID4", "SDataID43", "SDataID44", 
"SDataID45", "SDataID46", "SDataID5", "SDataID7", "SDataID8"), class = "factor"), 
    V1 = c(161L, 11L, 32L, 13L, 194L, 63L, 89L, 78L, 87L, 81L, 
    63L, 198L, 9L, 196L, 189L, 116L, 104L, 5L, 173L, 5L, 87L, 
    5L, 45L, 19L, 133L, 8L, 42L, 45L, 45L, 176L, 63L), V2 = structure(c(1L, 
    3L, 3L, 1L, 3L, 2L, 1L, 3L, 3L, 1L, 1L, 1L, 3L, 1L, 3L, 2L, 
    1L, 1L, 3L, 1L, 1L, 1L, 1L, 3L, 3L, 1L, 1L, 1L, 1L, 1L, 1L
    ), .Label = c("ONE", "THREE", "TWO"), class = "factor"), 
    V3 = c(1L, 2L, 2L, 1L, 2L, 3L, 1L, 2L, 2L, 1L, 3L, 3L, 3L, 
    2L, 2L, 3L, 1L, 2L, 3L, 3L, 3L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 
    1L, 1L, 1L), V5 = structure(c(1L, 3L, 1L, 3L, 1L, 1L, 1L, 
    1L, 3L, 3L, 1L, 3L, 3L, 3L, 2L, 4L, 1L, 2L, 1L, 2L, 1L, 3L, 
    1L, 3L, 1L, 3L, 3L, 3L, 1L, 1L, 3L), .Label = c("FOUR", "ONE", 
    "THREE", "TWO"), class = "factor"), V6 = c(0L, 2L, 2L, 2L, 
    0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 2L, 1L, 0L, 0L, 3L, 0L, 
    3L, 3L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 3L), Target = c(0L, 
    1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 
    0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L
    )), .Names = c("ID", "V1", "V2", "V3", "V5", "V6", "Target"
), class = "data.frame", row.names = c(NA, -31L))

set.seed(1000)
train = as.data.frame(lapply(init_train, function(x) sample(x, 27700, replace = TRUE)))