PHP项目评估教练换人得分能力的设计方案
这是一个体育数据分析类需求,下面给出完整的评估体系设计和 PHP 实现思路。

核心指标定义
基础换人指标
| 指标 | 说明 | 计算方式 |
|---|---|---|
| 换人次数 | 单场/赛季换人总数 | COUNT(substitutions) |
| 换人时间 | 换人发生分钟数 | minute |
| 换人类型 | 对位换人/战术换人/伤病换人 | type |
得分能力核心指标
换人得分贡献 = 替补球员上场后球队净胜分变化
换人效率 = 换人后净胜分 / 换人次数
换人得分转化率 = 替补球员进球数 / 换人次数
进阶指标
- xG变化值:换人前后预期进球变化
- 换人后15分钟净胜球(关键窗口期)
- 逆转换人成功率:落后时换人后扳平/反超比例
数据模型设计(MySQL)
-- 比赛表
CREATE TABLE matches (
id INT PRIMARY KEY,
home_team_id INT,
away_team_id INT,
match_date DATE
);
-- 换人记录表
CREATE TABLE substitutions (
id INT PRIMARY KEY,
match_id INT,
team_id INT,
coach_id INT,
player_out_id INT,
player_in_id INT,
minute INT,
score_diff_before INT, -- 换人时比分差
score_diff_after_15min INT, -- 15分钟后比分差
goal_after INT, -- 换人后该队进球
conceded_after INT
);
-- 球员表现表
CREATE TABLE player_performance (
id INT PRIMARY KEY,
match_id INT,
player_id INT,
goals INT,
assists INT,
xg DECIMAL(5,2),
minutes_played INT,
is_substitute TINYINT
);
PHP 核心计算逻辑
class CoachSubstitutionEvaluator
{
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
/**
* 计算教练换人得分能力综合评分
*/
public function evaluateCoach(int $coachId, string $season): array
{
$subs = $this->getSubstitutions($coachId, $season);
if (empty($subs)) {
return ['score' => 0, 'detail' => '无换人数据'];
}
$totalSubs = count($subs);
$netGoalChange = 0; // 换人后净胜球变化
$goalBySubs = 0; // 替补直接进球
$assistBySubs = 0; // 替补助攻
$comebackCount = 0; // 逆转换人成功次数
$effectiveSubs = 0; // 有效换人(正贡献)
foreach ($subs as $sub) {
$delta = $sub['score_diff_after_15min'] - $sub['score_diff_before'];
$netGoalChange += $delta;
if ($delta > 0) $effectiveSubs++;
// 落后时换人后扳平或反超
if ($sub['score_diff_before'] < 0 && $sub['score_diff_after_15min'] >= 0) {
$comebackCount++;
}
// 替补球员进球/助攻
$goalBySubs += $sub['goal_after'] ?? 0;
$assistBySubs += $sub['assist_after'] ?? 0;
}
// 归一化评分(各指标加权)
$scoreNetGoal = $this->normalize($netGoalChange / $totalSubs, -1, 1);
$scoreEffectRate = $effectiveSubs / $totalSubs;
$scoreGoalRate = min(1, ($goalBySubs + $assistBySubs * 0.5) / $totalSubs);
$scoreComeback = min(1, $comebackCount / max(1, $this->countLosingSubs($subs)));
// 综合评分(0-100)
$finalScore =
$scoreNetGoal * 0.35 +
$scoreEffectRate * 0.30 +
$scoreGoalRate * 0.20 +
$scoreComeback * 0.15;
return [
'coach_id' => $coachId,
'season' => $season,
'total_subs' => $totalSubs,
'effective_subs'=> $effectiveSubs,
'net_goal' => $netGoalChange,
'goals_by_subs' => $goalBySubs,
'score' => round($finalScore * 100, 2),
'level' => $this->getLevel($finalScore),
];
}
private function normalize(float $v, float $min, float $max): float
{
return max(0, min(1, ($v - $min) / ($max - $min)));
}
private function getLevel(float $score): string
{
return match(true) {
$score >= 0.8 => 'S 顶级',
$score >= 0.65 => 'A 优秀',
$score >= 0.5 => 'B 良好',
$score >= 0.35 => 'C 一般',
default => 'D 较弱',
};
}
private function getSubstitutions(int $coachId, string $season): array
{
$sql = "SELECT s.*,
p.goals AS goal_after,
p.assists AS assist_after
FROM substitutions s
LEFT JOIN player_performance p
ON p.match_id = s.match_id AND p.player_id = s.player_in_id
WHERE s.coach_id = :coach AND YEAR(s.match_id) = :season";
$stmt = $this->db->prepare($sql);
$stmt->execute(['coach' => $coachId, 'season' => $season]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
private function countLosingSubs(array $subs): int
{
return count(array_filter($subs, fn($s) => $s['score_diff_before'] < 0));
}
}
评估维度补充建议
时间窗口分析
// 分上下半场、15分钟窗口统计换人效果
$windows = [
'1-15' => [], '16-30' => [], '31-45' => [],
'46-60' => [], '61-75' => [], '76-90' => [],
];
// 按 minute 归类统计得分效率
换人类型对比
- 对位换人(前锋换前锋 vs 中场换中场)
- 战术调整(领先守 / 落后攻)
- 伤病被动换人应从评分中排除
预期进球差(xG Delta)
$xgDelta = $teamXgAfterSub - $teamXgBeforeSub; // 用于区分 "运气好" 与 "真实战术效果"
实施注意点
- 样本量控制:换人次数 < 10 的教练评分需标注置信度低
- 对手强度加权:对强队换人成功应加权更高
- 主客场差异:分开统计更准确
- 球员质量基线:避免把"好替补"的功劳全归给教练
- 缓存计算:赛季统计结果建议存
coach_metrics_cache表
可视化输出建议
// 返回结构适合前端 ECharts 渲染
return [
'radar' => ['净胜球', '有效性', '进球转化', '逆境能力', '战术针对性'],
'trend' => [/* 按轮次的评分曲线 */],
'heatmap' => [/* 换人分钟 × 效果 */],
];
需要我针对 具体数据源(如 Opta、Wyscout API 或自建录入系统)给出对接方案,或者补充 Markov 链/评分模型(如 Bradley-Terry) 的更严谨算法实现吗?