PHP项目量化主力缺阵损失值的实现方案
核心思路
"主力缺阵损失值"本质是评估某个关键成员(球员/员工/核心成员)缺席对整体绩效的影响,常见于体育竞猜、赛事分析、项目管理等场景。

核心公式:
损失值 = 基线实力值 - 缺阵后实力值
数据模型设计
数据库表
-- 主力成员表
CREATE TABLE players (
id INT PRIMARY KEY,
name VARCHAR(50),
team_id INT,
position VARCHAR(20),
base_score DECIMAL(10,2), -- 综合实力评分
is_starter TINYINT(1), -- 是否主力
avg_minutes DECIMAL(5,1) -- 场均出场时间
);
-- 比赛记录表
CREATE TABLE matches (
id INT PRIMARY KEY,
team_id INT,
opponent_id INT,
match_date DATE,
our_score INT,
opp_score INT,
win TINYINT(1)
);
-- 球员出场记录
CREATE TABLE player_appearances (
id INT PRIMARY KEY,
match_id INT,
player_id INT,
minutes INT,
performance_score DECIMAL(10,2), -- 该场表现评分
goals INT DEFAULT 0,
assists INT DEFAULT 0
);
量化算法
方法1:基于历史胜率的 Team-Without-Player 模型
class MissingStarterLossCalculator
{
private PDO $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
/**
* 计算某球员缺阵的损失值
*/
public function calculateLoss(int $playerId, int $teamId): array
{
// 1. 该球员在场时的球队胜率
$winRateWith = $this->getWinRate($teamId, $playerId, true);
// 2. 该球员缺阵时的球队胜率
$winRateWithout = $this->getWinRate($teamId, $playerId, false);
// 3. 该球员个人贡献值(基于表现评分)
$playerValue = $this->getPlayerValue($playerId);
// 4. 胜率差(权重0.6)+ 个人贡献(权重0.4)
$lossValue = ($winRateWith - $winRateWithout) * 100 * 0.6
+ $playerValue * 0.4;
return [
'player_id' => $playerId,
'win_rate_with' => round($winRateWith * 100, 2) . '%',
'win_rate_without'=> round($winRateWithout * 100, 2) . '%',
'player_value' => round($playerValue, 2),
'loss_value' => round($lossValue, 2),
];
}
/**
* 获取球队在某球员在场/缺阵情况下的胜率
*/
private function getWinRate(int $teamId, int $playerId, bool $withPlayer): float
{
$op = $withPlayer ? 'IN' : 'NOT IN';
$sql = "SELECT
COUNT(*) AS total,
SUM(m.win) AS wins
FROM matches m
WHERE m.team_id = :team
AND m.id {$op} (
SELECT pa.match_id
FROM player_appearances pa
WHERE pa.player_id = :player
AND pa.minutes > 0
)";
$stmt = $this->db->prepare($sql);
$stmt->execute(['team' => $teamId, 'player' => $playerId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row['total']) {
return 0.0;
}
return $row['wins'] / $row['total'];
}
/**
* 球员个人价值(标准化到0-100)
*/
private function getPlayerValue(int $playerId): float
{
$sql = "SELECT AVG(performance_score) AS avg_perf,
AVG(minutes) AS avg_minutes,
SUM(goals + assists) AS contributions
FROM player_appearances
WHERE player_id = :player";
$stmt = $this->db->prepare($sql);
$stmt->execute(['player' => $playerId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// 归一化计算(可依据业务调整权重)
$perfScore = min(($row['avg_perf'] ?? 0) * 10, 50);
$minuteScore = min(($row['avg_minutes'] ?? 0) / 90 * 30, 30);
$contriScore = min(($row['contributions'] ?? 0), 20);
return $perfScore + $minuteScore + $contriScore;
}
}
方法2:基于 Elo 评分的损失量化
适合有对战评分体系的场景:
class EloLossCalculator
{
public function calculateEloLoss(float $teamElo, float $playerEloWeight, float $playerContribution): float
{
// 球队 Elo 与球员权重挂钩
// playerEloWeight: 该球员对球队 Elo 的贡献占比 (0~1)
// 例如核心球员 0.25,替补 0.05
$expectedWinRateBefore = 1 / (1 + pow(10, -$teamElo / 400));
$newElo = $teamElo * (1 - $playerEloWeight);
$expectedWinRateAfter = 1 / (1 + pow(10, -$newElo / 400));
// 胜率下降 × 权重 × 100
return ($expectedWinRateBefore - $expectedWinRateAfter) * 100;
}
}
多层量化模型(推荐)
| 维度 | 权重 | 数据来源 |
|---|---|---|
| 胜率变化 | 35% | matches + player_appearances |
| 场均贡献 | 25% | goals/assists/performance_score |
| 出场时长 | 15% | minutes |
| 位置重要性 | 15% | position 映射表 |
| 替补替代能力 | 10% | 同位置第二名数据 |
public function compositeLoss(int $playerId): float
{
$winRate = $this->winRateDelta($playerId) * 35;
$contri = $this->contributionScore($playerId) * 25;
$minutes = $this->minutesScore($playerId) * 15;
$position = $this->positionWeight($playerId) * 15;
$backup = (100 - $this->backupStrength($playerId)) * 10;
return $winRate + $contri + $minutes + $position + $backup;
}
位置权重映射:
private array $positionWeightMap = [
'GK' => 100, // 守门员
'CB' => 85,
'DM' => 80,
'AM' => 90,
'ST' => 95,
'WING'=> 75,
];
性能优化建议
- 缓存中间结果:胜率、球员贡献值可用 Redis 缓存(TTL 1 小时)
- 预计算:定时任务(Cron)批量计算所有球员损失值存表
- 索引优化:
CREATE INDEX idx_player_app ON player_appearances(player_id, match_id); CREATE INDEX idx_match_team ON matches(team_id, match_date);
- 避免 N+1:批量查询时用
JOIN或WHERE IN
使用示例
$calc = new MissingStarterLossCalculator($pdo);
// 单个球员
$result = $calc->calculateLoss(playerId: 10, teamId: 1);
print_r($result);
/*
Array (
[player_id] => 10
[win_rate_with] => 72.50%
[win_rate_without] => 41.30%
[player_value] => 78.40
[loss_value] => 50.08
)
*/
// 批量计算全队
$sql = "SELECT id FROM players WHERE team_id = 1 AND is_starter = 1";
foreach ($pdo->query($sql) as $p) {
$losses[] = $calc->calculateLoss($p['id'], 1);
}
// 按 loss_value 降序排序,找出最关键球员
usort($losses, fn($a, $b) => $b['loss_value'] <=> $a['loss_value']);
输出建议
- 数值范围:建议归一化到 0-100,便于跨球员/跨球队比较
- 解释性:同时输出各维度分项,避免"黑盒"
- 动态更新:每场比赛后自动重算,用滑动窗口(如最近20场)
如果需要针对具体场景(体育竞猜赔率调整 / 项目管理人力评估 / 电竞战队分析)做进一步细化,告诉我具体业务,我可以给出更贴合的公式和权重。