php项目统计落叶球射门成功率如何?

wen PHP项目 1

PHP项目统计“落叶球”射门成功率方案

“落叶球”(knuckleball / dipping shot)本质是一种射门轨迹特征,要统计它的成功率,核心难点不在 PHP,而在于如何判定一次射门是“落叶球”,下面给你一套可落地的方案。

php项目统计落叶球射门成功率如何?


先定义清楚“落叶球”和“成功率”

落叶球的判定标准(业务规则先行)

建议用可量化的射门事件数据来定义,

特征 判定条件示例
射门类型 shot_type = 'knuckleball'(人工/模型标注)
球速 射门初速 > 25 m/s
旋转 球旋转速率 < 300 rpm(低旋转是关键)
轨迹下坠 飞行末端垂直下落加速度明显(追踪数据)
距离 射门距离 > 20m(远射才谈得上落叶)

最简单可行的 MVP:依赖数据源里已有的 shot_type 或 technique,不自己算物理量。

成功率定义

成功率 = 落叶球射门进球数 / 落叶球射门总数 × 100%

可扩展:

  • 射正率 = 射正次数 / 总数
  • 预期进球对比 = 实际进球 / xG(衡量是否“超常发挥”)
  • 分距离区间、分球员、分赛季统计

数据库表设计

-- 射门事件表
CREATE TABLE shots (
    id            BIGINT PRIMARY KEY AUTO_INCREMENT,
    match_id      BIGINT NOT NULL,
    player_id     BIGINT NOT NULL,
    team_id       BIGINT NOT NULL,
    shot_type     VARCHAR(32)  NOT NULL,   -- 'knuckleball','curve','power'...
    distance_m    DECIMAL(5,1),            -- 射门距离(米)
    is_on_target  TINYINT(1) DEFAULT 0,    -- 是否射正
    is_goal       TINYINT(1) DEFAULT 0,    -- 是否进球
    xg            DECIMAL(5,3),            -- 预期进球
    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_type (shot_type),
    INDEX idx_player (player_id),
    INDEX idx_match (match_id)
);

如果落叶球需要从原始追踪数据推断,再加一张 shot_tracking 表存速度、旋转、轨迹点,用离线任务打标后回写 shot_type。


PHP 统计代码(核心)

<?php
class KnuckleballStats
{
    public function __construct(private PDO $pdo) {}
    /**
     * 统计落叶球射门成功率
     * @param array $filter ['season'=>2024,'player_id'=>null,'min_distance'=>null]
     */
    public function successRate(array $filter = []): array
    {
        $where  = ["shot_type = :type"];
        $params = [':type' => 'knuckleball'];
        if (!empty($filter['season'])) {
            $where[] = "YEAR(created_at) = :season";
            $params[':season'] = $filter['season'];
        }
        if (!empty($filter['player_id'])) {
            $where[] = "player_id = :player_id";
            $params[':player_id'] = $filter['player_id'];
        }
        if (!empty($filter['min_distance'])) {
            $where[] = "distance_m >= :min_d";
            $params[':min_d'] = $filter['min_distance'];
        }
        $sql = "SELECT
                    COUNT(*)                          AS total,
                    SUM(is_goal)                      AS goals,
                    SUM(is_on_target)                 AS on_target,
                    AVG(xg)                           AS avg_xg,
                    SUM(xg)                           AS total_xg
                FROM shots
                WHERE " . implode(' AND ', $where);
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $total = (int)$row['total'];
        $goals = (int)$row['goals'];
        return [
            'total_shots'   => $total,
            'goals'         => $goals,
            'success_rate'  => $total > 0 ? round($goals / $total * 100, 2) : 0.0,
            'on_target_rate'=> $total > 0 ? round((int)$row['on_target'] / $total * 100, 2) : 0.0,
            'avg_xg'        => $row['avg_xg'] !== null ? round((float)$row['avg_xg'], 3) : null,
            'goals_vs_xg'   => ($row['total_xg'] > 0)
                                 ? round($goals / (float)$row['total_xg'], 2)
                                 : null,
        ];
    }
    /** 按球员分组排行(谁最擅长落叶球) */
    public function rankByPlayer(int $season, int $minShots = 5): array
    {
        $sql = "SELECT p.id, p.name,
                       COUNT(*) AS shots,
                       SUM(s.is_goal) AS goals,
                       ROUND(SUM(s.is_goal)/COUNT(*)*100, 2) AS rate
                FROM shots s
                JOIN players p ON p.id = s.player_id
                WHERE s.shot_type = 'knuckleball' AND YEAR(s.created_at) = :season
                GROUP BY p.id, p.name
                HAVING shots >= :min
                ORDER BY rate DESC, goals DESC";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':season' => $season, ':min' => $minShots]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

调用:

$stats = new KnuckleballStats($pdo);
$r = $stats->successRate(['season' => 2024, 'min_distance' => 20]);
// ['total_shots'=>128, 'goals'=>11, 'success_rate'=>8.59, ...]

几个务实的注意点

  1. 判定口径统一比算法花哨更重要,落叶球常被误标,建议制定标注手册,多人标注取一致,或只统计明确标注的样本。
  2. 样本量问题,落叶球本身占比小,单赛季可能几十次,成功率波动极大,建议给出置信区间(如 Wilson 区间)或至少标注样本量,避免“1射1进=100%”。
  3. 用 xG 校准,单纯成功率受射门位置影响大,goals / total_xg > 1 才说明“落叶球有额外价值”。
  4. 性能,数据量大时给 (shot_type, created_at) 建联合索引;统计结果可缓存到 Redis 或定时汇总表。
  5. 若要从原始数据推断落叶球:轨迹物理计算(速度、旋转、下落)更适合用 Python 离线跑批,PHP 只负责查询和展示,不要用 PHP 做大量数值计算。

需要的话,我可以再给你:Wilson 置信区间实现、按距离区间分层统计,或者从 tracking 数据判定落叶球的算法伪代码,你目前的数据里是否有 shot_type 标签,还是需要自己从轨迹判断?

上一篇这个php项目怎么看双方射门转化率差异?

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

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