In [1]:
from collections import namedtuple
import numpy.matlib as mat
import numpy as np
from numpy.linalg import inv

Match = namedtuple("Match", ["teams", "score"])

def CalcOpr(matches):
"""
Given a list of matches, to least squares OPR calculation
"""
num_matches = len(matches) # rows
teams = list(set().union(*[match.teams for match in matches]))
num_teams = len(teams)

alliances = mat.zeros((num_matches, num_teams))
scores = mat.zeros((num_matches, 1))

for idx, match in enumerate(matches):
scores[idx, 0] = match.score
for team in match.teams:
alliances[idx, teams.index(team)] = 1

least_squares_approx = np.dot(
inv(np.dot(np.transpose(alliances), alliances)),
np.dot(np.transpose(alliances), scores))

oprs = {}
for idx, team in enumerate(teams):
oprs[team] = least_squares_approx[idx, 0]
return oprs

print(CalcOpr([
Match(teams=['A', 'B'], score=10),
Match(teams=['A', 'C'], score=13),
Match(teams=['B', 'C'], score=7),
Match(teams=['A', 'D'], score=15),
Match(teams=['B', 'D'], score=10),
]))

{'C': 5.0, 'A': 7.75, 'D': 7.5, 'B': 2.25}

In [2]:
import tbapy
from pprint import pprint

api_key = "6qOZ9uAEsb4CDrOBNG6ZnIdi9cWBaZ6DHnCSato97Qfo7bBeUwT9NfFt4Gi5sHFN"

tba = tbapy.TBA(api_key)
pprint(tba.status())

{'android': {'latest_app_version': 4020399, 'min_app_version': 4000299},
'contbuild_enabled': True,
'current_season': 2018,
'down_events': [],
'ios': {'latest_app_version': -1, 'min_app_version': -1},
'is_datafeed_down': False,
'json': {'android': {'latest_app_version': 4020399,
'min_app_version': 4000299},
'contbuild_enabled': True,
'current_season': 2018,
'down_events': [],
'ios': {'latest_app_version': -1, 'min_app_version': -1},
'is_datafeed_down': False,
'max_season': 2018,
'web': {'commit_time': '2018-02-08 16:24:47 -0500',
'current_commit': '0818a656fa52a99bd0af8943e91bf6107a80314d',
'deploy_time': 'Thu Feb  8 21:39:42 UTC 2018',
'travis_job': '339186456'}},
'max_season': 2018,
'web': {'commit_time': '2018-02-08 16:24:47 -0500',
'current_commit': '0818a656fa52a99bd0af8943e91bf6107a80314d',
'deploy_time': 'Thu Feb  8 21:39:42 UTC 2018',
'travis_job': '339186456'}}

In [3]:
matches = tba.event_matches('2017roe')

In [4]:
match_outcomes = []
for match in matches:
match_outcomes.append(
Match(teams=match['alliances']['blue']['team_keys'],
score=match['alliances']['blue']['score'])
)
match_outcomes.append(
Match(teams=match['alliances']['red']['team_keys'],
score=match['alliances']['red']['score'])
)
score_oprs = sorted(CalcOpr(match_outcomes).items(), key=lambda x: -x[1])
pprint(score_oprs)

[('frc973', 177.56919288327308),
('frc115', 157.0259913331104),
('frc1011', 149.76450747382796),
('frc4265', 146.76438484413518),
('frc365', 143.49460917929289),
('frc2928', 135.80511381914454),
('frc624', 134.76677882359436),
('frc2403', 134.32245109158637),
('frc1574', 129.37613398098358),
('frc1414', 127.96800663206291),
('frc2642', 127.18581346587769),
('frc3824', 126.96309325527552),
('frc1339', 125.96798422065352),
('frc4590', 125.26308475200871),
('frc1002', 124.21225689931802),
('frc3316', 122.8078910824091),
('frc5970', 122.50535563795168),
('frc6325', 119.78641397302121),
('frc418', 116.62106655170464),
('frc1477', 114.77491503934719),
('frc8', 114.29890025268725),
('frc5803', 113.73483804756773),
('frc6705', 111.58307204661132),
('frc4561', 111.00355528639119),
('frc2468', 110.41012216879824),
('frc5026', 110.04546378593928),
('frc5614', 107.61788458386509),
('frc3402', 107.4626066948726),
('frc2655', 104.24531198243945),
('frc3158', 101.14330694999202),
('frc488', 98.055462452244143),
('frc4592', 98.052651237205325),
('frc435', 96.464573727156278),
('frc3834', 91.570774782459154),
('frc441', 91.149404570612035),
('frc3140', 90.638557116564982),
('frc5816', 89.708856584075434),
('frc4219', 89.586753586033979),
('frc2485', 88.505690883901565),
('frc585', 88.25561566820447),
('frc3229', 86.158182066996943),
('frc6304', 85.649736210525333),
('frc3653', 85.284652499378396),
('frc175', 85.116732278878715),
('frc6508', 84.873574464768041),
('frc4276', 81.574812779764528),
('frc5499', 81.076671646279891),
('frc2478', 80.843731553943798),
('frc5515', 80.505727211308368),
('frc3826', 78.089578436795648),
('frc2881', 76.769491169265663),
('frc955', 75.507539471727597),
('frc2183', 70.635782080362318),
('frc6361', 70.17568719278286),
('frc1482', 69.055515009333917),
('frc6560', 68.823962846736208),
('frc3991', 67.737826004420583),
('frc4371', 67.365329372744242),
('frc6388', 66.306467100842639),
('frc4060', 65.914273669761229),
('frc4191', 62.865270011745224),
('frc4723', 56.810485185257662),
('frc5472', 54.510742035159979),
('frc6144', 36.696388177764277),
('frc2905', 26.417361845177616),
('frc6409', 8.2241314774465888)]

