进攻三区传球成功率分析(Java 案例)
由于你没有贴出具体的 Java 案例代码,我按足球数据分析场景中最常见的实现方式给你一份完整的 Java 示例,并说明该怎么在这个案例里计算"进攻三区传球成功率"。

概念定义
| 概念 | 说明 |
|---|---|
| 进攻三区(Final Third) | 足球场按纵向划分为防守三区、中场三区、进攻三区,进攻三区通常指距对方球门 0~35 米(或按场地长度 1/3)的区域 |
| 传球成功率 | 成功传球次数 / 总传球次数 × 100% |
| 进攻三区传球成功率 | 在进攻三区内发起(或完成)的传球中,成功次数占总次数的比例 |
⚠️ 关键口径要统一:是"传球起点在进攻三区"还是"传球终点在进攻三区",不同数据源(Opta / Wyscout / StatsBomb)定义不同,代码里必须明确。
Java 案例代码
实体类:传球事件
public class PassEvent {
private String playerId;
private double startX; // 传球起点 x 坐标(0~105,单位米)
private double endX; // 传球终点 x 坐标
private boolean success; // 是否成功
private double fieldLength = 105.0; // 场地长度
// 判断起点是否在进攻三区(假设进攻方向为 x 增大)
public boolean isStartInFinalThird() {
return startX >= fieldLength * 2.0 / 3.0; // 后 1/3 区域
}
// 判断终点是否在进攻三区
public boolean isEndInFinalThird() {
return endX >= fieldLength * 2.0 / 3.0;
}
// getters / setters / constructor 省略
}
统计工具类
import java.util.*;
import java.util.stream.Collectors;
public class PassAnalyzer {
/**
* 计算进攻三区传球成功率
* @param events 全部传球事件
* @param byStart true=按起点判定;false=按终点判定
*/
public static double finalThirdPassSuccessRate(
List<PassEvent> events, boolean byStart) {
List<PassEvent> finalThirdPasses = events.stream()
.filter(e -> byStart ? e.isStartInFinalThird()
: e.isEndInFinalThird())
.collect(Collectors.toList());
if (finalThirdPasses.isEmpty()) return 0.0;
long successCount = finalThirdPasses.stream()
.filter(PassEvent::isSuccess)
.count();
return (double) successCount / finalThirdPasses.size() * 100;
}
/**
* 按球员统计
*/
public static Map<String, Double> finalThirdRateByPlayer(
List<PassEvent> events, boolean byStart) {
Map<String, List<PassEvent>> group = events.stream()
.filter(e -> byStart ? e.isStartInFinalThird()
: e.isEndInFinalThird())
.collect(Collectors.groupingBy(PassEvent::getPlayerId));
Map<String, Double> result = new LinkedHashMap<>();
group.forEach((player, list) -> {
long ok = list.stream().filter(PassEvent::isSuccess).count();
result.put(player, (double) ok / list.size() * 100);
});
return result;
}
}
测试主程序
public class Main {
public static void main(String[] args) {
List<PassEvent> events = List.of(
new PassEvent("P1", 30, 50, true),
new PassEvent("P1", 80, 95, true), // 起点在进攻三区
new PassEvent("P1", 85, 60, false), // 起点在进攻三区,失败
new PassEvent("P2", 70, 90, true), // 起点在进攻三区
new PassEvent("P2", 20, 75, true), // 终点在进攻三区
new PassEvent("P2", 40, 40, true)
);
double rate = PassAnalyzer.finalThirdPassSuccessRate(events, true);
System.out.printf("进攻三区传球成功率(按起点): %.2f%%%n", rate);
System.out.println("按球员统计:");
PassAnalyzer.finalThirdRateByPlayer(events, true)
.forEach((p, r) -> System.out.printf(" %s: %.2f%%%n", p, r));
}
}
输出示例:
进攻三区传球成功率(按起点): 66.67%
按球员统计:
P1: 50.00%
P2: 100.00%
几个易踩坑点
- 坐标系必须统一:有的数据用 0~100 归一化,有的用 0~105 米,判断阈值前先归一化。
- 进攻方向:主场球队可能从左往右、客队从右往左攻,要按
possession team的进攻方向翻转 x 坐标。 - 传球类型过滤:角球、界外球、门球是否计入?传球成功率"默认不含定位球,需要
passType过滤。 - 样本量:进攻三区传球次数很少(尤其后卫),低样本量下成功率波动大,建议同时输出
count。 - 成功率 vs 成功率%:注意返回的是
6667还是67,避免前端展示错位。
如果你能把具体的 Java 案例代码贴出来(类名、方法、字段),我可以直接在你的案例上指出"进攻三区"怎么判定、"成功率"在哪一行算、以及有没有逻辑 bug,并给出改造后的代码。