java案例认为补时长短会影响进球概率吗?

wen java案例 2

本文目录导读:

java案例认为补时长短会影响进球概率吗?

  1. 问题定义
  2. Java 数据模型
  3. 统计方法选择
  4. 核心Java实现
  5. 结果解读要点
  6. 结论模板
  7. 扩展建议

这是一个非常有趣的体育数据分析问题,用Java来做这个案例分析,核心思路是:用数据检验“补时长短”与“进球概率”之间是否存在统计上的显著关系。

需要先说明一点:“补时长短”和“进球概率”之间很可能存在“反向因果”或“混杂因素”——通常是因为比赛中断多(伤病、换人、VAR、拖延时间),所以补时更长;而比赛越激烈、双方越开放,进球概率也越高,直接跑一个相关性,很容易得到“补时越长,进球越多”的结论,但这未必是补时本身导致的。

下面给出一个完整的Java案例分析框架,包括数据模型、统计检验和可视化思路。


问题定义

目标:检验假设 H0:补时长短与补时阶段进球概率无关。

  • 自变量 X:补时长度(分钟),可分为短(1-3)、中(4-6)、长(7+)
  • 因变量 Y:补时阶段是否进球(0/1),或补时阶段进球数
  • 控制变量:赛事类型、比分差距、球队实力等

Java 数据模型

public class MatchRecord {
    private String matchId;
    private int addedTimeMinutes;      // 补时时长
    private int goalsInAddedTime;      // 补时阶段进球数
    private boolean isGoalInAddedTime; // 是否进球
    private int scoreDiffAt90;         // 90分钟时比分差
    private String competition;        // 赛事
    private double homeTeamStrength;   // 球队强度
    private double awayTeamStrength;
    // 构造器、getter、setter 省略
}

统计方法选择

方法 用途
卡方检验 补时长短分组 vs 是否进球
Logistic 回归 控制混杂变量后,补时对进球概率的影响
泊松回归 补时阶段进球数作为计数变量
分组比较 短/中/长补时的进球率对比

核心Java实现

数据分组与进球率计算

import java.util.*;
import java.util.stream.*;
public class AddedTimeAnalysis {
    public static Map<String, double[]> groupByAddedTime(List<MatchRecord> records) {
        Map<String, List<MatchRecord>> groups = records.stream()
            .collect(Collectors.groupingBy(r -> {
                int t = r.getAddedTimeMinutes();
                if (t <= 3) return "短(1-3)";
                else if (t <= 6) return "中(4-6)";
                else return "长(7+)";
            }));
        Map<String, double[]> result = new LinkedHashMap<>();
        for (String key : List.of("短(1-3)", "中(4-6)", "长(7+)")) {
            List<MatchRecord> g = groups.getOrDefault(key, List.of());
            long goalMatches = g.stream().filter(MatchRecord::isGoalInAddedTime).count();
            double rate = g.isEmpty() ? 0 : (double) goalMatches / g.size();
            result.put(key, new double[]{g.size(), goalMatches, rate});
        }
        return result;
    }
}

卡方检验

public class ChiSquareTest {
    /**
     * @param observed 二维列联表 [组][是否进球]
     * @return {chiSquare, pValue, df}
     */
    public static double[] test(int[][] observed) {
        int rows = observed.length;
        int cols = observed[0].length;
        int[] rowSum = new int[rows];
        int[] colSum = new int[cols];
        int total = 0;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++) {
                rowSum[i] += observed[i][j];
                colSum[j] += observed[i][j];
                total += observed[i][j];
            }
        double chiSq = 0;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++) {
                double expected = (double) rowSum[i] * colSum[j] / total;
                if (expected > 0) {
                    double diff = observed[i][j] - expected;
                    chiSq += diff * diff / expected;
                }
            }
        int df = (rows - 1) * (cols - 1);
        double p = 1 - chiSquareCDF(chiSq, df);
        return new double[]{chiSq, p, df};
    }
    // 卡方分布CDF近似(Wilson-Hilferty)
    private static double chiSquareCDF(double x, int df) {
        double z = Math.pow(x / df, 1.0 / 3)
                 - (1 - 2.0 / (9 * df));
        z /= Math.sqrt(2.0 / (9 * df));
        return normalCDF(z);
    }
    private static double normalCDF(double z) {
        return 0.5 * (1 + erf(z / Math.sqrt(2)));
    }
    private static double erf(double x) {
        double t = 1.0 / (1.0 + 0.3275911 * Math.abs(x));
        double y = 1 - (((((1.061405429 * t - 1.453152027) * t)
                + 1.421413741) * t - 0.284496736) * t
                + 0.254829592) * t * Math.exp(-x * x);
        return x >= 0 ? y : -y;
    }
}

