PHP项目中传球成功率与胜率的相关性分析
下面给你一套完整的 PHP 实现方案,从数据准备到相关性计算再到可视化。

核心思路
传球成功率(Pass Success Rate)与胜率(Win Rate)的相关性,通常用 皮尔逊相关系数(Pearson r) 来衡量:
$$r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (y_i - \bar{y})^2}}$$
x= 球队某场比赛的传球成功率y= 该场比赛结果(胜=1,平=0.5,负=0)
PHP 实现代码
数据表结构(MySQL)
CREATE TABLE match_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
team_id INT NOT NULL,
match_id INT NOT NULL,
passes_total INT,
passes_completed INT,
result ENUM('win','draw','loss'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
数据模型与查询
<?php
class MatchStatsRepository
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* 获取球队每场比赛的传球成功率与胜负结果
* @return array<array{pass_rate: float, result_score: float}>
*/
public function getPassRateAndResult(int $teamId): array
{
$sql = "SELECT
passes_completed / NULLIF(passes_total, 0) AS pass_rate,
CASE result
WHEN 'win' THEN 1.0
WHEN 'draw' THEN 0.5
WHEN 'loss' THEN 0.0
END AS result_score
FROM match_stats
WHERE team_id = :team_id
AND passes_total > 0";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':team_id' => $teamId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
相关系数计算工具类
<?php
class CorrelationCalculator
{
/**
* 皮尔逊相关系数
* @param float[] $x
* @param float[] $y
* @return array{r: float, p_value: float, n: int, interpretation: string}
*/
public static function pearson(array $x, array $y): array
{
$n = count($x);
if ($n < 3 || $n !== count($y)) {
throw new InvalidArgumentException('样本量不足或维度不匹配');
}
$meanX = array_sum($x) / $n;
$meanY = array_sum($y) / $n;
$num = 0.0;
$denX = 0.0;
$denY = 0.0;
for ($i = 0; $i < $n; $i++) {
$dx = $x[$i] - $meanX;
$dy = $y[$i] - $meanY;
$num += $dx * $dy;
$denX += $dx * $dx;
$denY += $dy * $dy;
}
$denominator = sqrt($denX * $denY);
$r = $denominator == 0 ? 0.0 : $num / $denominator;
// t 检验求 p 值(双侧)
$t = $r * sqrt(($n - 2) / max(1e-9, 1 - $r * $r));
$p = self::tDistTwoTailed(abs($t), $n - 2);
return [
'r' => round($r, 4),
'p_value' => round($p, 6),
'n' => $n,
'interpretation' => self::interpret($r, $p),
];
}
private static function interpret(float $r, float $p): string
{
$strength = match (true) {
abs($r) >= 0.8 => '极强',
abs($r) >= 0.6 => '强',
abs($r) >= 0.4 => '中等',
abs($r) >= 0.2 => '弱',
default => '可忽略',
};
$direction = $r > 0 ? '正' : '负';
$sig = $p < 0.05 ? '显著' : '不显著';
return "{$direction}相关,强度{$strength}({$sig})";
}
/**
* t 分布双侧 p 值(Abramowitz & Stegun 近似)
*/
private static function tDistTwoTailed(float $t, int $df): float
{
$x = $df / ($df + $t * $t);
return self::incompleteBeta($x, $df / 2, 0.5); // 即 p 值
}
private static function incompleteBeta(float $x, float $a, float $b): float
{
// 简化:使用连分式实现(略,可用开源库如 pear/pear 替代)
// 建议生产环境使用:composer require markrogoyski/math-php
$bt = exp(self::lnGamma($a + $b) - self::lnGamma($a) - self::lnGamma($b)
+ $a * log($x) + $b * log(1 - $x));
return $bt / $a * self::betacf($x, $a, $b);
}
private static function lnGamma(float $z): float
{
// Lanczos 近似
$g = 7;
$c = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,
771.32342877765313, -176.61502916214059, 12.507343278686905,
-0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
if ($z < 0.5) return log(M_PI / sin(M_PI * $z)) - self::lnGamma(1 - $z);
$z -= 1;
$x = $c[0];
for ($i = 1; $i < $g + 2; $i++) $x += $c[$i] / ($z + $i);
$tmp = $z + $g + 0.5;
return 0.5 * log(2 * M_PI) + ($z + 0.5) * log($tmp) - $tmp + log($x);
}
private static function betacf(float $x, float $a, float $b): float
{
$MAXIT = 100; $EPS = 3e-7; $FPMIN = 1e-30;
$qab = $a + $b; $qap = $a + 1; $qam = $a - 1;
$c = 1.0; $d = 1 - $qab * $x / $qap;
if (abs($d) < $FPMIN) $d = $FPMIN;
$d = 1 / $d; $h = $d;
for ($m = 1; $m <= $MAXIT; $m++) {
$m2 = 2 * $m;
$aa = $m * ($b - $m) * $x / (($qam + $m2) * ($a + $m2));
$d = 1 + $aa * $d; if (abs($d) < $FPMIN) $d = $FPMIN;
$c = 1 + $aa / $c; if (abs($c) < $FPMIN) $c = $FPMIN;
$d = 1 / $d; $h *= $d * $c;
$aa = -($a + $m) * ($qab + $m) * $x / (($a + $m2) * ($qap + $m2));
$d = 1 + $aa * $d; if (abs($d) < $FPMIN) $d = $FPMIN;
$c = 1 + $aa / $c; if (abs($c) < $FPMIN) $c = $FPMIN;
$d = 1 / $d; $del = $d * $c; $h *= $del;
if (abs($del - 1) < $EPS) break;
}
return $h;
}
}
业务服务层
<?php
class PassWinAnalysisService
{
public function __construct(private MatchStatsRepository $repo) {}
public function analyze(int $teamId): array
{
$rows = $this->repo->getPassRateAndResult($teamId);
if (count($rows) < 5) {
return ['error' => '样本量不足(少于5场)'];
}
$x = array_column($rows, 'pass_rate');
$y = array_column($rows, 'result_score');
$corr = CorrelationCalculator::pearson($x, $y);
// 附加:平均传球成功率对比胜/平/负
$grouped = [];
foreach ($rows as $row) {
$grouped[$row['result_score']][] = $row['pass_rate'];
}
$avgByResult = [];
foreach ($grouped as $score => $rates) {
$avgByResult[$score] = round(array_sum($rates) / count($rates), 4);
}
return array_merge($corr, [
'avg_pass_rate_by_result' => $avgByResult,
]);
}
}
调用示例
<?php
$pdo = new PDO('mysql:host=localhost;dbname=football;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$repo = new MatchStatsRepository($pdo);
$service = new PassWinAnalysisService($repo);
$result = $service->analyze(teamId: 12);
print_r($result);
输出示例:
Array (
[r] => 0.5231
[p_value] => 0.003421
[n] => 30
[interpretation] => 正相关,强度中等(显著)
[avg_pass_rate_by_result] => Array (
[1] => 0.8742 // 胜
[0.5] => 0.8510 // 平
[0] => 0.8295 // 负
)
)
结果解读
| r 值范围 | 相关性强度 | 典型足球场景) |
|---|---|---|
| 6 ~ 1.0 | 强正相关 | 传球成功率越高胜率越高(如曼城、巴萨) |
| 4 ~ 0.6 | 中等正相关 | 有一定影响,但不是决定性 |
| 2 ~ 0.4 | 弱相关 | 单个指标解释力有限 |
| -0.2 ~ 0.2 | 可忽略 | 基本无关 |
| < -0.2 | 负相关 | 反常现象(样本问题?) |
典型经验值(欧洲五大联赛数据):
- 顶级技术型球队:r ≈ 45 ~ 0.65(中等偏强)
- 完整联赛 380 场样本:r ≈ 35 ~ 0.50
- 单支球队 30~38 场:波动较大,需配合 p 值判断
进阶建议
- 区分主客场:客队传球成功率普遍低 3~5%,需分层分析
- 对手强弱加权:用 Elo 差值作为协变量,做偏相关分析
- 用胜率而非比赛结果:若按赛季聚合,需先算赛季平均传球成功率 → 赛季胜率
- 多因子回归:
胜率 ~ β0 + β1·传球成功率 + β2·射正数 + β3·控球率 + ... - 安装现成库(推荐,不必手写 t 分布):
composer require markrogoyski/math-php
use MathPHP\Statistics\Correlation; $result = Correlation::pearson($x, $y); // ['r', 'ρ', ...]
一句话结论
在 PHP 项目中,计算传球成功率与胜率的皮尔逊相关系数是可行的,代码量约 100 行;实战数据通常显示 中等正相关(r≈0.4~0.6),说明传球成功率是胜率的重要但非唯一因素,建议结合 p 值、样本量、队内分组均值综合判断,避免得出"传球成功率高必然赢"的误导结论。
如果你把数据表结构或具体样本贴出来,我可以帮你把 SQL 和 PHP 代码调整为完全匹配的版本。
友情链接
版权声明:本站文章为原创整理,转载请注明出处。