同母仔猪栏位体重均衡分配的data.table最优算法问询
优化同母猪仔猪的栏位分配:更高效的均衡算法
你当前的启发式思路方向是对的,但确实容易陷入局部最优或者无效循环(比如你代码里的switch变量用来避免重复交换,但可能错过更好的调整机会)。下面我给你两种更可靠的方案,分别适合不同规模的数据场景。
一、改进的启发式迭代算法(适合大规模数据)
这个思路核心是每次针对性交换最能缩小均值差距的仔猪对,同时严格遵守同sowid的约束:
算法步骤
- 计算当前各栏的平均体重,找到均值最高的栏(
high_pen)和最低的栏(low_pen) - 在
high_pen中筛选出sowid同时存在于low_pen的仔猪,取其中体重最大的个体(移走它能最大程度降低高栏均值) - 在
low_pen中找到对应sowid的仔猪,取其中体重最小的个体(移来它能最大程度升高低栏均值) - 交换这两个仔猪的栏位,重复上述步骤直到均值极差≤0.1,或者没有符合条件的可交换仔猪
代码实现
library(data.table) test <- data.table( sowid=c(12,23,25,45,65,12,58,85,96,85,45,23), pen=c(1,1,1,1,2,2,2,2,3,3,3,3), pigletid=c(1,2,3,4,5,6,7,8,9,10,11,12), weight=c(6.5,5.9,6.2,5.8,7.5,7.2,7.8,6.9,9.5,10.2,9.8,6.4) ) cutoff <- 0.1 max_iter <- 100 # 防止无限循环 iter <- 0 while(iter < max_iter) { # 计算各栏均值 pen_means <- test[, .(mean_weight = mean(weight)), by = pen] pen_means <- pen_means[order(mean_weight)] current_range <- pen_means$mean_weight[.N] - pen_means$mean_weight[1] if(current_range <= cutoff) break # 确定最高/最低均值的栏 low_pen <- pen_means$pen[1] high_pen <- pen_means$pen[.N] # 筛选高栏中sowid存在于低栏的仔猪,取体重最大的 high_piglets <- test[pen == high_pen] valid_sowids_high <- intersect(high_piglets$sowid, test[pen == low_pen]$sowid) if(length(valid_sowids_high) == 0) break # 无交换可能 candidate_high <- high_piglets[sowid %in% valid_sowids_high][order(-weight)][1] # 筛选低栏中对应sowid的仔猪,取体重最小的 candidate_low <- test[pen == low_pen & sowid == candidate_high$sowid][order(weight)][1] # 交换栏位 test[pigletid == candidate_high$pigletid, pen := low_pen] test[pigletid == candidate_low$pigletid, pen := high_pen] iter <- iter + 1 } # 输出结果 test[order(pen)]
运行后你会发现,这个算法能更快收敛到符合要求的均值差距,而且不会陷入无效循环。
二、线性规划求最优解(适合小规模数据)
如果你的数据集不大(比如像示例这样的几十行),可以用线性规划直接求解全局最优解,确保均值差距尽可能小。这里用lpSolve包实现:
代码实现
library(data.table) library(lpSolve) test <- data.table( sowid=c(12,23,25,45,65,12,58,85,96,85,45,23), pen=c(1,1,1,1,2,2,2,2,3,3,3,3), pigletid=c(1,2,3,4,5,6,7,8,9,10,11,12), weight=c(6.5,5.9,6.2,5.8,7.5,7.2,7.8,6.9,9.5,10.2,9.8,6.4) ) n_pigs <- nrow(test) n_pens <- length(unique(test$pen)) # 目标函数:最小化各栏均值的极差 obj <- rep(0, n_pigs*n_pens + 2) obj[n_pigs*n_pens + 1] <- 1 obj[n_pigs*n_pens + 2] <- -1 constraints <- list() rhs <- list() dir <- list() # 约束1:每个仔猪只能在一个栏 for(i in 1:n_pigs) { row <- rep(0, n_pigs*n_pens + 2) row[(i-1)*n_pens + 1 : i*n_pens] <- 1 constraints[[length(constraints)+1]] <- row rhs[[length(rhs)+1]] <- 1 dir[[length(dir)+1]] <- "=" } # 约束2:关联各栏均值与最大/最小均值 pigs_per_pen <- n_pigs / n_pens for(j in 1:n_pens) { # 最大均值 >= 栏j的均值 row <- rep(0, n_pigs*n_pens + 2) row[n_pigs*n_pens + 1] <- -pigs_per_pen row[(1:n_pigs)*n_pens - (n_pens - j)] <- test$weight constraints[[length(constraints)+1]] <- row rhs[[length(rhs)+1]] <- 0 dir[[length(dir)+1]] <- "<=" # 最小均值 <= 栏j的均值 row <- rep(0, n_pigs*n_pens + 2) row[n_pigs*n_pens + 2] <- pigs_per_pen row[(1:n_pigs)*n_pens - (n_pens - j)] <- -test$weight constraints[[length(constraints)+1]] <- row rhs[[length(rhs)+1]] <- 0 dir[[length(dir)+1]] <- "<=" } # 约束3:仔猪只能分配到初始时同sowid所在的栏 for(i in 1:n_pigs) { allowed_pens <- unique(test[sowid == test$sowid[i]]$pen) for(j in 1:n_pens) { if(!j %in% allowed_pens) { row <- rep(0, n_pigs*n_pens + 2) row[(i-1)*n_pens + j] <- 1 constraints[[length(constraints)+1]] <- row rhs[[length(rhs)+1]] <- 0 dir[[length(dir)+1]] <- "=" } } } # 求解线性规划 lp_result <- lp( direction = "min", objective.in = obj, const.mat = do.call(rbind, constraints), const.dir = unlist(dir), const.rhs = unlist(rhs), all.bin = c(rep(TRUE, n_pigs*n_pens), rep(FALSE, 2)) ) # 更新栏位分配 assignments <- matrix(lp_result$solution[1:(n_pigs*n_pens)], nrow = n_pigs, ncol = n_pens) test$pen <- apply(assignments, 1, function(x) which(x == 1)) # 输出结果 test[order(pen)]
这个方法能找到满足约束的全局最优解,确保均值差距最小。
两种方案对比
- 启发式算法:速度快,适合大规模数据集(比如上千条仔猪),能快速收敛到接近最优的解
- 线性规划:能找到精确最优解,但计算量随数据规模增长较快,适合小规模数据集
内容的提问来源于stack exchange,提问作者DataZhukov
相关产品推荐
相关产品推荐

