# 5.4 模型训练与集成

    ##### 5.4 模型训练与集成 #####

    ### 载入数据和相应 ###
    # 清空工作目录
    rm(list = ls())
    # 加载机器学习包
    # install.packages(caret)
    library(caret)
    # 加载数据
    dat0 = read.csv('相亲数据.csv', fileEncoding = "UTF-8")
    head(dat0)
    ##    决定 性别 吸引力 共同爱好 幽默 真诚 雄心 智力 好感 成功率自估
    ##  1    1    0      6        5    7    9    6    7    7          6
    ##  2    1    0      7        6    8    8    5    7    7          5
    ##  3    1    0      5        7    8    8    5    9    7         NA
    ##  4    1    0      7        8    7    6    6    8    7          6
    ##  5    1    0      5        6    7    6    6    7    6          6
    ##  6    0    0      4        4    4    9    6    7    6          5
    ##    日常出门频率 对宗教的看重程度 对种族的看重程度 年龄 种族 从事领域
    ##  1            1                4                2   21    4        1
    ##  2            1                4                2   21    4        1
    ##  3            1                4                2   21    4        1
    ##  4            1                4                2   21    4        1
    ##  5            1                4                2   21    4        1
    ##  6            1                4                2   21    4        1
    ##    对方决定 好感得分 对方评估成功率 吸引力得分 共同爱好得分 幽默得分
    ##  1        0        7              4          6            6        8
    ##  2        0        8              4          7            5        7
    ##  3        1       10             10         10           10       10
    ##  4        1        7              7          7            8        8
    ##  5        1        8              6          8            7        6
    ##  6        1        7              6          7            7        8
    ##    真诚得分 雄心得分 智力得分 对方年龄 对方种族 是否同一种族 日常约会频率
    ##  1        8        8        8       27        2            0            7
    ##  2        8        7       10       22        2            0            7
    ##  3       10       10       10       22        4            1            7
    ##  4        8        9        9       23        2            0            7
    ##  5        7        9        9       24        3            0            7
    ##  6        7        7        8       25        2            0            7

    ### 1.数据预处理及数据分割 ###
    ## (1) 缺失值处理 ##
    nrow(dat0)
    ##  [1] 8378
    dat = na.omit(dat0)
    nrow(dat)
    ##  [1] 5723

    ## (2) 将定性变量转换为因子性变量 ##

    # 转换数据类型
    dat$决定 = factor(dat$决定, levels = c(0, 1), labels = c("拒绝", "接收"))
    dat$性别 = factor(dat$性别, levels = c(0, 1), labels = c("女", "男"))
    dat$种族 = factor(dat$种族, levels = c(1, 2, 3, 4, 6), labels = c("非洲裔", "欧洲裔", "拉丁裔", "亚裔", "其他"))
    dat$从事领域 = factor(dat$从事领域, levels = 1:18,
                      labels = c("法律", "数学", "社会科学或心理学",
                                 "医学或药物学或生物技术", "工程学", "写作或新闻",
                                 "历史或宗教或哲学", "商业或经济或金融", "教育或学术",
                                 "生物科学或化学或物理", "社会工作", "大学在读或未择方向",
                                 "政治学或国际事务", "电影", "艺术管理",
                                 "语言", "建筑学", "其他"))
    dat$对方决定 = factor(dat$对方决定, levels = c(0, 1), labels = c("拒绝", "接收"))
    dat$对方种族 = factor(dat$对方种族, levels = c(1, 2, 3, 4, 6),
                      labels = c("非洲裔", "欧洲裔", "拉丁裔", "亚裔", "其他"))
    dat$是否同一种族 = factor(dat$是否同一种族, levels = c(0, 1), labels = c("非同一种族", "同一种族"))

    ## (3) 数据划分为训练集和测试集 ##

    # 设置随机种子
    set.seed(1234)
    # 将数据集的80%划分为训练集，20%划分为测试集
    trainIndex = createDataPartition(dat$决定, p = .8,
                                     list = FALSE,
                                     times = 1)
    # createDataPartition会自动从y的各个level随机取出等比例的数据来，组成训练集，可理解为分层抽样；
    datTrain = dat[trainIndex, ]
    # 训练集
    datTest = dat[-trainIndex, ]
    # 测试集
    table(dat$决定) / nrow(dat)  # 全集上因变量各个水平的比例
    ##
    ##      拒绝     接收
    ##  0.560021 0.439979
    table(datTrain$决定) / nrow(datTrain)  # 训练集上因变量各个水平的比例
    ##
    ##       拒绝      接收
    ##  0.5599476 0.4400524
    table(datTest$决定) / nrow(datTest)  # 测试集上因变量各个水平的比例
    ##
    ##       拒绝      接收
    ##  0.5603147 0.4396853

    ## (4) 标准化处理 ##

    preProcValues = preProcess(datTrain, method = c("center", "scale"))
    trainTransformed = predict(preProcValues, datTrain)
    testTransformed = predict(preProcValues, datTest)
    # 利用训练集的均值和方差对测试集进行标准化

    ### 2.模型训练 ###
    # 选择包含显著变量的数据框
    # selected_var = Profile$optVariables
    selected_var = c("吸引力", "好感", "共同爱好", "成功率自估", "幽默", "从事领域", "对种族的看重程度", "对宗教的看重程度", "种族", "性别", "年龄", "日常出门频率", "吸引力得分", "日常约会频率", "真诚")
    dat.train = trainTransformed[, c(selected_var, "决定")]
    dat.test = testTransformed[, c(selected_var, "决定")]

    ## (1) 使用经典的逻辑回归预测 ##

    fit1 = train(决定 ~., data = dat.train,
                      method = "glm",
                      family ="binomial")  # 训练模型
    pstate1 = predict(fit1, newdata = dat.test)  # 在测试集上预测
    confusionMatrix(pstate1, dat.test$决定)  # 利用混淆矩阵评估模型
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  515  119
    ##        接收  126  384
    ##
    ##                 Accuracy : 0.7858
    ##                   95% CI : (0.7609, 0.8093)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : <2e-16
    ##
    ##                    Kappa : 0.566
    ##   Mcnemar's Test P-Value : 0.7015
    ##
    ##              Sensitivity : 0.8034
    ##              Specificity : 0.7634
    ##           Pos Pred Value : 0.8123
    ##           Neg Pred Value : 0.7529
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4502
    ##     Detection Prevalence : 0.5542
    ##        Balanced Accuracy : 0.7834
    ##
    ##         'Positive' Class : 拒绝
    ##

    ## (2) 使用决策树预测 ##

    fit2 = train(决定 ~ ., data = dat.train,
                   method = "rpart")  # 训练模型
    pstate2 = predict(fit2, newdata = dat.test)  # 在测试集上预测
    confusionMatrix(pstate2, dat.test$决定)  # 利用混淆矩阵评估模型
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  566  175
    ##        接收   75  328
    ##
    ##                 Accuracy : 0.7815
    ##                   95% CI : (0.7564, 0.8051)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : < 2.2e-16
    ##
    ##                    Kappa : 0.5468
    ##   Mcnemar's Test P-Value : 3.818e-10
    ##
    ##              Sensitivity : 0.8830
    ##              Specificity : 0.6521
    ##           Pos Pred Value : 0.7638
    ##           Neg Pred Value : 0.8139
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4948
    ##     Detection Prevalence : 0.6477
    ##        Balanced Accuracy : 0.7675
    ##
    ##         'Positive' Class : 拒绝
    ##

    ## (3) 使用随机森林预测 ##

    set.seed(1234)
    fit3 = train(决定 ~., data = dat.train,
                   method = "rf")  # 训练模型
    pstate3 = predict(fit3, newdata = dat.test)  # 在测试集上预测
    confusionMatrix(pstate3, dat.test$决定)  # 利用混淆矩阵评估模型
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  528  105
    ##        接收  113  398
    ##
    ##                 Accuracy : 0.8094
    ##                   95% CI : (0.7855, 0.8318)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : <2e-16
    ##
    ##                    Kappa : 0.6139
    ##   Mcnemar's Test P-Value : 0.6354
    ##
    ##              Sensitivity : 0.8237
    ##              Specificity : 0.7913
    ##           Pos Pred Value : 0.8341
    ##           Neg Pred Value : 0.7789
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4615
    ##     Detection Prevalence : 0.5533
    ##        Balanced Accuracy : 0.8075
    ##
    ##         'Positive' Class : 拒绝
    ##

    ### 3.模型集成 ###

    ## (1) 投票法 ##
    results = data.frame(pstate1, pstate2, pstate3)
    results = apply(results, 2, as.character)
    major_results = apply(results, 1, function(x) {
      tb = sort(table(x), decreasing = T)
      if(tb[1] %in% tb[2]) {
        return(sample(c(names(tb)[1], names(tb)[2]), 1))
      } else {
        return(names(tb)[1])
      }
    })
    major_results = factor(major_results, levels = c("拒绝", "接收"))
    confusionMatrix(major_results, dat.test$决定)
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  530  126
    ##        接收  111  377
    ##
    ##                 Accuracy : 0.7928
    ##                   95% CI : (0.7682, 0.816)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : <2e-16
    ##
    ##                    Kappa : 0.5782
    ##   Mcnemar's Test P-Value : 0.3631
    ##
    ##              Sensitivity : 0.8268
    ##              Specificity : 0.7495
    ##           Pos Pred Value : 0.8079
    ##           Neg Pred Value : 0.7725
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4633
    ##     Detection Prevalence : 0.5734
    ##        Balanced Accuracy : 0.7882
    ##
    ##         'Positive' Class : 拒绝
    ##

    ## (2) 堆叠集成法 ##
    set.seed(1234)
    combPre = data.frame(pstate1, pstate2, pstate3, 决定 = dat.test$决定)
    combfit = train(决定~., method = "rf", data = combPre)
    ##  note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
    combpstate = predict(combfit, newdata = dat.test)
    confusionMatrix(combpstate, dat.test$决定)
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  525   99
    ##        接收  116  404
    ##
    ##                 Accuracy : 0.8121
    ##                   95% CI : (0.7882, 0.8343)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : <2e-16
    ##
    ##                    Kappa : 0.62
    ##   Mcnemar's Test P-Value : 0.2752
    ##
    ##              Sensitivity : 0.8190
    ##              Specificity : 0.8032
    ##           Pos Pred Value : 0.8413
    ##           Neg Pred Value : 0.7769
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4589
    ##     Detection Prevalence : 0.5455
    ##        Balanced Accuracy : 0.8111
    ##
    ##         'Positive' Class : 拒绝
    ##

    ## (3) 使用boosting预测 ##
    set.seed(1234)
    fit4 = train(决定 ~., data = dat.train,
                   method = "gam")  # 训练模型
    pstate4 = predict(fit4, newdata = dat.test)  # 在测试集上预测
    confusionMatrix(pstate4, dat.test$决定)  # 利用混淆矩阵评估模型
    ##  Confusion Matrix and Statistics
    ##
    ##            Reference
    ##  Prediction 拒绝 接收
    ##        拒绝  505  112
    ##        接收  136  391
    ##
    ##                 Accuracy : 0.7832
    ##                   95% CI : (0.7582, 0.8068)
    ##      No Information Rate : 0.5603
    ##      P-Value [Acc > NIR] : <2e-16
    ##
    ##                    Kappa : 0.5623
    ##   Mcnemar's Test P-Value : 0.1442
    ##
    ##              Sensitivity : 0.7878
    ##              Specificity : 0.7773
    ##           Pos Pred Value : 0.8185
    ##           Neg Pred Value : 0.7419
    ##               Prevalence : 0.5603
    ##           Detection Rate : 0.4414
    ##     Detection Prevalence : 0.5393
    ##        Balanced Accuracy : 0.7826
    ##
    ##         'Positive' Class : 拒绝
    ##