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

理解 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 做防守评估时,建议从这几个角度切:
- 整体防守:xGA/90 联赛排名
- 运动战 vs 定位球:分开算,定位球差往往是体系问题
- 机会质量分层:让对手打的 shots 里有多少是 big chance(>0.3 xG)
- 门将贡献:PSxG - GA,衡量门将超越预期
- 时间趋势:近 6 场 xGA 对比全季,判断状态走势
- 对手强度调整:面对强队 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" 不是足球领域(比如是某种安全评估/游戏评估/网络攻防指标),告诉我具体指代,我按那个方向重新给方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播