使用anyNA函数判断data.table中是否存在缺失值

41 篇文章 ¥59.90 ¥99.00
本文介绍如何在R语言的data.table中利用anyNA函数检测数据缺失值,提供示例代码展示如何创建含缺失值的数据集,并用anyNA和sum与is.na组合方法检查缺失值数量。

在R语言中,data.table是一种强大的数据处理工具,常用于处理大型数据集。在数据分析和清洗过程中,经常需要检查数据是否存在缺失值。本文将介绍如何使用anyNA函数来判断data.table中是否存在缺失值,并提供相应的源代码示例。

首先,我们需要确保已经安装并加载了data.table包。可以使用以下代码进行安装和加载:

# 安装data.table包
install.packages("data.table")

# 加载data.table包
library(data.table)

接下来,我们将创建一个包含缺失值的示例data.table。假设我们有一个包含姓名、年龄和收入的数据集。我们将创建一个包含缺失值的示例数据集,并将其存储为data.table对象。

# 创建一个包含缺失值的data.table
dt <- data.table(
  姓名 = c("张三", "李四", "王五", "赵六"),
  年龄 = c(25, 30, NA, 35),
  收入 = c(5000, 6000, 7000, NA)
)

现在,我们可以使用anyNA函数来检查data.table中是否存在缺失值。anyNA函数将返回一个逻辑值,为TRUE表示存在缺失值,为FALSE表示不存在缺失值。

# 使用anyNA函数判断data.table中是否存在缺失值
has_missing_values <- anyNA(dt)

