php项目如何结合xGA评估防守表现?

wen PHP项目 1

PHP 项目结合 xGA 评估防守表现

先明确一下场景,因为 "xGA" 在不同领域含义不同,最常见的是足球数据里的 Expected Goals Against(预期失球),也可能是安全领域的某种评估,下面主要按足球/体育数据类 PHP 项目来讲,如果你指的是别的,可以补充说明。

php项目如何结合xGA评估防守表现?

理解 xGA 的本质

xGA = 对手所有射门的 xG 之和,衡量"球队让对手获得了多少高质量机会"。

  • xGA 低 → 防守好(限制了对手机会质量)
  • xGA 高 → 防守差(让对手频繁获得好机会)
  • 对比实际失球 GA:xGA < GA 说明门将/运气差;xGA > GA 说明门将超神或对手射术差

核心指标组合: | 指标 | 含义 | |------|------| | xGA | 预期失球 | | GA | 实际失球 | | xGA/90 | 场均预期失球 | | PSxG | 射门后预期进球(考虑射门质量) | | xGA - GA | 防守超额表现 |

PHP 项目架构设计

数据层

-- 射门事件表(核心)
CREATE TABLE shots (
    id BIGINT PRIMARY KEY,
    match_id INT,
    team_id INT,           -- 射门方
    opponent_id INT,       -- 防守方
    player_id INT,
    minute INT,
    x DECIMAL(5,2),        -- 球场坐标
    y DECIMAL(5,2),
    body_part VARCHAR(20),
    situation VARCHAR(30), -- open_play / set_piece / penalty
    xg DECIMAL(5,4),       -- 该次射门预期进球
    is_goal TINYINT,
    created_at TIMESTAMP
);
-- 预聚合表(性能关键)
CREATE TABLE team_defense_stats (
    team_id INT,
    season VARCHAR(10),
    matches_played INT,
    xga DECIMAL(8,2),
    ga INT,
    xga_per90 DECIMAL(5,3),
    shots_faced INT,
    updated_at TIMESTAMP
);

xG 模型(射门概率计算)

如果自己训练模型,可用逻辑回归,简化版:

<?php
class XGCalculator
{
    // 简化示例,实际系数需用历史数据拟合
    private array $weights = [
        'distance'    => -0.12,
        'angle'       => 0.35,
        'is_header'   => -0.40,
        'is_penalty'  => 2.10,
        'is_counter'  => 0.25,
        'is_big_chance' => 0.90,
        'intercept'   => 0.85,
    ];
    public function calculate(array $shot): float
    {
        $distance = $this->distance($shot['x'], $shot['y']);
        $angle    = $this->angle($shot['x'], $shot['y']);
        $logit = $this->weights['intercept']
               + $this->weights['distance'] * $distance
               + $this->weights['angle'] * $angle
               + ($shot['body_part'] === 'head' ? $this->weights['is_header'] : 0)
               + ($shot['situation'] === 'penalty' ? $this->weights['is_penalty'] : 0)
               + ($shot['is_counter'] ?? 0 ? $this->weights['is_counter'] : 0)
               + ($shot['is_big_chance'] ?? 0 ? $this->weights['is_big_chance'] : 0);
        return round(1 / (1 + exp(-$logit)), 4);
    }
    private function distance(float $x, float $y): float
    {
        // 以球门中心(50, 50)为原点,单位:米
        return sqrt(pow($x - 50, 2) + pow($y - 50, 2)) * 1.05;
    }
    private function angle(float $x, float $y): float
    {
        // 射门可见球门的角度
        $goalWidth = 7.32;
        $px = $x - 50;
        $py = $y - 50;
        if (abs($px) < 0.01) return M_PI / 2;
        return atan2($goalWidth * abs($px), $px * $px + $py * $py);
    }
}

防守评估服务

