以下是一个分析框架。它用 pandas 建立了一套针对"使用伊泽瑞尔的玩家"的对手画像、团队指标、英雄池、BP、对线期、团战期、眼位、视野、核心对位、资源效率、胜利贡献值等分析模块。代码内置了模拟数据,可以替换为真实 CSV 数据。
```python
import pandas as pd
import numpy as np
from dataclasses import dataclass
@dataclass
class LoLData:
matches: pd.DataFrame
player_stats: pd.DataFrame
ward_events: pd.DataFrame
bp_events: pd.DataFrame
class EzrealOpponentAnalyzer:
def init(self, data: LoLData):
self.matches = data.matches
self.ps = data.player_stats.copy()
self.wards = data.ward_events.copy()
self.bp = data.bp_events.copy()
self._prepare()
def _prepare(self):
衍生指标
self.ps'damage_per_gold' = self.ps'damage_to_champs' / self.ps'gold'.replace(0, np.nan)
self.ps'kp' = (self.ps'kills' + self.ps'assists') / \
self.ps.groupby('match_id', 'team')'kills'.transform('sum').replace(0, np.nan)
self.ps'cc_hit_rate' = self.ps'cc_hits' / self.ps'cc_attempts'.replace(0, np.nan)
self.ps'skillshot_hit_rate' = self.ps'skillshots_hits' / self.ps'skillshots_attempts'.replace(0, np.nan)
self.ps'survive_rate' = self.ps'teamfight_survived' / self.ps'teamfights'.replace(0, np.nan)
self.ps'ineffective_death_rate' = self.ps'ineffective_deaths' / self.ps'deaths'.replace(0, np.nan)
self.ps'free_kill_rate' = self.ps'free_kills' / self.ps'kills'.replace(0, np.nan)
胜利贡献值 VC(简化加权模型:伤害、击杀、经济、对线)
self.ps'VC' = (
0.35 * self.ps'damage_to_champs' / self.ps.groupby('match_id', 'team')'damage_to_champs'.transform('sum') +
0.25 * self.ps'kills' / self.ps.groupby('match_id', 'team')'kills'.transform('sum') +
0.20 * self.ps'gold' / self.ps.groupby('match_id', 'team')'gold'.transform('sum') +
0.20 * self.ps'gd15' / 1000
)
合并比赛信息
self.ps = self.ps.merge(
self.matches[['match_id', 'team', 'result', 'first_blood_team',
'duration', 'dragons', 'heralds']],
on='match_id', 'team', how='left'
)
self.ps'is_ezreal' = self.ps'champion' == 'Ezreal'
1. 对手风格画像
def opponent_style(self, target_champion='Ezreal'):
ez = self.psself.ps\['champion' == target_champion]\['match_id', 'team'].drop_duplicates()
opp = self.ps.merge(ez.rename(columns={'team': 'ez_team'}), on='match_id')
opp = oppopp\['team' != opp'ez_team']
team_match = opp.groupby('match_id', 'team').agg(
kills=('kills', 'sum'), deaths=('deaths', 'sum'),
gold=('gold', 'sum'), damage=('damage_to_champs', 'sum')
).reset_index()
team_match = team_match.merge(
self.matches\['match_id', 'team', 'first_blood_team', 'dragons', 'heralds', 'result'],
on='match_id', 'team'
)
team_match'first_blood' = (team_match'team' == team_match'first_blood_team').astype(int)
team_match'win' = (team_match'result' == 'win').astype(int)
资源倾斜:每场每选手经济/伤害/补刀占比
totals = opp.groupby('match_id', 'team')\['gold', 'damage_to_champs', 'cs'].transform('sum')
opp = opp.assign(
gold_share=opp'gold' / totals'gold',
damage_share=opp'damage_to_champs' / totals'damage_to_champs',
cs_share=opp'cs' / totals'cs'
)
bias = opp.groupby('match_id', 'team').agg(
max_gold_share=('gold_share', 'max'),
max_damage_share=('damage_share', 'max'),
gold_share_std=('gold_share', 'std')
).reset_index()
team_match = team_match.merge(bias, on='match_id', 'team')
style = team_match.groupby('team').agg(
matches=('match_id', 'nunique'),
avg_kills=('kills', 'mean'),
avg_deaths=('deaths', 'mean'),
first_blood_rate=('first_blood', 'mean'),
avg_dragons=('dragons', 'mean'),
avg_heralds=('heralds', 'mean'),
win_rate=('win', 'mean'),
avg_max_gold_share=('max_gold_share', 'mean'),
avg_max_damage_share=('max_damage_share', 'mean'),
gold_share_std=('gold_share_std', 'mean')
).reset_index()
style'style' = np.where(style'avg_kills' > style'avg_kills'.median(), '激进', '保守')
style'resource_bias' = np.where(style'avg_max_gold_share' > 0.35, '资源倾斜', '资源均衡')
return style.sort_values('matches', ascending=False)
2. 团队整体指标
def team_metrics(self):
team_match = self.ps.groupby('match_id', 'team').agg(
kills=('kills', 'sum'), deaths=('deaths', 'sum')
).reset_index()
team_match = team_match.merge(
self.matches[['match_id', 'team', 'first_blood_team', 'dragons',
'heralds', 'result', 'duration']],
on='match_id', 'team'
)
team_match'first_blood' = (team_match'team' == team_match'first_blood_team').astype(int)
team_match'win' = (team_match'result' == 'win').astype(int)
bins = 0, 1500, 1800, 2100, 2400, np.inf
labels = '\<25min', '25-30min', '30-35min', '35-40min', '\>40min'
team_match'time_bucket' = pd.cut(team_match'duration', bins=bins, labels=labels)
time_win = team_match.groupby('team', 'time_bucket', observed=True)'win'.mean().unstack(fill_value=np.nan)
overall = team_match.groupby('team').agg(
matches=('match_id', 'nunique'),
avg_kills=('kills', 'mean'),
avg_deaths=('deaths', 'mean'),
first_blood_rate=('first_blood', 'mean'),
avg_dragons=('dragons', 'mean'),
avg_heralds=('heralds', 'mean'),
win_rate=('win', 'mean')
).reset_index()
return overall.merge(time_win, on='team')
3. 英雄池与胜率
def champion_pool(self, player=None):
df = self.ps.copy()
if player:
df = dfdf\['player' == player]
pool = df.groupby('player', 'champion').agg(
games=('match_id', 'nunique'),
wins=('result', lambda x: (x == 'win').sum()),
kills=('kills', 'mean'),
deaths=('deaths', 'mean'),
assists=('assists', 'mean'),
gd15=('gd15', 'mean'),
xpd15=('xpd15', 'mean'),
cspm=('cspm', 'mean'),
dpm=('dpm', 'mean'),
damage_per_gold=('damage_per_gold', 'mean')
).reset_index()
pool'win_rate' = pool'wins' / pool'games'
pool'kda' = (pool'kills' + pool'assists') / pool'deaths'.replace(0, np.nan)
return pool.sort_values('player', 'games', ascending=True, False)
4. BP 趋势与协同
def bp_trends(self):
total_matches = self.matches'match_id'.nunique()
bp = self.bp.copy()
bp'is_ban' = bp'action' == 'ban'
bp'is_pick' = bp'action' == 'pick'
champ = bp.groupby('champion').agg(
bans=('is_ban', 'sum'),
picks=('is_pick', 'sum')
).reset_index()
champ'ban_rate' = champ'bans' / total_matches
champ'pick_rate' = champ'picks' / total_matches
中野组合
mid_jug = self.psself.ps\['role'.isin('MID', 'JUG')].pivot_table(
index='match_id', 'team', columns='role', values='champion', aggfunc='first'
).reset_index().dropna(subset='MID', 'JUG')
mid_jug = mid_jug.merge(self.matches\['match_id', 'team', 'result'], on='match_id', 'team')
mid_jug'combo' = mid_jug'MID' + ' + ' + mid_jug'JUG'
mid_jug_win = mid_jug.groupby('combo').agg(
games=('match_id', 'nunique'),
win_rate=('result', lambda x: (x == 'win').mean())
).reset_index()
下路双人组
bot_sup = self.psself.ps\['role'.isin('ADC', 'SUP')].pivot_table(
index='match_id', 'team', columns='role', values='champion', aggfunc='first'
).reset_index().dropna(subset='ADC', 'SUP')
bot_sup = bot_sup.merge(self.matches\['match_id', 'team', 'result'], on='match_id', 'team')
bot_sup'combo' = bot_sup'ADC' + ' + ' + bot_sup'SUP'
bot_sup_win = bot_sup.groupby('combo').agg(
games=('match_id', 'nunique'),
win_rate=('result', lambda x: (x == 'win').mean())
).reset_index()
return champ, mid_jug_win, bot_sup_win
5. 对线期
def laning_phase(self):
return self.ps.groupby('player', 'champion').agg(
games=('match_id', 'nunique'),
gd15=('gd15', 'mean'),
xpd15=('xpd15', 'mean'),
cspm=('cspm', 'mean'),
solo_kills=('solo_kills', 'sum'),
solo_deaths=('solo_deaths', 'sum'),
gpm=('gpm', 'mean'),
dpm=('dpm', 'mean')
).reset_index()
6. 团战期
def teamfight_phase(self):
return self.ps.groupby('player', 'champion').agg(
cc_hit_rate=('cc_hit_rate', 'mean'),
skillshot_hit_rate=('skillshot_hit_rate', 'mean'),
survive_rate=('survive_rate', 'mean'),
damage_to_champs=('damage_to_champs', 'mean'),
damage_to_towers=('damage_to_towers', 'mean'),
damage_to_epics=('damage_to_epics', 'mean')
).reset_index()
7. 眼位深度分析
def ward_analysis(self):
w = self.wards.copy()
w'is_trinket' = w'ward_type' == 'trinket'
w'is_control' = w'ward_type' == 'control'
ward_stats = w.groupby('player').agg(
wards_placed=('ward_id', 'count'),
avg_duration=('duration', 'mean'),
killed_rate=('killed', 'mean'),
trinket_ratio=('is_trinket', 'mean'),
control_ratio=('is_control', 'mean')
).reset_index()
first_trinket = ww\['is_trinket'].sort_values('timestamp').groupby('match_id', 'player').first().reset_index()
first_pos = first_trinket.groupby('player').agg(
avg_x=('x', 'mean'), avg_y=('y', 'mean'), sample=('x', 'count')
).reset_index()
return ward_stats, first_pos
8. 视野控制效率
def vision_control(self):
return self.ps.groupby('player', 'champion').agg(
vision_score=('vision_score', 'mean'),
wards_killed=('wards_killed', 'mean'),
control_wards=('control_wards', 'mean')
).reset_index()
9. 核心对位指标
def core_matchup(self):
return self.ps.groupby('player', 'champion').agg(
gd15=('gd15', 'mean'),
xpd15=('xpd15', 'mean'),
cspm=('cspm', 'mean'),
gpm=('gpm', 'mean'),
dpm=('dpm', 'mean'),
kp=('kp', 'mean')
).reset_index()
10. 进阶效率与影响力
def advanced_efficiency(self):
return self.ps.groupby('player', 'champion').agg(
damage_per_gold=('damage_per_gold', 'mean'),
VC=('VC', 'mean'),
ineffective_death_rate=('ineffective_death_rate', 'mean'),
free_kill_rate=('free_kill_rate', 'mean')
).reset_index()
---------------- 模拟数据生成 ----------------
def make_demo_data(n_matches=60, seed=42):
rng = np.random.default_rng(seed)
teams = f'Team{i}' for i in range(1, 9)
players = f'P{i}' for i in range(1, 41)
champions = ['Ezreal', 'KaiSa', 'Jinx', 'Aphelios', 'Leona', 'Nautilus',
'LeeSin', 'Viego', 'Ahri', 'Sylas', 'Ornn', 'Gnar']
roles = 'TOP', 'JUG', 'MID', 'ADC', 'SUP'
matches, player_stats, ward_events, bp_events = \[\], \[\], \[\], \[\]
for m in range(n_matches):
match_id = f'M{m:04d}'
t1, t2 = rng.choice(teams, 2, replace=False)
duration = int(rng.normal(1800, 300))
winner = rng.choice(t1, t2)
first_blood_team = rng.choice(t1, t2)
for team, opp in (t1, t2), (t2, t1):
matches.append(dict(
match_id=match_id, team=team, opponent_team=opp,
result='win' if winner == team else 'loss',
first_blood_team=first_blood_team, duration=duration,
dragons=int(rng.integers(0, 5)), heralds=int(rng.integers(0, 3))
))
for role in roles:
player = rng.choice(players)
champ = rng.choice(champions)
if role == 'ADC' and rng.random() < 0.4:
champ = 'Ezreal'
kills = int(rng.poisson(4))
deaths = int(rng.poisson(4))
assists = int(rng.poisson(7))
gold = float(rng.normal(12000, 3000))
cs = float(rng.normal(220, 50))
damage = float(rng.normal(20000, 8000))
gd15 = float(rng.normal(0, 500))
xpd15 = float(rng.normal(0, 500))
player_stats.append(dict(
match_id=match_id, team=team, opponent_team=opp,
player=player, champion=champ, role=role,
kills=kills, deaths=deaths, assists=assists,
gold=gold, cs=cs, damage_to_champs=damage,
damage_to_towers=float(rng.normal(3000, 1000)),
damage_to_epics=float(rng.normal(1000, 500)),
vision_score=float(rng.normal(30, 10)),
wards_killed=int(rng.poisson(5)),
control_wards=int(rng.poisson(3)),
gd15=gd15, xpd15=xpd15,
cspm=cs / (duration / 60),
gpm=gold / (duration / 60),
dpm=damage / (duration / 60),
kp=float(rng.uniform(0.4, 0.9)),
solo_kills=int(rng.poisson(0.5)),
solo_deaths=int(rng.poisson(0.5)),
cc_hits=int(rng.poisson(8)),
cc_attempts=int(rng.poisson(12)),
skillshots_hits=int(rng.poisson(20)),
skillshots_attempts=int(rng.poisson(30)),
teamfight_survived=int(rng.poisson(3)),
teamfights=int(rng.poisson(5)),
ineffective_deaths=int(rng.poisson(1)),
free_kills=int(rng.poisson(1))
))
眼位事件
for i in range(10):
ward_events.append(dict(
ward_id=f'W{m}_{i}', match_id=match_id,
player=rng.choice(players), team=rng.choice(t1, t2),
timestamp=int(rng.integers(60, duration)),
x=float(rng.normal(7500, 2000)),
y=float(rng.normal(7500, 2000)),
ward_type=rng.choice('trinket', 'control', 'yellow'),
duration=float(rng.normal(90, 30)),
killed=int(rng.random() < 0.4),
before_objective=int(rng.random() < 0.3)
))
BP 事件
for i in range(10):
bp_events.append(dict(
match_id=match_id, team=rng.choice(t1, t2),
champion=rng.choice(champions),
action=rng.choice('ban', 'pick'),
order=int(rng.integers(1, 11)),
role=rng.choice(roles)
))
return LoLData(
pd.DataFrame(matches),
pd.DataFrame(player_stats),
pd.DataFrame(ward_events),
pd.DataFrame(bp_events)
)
if name == 'main':
data = make_demo_data()
analyzer = EzrealOpponentAnalyzer(data)
print("=== 对手风格画像 ===")
print(analyzer.opponent_style('Ezreal').head())
print("\n=== 团队整体指标 ===")
print(analyzer.team_metrics().head())
print("\n=== 英雄池与胜率 ===")
print(analyzer.champion_pool().head())
print("\n=== BP 趋势 ===")
champ, mid_jug, bot_sup = analyzer.bp_trends()
print(champ.head())
print(mid_jug.head())
print(bot_sup.head())
print("\n=== 对线期 ===")
print(analyzer.laning_phase().head())
print("\n=== 团战期 ===")
print(analyzer.teamfight_phase().head())
print("\n=== 眼位分析 ===")
ward_stats, first_pos = analyzer.ward_analysis()
print(ward_stats.head())
print(first_pos.head())
print("\n=== 视野控制效率 ===")
print(analyzer.vision_control().head())
print("\n=== 核心对位指标 ===")
print(analyzer.core_matchup().head())
print("\n=== 进阶效率与影响力 ===")
print(analyzer.advanced_efficiency().head())
```
这段代码会生成模拟比赛数据,并输出各项分析结果。理论上实际使用时,只需把 make_demo_data() 替换为从 CSV 读取真实数据,并保证字段与代码中的列名一致即可。
禁止一切转载或商用。