In [5]:
match_outcomes = []
for match in matches:
match_outcomes.append(
Match(teams=match['alliances']['blue']['team_keys'],
score=match['score_breakdown']['blue']['kPaRankingPointAchieved'])
)
match_outcomes.append(
Match(teams=match['alliances']['red']['team_keys'],
score=match['score_breakdown']['red']['kPaRankingPointAchieved'])
)
kpa_oprs = CalcOpr(match_outcomes)
pprint(kpa_oprs)

{'frc1002': 0.011495807536502572,
'frc1011': -0.23843650566297964,
'frc115': -0.012713387884420797,
'frc1339': 0.049583694803860427,
'frc1414': -0.025609512663978932,
'frc1477': 0.022485935872360904,
'frc1482': 0.024757792159351494,
'frc1574': 0.66808268010260641,
'frc175': 0.014010517155780116,
'frc2183': 0.014016941366526755,
'frc2403': 0.080360541678350023,
'frc2468': 0.054313747859282599,
'frc2478': 0.056793675027969996,
'frc2485': -0.11004523242061667,
'frc2642': -0.0093209117666008812,
'frc2655': 0.026158138151728107,
'frc2881': 0.12976928277693456,
'frc2905': -0.0054328961126955868,
'frc2928': -0.14365141927979586,
'frc3140': 0.034966321717088245,
'frc3158': -0.0083270752513364132,
'frc3229': -0.052914169109274517,
'frc3316': 0.022681055565912493,
'frc3402': -0.036669808722433681,
'frc365': -0.033387969672823169,
'frc3653': 0.014729368563237756,
'frc3824': 0.080450382242200247,
'frc3826': 0.019110268657443839,
'frc3834': -0.038611090946230317,
'frc3991': 0.0069417322847042124,
'frc4060': 0.031568346005882965,
'frc418': -0.17836616410749317,
'frc4191': -0.027664312060668557,
'frc4219': 0.018668059497609552,
'frc4265': -0.039762814142503211,
'frc4276': -0.011733796335331451,
'frc435': 0.021490162884872023,
'frc4371': 0.0013029801530613384,
'frc441': -0.017291511574850489,
'frc4561': 0.039187096494449604,
'frc4590': -0.0047923055382814448,
'frc4592': -0.0099013917493386283,
'frc4723': 0.027083177527512065,
'frc488': 0.30838132037945404,
'frc5026': -0.020690991999509637,
'frc5472': 0.070349291821964782,
'frc5499': 0.019626942011950511,
'frc5515': 0.063851785439747955,
'frc5614': -0.024521676323755421,
'frc5803': -0.015295375370894511,
'frc5816': 0.016515716145632582,
'frc585': 0.0023789472066973009,
'frc5970': -0.0044706821988662231,
'frc6144': 0.050254228103232886,
'frc624': -0.081349484164234639,
'frc6304': 0.033648856674681996,
'frc6325': -0.034250313916356766,
'frc6361': 0.045042709293893271,
'frc6388': -0.021082445337662147,
'frc6409': 0.045497807930851217,
'frc6508': 0.074536576581233485,
'frc6560': -0.080212765492548535,
'frc6705': -0.056760688554744892,
'frc8': 0.013041665258332877,
'frc955': 0.05983277602696755,
'frc973': 0.69379487994753519}

In [6]:
true_positives = 0
true_negatives = 0
false_positives = 0
false_negatives = 0

for match in matches:
for alliance in ['red', 'blue']:
result = match['score_breakdown'][alliance]['kPaRankingPointAchieved']
prediction = sum(
kpa_oprs[team] for team in match['alliances'][alliance]['team_keys']
) > 0.5
if result and prediction:
true_positives += 1
elif result and not prediction:
false_negatives += 1
elif not result and prediction:
false_positives += 1
elif not result and not prediction:
true_negatives += 1