<?php
class DefenseEvaluator
{
    public function __construct(
        private PDO $db,
        private XGCalculator $xgCalc
    ) {}
    /** 计算某队某赛季的 xGA */
    public function calculateXGA(int $teamId, string $season): array
    {
        $sql = "SELECT s.xg, s.is_goal, s.situation
                FROM shots s
                JOIN matches m ON s.match_id = m.id
                WHERE s.opponent_id = :team_id
                  AND m.season = :season";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['team_id' => $teamId, 'season' => $season]);
        $shots = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $xga = 0.0;
        $ga  = 0;
        $setPieceXga = 0.0;
        foreach ($shots as $shot) {
            $xga += (float)$shot['xg'];
            $ga  += (int)$shot['is_goal'];
            if (in_array($shot['situation'], ['corner', 'free_kick', 'penalty'])) {
                $setPieceXga += (float)$shot['xg'];
            }
        }
        $matches = $this->getMatchCount($teamId, $season);
        return [
            'team_id'         => $teamId,
            'season'          => $season,
            'matches'         => $matches,
            'xga'             => round($xga, 2),
            'xga_per90'       => $matches ? round($xga / $matches, 3) : 0,
            'ga'              => $ga,
            'ga_per90'        => $matches ? round($ga / $matches, 3) : 0,
            'xga_over_ga'     => round($xga - $ga, 2),  // 防守超额表现
            'set_piece_xga'   => round($setPieceXga, 2),
            'open_play_xga'   => round($xga - $setPieceXga, 2),
        ];
    }
    /** 联赛排名与百分位 */
    public function rankInLeague(string $season, string $league): array
    {
        $sql = "SELECT s.opponent_id AS team_id,
                       SUM(s.xg) AS xga, SUM(s.is_goal) AS ga
                FROM shots s
                JOIN matches m ON s.match_id = m.id
                WHERE m.season = :season AND m.league = :league
                GROUP BY s.opponent_id
                ORDER BY xga ASC";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['season' => $season, 'league' => $league]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /** PSxG(排除门将扑救后的机会质量) */
    public function calculatePSxG(int $teamId, string $season): float
    {
        // 需要射门后的追踪数据,这里示意接口
        $stmt = $this->db->prepare(
            "SELECT AVG(post_shot_xg) FROM shots
             WHERE opponent_id = ? AND season = ?"
        );
        return (float)$stmt->execute([$teamId, $season]);
    }
    private function getMatchCount(int $teamId, string $season): int
    {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM matches
             WHERE (home_id = ? OR away_id = ?) AND season = ?"
        );
        $stmt->execute([$teamId, $teamId, $season]);
        return (int)$stmt->fetchColumn();
    }
}

使用示例

$db = new PDO('mysql:host=localhost;dbname=football', 'user', 'pass');
$evaluator = new DefenseEvaluator($db, new XGCalculator());
$stats = $evaluator->calculateXGA(teamId: 12, season: '2024/25');
echo "xGA: {$stats['xga']}  (场均 {$stats['xga_per90']})\n";
echo "实际失球: {$stats['ga']}  (场均 {$stats['ga_per90']})\n";
echo "防守超额表现: {$stats['xga_over_ga']}\n";

评估维度建议

结合 xGA 做防守评估时,建议从这几个角度切:

  1. 整体防守:xGA/90 联赛排名
  2. 运动战 vs 定位球:分开算,定位球差往往是体系问题
  3. 机会质量分层:让对手打的 shots 里有多少是 big chance(>0.3 xG)
  4. 门将贡献:PSxG - GA,衡量门将超越预期
  5. 时间趋势:近 6 场 xGA 对比全季,判断状态走势
  6. 对手强度调整:面对强队 xGA 偏高是正常的,可做对手强度加权

可视化与 API 输出

// 输出给前端的 JSON
header('Content-Type: application/json');
echo json_encode([
    'xga'           => $stats['xga'],
    'xga_per90'     => $stats['xga_per90'],
    'percentile'    => $this->percentile($stats['xga'], $leagueData),
    'breakdown'     => [
        'open_play'  => $stats['open_play_xga'],
        'set_piece'  => $stats['set_piece_xga'],
    ],
    'trend'         => $this->last6MatchesTrend($teamId, $season),
], JSON_PRETTY_PRINT);

性能与工程建议

  • 预计算:xGA 不要每次都从 shots 表现算,用定时任务(如 Laravel Scheduler)每天刷新 team_defense_stats
  • 缓存:联赛榜单用 Redis,TTL 1 小时
  • 数据源:StatsBomb、Opta、Understat 都有 xG 数据,可直接采购而非自研模型
  • 验证:自研 xG 模型要用 Brier Score 或 Log Loss 评估校准度

如果你的 "xGA" 不是足球领域(比如是某种安全评估/游戏评估/网络攻防指标),告诉我具体指代,我按那个方向重新给方案。

上一篇最新php项目支持冷门出现吗?

下一篇当前分类已是最新一篇

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