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