print("True positives", true_positives)
print("True negatives", true_negatives)
print("False positives", false_positives)
print("False negatives", false_negatives)
print("Correctness", (true_positives + true_negatives) / (true_positives + true_negatives + false_positives + false_negatives + 0.0))

True positives 17
True negatives 229
False positives 3
False negatives 3
Correctness 0.9761904761904762

In [7]:
match_outcomes = []
for match in matches:
match_outcomes.append(
Match(teams=match['alliances']['blue']['team_keys'],
score=match['score_breakdown']['blue']['rotorRankingPointAchieved'])
)
match_outcomes.append(
Match(teams=match['alliances']['red']['team_keys'],
score=match['score_breakdown']['red']['rotorRankingPointAchieved'])
)
rotor_oprs = CalcOpr(match_outcomes)
pprint(rotor_oprs)

{'frc1002': 0.17727192353651036,
'frc1011': 0.23646020615753144,
'frc115': 0.27027459301427076,
'frc1339': 0.27767467034227666,
'frc1414': 0.097528516725300579,
'frc1477': 0.24271506617743271,
'frc1482': -0.20183290190431527,
'frc1574': -0.062725432249641944,
'frc175': 0.066614006692210043,
'frc2183': 0.17779147326885175,
'frc2403': 0.26038237793502095,
'frc2468': 0.077335124862353138,
'frc2478': 0.11611763401010168,
'frc2485': 0.22766288292212927,
'frc2642': 0.44131736419305789,
'frc2655': 0.078615164315436414,
'frc2881': 0.026395389715158406,
'frc2905': -0.15742362551120212,
'frc2928': 0.19144017072648187,
'frc3140': 0.050819349427158898,
'frc3158': 0.099557651860255836,
'frc3229': 0.30137859678360285,
'frc3316': -0.017803621940292693,
'frc3402': 0.23407474552151586,
'frc365': 0.027014917937426858,
'frc3653': 0.055081036501289343,
'frc3824': 0.034654129291765062,
'frc3826': 0.1618711950878218,
'frc3834': -0.10927154609293535,
'frc3991': 0.20426591613959094,
'frc4060': -0.041813912600997927,
'frc418': -0.18067404916238411,
'frc4191': -0.16868864593985589,
'frc4219': 0.0035790511659747511,
'frc4265': 0.080896946538550563,
'frc4276': 0.13708185480133839,
'frc435': 0.16573089995659673,
'frc4371': 0.080681643521142932,
'frc441': -0.077337956468450111,
'frc4561': 0.06389536845233032,
'frc4590': -0.049808506391208721,
'frc4592': 0.10461776563177416,
'frc4723': 0.086487057324865432,
'frc488': 0.012815142631013174,
'frc5026': -0.039661993822559902,
'frc5472': -0.19424322205436845,
'frc5499': 0.19376531412413966,
'frc5515': 0.064064234500017039,
'frc5614': 0.08808190904164967,
'frc5803': 0.057311874395802438,
'frc5816': 0.069464650468857733,
'frc585': 0.035569288619228395,
'frc5970': 0.44357144135582388,
'frc6144': 0.0079100008274700304,
'frc624': 0.35576013463202721,
'frc6304': -0.10973657565707473,
'frc6325': 0.26476227726013973,
'frc6361': 0.13208922601294448,
'frc6388': -0.15295108609704131,
'frc6409': -0.10431659019076206,
'frc6508': 0.086131409830338679,
'frc6560': 0.074907461418003418,
'frc6705': 0.22003298058691301,
'frc8': 0.247847970810986,
'frc955': -0.010061945508252013,
'frc973': -0.076970861048886952}

In [8]:
true_positives = 0
true_negatives = 0
false_positives = 0
false_negatives = 0

for match in matches:
for alliance in ['red', 'blue']:
result = match['score_breakdown'][alliance]['rotorRankingPointAchieved']
prediction = sum(
rotor_oprs[team] for team in match['alliances'][alliance]['team_keys']
) > 0.5
if result and prediction:
true_positives += 1
elif result and not prediction:
false_negatives += 1
elif not result and prediction:
false_positives += 1
elif not result and not prediction:
true_negatives += 1

print("True positives", true_positives)
print("True negatives", true_negatives)
print("False positives", false_positives)
print("False negatives", false_negatives)
print("Correctness", (true_positives + true_negatives) / (true_positives + true_negatives + false_positives + false_negatives + 0.0))

True positives 27
True negatives 177
False positives 9
False negatives 39
Correctness 0.8095238095238095