php项目如何评估教练换人得分能力?

wen PHP项目 3

PHP项目评估教练换人得分能力的设计方案

这是一个体育数据分析类需求,下面给出完整的评估体系设计和 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;
// 用于区分 "运气好" 与 "真实战术效果"

实施注意点

  1. 样本量控制:换人次数 < 10 的教练评分需标注置信度低
  2. 对手强度加权:对强队换人成功应加权更高
  3. 主客场差异:分开统计更准确
  4. 球员质量基线:避免把"好替补"的功劳全归给教练
  5. 缓存计算:赛季统计结果建议存 coach_metrics_cache 表

可视化输出建议

// 返回结构适合前端 ECharts 渲染
return [
    'radar' => ['净胜球', '有效性', '进球转化', '逆境能力', '战术针对性'],
    'trend' => [/* 按轮次的评分曲线 */],
    'heatmap' => [/* 换人分钟 × 效果 */],
];

需要我针对 具体数据源(如 Opta、Wyscout API 或自建录入系统)给出对接方案,或者补充 Markov 链/评分模型(如 Bradley-Terry) 的更严谨算法实现吗?

抱歉,评论功能暂时关闭!