Logistic 回归(简化版,梯度下降)

public class LogisticRegression {
    private double[] weights;
    private double bias;
    public void fit(double[][] X, int[] y, int epochs, double lr) {
        int n = X.length, m = X[0].length;
        weights = new double[m];
        bias = 0;
        for (int epoch = 0; epoch < epochs; epoch++) {
            double[] gradW = new double[m];
            double gradB = 0;
            for (int i = 0; i < n; i++) {
                double z = bias;
                for (int j = 0; j < m; j++) z += weights[j] * X[i][j];
                double pred = sigmoid(z);
                double err = pred - y[i];
                for (int j = 0; j < m; j++) gradW[j] += err * X[i][j];
                gradB += err;
            }
            for (int j = 0; j < m; j++) weights[j] -= lr * gradW[j] / n;
            bias -= lr * gradB / n;
        }
    }
    public double predictProb(double[] x) {
        double z = bias;
        for (int j = 0; j < x.length; j++) z += weights[j] * x[j];
        return sigmoid(z);
    }
    public double[] getWeights() { return weights; }
    public double getBias() { return bias; }
    private double sigmoid(double z) { return 1.0 / (1 + Math.exp(-z)); }
}

主流程

public class Main {
    public static void main(String[] args) {
        List<MatchRecord> data = DataLoader.load("matches.csv");
        // 1. 分组描述统计
        Map<String, double[]> stats = AddedTimeAnalysis.groupByAddedTime(data);
        stats.forEach((k, v) ->
            System.out.printf("%s: n=%.0f, 进球场次=%.0f, 进球率=%.2f%%%n",
                k, v[0], v[1], v[2] * 100));
        // 2. 卡方检验
        int[][] table = buildContingencyTable(data);
        double[] chi = ChiSquareTest.test(table);
        System.out.printf("卡方=%.3f, df=%.0f, p=%.4f%n", chi[0], chi[2], chi[1]);
        System.out.println(chi[1] < 0.05 ? "拒绝H0:补时长短与进球相关" 
                                          : "不能拒绝H0");
        // 3. Logistic回归(控制比分差、球队强度等)
        double[][] X = buildFeatures(data); // [补时分钟, 比分差, 强度差, ...]
        int[] y = data.stream().mapToInt(r -> r.isGoalInAddedTime() ? 1 : 0).toArray();
        LogisticRegression lr = new LogisticRegression();
        lr.fit(X, y, 5000, 0.01);
        System.out.println("补时系数=" + lr.getWeights()[0] 
            + ", OR=" + Math.exp(lr.getWeights()[0]));
    }
}

结果解读要点

典型发现(基于真实赛事数据经验)

补时长度 进球率(大致)
1-3 分钟 8-12%
4-6 分钟 15-20%
7+ 分钟 25-35%

看起来“补时越长进球越多”,但要注意:

关键偏差来源

  • 反向因果:不是补时长导致进球,而是比赛越开放/越混乱 → 补时越长 + 进球越多。
  • 选择偏差:强队落后时会疯狂进攻,裁判也倾向给更长补时。
  • 分母问题:补时7分钟比补时2分钟多了3.5倍的“暴露时间”,进球概率自然更高,应比较每分钟进球率。

正确的分析姿势

// 计算“每分钟进球率”而非“每场进球率”
double goalsPerMinute = goalsInAddedTime / (double) addedTimeMinutes;

或用泊松回归,把补时分钟数作为 offset 处理。


结论模板

在控制比分差、球队强度、赛事类型后,Logistic回归显示补时长度对进球概率的独立效应为 OR = X.XX(95% CI ...),若 OR 接近 1 且 p > 0.05,说明补时长短本身并不显著影响进球概率,此前观察到的相关性主要由比赛激烈程度和暴露时间驱动。


扩展建议

  1. 按赛事分层:杯赛 vs 联赛补时行为不同。
  2. 时间序列:VAR引入前后补时长度变化。
  3. 生存分析:把补时阶段建模为“到进球的时间”。
  4. 可视化:用 JFreeChart 画补时长度分布 + 进球率曲线。

如果你有具体数据集(CSV格式),我可以帮你写出完整的可运行 Java 项目结构,包括 Maven 依赖、数据加载和报表输出。

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