量化主队球迷人数对比赛影响的Java案例分析
问题定义
在体育数据分析中,"主队球迷人数影响"通常指主场观众数量对主队表现(胜率、进球数、得分等)的量化影响,下面给出一个完整的Java实现思路与代码。

分析框架
| 维度 | 说明 |
|---|---|
| 自变量 X | 主队球迷人数(上座人数) |
| 因变量 Y | 比赛结果(胜=1/平=0.5/负=0,或进球差) |
| 控制变量 | 对手实力、赛季、天气、球队近期状态 |
| 模型 | 线性回归 / 逻辑回归 / 相关性分析 |
数据结构设计
public class MatchRecord {
private String matchId;
private String homeTeam;
private String awayTeam;
private int attendance; // 主队球迷人数
private int homeGoals;
private int awayGoals;
private int homeRank; // 主队联赛排名(控制变量)
private int awayRank; // 客队联赛排名
private String weather; // 天气
private String season;
// 构造器、getter、setter 省略
}
核心量化指标
胜率与上座率分组对比
import java.util.*;
import java.util.stream.Collectors;
public class AttendanceAnalyzer {
/** 按上座人数分档,统计主队胜率 */
public static Map<String, Double> winRateByAttendanceBucket(List<MatchRecord> matches) {
// 计算上座人数的分位数
List<Integer> attendances = matches.stream()
.map(MatchRecord::getAttendance)
.sorted()
.collect(Collectors.toList());
int q1 = attendances.get((int) (attendances.size() * 0.25));
int q2 = attendances.get((int) (attendances.size() * 0.50));
int q3 = attendances.get((int) (attendances.size() * 0.75));
Map<String, List<MatchRecord>> buckets = new HashMap<>();
buckets.put("低上座(<Q1)", new ArrayList<>());
buckets.put("中低(Q1-Q2)", new ArrayList<>());
buckets.put("中高(Q2-Q3)", new ArrayList<>());
buckets.put("高上座(>Q3)", new ArrayList<>());
for (MatchRecord m : matches) {
int att = m.getAttendance();
if (att <= q1) buckets.get("低上座(<Q1)").add(m);
else if (att <= q2) buckets.get("中低(Q1-Q2)").add(m);
else if (att <= q3) buckets.get("中高(Q2-Q3)").add(m);
else buckets.get("高上座(>Q3)").add(m);
}
Map<String, Double> result = new LinkedHashMap<>();
for (Map.Entry<String, List<MatchRecord>> e : buckets.entrySet()) {
long wins = e.getValue().stream()
.filter(m -> m.getHomeGoals() > m.getAwayGoals())
.count();
double rate = e.getValue().isEmpty() ? 0 : (double) wins / e.getValue().size();
result.put(e.getKey(), rate);
}
return result;
}
}
皮尔逊相关系数(上座人数 vs 净胜球)
public class CorrelationUtil {
public static double pearson(List<Double> x, List<Double> y) {
int n = x.size();
double sumX = x.stream().mapToDouble(Double::doubleValue).sum();
double sumY = y.stream().mapToDouble(Double::doubleValue).sum();
double sumXY = 0, sumX2 = 0, sumY2 = 0;
for (int i = 0; i < n; i++) {
sumXY += x.get(i) * y.get(i);
sumX2 += x.get(i) * x.get(i);
sumY2 += y.get(i) * y.get(i);
}
double num = n * sumXY - sumX * sumY;
double den = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
return den == 0 ? 0 : num / den;
}
public static void main(String[] args) {
List<Double> attendance = Arrays.asList(20000.0, 35000.0, 45000.0, 60000.0, 75000.0);
List<Double> goalDiff = Arrays.asList(0.0, 1.0, 1.0, 2.0, 3.0);
System.out.println("相关系数 r = " + pearson(attendance, goalDiff));
}
}
多元线性回归(控制对手实力)
单纯相关无法排除"强队吸引更多球迷"这一反向因果,需引入控制变量。
import org.apache.commons.math3.linear.*;
public class LinearRegressionAnalyzer {
/**
* Y = b0 + b1*attendance + b2*homeRank + b3*awayRank
* 返回系数数组 [b0, b1, b2, b3]
*/
public static double[] fit(List<MatchRecord> data) {
int n = data.size();
double[][] design = new double[n][4];
double[] y = new double[n];
for (int i = 0; i < n; i++) {
MatchRecord m = data.get(i);
design[i][0] = 1; // 截距
design[i][1] = m.getAttendance() / 10000.0; // 万人为单位,便于解读
design[i][2] = m.getHomeRank();
design[i][3] = m.getAwayRank();
y[i] = m.getHomeGoals() - m.getAwayGoals(); // 净胜球
}
RealMatrix X = new Array2DRowRealMatrix(design);
RealVector Y = new ArrayRealVector(y);
// 最小二乘:beta = (X'X)^-1 X'Y
RealMatrix Xt = X.transpose();
RealMatrix XtX = Xt.multiply(X);
RealVector XtY = Xt.operate(Y);
DecompositionSolver solver = new LUDecomposition(XtX).getSolver();
return solver.solve(XtY).toArray();
}
public static void main(String[] args) {
List<MatchRecord> data = loadData(); // 加载历史数据
double[] beta = fit(data);
System.out.println("截距 b0 = " + beta[0]);
System.out.println("球迷效应 b1 = " + beta[1] + " (每增加1万人,净胜球平均+ " + beta[1] + ")");
System.out.println("主队实力 b2 = " + beta[2]);
System.out.println("客队实力 b3 = " + beta[3]);
}
}
进阶:控制球队固定效应
由于不同球队基础实力差异大,可做去均值处理(固定效应):
public static List<MatchRecord> demeanByTeam(List<MatchRecord> data) {
Map<String, Double> teamAvgAttendance = data.stream()
.collect(Collectors.groupingBy(
MatchRecord::getHomeTeam,
Collectors.averagingDouble(MatchRecord::getAttendance)));
Map<String, Double> teamAvgGoalDiff = data.stream()
.collect(Collectors.groupingBy(
MatchRecord::getHomeTeam,
Collectors.averagingDouble(m -> m.getHomeGoals() - m.getAwayGoals())));
data.forEach(m -> {
m.setAttendance((int) (m.getAttendance() - teamAvgAttendance.get(m.getHomeTeam())));
// goalDiff 同样去均值后参与回归
});
return data;
}
完整量化流程
数据采集 → 爬取/导入比赛数据(含上座、比分、排名)
2. 数据清洗 → 剔除空场、疫情赛季异常值
3. 描述统计 → 分组胜率、相关系数
4. 多元回归 → 加入控制变量,得出边际效应 b1
5. 稳健性检验 → 去均值、分赛季子样本、工具变量
6. 结果解读 → "每增加1万球迷,主队净胜球平均 +0.15"
结果解读示例
| 指标 | 数值 | 含义 |
|---|---|---|
| r | 42 | 中等正相关 |
| b1 | +0.15 | 每增加1万球迷,净胜球+0.15 |
| p-value | <0.01 | 显著 |
主队球迷人数对比赛结果存在统计显著的正向影响,符合"主场优势"理论。
注意事项(坑点)
- 反向因果:强队吸引更多球迷 → 需控制球队实力。
- 混淆变量:周末/节假日比赛人多且对手弱 → 需加入时间/对手固定效应。
- 疫情空场数据:2020-2021赛季应单独分析(天然实验)。
- 样本量:少于100场时回归系数不稳定。
- 容量上限效应:球场容量饱和后效应可能非线性 → 可尝试对数或分段回归。
如果你有具体数据集格式(CSV字段、联赛、年份),我可以帮你写针对性的 loadData() 和完整可运行 Demo。