# 
## ===== 仅在缺包时安装,并加载 ===== need_pkgs <- c("glmnet", "readxl", "readr") to_install <- setdiff(need_pkgs, rownames(installed.packages())) if (length(to_install)) install.packages(to_install, dependencies = TRUE) install.packages("readxl") library(readxl) install.packages("glmnet") library(glmnet) install.packages("readr") library(readr) # 为 parse_number # 1.读取数据(直接跳过前两行说明/单位) dat <- read_excel("武汉数据lasso.xlsx") # 删除第1和第3行 dat <- dat[-c(1, 2), ] View(dat) # 2.定义所有需要处理的分类变量(按实际列名) categorical_cols <- c( "gender", "age", "nationality", "marig", "edu", "hospital", "pos", "career", "title", "appointment", "meditime", "salary", "commutetime", "nightworkif", "shiftwork", "workweekend" ) # 3. 为每个变量定义标签映射 label_mappings <- list( gender = c("1" = "Female", "2" = "Male"), age = c("1" = "<30", "2" = ">=30,<40", "3" = ">=40,<50", "4" = ">=50"), nationality = c("1" = "Han_nationality", "2" = "Minority"), marig = c("1" = "Spinsterhood", "2" = "Married", "3" = "Other_Marig"), edu = c("1" = "High_school_below", "2" = "Junior_College", "3" = "Undergraduate", "4" = "Master", "5" = "Doctor"), hospital = c("1" = "Red_Crossd", "2" = "Third_Hospital", "3" = "People's_Hospital", "4" = "Fifth_Hospital"), pos = c("1" = "Senior_manager", "2" = "Department_manager", "3" = "Department_staff", "4" = "Intern/Trainee/student"), career = c("1" = "Physician", "2" = "Nurse", "3" = "Medical_Technician", "4" = "Administrative/scientific", "5" = "Support_Crew", "6" = "Other_Career"), title = c("1" = "Advanced", "2" = "Intermediate", "3" = "Primary", "4" = "No_Title"), appointment = c("1" = "Establishment", "2" = "Contract", "3" = "Labor_Dispatching", "4" = "Personnel_Agency", "5" = "Other_Appointment"), meditime = c("1" = "<1year", "2" = "1~5years", "3" = "6~10years", "4" = "11~15years", "5" = ">15years"), salary = c("1" = "<2000", "2" = "2000~4000", "3" = "4000~6000", "4" = "6000~8000", "5" = "8000~10000", "6" = ">10000"), commutetime = c("1" = "<0.5h", "2" = "0.5~1h", "3" = ">1h"), nightworkif = c("1" = "Nightwork", "2" = "Not_Nightwork"), shiftwork = c("1" = "Shiftwork", "2" = "Not_Shiftwork"), workweekend = c("1" = "Never", "2" = "Seldom", "3" = "Sometimes", "4" = "Often", "5" = "Always") ) # 4.选择特征列(X)和目标列(y) ===== x_cols <- c("gender", "age", "edu", "pos", "career", "title", "meditime", "salary", "commutetime", "nightworkif", "shiftwork", "workweekend","ED","HE","RE","SS","SC") # 构造 X(不再做任何行切片) X <- dat[, x_cols, drop = FALSE] # 清洗+转数值: X[] <- lapply(X, function(v){ if (is.numeric(v)) return(v) s <- as.character(v) out <- readr::parse_number(s) out[grepl("%", s)] <- out[grepl("%", s)] / 100 out }) # 若只能用列号,就明确一个数字,二选一(确认后只保留一个) y <- dat[["BO"]] # 把 y 也安全转成数值(万一是字符/因子) if (!is.numeric(y)) y <- parse_number(as.character(y)) # 5.同步清理:X 用均值填补,y 去掉非有限值,并同步子集 # 计算每列均值 col_means <- sapply(X, function(v) mean(v, na.rm = TRUE)) # 用列均值填补 X 的 NA for (j in seq_along(X)) { idx <- is.na(X[[j]]) if (any(idx)) X[[j]][idx] <- col_means[j] } # y 的非有限值行剔除(并同步到 X) ok <- is.finite(y) X <- X[ok, , drop = FALSE] y <- y[ok] # 基本检查 stopifnot(nrow(X) == length(y)) if (sd(y) == 0) stop("y 为常数列,无法做高斯回归;请换一个有变动的 y。") if (any(!is.finite(as.matrix(X)))) stop("X 中仍有 NA/Inf/-Inf,请检查数据清洗。") # 转矩阵/向量 x_mat <- as.matrix(X) y_vec <- as.numeric(y) # 6.拟合 LASSO 与 CV set.seed(123) fit <- glmnet(x_mat, y_vec, alpha = 1, family = "gaussian", standardize = TRUE) cvfit <- cv.glmnet(x_mat, y_vec, alpha = 1, family = "gaussian", nfolds = 10, standardize = TRUE) # 7.作图 # xvar = "lambda" 时横轴是 log(lambda),下面两条竖线用 log(lambda) 是对的 plot(fit, xvar = "lambda", label = TRUE, xlab = "log(Lambda)", ylab = "Coefficients", main = "LASSO Coefficient Paths (log λ)",sign.lambda=1) abline(v = log(cvfit$lambda.min), lty = 2, col = "red",) abline(v = log(cvfit$lambda.1se), lty = 2, col = "blue") plot(cvfit, sign.lambda=1) # CV 曲线(自带两条竖线) # 8.提取非零系数 coef_min <- coef(cvfit, s = "lambda.min") nz_min <- setdiff(which(coef_min != 0), 1) # 去掉截距 result_min <- data.frame( variable = rownames(coef_min)[nz_min], coef = as.numeric(coef_min[nz_min]) ) result_min <- result_min[order(-abs(result_min$coef)), ] coef_1se <- coef(cvfit, s = "lambda.1se") nz_1se <- setdiff(which(coef_1se != 0), 1) result_1se <- data.frame( variable = rownames(coef_1se)[nz_1se], coef = as.numeric(coef_1se[nz_1se]) ) result_1se <- result_1se[order(-abs(result_1se$coef)), ] cat("\n--- 选出的特征(lambda.min)---\n"); print(result_min) cat("\n--- 选出的特征(lambda.1se)---\n"); print(result_1se) cat("\nlambda.min =", cvfit$lambda.min, "\nlambda.1se =", cvfit$lambda.1se, "\n") 这是一份做Lasso回归分析的代码,请检查其中是否有错误,并提出改进方法。
10-16
<think>我们首先检查代码中的潜在问题,然后提出改进方法。 1. 安装包部分: 代码中先检查并安装缺失包,但后面又单独对每个包调用install.packages,这会导致已经安装的包又被重新安装一次,而且可能引起不必要的依赖问题。此外,安装包时设置了dependencies=TRUE,这可能会安装很多不必要的依赖包。建议只在第一次检查缺失包时安装。 2. 读取数据部分: 使用`read_excel`读取数据,然后删除了前两行(说明行)。这里需要注意,删除行后,数据框的行名会保留原始的行号(即从3开始),但后续操作通常不需要行名,所以问题不大。不过,如果后续需要行号索引,可能需要重置行名。 3. 分类变量处理: 代码中定义了分类变量的列名和标签映射,但后续在构造X矩阵时并没有使用这些映射将分类变量转换为因子或进行独热编码。在glmnet中,输入矩阵必须是数值矩阵,分类变量需要转换为数值编码(比如虚拟变量)。当前代码中,这些分类变量在数据读取后可能是字符型或因子型,然后在清洗步骤中通过`parse_number`强制转换为数值。如果原始数据是文本标签(如“Female”、“Male”),则`parse_number`会无法解析(得到NA)。而如果原始数据已经是数值编码(如1,2),则可以直接使用。但是,在定义`categorical_cols`后,代码中并没有对这些变量做特殊处理,而是统一用`parse_number`转换,这可能会导致分类变量被错误地当作连续变量处理。 4. 特征列选择: 在`x_cols`中选择了17个特征列,其中包括一些分类变量(如gender, age等)和几个连续变量(ED, HE, RE, SS, SC)。注意:分类变量应该被转换为虚拟变量(哑变量),否则lasso会将其视为连续变量,导致错误的系数解释。 5. 数据清洗部分: - 代码尝试将非数值列转换为数值:先判断是否为数值列,如果不是则转为字符,然后使用`parse_number`解析数字。但这种方式可能会丢失分类变量的信息(特别是当分类变量是字符串标签时,解析会得到NA)。另外,对于包含百分号的字符串,除以100的处理是合理的,但这里可能只针对特定列(然而代码中并没有针对特定列,而是所有非数值列都这样处理),所以需要确保其他列不会出现百分号。 - 在清洗后,用列均值填补缺失值。这里需要注意,分类变量(特别是名义变量)用均值填补可能不合适(例如,性别用平均值0.5填补没有意义)。建议分类变量应该单独处理(比如用众数填补,或者使用其他方法),或者确保在转换为虚拟变量后再进行填补(此时每一列都是0/1,均值填补相当于用比例填补)。 6. 目标变量y的处理: 将y转换为数值型,并处理缺失值(非有限值)。这里处理合理。 7. 同步清理: 在清除y的非有限值后,同步清除X中对应的行,这是正确的。并且做了基本检查,确保X和y维度一致。 8. 建模部分: 使用glmnet进行lasso回归,并做了交叉验证。这里参数设置合理。 9. 作图部分: 绘制系数路径和交叉验证误差。注意:在系数路径图中,竖线使用log(lambda)是合适的,因为xvar="lambda"时横坐标是log(lambda)。 10. 提取非零系数: 使用lambda.min和lambda.1se提取非零系数,并排除截距项,然后按系数绝对值排序。这部分处理正确。 改进建议: 1. 安装包部分: 去掉重复安装的代码,只保留最开始的条件安装。 2. 分类变量处理: 由于glmnet要求输入数值矩阵,我们需要将分类变量转换为虚拟变量。可以使用`model.matrix`函数来创建包含所有虚拟变量的设计矩阵。注意,这样会为每个分类变量的每个水平创建一列(除了作为参考水平的那一列会被忽略)。为了避免多重共线性,我们通常设置`drop=TRUE`(实际上`model.matrix`默认会去掉一个水平作为参考水平)。 但是,在原始数据中,很多分类变量已经被编码为数值(1,2,3...),如果直接作为连续变量使用是不合适的。我们应该将这些分类变量转换为因子,然后生成虚拟变量。 步骤: - 将`categorical_cols`中的列转换为因子,并使用之前定义的标签映射(但注意,标签映射目前只是定义了数值编码对应的标签,在建模中标签并不影响数值计算,但会影响输出的变量名)。 - 使用`model.matrix`创建设计矩阵。注意,这里要排除因变量(y),并且要处理连续变量(连续变量保持不变)。 3. 数据预处理流程调整: 建议步骤: a. 读取数据,删除不需要的行。 b. 将分类变量转换为因子(使用之前定义的映射,但注意:映射中有些分类变量的水平是字符串,而原始数据可能是数值,因此需要先转换为字符串再匹配因子水平?或者直接使用数值因子水平?) c. 选择特征列和目标列。 d. 将特征列中的分类变量转换为虚拟变量(使用`model.matrix`),同时保留连续变量。 e. 处理缺失值:连续变量用均值填补,分类变量(在虚拟变量转换后,每一列都是0/1)可以用0填补(表示缺失的样本在每一个虚拟变量上都取0,即参考水平)或者用该虚拟变量的均值(相当于该水平出现的频率)。但注意,虚拟变量转换后,每一列都是二值的,用均值填补可能得到非整数,这可能会影响模型解释。另一种做法是,在转换虚拟变量之前,对原始分类变量用众数填补(这样不会引入小数)。这里需要根据实际情况选择。 4. 注意:在转换虚拟变量后,变量名会改变(例如gender变成genderFemale, genderMale等)。这样在最终结果中,我们可以知道哪个水平被选中。 5. 目标变量y的处理保持不变。 6. 由于转换虚拟变量后,矩阵的列数会增加,我们需要确保在填补缺失值之前,已经完成了虚拟变量转换(以避免分类变量被当作连续变量处理)。 7. 另外,在数据清洗步骤中,原来使用`parse_number`强制转换的方法存在风险,特别是对于分类变量(字符串标签)会得到NA。因此,我们应该在读取数据后,先对分类变量进行因子转换,而不是用`parse_number`。 由于代码较长,我们给出改进后的关键步骤: 步骤1:安装包(只安装缺失的包) 步骤2:读取数据,删除说明行 步骤3:定义分类变量和标签映射(用于转换为因子) 步骤4:将分类变量转换为因子(使用定义好的映射) 步骤5:选择特征列(包括分类变量和连续变量)和目标列 步骤6:将分类变量转换为虚拟变量(使用model.matrix),同时保留连续变量 步骤7:处理缺失值(连续变量用均值,分类变量在转换虚拟变量后,我们通常用0填补,表示参考水平,但也可以考虑用众数填补原始分类变量,然后再转换) 步骤8:处理y,并同步清理X和y 步骤9:建模和交叉验证 步骤10:结果提取和可视化 由于修改较大,我们给出部分关键代码: 关键修改1:在读取数据后,将分类变量转换为因子 ```r # 假设dat已经读取,并删除了前两行 # 遍历categorical_cols,将每一列转换为因子,并使用对应的标签映射 for (col in categorical_cols) { # 确保列存在 if (col %in% colnames(dat)) { # 将列转换为字符(以防是数值),然后根据映射转换 # 注意:原始数据中可能是数值型,也可能是字符型,我们统一转为字符然后匹配 dat[[col]] <- as.character(dat[[col]]) # 使用映射:将原始编码替换为对应的标签(字符串),然后再转换为因子 # 注意:如果原始数据中有映射中没有的值,会变成NA,所以需要确保映射覆盖所有情况 dat[[col]] <- factor(dat[[col]], levels = names(label_mappings[[col]]), labels = label_mappings[[col]]) } } ``` 关键修改2:构建设计矩阵(包含虚拟变量) ```r # 首先,我们将特征列分为连续变量和分类变量(这里分类变量已经是因子) # 注意:在x_cols中,有些列是分类变量(已经在categorical_cols中定义),有些是连续变量(比如ED,HE等) # 我们这里假设x_cols中除了分类变量都是连续变量 # 创建模型公式:~ . 表示所有变量,但我们不需要截距(因为glmnet会自己加截距,而且model.matrix默认加截距,我们后面要去掉) # 但是我们希望只对因子变量进行虚拟变量转换,连续变量保持不变 # 提取特征数据框(包含所有x_cols) features <- dat[, x_cols] # 使用model.matrix创建设计矩阵 # 注意:这里我们保留连续变量不变,对因子变量自动生成虚拟变量(因子变量会自动处理,连续变量保持不变) # 公式:~ . 表示所有列 x_mat <- model.matrix(~ . , data = features) # model.matrix会生成一个包含截距(第一列全1)的矩阵,但是我们通常不需要截距列(因为glmnet会自己加) # 实际上,在glmnet中,我们通常设置intercept=TRUE,所以这里的设计矩阵不应该包含截距列(去掉第一列) x_mat <- x_mat[, -1] # 去掉截距列 # 此时,x_mat已经是一个数值矩阵,包含所有连续变量和分类变量的虚拟变量。 ``` 关键修改3:处理缺失值(在转换之后) 在转换虚拟变量后,我们仍然可能遇到缺失值(因为在转换过程中,如果原始分类变量有缺失,那么生成的虚拟变量列也会是NA)。同样,连续变量也可能有缺失。 我们可以: - 对于连续变量,仍然用均值填补。 - 对于虚拟变量(二值变量),缺失值可以用0填补(表示在参考水平),但注意:参考水平在虚拟变量中是被省略的,所以缺失值填补为0是合理的(相当于参考水平)。然而,如果原始分类变量有缺失,那么生成的虚拟变量列全部缺失(因为原始缺失导致无法判断属于哪个水平)。因此,更合理的做法是:在转换因子之前,先填补分类变量的缺失值(用众数或单独的一个类别“Missing”)。 鉴于时间,我们可以在转换因子之前对分类变量进行缺失值填补(用众数),对连续变量则在转换为矩阵后填补(均值)。 因此,调整步骤: 1. 填补分类变量(因子转换前)的缺失值:用众数(mode)填补。 2. 转换因子。 3. 创建设计矩阵(此时没有缺失值?不对,连续变量可能还有缺失,所以需要在设计矩阵创建后,再整体填补连续变量的缺失值)。 或者,我们可以在创建设计矩阵后统一处理缺失值(但要注意虚拟变量列不应该有缺失值,因为分类变量已经被填补过)。所以,我们先填补分类变量(在转换为因子之前)的缺失值,然后转换因子,然后创建设计矩阵,然后填补连续变量的缺失值(连续变量在x_cols中,但设计矩阵中连续变量是单独列,虚拟变量是另外的列)。 改进后的数据预处理流程: 步骤1:填补分类变量的缺失值(用众数) 步骤2:将分类变量转换为因子(使用映射) 步骤3:填补连续变量的缺失值(用均值)——注意:此时连续变量还没有转换到设计矩阵,所以我们直接对原始连续变量列填补。 步骤4:创建设计矩阵(此时没有缺失值) 但是,在步骤3中,我们需要先区分连续变量和分类变量?不对,在步骤1中我们只处理了分类变量(在categorical_cols中定义的列)的缺失值,而连续变量我们还没有处理。在步骤4创建设计矩阵时,连续变量如果有缺失,设计矩阵中也会是缺失。因此,我们可以在步骤3中处理连续变量的缺失值(在features数据框中)。 具体代码: ```r # 步骤1:填补分类变量(在dat中)的缺失值(用众数) for (col in categorical_cols) { if (col %in% colnames(dat)) { # 获取该列的众数(忽略NA) freq_table <- table(dat[[col]], useNA = "no") mode_val <- names(freq_table)[which.max(freq_table)] # 如果缺失,用众数填补 dat[[col]][is.na(dat[[col]])] <- mode_val } } # 步骤2:将分类变量转换为因子(使用之前定义的映射) for (col in categorical_cols) { if (col %in% colnames(dat)) { dat[[col]] <- as.character(dat[[col]]) # 注意:这里我们使用映射将原始值转换为标签,然后再转为因子 # 但注意:原始数据中可能有不在映射中的值?我们已经用众数填补了缺失,所以应该都在映射中 # 如果出现不在映射中的值,这里会变成NA,所以确保映射覆盖所有可能的值 # 将原始值映射到标签 # 由于dat[[col]]现在是字符向量,我们直接用映射的标签替换 # 注意:label_mappings[[col]]是一个命名向量,例如c("1"="Female",...) # 我们可以用unname来替换:先根据原始值(作为索引)取出对应的标签 # 例如:对于gender列,原始值为"1",则替换为"Female" # 但是,原始数据中可能有多种表示(比如数字字符串,或者直接是标签),所以最好确保原始数据是映射中的名字(即1,2等) # 如果原始数据已经是标签,那么映射中可能没有(因为映射的names是1,2),所以这里我们要注意。 # 这里我们假设原始数据是1,2这样的字符串(即映射的names) # 重新构造一个映射向量,将原始值(字符串)映射为标签(字符串) mapping_vec <- label_mappings[[col]] # 将原始值替换为映射后的标签 dat[[col]] <- mapping_vec[dat[[col]]] # 然后转换为因子,水平为mapping_vec中的标签(按顺序) dat[[col]] <- factor(dat[[col]], levels = unname(mapping_vec)) } } # 步骤3:填补连续变量的缺失值(这里的连续变量是x_cols中那些不在categorical_cols中的列) continuous_cols <- setdiff(x_cols, categorical_cols) for (col in continuous_cols) { if (col %in% colnames(dat)) { col_mean <- mean(dat[[col]], na.rm = TRUE) dat[[col]][is.na(dat[[col]])] <- col_mean } } # 步骤4:构建设计矩阵 features <- dat[, x_cols] # 此时features中,分类变量是因子,连续变量是数值(已填补) x_mat <- model.matrix(~ ., data = features) # 包含截距 x_mat <- x_mat[, -1] # 去掉截距列 # 步骤5:提取y并处理 y <- dat[["BO"]] if (!is.numeric(y)) { y <- as.numeric(as.character(y)) } # 处理y的缺失值(非有限值) ok <- is.finite(y) x_mat <- x_mat[ok, , drop = FALSE] y <- y[ok] # 步骤6:检查 # ...(同原代码) ``` 注意:在步骤2中,我们假设原始数据中的分类变量值都是映射向量中的names(即字符串形式的“1”,“2”等),然后替换为对应的标签,再转换为因子。如果原始数据中已经是标签字符串,那么映射向量(names为1,2)将无法匹配,导致替换后为NA。因此,我们需要确保原始数据是数值编码(字符串形式),或者调整映射方式。 另外,在映射替换时,我们使用了`mapping_vec[dat[[col]]]`,这要求`dat[[col]]`的每个元素都是`mapping_vec`的names中的一个。如果原始数据有不在names中的值,会得到NA。因此,在填补缺失值后,应该确保所有值都在映射的names中。 由于修改较大,我们仅给出关键部分的修改,其他部分(如模型拟合、绘图等)保持不变。 此外,原代码中在安装包后重复安装了readxl, glmnet, readr,这部分可以去掉。 改进后的安装包代码: ```r need_pkgs <- c("glmnet", "readxl", "readr") to_install <- setdiff(need_pkgs, rownames(installed.packages())) if (length(to_install)) install.packages(to_install, dependencies = TRUE) library(glmnet) library(readxl) library(readr) ``` 最后,由于变量名在转换为虚拟变量后变得很长,在结果中可能显示不友好,但这是可接受的。 由于时间关系,我们只检查了主要问题,并提出了改进方案。实际运行中,可能需要根据数据实际情况调整。 相关问题: §§相关问题§§: 1. 在glmnet中,对于分类变量,是否必须转换为虚拟变量?如果直接用数值编码(1,2,3)会有什么问题? 2.使用Lasso回归时,为什么通常需要对连续变量进行标准化?在glmnet中如何控制标准化? 3. 在构建设计矩阵时,使用model.matrix函数,为什么需要去掉截距项? 4.缺失值填补时,对于分类变量使用众数填补,对于连续变量使用均值填补,这样做的优缺点是什么? </think>### 代码检查与改进建议 #### 主要问题分析 1. **重复安装包**: ```r install.packages("readxl") # 重复安装 install.packages("glmnet") # 重复安装 install.packages("readr") # 重复安装 ``` - 已在前面的条件安装中处理过,此处冗余且降低效率 2. **分类变量处理缺失**: - 定义了`categorical_cols`和`label_mappings`但未实际使用 - 分类变量被强制转换为数值,导致: - 无法区分名义/有序变量 - 信息丢失(如gender的1/2变为数值) - 模型解释性降低 3. **数据清洗风险**: ```r X[] <- lapply(X, function(v){ if (is.numeric(v)) return(v) s <- as.character(v) out <- readr::parse_number(s) out[grepl("%", s)] <- out[grepl("%", s)] / 100 out }) ``` - 对分类变量使用`parse_number()`会丢失信息(如"Female"→NA) - 百分号处理未限定特定列,可能误转换 4. **缺失值处理不当**: - 分类变量用均值填补不合理(如gender均值1.5无意义) - 未处理分类变量的缺失值 5. **特征选择冗余**: - `x_cols`包含`categorical_cols`中的变量(如gender)和额外变量(ED,HE等) - 未明确区分连续/分类变量 --- #### 改进方案代码 ```r ## ===== 安装包优化 ===== need_pkgs <- c("glmnet", "readxl", "readr", "dplyr") to_install <- setdiff(need_pkgs, rownames(installed.packages())) if (length(to_install)) install.packages(to_install, dependencies = TRUE) invisible(lapply(need_pkgs, library, character.only = TRUE)) ## ===== 数据读取与预处理 ===== dat <- read_excel("武汉数据lasso.xlsx")[-c(1, 2), ] ## 1. 分类变量因子化处理 categorical_cols <- c("gender", "age", "nationality", "marig", "edu", "hospital", "pos", "career", "title", "appointment", "meditime", "salary", "commutetime", "nightworkif", "shiftwork", "workweekend") # 应用标签映射并转换为因子 dat <- dat %>% mutate(across(all_of(categorical_cols), ~ { mapping <- label_mappings[[cur_column()]] factor(., levels = names(mapping), labels = mapping) })) ## 2. 特征列处理 x_cols <- c(categorical_cols, "ED", "HE", "RE", "SS", "SC") # 明确分类+连续变量 # 创建设计矩阵(自动处理分类变量哑编码) x_mat <- model.matrix(~ . - 1, data = dat[, x_cols]) # -1 表示去除截距项 # 目标变量处理 y <- as.numeric(dat[["BO"]]) ## 3. 缺失值处理优化 # 连续变量:均值填补 cont_cols <- c("ED", "HE", "RE", "SS", "SC") dat <- dat %>% mutate(across(all_of(cont_cols), ~ ifelse(is.na(.), mean(., na.rm = TRUE), .))) # 分类变量:众数填补 dat <- dat %>% mutate(across(all_of(categorical_cols), ~ { if (anyNA(.)) { mode_val <- names(which.max(table(.))) replace(., is.na(.), mode_val) } else . })) ## 4. 数据过滤 ok <- is.finite(y) x_mat <- x_mat[ok, ] y <- y[ok] ## 5. 拟合LASSO模型(设置标准化的参数) set.seed(123) cvfit <- cv.glmnet( x = x_mat, y = y, alpha = 1, family = "gaussian", standardize = TRUE, # 关键:启用变量标准化 nfolds = 10 ) ## 6. 结果提取(使用变量名) coef_df <- function(coef_obj) { coef_mat <- as.matrix(coef_obj) data.frame( variable = rownames(coef_mat)[coef_mat != 0], coef = coef_mat[coef_mat != 0] ) %>% filter(variable != "(Intercept)") } result_min <- coef_df(coef(cvfit, s = "lambda.min")) result_1se <- coef_df(coef(cvfit, s = "lambda.1se")) ## 7. 可视化优化 plot(cvfit, main = "Cross-Validation Error (log λ)") + abline(v = log(cvfit$lambda.min), col = "red", lty = 2) + abline(v = log(cvfit$lambda.1se), col = "blue", lty = 2) ``` --- ### 关键改进说明 1. **分类变量处理**: - 使用`factor()`和预定义的`label_mappings`正确转换分类变量 - 通过`model.matrix()`自动生成哑变量,保留类别信息 - 解决原代码强制转换导致的信息丢失问题 2. **安装包优化**: - 移除重复安装 - 使用`invisible(lapply(...))`批量加载包 - 添加`dplyr`用于数据操作 3. **缺失值分层处理**: ```r # 连续变量 → 均值填补 mutate(across(cont_cols, ~ ifelse(is.na(.), mean(., na.rm = TRUE), .)) # 分类变量 → 众数填补 mutate(across(categorical_cols, ~ replace(., is.na(.), names(which.max(table(.))))) ``` 4. **特征矩阵构建**: - 明确区分连续/分类变量 - `model.matrix(~ . -1)`自动生成哑变量并移除截距(glmnet自带的截距更稳定) 5. **标准化启用**: ```r cv.glmnet(..., standardize = TRUE) # 必须标准化! ``` - 解决量纲差异问题(尤其分类变量生成哑变量后) 6. **结果提取优化**: - 封装`coef_df()`函数直接获取带变量名的系数 - 过滤截距项避免干扰 --- ### 改进后优势 1. **模型准确性提升**:正确处理分类变量,避免信息损失 2. **结果可解释性**:保留原始变量标签(如"genderMale"而非数字) 3. **鲁棒性增强**:分层处理缺失值,标准化保证模型稳定性 4. **代码简洁性**:移除冗余操作,逻辑更清晰
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值