From 91cbcae3f0a774ea319d90d24453d67488670c4c Mon Sep 17 00:00:00 2001 From: ltcptgeneral <35508619+ltcptgeneral@users.noreply.github.com> Date: Thu, 30 Apr 2020 16:03:37 -0500 Subject: [PATCH 1/5] analysis pkg v 1.0.0.12 analysis.py v 1.2.0.004 --- .../analysis-amd64/analysis.egg-info/PKG-INFO | 2 +- .../analysis.egg-info/SOURCES.txt | 8 +- .../analysis-amd64/analysis/analysis.py | 113 +-- .../build/lib/analysis/analysis.py | 263 ++++- .../build/lib/analysis/metrics/__init__.py | 0 .../build/lib/analysis/metrics/elo.py | 7 + .../build/lib/analysis/metrics/glicko2.py | 99 ++ .../build/lib/analysis/metrics/trueskill.py | 907 ++++++++++++++++++ .../dist/analysis-1.0.0.12-py3-none-any.whl | Bin 0 -> 32026 bytes .../dist/analysis-1.0.0.12.tar.gz | Bin 0 -> 21001 bytes analysis-master/analysis-amd64/setup.py | 2 +- 11 files changed, 1322 insertions(+), 79 deletions(-) create mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py create mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py create mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py create mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py create mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl create mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz diff --git a/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO b/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO index 410189e2..83058193 100644 --- a/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO +++ b/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: analysis -Version: 1.0.0.11 +Version: 1.0.0.12 Summary: analysis package developed by Titan Scouting for The Red Alliance Home-page: https://github.com/titanscout2022/tr2022-strategy Author: The Titan Scouting Team diff --git a/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt b/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt index 25a54640..2d8be231 100644 --- a/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt +++ b/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt @@ -1,13 +1,15 @@ setup.py analysis/__init__.py analysis/analysis.py -analysis/glicko2.py analysis/regression.py analysis/titanlearn.py -analysis/trueskill.py analysis/visualization.py analysis.egg-info/PKG-INFO analysis.egg-info/SOURCES.txt analysis.egg-info/dependency_links.txt analysis.egg-info/requires.txt -analysis.egg-info/top_level.txt \ No newline at end of file +analysis.egg-info/top_level.txt +analysis/metrics/__init__.py +analysis/metrics/elo.py +analysis/metrics/glicko2.py +analysis/metrics/trueskill.py \ No newline at end of file diff --git a/analysis-master/analysis-amd64/analysis/analysis.py b/analysis-master/analysis-amd64/analysis/analysis.py index eb898a1a..c13aef90 100644 --- a/analysis-master/analysis-amd64/analysis/analysis.py +++ b/analysis-master/analysis-amd64/analysis/analysis.py @@ -7,10 +7,17 @@ # current benchmark of optimization: 1.33 times faster # setup: -__version__ = "1.2.0.003" +__version__ = "1.2.0.004" # changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: + 1.2.0.004: + - fixed __all__ to reflected the correct functions and classes + - fixed CorrelationTests and StatisticalTests class functions to require self invocation + - added missing math import + - fixed KNN class functions to require self invocation + - fixed Metrics class functions to require self invocation + - various spelling fixes in CorrelationTests and StatisticalTests 1.2.0.003: - bug fixes with CorrelationTests and StatisticalTests - moved glicko2 and trueskill to the metrics subpackage @@ -275,22 +282,19 @@ __all__ = [ 'z_normalize', 'histo_analysis', 'regression', - 'elo', - 'glicko2', - 'trueskill', + 'Metrics', 'RegressionMetrics', 'ClassificationMetrics', 'kmeans', 'pca', 'decisiontree', - 'knn_classifier', - 'knn_regressor', + 'KNN', 'NaiveBayes', 'SVM', 'random_forest_classifier', 'random_forest_regressor', 'CorrelationTests', - 'RegressionTests', + 'StatisticalTests', # all statistics functions left out due to integration in other functions ] @@ -301,6 +305,7 @@ __all__ = [ import csv from analysis.metrics import elo as Elo from analysis.metrics import glicko2 as Glicko2 +import math import numba from numba import jit import numpy as np @@ -467,11 +472,11 @@ def regression(inputs, outputs, args): # inputs, outputs expects N-D array class Metrics: - def elo(starting_score, opposing_score, observed, N, K): + def elo(self, starting_score, opposing_score, observed, N, K): return Elo.calculate(starting_score, opposing_score, observed, N, K) - def glicko2(starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) @@ -479,7 +484,7 @@ class Metrics: return (player.rating, player.rd, player.vol) - def trueskill(teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] team_ratings = [] @@ -584,7 +589,7 @@ def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = " class KNN: - def knn_classifier(data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) model = sklearn.neighbors.KNeighborsClassifier() @@ -593,7 +598,7 @@ class KNN: return model, ClassificationMetrics(predictions, labels_test) - def knn_regressor(data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) @@ -716,203 +721,203 @@ def random_forest_regressor(data, outputs, test_size, n_estimators="warn", crite class CorrelationTests: - def anova_oneway(*args): #expects arrays of samples + def anova_oneway(self, *args): #expects arrays of samples results = scipy.stats.f_oneway(*args) return {"F-value": results[0], "p-value": results[1]} - def pearson(x, y): + def pearson(self, x, y): results = scipy.stats.pearsonr(x, y) return {"r-value": results[0], "p-value": results[1]} - def spearman(a, b = None, axis = 0, nan_policy = 'propagate'): + def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) return {"r-value": results[0], "p-value": results[1]} - def point_biserial(x,y): + def point_biserial(self, x,y): results = scipy.stats.pointbiserialr(x, y) return {"r-value": results[0], "p-value": results[1]} - def kendall(x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): + def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) return {"tau": results[0], "p-value": results[1]} - def kendall_weighted(x, y, rank = True, weigher = None, additive = True): + def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) return {"tau": results[0], "p-value": results[1]} - def mgc(x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): + def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value class StatisticalTests: - def ttest_onesample(a, popmean, axis = 0, nan_policy = 'propagate'): + def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) return {"t-value": results[0], "p-value": results[1]} - def ttest_independent(a, b, equal = True, nan_policy = 'propagate'): + def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): - results = scipt.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) + results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) return {"t-value": results[0], "p-value": results[1]} - def ttest_statistic(o1, o2, equal = True): + def ttest_statistic(self, o1, o2, equal = True): results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) return {"t-value": results[0], "p-value": results[1]} - def ttest_related(a, b, axis = 0, nan_policy='propagate'): + def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) return {"t-value": results[0], "p-value": results[1]} - def ks_fitness(rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): + def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) return {"ks-value": results[0], "p-value": results[1]} - def chisquare(f_obs, f_exp = None, ddof = None, axis = 0): + def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) return {"chisquared-value": results[0], "p-value": results[1]} - def powerdivergence(f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): + def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) return {"powerdivergence-value": results[0], "p-value": results[1]} - def ks_twosample(x, y, alternative = 'two_sided', mode = 'auto'): + def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) return {"ks-value": results[0], "p-value": results[1]} - def es_twosample(x, y, t = (0.4, 0.8)): + def es_twosample(self, x, y, t = (0.4, 0.8)): results = scipy.stats.epps_singleton_2samp(x, y, t = t) return {"es-value": results[0], "p-value": results[1]} - def mw_rank(x, y, use_continuity = True, alternative = None): + def mw_rank(self, x, y, use_continuity = True, alternative = None): results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) return {"u-value": results[0], "p-value": results[1]} - def mw_tiecorrection(rank_values): + def mw_tiecorrection(self, rank_values): results = scipy.stats.tiecorrect(rank_values) return {"correction-factor": results} - def rankdata(a, method = 'average'): + def rankdata(self, a, method = 'average'): results = scipy.stats.rankdata(a, method = method) return results - def wilcoxon_ranksum(a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test + def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test results = scipy.stats.ranksums(a, b) return {"u-value": results[0], "p-value": results[1]} - def wilcoxon_signedrank(x, y = None, method = 'wilcox', correction = False, alternative = 'two-sided'): + def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): - results = scipy.stats.wilcoxon(x, y = y, method = method, correction = correction, alternative = alternative) + results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) return {"t-value": results[0], "p-value": results[1]} - def kw_htest(*args, nan_policy = 'propagate'): + def kw_htest(self, *args, nan_policy = 'propagate'): results = scipy.stats.kruskal(*args, nan_policy = nan_policy) return {"h-value": results[0], "p-value": results[1]} - def friedman_chisquare(*args): + def friedman_chisquare(self, *args): results = scipy.stats.friedmanchisquare(*args) return {"chisquared-value": results[0], "p-value": results[1]} - def bm_wtest(x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): + def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) return {"w-value": results[0], "p-value": results[1]} - def combine_pvalues(pvalues, method = 'fisher', weights = None): + def combine_pvalues(self, pvalues, method = 'fisher', weights = None): results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) return {"combined-statistic": results[0], "p-value": results[1]} - def jb_fitness(x): + def jb_fitness(self, x): results = scipy.stats.jarque_bera(x) return {"jb-value": results[0], "p-value": results[1]} - def ab_equality(x, y): + def ab_equality(self, x, y): results = scipy.stats.ansari(x, y) return {"ab-value": results[0], "p-value": results[1]} - def bartlett_variance(*args): + def bartlett_variance(self, *args): results = scipy.stats.bartlett(*args) return {"t-value": results[0], "p-value": results[1]} - def levene_variance(*args, center = 'median', proportiontocut = 0.05): + def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) return {"w-value": results[0], "p-value": results[1]} - def sw_normality(x): + def sw_normality(self, x): results = scipy.stats.shapiro(x) return {"w-value": results[0], "p-value": results[1]} - def shapiro(x): + def shapiro(self, x): return "destroyed by facts and logic" - def ad_onesample(x, dist = 'norm'): + def ad_onesample(self, x, dist = 'norm'): results = scipy.stats.anderson(x, dist = dist) return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - def ad_ksample(samples, midrank = True): + def ad_ksample(self, samples, midrank = True): results = scipy.stats.anderson_ksamp(samples, midrank = midrank) return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - def binomial(x, n = None, p = 0.5, alternative = 'two-sided'): + def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) return {"p-value": results} - def fk_variance(*args, center = 'median', proportiontocut = 0.05): + def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value - def mood_mediantest(*args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): + def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} - def mood_equalscale(x, y, axis = 0): + def mood_equalscale(self, x, y, axis = 0): results = scipy.stats.mood(x, y, axis = axis) return {"z-score": results[0], "p-value": results[1]} - def skewtest(a, axis = 0, nan_policy = 'propogate'): + def skewtest(self, a, axis = 0, nan_policy = 'propogate'): results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) return {"z-score": results[0], "p-value": results[1]} - def kurtosistest(a, axis = 0, nan_policy = 'propogate'): + def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) return {"z-score": results[0], "p-value": results[1]} - def normaltest(a, axis = 0, nan_policy = 'propogate'): + def normaltest(self, a, axis = 0, nan_policy = 'propogate'): results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/analysis.py b/analysis-master/analysis-amd64/build/lib/analysis/analysis.py index 944dd0c7..c13aef90 100644 --- a/analysis-master/analysis-amd64/build/lib/analysis/analysis.py +++ b/analysis-master/analysis-amd64/build/lib/analysis/analysis.py @@ -1,16 +1,37 @@ # Titan Robotics Team 2022: Data Analysis Module # Written by Arthur Lu & Jacob Levine # Notes: -# this should be imported as a python module using 'import analysis' +# this should be imported as a python module using 'from analysis import analysis' # this should be included in the local directory or environment variable # this module has been optimized for multhreaded computing # current benchmark of optimization: 1.33 times faster # setup: -__version__ = "1.1.13.009" +__version__ = "1.2.0.004" # changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: + 1.2.0.004: + - fixed __all__ to reflected the correct functions and classes + - fixed CorrelationTests and StatisticalTests class functions to require self invocation + - added missing math import + - fixed KNN class functions to require self invocation + - fixed Metrics class functions to require self invocation + - various spelling fixes in CorrelationTests and StatisticalTests + 1.2.0.003: + - bug fixes with CorrelationTests and StatisticalTests + - moved glicko2 and trueskill to the metrics subpackage + - moved elo to a new metrics subpackage + 1.2.0.002: + - fixed docs + 1.2.0.001: + - fixed docs + 1.2.0.000: + - cleaned up wild card imports with scipy and sklearn + - added CorrelationTests class + - added StatisticalTests class + - added several correlation tests to CorrelationTests + - added several statistical tests to StatisticalTests 1.1.13.009: - moved elo, glicko2, trueskill functions under class Metrics 1.1.13.008: @@ -261,20 +282,19 @@ __all__ = [ 'z_normalize', 'histo_analysis', 'regression', - 'elo', - 'glicko2', - 'trueskill', + 'Metrics', 'RegressionMetrics', 'ClassificationMetrics', 'kmeans', 'pca', 'decisiontree', - 'knn_classifier', - 'knn_regressor', + 'KNN', 'NaiveBayes', 'SVM', 'random_forest_classifier', 'random_forest_regressor', + 'CorrelationTests', + 'StatisticalTests', # all statistics functions left out due to integration in other functions ] @@ -283,15 +303,17 @@ __all__ = [ # imports (now in alphabetical order! v 1.0.3.006): import csv -from analysis import glicko2 as Glicko2 +from analysis.metrics import elo as Elo +from analysis.metrics import glicko2 as Glicko2 +import math import numba from numba import jit import numpy as np import scipy -from scipy import * +from scipy import optimize, stats import sklearn -from sklearn import * -from analysis import trueskill as Trueskill +from sklearn import preprocessing, pipeline, linear_model, metrics, cluster, decomposition, tree, neighbors, naive_bayes, svm, model_selection, ensemble +from analysis.metrics import trueskill as Trueskill class error(ValueError): pass @@ -450,13 +472,11 @@ def regression(inputs, outputs, args): # inputs, outputs expects N-D array class Metrics: - def elo(starting_score, opposing_score, observed, N, K): + def elo(self, starting_score, opposing_score, observed, N, K): - expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + return Elo.calculate(starting_score, opposing_score, observed, N, K) - return starting_score + K*(np.sum(observed) - np.sum(expected)) - - def glicko2(starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) @@ -464,7 +484,7 @@ class Metrics: return (player.rating, player.rd, player.vol) - def trueskill(teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] team_ratings = [] @@ -569,7 +589,7 @@ def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = " class KNN: - def knn_classifier(data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) model = sklearn.neighbors.KNeighborsClassifier() @@ -578,7 +598,7 @@ class KNN: return model, ClassificationMetrics(predictions, labels_test) - def knn_regressor(data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) @@ -697,4 +717,207 @@ def random_forest_regressor(data, outputs, test_size, n_estimators="warn", crite kernel.fit(data_train, outputs_train) predictions = kernel.predict(data_test) - return kernel, RegressionMetrics(predictions, outputs_test) \ No newline at end of file + return kernel, RegressionMetrics(predictions, outputs_test) + +class CorrelationTests: + + def anova_oneway(self, *args): #expects arrays of samples + + results = scipy.stats.f_oneway(*args) + return {"F-value": results[0], "p-value": results[1]} + + def pearson(self, x, y): + + results = scipy.stats.pearsonr(x, y) + return {"r-value": results[0], "p-value": results[1]} + + def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): + + results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) + return {"r-value": results[0], "p-value": results[1]} + + def point_biserial(self, x,y): + + results = scipy.stats.pointbiserialr(x, y) + return {"r-value": results[0], "p-value": results[1]} + + def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): + + results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) + return {"tau": results[0], "p-value": results[1]} + + def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): + + results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) + return {"tau": results[0], "p-value": results[1]} + + def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): + + results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) + return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value + +class StatisticalTests: + + def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): + + results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): + + results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_statistic(self, o1, o2, equal = True): + + results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): + + results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): + + results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} + + def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): + + results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) + return {"chisquared-value": results[0], "p-value": results[1]} + + def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): + + results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) + return {"powerdivergence-value": results[0], "p-value": results[1]} + + def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): + + results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} + + def es_twosample(self, x, y, t = (0.4, 0.8)): + + results = scipy.stats.epps_singleton_2samp(x, y, t = t) + return {"es-value": results[0], "p-value": results[1]} + + def mw_rank(self, x, y, use_continuity = True, alternative = None): + + results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) + return {"u-value": results[0], "p-value": results[1]} + + def mw_tiecorrection(self, rank_values): + + results = scipy.stats.tiecorrect(rank_values) + return {"correction-factor": results} + + def rankdata(self, a, method = 'average'): + + results = scipy.stats.rankdata(a, method = method) + return results + + def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test + + results = scipy.stats.ranksums(a, b) + return {"u-value": results[0], "p-value": results[1]} + + def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): + + results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) + return {"t-value": results[0], "p-value": results[1]} + + def kw_htest(self, *args, nan_policy = 'propagate'): + + results = scipy.stats.kruskal(*args, nan_policy = nan_policy) + return {"h-value": results[0], "p-value": results[1]} + + def friedman_chisquare(self, *args): + + results = scipy.stats.friedmanchisquare(*args) + return {"chisquared-value": results[0], "p-value": results[1]} + + def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): + + results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) + return {"w-value": results[0], "p-value": results[1]} + + def combine_pvalues(self, pvalues, method = 'fisher', weights = None): + + results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) + return {"combined-statistic": results[0], "p-value": results[1]} + + def jb_fitness(self, x): + + results = scipy.stats.jarque_bera(x) + return {"jb-value": results[0], "p-value": results[1]} + + def ab_equality(self, x, y): + + results = scipy.stats.ansari(x, y) + return {"ab-value": results[0], "p-value": results[1]} + + def bartlett_variance(self, *args): + + results = scipy.stats.bartlett(*args) + return {"t-value": results[0], "p-value": results[1]} + + def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): + + results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) + return {"w-value": results[0], "p-value": results[1]} + + def sw_normality(self, x): + + results = scipy.stats.shapiro(x) + return {"w-value": results[0], "p-value": results[1]} + + def shapiro(self, x): + + return "destroyed by facts and logic" + + def ad_onesample(self, x, dist = 'norm'): + + results = scipy.stats.anderson(x, dist = dist) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + + def ad_ksample(self, samples, midrank = True): + + results = scipy.stats.anderson_ksamp(samples, midrank = midrank) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + + def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): + + results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) + return {"p-value": results} + + def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): + + results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) + return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value + + def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): + + results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) + return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} + + def mood_equalscale(self, x, y, axis = 0): + + results = scipy.stats.mood(x, y, axis = axis) + return {"z-score": results[0], "p-value": results[1]} + + def skewtest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} + + def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} + + def normaltest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py new file mode 100644 index 00000000..3c8ef2e0 --- /dev/null +++ b/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py @@ -0,0 +1,7 @@ +import numpy as np + +def calculate(starting_score, opposing_score, observed, N, K): + + expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + + return starting_score + K*(np.sum(observed) - np.sum(expected)) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py new file mode 100644 index 00000000..66c0df94 --- /dev/null +++ b/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py @@ -0,0 +1,99 @@ +import math + +class Glicko2: + _tau = 0.5 + + def getRating(self): + return (self.__rating * 173.7178) + 1500 + + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 + + rating = property(getRating, setRating) + + def getRd(self): + return self.__rd * 173.7178 + + def setRd(self, rd): + self.__rd = rd / 173.7178 + + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): + + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): + + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): + + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] + + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): + + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) + + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): + + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): + + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): + + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): + + self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py new file mode 100644 index 00000000..116357df --- /dev/null +++ b/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py @@ -0,0 +1,907 @@ +from __future__ import absolute_import + +from itertools import chain +import math + +from six import iteritems +from six.moves import map, range, zip +from six import iterkeys + +import copy +try: + from numbers import Number +except ImportError: + Number = (int, long, float, complex) + +inf = float('inf') + +class Gaussian(object): + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 + + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau + + @property + def mu(self): + return self.pi and self.tau / self.pi + + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf + + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) + + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) + + __div__ = __truediv__ # for Python 2 + + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau + + def __lt__(self, other): + return self.mu < other.mu + + def __le__(self, other): + return self.mu <= other.mu + + def __gt__(self, other): + return self.mu > other.mu + + def __ge__(self, other): + return self.mu >= other.mu + + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex + +class Matrix(list): + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) + + @property + def height(self): + return len(self) + + @property + def width(self): + return len(self[0]) + + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) + + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) + + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] + + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) + + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() + + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) + + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) + + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex + +def _gen_erfcinv(erfc, math=math): + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv + +def _gen_ppf(erfc, math=math): + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf + +def erfc(x): + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r + +def cdf(x, mu=0, sigma=1): + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + + +def pdf(x, mu=0, sigma=1): + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + +ppf = _gen_ppf(erfc) + +def choose_backend(backend): + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) + +def available_backends(): + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends + +class Node(object): + + pass + +class Variable(Node, Gaussian): + + def __init__(self): + self.messages = {} + super(Variable, self).__init__() + + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta + + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) + + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) + + def __getitem__(self, factor): + return self.messages[factor] + + def __setitem__(self, factor, message): + self.messages[factor] = message + + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args + + +class Factor(Node): + + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() + + def down(self): + return 0 + + def up(self): + return 0 + + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] + + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args + + +class PriorFactor(Factor): + + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic + + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) + + +class LikelihoodFactor(Factor): + + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance + + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) + + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) + + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) + + +class SumFactor(Factor): + + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs + + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) + + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) + + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) + + +class TruncateFactor(Factor): + + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin + + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) + +#: Default initial mean of ratings. +MU = 25. +#: Default initial standard deviation of ratings. +SIGMA = MU / 3 +#: Default distance that guarantees about 76% chance of winning. +BETA = SIGMA / 2 +#: Default dynamic factor. +TAU = SIGMA / 100 +#: Default draw probability of the game. +DRAW_PROBABILITY = .10 +#: A basis to check reliability of the result. +DELTA = 0.0001 + + +def calc_draw_probability(draw_margin, size, env=None): + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + + +def calc_draw_margin(draw_probability, size, env=None): + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + + +def _team_sizes(rating_groups): + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes + + +def _floating_point_error(env): + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) + + +class Rating(Gaussian): + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) + + def __int__(self): + return int(self.mu) + + def __long__(self): + return long(self.mu) + + def __float__(self): + return float(self.mu) + + def __iter__(self): + return iter((self.mu, self.sigma)) + + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args + + +class TrueSkill(object): + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) + + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) + + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x + + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) + + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys + + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights + + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) + + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers + + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] + + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) + + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma + + def make_as_global(self): + return setup(env=self) + + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) + + +def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] + + +def quality_1vs1(rating1, rating2, env=None): + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) + + +def global_env(): + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ + + +def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env + + +def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): + return global_env().rate(rating_groups, ranks, weights, min_delta) + + +def quality(rating_groups, weights=None): + return global_env().quality(rating_groups, weights) + + +def expose(rating): + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl b/analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..60e0b51c7e0824323d9fea32c844cadcaeb7528a GIT binary patch literal 32026 zcmZ6xQ;;S~w5?mV`Il{W*>-i=wr$(CZQHhO+cvv!)_%D6>^SowBO@c`Tjm@iV|?<` zpkQb~KtNDHnav^c|80Ok{%85G{^!)d*1+1s$-;?2PtU^E!dXv`-rfTgNdEutkt)xX z*UAin3nc&o+Kc&rdj7xW|9WnCt>TVG6ZYWW`R_;j;Z3a2C9htL=(&b<`Td<-jn?@n z`VD28$5V&#B8xYUr_B*R?$lJi$+HqoU7H#btU;ozUDec7)Yh6NJ_tXl>@={xe1H~)~QbwGDxZ7iqIFDsjQ-ZeZTRA|7;%HOih{lg!h_n>a2$KF&75ythTZwp6OI^e}gXBWY z(6(?=TJeg!)HDTr`r`;@oq?ePq5p(}|4?48s4Akdd*57>DXh$j%wYFeO8wStU4*lG zY)u!j*pG(7EnqzpFkWf<#^@OS7pngf*L>`mfUpSdEqECKIBd)+6KgwYnp= zG6sKkvzxlEaOC>o2nxGBob7LO_dr2ln-09U=Wh>b$3cvLt|UrLVbq85Qyjzn z0i;5w(Mc-i%4)$o`B|HP)Un3Su?db9{!HR~r67cOR_)&eS@lJfbtOikcEF%VbMy8t$(qG-;F+1fWZia>N^AmW?lz#J)D|p9_-;*ZVNXVr!Y&e*Uwi#()h!i z`__J_2LKMOrxJ9d#zTRy7D_pPJ9h-%uLa+$s^ylYc?u8Qo% zHY+yjs+%&NL;InpP{;Wyvt-SBDhx=VvYtCi%9oo=O&BYEJfPEj2;j_*_J2K=OI3xx+ldRdUAPwqo^gS^e z2Fsm;$45?m-#6BuGglWV&AI~1=c2j=6q&VRFIHw=3>r$ENpyeWl&8kO^9~mLjGl_|Z?$0=3}lZTg(g zGC*D=NPNoA(wA;uPE~Z{zSdqFDXekh6mPQC*BAKY zpS<75K@z{-*s0=<9(LYpS&PRw6|hYyf`$+G6_Ta>c#G6?DiflKr5)5N)ogJS;DrLD z=K7B{svjxfn3zzL{W#s5aBX|Xy|?sc=H5XLLaH^5>oYUC8f*dQiGz|It8PI3teOmw z{HRBeWH40rfZR?*dN-*$CXcs8eh

x#v7H(S=Q=4L*Ouq%iyj9OET3NC4 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 literal 0 HcmV?d00001 diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz b/analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1ba274315415d3096304b963298c369f7d3b44c2 GIT binary patch literal 21001 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 literal 0 HcmV?d00001 diff --git a/analysis-master/analysis-amd64/setup.py b/analysis-master/analysis-amd64/setup.py index 89a9bdf6..f290c88d 100644 --- a/analysis-master/analysis-amd64/setup.py +++ b/analysis-master/analysis-amd64/setup.py @@ -8,7 +8,7 @@ with open("requirements.txt", 'r') as file: setuptools.setup( name="analysis", - version="1.0.0.011", + version="1.0.0.012", author="The Titan Scouting Team", author_email="titanscout2022@gmail.com", description="analysis package developed by Titan Scouting for The Red Alliance", From 7a95550954be79e41e6c1043d8617c88833b20a1 Mon Sep 17 00:00:00 2001 From: ltcptgeneral <35508619+ltcptgeneral@users.noreply.github.com> Date: Fri, 1 May 2020 15:52:27 -0500 Subject: [PATCH 2/5] reconsolidated arm64 and amd64 versions --- .../analysis-amd64/analysis.egg-info/PKG-INFO | 14 - .../analysis.egg-info/SOURCES.txt | 15 - .../analysis.egg-info/dependency_links.txt | 1 - .../analysis.egg-info/requires.txt | 6 - .../analysis.egg-info/top_level.txt | 1 - .../__pycache__/__init__.cpython-37.pyc | Bin 166 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 170 -> 0 bytes .../__pycache__/analysis.cpython-37.pyc | Bin 23330 -> 0 bytes .../__pycache__/analysis.cpython-38.pyc | Bin 34599 -> 0 bytes .../__pycache__/glicko2.cpython-37.pyc | Bin 3700 -> 0 bytes .../__pycache__/glicko2.cpython-38.pyc | Bin 3712 -> 0 bytes .../__pycache__/trueskill.cpython-37.pyc | Bin 32184 -> 0 bytes .../__pycache__/trueskill.cpython-38.pyc | Bin 32190 -> 0 bytes analysis-master/analysis-amd64/build.sh | 1 - .../build/lib/analysis/__init__.py | 0 .../build/lib/analysis/analysis.py | 923 ------------------ .../build/lib/analysis/glicko2.py | 99 -- .../build/lib/analysis/metrics/__init__.py | 0 .../build/lib/analysis/metrics/elo.py | 7 - .../build/lib/analysis/metrics/glicko2.py | 99 -- .../build/lib/analysis/metrics/trueskill.py | 907 ----------------- .../build/lib/analysis/regression.py | 220 ----- .../build/lib/analysis/titanlearn.py | 122 --- .../build/lib/analysis/trueskill.py | 907 ----------------- .../build/lib/analysis/visualization.py | 34 - .../dist/analysis-1.0.0.10-py3-none-any.whl | Bin 20742 -> 0 bytes .../dist/analysis-1.0.0.10.tar.gz | Bin 18950 -> 0 bytes .../dist/analysis-1.0.0.11-py3-none-any.whl | Bin 20782 -> 0 bytes .../dist/analysis-1.0.0.11.tar.gz | Bin 19444 -> 0 bytes .../dist/analysis-1.0.0.12-py3-none-any.whl | Bin 32026 -> 0 bytes .../dist/analysis-1.0.0.12.tar.gz | Bin 21001 -> 0 bytes .../dist/analysis-1.0.0.8-py3-none-any.whl | Bin 20459 -> 0 bytes .../dist/analysis-1.0.0.8.tar.gz | Bin 18782 -> 0 bytes .../dist/analysis-1.0.0.9-py3-none-any.whl | Bin 20332 -> 0 bytes .../dist/analysis-1.0.0.9.tar.gz | Bin 19036 -> 0 bytes .../analysis-amd64/docker/Dockerfile | 5 - .../analysis-amd64/docker/start-docker.sh | 3 - .../analysis-amd64/requirements.txt | 6 - analysis-master/analysis-amd64/setup.py | 26 - .../analysis-arm64/docker/Dockerfile | 0 .../analysis-arm64/docker/start-docker.sh | 3 - .../{analysis-amd64 => }/analysis/__init__.py | 0 .../{analysis-amd64 => }/analysis/analysis.py | 0 .../analysis/metrics/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin .../metrics/__pycache__/elo.cpython-38.pyc | Bin .../__pycache__/glicko2.cpython-38.pyc | Bin .../__pycache__/trueskill.cpython-38.pyc | Bin .../analysis/metrics/elo.py | 0 .../analysis/metrics/glicko2.py | 0 .../analysis/metrics/trueskill.py | 0 .../analysis/regression.py | 0 .../analysis/titanlearn.py | 0 .../analysis/visualization.py | 0 54 files changed, 3399 deletions(-) delete mode 100644 analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO delete mode 100644 analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt delete mode 100644 analysis-master/analysis-amd64/analysis.egg-info/dependency_links.txt delete mode 100644 analysis-master/analysis-amd64/analysis.egg-info/requires.txt delete mode 100644 analysis-master/analysis-amd64/analysis.egg-info/top_level.txt delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-37.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-38.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/analysis.cpython-37.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/analysis.cpython-38.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/glicko2.cpython-37.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/glicko2.cpython-38.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/trueskill.cpython-37.pyc delete mode 100644 analysis-master/analysis-amd64/analysis/__pycache__/trueskill.cpython-38.pyc delete mode 100755 analysis-master/analysis-amd64/build.sh delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/__init__.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/analysis.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/glicko2.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/regression.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/titanlearn.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/trueskill.py delete mode 100644 analysis-master/analysis-amd64/build/lib/analysis/visualization.py delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.10-py3-none-any.whl delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.10.tar.gz delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.11-py3-none-any.whl delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.11.tar.gz delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.8-py3-none-any.whl delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.8.tar.gz delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.9-py3-none-any.whl delete mode 100644 analysis-master/analysis-amd64/dist/analysis-1.0.0.9.tar.gz delete mode 100644 analysis-master/analysis-amd64/docker/Dockerfile delete mode 100755 analysis-master/analysis-amd64/docker/start-docker.sh delete mode 100644 analysis-master/analysis-amd64/requirements.txt delete mode 100644 analysis-master/analysis-amd64/setup.py delete mode 100644 analysis-master/analysis-arm64/docker/Dockerfile delete mode 100755 analysis-master/analysis-arm64/docker/start-docker.sh rename analysis-master/{analysis-amd64 => }/analysis/__init__.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/analysis.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/__init__.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/__pycache__/__init__.cpython-38.pyc (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/__pycache__/elo.cpython-38.pyc (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/__pycache__/glicko2.cpython-38.pyc (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/__pycache__/trueskill.cpython-38.pyc (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/elo.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/glicko2.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/metrics/trueskill.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/regression.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/titanlearn.py (100%) rename analysis-master/{analysis-amd64 => }/analysis/visualization.py (100%) diff --git a/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO b/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO deleted file mode 100644 index 83058193..00000000 --- a/analysis-master/analysis-amd64/analysis.egg-info/PKG-INFO +++ /dev/null @@ -1,14 +0,0 @@ -Metadata-Version: 2.1 -Name: analysis -Version: 1.0.0.12 -Summary: analysis package developed by Titan Scouting for The Red Alliance -Home-page: https://github.com/titanscout2022/tr2022-strategy -Author: The Titan Scouting Team -Author-email: titanscout2022@gmail.com -License: GNU General Public License v3.0 -Description: UNKNOWN -Platform: UNKNOWN -Classifier: Programming Language :: Python :: 3 -Classifier: Operating System :: OS Independent -Requires-Python: >=3.6 -Description-Content-Type: text/markdown diff --git a/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt b/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt deleted file mode 100644 index 2d8be231..00000000 --- a/analysis-master/analysis-amd64/analysis.egg-info/SOURCES.txt +++ /dev/null @@ -1,15 +0,0 @@ -setup.py -analysis/__init__.py -analysis/analysis.py -analysis/regression.py -analysis/titanlearn.py -analysis/visualization.py -analysis.egg-info/PKG-INFO -analysis.egg-info/SOURCES.txt -analysis.egg-info/dependency_links.txt -analysis.egg-info/requires.txt -analysis.egg-info/top_level.txt -analysis/metrics/__init__.py -analysis/metrics/elo.py -analysis/metrics/glicko2.py -analysis/metrics/trueskill.py \ No newline at end of file diff --git a/analysis-master/analysis-amd64/analysis.egg-info/dependency_links.txt b/analysis-master/analysis-amd64/analysis.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/analysis-master/analysis-amd64/analysis.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/analysis-master/analysis-amd64/analysis.egg-info/requires.txt b/analysis-master/analysis-amd64/analysis.egg-info/requires.txt deleted file mode 100644 index 6868226f..00000000 --- a/analysis-master/analysis-amd64/analysis.egg-info/requires.txt +++ /dev/null @@ -1,6 +0,0 @@ -numba -numpy -scipy -scikit-learn -six -matplotlib diff --git a/analysis-master/analysis-amd64/analysis.egg-info/top_level.txt b/analysis-master/analysis-amd64/analysis.egg-info/top_level.txt deleted file mode 100644 index 09ad3be3..00000000 --- a/analysis-master/analysis-amd64/analysis.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -analysis diff --git a/analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-37.pyc b/analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 58e435d647c201f89a2ea771b73275d3a98ad37a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmZ?b<>g`kg4WosI1v39M8E(ekl_Ht#VkM~g&~+hlhJP_LlHtPSz_< diff --git a/analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-38.pyc b/analysis-master/analysis-amd64/analysis/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 2d9cbe89bf0f9337ca7bf5887152effbe568076d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170 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 m>gPP^t$KAY?|Gib6-3aaIDs@COwSHxu{*Qq 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

!udj${drSsBjn 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", - "Jacob Levine ", -) - -__all__ = [ - 'load_csv', - 'basic_stats', - 'z_score', - 'z_normalize', - 'histo_analysis', - 'regression', - 'Metrics', - 'RegressionMetrics', - 'ClassificationMetrics', - 'kmeans', - 'pca', - 'decisiontree', - 'KNN', - 'NaiveBayes', - 'SVM', - 'random_forest_classifier', - 'random_forest_regressor', - 'CorrelationTests', - 'StatisticalTests', - # all statistics functions left out due to integration in other functions -] - -# now back to your regularly scheduled programming: - -# imports (now in alphabetical order! v 1.0.3.006): - -import csv -from analysis.metrics import elo as Elo -from analysis.metrics import glicko2 as Glicko2 -import math -import numba -from numba import jit -import numpy as np -import scipy -from scipy import optimize, stats -import sklearn -from sklearn import preprocessing, pipeline, linear_model, metrics, cluster, decomposition, tree, neighbors, naive_bayes, svm, model_selection, ensemble -from analysis.metrics import trueskill as Trueskill - -class error(ValueError): - pass - -def load_csv(filepath): - with open(filepath, newline='') as csvfile: - file_array = np.array(list(csv.reader(csvfile))) - csvfile.close() - return file_array - -# expects 1d array -@jit(forceobj=True) -def basic_stats(data): - - data_t = np.array(data).astype(float) - - _mean = mean(data_t) - _median = median(data_t) - _stdev = stdev(data_t) - _variance = variance(data_t) - _min = npmin(data_t) - _max = npmax(data_t) - - return _mean, _median, _stdev, _variance, _min, _max - -# returns z score with inputs of point, mean and standard deviation of spread -@jit(forceobj=True) -def z_score(point, mean, stdev): - score = (point - mean) / stdev - - return score - -# expects 2d array, normalizes across all axes -@jit(forceobj=True) -def z_normalize(array, *args): - - array = np.array(array) - for arg in args: - array = sklearn.preprocessing.normalize(array, axis = arg) - - return array - -@jit(forceobj=True) -# expects 2d array of [x,y] -def histo_analysis(hist_data): - - if(len(hist_data[0]) > 2): - - hist_data = np.array(hist_data) - derivative = np.array(len(hist_data) - 1, dtype = float) - t = np.diff(hist_data) - derivative = t[1] / t[0] - np.sort(derivative) - - return basic_stats(derivative)[0], basic_stats(derivative)[3] - - else: - - return None - -def regression(inputs, outputs, args): # inputs, outputs expects N-D array - - X = np.array(inputs) - y = np.array(outputs) - - regressions = [] - - if 'lin' in args: # formula: ax + b - - try: - - def func(x, a, b): - - return a * x + b - - popt, pcov = scipy.optimize.curve_fit(func, X, y) - - regressions.append((popt.flatten().tolist(), None)) - - except Exception as e: - - pass - - if 'log' in args: # formula: a log (b(x + c)) + d - - try: - - def func(x, a, b, c, d): - - return a * np.log(b*(x + c)) + d - - popt, pcov = scipy.optimize.curve_fit(func, X, y) - - regressions.append((popt.flatten().tolist(), None)) - - except Exception as e: - - pass - - if 'exp' in args: # formula: a e ^ (b(x + c)) + d - - try: - - def func(x, a, b, c, d): - - return a * np.exp(b*(x + c)) + d - - popt, pcov = scipy.optimize.curve_fit(func, X, y) - - regressions.append((popt.flatten().tolist(), None)) - - except Exception as e: - - pass - - if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... - - inputs = np.array([inputs]) - outputs = np.array([outputs]) - - plys = [] - limit = len(outputs[0]) - - for i in range(2, limit): - - model = sklearn.preprocessing.PolynomialFeatures(degree = i) - model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) - model = model.fit(np.rot90(inputs), np.rot90(outputs)) - - params = model.steps[1][1].intercept_.tolist() - params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) - params.flatten() - params = params.tolist() - - plys.append(params) - - regressions.append(plys) - - if 'sig' in args: # formula: a tanh (b(x + c)) + d - - try: - - def func(x, a, b, c, d): - - return a * np.tanh(b*(x + c)) + d - - popt, pcov = scipy.optimize.curve_fit(func, X, y) - - regressions.append((popt.flatten().tolist(), None)) - - except Exception as e: - - pass - - return regressions - -class Metrics: - - def elo(self, starting_score, opposing_score, observed, N, K): - - return Elo.calculate(starting_score, opposing_score, observed, N, K) - - def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): - - player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) - - player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) - - return (player.rating, player.rd, player.vol) - - def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] - - team_ratings = [] - - for team in teams_data: - team_temp = () - for player in team: - player = Trueskill.Rating(player[0], player[1]) - team_temp = team_temp + (player,) - team_ratings.append(team_temp) - - return Trueskill.rate(team_ratings, ranks=observations) - -class RegressionMetrics(): - - def __new__(cls, predictions, targets): - - return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) - - def r_squared(self, predictions, targets): # assumes equal size inputs - - return sklearn.metrics.r2_score(targets, predictions) - - def mse(self, predictions, targets): - - return sklearn.metrics.mean_squared_error(targets, predictions) - - def rms(self, predictions, targets): - - return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) - -class ClassificationMetrics(): - - def __new__(cls, predictions, targets): - - return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) - - def cm(self, predictions, targets): - - return sklearn.metrics.confusion_matrix(targets, predictions) - - def cr(self, predictions, targets): - - return sklearn.metrics.classification_report(targets, predictions) - -@jit(nopython=True) -def mean(data): - - return np.mean(data) - -@jit(nopython=True) -def median(data): - - return np.median(data) - -@jit(nopython=True) -def stdev(data): - - return np.std(data) - -@jit(nopython=True) -def variance(data): - - return np.var(data) - -@jit(nopython=True) -def npmin(data): - - return np.amin(data) - -@jit(nopython=True) -def npmax(data): - - return np.amax(data) - -@jit(forceobj=True) -def kmeans(data, n_clusters=8, init="k-means++", n_init=10, max_iter=300, tol=0.0001, precompute_distances="auto", verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm="auto"): - - kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) - kernel.fit(data) - predictions = kernel.predict(data) - centers = kernel.cluster_centers_ - - return centers, predictions - -@jit(forceobj=True) -def pca(data, n_components = None, copy = True, whiten = False, svd_solver = "auto", tol = 0.0, iterated_power = "auto", random_state = None): - - kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) - - return kernel.fit_transform(data) - -@jit(forceobj=True) -def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = "default", max_depth = None): #expects *2d data and 1d labels - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) - model = model.fit(data_train,labels_train) - predictions = model.predict(data_test) - metrics = ClassificationMetrics(predictions, labels_test) - - return model, metrics - -class KNN: - - def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.neighbors.KNeighborsClassifier() - model.fit(data_train, labels_train) - predictions = model.predict(data_test) - - return model, ClassificationMetrics(predictions, labels_test) - - def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): - - data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) - model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) - model.fit(data_train, outputs_train) - predictions = model.predict(data_test) - - return model, RegressionMetrics(predictions, outputs_test) - -class NaiveBayes: - - def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) - - return model, ClassificationMetrics(predictions, labels_test) - - def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) - - return model, ClassificationMetrics(predictions, labels_test) - - def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) - - return model, ClassificationMetrics(predictions, labels_test) - - def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) - - return model, ClassificationMetrics(predictions, labels_test) - -class SVM: - - class CustomKernel: - - def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): - - return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) - - class StandardKernel: - - def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): - - return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) - - class PrebuiltKernel: - - class Linear: - - def __new__(cls): - - return sklearn.svm.SVC(kernel = 'linear') - - class Polynomial: - - def __new__(cls, power, r_bias): - - return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) - - class RBF: - - def __new__(cls, gamma): - - return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) - - class Sigmoid: - - def __new__(cls, r_bias): - - return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) - - def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs - - return kernel.fit(train_data, train_outputs) - - def eval_classification(self, kernel, test_data, test_outputs): - - predictions = kernel.predict(test_data) - - return ClassificationMetrics(predictions, test_outputs) - - def eval_regression(self, kernel, test_data, test_outputs): - - predictions = kernel.predict(test_data) - - return RegressionMetrics(predictions, test_outputs) - -def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): - - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) - kernel.fit(data_train, labels_train) - predictions = kernel.predict(data_test) - - return kernel, ClassificationMetrics(predictions, labels_test) - -def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): - - data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) - kernel.fit(data_train, outputs_train) - predictions = kernel.predict(data_test) - - return kernel, RegressionMetrics(predictions, outputs_test) - -class CorrelationTests: - - def anova_oneway(self, *args): #expects arrays of samples - - results = scipy.stats.f_oneway(*args) - return {"F-value": results[0], "p-value": results[1]} - - def pearson(self, x, y): - - results = scipy.stats.pearsonr(x, y) - return {"r-value": results[0], "p-value": results[1]} - - def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): - - results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) - return {"r-value": results[0], "p-value": results[1]} - - def point_biserial(self, x,y): - - results = scipy.stats.pointbiserialr(x, y) - return {"r-value": results[0], "p-value": results[1]} - - def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): - - results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) - return {"tau": results[0], "p-value": results[1]} - - def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): - - results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) - return {"tau": results[0], "p-value": results[1]} - - def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): - - results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) - return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value - -class StatisticalTests: - - def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): - - results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} - - def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): - - results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} - - def ttest_statistic(self, o1, o2, equal = True): - - results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) - return {"t-value": results[0], "p-value": results[1]} - - def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): - - results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} - - def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): - - results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) - return {"ks-value": results[0], "p-value": results[1]} - - def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): - - results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) - return {"chisquared-value": results[0], "p-value": results[1]} - - def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): - - results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) - return {"powerdivergence-value": results[0], "p-value": results[1]} - - def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): - - results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) - return {"ks-value": results[0], "p-value": results[1]} - - def es_twosample(self, x, y, t = (0.4, 0.8)): - - results = scipy.stats.epps_singleton_2samp(x, y, t = t) - return {"es-value": results[0], "p-value": results[1]} - - def mw_rank(self, x, y, use_continuity = True, alternative = None): - - results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) - return {"u-value": results[0], "p-value": results[1]} - - def mw_tiecorrection(self, rank_values): - - results = scipy.stats.tiecorrect(rank_values) - return {"correction-factor": results} - - def rankdata(self, a, method = 'average'): - - results = scipy.stats.rankdata(a, method = method) - return results - - def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test - - results = scipy.stats.ranksums(a, b) - return {"u-value": results[0], "p-value": results[1]} - - def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): - - results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) - return {"t-value": results[0], "p-value": results[1]} - - def kw_htest(self, *args, nan_policy = 'propagate'): - - results = scipy.stats.kruskal(*args, nan_policy = nan_policy) - return {"h-value": results[0], "p-value": results[1]} - - def friedman_chisquare(self, *args): - - results = scipy.stats.friedmanchisquare(*args) - return {"chisquared-value": results[0], "p-value": results[1]} - - def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): - - results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) - return {"w-value": results[0], "p-value": results[1]} - - def combine_pvalues(self, pvalues, method = 'fisher', weights = None): - - results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) - return {"combined-statistic": results[0], "p-value": results[1]} - - def jb_fitness(self, x): - - results = scipy.stats.jarque_bera(x) - return {"jb-value": results[0], "p-value": results[1]} - - def ab_equality(self, x, y): - - results = scipy.stats.ansari(x, y) - return {"ab-value": results[0], "p-value": results[1]} - - def bartlett_variance(self, *args): - - results = scipy.stats.bartlett(*args) - return {"t-value": results[0], "p-value": results[1]} - - def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): - - results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) - return {"w-value": results[0], "p-value": results[1]} - - def sw_normality(self, x): - - results = scipy.stats.shapiro(x) - return {"w-value": results[0], "p-value": results[1]} - - def shapiro(self, x): - - return "destroyed by facts and logic" - - def ad_onesample(self, x, dist = 'norm'): - - results = scipy.stats.anderson(x, dist = dist) - return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - - def ad_ksample(self, samples, midrank = True): - - results = scipy.stats.anderson_ksamp(samples, midrank = midrank) - return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - - def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): - - results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) - return {"p-value": results} - - def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): - - results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) - return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value - - def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): - - results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) - return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} - - def mood_equalscale(self, x, y, axis = 0): - - results = scipy.stats.mood(x, y, axis = axis) - return {"z-score": results[0], "p-value": results[1]} - - def skewtest(self, a, axis = 0, nan_policy = 'propogate'): - - results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} - - def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): - - results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} - - def normaltest(self, a, axis = 0, nan_policy = 'propogate'): - - results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/glicko2.py b/analysis-master/analysis-amd64/build/lib/analysis/glicko2.py deleted file mode 100644 index 66c0df94..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/glicko2.py +++ /dev/null @@ -1,99 +0,0 @@ -import math - -class Glicko2: - _tau = 0.5 - - def getRating(self): - return (self.__rating * 173.7178) + 1500 - - def setRating(self, rating): - self.__rating = (rating - 1500) / 173.7178 - - rating = property(getRating, setRating) - - def getRd(self): - return self.__rd * 173.7178 - - def setRd(self, rd): - self.__rd = rd / 173.7178 - - rd = property(getRd, setRd) - - def __init__(self, rating = 1500, rd = 350, vol = 0.06): - - self.setRating(rating) - self.setRd(rd) - self.vol = vol - - def _preRatingRD(self): - - self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) - - def update_player(self, rating_list, RD_list, outcome_list): - - rating_list = [(x - 1500) / 173.7178 for x in rating_list] - RD_list = [x / 173.7178 for x in RD_list] - - v = self._v(rating_list, RD_list) - self.vol = self._newVol(rating_list, RD_list, outcome_list, v) - self._preRatingRD() - - self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) - - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * \ - (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - self.__rating += math.pow(self.__rd, 2) * tempSum - - - def _newVol(self, rating_list, RD_list, outcome_list, v): - - i = 0 - delta = self._delta(rating_list, RD_list, outcome_list, v) - a = math.log(math.pow(self.vol, 2)) - tau = self._tau - x0 = a - x1 = 0 - - while x0 != x1: - # New iteration, so x(i) becomes x(i-1) - x0 = x1 - d = math.pow(self.__rating, 2) + v + math.exp(x0) - h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ - / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) - h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ - (math.pow(self.__rating, 2) + v) \ - / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ - * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) - x1 = x0 - (h1 / h2) - - return math.exp(x1 / 2) - - def _delta(self, rating_list, RD_list, outcome_list, v): - - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - return v * tempSum - - def _v(self, rating_list, RD_list): - - tempSum = 0 - for i in range(len(rating_list)): - tempE = self._E(rating_list[i], RD_list[i]) - tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) - return 1 / tempSum - - def _E(self, p2rating, p2RD): - - return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ - (self.__rating - p2rating))) - - def _g(self, RD): - - return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) - - def did_not_compete(self): - - self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py deleted file mode 100644 index 3c8ef2e0..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/metrics/elo.py +++ /dev/null @@ -1,7 +0,0 @@ -import numpy as np - -def calculate(starting_score, opposing_score, observed, N, K): - - expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) - - return starting_score + K*(np.sum(observed) - np.sum(expected)) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py deleted file mode 100644 index 66c0df94..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/metrics/glicko2.py +++ /dev/null @@ -1,99 +0,0 @@ -import math - -class Glicko2: - _tau = 0.5 - - def getRating(self): - return (self.__rating * 173.7178) + 1500 - - def setRating(self, rating): - self.__rating = (rating - 1500) / 173.7178 - - rating = property(getRating, setRating) - - def getRd(self): - return self.__rd * 173.7178 - - def setRd(self, rd): - self.__rd = rd / 173.7178 - - rd = property(getRd, setRd) - - def __init__(self, rating = 1500, rd = 350, vol = 0.06): - - self.setRating(rating) - self.setRd(rd) - self.vol = vol - - def _preRatingRD(self): - - self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) - - def update_player(self, rating_list, RD_list, outcome_list): - - rating_list = [(x - 1500) / 173.7178 for x in rating_list] - RD_list = [x / 173.7178 for x in RD_list] - - v = self._v(rating_list, RD_list) - self.vol = self._newVol(rating_list, RD_list, outcome_list, v) - self._preRatingRD() - - self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) - - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * \ - (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - self.__rating += math.pow(self.__rd, 2) * tempSum - - - def _newVol(self, rating_list, RD_list, outcome_list, v): - - i = 0 - delta = self._delta(rating_list, RD_list, outcome_list, v) - a = math.log(math.pow(self.vol, 2)) - tau = self._tau - x0 = a - x1 = 0 - - while x0 != x1: - # New iteration, so x(i) becomes x(i-1) - x0 = x1 - d = math.pow(self.__rating, 2) + v + math.exp(x0) - h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ - / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) - h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ - (math.pow(self.__rating, 2) + v) \ - / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ - * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) - x1 = x0 - (h1 / h2) - - return math.exp(x1 / 2) - - def _delta(self, rating_list, RD_list, outcome_list, v): - - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - return v * tempSum - - def _v(self, rating_list, RD_list): - - tempSum = 0 - for i in range(len(rating_list)): - tempE = self._E(rating_list[i], RD_list[i]) - tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) - return 1 / tempSum - - def _E(self, p2rating, p2RD): - - return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ - (self.__rating - p2rating))) - - def _g(self, RD): - - return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) - - def did_not_compete(self): - - self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py b/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py deleted file mode 100644 index 116357df..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/metrics/trueskill.py +++ /dev/null @@ -1,907 +0,0 @@ -from __future__ import absolute_import - -from itertools import chain -import math - -from six import iteritems -from six.moves import map, range, zip -from six import iterkeys - -import copy -try: - from numbers import Number -except ImportError: - Number = (int, long, float, complex) - -inf = float('inf') - -class Gaussian(object): - #: Precision, the inverse of the variance. - pi = 0 - #: Precision adjusted mean, the precision multiplied by the mean. - tau = 0 - - def __init__(self, mu=None, sigma=None, pi=0, tau=0): - if mu is not None: - if sigma is None: - raise TypeError('sigma argument is needed') - elif sigma == 0: - raise ValueError('sigma**2 should be greater than 0') - pi = sigma ** -2 - tau = pi * mu - self.pi = pi - self.tau = tau - - @property - def mu(self): - return self.pi and self.tau / self.pi - - @property - def sigma(self): - return math.sqrt(1 / self.pi) if self.pi else inf - - def __mul__(self, other): - pi, tau = self.pi + other.pi, self.tau + other.tau - return Gaussian(pi=pi, tau=tau) - - def __truediv__(self, other): - pi, tau = self.pi - other.pi, self.tau - other.tau - return Gaussian(pi=pi, tau=tau) - - __div__ = __truediv__ # for Python 2 - - def __eq__(self, other): - return self.pi == other.pi and self.tau == other.tau - - def __lt__(self, other): - return self.mu < other.mu - - def __le__(self, other): - return self.mu <= other.mu - - def __gt__(self, other): - return self.mu > other.mu - - def __ge__(self, other): - return self.mu >= other.mu - - def __repr__(self): - return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) - - def _repr_latex_(self): - latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) - return '$%s$' % latex - -class Matrix(list): - def __init__(self, src, height=None, width=None): - if callable(src): - f, src = src, {} - size = [height, width] - if not height: - def set_height(height): - size[0] = height - size[0] = set_height - if not width: - def set_width(width): - size[1] = width - size[1] = set_width - try: - for (r, c), val in f(*size): - src[r, c] = val - except TypeError: - raise TypeError('A callable src must return an interable ' - 'which generates a tuple containing ' - 'coordinate and value') - height, width = tuple(size) - if height is None or width is None: - raise TypeError('A callable src must call set_height and ' - 'set_width if the size is non-deterministic') - if isinstance(src, list): - is_number = lambda x: isinstance(x, Number) - unique_col_sizes = set(map(len, src)) - everything_are_number = filter(is_number, sum(src, [])) - if len(unique_col_sizes) != 1 or not everything_are_number: - raise ValueError('src must be a rectangular array of numbers') - two_dimensional_array = src - elif isinstance(src, dict): - if not height or not width: - w = h = 0 - for r, c in iterkeys(src): - if not height: - h = max(h, r + 1) - if not width: - w = max(w, c + 1) - if not height: - height = h - if not width: - width = w - two_dimensional_array = [] - for r in range(height): - row = [] - two_dimensional_array.append(row) - for c in range(width): - row.append(src.get((r, c), 0)) - else: - raise TypeError('src must be a list or dict or callable') - super(Matrix, self).__init__(two_dimensional_array) - - @property - def height(self): - return len(self) - - @property - def width(self): - return len(self[0]) - - def transpose(self): - height, width = self.height, self.width - src = {} - for c in range(width): - for r in range(height): - src[c, r] = self[r][c] - return type(self)(src, height=width, width=height) - - def minor(self, row_n, col_n): - height, width = self.height, self.width - if not (0 <= row_n < height): - raise ValueError('row_n should be between 0 and %d' % height) - elif not (0 <= col_n < width): - raise ValueError('col_n should be between 0 and %d' % width) - two_dimensional_array = [] - for r in range(height): - if r == row_n: - continue - row = [] - two_dimensional_array.append(row) - for c in range(width): - if c == col_n: - continue - row.append(self[r][c]) - return type(self)(two_dimensional_array) - - def determinant(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can calculate a determinant') - tmp, rv = copy.deepcopy(self), 1. - for c in range(width - 1, 0, -1): - pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) - pivot = tmp[r][c] - if not pivot: - return 0. - tmp[r], tmp[c] = tmp[c], tmp[r] - if r != c: - rv = -rv - rv *= pivot - fact = -1. / pivot - for r in range(c): - f = fact * tmp[r][c] - for x in range(c): - tmp[r][x] += f * tmp[c][x] - return rv * tmp[0][0] - - def adjugate(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can be adjugated') - if height == 2: - a, b = self[0][0], self[0][1] - c, d = self[1][0], self[1][1] - return type(self)([[d, -b], [-c, a]]) - src = {} - for r in range(height): - for c in range(width): - sign = -1 if (r + c) % 2 else 1 - src[r, c] = self.minor(r, c).determinant() * sign - return type(self)(src, height, width) - - def inverse(self): - if self.height == self.width == 1: - return type(self)([[1. / self[0][0]]]) - return (1. / self.determinant()) * self.adjugate() - - def __add__(self, other): - height, width = self.height, self.width - if (height, width) != (other.height, other.width): - raise ValueError('Must be same size') - src = {} - for r in range(height): - for c in range(width): - src[r, c] = self[r][c] + other[r][c] - return type(self)(src, height, width) - - def __mul__(self, other): - if self.width != other.height: - raise ValueError('Bad size') - height, width = self.height, other.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = sum(self[r][x] * other[x][c] - for x in range(self.width)) - return type(self)(src, height, width) - - def __rmul__(self, other): - if not isinstance(other, Number): - raise TypeError('The operand should be a number') - height, width = self.height, self.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = other * self[r][c] - return type(self)(src, height, width) - - def __repr__(self): - return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) - - def _repr_latex_(self): - rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] - latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) - return '$%s$' % latex - -def _gen_erfcinv(erfc, math=math): - def erfcinv(y): - """The inverse function of erfc.""" - if y >= 2: - return -100. - elif y <= 0: - return 100. - zero_point = y < 1 - if not zero_point: - y = 2 - y - t = math.sqrt(-2 * math.log(y / 2.)) - x = -0.70711 * \ - ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) - for i in range(2): - err = erfc(x) - y - x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) - return x if zero_point else -x - return erfcinv - -def _gen_ppf(erfc, math=math): - erfcinv = _gen_erfcinv(erfc, math) - def ppf(x, mu=0, sigma=1): - return mu - sigma * math.sqrt(2) * erfcinv(2 * x) - return ppf - -def erfc(x): - z = abs(x) - t = 1. / (1. + z / 2.) - r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( - 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( - 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( - -0.82215223 + t * 0.17087277 - ))) - ))) - ))) - return 2. - r if x < 0 else r - -def cdf(x, mu=0, sigma=1): - return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) - - -def pdf(x, mu=0, sigma=1): - return (1 / math.sqrt(2 * math.pi) * abs(sigma) * - math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) - -ppf = _gen_ppf(erfc) - -def choose_backend(backend): - if backend is None: # fallback - return cdf, pdf, ppf - elif backend == 'mpmath': - try: - import mpmath - except ImportError: - raise ImportError('Install "mpmath" to use this backend') - return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) - elif backend == 'scipy': - try: - from scipy.stats import norm - except ImportError: - raise ImportError('Install "scipy" to use this backend') - return norm.cdf, norm.pdf, norm.ppf - raise ValueError('%r backend is not defined' % backend) - -def available_backends(): - backends = [None] - for backend in ['mpmath', 'scipy']: - try: - __import__(backend) - except ImportError: - continue - backends.append(backend) - return backends - -class Node(object): - - pass - -class Variable(Node, Gaussian): - - def __init__(self): - self.messages = {} - super(Variable, self).__init__() - - def set(self, val): - delta = self.delta(val) - self.pi, self.tau = val.pi, val.tau - return delta - - def delta(self, other): - pi_delta = abs(self.pi - other.pi) - if pi_delta == inf: - return 0. - return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) - - def update_message(self, factor, pi=0, tau=0, message=None): - message = message or Gaussian(pi=pi, tau=tau) - old_message, self[factor] = self[factor], message - return self.set(self / old_message * message) - - def update_value(self, factor, pi=0, tau=0, value=None): - value = value or Gaussian(pi=pi, tau=tau) - old_message = self[factor] - self[factor] = value * old_message / self - return self.set(value) - - def __getitem__(self, factor): - return self.messages[factor] - - def __setitem__(self, factor, message): - self.messages[factor] = message - - def __repr__(self): - args = (type(self).__name__, super(Variable, self).__repr__(), - len(self.messages), '' if len(self.messages) == 1 else 's') - return '<%s %s with %d connection%s>' % args - - -class Factor(Node): - - def __init__(self, variables): - self.vars = variables - for var in variables: - var[self] = Gaussian() - - def down(self): - return 0 - - def up(self): - return 0 - - @property - def var(self): - assert len(self.vars) == 1 - return self.vars[0] - - def __repr__(self): - args = (type(self).__name__, len(self.vars), - '' if len(self.vars) == 1 else 's') - return '<%s with %d connection%s>' % args - - -class PriorFactor(Factor): - - def __init__(self, var, val, dynamic=0): - super(PriorFactor, self).__init__([var]) - self.val = val - self.dynamic = dynamic - - def down(self): - sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) - value = Gaussian(self.val.mu, sigma) - return self.var.update_value(self, value=value) - - -class LikelihoodFactor(Factor): - - def __init__(self, mean_var, value_var, variance): - super(LikelihoodFactor, self).__init__([mean_var, value_var]) - self.mean = mean_var - self.value = value_var - self.variance = variance - - def calc_a(self, var): - return 1. / (1. + self.variance * var.pi) - - def down(self): - # update value. - msg = self.mean / self.mean[self] - a = self.calc_a(msg) - return self.value.update_message(self, a * msg.pi, a * msg.tau) - - def up(self): - # update mean. - msg = self.value / self.value[self] - a = self.calc_a(msg) - return self.mean.update_message(self, a * msg.pi, a * msg.tau) - - -class SumFactor(Factor): - - def __init__(self, sum_var, term_vars, coeffs): - super(SumFactor, self).__init__([sum_var] + term_vars) - self.sum = sum_var - self.terms = term_vars - self.coeffs = coeffs - - def down(self): - vals = self.terms - msgs = [var[self] for var in vals] - return self.update(self.sum, vals, msgs, self.coeffs) - - def up(self, index=0): - coeff = self.coeffs[index] - coeffs = [] - for x, c in enumerate(self.coeffs): - try: - if x == index: - coeffs.append(1. / coeff) - else: - coeffs.append(-c / coeff) - except ZeroDivisionError: - coeffs.append(0.) - vals = self.terms[:] - vals[index] = self.sum - msgs = [var[self] for var in vals] - return self.update(self.terms[index], vals, msgs, coeffs) - - def update(self, var, vals, msgs, coeffs): - pi_inv = 0 - mu = 0 - for val, msg, coeff in zip(vals, msgs, coeffs): - div = val / msg - mu += coeff * div.mu - if pi_inv == inf: - continue - try: - # numpy.float64 handles floating-point error by different way. - # For example, it can just warn RuntimeWarning on n/0 problem - # instead of throwing ZeroDivisionError. So div.pi, the - # denominator has to be a built-in float. - pi_inv += coeff ** 2 / float(div.pi) - except ZeroDivisionError: - pi_inv = inf - pi = 1. / pi_inv - tau = pi * mu - return var.update_message(self, pi, tau) - - -class TruncateFactor(Factor): - - def __init__(self, var, v_func, w_func, draw_margin): - super(TruncateFactor, self).__init__([var]) - self.v_func = v_func - self.w_func = w_func - self.draw_margin = draw_margin - - def up(self): - val = self.var - msg = self.var[self] - div = val / msg - sqrt_pi = math.sqrt(div.pi) - args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) - v = self.v_func(*args) - w = self.w_func(*args) - denom = (1. - w) - pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom - return val.update_value(self, pi, tau) - -#: Default initial mean of ratings. -MU = 25. -#: Default initial standard deviation of ratings. -SIGMA = MU / 3 -#: Default distance that guarantees about 76% chance of winning. -BETA = SIGMA / 2 -#: Default dynamic factor. -TAU = SIGMA / 100 -#: Default draw probability of the game. -DRAW_PROBABILITY = .10 -#: A basis to check reliability of the result. -DELTA = 0.0001 - - -def calc_draw_probability(draw_margin, size, env=None): - if env is None: - env = global_env() - return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 - - -def calc_draw_margin(draw_probability, size, env=None): - if env is None: - env = global_env() - return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta - - -def _team_sizes(rating_groups): - team_sizes = [0] - for group in rating_groups: - team_sizes.append(len(group) + team_sizes[-1]) - del team_sizes[0] - return team_sizes - - -def _floating_point_error(env): - if env.backend == 'mpmath': - msg = 'Set "mpmath.mp.dps" to higher' - else: - msg = 'Cannot calculate correctly, set backend to "mpmath"' - return FloatingPointError(msg) - - -class Rating(Gaussian): - def __init__(self, mu=None, sigma=None): - if isinstance(mu, tuple): - mu, sigma = mu - elif isinstance(mu, Gaussian): - mu, sigma = mu.mu, mu.sigma - if mu is None: - mu = global_env().mu - if sigma is None: - sigma = global_env().sigma - super(Rating, self).__init__(mu, sigma) - - def __int__(self): - return int(self.mu) - - def __long__(self): - return long(self.mu) - - def __float__(self): - return float(self.mu) - - def __iter__(self): - return iter((self.mu, self.sigma)) - - def __repr__(self): - c = type(self) - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) - return '%s(mu=%.3f, sigma=%.3f)' % args - - -class TrueSkill(object): - def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None): - self.mu = mu - self.sigma = sigma - self.beta = beta - self.tau = tau - self.draw_probability = draw_probability - self.backend = backend - if isinstance(backend, tuple): - self.cdf, self.pdf, self.ppf = backend - else: - self.cdf, self.pdf, self.ppf = choose_backend(backend) - - def create_rating(self, mu=None, sigma=None): - if mu is None: - mu = self.mu - if sigma is None: - sigma = self.sigma - return Rating(mu, sigma) - - def v_win(self, diff, draw_margin): - x = diff - draw_margin - denom = self.cdf(x) - return (self.pdf(x) / denom) if denom else -x - - def v_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - numer = self.pdf(b) - self.pdf(a) - return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) - - def w_win(self, diff, draw_margin): - x = diff - draw_margin - v = self.v_win(diff, draw_margin) - w = v * (v + x) - if 0 < w < 1: - return w - raise _floating_point_error(self) - - def w_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - if not denom: - raise _floating_point_error(self) - v = self.v_draw(abs_diff, draw_margin) - return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom - - def validate_rating_groups(self, rating_groups): - # check group sizes - if len(rating_groups) < 2: - raise ValueError('Need multiple rating groups') - elif not all(rating_groups): - raise ValueError('Each group must contain multiple ratings') - # check group types - group_types = set(map(type, rating_groups)) - if len(group_types) != 1: - raise TypeError('All groups should be same type') - elif group_types.pop() is Rating: - raise TypeError('Rating cannot be a rating group') - # normalize rating_groups - if isinstance(rating_groups[0], dict): - dict_rating_groups = rating_groups - rating_groups = [] - keys = [] - for dict_rating_group in dict_rating_groups: - rating_group, key_group = [], [] - for key, rating in iteritems(dict_rating_group): - rating_group.append(rating) - key_group.append(key) - rating_groups.append(tuple(rating_group)) - keys.append(tuple(key_group)) - else: - rating_groups = list(rating_groups) - keys = None - return rating_groups, keys - - def validate_weights(self, weights, rating_groups, keys=None): - if weights is None: - weights = [(1,) * len(g) for g in rating_groups] - elif isinstance(weights, dict): - weights_dict, weights = weights, [] - for x, group in enumerate(rating_groups): - w = [] - weights.append(w) - for y, rating in enumerate(group): - if keys is not None: - y = keys[x][y] - w.append(weights_dict.get((x, y), 1)) - return weights - - def factor_graph_builders(self, rating_groups, ranks, weights): - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - size = len(flatten_ratings) - group_size = len(rating_groups) - # create variables - rating_vars = [Variable() for x in range(size)] - perf_vars = [Variable() for x in range(size)] - team_perf_vars = [Variable() for x in range(group_size)] - team_diff_vars = [Variable() for x in range(group_size - 1)] - team_sizes = _team_sizes(rating_groups) - # layer builders - def build_rating_layer(): - for rating_var, rating in zip(rating_vars, flatten_ratings): - yield PriorFactor(rating_var, rating, self.tau) - def build_perf_layer(): - for rating_var, perf_var in zip(rating_vars, perf_vars): - yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) - def build_team_perf_layer(): - for team, team_perf_var in enumerate(team_perf_vars): - if team > 0: - start = team_sizes[team - 1] - else: - start = 0 - end = team_sizes[team] - child_perf_vars = perf_vars[start:end] - coeffs = flatten_weights[start:end] - yield SumFactor(team_perf_var, child_perf_vars, coeffs) - def build_team_diff_layer(): - for team, team_diff_var in enumerate(team_diff_vars): - yield SumFactor(team_diff_var, - team_perf_vars[team:team + 2], [+1, -1]) - def build_trunc_layer(): - for x, team_diff_var in enumerate(team_diff_vars): - if callable(self.draw_probability): - # dynamic draw probability - team_perf1, team_perf2 = team_perf_vars[x:x + 2] - args = (Rating(team_perf1), Rating(team_perf2), self) - draw_probability = self.draw_probability(*args) - else: - # static draw probability - draw_probability = self.draw_probability - size = sum(map(len, rating_groups[x:x + 2])) - draw_margin = calc_draw_margin(draw_probability, size, self) - if ranks[x] == ranks[x + 1]: # is a tie? - v_func, w_func = self.v_draw, self.w_draw - else: - v_func, w_func = self.v_win, self.w_win - yield TruncateFactor(team_diff_var, - v_func, w_func, draw_margin) - # build layers - return (build_rating_layer, build_perf_layer, build_team_perf_layer, - build_team_diff_layer, build_trunc_layer) - - def run_schedule(self, build_rating_layer, build_perf_layer, - build_team_perf_layer, build_team_diff_layer, - build_trunc_layer, min_delta=DELTA): - if min_delta <= 0: - raise ValueError('min_delta must be greater than 0') - layers = [] - def build(builders): - layers_built = [list(build()) for build in builders] - layers.extend(layers_built) - return layers_built - # gray arrows - layers_built = build([build_rating_layer, - build_perf_layer, - build_team_perf_layer]) - rating_layer, perf_layer, team_perf_layer = layers_built - for f in chain(*layers_built): - f.down() - # arrow #1, #2, #3 - team_diff_layer, trunc_layer = build([build_team_diff_layer, - build_trunc_layer]) - team_diff_len = len(team_diff_layer) - for x in range(10): - if team_diff_len == 1: - # only two teams - team_diff_layer[0].down() - delta = trunc_layer[0].up() - else: - # multiple teams - delta = 0 - for x in range(team_diff_len - 1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(1) # up to right variable - for x in range(team_diff_len - 1, 0, -1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(0) # up to left variable - # repeat until to small update - if delta <= min_delta: - break - # up both ends - team_diff_layer[0].up(0) - team_diff_layer[team_diff_len - 1].up(1) - # up the remainder of the black arrows - for f in team_perf_layer: - for x in range(len(f.vars) - 1): - f.up(x) - for f in perf_layer: - f.up() - return layers - - def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - group_size = len(rating_groups) - if ranks is None: - ranks = range(group_size) - elif len(ranks) != group_size: - raise ValueError('Wrong ranks') - # sort rating groups by rank - by_rank = lambda x: x[1][1] - sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), - key=by_rank) - sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] - for x, (g, r, w) in sorting: - sorted_rating_groups.append(g) - sorted_ranks.append(r) - # make weights to be greater than 0 - sorted_weights.append(max(min_delta, w_) for w_ in w) - # build factor graph - args = (sorted_rating_groups, sorted_ranks, sorted_weights) - builders = self.factor_graph_builders(*args) - args = builders + (min_delta,) - layers = self.run_schedule(*args) - # make result - rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) - transformed_groups = [] - for start, end in zip([0] + team_sizes[:-1], team_sizes): - group = [] - for f in rating_layer[start:end]: - group.append(Rating(float(f.var.mu), float(f.var.sigma))) - transformed_groups.append(tuple(group)) - by_hint = lambda x: x[0] - unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), - key=by_hint) - if keys is None: - return [g for x, g in unsorting] - # restore the structure with input dictionary keys - return [dict(zip(keys[x], g)) for x, g in unsorting] - - def quality(self, rating_groups, weights=None): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - length = len(flatten_ratings) - # a vector of all of the skill means - mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) - # a matrix whose diagonal values are the variances (sigma ** 2) of each - # of the players. - def variance_matrix(height, width): - variances = (r.sigma ** 2 for r in flatten_ratings) - for x, variance in enumerate(variances): - yield (x, x), variance - variance_matrix = Matrix(variance_matrix, length, length) - # the player-team assignment and comparison matrix - def rotated_a_matrix(set_height, set_width): - t = 0 - for r, (cur, _next) in enumerate(zip(rating_groups[:-1], - rating_groups[1:])): - for x in range(t, t + len(cur)): - yield (r, x), flatten_weights[x] - t += 1 - x += 1 - for x in range(x, x + len(_next)): - yield (r, x), -flatten_weights[x] - set_height(r + 1) - set_width(x + 1) - rotated_a_matrix = Matrix(rotated_a_matrix) - a_matrix = rotated_a_matrix.transpose() - # match quality further derivation - _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix - _atsa = rotated_a_matrix * variance_matrix * a_matrix - start = mean_matrix.transpose() * a_matrix - middle = _ata + _atsa - end = rotated_a_matrix * mean_matrix - # make result - e_arg = (-0.5 * start * middle.inverse() * end).determinant() - s_arg = _ata.determinant() / middle.determinant() - return math.exp(e_arg) * math.sqrt(s_arg) - - def expose(self, rating): - k = self.mu / self.sigma - return rating.mu - k * rating.sigma - - def make_as_global(self): - return setup(env=self) - - def __repr__(self): - c = type(self) - if callable(self.draw_probability): - f = self.draw_probability - draw_probability = '.'.join([f.__module__, f.__name__]) - else: - draw_probability = '%.1f%%' % (self.draw_probability * 100) - if self.backend is None: - backend = '' - elif isinstance(self.backend, tuple): - backend = ', backend=...' - else: - backend = ', backend=%r' % self.backend - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, - self.beta, self.tau, draw_probability, backend) - return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' - 'draw_probability=%s%s)' % args) - - -def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): - if env is None: - env = global_env() - ranks = [0, 0 if drawn else 1] - teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) - return teams[0][0], teams[1][0] - - -def quality_1vs1(rating1, rating2, env=None): - if env is None: - env = global_env() - return env.quality([(rating1,), (rating2,)]) - - -def global_env(): - try: - global_env.__trueskill__ - except AttributeError: - # setup the default environment - setup() - return global_env.__trueskill__ - - -def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None, env=None): - if env is None: - env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) - global_env.__trueskill__ = env - return env - - -def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): - return global_env().rate(rating_groups, ranks, weights, min_delta) - - -def quality(rating_groups, weights=None): - return global_env().quality(rating_groups, weights) - - -def expose(rating): - return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/regression.py b/analysis-master/analysis-amd64/build/lib/analysis/regression.py deleted file mode 100644 index e899e9ff..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/regression.py +++ /dev/null @@ -1,220 +0,0 @@ -# Titan Robotics Team 2022: CUDA-based Regressions Module -# Written by Arthur Lu & Jacob Levine -# Notes: -# this module has been automatically inegrated into analysis.py, and should be callable as a class from the package -# this module is cuda-optimized and vectorized (except for one small part) -# setup: - -__version__ = "1.0.0.004" - -# changelog should be viewed using print(analysis.regression.__changelog__) -__changelog__ = """ - 1.0.0.004: - - bug fixes - - fixed changelog - 1.0.0.003: - - bug fixes - 1.0.0.002: - -Added more parameters to log, exponential, polynomial - -Added SigmoidalRegKernelArthur, because Arthur apparently needs - to train the scaling and shifting of sigmoids - 1.0.0.001: - -initial release, with linear, log, exponential, polynomial, and sigmoid kernels - -already vectorized (except for polynomial generation) and CUDA-optimized -""" - -__author__ = ( - "Jacob Levine ", - "Arthur Lu " -) - -__all__ = [ - 'factorial', - 'take_all_pwrs', - 'num_poly_terms', - 'set_device', - 'LinearRegKernel', - 'SigmoidalRegKernel', - 'LogRegKernel', - 'PolyRegKernel', - 'ExpRegKernel', - 'SigmoidalRegKernelArthur', - 'SGDTrain', - 'CustomTrain' -] - -import torch - -global device - -device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" - -#todo: document completely - -def set_device(self, new_device): - device=new_device - -class LinearRegKernel(): - parameters= [] - weights=None - bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def forward(self,mtx): - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return torch.matmul(self.weights,mtx)+long_bias - -class SigmoidalRegKernel(): - parameters= [] - weights=None - bias=None - sigmoid=torch.nn.Sigmoid() - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def forward(self,mtx): - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) - -class SigmoidalRegKernelArthur(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - sigmoid=torch.nn.Sigmoid() - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias - -class LogRegKernel(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias - -class ExpRegKernel(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias - -class PolyRegKernel(): - parameters= [] - weights=None - bias=None - power=None - def __init__(self, num_vars, power): - self.power=power - num_terms=self.num_poly_terms(num_vars, power) - self.weights=torch.rand(num_terms, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def num_poly_terms(self,num_vars, power): - if power == 0: - return 0 - return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) - def factorial(self,n): - if n==0: - return 1 - else: - return n*self.factorial(n-1) - def take_all_pwrs(self, vec, pwr): - #todo: vectorize (kinda) - combins=torch.combinations(vec, r=pwr, with_replacement=True) - out=torch.ones(combins.size()[0]).to(device).to(torch.float) - for i in torch.t(combins).to(device).to(torch.float): - out *= i - if pwr == 1: - return out - else: - return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) - def forward(self,mtx): - #TODO: Vectorize the last part - cols=[] - for i in torch.t(mtx): - cols.append(self.take_all_pwrs(i,self.power)) - new_mtx=torch.t(torch.stack(cols)) - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return torch.matmul(self.weights,new_mtx)+long_bias - -def SGDTrain(self, kernel, data, ground, loss=torch.nn.MSELoss(), iterations=1000, learning_rate=.1, return_losses=False): - optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) - data_cuda=data.to(device) - ground_cuda=ground.to(device) - if (return_losses): - losses=[] - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - losses.append(ls.item()) - ls.backward() - optim.step() - return [kernel,losses] - else: - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - ls.backward() - optim.step() - return kernel - -def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iterations=1000, return_losses=False): - data_cuda=data.to(device) - ground_cuda=ground.to(device) - if (return_losses): - losses=[] - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data) - ls=loss(pred,ground) - losses.append(ls.item()) - ls.backward() - optim.step() - return [kernel,losses] - else: - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - ls.backward() - optim.step() - return kernel \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/titanlearn.py b/analysis-master/analysis-amd64/build/lib/analysis/titanlearn.py deleted file mode 100644 index b69d36e3..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/titanlearn.py +++ /dev/null @@ -1,122 +0,0 @@ -# Titan Robotics Team 2022: ML Module -# Written by Arthur Lu & Jacob Levine -# Notes: -# this should be imported as a python module using 'import titanlearn' -# this should be included in the local directory or environment variable -# this module is optimized for multhreaded computing -# this module learns from its mistakes far faster than 2022's captains -# setup: - -__version__ = "2.0.1.001" - -#changelog should be viewed using print(analysis.__changelog__) -__changelog__ = """changelog: - 2.0.1.001: - - removed matplotlib import - - removed graphloss() - 2.0.1.000: - - added net, dataset, dataloader, and stdtrain template definitions - - added graphloss function - 2.0.0.001: - - added clear functions - 2.0.0.000: - - complete rewrite planned - - depreciated 1.0.0.xxx versions - - added simple training loop - 1.0.0.xxx: - -added generation of ANNS, basic SGD training -""" - -__author__ = ( - "Arthur Lu ," - "Jacob Levine ," - ) - -__all__ = [ - 'clear', - 'net', - 'dataset', - 'dataloader', - 'train', - 'stdtrainer', - ] - -import torch -from os import system, name -import numpy as np - -def clear(): - if name == 'nt': - _ = system('cls') - else: - _ = system('clear') - -class net(torch.nn.Module): #template for standard neural net - def __init__(self): - super(Net, self).__init__() - - def forward(self, input): - pass - -class dataset(torch.utils.data.Dataset): #template for standard dataset - - def __init__(self): - super(torch.utils.data.Dataset).__init__() - - def __getitem__(self, index): - pass - - def __len__(self): - pass - -def dataloader(dataset, batch_size, num_workers, shuffle = True): - - return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) - -def train(device, net, epochs, trainloader, optimizer, criterion): #expects standard dataloader, whch returns (inputs, labels) - - dataset_len = trainloader.dataset.__len__() - iter_count = 0 - running_loss = 0 - running_loss_list = [] - - for epoch in range(epochs): # loop over the dataset multiple times - - for i, data in enumerate(trainloader, 0): - - inputs = data[0].to(device) - labels = data[1].to(device) - - optimizer.zero_grad() - - outputs = net(inputs) - loss = criterion(outputs, labels.to(torch.float)) - - loss.backward() - optimizer.step() - - # monitoring steps below - - iter_count += 1 - running_loss += loss.item() - running_loss_list.append(running_loss) - clear() - - print("training on: " + device) - print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) - print("current batch loss: " + str(loss.item)) - print("running loss: " + str(running_loss / iter_count)) - - return net, running_loss_list - print("finished training") - -def stdtrainer(net, criterion, optimizer, dataloader, epochs, batch_size): - - device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - - net = net.to(device) - criterion = criterion.to(device) - optimizer = optimizer.to(device) - trainloader = dataloader - - return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/trueskill.py b/analysis-master/analysis-amd64/build/lib/analysis/trueskill.py deleted file mode 100644 index 116357df..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/trueskill.py +++ /dev/null @@ -1,907 +0,0 @@ -from __future__ import absolute_import - -from itertools import chain -import math - -from six import iteritems -from six.moves import map, range, zip -from six import iterkeys - -import copy -try: - from numbers import Number -except ImportError: - Number = (int, long, float, complex) - -inf = float('inf') - -class Gaussian(object): - #: Precision, the inverse of the variance. - pi = 0 - #: Precision adjusted mean, the precision multiplied by the mean. - tau = 0 - - def __init__(self, mu=None, sigma=None, pi=0, tau=0): - if mu is not None: - if sigma is None: - raise TypeError('sigma argument is needed') - elif sigma == 0: - raise ValueError('sigma**2 should be greater than 0') - pi = sigma ** -2 - tau = pi * mu - self.pi = pi - self.tau = tau - - @property - def mu(self): - return self.pi and self.tau / self.pi - - @property - def sigma(self): - return math.sqrt(1 / self.pi) if self.pi else inf - - def __mul__(self, other): - pi, tau = self.pi + other.pi, self.tau + other.tau - return Gaussian(pi=pi, tau=tau) - - def __truediv__(self, other): - pi, tau = self.pi - other.pi, self.tau - other.tau - return Gaussian(pi=pi, tau=tau) - - __div__ = __truediv__ # for Python 2 - - def __eq__(self, other): - return self.pi == other.pi and self.tau == other.tau - - def __lt__(self, other): - return self.mu < other.mu - - def __le__(self, other): - return self.mu <= other.mu - - def __gt__(self, other): - return self.mu > other.mu - - def __ge__(self, other): - return self.mu >= other.mu - - def __repr__(self): - return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) - - def _repr_latex_(self): - latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) - return '$%s$' % latex - -class Matrix(list): - def __init__(self, src, height=None, width=None): - if callable(src): - f, src = src, {} - size = [height, width] - if not height: - def set_height(height): - size[0] = height - size[0] = set_height - if not width: - def set_width(width): - size[1] = width - size[1] = set_width - try: - for (r, c), val in f(*size): - src[r, c] = val - except TypeError: - raise TypeError('A callable src must return an interable ' - 'which generates a tuple containing ' - 'coordinate and value') - height, width = tuple(size) - if height is None or width is None: - raise TypeError('A callable src must call set_height and ' - 'set_width if the size is non-deterministic') - if isinstance(src, list): - is_number = lambda x: isinstance(x, Number) - unique_col_sizes = set(map(len, src)) - everything_are_number = filter(is_number, sum(src, [])) - if len(unique_col_sizes) != 1 or not everything_are_number: - raise ValueError('src must be a rectangular array of numbers') - two_dimensional_array = src - elif isinstance(src, dict): - if not height or not width: - w = h = 0 - for r, c in iterkeys(src): - if not height: - h = max(h, r + 1) - if not width: - w = max(w, c + 1) - if not height: - height = h - if not width: - width = w - two_dimensional_array = [] - for r in range(height): - row = [] - two_dimensional_array.append(row) - for c in range(width): - row.append(src.get((r, c), 0)) - else: - raise TypeError('src must be a list or dict or callable') - super(Matrix, self).__init__(two_dimensional_array) - - @property - def height(self): - return len(self) - - @property - def width(self): - return len(self[0]) - - def transpose(self): - height, width = self.height, self.width - src = {} - for c in range(width): - for r in range(height): - src[c, r] = self[r][c] - return type(self)(src, height=width, width=height) - - def minor(self, row_n, col_n): - height, width = self.height, self.width - if not (0 <= row_n < height): - raise ValueError('row_n should be between 0 and %d' % height) - elif not (0 <= col_n < width): - raise ValueError('col_n should be between 0 and %d' % width) - two_dimensional_array = [] - for r in range(height): - if r == row_n: - continue - row = [] - two_dimensional_array.append(row) - for c in range(width): - if c == col_n: - continue - row.append(self[r][c]) - return type(self)(two_dimensional_array) - - def determinant(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can calculate a determinant') - tmp, rv = copy.deepcopy(self), 1. - for c in range(width - 1, 0, -1): - pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) - pivot = tmp[r][c] - if not pivot: - return 0. - tmp[r], tmp[c] = tmp[c], tmp[r] - if r != c: - rv = -rv - rv *= pivot - fact = -1. / pivot - for r in range(c): - f = fact * tmp[r][c] - for x in range(c): - tmp[r][x] += f * tmp[c][x] - return rv * tmp[0][0] - - def adjugate(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can be adjugated') - if height == 2: - a, b = self[0][0], self[0][1] - c, d = self[1][0], self[1][1] - return type(self)([[d, -b], [-c, a]]) - src = {} - for r in range(height): - for c in range(width): - sign = -1 if (r + c) % 2 else 1 - src[r, c] = self.minor(r, c).determinant() * sign - return type(self)(src, height, width) - - def inverse(self): - if self.height == self.width == 1: - return type(self)([[1. / self[0][0]]]) - return (1. / self.determinant()) * self.adjugate() - - def __add__(self, other): - height, width = self.height, self.width - if (height, width) != (other.height, other.width): - raise ValueError('Must be same size') - src = {} - for r in range(height): - for c in range(width): - src[r, c] = self[r][c] + other[r][c] - return type(self)(src, height, width) - - def __mul__(self, other): - if self.width != other.height: - raise ValueError('Bad size') - height, width = self.height, other.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = sum(self[r][x] * other[x][c] - for x in range(self.width)) - return type(self)(src, height, width) - - def __rmul__(self, other): - if not isinstance(other, Number): - raise TypeError('The operand should be a number') - height, width = self.height, self.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = other * self[r][c] - return type(self)(src, height, width) - - def __repr__(self): - return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) - - def _repr_latex_(self): - rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] - latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) - return '$%s$' % latex - -def _gen_erfcinv(erfc, math=math): - def erfcinv(y): - """The inverse function of erfc.""" - if y >= 2: - return -100. - elif y <= 0: - return 100. - zero_point = y < 1 - if not zero_point: - y = 2 - y - t = math.sqrt(-2 * math.log(y / 2.)) - x = -0.70711 * \ - ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) - for i in range(2): - err = erfc(x) - y - x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) - return x if zero_point else -x - return erfcinv - -def _gen_ppf(erfc, math=math): - erfcinv = _gen_erfcinv(erfc, math) - def ppf(x, mu=0, sigma=1): - return mu - sigma * math.sqrt(2) * erfcinv(2 * x) - return ppf - -def erfc(x): - z = abs(x) - t = 1. / (1. + z / 2.) - r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( - 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( - 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( - -0.82215223 + t * 0.17087277 - ))) - ))) - ))) - return 2. - r if x < 0 else r - -def cdf(x, mu=0, sigma=1): - return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) - - -def pdf(x, mu=0, sigma=1): - return (1 / math.sqrt(2 * math.pi) * abs(sigma) * - math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) - -ppf = _gen_ppf(erfc) - -def choose_backend(backend): - if backend is None: # fallback - return cdf, pdf, ppf - elif backend == 'mpmath': - try: - import mpmath - except ImportError: - raise ImportError('Install "mpmath" to use this backend') - return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) - elif backend == 'scipy': - try: - from scipy.stats import norm - except ImportError: - raise ImportError('Install "scipy" to use this backend') - return norm.cdf, norm.pdf, norm.ppf - raise ValueError('%r backend is not defined' % backend) - -def available_backends(): - backends = [None] - for backend in ['mpmath', 'scipy']: - try: - __import__(backend) - except ImportError: - continue - backends.append(backend) - return backends - -class Node(object): - - pass - -class Variable(Node, Gaussian): - - def __init__(self): - self.messages = {} - super(Variable, self).__init__() - - def set(self, val): - delta = self.delta(val) - self.pi, self.tau = val.pi, val.tau - return delta - - def delta(self, other): - pi_delta = abs(self.pi - other.pi) - if pi_delta == inf: - return 0. - return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) - - def update_message(self, factor, pi=0, tau=0, message=None): - message = message or Gaussian(pi=pi, tau=tau) - old_message, self[factor] = self[factor], message - return self.set(self / old_message * message) - - def update_value(self, factor, pi=0, tau=0, value=None): - value = value or Gaussian(pi=pi, tau=tau) - old_message = self[factor] - self[factor] = value * old_message / self - return self.set(value) - - def __getitem__(self, factor): - return self.messages[factor] - - def __setitem__(self, factor, message): - self.messages[factor] = message - - def __repr__(self): - args = (type(self).__name__, super(Variable, self).__repr__(), - len(self.messages), '' if len(self.messages) == 1 else 's') - return '<%s %s with %d connection%s>' % args - - -class Factor(Node): - - def __init__(self, variables): - self.vars = variables - for var in variables: - var[self] = Gaussian() - - def down(self): - return 0 - - def up(self): - return 0 - - @property - def var(self): - assert len(self.vars) == 1 - return self.vars[0] - - def __repr__(self): - args = (type(self).__name__, len(self.vars), - '' if len(self.vars) == 1 else 's') - return '<%s with %d connection%s>' % args - - -class PriorFactor(Factor): - - def __init__(self, var, val, dynamic=0): - super(PriorFactor, self).__init__([var]) - self.val = val - self.dynamic = dynamic - - def down(self): - sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) - value = Gaussian(self.val.mu, sigma) - return self.var.update_value(self, value=value) - - -class LikelihoodFactor(Factor): - - def __init__(self, mean_var, value_var, variance): - super(LikelihoodFactor, self).__init__([mean_var, value_var]) - self.mean = mean_var - self.value = value_var - self.variance = variance - - def calc_a(self, var): - return 1. / (1. + self.variance * var.pi) - - def down(self): - # update value. - msg = self.mean / self.mean[self] - a = self.calc_a(msg) - return self.value.update_message(self, a * msg.pi, a * msg.tau) - - def up(self): - # update mean. - msg = self.value / self.value[self] - a = self.calc_a(msg) - return self.mean.update_message(self, a * msg.pi, a * msg.tau) - - -class SumFactor(Factor): - - def __init__(self, sum_var, term_vars, coeffs): - super(SumFactor, self).__init__([sum_var] + term_vars) - self.sum = sum_var - self.terms = term_vars - self.coeffs = coeffs - - def down(self): - vals = self.terms - msgs = [var[self] for var in vals] - return self.update(self.sum, vals, msgs, self.coeffs) - - def up(self, index=0): - coeff = self.coeffs[index] - coeffs = [] - for x, c in enumerate(self.coeffs): - try: - if x == index: - coeffs.append(1. / coeff) - else: - coeffs.append(-c / coeff) - except ZeroDivisionError: - coeffs.append(0.) - vals = self.terms[:] - vals[index] = self.sum - msgs = [var[self] for var in vals] - return self.update(self.terms[index], vals, msgs, coeffs) - - def update(self, var, vals, msgs, coeffs): - pi_inv = 0 - mu = 0 - for val, msg, coeff in zip(vals, msgs, coeffs): - div = val / msg - mu += coeff * div.mu - if pi_inv == inf: - continue - try: - # numpy.float64 handles floating-point error by different way. - # For example, it can just warn RuntimeWarning on n/0 problem - # instead of throwing ZeroDivisionError. So div.pi, the - # denominator has to be a built-in float. - pi_inv += coeff ** 2 / float(div.pi) - except ZeroDivisionError: - pi_inv = inf - pi = 1. / pi_inv - tau = pi * mu - return var.update_message(self, pi, tau) - - -class TruncateFactor(Factor): - - def __init__(self, var, v_func, w_func, draw_margin): - super(TruncateFactor, self).__init__([var]) - self.v_func = v_func - self.w_func = w_func - self.draw_margin = draw_margin - - def up(self): - val = self.var - msg = self.var[self] - div = val / msg - sqrt_pi = math.sqrt(div.pi) - args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) - v = self.v_func(*args) - w = self.w_func(*args) - denom = (1. - w) - pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom - return val.update_value(self, pi, tau) - -#: Default initial mean of ratings. -MU = 25. -#: Default initial standard deviation of ratings. -SIGMA = MU / 3 -#: Default distance that guarantees about 76% chance of winning. -BETA = SIGMA / 2 -#: Default dynamic factor. -TAU = SIGMA / 100 -#: Default draw probability of the game. -DRAW_PROBABILITY = .10 -#: A basis to check reliability of the result. -DELTA = 0.0001 - - -def calc_draw_probability(draw_margin, size, env=None): - if env is None: - env = global_env() - return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 - - -def calc_draw_margin(draw_probability, size, env=None): - if env is None: - env = global_env() - return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta - - -def _team_sizes(rating_groups): - team_sizes = [0] - for group in rating_groups: - team_sizes.append(len(group) + team_sizes[-1]) - del team_sizes[0] - return team_sizes - - -def _floating_point_error(env): - if env.backend == 'mpmath': - msg = 'Set "mpmath.mp.dps" to higher' - else: - msg = 'Cannot calculate correctly, set backend to "mpmath"' - return FloatingPointError(msg) - - -class Rating(Gaussian): - def __init__(self, mu=None, sigma=None): - if isinstance(mu, tuple): - mu, sigma = mu - elif isinstance(mu, Gaussian): - mu, sigma = mu.mu, mu.sigma - if mu is None: - mu = global_env().mu - if sigma is None: - sigma = global_env().sigma - super(Rating, self).__init__(mu, sigma) - - def __int__(self): - return int(self.mu) - - def __long__(self): - return long(self.mu) - - def __float__(self): - return float(self.mu) - - def __iter__(self): - return iter((self.mu, self.sigma)) - - def __repr__(self): - c = type(self) - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) - return '%s(mu=%.3f, sigma=%.3f)' % args - - -class TrueSkill(object): - def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None): - self.mu = mu - self.sigma = sigma - self.beta = beta - self.tau = tau - self.draw_probability = draw_probability - self.backend = backend - if isinstance(backend, tuple): - self.cdf, self.pdf, self.ppf = backend - else: - self.cdf, self.pdf, self.ppf = choose_backend(backend) - - def create_rating(self, mu=None, sigma=None): - if mu is None: - mu = self.mu - if sigma is None: - sigma = self.sigma - return Rating(mu, sigma) - - def v_win(self, diff, draw_margin): - x = diff - draw_margin - denom = self.cdf(x) - return (self.pdf(x) / denom) if denom else -x - - def v_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - numer = self.pdf(b) - self.pdf(a) - return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) - - def w_win(self, diff, draw_margin): - x = diff - draw_margin - v = self.v_win(diff, draw_margin) - w = v * (v + x) - if 0 < w < 1: - return w - raise _floating_point_error(self) - - def w_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - if not denom: - raise _floating_point_error(self) - v = self.v_draw(abs_diff, draw_margin) - return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom - - def validate_rating_groups(self, rating_groups): - # check group sizes - if len(rating_groups) < 2: - raise ValueError('Need multiple rating groups') - elif not all(rating_groups): - raise ValueError('Each group must contain multiple ratings') - # check group types - group_types = set(map(type, rating_groups)) - if len(group_types) != 1: - raise TypeError('All groups should be same type') - elif group_types.pop() is Rating: - raise TypeError('Rating cannot be a rating group') - # normalize rating_groups - if isinstance(rating_groups[0], dict): - dict_rating_groups = rating_groups - rating_groups = [] - keys = [] - for dict_rating_group in dict_rating_groups: - rating_group, key_group = [], [] - for key, rating in iteritems(dict_rating_group): - rating_group.append(rating) - key_group.append(key) - rating_groups.append(tuple(rating_group)) - keys.append(tuple(key_group)) - else: - rating_groups = list(rating_groups) - keys = None - return rating_groups, keys - - def validate_weights(self, weights, rating_groups, keys=None): - if weights is None: - weights = [(1,) * len(g) for g in rating_groups] - elif isinstance(weights, dict): - weights_dict, weights = weights, [] - for x, group in enumerate(rating_groups): - w = [] - weights.append(w) - for y, rating in enumerate(group): - if keys is not None: - y = keys[x][y] - w.append(weights_dict.get((x, y), 1)) - return weights - - def factor_graph_builders(self, rating_groups, ranks, weights): - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - size = len(flatten_ratings) - group_size = len(rating_groups) - # create variables - rating_vars = [Variable() for x in range(size)] - perf_vars = [Variable() for x in range(size)] - team_perf_vars = [Variable() for x in range(group_size)] - team_diff_vars = [Variable() for x in range(group_size - 1)] - team_sizes = _team_sizes(rating_groups) - # layer builders - def build_rating_layer(): - for rating_var, rating in zip(rating_vars, flatten_ratings): - yield PriorFactor(rating_var, rating, self.tau) - def build_perf_layer(): - for rating_var, perf_var in zip(rating_vars, perf_vars): - yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) - def build_team_perf_layer(): - for team, team_perf_var in enumerate(team_perf_vars): - if team > 0: - start = team_sizes[team - 1] - else: - start = 0 - end = team_sizes[team] - child_perf_vars = perf_vars[start:end] - coeffs = flatten_weights[start:end] - yield SumFactor(team_perf_var, child_perf_vars, coeffs) - def build_team_diff_layer(): - for team, team_diff_var in enumerate(team_diff_vars): - yield SumFactor(team_diff_var, - team_perf_vars[team:team + 2], [+1, -1]) - def build_trunc_layer(): - for x, team_diff_var in enumerate(team_diff_vars): - if callable(self.draw_probability): - # dynamic draw probability - team_perf1, team_perf2 = team_perf_vars[x:x + 2] - args = (Rating(team_perf1), Rating(team_perf2), self) - draw_probability = self.draw_probability(*args) - else: - # static draw probability - draw_probability = self.draw_probability - size = sum(map(len, rating_groups[x:x + 2])) - draw_margin = calc_draw_margin(draw_probability, size, self) - if ranks[x] == ranks[x + 1]: # is a tie? - v_func, w_func = self.v_draw, self.w_draw - else: - v_func, w_func = self.v_win, self.w_win - yield TruncateFactor(team_diff_var, - v_func, w_func, draw_margin) - # build layers - return (build_rating_layer, build_perf_layer, build_team_perf_layer, - build_team_diff_layer, build_trunc_layer) - - def run_schedule(self, build_rating_layer, build_perf_layer, - build_team_perf_layer, build_team_diff_layer, - build_trunc_layer, min_delta=DELTA): - if min_delta <= 0: - raise ValueError('min_delta must be greater than 0') - layers = [] - def build(builders): - layers_built = [list(build()) for build in builders] - layers.extend(layers_built) - return layers_built - # gray arrows - layers_built = build([build_rating_layer, - build_perf_layer, - build_team_perf_layer]) - rating_layer, perf_layer, team_perf_layer = layers_built - for f in chain(*layers_built): - f.down() - # arrow #1, #2, #3 - team_diff_layer, trunc_layer = build([build_team_diff_layer, - build_trunc_layer]) - team_diff_len = len(team_diff_layer) - for x in range(10): - if team_diff_len == 1: - # only two teams - team_diff_layer[0].down() - delta = trunc_layer[0].up() - else: - # multiple teams - delta = 0 - for x in range(team_diff_len - 1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(1) # up to right variable - for x in range(team_diff_len - 1, 0, -1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(0) # up to left variable - # repeat until to small update - if delta <= min_delta: - break - # up both ends - team_diff_layer[0].up(0) - team_diff_layer[team_diff_len - 1].up(1) - # up the remainder of the black arrows - for f in team_perf_layer: - for x in range(len(f.vars) - 1): - f.up(x) - for f in perf_layer: - f.up() - return layers - - def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - group_size = len(rating_groups) - if ranks is None: - ranks = range(group_size) - elif len(ranks) != group_size: - raise ValueError('Wrong ranks') - # sort rating groups by rank - by_rank = lambda x: x[1][1] - sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), - key=by_rank) - sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] - for x, (g, r, w) in sorting: - sorted_rating_groups.append(g) - sorted_ranks.append(r) - # make weights to be greater than 0 - sorted_weights.append(max(min_delta, w_) for w_ in w) - # build factor graph - args = (sorted_rating_groups, sorted_ranks, sorted_weights) - builders = self.factor_graph_builders(*args) - args = builders + (min_delta,) - layers = self.run_schedule(*args) - # make result - rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) - transformed_groups = [] - for start, end in zip([0] + team_sizes[:-1], team_sizes): - group = [] - for f in rating_layer[start:end]: - group.append(Rating(float(f.var.mu), float(f.var.sigma))) - transformed_groups.append(tuple(group)) - by_hint = lambda x: x[0] - unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), - key=by_hint) - if keys is None: - return [g for x, g in unsorting] - # restore the structure with input dictionary keys - return [dict(zip(keys[x], g)) for x, g in unsorting] - - def quality(self, rating_groups, weights=None): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - length = len(flatten_ratings) - # a vector of all of the skill means - mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) - # a matrix whose diagonal values are the variances (sigma ** 2) of each - # of the players. - def variance_matrix(height, width): - variances = (r.sigma ** 2 for r in flatten_ratings) - for x, variance in enumerate(variances): - yield (x, x), variance - variance_matrix = Matrix(variance_matrix, length, length) - # the player-team assignment and comparison matrix - def rotated_a_matrix(set_height, set_width): - t = 0 - for r, (cur, _next) in enumerate(zip(rating_groups[:-1], - rating_groups[1:])): - for x in range(t, t + len(cur)): - yield (r, x), flatten_weights[x] - t += 1 - x += 1 - for x in range(x, x + len(_next)): - yield (r, x), -flatten_weights[x] - set_height(r + 1) - set_width(x + 1) - rotated_a_matrix = Matrix(rotated_a_matrix) - a_matrix = rotated_a_matrix.transpose() - # match quality further derivation - _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix - _atsa = rotated_a_matrix * variance_matrix * a_matrix - start = mean_matrix.transpose() * a_matrix - middle = _ata + _atsa - end = rotated_a_matrix * mean_matrix - # make result - e_arg = (-0.5 * start * middle.inverse() * end).determinant() - s_arg = _ata.determinant() / middle.determinant() - return math.exp(e_arg) * math.sqrt(s_arg) - - def expose(self, rating): - k = self.mu / self.sigma - return rating.mu - k * rating.sigma - - def make_as_global(self): - return setup(env=self) - - def __repr__(self): - c = type(self) - if callable(self.draw_probability): - f = self.draw_probability - draw_probability = '.'.join([f.__module__, f.__name__]) - else: - draw_probability = '%.1f%%' % (self.draw_probability * 100) - if self.backend is None: - backend = '' - elif isinstance(self.backend, tuple): - backend = ', backend=...' - else: - backend = ', backend=%r' % self.backend - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, - self.beta, self.tau, draw_probability, backend) - return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' - 'draw_probability=%s%s)' % args) - - -def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): - if env is None: - env = global_env() - ranks = [0, 0 if drawn else 1] - teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) - return teams[0][0], teams[1][0] - - -def quality_1vs1(rating1, rating2, env=None): - if env is None: - env = global_env() - return env.quality([(rating1,), (rating2,)]) - - -def global_env(): - try: - global_env.__trueskill__ - except AttributeError: - # setup the default environment - setup() - return global_env.__trueskill__ - - -def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None, env=None): - if env is None: - env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) - global_env.__trueskill__ = env - return env - - -def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): - return global_env().rate(rating_groups, ranks, weights, min_delta) - - -def quality(rating_groups, weights=None): - return global_env().quality(rating_groups, weights) - - -def expose(rating): - return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/analysis-amd64/build/lib/analysis/visualization.py b/analysis-master/analysis-amd64/build/lib/analysis/visualization.py deleted file mode 100644 index 72358662..00000000 --- a/analysis-master/analysis-amd64/build/lib/analysis/visualization.py +++ /dev/null @@ -1,34 +0,0 @@ -# Titan Robotics Team 2022: Visualization Module -# Written by Arthur Lu & Jacob Levine -# Notes: -# this should be imported as a python module using 'import visualization' -# this should be included in the local directory or environment variable -# fancy -# setup: - -__version__ = "1.0.0.000" - -#changelog should be viewed using print(analysis.__changelog__) -__changelog__ = """changelog: - 1.0.0.000: - - created visualization.py - - added graphloss() - - added imports -""" - -__author__ = ( - "Arthur Lu ," - "Jacob Levine ," - ) - -__all__ = [ - 'graphloss', - ] - -import matplotlib.pyplot as plt - -def graphloss(losses): - - x = range(0, len(losses)) - plt.plot(x, losses) - plt.show() \ No newline at end of file diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.10-py3-none-any.whl b/analysis-master/analysis-amd64/dist/analysis-1.0.0.10-py3-none-any.whl deleted file mode 100644 index a9401521bc24a7d3c97b652f8e3e6fa586d11beb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20742 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 z 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 a_Ak{m)}tonHj$Chk=g~4)(#l_@%BIIzQM%+ diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.10.tar.gz b/analysis-master/analysis-amd64/dist/analysis-1.0.0.10.tar.gz deleted file mode 100644 index a970f1b5e6e730256b88c71d5c83aadbfed08b16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18950 zcmV(?K-a$?iwFo}VQhJGX>%d`82P8oXF$iz~P@*zB|9$JF-_al` z+wmmNkeRW7Mt5~}b#--hb#--|$Jt$#R>y;0AO0KkpM3Jce|`9!pP$oT__=@Sd2n*} z^yK_(aQbxkNq;anADn*@oPP2VeyggE%OLn5jWiqg2l>T~{{Qj2Z;rox`SlMUw*Sx0 z&f5Ba(C-hO{y*vWPd^Fz`~LsUe=m|cp2l^2{Bu%PX^~$9!`|TGgO~9lxdjD_F*pxA8m)rpZl`70YBAjPHUsX&vXm>q)Vy(|jJxiZXa}odmDo z>+>v2<9w1leDFVuMRL4^vKPU1T`#MPCr{>SeZ3m@CdJ}OjUXz7FzgS9PwEo?99MN2 z*U9|u;e+R^`no7D2(DAznzKY9J| z!K>u&tF%n2V}^1Od_Fqqomm(je^KN$WIld#w}i&k$+~_5UH5ic+~yA-e6s)j8~>W} zU#2|ypz{BI^ZyC{+0Xy~+WwECG*9a&>Mien)A?WZ|M2PQ*%?gy{lV$k)4_iJ{}=ba zeE5y~|7>`6ru4u5$tdCG zR+t3oVp)_m=zwt*#KDpkpasj_KCI`?& zRN;ADVgWmG+Lu9ZLghIuCW2X6EDV57$)Q!UOqjO7pOb5NBmEt@vkS~V6@-7KksCwP zY^>i(4;!oqVgpv#Q!M$m z!1gi-r_70$Z(#8U(k+c^l1S6UDb&JjDAs3bS=A(sG@3vfm;v)TC4q&6n#DID_dt?? zWPwgm>D3B)gBO)H0}dMuC1yDN`!@#e7amBWnosSpl&os znBZ0wfLg>$!O-zyJdH^tO|Fv^#w-ecUzH||tbnF-&;63t*ch5#2%<)dhv1x+{y^Ud zv!c9&S*i|_buwAi&KP*}!C}dSoimQBbP~aEXteq1 zPI#W|l*=BV6X?YvOQ$rP7YPi7N!64^Q3NtFiX2D-Pm_>3PwFzp0lS1jfaBy9w6R!x zT>vZ8+P*|BH$RCvrzo9j`krdu4J1Q=`6syuRylX1QCYvGG8##}U$`+Pn(=7nnyvCl zZBH?uHR$^`Df1*Fb?(P6o)d!Ky?kkx82B}pilxKrpI=b1modnizr=TmQ=boGEHgo} zbV1UL=_#pLxHwpECdsoF_!<|k2B<$_8I$C5(!aOEK5v2jHqXOOGZ`3lwj$BJh=EJj z*cVfnhcKxwlSGz5&}U6HC#`b|!ut*+Kh6b)YKovxv8`}AgLySg@@aNwgY)q^{F`E< zK_*_`!Q`}zOPEzbEvvR6=hH$AUA%PwvV3e}(pJ}LLn?6o(xmhTF5-_UN0W9sZ zgtRJ9M3B+rvW)L+Xuj49ST;7KjnD^sNCKyFFKP_p2`oqBiBZ$gvuHtHh9PHCxT!QU zLPj=DrUYImqj2OwOCW7v1E;$f9MWA9p847?Wz$5eO;$gH+KC1Ox8r^Ku93&A%p}7? zY<zsH}$Ez&6lX(*dppRQTU_J8DSyv_NNwTbs zfvfB3Hzd1})}ENkrLAwi`6?b`Y(3#Zp7gPDd>K zh>EIhw`n$=#O1VU$7w2g68HpIxrPZH#zk&C7h_NMOchYvI%?WnXkS`}Kv=WcE+Li(h%+wu+8nNY@B405)o-hlvfOmYiDiR>P7uYZ!dg}sVp zL}omNmR(}#m+(Tj6^09#h)7E0H7+7q)u}*$O-W?jC}4eJC;dLK>l%1TUpXse4^8Yo zGuhW3a#4{@+j345fNfhj*jj6IHa4`5-zKc1k=@DE+-4vqmvMdVYlw&WW1HaT4T3i; z0-R~EOD3LRX^+(KD3KS||?`og;Yx=>M8ERRWXWPtE*=z zTC{;Q1jJ|8x237!v$86y+n8HiTz%415g?^ti~cwtV$qqX&NqlBd56|$NPG_|8U4y|dlcaDvXJNMCIZyF4yU3ke>3$e#l7PqVn zDa{ty*+}pc%QwS#Si@6#*jumHJ~bn#V1iC)<3i^iG;(`cn!T3%fU_c=;siRWZkiuW zayX=T_~78+fSofW!@_J7GK>T5@$x5J)!@s#&`!jkA9O`V(;X)G(=Qo4{5f4zaW9#! zC|^g^LORJPxa5+DhyYNPDxYrfsj7q7hFtEK_Qm+PCox37G*S@b*Pvy)zDBN(}^0W;)`-&sHD%2 zDnv#MQB3S<^9M3U=}c^#&6g^znxrV4*k**!%3`FmuxUVMatuo?xN6@8sPB#Tw|Mw~ zjpn3ehd<*w_$z$u2;s0KQ|-eC(_|LNF&4@-p3Fq*TmegN^#$hQvx>nS?VSOx=;0Bbs7IG}DG$ zvZKDBRg}KrgY%UPXz-E#I^U!L6+X*H7j~Kxg7DY<60iA7yw+bVqD5@9jil8!5@uUf zZI|G82^Q0pOIB(oA{Izv8Qc)le3k@*qzqc*40jkJVNxVDrXM@J3fJ(_w8lDGS_8!VyDL=LRHIu~5J{ zipzPW_@}|-^e=Z0ZH$z25=u};j}KXBiy=_f7_VWhj}V6JT+v=N7QFa3iGclbx$fRw zapUaiFvQo$9vtZ`%wXiJFPHtRPVjj!)H(2{KAUvV&~!!^^yy8Eik&4E%o;k-&4X?* z#lZ)edV0ua7ztX>l{!TX0bZdIXh-s1Z6FGp>qPHZtJ&`D+wsc zs^r20^<|MKQs5e!!aTIP(gmy=Fo(eu>wHli`ttZo(WA1K|7}u~iwUw=+$jJVLPGC` zfx>mAJ08@oG(S|l3&@~(2Ik`n;K$%`FgDv)mv>fsxhrNg$%kub5Kyht$YD?>4vvBb zRGOoTB?yP*q#)^ulV(qvGkTL%d6PslWPPZo8~pEXaMy7G7%lAKCR9ERLj>BJWifhf zhn-$s(9G27QU`S`5Mqn)EBZtB1L%cDOXN(dNLS2TNJoZ^a2#S|C!G%bJH0O3AFQtK){6v+XdKPbsJlX;olwUHS{?fP#6 zZ1DPTLu~Q-Zzu4l*XwDJ`bp>!hD^HTq$@*=OL=GJ5@}afV*o8FxMWfWq{L|grxj5K zr!BJ-PRwb*hO~JS4!c~a*}u3AoV8BWO~+E051?;w?nygaAf?L$b?DwAew#${Eu?H+ zm5U905vBINraqHve<3Rnw-@2K${fFEP}34ur3D|7gt`0nzdW%6WRP&Ic0!lY~&mTpfS3z zM8_NqRoW`vaSQ6B3mvTVk1y!k1(0a1W#(rs3SF@9z#8rh@p=N3rRB1y%=dAXKm(_M zRyX)g)035{WQtoWPr||DLI3C|47I9cfkD00uBh|mWyf7?**TyezB@vI)oKw+ED(@L zk_a#uY>xC^bDN~z%c=2lQ)Hgj%6H7lm9mqCZul~b?~)RlEi4;7@i!EQ>qmiGe>pW1 z5i-odcoyoxL84Vr1M1#Jm2<$?b|J*(!LRih?`}r z@;3R7bkQAs>3fy?HOJ>!XP7ey~zsVI0t*^hd!2mwK5TKR~_fh(lVTJ`f| z*+boqgP>@dlq0H$+rt_c^n47>vR$0J-&UgrgRse~VUZ0Di%){i)_HOpMd2g^NHD8U zQ#M9+L4cR@q^>%?^&8~wm66yz*-*AiITuy3HBY&yHsmoe*7a6NHfuwg?6MUI7vRwZ z@@K$Xza~QI^0Y`Q8p2PoR}O_$MnJUcvGBpxZNg-S%Fvu6TNSa9a%;6nuzOa!i0kWK z^>(|pDR*QTfc2jJqfR(Pe8ORS?*MY|Tc<_e2=r|<*0fek zhA-*0X&Ua7g;v)pYJ|yGf7}8?S#teQ^yiImw9(fG2EOh9!?Fzfz(9r_z-Sw?4@Rs$ zZUZ9B&3+*AW2CPYjHq!S=?HA9CFjbsGWtT)QO<~VrzOoe(^kXP&Ho_ z%J|1XnI9aS^^3(#4ZQ7yjqKuTYr6ZR8uC@RD&9d5SVzUUf3kR-;2ei85D_UbNaho0 zaEbWg@)0OJhXDf=CZBX3Fb4oHUS;(G52Gn8HLn%XgGbVOel(o2%M|Wj4W^k-nb_oX1*8+g%2 zPD7JM>e(Wbe6*>go}D_iHOZiiNe%Bf=Thb{Cpt*j;#kV4MW3?N2ZPow%L#LtIwrhn zzOj5KwlSI(`-eD?4l)4%nuJRNI%;9!M4M0HD$JHdx$Tk0u!?mjQz3*PiM}u+AQZBy*@4;|B zj(G>RlT4&MVFX>&uAM;+qo)D2Q`OBVqeMp^*mJ3zFBF?W8ZsI#sfUucFrVVM1Bc_( zM5g9!d$MWa5P`n@qF&#LN1C+wAWWi8&4)H&jW?6T;;BVhv1zQHnbb>z)cnw-vi06j zkv4L%MeGm2#!WMa#lmK18f#eX8D=e8=PMi&u%h85+!hJBglVPJOb5%4s9F>Sofkrj zYI5BF%wF;R9z~Rw1Ccz@80pjdCSDOuUj8Li4d#btldX3b{w@OT{@n0E1e>zxtkAif z_Oo_l0rIpP4N(6@rH|+$)=qsxkXwy1#0n11^M za>p37`(l-4>AiVloaS+fUC{sc;FP~eN;UaJk^)lXf5#kCx^(uEwnQmF#T@yEIa zD-`@JUGvZ%#We+X4Lcx)-QcXA`PbNtX|KV=EV+rZ$liW+F-?TSy?9i3S{|93wtEx= zpT@9*s$f;yK;Cev|EP#>vBETvCO?Lj<9BVECEA(bIbFmUG-Pye3p{vWY>15Hc8D_L zi@9b2-OZ5TD*W1#C3nG#0_ptc+P~(l?p|5$sHOxvEW7Oe6zZ4$59-RG2Q7 zD~#?0m`utfuEg5U`ozd^nQ>9ncz?}O%&~N^Tvl~N8M{O zRx0qjZGE&H2i~{|vRPm)s$?&(n)2#*zhkqTd@}cJ{uScsog|Os7*VTy{qSO`$ysh* z(UjkZ{Msy{w(n})S4Q1uU+bgY|0A69-~0NXZTCN&4f_Li|IcuEj`#mOefo5N|If$x ziI55ligG7yk>Y%WaG8(lctwWlQ)~A$sfEKlu01*zja34r_afoI9vlUO^ON5B;QSe$ z*BPAl`+*6rvcTa$#upQ;4Q>?3(;LSG43Ejn3e+HzS@|N7`Yu#RcXfpww^h@&7D)`I zMq}JINO$#e>cbt6mB2q9RHrVQQ$}&xaSs*pBA%~ivxtSe(5GmDBVZ?|eR*mH2KJe~ z_9J{W$xBIy#`kGhPTh0{3jg{}oe_2*ycqJUFBL8Q#L&L#^t3E)725y-hR7=R2RZ{Z z?0BhSpTIFm6X8KE5VK#32N7JD-W)e=ogYu4gli9f>BBolZsSCzs0Pcgy~RX^E3;xc zr$$Y`34KWWdx1;j$?eZY7H)4VaD)rT>a33M1gqI!189%#vrxS@Iu3h|KHqe_K=Js? z>lN3=n?TJjf~WP$8>+>Ky-WmX9v z6eSU6r%fC^9<|T`qa!n#J}MYvM)cvXT#G$vF>Y#;5|m+zPr=C0+r9ZCmID;e)o&Yy zJV|hM@T@zD%onoSUnajt}pH(|KE z_yo{+?8|qg;c*{APK4?pfdMj)2Rql^7z^QM3V86K)uBl0(i^bTb2*}UVvgrmUlP%b!N(72tNt}jr%_(ibYvr` zlUCKw(m3{4aQ|NK|F!O2`R&&K!+!t##JT_ZYsE`m z_N=DyvA(i`?lGVXgXuLC!8>pcEAm9#8@pt?;Kti~`+P~wJ6_&v47eTc@ihbBZV9k& zUSp)-*yXpz-fvuT^PF!`M4POA?;61Fn&Ha3jSGj2^1@Xi&Va+%_k=kK?;M%Lt16M4 z6)&L{00<0%SIihK!#f=4zC-MFaZE@sN;=~Z`m;bdD>x0ZVkL_p;V?aBY_vA6&+Tq( zmEcRRLKt;T(#ILbhP-RzF(c-+4r<;Zm^1_xqi&8&yhFg@-69yb1}5g4FUU7W#sx zXZYsJH^@&qs4+QWa+-+VD&x$eLJ5H_REkp`D40Etqge+UlgIWrq9dlDr;qXKu}UHr+{9(oFoa5AObFbg31!}{ybrsI zU=Emy?MBfED_*~XMu-@o2JZmFX!50rVpr90WwhCc=Lc1MD_Z}|-T?pp+kJEQL`Tv z`%&>BM+FlPw`M-bkT7-S{gBuXi9ha;Ks);f84}i_^8NH`IzX};=ZkYJjRC+Q=wBnc z|7yXDY^I^T@D5#&yA3HS`$y2woz@Z=H&8L11tRLxDCl2!&ye*SES8ERygg<6P*i

u#pYqEHC8I(O(ZwWUqNQ32w%I<3n`qn36VJK*B322ZB^$i>#G+%okD zp1nyXfak46W?{Bawyq$2o90vV(uzs37^k`96@KH0rXc|YF260={DuJ$vv@)$gk^Li zBWSt+fwvfk0;&`k{VNR6911qWN6zcsu%R|8g>gp0*;Lx0A71@*GzwCi)ozK=27X#Y zkzKk(PzaG=%VL#J(Lh{P zdP)4^^;h3R0tN?HJ}cE|&`19~@*<~@&*)~}BRPq;fV}$% z-Y{PG<4v-{Zd|Y*Wq;IBws)TS|F{27jgBGW_MYF+|EGW6KN~dse;{e^|MM^Q|9SEK z`}z8?*Nygl;Z1Y&w?TIuj(`BLERx6`zwMa>TCy2!!$;ElJmOW9u~F5aSefwo#DK6aGeN4apdW{A~g$ zz)!OnLwQ4fAT1K}unH#evIa$@+UP2e%=WAZ8?0qjT^5qPRi# zp159SMV+N%(R;p3FncYpY2NABFnyPW=Sj_aS0x|tDqf7P!gs4qg)dLCfQ5gZ;A)l} zb12%m3ryAQ+&4b-`S-|M+_B@#>zK^~{l?|#=y3rpy~VS-cp*%lqaTjW!$Fi%@>f&0 zkq4qvJt$Wx!eoyn?7^%kmK*Le7wuGTLFmi${N>Bn;#y8znkra3UtG>kKiTTfH4J%j z4(c9kbRufxYjF@F+G;nTq}KO)bB|l^-0{XeQ>=AS(PBmpiGOY*s=_-mPr}DOoR=;(^EOU9@>^!h;PX;p~*YQ7;DjG z0&@_9pfb{$=1cz2-ULz7ys>zPR~A|jHMxpL|&jmQLMM0Tr7Wue>a$+fyPTixc%K{PsMFb7-;Wpqb_ zQE5+n>B$a|%U-(s0+uYbK1oupa(JCiv-#}ItczR!8suQ)-dVShAT#ZAFau%M9*p# zK14hXOOdjrtRS3I%Ic6OM&~sT4#~|Y6j6=tk+C(XD%1n3YtV)y%@35g)5=0ffQ-m0 zhJ-U^n3RSFNsZv}!grU4fbg(wI`VFI;Z+<181YzaNC0mzm_{zu0*rm4hLfAi;1eNA z_{E&|7#76&YTxCRhDpt%XvcE{aSoxhB^jFw|O0hLNGXRl1hBNO-^`Qt3}Uv>hlg3k>o^ z%33%0HC_54yiM*ZV-1McfIWPGH%tq~4~u|$HpX*EBF{^DefU6}w)vJ)zrvd@MIp|B z%Vd~iy0o|44M^$fisk>B1bLccG%flN9>U9^;g2mZ%?OL}FQ7Hc#l)ivjH=6#dAp=- zra9UL61rCxUgaH!;@TvPZwzN2rLOMn!Sd+f_P zEw7`+NNp^;=5mlatCNC>jQT`Lhi1W8OltG29RUCcBsf_e3Ud07)%qgKD1m zR=_&cccMO0VSvSLt^qz%Oj7`xSw25_DBK7D0ze*fCOR&zke7*8d=P<`8brW+0^A7x z-?6d4Icb{S+y|3mA124|fk_k*22h2853XczsrVyXwTCv^$=};Cc6uAQTjFc?y!v49 zldxmdyFrJ+`zHZo;etqZ2Qg}aG2b1H&)Y!kj>hM0Xq3sa^q6%-@xzxPh`fJX^iF2~ zltPc+I)}K$3d185Rdn@1o!DkWsshbk>t;QajA6bG{|krOB+mZvkKkqS&wqx2td;-% zZ5V8;(#8G1eOmpu!{Ae{L(e}i__kshnWIJ0SLLJ|T$^F}Z_}v=L+VI*d9FHyGVabb zL&EWe@c!{n`>Tk3e|arp+fx-+jcEcW5YFNe2<>V3z zT+u~cPN+7a4XJ5(mx=nhK5dWvzW|Ly@?}hJvPnrF54Q(1IlR42C)aXQBf-tUdWFWf zNs-sWoU#j;Nl}zjbRA9j1V2vw?v!pZB2LCwQAm_)FahV4+6(R>a;bZ$Gd?O|iott? z>n_bx96}ZcJljrJ+oSrGA5YQiZ-Ew(I-Qt}hPCCx z2kQ&7?4>iG z5vlcNL;HMH7Pp?_tyL-8baC79cko2lx^Y?p2+AkBob=`}Ugd1kH^-UJ5xucsvjz(7 zz5pi3%J^4GKXZ`q?j9>CjcrPA%R3tuH$p3DQSVSh(5DR$cpl#Z0M8GYIv?Hey)3Gv zQP7=XNQ0A!>|kzB^Q=CZn$s?QxigpH%#VjCt^_WZ<<;fns_6$0UCM0pAWkPssNJ6aM-QhPT2=gyk#}Nga?B5Tb{ABC zUx8MF78sC(AS!bdmy;FwPsApQru3{AXc@hs8_Je4F&N6HH#LQd4BrUhu^G-64pN`^iv@aE{t&A|b7} zK+=@BTwmeF_DsN>U_xU=lsDpY{|f$RvOijo=W(6Qxh%sMc#4w_k;66Nx83PWZLWiF2aP;8wf^H1Pg1^2Gfvr6YVNB5pk(U^4JaQ$grx4(;dTcJe{_i7T;wQgf@+EID~AFlppwQN0s7*P^l|) zM8uWt;YVaRhksaPNo)PR^hY1dY}q@IoWsQb4{6r*F8G>s`%64+kZWUi8olzbAuyUa z1d&0g9|_>Z{M4gC|?@t|+&uC%Va!zE=~BnB%xd2ueG@u+k{F>`hogHC600OI0t z22XaNZas=nli)_e?Q?U3A>q<}es72e zwK^2iyeAUFe($r-hQrTPp8nIP&oH*dao~-yz@u&GjONf?|*iBIvAdwoA(VIhilxr9})`ih{c*bwMOC^v?s;tcx^e!3C^(?Zn>Pb zF`LMao5Ft9G=eiAm^Hax^yL=hz_(M6+tk7v!RS^BWG~@k7q2@O;s9MDEhs8L|BCl; zz#32HM{Xf?iu?0dW)J}jms}QMe}%VSaW&Z+o*~_bk}*O$>C+#coIR5%x+(qM$@$a% zXM@kqG{^KmJ3D{&bf9tr4-TH44WB*hpEUt6km32WXJ^m)=c)*>!O7{cfAZNgjm6Vv z&rS!Y&(8fo=rm2g_iQ*EoNDBu>hu1y^Wpiqo!bhjuU^!HAoZ|^r=XCT)-ZPa%txg< zWioB=X0_}+6`e_(dkmRrfcbizdWUyxZJvcB;dC7+R%PUUINnTznuh5cFNC zfc5r>1oX7jPqKTP3je32$ul@Z(h0$YjINTuB>O4PY{`L@3gaacQ13y`yV9pe6#N`i zBEe5xN#LuE6pn<$Bq^K{qG;z(?W zdArK#$@GHXbYhFl3`teRbJ_wn6>!!{C4Aq(H0#Pd8A>Y7%nGdgtN30X%+YYW>zbZ+ zkb{!&ug|lK;Mitiec_MCl`N82ko0T#li5ZqoqdGfb1ebJdLDpxp^ho$Se`vQ}YP<64y!LA8fQYjdKlvIckz^uyHv@p|EMu+7u6R+1mBQDiNEq3NIKJhlkQ7 z#Qs9YM%Kg*E6<#B_$OF}!v82@%cpq6KhOEf=ufN9aSlP`S<_-c`8AP%#N-Cy*i|tG znxRWEoF6fU$Y3vwAQ@*G_0A$PWE)1nki@wFP0o7jTnQ8$?0K`YXU z?NiUA!GPJAFE0VYRpa*0ICCBQc&6k!@B|n?ZDkbUQ*X>OvVxxSgdfUSl@dHHv73Tb z@uvboF0FoVR@py2k5Fg%t)-&Avf?f1y|AX3?%n}-93qmvuu9}3AH8UHgi zjhGIk`eY^}jTf6^=B`Hev1;QHUO_H0co;JCEgDRAd~^|fnatuTiH!q$;c>wYxII%!XQ}&X;Q^s{LU&Upd*9o3; z8e^#V^RrLs7S0JB1P9@RQVSsd<*PRckO4ozBcvuwvGQVr4CH?E{3j#xpx?K%LpPHI zjK^t~)_3yA`5e~KQ25JN&;J_z`09thJpaqL-+%k&{{u+90Yi9Bf#pb&O|Fy4Tf7c4 zb)b|<1(hJ2uf8XoDRkaI?@y7oOsr<$5E{JK4LGn>lHXWnI+%$d$#dc=A$i8vEJpAW zIy-T=jh5fwW`bD_E`p4OEuQ)k8OI44QgJ`ihc%ZD-4=XktPv61>SqpH+5wmT>v@6H?L%xw-;RK;XY@{qE1Xz?IFUmg#x z)IN7+evtJ^KNgD4W`KyAzhW{AJo*prT76QC-`z(tGfL|JF?%)oBv*gviC5I*{sNyf(Kj?dbP zp%hIqVxFP=+HIHDiKE zo^{!jE-guDUy)^aTg;HA5^7EnqXyb;9a2zA;8;Y3gmafB%P1LJsJyx!53Iht{ZM^r zthW$i6PCU7!V^B>4Jch}+xei6hZoU*)bDm+oNQ)~8o#n-ot))n{^58+xXn@tuiw(f zrhB@DD4=om_l;z)8{Qg&4hmy=BxNb_9h)iRjD?@1cQ#r(6|hM>#(*_)nVEgrs-B3{Z)JwVbiJV$us+BqnM20QplF3*qt4a{3q~Gwewxx+ z0%R!EWNt|4Y;KQQeRB*O3oTDkq_15<)wwn1gWYo&-0 zTlj>elXvp@iu*J@k-tTAlV5=g!a*13cpB||hGpI`eO$G#kQH9O5h6ZD_^oSHstWmd zecjat7`-vLQHn}sz{k#_F4^&IkwYwg*VS(1ai9csbbA0u_jn3s$CGV$EhpR5Ie(LK z9G(Qoa)-y;J?Gj)W|NE9DjLysAQA4pOq0?(X;P5Ux0UK0v#ql%#v5+Lc(24+J(@jM zwDDwjN;j~sB$K6ew>za}jZQfcF%22gL7?MKYDU(!(fCNLlzh%+qKxoHyHpOqoyMs` zv@#l9woJ;|I}{@$-;QN<0~-)f?Yb8PH0m~>NXuutozx^^7T>`lK{Ar5ht4=vP70JU z^u}sY)Ha72Zs!{P&~=&4H}>w*1QZL??W_q~yFqt+*itX-hO6u_FSe>T+wp9;X!(G5 z4J(M_OQw<5{L_v(=IPp;=G@X?F=0!v63i5Q?u{x-L$r=dig2Or*;E4f?CM@V6%vpq zUKbl`oEkjPC)cV!#h6gfmjwO-AYHXNUr-2ROHOSp#67BglPqF(U0e)TiI&c!p}bRP z%0chxOeGK6dCt+wR`Ap3$}zDkwh36Lu)A6Q&VSsu_n6sp-98Au!(7)Fax@2Lt#2@QIz!${D=PlbpN3XI!4mD2m!zjpo40)scHXIMAu2}9K|Kf~5MU@Ne#TwL zD>{LfQcTV?`5%5F*xnkpsYD*rrtsw+Ae0Gx2EmP9`ugq$$dw@2Zc6c&m$i2 ztR+-UBT!RA=r-ozu78sAAnKK$-KNa340n(mRg>!kcWHzkyc1R}bn@fcg4$*n8sRPm zlj9>zBXT5gt@rY)_c(pSlBBcC@PzHb-nr+TC6tonp;Y`_fy%{bfkAgp(ZZOEbXXx} z#(*&@L0tJD^pbT=+voj=|&N5<@1qty1VQVwawN-!>X{WsA-HVTJFp z*v{l3rx1ms^|cwPOCFJQ<3xC5(axQ1d*onj4Ot>q@Cc@~M??7kiEou5QPMo>l5s~~ z-$p21fk`9-I9-~C3q7YoC#Zt4{u%T=fdU1vi82MYzg=G`)O?7Xl$0u+S>XEgy0e+c!2J`8D_2)c*dMv z{~ElmrV2F?cnC0tDdDYsMkE*)^>x7Kr#CP>;oY92(F>wmox0e2ZV{uSc`4oL##uah z+mPl;;5ib~x3bXOKz1Z??5&04(ed5dGFCOUSK%qigSC~1F8G+yh&pf=VS*JJe(eTML_i2+L{G^y)N5J8Zq#CS?QEJkf#xHD zLdUKznMMv=l}g~nn_=&#U7xP+C{8%bY`Y`7QCK z-1k4#j{?VWN-7+CuVY0N9rJ6GzD`-`T$wo}bm&Ax_UYh=IL1LTBHQ`etD>wpnaAu9 zGR`rwk9Cl7_}uUJ-uoZipWc7O>-~6cesR2C$zs4B&wRA!AL*s|#1R+yX(Ma8B*>xU zA@kjnoD%Da+w4}`t`a_Meayklq3`Bp7rCEVFG@&hif%J=6Ri*CgM8ej7EPbDJez#; zTh)~qhX;Fg(k_J|b72`y6C*ediV|cnDpP{aJcDHyR%~bx5P5)wL|1El$drd3LoqhN zx8fvfJ3fjQ!QA2p?~Y?`A?n$c+%C_%m?1u={o)zaZGsPuF^fs%`?RQJvLAvD_2y2r z43)DedLdyM+1|6o2}bdz@Y&cF%Le{_!%F@p_j@^Z=EqNdSW(Hg%9G8_^_5zLvnsh! z^gxM-dAVQQjnBbTAe)4C3!^%o`|6{5uezY)kG{up({Kvn2mFkQw|Dyu;yfirs6+e z1JB4Op%|ml%T`d&E@ls;LpSoAt=x#>dPC%__h`4`+zUK1`*k1;$w@vM<~vma9E!%> zv}G}9N0%bEFK;*rZ|C7ShDwF!BgPu?#DXr(AF%75rCTSfYvu2S5n=C$cD~QdH0|5l zVDBr|6D>-ky*pKVQ6R3wtBssdJ?? z{d~}g7Jb?YI=V&aikbp7EqJX61tuR=HILY0Ds!rptUS8>+cL_M2RD}1R?Y5+c3#|~ z^3!fY9;EU9#m`&57tLO^+6*&C9t}^TK#5t6;_~Jd^VSc~HBA4rPSj5q+C{rFI+H3nbVL~&y`EOaFm z5!*HfYC&V%A=}-P%3{d=d~GF+@a}LHVY%6|pf%;CGgu#sPip9|SWr}52qHr9THt=m zP|PGgcfOY2;Mj2EW*gbF%P6q$(NF7Dj95&JNR^=K->(WPgkhCOsl{o_$JMN*SYNR= z^KlM%`%*F@z>3>EwACN8q~xJ`sc^aosTC!Y$hIRbE;KJUghIt6Sl!CC$+cPIOy#QN zTc&#WSb@HM)58&7^juvotag>~BXA!`vS+dAklfou_Igd@_G914RPFWN>&L`gGwE!+ zA5<`V9Y%GOzn0i#rT0R3S?5~t&5Lbpi#ampJjxF~CjYwiz9x;e_|sz{J@<~}<;Rtr z0@}BdZUDt;hP<&n#i9js!64FLko6x*<6+;vC;JbSYL^U%syeBRV`!woL zyL{X%5AgFX6weo)Fpc0S$k|Cf-6B&3mVEq2*fGL@)$!qs{u@g0YN6a{7+z}El{`DG9`~2ohx!kg!4Y>D;!|;iOwQ<-#hG1FZXxAJO=*3styW^TRQ%{dYOV00Zz@ z#`$ieo~JIop}NDY)JV705+2F;prCPuZgM0EsAc^xI4U8UuT0X;+Mn^mYYT7RITzLs zVJVd*mxZQ$y$4wAG?+*qWTs}sqL0-}>|L8O!v_rEtpr4tf&6_K?_3IpLXp-|csKLj z8e`|%c8VfR=Gqc7%RkJ+CCDa`Tl!*81e4!e1ukQ;DDP%zI_7+ugxklmkqon>SI+=7 zpW7vwa_^-iJ%B6M%K9AL`;yEj*XGWDSz+3VwH;=>A}L*Ep*gH~g3l1IAIn8ja84?4 z)GVJsk!S`=oW7bKFP?RzT_CLN>HK^xzhn2_U(!VqeJga|mi{P_WCsD0tQ`M z7b-eRe8s>(KhLZ&WCJ9wA0%`m1#4K>LAbb9?v@(E;Z)6!73A+$u&s$Q7J)Y9%KiaK zQ*l+wzBga*NONNo{xL|+;{ua2s}^ISB@Zr_{W8PxV0Q>|1M#jG8L9ZY1PSO zbGq_(HY>0aQ+WRcO{DvH>+$a~BAMXigDEu{v%(tBhDb9QGMDKg~wg zj}yvQZRW4iJAFOYOwRu2ZpHn;U|JshN^ni`13l;B#q4Qg`?#u*v!}(lGR55WM?&tJ z#1>k{p>zToJfOdLc#&^d93ZrtpsNAY8}iFz@(?i9)h5q|#vZkyLzV^sgdF~42T*<) zbRm!)KEH2}$I8A#Ztz5zr|x4E;0D9djV(2Y4WhtSIrXU+fQisK8?yX;4BVyyr^B;J zXmOxfD!YFPT#-nnwIKmLt(hSJyc~G>U;0M!=>6@RVnatgHV~G>giTA4oqgO%?e3P^ z8JRG%(7J%$-Yfk9#GK+a&vM_h9V`H@^ly)KUWCATg>shoS@r$j;_xD1{XA8#PL*E5 zd792z)ZfXlMt6h4e0qJL2mpKUyR8U>-^Tc>G!+OjEaphz(&$WTN_qz#i_cdib+vKy z5D8u8sNAkQ#~y6QyR0W_@$3!(uQr}?Q9%jpKM$haHcMx%M;e=HUFlJ#4JLMZQ>}mU z%=YC=bc9!ZPLn29n0oZIG>o`;j=X*1=1;gjfKR69d3(rO(yn8Rf*-malznsF$V!OB zD26_%JB$4ZltI(8T-gIAbaf!v4jiAha(Xv*w|PS)SC>S;-xdB+vLPn@yY2KDFa;d^ TzeDcb0vMeIoNF0yj12zfuq^m3+qP}nwr$(CZQC~YvTfV8t-bdA=V9iax%JS|(Gl5CU0qq3r6>ae ziV6S#AOYE}VT%840KosV{1^XoYGiL@>*ZqQ!eC%vWpCwbU_kHa1p-j~|1cWpv5F+q zk!TK>06>`e|AqN~!T(`yd2JH5+1vJEUIZK~2JpDtbhnpZm^^wdbL?SuST~F5x?n(~ z1Z7$!uzIDXS&o0deBcd;siyKvH+Mn1mx)Opojm6by;8ChCK$vdY@SqF?7E0iPB6r2 zn)U0~Gfs52l%Cw=rb8$}y|zyLCi#C3NS-|OUGZur-vs;4cGc!55%@j{o&s2H+D;(c zRaTrgTFOn1kP8kfx`7dosxPN=ZLz4T+Ya%jq0mB;-xl=P*s4yTOEJWHDbB4Si@g6n zLkYU6zi|PmtuKsAZJD)`dk$!?vHc0*C~EttJXJJm?6kBqKZsEa(G&q@eF!V5u(fHiO1=r!A(XrJL+KEyjN?4!`vs zA)nj+3idH~)`C=-FIgN^G+1>>7Io=8h~p9}!^f05V5lS8`K$rCQ4Y-n zkV#NUP^W6kys!a!;4kS!WX+!Pfn7WYY6wF{m)qGB*+=vf{_f!ReeVU})GsbdrFyJ? z%Yi%SlFyHNtr3P{Y3;zycoV9*3_`P19#wTY@cP~P>y%lAYdt{KLg!QYa*Z6MbY0_i zD)?9B)PhYBkB9K|B+9|tM>B%P*JtX-YWNd&!wq`38vMew;eU!PCq6gg9K5e8KgQxxJ<8I!d_@m z#&cq74lbKPYxLHXbb5=yQYa%4k0zRqi_$a~Sy3ezM_N51I3i|i35S9F;4sh_upHxU zF@$a+X8qPYhQRA2fVJ5a6!wX+PO6xPU}QWZyB`F*HnVylo3+jfttSnT&H2HCu46Ai z5zX$8;Ieg&idAwmje6i*u}Bo%2@+AL~APaofaLOUjC6{>+r!} z?x8!)HIE?ppQm<*f9_C-hJTfNB3;U zY&2$>AIHZHWWfsge9soOM@cb?Ylr6s1yfe@lpEO&7eQdxql{x*%xl>pWt&qJK(qp1z!WE9ikOTrt`-LxFzV;0gAf=gZXfM}#e?MfcmrI2QbRPt z8f9~Z>CwYDnSM)wslPeLOs(rANNNhl+Ob|C!t{CWS@xkN-l~i-y@mC|KB1{YyWhbZ z=Q)Dxq1D(t{F>Z|W9p@xC1F|67Z6iYDkYvkR!!gfkp{nAfeYiLbm7O2EYNtkVyaug z>GF;zBYstx$g{wV`eXanx;y|+y|!;knqp;I5dPqd;VYoZW66`1(7;Hju2Wom9}C7S zHk$nG%-BhIebT|D)q1#$g;=GXtS;)<5sA|2LZ`a$2_fQPi7c{g)8J_VB~Gf+G(ukk z>Vxi7juRfko@DF4v;`F8woh+ZL-Mqup*RFF9(hc6#`HJ?%8Ie9W}gR`GZT&};vFGn zFfoY&>B2ZzGiLcBE%9$=UW_JM07h~;q(-D~%oDX6dK0^I%DcunXVlo&x@ak7$2Ps{ z+F%;?X(dB*PlQA7NyRkIu{Qwdu<_x%FKS+t4p@i1P|$#ksoZF=Rl(TJLSj=6Ae!{& z)LWi+u1*UexJC(*rcYhVOUge295dIqB1k%I^lmx3^1qmyT6cv`b^h_qW= zF=`qxy~5LomWn=(x+MBZ`VAVUT+1$b*2BHZxOW-E?4KNy-nLyl!!q^kglB;9xW%xhtK?vn5xyxPmQvqF&Ko!5o}V z8TIiBswJ$<6sE%_V7TnM+Pc%Qf;K6t1p_!5Phg1dW^j)ve-nx||^KbW` zAyG2n@U~Ay&b%5^J~5FCtlT2ZZy)UvcH@cLigK6%5a$De)(D)mmii~E79VD^dTM@>#M^$R8gyJt!$t*OG@ zc85fZAzTJ5)#0J)#69e0llDUC<~yKoh(ktOK_vNj@boI$W3AR7tm}@j^`<&{-9VCL z_2N6mDAl5P07$w_ijzqYT^T3bJxxH}LLlXuPcwo);}A-~yzb+x8ok|=ueIqMpv~Y^ zaaG%(IcXtYp9QUCvMU9r^L9YCt+hKE2^q=%isk1SX;=ach>cRa2M$6E#9Z5;EFPIO zQ3fccG5Ppn><08@o;;eQo>&JgwM=U{`UknT8{!A$6@~3W^wSheiiQFlY_TiZMq&x? zQ=A1-Cys0+k~+afEDv^>Sw=|Xv;N}jf*-k4(kkH19|5wdd!t?3&)(3ar-?S)J5gGb-L%pxC2*)u z;4h~GRmlXlOUEwg;F_Ho=aRL;?yL9%Yx_b4BHq$WT!La~B@&MOr5Yv)I4iWG2Ht-6 zHE_j0FO|a%);|&-Fog`7L4q8P()gp;vlC7+qmp&j~Kc!?#C_EHjsv zvWsV47ik`ReWr9Spr;mF+6Q04mYt9MsH?cxs4xpuh-Hc-aO0sbG?MTINa!WQ9Q65A zpLtS%VERk1(>Fo}bH#N^GvbcYnWRyt-JF`H%^#NC(D;pw=IEfoZcd<&ZdygM(3}|& z7b9i$>hJ9Cjt;>R4+yb%IWooS$B9dbX_u#<1Wt#aT?n!`vD~?CA~hZjBh1ast&|~< zG#~5DHaui&6U<;(mgNM{wcqTJJ91~ThimQV*=x9PLXa@e)pD>wYOGCcp(A3e7-pM@#v+z)!i20s+Yn)odIZ181UAm>nC1=<~B0dyuEvMNxuqW|$Vv3a5P(>dXBT|_m> z?G{r$jg+zPnDK$r(h__FTCf0sPe@-yW?rCbE3Cpt-VAQumQCWhQLGy z(YmZTicRGqrPu)9o~r?UoJ1zdwlVLZWY0+%lhI&g{2yRks-OM=(fE>$QuQJ7v!gbu zMOC?H$ktk;Zq&B1A@9Df9$%y|&d9eBZ*NW98bkQV*aTffKx`|F#f+{Q;%KSYI7wwd z=i;E)zDf;CuUtIP(u%j4f6C5P_Eh3L5XPf1`iKSYe2i*35RvLO`;gy0G`Q(`;7u;? zV`uFUZTfN1G{xlA@#@oH2q{)DB?78=?XPi*LjJ6=P?-R<`{g7EDD@XL(*_~qu4y8s zAd%IXzm8W3gkVV?x25xY%XX&27=FA5%mnT@20?zMRmqzYs$X zKTTSEp0z-YmiaPS?F8O5~t!NRs@ zR7Aajco3$%lt1#I#q!I@r{#}@vi=an>;^NZei5^quOeJW#|iU|25oieE-#ysYE-*x6ziBx5PcE z7sn=z12tC!x_8g#&+4T_@aWZ3c?zi&Qrdjp|E^&*3O6%A^~!Wv^DL8ABA2KH7a=Hj z{ZdMfqDMa-#!Smp7R#nv^;9p)_q{6G^<(o5G=DYpBw8GXh#=5BVxMQpAYo2(gYJ!= z8p7oh=jb|SR<|*~WXd+Fx|0M^TAt09ewR$;YLr!n0|0ju$}N| z6|IlT;Z4&RaWaUNteEhr z%m``rhIGW&=e5EL;^bLHoCXBj{0R(s z7D+%}I&jEG3G1l2=d1zk0{&oW;BFxu=Paq;K)XCCQI7x5Ft0{N(-XgK>ecxdiNrsc zdwo7IY;e*wljFj6x9po8 SC5o|Df~Wy~-+JsXFvGA! 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 Lk)@Vo@cZsRs#&%h diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.11.tar.gz b/analysis-master/analysis-amd64/dist/analysis-1.0.0.11.tar.gz deleted file mode 100644 index 2e9aa30eeabf42fb316d8dbb5b93d9b1f72dd957..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19444 zcmV(|K+(S+iwFn~f|6bW|72-%bT46UVQhJGX>%3MDvLt^cepkKCHf>LD8#hmqcJKT8c==EgEU}?Tm85K`d-s2TX9nLO zD9K5hZBM1$#v}*~27|$1Fc=Iv$DQLZUdGG+V)2+2pZq4jF8?+C?RHO2^)sgTdZ%ao zPlDwqAK|wstGIybKXfDiAiw^TU{)nFHt3x_IX^#p`sD1ibMfS)f6_bK`(*d~AODN9 zI9-)VdDQE4;eWl}@ozK!FD@>c$G`jBJ2~r}e-fO1%<*63d9^Ozrtg2y@!#3W|DF7I z<^TEl`P2USPX7NX<^Rj?zB&5#>gyjqu>3zeYaIU<=kEAFJ@5BE3A!J1{QpVi{|i>d zFD96Z zwd{SLj969z2H#x$7<|JrR>W!WaxqMkQ6RnrcPE|h-g8!tiez39HGaJM?&^oXU+uk2 z;|iKK()Td^)l6|1gKzsRgfBWyNNg zM5x7nD@8T^w~d#~6pi=<%X5r=UZd_FkooSVoUeVJz!WIcMbnnSZHwych!!`_bb z`)qGF|L^Aib@Tt(#l_R7XD7S)|4%vp%ar2}I{qKF{yRH4?d{fof134Q|GX~$Pj>SE zqsafhA^&@Q(7RF1*~!J7{6CJOBulC&>daTa?fS1S|1VBYE_Um`o&4X)|BovFQU5?DS+O|3Aj>kuaOR%7=NCj7l<_1!x|-44%hT96XZ- zz8Cp;k+M(s9tD3dlB!}E8u*?S)pSt=-!Fo{1phl8<-_26c9&$7?JBQWd5KT(tEK>= zoaT#k91K~I%;tGfv2hTWK^)AvDQreH1d9@lUk5ykGB6%A0Ln(`Vhn(i3_h_S%|~$> zjFWhllt7(C= z<2V@QvpE?TMe)(1C=lw9WuqyY*Mj_37LO}oF+Dj6P{5YKZCqBYz@jCq7IUD?r+ZO! z$8D=o6byp>UI+dM3V8SFK7M@^jHYomVQD@wntYeAdq9ShXs{}v*8=>nI3HUn#s@y>(kgpv~(q!~D@3(`hSg`VKlBU7!A{$`?%V3d#-Vlt? zfEm1CRRKKYzYTp;5}> z3~4qOoaf?r)|B0k(y5iboW)2IK&-?D6>L(l5<3;&puPf{96%G%MvNwJ2iS?zz7)6w zD$in|?ro9J41i9_zE!edoVLLCqba(hWH-x6y_=2Q&YUo)tAl0Fn zobo9Q^SCfuo8=X}R*ND-&V*rI4QOqZoD7pRsa6QlV8VP+fs7`?mLQGhbEGWHRwdMp8(k*2MG2s0@mw%;JR6Q<5=o;e zOJL06gyO5xgpuaZbl!76Clxk^rY(Y~(c(jJLOV-9-w3m!9ERDi3fPj37L_vw-u&?V zhf9C}^`|t7p-&2&)g=GeMD)ster;S-48M0Bwr?@l!2wCG+;>;#Zxh}TKb?(rn zMAn}HHA!B^OD~MT0b&pFcs#aYVCM|uG8siM9BOSo-3rf>t#a7|bOgPar^%Rx^UR!> zO<5F0AS0v5fzSW%Vw$;>a-%^!t9ann)vd|XN2H)S66n4 zo?mk*xpa8_(+etg6@#q#TfAaUeLjq_4Cz{JkTgSjVkH+Y4%X{Q@}vR2#)Ve{)E{vf z!?FqK^_yW|G{AnFWnrtH42*iaAkjUGflHUz7h{-*FsUjSlVuR}d7aHkn;nDjUV-Gt zxxi3)5fm!SB~E8BuO=)Trz;zrkJsVf1RD)9ak_%ZX&x6atAbiqZbHtdg%~=1;{fFH zv5rYoU1v3^!1K>Xv7O1Cwa1H*ErmV(G-IfG!nz9AD>xv`zL$V)x|KLh;ZPl$bFrF4 zI#nKyVPQC8P8)oD;+>IfMKx^wKOV+AZ2+ir5|e^H{h8}dMx}gNgObje$@TY$6DDjq zYCmaTeeqQT`G%5h6nb867K)z)j4E{iOU9IuRt1U(GJ0GT@ydqgYrTMFV?){qeXxfl za4PRbjX^wuWo|q&YU+CyEla*wG$~wH8aYBnHl9ogeEp5Wkq=q|X#?vy-NnHn-6i2U zU)!Z@nn<Ss_p(GcNwyieaX@|cyGWS9w;Dl#9egXzhx+^LMrG=+JZU*|%l8MJAg z6OXERk)|t|mpK4^+~NW2k&n*0iY-TMUKs;d*VC;jV~aW0N%-y(mkK7^bZd&4q&=wX zDZ`;upl%O9P%SR)l4stM*j0e*5C#y;5~F;^oMNXwIiy$YQh^G>;vfBXFe@2|fm!Kb z?Jlc8|H8_%T-&!8w<&>u1t@Hs;NP+WBDA?_%CZi0YXhjv;yJq3g`#0Cm@|LQ3$Y4X zWQ>iKbhlY8FHr91!=G^lL5PII7|H`$*qfjO)fXSIuAPh323o#Sp&rFUoM~~fKV?Yd{>Ulee*1Te=A!nyUF8qj!s%`g4Iv&NvxNgU(D|r(52w1s-2_42o zW<2LZV4_hNfe1yTp(uxI3%wc|WRmRw~gqgX`r8J6Ki3xRe^lL;Kbc0F6wkMDY ziQ71_ZWL6RiG^ux+zJJI>>7qVbA^7p!1W#4WyzK#EuzJ?6=Zpa@&;G`rmZmK3nAII zwlgnL1+maZH)aj%5XHI%55)AJAenQ(MQ;#Y|>G=>I|*d(p-B#mZ;$h4M+S+ z+Rn5&!EQRT*;5ar-NtBcmJ^oHkFmROzJo~>Mgy6R>&!{3LW}skn`?`ZE-CG{e1ml+ zl(80rJlm;vAix~M?qMjA-9x&QjuN-9SFw!9jHl4DOZ5E`Ug);MZ~+q$Nr|k&MI={s zN)TXU5*c?2SeMvIzxV9AdS23(&I;K>6T8n$_O*vxl&9mSoMQ&CZ7T=2*4mtn4Xxt0 zjO%D*cQQ4%TM(1;xSIMJ;$i;SCiq2-;5CZ?&(zx{lUHGmvuR`uf)LhHKq9o{7-OTg z0s47^al{*(F|MYT;Oefo9>Ti?D(bhR%8=P+NH;O^%YBV8whVpp> z<$=PZ2*zr0n^f)Kewu(Z!n8#S><4UhpQG|nqVbLPnq*E^6gjt&)b<%%@CXW}rznd! zOCcB4t%%*mWtyjuYNF=vU?39$K(FG-_bgD&= zL(DgN>f!d-fAoVv(6bm9)S4_a*8G2=z$?6J1ueBzR1i*6pb zN#_&lRkobNl*;E*&mIV`)o02;01d zY3|KNCC~CVhh+^e&w}!oMT`@BGY=FEc_iX%ryWZws~)id^tT1DZJ&B}22lN^sU6*X zXicNNer#;qxsMim1GrB_2-lxzDQXX3`$Y_8JEQG&vW z+l=s8S&VcRZW@r89K%uruG)72>U*R8Ek51jMsrfK!=G>+{1ra7gm9RXsrJ*oF}n@q z7z^bZPi7)@E{7$z`T}z?yGPYyaB$GVDxnyDGei!)Mtlu9019;IF+?RHgv=dsODjTA zuGKOWTanforFp5$t70cXL*+dWY8*@xCCDzhr|<=IK}a@3m_i0fiWYC4Au&R-)esjT zY|D`aH5R=1*NK4ra=mP?Zg}JD=`h6C$Q~TYZJ5HyS6{BXH?83F zps#b_PklD&prPrEFzA!J7!^B9ESNR4pqqQ`V2pzgGRfg5A&7xGPHt~E*HB&eZh%`V zz*^^k(lCovq0ZSd+af4y$qStmAhjF6o!m%3EG^lk2kKRxF)473O<_K?+R_EA9dHhV zDc1R-I`r!3x#&?@%l|Pc%8LoISllT98A3wuhJnI$qdOkdt|U89ybH*nc?Ra=OW?=g zaWFL7R~0L(y}T=KX_60@&>)~%tCqu{OdK2rHK;U4<#P}Y^HEOH6DQ4%G-q^1i{g$& zx5)ZXPdoTO?O@e%0T?ap;3iZy4nqXmxlLpA1`k`EDyNyL)us+=Ss=s~;aBvB><7>b zwU)@4RFN*9G?0!A8{sg-#*SJo_;>tJLV}_W17ssPq0gbNaCq2M*YBCUW)Pw#vl~d=aH~zNeR0hBHw^9Xva75m2wl ztEXK#BeVmRB4>hDz2{+lRVpxL#pWeUr11X^F60Vin@Dk3Ls{%L&YC=_?SVGR*=+<} zqhMY4E-!Dq5H)Kz)F-m}v2w})%h<>{3P5diVTqPG7|NtkyyF(sM;AI+=^tOvHwz%q zTFcBY7q`NuqStdkMRd%<<8ioO#8(-hLe1xSX}%9j2F)G=vhCnIo620xVq@HNISzY| zd)>psFw{zv1qwA*yRz2tRm-PT+PR>YzB@#qArzm+zRBSIpfoY$D9lc*P1Jfv|sc#NSXvc^U*R4#n6^MCeeznFOz`dKDdRfsgoG zc)cX~Ob+Na>Y#gjKNjN~ry(BVhoUl$eOuOzSc*pzJ0!Jszr@2LB`q~vg2CX|A8FfA zuCvqw`9GFH!aBkAbvRo<_au{945Os!1>3n@#v4nv03s1@o~_KwDUZ&);GUqu*wM5I z0v@%%MYkhF5rAL`0phYQmK4t@D^%wdAtb0z>nHKpb49(2fqs50d#~N{AS{|JrIIS< z;{uHr`cj5w`8H|;Z_7cAk+__x*?7a2C3B)EWA{-Mj#7XGdcrv2=FK)t3B`m}&gc^O zK<-WviT#*0W!scQQ00`L9Ggc|1)azb?x zo`;>HFYG)5qE(ND54LU{CRivm zOxXJ41{lgV?1!R1uZ5${$UZRebqg4lt=R_#GHd}xTcv$4V)bzo5MjXf1CbvaZJh{& z@1})(n#PSXItNk?p0vr|VX*)9h;lrBypP{0t=DbiVYeuOoP(1tynzxsAeY=8@diIS z&!TaHW^P#d4ff+jl>>x3Rt)o!4Z3aNk3l9uHbC7M_fqGpXi4Z{Wk18LBEH1w1U;Xp zGXY7rm^*hGi$u|c&UY`UWx7qc!cs51dAR1x(4~sEjB@Y<**;v-iAQQCPO+#T`dW!g zDo?U%HSSgLC%g5KZoW!7Kr(-jv>^2G!Jr44xaD)pqzEl#4MVRnWTA}6BNEMgBbEtb zi9CX0k@yfftR|8znXs1Q=A&4%8_0pXH;%|f9Q#G` z6z>&aKj_m+9J!t6_f{T<+_cncW}z{4BIwE3n$F8FpM?&p=Bq*({}?Fq2M1^UVsTRq zZ#!WvySRH-y8ELF@|C#G-a-&qhtGKbr16m99EVO75h*Z8<`Zaef%xHq6ev7_0Rt2! zFLyp*_5ohJNUME5jK;8dohqUSkEA{Rus`NbShye78;k1fEk-evO9PM&)loT$Q}XmT_R@ZbZ0loPMTO3HSMt%|Ubm#nc!7uW9>RU~ z)d6RrcjQCKT*~1_&`NC%8YD6}08l(r-H(DxFu=Y&!OBTQu^*%%1LAtSFL@60DNfvX zI8n`RY9hC1pf#K$njpWZ*LUKbIz2uZ^Qc4mp-o`p%_OyWYS3hCTB~PH^0`5B{!pj3 z^=t0efU~$gEXDP{8MN~gmp?(&hng& zDxt}i9d(~>#gM;0QHJD+hDe{zH}QgKa`m@RHJCrtn{2)R0R&sIg~IBkX{34 z19>3NR)lM^ObHGVF3;O=grww`T&Qplp}~nDEjI{6{B#+b$qodO`IM!IDnyV$%YiT? zN`rOJr0AfJwrrDfLSjj)wJ5A?-$%Vhk_`^>yW+rO<=!#ZZ^B$J3q}V$J

3a7Qq& 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 LZ?kZD0Js4F(KkD> diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl b/analysis-master/analysis-amd64/dist/analysis-1.0.0.12-py3-none-any.whl deleted file mode 100644 index 60e0b51c7e0824323d9fea32c844cadcaeb7528a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32026 zcmZ6xQ;;S~w5?mV`Il{W*>-i=wr$(CZQHhO+cvv!)_%D6>^SowBO@c`Tjm@iV|?<` zpkQb~KtNDHnav^c|80Ok{%85G{^!)d*1+1s$-;?2PtU^E!dXv`-rfTgNdEutkt)xX z*UAin3nc&o+Kc&rdj7xW|9WnCt>TVG6ZYWW`R_;j;Z3a2C9htL=(&b<`Td<-jn?@n z`VD28$5V&#B8xYUr_B*R?$lJi$+HqoU7H#btU;ozUDec7)Yh6NJ_tXl>@={xe1H~)~QbwGDxZ7iqIFDsjQ-ZeZTRA|7;%HOih{lg!h_n>a2$KF&75ythTZwp6OI^e}gXBWY z(6(?=TJeg!)HDTr`r`;@oq?ePq5p(}|4?48s4Akdd*57>DXh$j%wYFeO8wStU4*lG zY)u!j*pG(7EnqzpFkWf<#^@OS7pngf*L>`mfUpSdEqECKIBd)+6KgwYnp= zG6sKkvzxlEaOC>o2nxGBob7LO_dr2ln-09U=Wh>b$3cvLt|UrLVbq85Qyjzn z0i;5w(Mc-i%4)$o`B|HP)Un3Su?db9{!HR~r67cOR_)&eS@lJfbtOikcEF%VbMy8t$(qG-;F+1fWZia>N^AmW?lz#J)D|p9_-;*ZVNXVr!Y&e*Uwi#()h!i z`__J_2LKMOrxJ9d#zTRy7D_pPJ9h-%uLa+$s^ylYc?u8Qo% zHY+yjs+%&NL;InpP{;Wyvt-SBDhx=VvYtCi%9oo=O&BYEJfPEj2;j_*_J2K=OI3xx+ldRdUAPwqo^gS^e z2Fsm;$45?m-#6BuGglWV&AI~1=c2j=6q&VRFIHw=3>r$ENpyeWl&8kO^9~mLjGl_|Z?$0=3}lZTg(g zGC*D=NPNoA(wA;uPE~Z{zSdqFDXekh6mPQC*BAKY zpS<75K@z{-*s0=<9(LYpS&PRw6|hYyf`$+G6_Ta>c#G6?DiflKr5)5N)ogJS;DrLD z=K7B{svjxfn3zzL{W#s5aBX|Xy|?sc=H5XLLaH^5>oYUC8f*dQiGz|It8PI3teOmw z{HRBeWH40rfZR?*dN-*$CXcs8eh

x#v7H(S=Q=4L*Ouq%iyj9OET3NC4 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 diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz b/analysis-master/analysis-amd64/dist/analysis-1.0.0.12.tar.gz deleted file mode 100644 index 1ba274315415d3096304b963298c369f7d3b44c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21001 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 diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.8-py3-none-any.whl b/analysis-master/analysis-amd64/dist/analysis-1.0.0.8-py3-none-any.whl deleted file mode 100644 index 83d5cb968fdb50f333025e6373172553d91df843..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20459 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 Z{&)Yo|K0!Ye;@Gg{{uiF_T2zz0RRUFw=Vzy diff --git a/analysis-master/analysis-amd64/dist/analysis-1.0.0.9-py3-none-any.whl b/analysis-master/analysis-amd64/dist/analysis-1.0.0.9-py3-none-any.whl deleted file mode 100644 index 24f78782f4be9579653b2260cea98482394d1c8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20332 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 Date: Fri, 1 May 2020 16:07:57 -0500 Subject: [PATCH 3/5] fixes --- analysis-master/analysis.egg-info/PKG-INFO | 14 + analysis-master/analysis.egg-info/SOURCES.txt | 15 + .../analysis.egg-info/dependency_links.txt | 1 + .../analysis.egg-info/requires.txt | 6 + .../analysis.egg-info/top_level.txt | 1 + analysis-master/build.sh | 1 + .../build/lib/analysis/__init__.py | 0 .../build/lib/analysis/analysis.py | 923 ++++++++++++++++++ analysis-master/build/lib/analysis/glicko2.py | 99 ++ .../build/lib/analysis/metrics/__init__.py | 0 .../build/lib/analysis/metrics/elo.py | 7 + .../build/lib/analysis/metrics/glicko2.py | 99 ++ .../build/lib/analysis/metrics/trueskill.py | 907 +++++++++++++++++ .../build/lib/analysis/regression.py | 220 +++++ .../build/lib/analysis/titanlearn.py | 122 +++ .../build/lib/analysis/trueskill.py | 907 +++++++++++++++++ .../build/lib/analysis/visualization.py | 34 + .../dist/analysis-1.0.0.10-py3-none-any.whl | Bin 0 -> 20742 bytes analysis-master/dist/analysis-1.0.0.10.tar.gz | Bin 0 -> 18950 bytes .../dist/analysis-1.0.0.11-py3-none-any.whl | Bin 0 -> 20782 bytes analysis-master/dist/analysis-1.0.0.11.tar.gz | Bin 0 -> 19444 bytes .../dist/analysis-1.0.0.12-py3-none-any.whl | Bin 0 -> 32026 bytes analysis-master/dist/analysis-1.0.0.12.tar.gz | Bin 0 -> 21001 bytes .../dist/analysis-1.0.0.8-py3-none-any.whl | Bin 0 -> 20459 bytes analysis-master/dist/analysis-1.0.0.8.tar.gz | Bin 0 -> 18782 bytes .../dist/analysis-1.0.0.9-py3-none-any.whl | Bin 0 -> 20332 bytes analysis-master/dist/analysis-1.0.0.9.tar.gz | Bin 0 -> 19036 bytes analysis-master/docker/Dockerfile | 5 + analysis-master/docker/start-docker.sh | 3 + analysis-master/requirements.txt | 6 + analysis-master/setup.py | 26 + 31 files changed, 3396 insertions(+) create mode 100644 analysis-master/analysis.egg-info/PKG-INFO create mode 100644 analysis-master/analysis.egg-info/SOURCES.txt create mode 100644 analysis-master/analysis.egg-info/dependency_links.txt create mode 100644 analysis-master/analysis.egg-info/requires.txt create mode 100644 analysis-master/analysis.egg-info/top_level.txt create mode 100644 analysis-master/build.sh create mode 100644 analysis-master/build/lib/analysis/__init__.py create mode 100644 analysis-master/build/lib/analysis/analysis.py create mode 100644 analysis-master/build/lib/analysis/glicko2.py create mode 100644 analysis-master/build/lib/analysis/metrics/__init__.py create mode 100644 analysis-master/build/lib/analysis/metrics/elo.py create mode 100644 analysis-master/build/lib/analysis/metrics/glicko2.py create mode 100644 analysis-master/build/lib/analysis/metrics/trueskill.py create mode 100644 analysis-master/build/lib/analysis/regression.py create mode 100644 analysis-master/build/lib/analysis/titanlearn.py create mode 100644 analysis-master/build/lib/analysis/trueskill.py create mode 100644 analysis-master/build/lib/analysis/visualization.py create mode 100644 analysis-master/dist/analysis-1.0.0.10-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.10.tar.gz create mode 100644 analysis-master/dist/analysis-1.0.0.11-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.11.tar.gz create mode 100644 analysis-master/dist/analysis-1.0.0.12-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.12.tar.gz create mode 100644 analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.8.tar.gz create mode 100644 analysis-master/dist/analysis-1.0.0.9-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.9.tar.gz create mode 100644 analysis-master/docker/Dockerfile create mode 100644 analysis-master/docker/start-docker.sh create mode 100644 analysis-master/requirements.txt create mode 100644 analysis-master/setup.py diff --git a/analysis-master/analysis.egg-info/PKG-INFO b/analysis-master/analysis.egg-info/PKG-INFO new file mode 100644 index 00000000..83058193 --- /dev/null +++ b/analysis-master/analysis.egg-info/PKG-INFO @@ -0,0 +1,14 @@ +Metadata-Version: 2.1 +Name: analysis +Version: 1.0.0.12 +Summary: analysis package developed by Titan Scouting for The Red Alliance +Home-page: https://github.com/titanscout2022/tr2022-strategy +Author: The Titan Scouting Team +Author-email: titanscout2022@gmail.com +License: GNU General Public License v3.0 +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown diff --git a/analysis-master/analysis.egg-info/SOURCES.txt b/analysis-master/analysis.egg-info/SOURCES.txt new file mode 100644 index 00000000..2d8be231 --- /dev/null +++ b/analysis-master/analysis.egg-info/SOURCES.txt @@ -0,0 +1,15 @@ +setup.py +analysis/__init__.py +analysis/analysis.py +analysis/regression.py +analysis/titanlearn.py +analysis/visualization.py +analysis.egg-info/PKG-INFO +analysis.egg-info/SOURCES.txt +analysis.egg-info/dependency_links.txt +analysis.egg-info/requires.txt +analysis.egg-info/top_level.txt +analysis/metrics/__init__.py +analysis/metrics/elo.py +analysis/metrics/glicko2.py +analysis/metrics/trueskill.py \ No newline at end of file diff --git a/analysis-master/analysis.egg-info/dependency_links.txt b/analysis-master/analysis.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/analysis-master/analysis.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/analysis-master/analysis.egg-info/requires.txt b/analysis-master/analysis.egg-info/requires.txt new file mode 100644 index 00000000..6868226f --- /dev/null +++ b/analysis-master/analysis.egg-info/requires.txt @@ -0,0 +1,6 @@ +numba +numpy +scipy +scikit-learn +six +matplotlib diff --git a/analysis-master/analysis.egg-info/top_level.txt b/analysis-master/analysis.egg-info/top_level.txt new file mode 100644 index 00000000..09ad3be3 --- /dev/null +++ b/analysis-master/analysis.egg-info/top_level.txt @@ -0,0 +1 @@ +analysis diff --git a/analysis-master/build.sh b/analysis-master/build.sh new file mode 100644 index 00000000..de0cce90 --- /dev/null +++ b/analysis-master/build.sh @@ -0,0 +1 @@ +python setup.py sdist bdist_wheel || python3 setup.py sdist bdist_wheel \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/__init__.py b/analysis-master/build/lib/analysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/analysis-master/build/lib/analysis/analysis.py b/analysis-master/build/lib/analysis/analysis.py new file mode 100644 index 00000000..c13aef90 --- /dev/null +++ b/analysis-master/build/lib/analysis/analysis.py @@ -0,0 +1,923 @@ +# Titan Robotics Team 2022: Data Analysis Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'from analysis import analysis' +# this should be included in the local directory or environment variable +# this module has been optimized for multhreaded computing +# current benchmark of optimization: 1.33 times faster +# setup: + +__version__ = "1.2.0.004" + +# changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 1.2.0.004: + - fixed __all__ to reflected the correct functions and classes + - fixed CorrelationTests and StatisticalTests class functions to require self invocation + - added missing math import + - fixed KNN class functions to require self invocation + - fixed Metrics class functions to require self invocation + - various spelling fixes in CorrelationTests and StatisticalTests + 1.2.0.003: + - bug fixes with CorrelationTests and StatisticalTests + - moved glicko2 and trueskill to the metrics subpackage + - moved elo to a new metrics subpackage + 1.2.0.002: + - fixed docs + 1.2.0.001: + - fixed docs + 1.2.0.000: + - cleaned up wild card imports with scipy and sklearn + - added CorrelationTests class + - added StatisticalTests class + - added several correlation tests to CorrelationTests + - added several statistical tests to StatisticalTests + 1.1.13.009: + - moved elo, glicko2, trueskill functions under class Metrics + 1.1.13.008: + - moved Glicko2 to a seperate package + 1.1.13.007: + - fixed bug with trueskill + 1.1.13.006: + - cleaned up imports + 1.1.13.005: + - cleaned up package + 1.1.13.004: + - small fixes to regression to improve performance + 1.1.13.003: + - filtered nans from regression + 1.1.13.002: + - removed torch requirement, and moved Regression back to regression.py + 1.1.13.001: + - bug fix with linear regression not returning a proper value + - cleaned up regression + - fixed bug with polynomial regressions + 1.1.13.000: + - fixed all regressions to now properly work + 1.1.12.006: + - fixed bg with a division by zero in histo_analysis + 1.1.12.005: + - fixed numba issues by removing numba from elo, glicko2 and trueskill + 1.1.12.004: + - renamed gliko to glicko + 1.1.12.003: + - removed depreciated code + 1.1.12.002: + - removed team first time trueskill instantiation in favor of integration in superscript.py + 1.1.12.001: + - improved readibility of regression outputs by stripping tensor data + - used map with lambda to acheive the improved readibility + - lost numba jit support with regression, and generated_jit hangs at execution + - TODO: reimplement correct numba integration in regression + 1.1.12.000: + - temporarily fixed polynomial regressions by using sklearn's PolynomialFeatures + 1.1.11.010: + - alphabeticaly ordered import lists + 1.1.11.009: + - bug fixes + 1.1.11.008: + - bug fixes + 1.1.11.007: + - bug fixes + 1.1.11.006: + - tested min and max + - bug fixes + 1.1.11.005: + - added min and max in basic_stats + 1.1.11.004: + - bug fixes + 1.1.11.003: + - bug fixes + 1.1.11.002: + - consolidated metrics + - fixed __all__ + 1.1.11.001: + - added test/train split to RandomForestClassifier and RandomForestRegressor + 1.1.11.000: + - added RandomForestClassifier and RandomForestRegressor + - note: untested + 1.1.10.000: + - added numba.jit to remaining functions + 1.1.9.002: + - kernelized PCA and KNN + 1.1.9.001: + - fixed bugs with SVM and NaiveBayes + 1.1.9.000: + - added SVM class, subclasses, and functions + - note: untested + 1.1.8.000: + - added NaiveBayes classification engine + - note: untested + 1.1.7.000: + - added knn() + - added confusion matrix to decisiontree() + 1.1.6.002: + - changed layout of __changelog to be vscode friendly + 1.1.6.001: + - added additional hyperparameters to decisiontree() + 1.1.6.000: + - fixed __version__ + - fixed __all__ order + - added decisiontree() + 1.1.5.003: + - added pca + 1.1.5.002: + - reduced import list + - added kmeans clustering engine + 1.1.5.001: + - simplified regression by using .to(device) + 1.1.5.000: + - added polynomial regression to regression(); untested + 1.1.4.000: + - added trueskill() + 1.1.3.002: + - renamed regression class to Regression, regression_engine() to regression gliko2_engine class to Gliko2 + 1.1.3.001: + - changed glicko2() to return tuple instead of array + 1.1.3.000: + - added glicko2_engine class and glicko() + - verified glicko2() accuracy + 1.1.2.003: + - fixed elo() + 1.1.2.002: + - added elo() + - elo() has bugs to be fixed + 1.1.2.001: + - readded regrression import + 1.1.2.000: + - integrated regression.py as regression class + - removed regression import + - fixed metadata for regression class + - fixed metadata for analysis class + 1.1.1.001: + - regression_engine() bug fixes, now actaully regresses + 1.1.1.000: + - added regression_engine() + - added all regressions except polynomial + 1.1.0.007: + - updated _init_device() + 1.1.0.006: + - removed useless try statements + 1.1.0.005: + - removed impossible outcomes + 1.1.0.004: + - added performance metrics (r^2, mse, rms) + 1.1.0.003: + - resolved nopython mode for mean, median, stdev, variance + 1.1.0.002: + - snapped (removed) majority of uneeded imports + - forced object mode (bad) on all jit + - TODO: stop numba complaining about not being able to compile in nopython mode + 1.1.0.001: + - removed from sklearn import * to resolve uneeded wildcard imports + 1.1.0.000: + - removed c_entities,nc_entities,obstacles,objectives from __all__ + - applied numba.jit to all functions + - depreciated and removed stdev_z_split + - cleaned up histo_analysis to include numpy and numba.jit optimizations + - depreciated and removed all regression functions in favor of future pytorch optimizer + - depreciated and removed all nonessential functions (basic_analysis, benchmark, strip_data) + - optimized z_normalize using sklearn.preprocessing.normalize + - TODO: implement kernel/function based pytorch regression optimizer + 1.0.9.000: + - refactored + - numpyed everything + - removed stats in favor of numpy functions + 1.0.8.005: + - minor fixes + 1.0.8.004: + - removed a few unused dependencies + 1.0.8.003: + - added p_value function + 1.0.8.002: + - updated __all__ correctly to contain changes made in v 1.0.8.000 and v 1.0.8.001 + 1.0.8.001: + - refactors + - bugfixes + 1.0.8.000: + - depreciated histo_analysis_old + - depreciated debug + - altered basic_analysis to take array data instead of filepath + - refactor + - optimization + 1.0.7.002: + - bug fixes + 1.0.7.001: + - bug fixes + 1.0.7.000: + - added tanh_regression (logistical regression) + - bug fixes + 1.0.6.005: + - added z_normalize function to normalize dataset + - bug fixes + 1.0.6.004: + - bug fixes + 1.0.6.003: + - bug fixes + 1.0.6.002: + - bug fixes + 1.0.6.001: + - corrected __all__ to contain all of the functions + 1.0.6.000: + - added calc_overfit, which calculates two measures of overfit, error and performance + - added calculating overfit to optimize_regression + 1.0.5.000: + - added optimize_regression function, which is a sample function to find the optimal regressions + - optimize_regression function filters out some overfit funtions (functions with r^2 = 1) + - planned addition: overfit detection in the optimize_regression function + 1.0.4.002: + - added __changelog__ + - updated debug function with log and exponential regressions + 1.0.4.001: + - added log regressions + - added exponential regressions + - added log_regression and exp_regression to __all__ + 1.0.3.008: + - added debug function to further consolidate functions + 1.0.3.007: + - added builtin benchmark function + - added builtin random (linear) data generation function + - added device initialization (_init_device) + 1.0.3.006: + - reorganized the imports list to be in alphabetical order + - added search and regurgitate functions to c_entities, nc_entities, obstacles, objectives + 1.0.3.005: + - major bug fixes + - updated historical analysis + - depreciated old historical analysis + 1.0.3.004: + - added __version__, __author__, __all__ + - added polynomial regression + - added root mean squared function + - added r squared function + 1.0.3.003: + - bug fixes + - added c_entities + 1.0.3.002: + - bug fixes + - added nc_entities, obstacles, objectives + - consolidated statistics.py to analysis.py + 1.0.3.001: + - compiled 1d, column, and row basic stats into basic stats function + 1.0.3.000: + - added historical analysis function + 1.0.2.xxx: + - added z score test + 1.0.1.xxx: + - major bug fixes + 1.0.0.xxx: + - added loading csv + - added 1d, column, row basic stats +""" + +__author__ = ( + "Arthur Lu ", + "Jacob Levine ", +) + +__all__ = [ + 'load_csv', + 'basic_stats', + 'z_score', + 'z_normalize', + 'histo_analysis', + 'regression', + 'Metrics', + 'RegressionMetrics', + 'ClassificationMetrics', + 'kmeans', + 'pca', + 'decisiontree', + 'KNN', + 'NaiveBayes', + 'SVM', + 'random_forest_classifier', + 'random_forest_regressor', + 'CorrelationTests', + 'StatisticalTests', + # all statistics functions left out due to integration in other functions +] + +# now back to your regularly scheduled programming: + +# imports (now in alphabetical order! v 1.0.3.006): + +import csv +from analysis.metrics import elo as Elo +from analysis.metrics import glicko2 as Glicko2 +import math +import numba +from numba import jit +import numpy as np +import scipy +from scipy import optimize, stats +import sklearn +from sklearn import preprocessing, pipeline, linear_model, metrics, cluster, decomposition, tree, neighbors, naive_bayes, svm, model_selection, ensemble +from analysis.metrics import trueskill as Trueskill + +class error(ValueError): + pass + +def load_csv(filepath): + with open(filepath, newline='') as csvfile: + file_array = np.array(list(csv.reader(csvfile))) + csvfile.close() + return file_array + +# expects 1d array +@jit(forceobj=True) +def basic_stats(data): + + data_t = np.array(data).astype(float) + + _mean = mean(data_t) + _median = median(data_t) + _stdev = stdev(data_t) + _variance = variance(data_t) + _min = npmin(data_t) + _max = npmax(data_t) + + return _mean, _median, _stdev, _variance, _min, _max + +# returns z score with inputs of point, mean and standard deviation of spread +@jit(forceobj=True) +def z_score(point, mean, stdev): + score = (point - mean) / stdev + + return score + +# expects 2d array, normalizes across all axes +@jit(forceobj=True) +def z_normalize(array, *args): + + array = np.array(array) + for arg in args: + array = sklearn.preprocessing.normalize(array, axis = arg) + + return array + +@jit(forceobj=True) +# expects 2d array of [x,y] +def histo_analysis(hist_data): + + if(len(hist_data[0]) > 2): + + hist_data = np.array(hist_data) + derivative = np.array(len(hist_data) - 1, dtype = float) + t = np.diff(hist_data) + derivative = t[1] / t[0] + np.sort(derivative) + + return basic_stats(derivative)[0], basic_stats(derivative)[3] + + else: + + return None + +def regression(inputs, outputs, args): # inputs, outputs expects N-D array + + X = np.array(inputs) + y = np.array(outputs) + + regressions = [] + + if 'lin' in args: # formula: ax + b + + try: + + def func(x, a, b): + + return a * x + b + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'log' in args: # formula: a log (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.log(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'exp' in args: # formula: a e ^ (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.exp(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... + + inputs = np.array([inputs]) + outputs = np.array([outputs]) + + plys = [] + limit = len(outputs[0]) + + for i in range(2, limit): + + model = sklearn.preprocessing.PolynomialFeatures(degree = i) + model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) + model = model.fit(np.rot90(inputs), np.rot90(outputs)) + + params = model.steps[1][1].intercept_.tolist() + params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) + params.flatten() + params = params.tolist() + + plys.append(params) + + regressions.append(plys) + + if 'sig' in args: # formula: a tanh (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.tanh(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + return regressions + +class Metrics: + + def elo(self, starting_score, opposing_score, observed, N, K): + + return Elo.calculate(starting_score, opposing_score, observed, N, K) + + def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + + player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) + + player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) + + return (player.rating, player.rd, player.vol) + + def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + + team_ratings = [] + + for team in teams_data: + team_temp = () + for player in team: + player = Trueskill.Rating(player[0], player[1]) + team_temp = team_temp + (player,) + team_ratings.append(team_temp) + + return Trueskill.rate(team_ratings, ranks=observations) + +class RegressionMetrics(): + + def __new__(cls, predictions, targets): + + return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) + + def r_squared(self, predictions, targets): # assumes equal size inputs + + return sklearn.metrics.r2_score(targets, predictions) + + def mse(self, predictions, targets): + + return sklearn.metrics.mean_squared_error(targets, predictions) + + def rms(self, predictions, targets): + + return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) + +class ClassificationMetrics(): + + def __new__(cls, predictions, targets): + + return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) + + def cm(self, predictions, targets): + + return sklearn.metrics.confusion_matrix(targets, predictions) + + def cr(self, predictions, targets): + + return sklearn.metrics.classification_report(targets, predictions) + +@jit(nopython=True) +def mean(data): + + return np.mean(data) + +@jit(nopython=True) +def median(data): + + return np.median(data) + +@jit(nopython=True) +def stdev(data): + + return np.std(data) + +@jit(nopython=True) +def variance(data): + + return np.var(data) + +@jit(nopython=True) +def npmin(data): + + return np.amin(data) + +@jit(nopython=True) +def npmax(data): + + return np.amax(data) + +@jit(forceobj=True) +def kmeans(data, n_clusters=8, init="k-means++", n_init=10, max_iter=300, tol=0.0001, precompute_distances="auto", verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm="auto"): + + kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) + kernel.fit(data) + predictions = kernel.predict(data) + centers = kernel.cluster_centers_ + + return centers, predictions + +@jit(forceobj=True) +def pca(data, n_components = None, copy = True, whiten = False, svd_solver = "auto", tol = 0.0, iterated_power = "auto", random_state = None): + + kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) + + return kernel.fit_transform(data) + +@jit(forceobj=True) +def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = "default", max_depth = None): #expects *2d data and 1d labels + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) + model = model.fit(data_train,labels_train) + predictions = model.predict(data_test) + metrics = ClassificationMetrics(predictions, labels_test) + + return model, metrics + +class KNN: + + def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsClassifier() + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) + model.fit(data_train, outputs_train) + predictions = model.predict(data_test) + + return model, RegressionMetrics(predictions, outputs_test) + +class NaiveBayes: + + def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + +class SVM: + + class CustomKernel: + + def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class StandardKernel: + + def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class PrebuiltKernel: + + class Linear: + + def __new__(cls): + + return sklearn.svm.SVC(kernel = 'linear') + + class Polynomial: + + def __new__(cls, power, r_bias): + + return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) + + class RBF: + + def __new__(cls, gamma): + + return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) + + class Sigmoid: + + def __new__(cls, r_bias): + + return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) + + def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs + + return kernel.fit(train_data, train_outputs) + + def eval_classification(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return ClassificationMetrics(predictions, test_outputs) + + def eval_regression(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return RegressionMetrics(predictions, test_outputs) + +def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) + kernel.fit(data_train, labels_train) + predictions = kernel.predict(data_test) + + return kernel, ClassificationMetrics(predictions, labels_test) + +def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) + kernel.fit(data_train, outputs_train) + predictions = kernel.predict(data_test) + + return kernel, RegressionMetrics(predictions, outputs_test) + +class CorrelationTests: + + def anova_oneway(self, *args): #expects arrays of samples + + results = scipy.stats.f_oneway(*args) + return {"F-value": results[0], "p-value": results[1]} + + def pearson(self, x, y): + + results = scipy.stats.pearsonr(x, y) + return {"r-value": results[0], "p-value": results[1]} + + def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): + + results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) + return {"r-value": results[0], "p-value": results[1]} + + def point_biserial(self, x,y): + + results = scipy.stats.pointbiserialr(x, y) + return {"r-value": results[0], "p-value": results[1]} + + def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): + + results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) + return {"tau": results[0], "p-value": results[1]} + + def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): + + results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) + return {"tau": results[0], "p-value": results[1]} + + def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): + + results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) + return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value + +class StatisticalTests: + + def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): + + results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): + + results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_statistic(self, o1, o2, equal = True): + + results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) + return {"t-value": results[0], "p-value": results[1]} + + def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): + + results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} + + def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): + + results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} + + def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): + + results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) + return {"chisquared-value": results[0], "p-value": results[1]} + + def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): + + results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) + return {"powerdivergence-value": results[0], "p-value": results[1]} + + def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): + + results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} + + def es_twosample(self, x, y, t = (0.4, 0.8)): + + results = scipy.stats.epps_singleton_2samp(x, y, t = t) + return {"es-value": results[0], "p-value": results[1]} + + def mw_rank(self, x, y, use_continuity = True, alternative = None): + + results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) + return {"u-value": results[0], "p-value": results[1]} + + def mw_tiecorrection(self, rank_values): + + results = scipy.stats.tiecorrect(rank_values) + return {"correction-factor": results} + + def rankdata(self, a, method = 'average'): + + results = scipy.stats.rankdata(a, method = method) + return results + + def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test + + results = scipy.stats.ranksums(a, b) + return {"u-value": results[0], "p-value": results[1]} + + def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): + + results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) + return {"t-value": results[0], "p-value": results[1]} + + def kw_htest(self, *args, nan_policy = 'propagate'): + + results = scipy.stats.kruskal(*args, nan_policy = nan_policy) + return {"h-value": results[0], "p-value": results[1]} + + def friedman_chisquare(self, *args): + + results = scipy.stats.friedmanchisquare(*args) + return {"chisquared-value": results[0], "p-value": results[1]} + + def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): + + results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) + return {"w-value": results[0], "p-value": results[1]} + + def combine_pvalues(self, pvalues, method = 'fisher', weights = None): + + results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) + return {"combined-statistic": results[0], "p-value": results[1]} + + def jb_fitness(self, x): + + results = scipy.stats.jarque_bera(x) + return {"jb-value": results[0], "p-value": results[1]} + + def ab_equality(self, x, y): + + results = scipy.stats.ansari(x, y) + return {"ab-value": results[0], "p-value": results[1]} + + def bartlett_variance(self, *args): + + results = scipy.stats.bartlett(*args) + return {"t-value": results[0], "p-value": results[1]} + + def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): + + results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) + return {"w-value": results[0], "p-value": results[1]} + + def sw_normality(self, x): + + results = scipy.stats.shapiro(x) + return {"w-value": results[0], "p-value": results[1]} + + def shapiro(self, x): + + return "destroyed by facts and logic" + + def ad_onesample(self, x, dist = 'norm'): + + results = scipy.stats.anderson(x, dist = dist) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + + def ad_ksample(self, samples, midrank = True): + + results = scipy.stats.anderson_ksamp(samples, midrank = midrank) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + + def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): + + results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) + return {"p-value": results} + + def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): + + results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) + return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value + + def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): + + results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) + return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} + + def mood_equalscale(self, x, y, axis = 0): + + results = scipy.stats.mood(x, y, axis = axis) + return {"z-score": results[0], "p-value": results[1]} + + def skewtest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} + + def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} + + def normaltest(self, a, axis = 0, nan_policy = 'propogate'): + + results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/glicko2.py b/analysis-master/build/lib/analysis/glicko2.py new file mode 100644 index 00000000..66c0df94 --- /dev/null +++ b/analysis-master/build/lib/analysis/glicko2.py @@ -0,0 +1,99 @@ +import math + +class Glicko2: + _tau = 0.5 + + def getRating(self): + return (self.__rating * 173.7178) + 1500 + + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 + + rating = property(getRating, setRating) + + def getRd(self): + return self.__rd * 173.7178 + + def setRd(self, rd): + self.__rd = rd / 173.7178 + + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): + + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): + + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): + + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] + + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): + + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) + + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): + + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): + + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): + + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): + + self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/metrics/__init__.py b/analysis-master/build/lib/analysis/metrics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/analysis-master/build/lib/analysis/metrics/elo.py b/analysis-master/build/lib/analysis/metrics/elo.py new file mode 100644 index 00000000..3c8ef2e0 --- /dev/null +++ b/analysis-master/build/lib/analysis/metrics/elo.py @@ -0,0 +1,7 @@ +import numpy as np + +def calculate(starting_score, opposing_score, observed, N, K): + + expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + + return starting_score + K*(np.sum(observed) - np.sum(expected)) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/metrics/glicko2.py b/analysis-master/build/lib/analysis/metrics/glicko2.py new file mode 100644 index 00000000..66c0df94 --- /dev/null +++ b/analysis-master/build/lib/analysis/metrics/glicko2.py @@ -0,0 +1,99 @@ +import math + +class Glicko2: + _tau = 0.5 + + def getRating(self): + return (self.__rating * 173.7178) + 1500 + + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 + + rating = property(getRating, setRating) + + def getRd(self): + return self.__rd * 173.7178 + + def setRd(self, rd): + self.__rd = rd / 173.7178 + + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): + + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): + + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): + + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] + + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): + + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) + + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): + + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): + + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): + + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): + + self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/metrics/trueskill.py b/analysis-master/build/lib/analysis/metrics/trueskill.py new file mode 100644 index 00000000..116357df --- /dev/null +++ b/analysis-master/build/lib/analysis/metrics/trueskill.py @@ -0,0 +1,907 @@ +from __future__ import absolute_import + +from itertools import chain +import math + +from six import iteritems +from six.moves import map, range, zip +from six import iterkeys + +import copy +try: + from numbers import Number +except ImportError: + Number = (int, long, float, complex) + +inf = float('inf') + +class Gaussian(object): + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 + + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau + + @property + def mu(self): + return self.pi and self.tau / self.pi + + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf + + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) + + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) + + __div__ = __truediv__ # for Python 2 + + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau + + def __lt__(self, other): + return self.mu < other.mu + + def __le__(self, other): + return self.mu <= other.mu + + def __gt__(self, other): + return self.mu > other.mu + + def __ge__(self, other): + return self.mu >= other.mu + + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex + +class Matrix(list): + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) + + @property + def height(self): + return len(self) + + @property + def width(self): + return len(self[0]) + + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) + + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) + + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] + + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) + + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() + + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) + + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) + + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex + +def _gen_erfcinv(erfc, math=math): + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv + +def _gen_ppf(erfc, math=math): + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf + +def erfc(x): + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r + +def cdf(x, mu=0, sigma=1): + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + + +def pdf(x, mu=0, sigma=1): + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + +ppf = _gen_ppf(erfc) + +def choose_backend(backend): + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) + +def available_backends(): + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends + +class Node(object): + + pass + +class Variable(Node, Gaussian): + + def __init__(self): + self.messages = {} + super(Variable, self).__init__() + + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta + + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) + + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) + + def __getitem__(self, factor): + return self.messages[factor] + + def __setitem__(self, factor, message): + self.messages[factor] = message + + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args + + +class Factor(Node): + + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() + + def down(self): + return 0 + + def up(self): + return 0 + + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] + + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args + + +class PriorFactor(Factor): + + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic + + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) + + +class LikelihoodFactor(Factor): + + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance + + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) + + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) + + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) + + +class SumFactor(Factor): + + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs + + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) + + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) + + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) + + +class TruncateFactor(Factor): + + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin + + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) + +#: Default initial mean of ratings. +MU = 25. +#: Default initial standard deviation of ratings. +SIGMA = MU / 3 +#: Default distance that guarantees about 76% chance of winning. +BETA = SIGMA / 2 +#: Default dynamic factor. +TAU = SIGMA / 100 +#: Default draw probability of the game. +DRAW_PROBABILITY = .10 +#: A basis to check reliability of the result. +DELTA = 0.0001 + + +def calc_draw_probability(draw_margin, size, env=None): + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + + +def calc_draw_margin(draw_probability, size, env=None): + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + + +def _team_sizes(rating_groups): + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes + + +def _floating_point_error(env): + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) + + +class Rating(Gaussian): + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) + + def __int__(self): + return int(self.mu) + + def __long__(self): + return long(self.mu) + + def __float__(self): + return float(self.mu) + + def __iter__(self): + return iter((self.mu, self.sigma)) + + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args + + +class TrueSkill(object): + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) + + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) + + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x + + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) + + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys + + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights + + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) + + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers + + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] + + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) + + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma + + def make_as_global(self): + return setup(env=self) + + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) + + +def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] + + +def quality_1vs1(rating1, rating2, env=None): + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) + + +def global_env(): + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ + + +def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env + + +def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): + return global_env().rate(rating_groups, ranks, weights, min_delta) + + +def quality(rating_groups, weights=None): + return global_env().quality(rating_groups, weights) + + +def expose(rating): + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/regression.py b/analysis-master/build/lib/analysis/regression.py new file mode 100644 index 00000000..e899e9ff --- /dev/null +++ b/analysis-master/build/lib/analysis/regression.py @@ -0,0 +1,220 @@ +# Titan Robotics Team 2022: CUDA-based Regressions Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this module has been automatically inegrated into analysis.py, and should be callable as a class from the package +# this module is cuda-optimized and vectorized (except for one small part) +# setup: + +__version__ = "1.0.0.004" + +# changelog should be viewed using print(analysis.regression.__changelog__) +__changelog__ = """ + 1.0.0.004: + - bug fixes + - fixed changelog + 1.0.0.003: + - bug fixes + 1.0.0.002: + -Added more parameters to log, exponential, polynomial + -Added SigmoidalRegKernelArthur, because Arthur apparently needs + to train the scaling and shifting of sigmoids + 1.0.0.001: + -initial release, with linear, log, exponential, polynomial, and sigmoid kernels + -already vectorized (except for polynomial generation) and CUDA-optimized +""" + +__author__ = ( + "Jacob Levine ", + "Arthur Lu " +) + +__all__ = [ + 'factorial', + 'take_all_pwrs', + 'num_poly_terms', + 'set_device', + 'LinearRegKernel', + 'SigmoidalRegKernel', + 'LogRegKernel', + 'PolyRegKernel', + 'ExpRegKernel', + 'SigmoidalRegKernelArthur', + 'SGDTrain', + 'CustomTrain' +] + +import torch + +global device + +device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" + +#todo: document completely + +def set_device(self, new_device): + device=new_device + +class LinearRegKernel(): + parameters= [] + weights=None + bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,mtx)+long_bias + +class SigmoidalRegKernel(): + parameters= [] + weights=None + bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) + +class SigmoidalRegKernelArthur(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class LogRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class ExpRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class PolyRegKernel(): + parameters= [] + weights=None + bias=None + power=None + def __init__(self, num_vars, power): + self.power=power + num_terms=self.num_poly_terms(num_vars, power) + self.weights=torch.rand(num_terms, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def num_poly_terms(self,num_vars, power): + if power == 0: + return 0 + return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) + def factorial(self,n): + if n==0: + return 1 + else: + return n*self.factorial(n-1) + def take_all_pwrs(self, vec, pwr): + #todo: vectorize (kinda) + combins=torch.combinations(vec, r=pwr, with_replacement=True) + out=torch.ones(combins.size()[0]).to(device).to(torch.float) + for i in torch.t(combins).to(device).to(torch.float): + out *= i + if pwr == 1: + return out + else: + return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) + def forward(self,mtx): + #TODO: Vectorize the last part + cols=[] + for i in torch.t(mtx): + cols.append(self.take_all_pwrs(i,self.power)) + new_mtx=torch.t(torch.stack(cols)) + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,new_mtx)+long_bias + +def SGDTrain(self, kernel, data, ground, loss=torch.nn.MSELoss(), iterations=1000, learning_rate=.1, return_losses=False): + optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel + +def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iterations=1000, return_losses=False): + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data) + ls=loss(pred,ground) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/titanlearn.py b/analysis-master/build/lib/analysis/titanlearn.py new file mode 100644 index 00000000..b69d36e3 --- /dev/null +++ b/analysis-master/build/lib/analysis/titanlearn.py @@ -0,0 +1,122 @@ +# Titan Robotics Team 2022: ML Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import titanlearn' +# this should be included in the local directory or environment variable +# this module is optimized for multhreaded computing +# this module learns from its mistakes far faster than 2022's captains +# setup: + +__version__ = "2.0.1.001" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 2.0.1.001: + - removed matplotlib import + - removed graphloss() + 2.0.1.000: + - added net, dataset, dataloader, and stdtrain template definitions + - added graphloss function + 2.0.0.001: + - added clear functions + 2.0.0.000: + - complete rewrite planned + - depreciated 1.0.0.xxx versions + - added simple training loop + 1.0.0.xxx: + -added generation of ANNS, basic SGD training +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'clear', + 'net', + 'dataset', + 'dataloader', + 'train', + 'stdtrainer', + ] + +import torch +from os import system, name +import numpy as np + +def clear(): + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') + +class net(torch.nn.Module): #template for standard neural net + def __init__(self): + super(Net, self).__init__() + + def forward(self, input): + pass + +class dataset(torch.utils.data.Dataset): #template for standard dataset + + def __init__(self): + super(torch.utils.data.Dataset).__init__() + + def __getitem__(self, index): + pass + + def __len__(self): + pass + +def dataloader(dataset, batch_size, num_workers, shuffle = True): + + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) + +def train(device, net, epochs, trainloader, optimizer, criterion): #expects standard dataloader, whch returns (inputs, labels) + + dataset_len = trainloader.dataset.__len__() + iter_count = 0 + running_loss = 0 + running_loss_list = [] + + for epoch in range(epochs): # loop over the dataset multiple times + + for i, data in enumerate(trainloader, 0): + + inputs = data[0].to(device) + labels = data[1].to(device) + + optimizer.zero_grad() + + outputs = net(inputs) + loss = criterion(outputs, labels.to(torch.float)) + + loss.backward() + optimizer.step() + + # monitoring steps below + + iter_count += 1 + running_loss += loss.item() + running_loss_list.append(running_loss) + clear() + + print("training on: " + device) + print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) + print("current batch loss: " + str(loss.item)) + print("running loss: " + str(running_loss / iter_count)) + + return net, running_loss_list + print("finished training") + +def stdtrainer(net, criterion, optimizer, dataloader, epochs, batch_size): + + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + net = net.to(device) + criterion = criterion.to(device) + optimizer = optimizer.to(device) + trainloader = dataloader + + return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/trueskill.py b/analysis-master/build/lib/analysis/trueskill.py new file mode 100644 index 00000000..116357df --- /dev/null +++ b/analysis-master/build/lib/analysis/trueskill.py @@ -0,0 +1,907 @@ +from __future__ import absolute_import + +from itertools import chain +import math + +from six import iteritems +from six.moves import map, range, zip +from six import iterkeys + +import copy +try: + from numbers import Number +except ImportError: + Number = (int, long, float, complex) + +inf = float('inf') + +class Gaussian(object): + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 + + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau + + @property + def mu(self): + return self.pi and self.tau / self.pi + + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf + + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) + + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) + + __div__ = __truediv__ # for Python 2 + + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau + + def __lt__(self, other): + return self.mu < other.mu + + def __le__(self, other): + return self.mu <= other.mu + + def __gt__(self, other): + return self.mu > other.mu + + def __ge__(self, other): + return self.mu >= other.mu + + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex + +class Matrix(list): + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) + + @property + def height(self): + return len(self) + + @property + def width(self): + return len(self[0]) + + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) + + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) + + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] + + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) + + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() + + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) + + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) + + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex + +def _gen_erfcinv(erfc, math=math): + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv + +def _gen_ppf(erfc, math=math): + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf + +def erfc(x): + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r + +def cdf(x, mu=0, sigma=1): + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + + +def pdf(x, mu=0, sigma=1): + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + +ppf = _gen_ppf(erfc) + +def choose_backend(backend): + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) + +def available_backends(): + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends + +class Node(object): + + pass + +class Variable(Node, Gaussian): + + def __init__(self): + self.messages = {} + super(Variable, self).__init__() + + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta + + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) + + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) + + def __getitem__(self, factor): + return self.messages[factor] + + def __setitem__(self, factor, message): + self.messages[factor] = message + + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args + + +class Factor(Node): + + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() + + def down(self): + return 0 + + def up(self): + return 0 + + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] + + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args + + +class PriorFactor(Factor): + + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic + + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) + + +class LikelihoodFactor(Factor): + + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance + + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) + + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) + + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) + + +class SumFactor(Factor): + + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs + + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) + + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) + + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) + + +class TruncateFactor(Factor): + + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin + + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) + +#: Default initial mean of ratings. +MU = 25. +#: Default initial standard deviation of ratings. +SIGMA = MU / 3 +#: Default distance that guarantees about 76% chance of winning. +BETA = SIGMA / 2 +#: Default dynamic factor. +TAU = SIGMA / 100 +#: Default draw probability of the game. +DRAW_PROBABILITY = .10 +#: A basis to check reliability of the result. +DELTA = 0.0001 + + +def calc_draw_probability(draw_margin, size, env=None): + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + + +def calc_draw_margin(draw_probability, size, env=None): + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + + +def _team_sizes(rating_groups): + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes + + +def _floating_point_error(env): + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) + + +class Rating(Gaussian): + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) + + def __int__(self): + return int(self.mu) + + def __long__(self): + return long(self.mu) + + def __float__(self): + return float(self.mu) + + def __iter__(self): + return iter((self.mu, self.sigma)) + + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args + + +class TrueSkill(object): + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) + + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) + + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x + + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) + + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys + + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights + + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) + + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers + + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] + + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) + + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma + + def make_as_global(self): + return setup(env=self) + + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) + + +def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] + + +def quality_1vs1(rating1, rating2, env=None): + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) + + +def global_env(): + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ + + +def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env + + +def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): + return global_env().rate(rating_groups, ranks, weights, min_delta) + + +def quality(rating_groups, weights=None): + return global_env().quality(rating_groups, weights) + + +def expose(rating): + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/visualization.py b/analysis-master/build/lib/analysis/visualization.py new file mode 100644 index 00000000..72358662 --- /dev/null +++ b/analysis-master/build/lib/analysis/visualization.py @@ -0,0 +1,34 @@ +# Titan Robotics Team 2022: Visualization Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import visualization' +# this should be included in the local directory or environment variable +# fancy +# setup: + +__version__ = "1.0.0.000" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 1.0.0.000: + - created visualization.py + - added graphloss() + - added imports +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'graphloss', + ] + +import matplotlib.pyplot as plt + +def graphloss(losses): + + x = range(0, len(losses)) + plt.plot(x, losses) + plt.show() \ No newline at end of file diff --git a/analysis-master/dist/analysis-1.0.0.10-py3-none-any.whl b/analysis-master/dist/analysis-1.0.0.10-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a9401521bc24a7d3c97b652f8e3e6fa586d11beb GIT binary patch literal 20742 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 z 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 a_Ak{m)}tonHj$Chk=g~4)(#l_@%BIIzQM%+ literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.10.tar.gz b/analysis-master/dist/analysis-1.0.0.10.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a970f1b5e6e730256b88c71d5c83aadbfed08b16 GIT binary patch literal 18950 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 SC5o|Df~Wy~-+JsXFvGA! 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 Lk)@Vo@cZsRs#&%h literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.11.tar.gz b/analysis-master/dist/analysis-1.0.0.11.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2e9aa30eeabf42fb316d8dbb5b93d9b1f72dd957 GIT binary patch literal 19444 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

3a7Qq& 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 LZ?kZD0Js4F(KkD> literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.12-py3-none-any.whl b/analysis-master/dist/analysis-1.0.0.12-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..60e0b51c7e0824323d9fea32c844cadcaeb7528a GIT binary patch literal 32026 zcmZ6xQ;;S~w5?mV`Il{W*>-i=wr$(CZQHhO+cvv!)_%D6>^SowBO@c`Tjm@iV|?<` zpkQb~KtNDHnav^c|80Ok{%85G{^!)d*1+1s$-;?2PtU^E!dXv`-rfTgNdEutkt)xX z*UAin3nc&o+Kc&rdj7xW|9WnCt>TVG6ZYWW`R_;j;Z3a2C9htL=(&b<`Td<-jn?@n z`VD28$5V&#B8xYUr_B*R?$lJi$+HqoU7H#btU;ozUDec7)Yh6NJ_tXl>@={xe1H~)~QbwGDxZ7iqIFDsjQ-ZeZTRA|7;%HOih{lg!h_n>a2$KF&75ythTZwp6OI^e}gXBWY z(6(?=TJeg!)HDTr`r`;@oq?ePq5p(}|4?48s4Akdd*57>DXh$j%wYFeO8wStU4*lG zY)u!j*pG(7EnqzpFkWf<#^@OS7pngf*L>`mfUpSdEqECKIBd)+6KgwYnp= zG6sKkvzxlEaOC>o2nxGBob7LO_dr2ln-09U=Wh>b$3cvLt|UrLVbq85Qyjzn z0i;5w(Mc-i%4)$o`B|HP)Un3Su?db9{!HR~r67cOR_)&eS@lJfbtOikcEF%VbMy8t$(qG-;F+1fWZia>N^AmW?lz#J)D|p9_-;*ZVNXVr!Y&e*Uwi#()h!i z`__J_2LKMOrxJ9d#zTRy7D_pPJ9h-%uLa+$s^ylYc?u8Qo% zHY+yjs+%&NL;InpP{;Wyvt-SBDhx=VvYtCi%9oo=O&BYEJfPEj2;j_*_J2K=OI3xx+ldRdUAPwqo^gS^e z2Fsm;$45?m-#6BuGglWV&AI~1=c2j=6q&VRFIHw=3>r$ENpyeWl&8kO^9~mLjGl_|Z?$0=3}lZTg(g zGC*D=NPNoA(wA;uPE~Z{zSdqFDXekh6mPQC*BAKY zpS<75K@z{-*s0=<9(LYpS&PRw6|hYyf`$+G6_Ta>c#G6?DiflKr5)5N)ogJS;DrLD z=K7B{svjxfn3zzL{W#s5aBX|Xy|?sc=H5XLLaH^5>oYUC8f*dQiGz|It8PI3teOmw z{HRBeWH40rfZR?*dN-*$CXcs8eh

x#v7H(S=Q=4L*Ouq%iyj9OET3NC4 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 literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.12.tar.gz b/analysis-master/dist/analysis-1.0.0.12.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1ba274315415d3096304b963298c369f7d3b44c2 GIT binary patch literal 21001 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 literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl b/analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..83d5cb968fdb50f333025e6373172553d91df843 GIT binary patch literal 20459 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 Z{&)Yo|K0!Ye;@Gg{{uiF_T2zz0RRUFw=Vzy literal 0 HcmV?d00001 diff --git a/analysis-master/dist/analysis-1.0.0.9-py3-none-any.whl b/analysis-master/dist/analysis-1.0.0.9-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..24f78782f4be9579653b2260cea98482394d1c8f GIT binary patch literal 20332 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 Date: Fri, 1 May 2020 16:15:07 -0500 Subject: [PATCH 4/5] converted space indentation to tab indentation --- analysis-master/analysis/analysis.py | 1204 +++++++------- analysis-master/analysis/metrics/elo.py | 4 +- analysis-master/analysis/metrics/glicko2.py | 158 +- analysis-master/analysis/metrics/trueskill.py | 1462 ++++++++--------- analysis-master/analysis/regression.py | 352 ++-- analysis-master/analysis/titanlearn.py | 152 +- analysis-master/analysis/visualization.py | 24 +- analysis-master/setup.py | 36 +- data analysis/data.py | 148 +- data analysis/get_team_rankings.py | 68 +- data analysis/superscript.py | 478 +++--- data analysis/visualize_pit.py | 34 +- 12 files changed, 2060 insertions(+), 2060 deletions(-) diff --git a/analysis-master/analysis/analysis.py b/analysis-master/analysis/analysis.py index c13aef90..c4cce961 100644 --- a/analysis-master/analysis/analysis.py +++ b/analysis-master/analysis/analysis.py @@ -11,291 +11,291 @@ __version__ = "1.2.0.004" # changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: - 1.2.0.004: - - fixed __all__ to reflected the correct functions and classes - - fixed CorrelationTests and StatisticalTests class functions to require self invocation - - added missing math import - - fixed KNN class functions to require self invocation - - fixed Metrics class functions to require self invocation - - various spelling fixes in CorrelationTests and StatisticalTests - 1.2.0.003: - - bug fixes with CorrelationTests and StatisticalTests - - moved glicko2 and trueskill to the metrics subpackage - - moved elo to a new metrics subpackage - 1.2.0.002: - - fixed docs - 1.2.0.001: - - fixed docs - 1.2.0.000: - - cleaned up wild card imports with scipy and sklearn - - added CorrelationTests class - - added StatisticalTests class - - added several correlation tests to CorrelationTests - - added several statistical tests to StatisticalTests - 1.1.13.009: - - moved elo, glicko2, trueskill functions under class Metrics - 1.1.13.008: - - moved Glicko2 to a seperate package - 1.1.13.007: - - fixed bug with trueskill - 1.1.13.006: - - cleaned up imports - 1.1.13.005: - - cleaned up package - 1.1.13.004: - - small fixes to regression to improve performance - 1.1.13.003: - - filtered nans from regression - 1.1.13.002: - - removed torch requirement, and moved Regression back to regression.py - 1.1.13.001: - - bug fix with linear regression not returning a proper value - - cleaned up regression - - fixed bug with polynomial regressions - 1.1.13.000: - - fixed all regressions to now properly work - 1.1.12.006: - - fixed bg with a division by zero in histo_analysis - 1.1.12.005: - - fixed numba issues by removing numba from elo, glicko2 and trueskill - 1.1.12.004: - - renamed gliko to glicko - 1.1.12.003: - - removed depreciated code - 1.1.12.002: - - removed team first time trueskill instantiation in favor of integration in superscript.py - 1.1.12.001: - - improved readibility of regression outputs by stripping tensor data - - used map with lambda to acheive the improved readibility - - lost numba jit support with regression, and generated_jit hangs at execution - - TODO: reimplement correct numba integration in regression - 1.1.12.000: - - temporarily fixed polynomial regressions by using sklearn's PolynomialFeatures - 1.1.11.010: - - alphabeticaly ordered import lists - 1.1.11.009: - - bug fixes - 1.1.11.008: - - bug fixes - 1.1.11.007: - - bug fixes - 1.1.11.006: - - tested min and max - - bug fixes - 1.1.11.005: - - added min and max in basic_stats - 1.1.11.004: - - bug fixes - 1.1.11.003: - - bug fixes - 1.1.11.002: - - consolidated metrics - - fixed __all__ - 1.1.11.001: - - added test/train split to RandomForestClassifier and RandomForestRegressor - 1.1.11.000: - - added RandomForestClassifier and RandomForestRegressor - - note: untested - 1.1.10.000: - - added numba.jit to remaining functions - 1.1.9.002: - - kernelized PCA and KNN - 1.1.9.001: - - fixed bugs with SVM and NaiveBayes - 1.1.9.000: - - added SVM class, subclasses, and functions - - note: untested - 1.1.8.000: - - added NaiveBayes classification engine - - note: untested - 1.1.7.000: - - added knn() - - added confusion matrix to decisiontree() - 1.1.6.002: - - changed layout of __changelog to be vscode friendly - 1.1.6.001: - - added additional hyperparameters to decisiontree() - 1.1.6.000: - - fixed __version__ - - fixed __all__ order - - added decisiontree() - 1.1.5.003: - - added pca - 1.1.5.002: - - reduced import list - - added kmeans clustering engine - 1.1.5.001: - - simplified regression by using .to(device) - 1.1.5.000: - - added polynomial regression to regression(); untested - 1.1.4.000: - - added trueskill() - 1.1.3.002: - - renamed regression class to Regression, regression_engine() to regression gliko2_engine class to Gliko2 - 1.1.3.001: - - changed glicko2() to return tuple instead of array - 1.1.3.000: - - added glicko2_engine class and glicko() - - verified glicko2() accuracy - 1.1.2.003: - - fixed elo() - 1.1.2.002: - - added elo() - - elo() has bugs to be fixed - 1.1.2.001: - - readded regrression import - 1.1.2.000: - - integrated regression.py as regression class - - removed regression import - - fixed metadata for regression class - - fixed metadata for analysis class - 1.1.1.001: - - regression_engine() bug fixes, now actaully regresses - 1.1.1.000: - - added regression_engine() - - added all regressions except polynomial - 1.1.0.007: - - updated _init_device() - 1.1.0.006: - - removed useless try statements - 1.1.0.005: - - removed impossible outcomes - 1.1.0.004: - - added performance metrics (r^2, mse, rms) - 1.1.0.003: - - resolved nopython mode for mean, median, stdev, variance - 1.1.0.002: - - snapped (removed) majority of uneeded imports - - forced object mode (bad) on all jit - - TODO: stop numba complaining about not being able to compile in nopython mode - 1.1.0.001: - - removed from sklearn import * to resolve uneeded wildcard imports - 1.1.0.000: - - removed c_entities,nc_entities,obstacles,objectives from __all__ - - applied numba.jit to all functions - - depreciated and removed stdev_z_split - - cleaned up histo_analysis to include numpy and numba.jit optimizations - - depreciated and removed all regression functions in favor of future pytorch optimizer - - depreciated and removed all nonessential functions (basic_analysis, benchmark, strip_data) - - optimized z_normalize using sklearn.preprocessing.normalize - - TODO: implement kernel/function based pytorch regression optimizer - 1.0.9.000: - - refactored - - numpyed everything - - removed stats in favor of numpy functions - 1.0.8.005: - - minor fixes - 1.0.8.004: - - removed a few unused dependencies - 1.0.8.003: - - added p_value function - 1.0.8.002: - - updated __all__ correctly to contain changes made in v 1.0.8.000 and v 1.0.8.001 - 1.0.8.001: - - refactors - - bugfixes - 1.0.8.000: - - depreciated histo_analysis_old - - depreciated debug - - altered basic_analysis to take array data instead of filepath - - refactor - - optimization - 1.0.7.002: - - bug fixes - 1.0.7.001: - - bug fixes - 1.0.7.000: - - added tanh_regression (logistical regression) - - bug fixes - 1.0.6.005: - - added z_normalize function to normalize dataset - - bug fixes - 1.0.6.004: - - bug fixes - 1.0.6.003: - - bug fixes - 1.0.6.002: - - bug fixes - 1.0.6.001: - - corrected __all__ to contain all of the functions - 1.0.6.000: - - added calc_overfit, which calculates two measures of overfit, error and performance - - added calculating overfit to optimize_regression - 1.0.5.000: - - added optimize_regression function, which is a sample function to find the optimal regressions - - optimize_regression function filters out some overfit funtions (functions with r^2 = 1) - - planned addition: overfit detection in the optimize_regression function - 1.0.4.002: - - added __changelog__ - - updated debug function with log and exponential regressions - 1.0.4.001: - - added log regressions - - added exponential regressions - - added log_regression and exp_regression to __all__ - 1.0.3.008: - - added debug function to further consolidate functions - 1.0.3.007: - - added builtin benchmark function - - added builtin random (linear) data generation function - - added device initialization (_init_device) - 1.0.3.006: - - reorganized the imports list to be in alphabetical order - - added search and regurgitate functions to c_entities, nc_entities, obstacles, objectives - 1.0.3.005: - - major bug fixes - - updated historical analysis - - depreciated old historical analysis - 1.0.3.004: - - added __version__, __author__, __all__ - - added polynomial regression - - added root mean squared function - - added r squared function - 1.0.3.003: - - bug fixes - - added c_entities - 1.0.3.002: - - bug fixes - - added nc_entities, obstacles, objectives - - consolidated statistics.py to analysis.py - 1.0.3.001: - - compiled 1d, column, and row basic stats into basic stats function - 1.0.3.000: - - added historical analysis function - 1.0.2.xxx: - - added z score test - 1.0.1.xxx: - - major bug fixes - 1.0.0.xxx: - - added loading csv - - added 1d, column, row basic stats + 1.2.0.004: + - fixed __all__ to reflected the correct functions and classes + - fixed CorrelationTests and StatisticalTests class functions to require self invocation + - added missing math import + - fixed KNN class functions to require self invocation + - fixed Metrics class functions to require self invocation + - various spelling fixes in CorrelationTests and StatisticalTests + 1.2.0.003: + - bug fixes with CorrelationTests and StatisticalTests + - moved glicko2 and trueskill to the metrics subpackage + - moved elo to a new metrics subpackage + 1.2.0.002: + - fixed docs + 1.2.0.001: + - fixed docs + 1.2.0.000: + - cleaned up wild card imports with scipy and sklearn + - added CorrelationTests class + - added StatisticalTests class + - added several correlation tests to CorrelationTests + - added several statistical tests to StatisticalTests + 1.1.13.009: + - moved elo, glicko2, trueskill functions under class Metrics + 1.1.13.008: + - moved Glicko2 to a seperate package + 1.1.13.007: + - fixed bug with trueskill + 1.1.13.006: + - cleaned up imports + 1.1.13.005: + - cleaned up package + 1.1.13.004: + - small fixes to regression to improve performance + 1.1.13.003: + - filtered nans from regression + 1.1.13.002: + - removed torch requirement, and moved Regression back to regression.py + 1.1.13.001: + - bug fix with linear regression not returning a proper value + - cleaned up regression + - fixed bug with polynomial regressions + 1.1.13.000: + - fixed all regressions to now properly work + 1.1.12.006: + - fixed bg with a division by zero in histo_analysis + 1.1.12.005: + - fixed numba issues by removing numba from elo, glicko2 and trueskill + 1.1.12.004: + - renamed gliko to glicko + 1.1.12.003: + - removed depreciated code + 1.1.12.002: + - removed team first time trueskill instantiation in favor of integration in superscript.py + 1.1.12.001: + - improved readibility of regression outputs by stripping tensor data + - used map with lambda to acheive the improved readibility + - lost numba jit support with regression, and generated_jit hangs at execution + - TODO: reimplement correct numba integration in regression + 1.1.12.000: + - temporarily fixed polynomial regressions by using sklearn's PolynomialFeatures + 1.1.11.010: + - alphabeticaly ordered import lists + 1.1.11.009: + - bug fixes + 1.1.11.008: + - bug fixes + 1.1.11.007: + - bug fixes + 1.1.11.006: + - tested min and max + - bug fixes + 1.1.11.005: + - added min and max in basic_stats + 1.1.11.004: + - bug fixes + 1.1.11.003: + - bug fixes + 1.1.11.002: + - consolidated metrics + - fixed __all__ + 1.1.11.001: + - added test/train split to RandomForestClassifier and RandomForestRegressor + 1.1.11.000: + - added RandomForestClassifier and RandomForestRegressor + - note: untested + 1.1.10.000: + - added numba.jit to remaining functions + 1.1.9.002: + - kernelized PCA and KNN + 1.1.9.001: + - fixed bugs with SVM and NaiveBayes + 1.1.9.000: + - added SVM class, subclasses, and functions + - note: untested + 1.1.8.000: + - added NaiveBayes classification engine + - note: untested + 1.1.7.000: + - added knn() + - added confusion matrix to decisiontree() + 1.1.6.002: + - changed layout of __changelog to be vscode friendly + 1.1.6.001: + - added additional hyperparameters to decisiontree() + 1.1.6.000: + - fixed __version__ + - fixed __all__ order + - added decisiontree() + 1.1.5.003: + - added pca + 1.1.5.002: + - reduced import list + - added kmeans clustering engine + 1.1.5.001: + - simplified regression by using .to(device) + 1.1.5.000: + - added polynomial regression to regression(); untested + 1.1.4.000: + - added trueskill() + 1.1.3.002: + - renamed regression class to Regression, regression_engine() to regression gliko2_engine class to Gliko2 + 1.1.3.001: + - changed glicko2() to return tuple instead of array + 1.1.3.000: + - added glicko2_engine class and glicko() + - verified glicko2() accuracy + 1.1.2.003: + - fixed elo() + 1.1.2.002: + - added elo() + - elo() has bugs to be fixed + 1.1.2.001: + - readded regrression import + 1.1.2.000: + - integrated regression.py as regression class + - removed regression import + - fixed metadata for regression class + - fixed metadata for analysis class + 1.1.1.001: + - regression_engine() bug fixes, now actaully regresses + 1.1.1.000: + - added regression_engine() + - added all regressions except polynomial + 1.1.0.007: + - updated _init_device() + 1.1.0.006: + - removed useless try statements + 1.1.0.005: + - removed impossible outcomes + 1.1.0.004: + - added performance metrics (r^2, mse, rms) + 1.1.0.003: + - resolved nopython mode for mean, median, stdev, variance + 1.1.0.002: + - snapped (removed) majority of uneeded imports + - forced object mode (bad) on all jit + - TODO: stop numba complaining about not being able to compile in nopython mode + 1.1.0.001: + - removed from sklearn import * to resolve uneeded wildcard imports + 1.1.0.000: + - removed c_entities,nc_entities,obstacles,objectives from __all__ + - applied numba.jit to all functions + - depreciated and removed stdev_z_split + - cleaned up histo_analysis to include numpy and numba.jit optimizations + - depreciated and removed all regression functions in favor of future pytorch optimizer + - depreciated and removed all nonessential functions (basic_analysis, benchmark, strip_data) + - optimized z_normalize using sklearn.preprocessing.normalize + - TODO: implement kernel/function based pytorch regression optimizer + 1.0.9.000: + - refactored + - numpyed everything + - removed stats in favor of numpy functions + 1.0.8.005: + - minor fixes + 1.0.8.004: + - removed a few unused dependencies + 1.0.8.003: + - added p_value function + 1.0.8.002: + - updated __all__ correctly to contain changes made in v 1.0.8.000 and v 1.0.8.001 + 1.0.8.001: + - refactors + - bugfixes + 1.0.8.000: + - depreciated histo_analysis_old + - depreciated debug + - altered basic_analysis to take array data instead of filepath + - refactor + - optimization + 1.0.7.002: + - bug fixes + 1.0.7.001: + - bug fixes + 1.0.7.000: + - added tanh_regression (logistical regression) + - bug fixes + 1.0.6.005: + - added z_normalize function to normalize dataset + - bug fixes + 1.0.6.004: + - bug fixes + 1.0.6.003: + - bug fixes + 1.0.6.002: + - bug fixes + 1.0.6.001: + - corrected __all__ to contain all of the functions + 1.0.6.000: + - added calc_overfit, which calculates two measures of overfit, error and performance + - added calculating overfit to optimize_regression + 1.0.5.000: + - added optimize_regression function, which is a sample function to find the optimal regressions + - optimize_regression function filters out some overfit funtions (functions with r^2 = 1) + - planned addition: overfit detection in the optimize_regression function + 1.0.4.002: + - added __changelog__ + - updated debug function with log and exponential regressions + 1.0.4.001: + - added log regressions + - added exponential regressions + - added log_regression and exp_regression to __all__ + 1.0.3.008: + - added debug function to further consolidate functions + 1.0.3.007: + - added builtin benchmark function + - added builtin random (linear) data generation function + - added device initialization (_init_device) + 1.0.3.006: + - reorganized the imports list to be in alphabetical order + - added search and regurgitate functions to c_entities, nc_entities, obstacles, objectives + 1.0.3.005: + - major bug fixes + - updated historical analysis + - depreciated old historical analysis + 1.0.3.004: + - added __version__, __author__, __all__ + - added polynomial regression + - added root mean squared function + - added r squared function + 1.0.3.003: + - bug fixes + - added c_entities + 1.0.3.002: + - bug fixes + - added nc_entities, obstacles, objectives + - consolidated statistics.py to analysis.py + 1.0.3.001: + - compiled 1d, column, and row basic stats into basic stats function + 1.0.3.000: + - added historical analysis function + 1.0.2.xxx: + - added z score test + 1.0.1.xxx: + - major bug fixes + 1.0.0.xxx: + - added loading csv + - added 1d, column, row basic stats """ __author__ = ( - "Arthur Lu ", - "Jacob Levine ", + "Arthur Lu ", + "Jacob Levine ", ) __all__ = [ - 'load_csv', - 'basic_stats', - 'z_score', - 'z_normalize', - 'histo_analysis', - 'regression', - 'Metrics', - 'RegressionMetrics', - 'ClassificationMetrics', - 'kmeans', - 'pca', - 'decisiontree', - 'KNN', - 'NaiveBayes', - 'SVM', - 'random_forest_classifier', - 'random_forest_regressor', - 'CorrelationTests', - 'StatisticalTests', - # all statistics functions left out due to integration in other functions + 'load_csv', + 'basic_stats', + 'z_score', + 'z_normalize', + 'histo_analysis', + 'regression', + 'Metrics', + 'RegressionMetrics', + 'ClassificationMetrics', + 'kmeans', + 'pca', + 'decisiontree', + 'KNN', + 'NaiveBayes', + 'SVM', + 'random_forest_classifier', + 'random_forest_regressor', + 'CorrelationTests', + 'StatisticalTests', + # all statistics functions left out due to integration in other functions ] # now back to your regularly scheduled programming: @@ -316,35 +316,35 @@ from sklearn import preprocessing, pipeline, linear_model, metrics, cluster, dec from analysis.metrics import trueskill as Trueskill class error(ValueError): - pass + pass def load_csv(filepath): - with open(filepath, newline='') as csvfile: - file_array = np.array(list(csv.reader(csvfile))) - csvfile.close() - return file_array + with open(filepath, newline='') as csvfile: + file_array = np.array(list(csv.reader(csvfile))) + csvfile.close() + return file_array # expects 1d array @jit(forceobj=True) def basic_stats(data): - data_t = np.array(data).astype(float) + data_t = np.array(data).astype(float) - _mean = mean(data_t) - _median = median(data_t) - _stdev = stdev(data_t) - _variance = variance(data_t) - _min = npmin(data_t) - _max = npmax(data_t) + _mean = mean(data_t) + _median = median(data_t) + _stdev = stdev(data_t) + _variance = variance(data_t) + _min = npmin(data_t) + _max = npmax(data_t) - return _mean, _median, _stdev, _variance, _min, _max + return _mean, _median, _stdev, _variance, _min, _max # returns z score with inputs of point, mean and standard deviation of spread @jit(forceobj=True) def z_score(point, mean, stdev): - score = (point - mean) / stdev - - return score + score = (point - mean) / stdev + + return score # expects 2d array, normalizes across all axes @jit(forceobj=True) @@ -352,7 +352,7 @@ def z_normalize(array, *args): array = np.array(array) for arg in args: - array = sklearn.preprocessing.normalize(array, axis = arg) + array = sklearn.preprocessing.normalize(array, axis = arg) return array @@ -360,564 +360,564 @@ def z_normalize(array, *args): # expects 2d array of [x,y] def histo_analysis(hist_data): - if(len(hist_data[0]) > 2): + if(len(hist_data[0]) > 2): - hist_data = np.array(hist_data) - derivative = np.array(len(hist_data) - 1, dtype = float) - t = np.diff(hist_data) - derivative = t[1] / t[0] - np.sort(derivative) + hist_data = np.array(hist_data) + derivative = np.array(len(hist_data) - 1, dtype = float) + t = np.diff(hist_data) + derivative = t[1] / t[0] + np.sort(derivative) - return basic_stats(derivative)[0], basic_stats(derivative)[3] + return basic_stats(derivative)[0], basic_stats(derivative)[3] - else: + else: - return None + return None def regression(inputs, outputs, args): # inputs, outputs expects N-D array - X = np.array(inputs) - y = np.array(outputs) + X = np.array(inputs) + y = np.array(outputs) - regressions = [] + regressions = [] - if 'lin' in args: # formula: ax + b + if 'lin' in args: # formula: ax + b - try: + try: - def func(x, a, b): + def func(x, a, b): - return a * x + b + return a * x + b - popt, pcov = scipy.optimize.curve_fit(func, X, y) + popt, pcov = scipy.optimize.curve_fit(func, X, y) - regressions.append((popt.flatten().tolist(), None)) + regressions.append((popt.flatten().tolist(), None)) - except Exception as e: + except Exception as e: - pass + pass - if 'log' in args: # formula: a log (b(x + c)) + d + if 'log' in args: # formula: a log (b(x + c)) + d - try: + try: - def func(x, a, b, c, d): + def func(x, a, b, c, d): - return a * np.log(b*(x + c)) + d + return a * np.log(b*(x + c)) + d - popt, pcov = scipy.optimize.curve_fit(func, X, y) + popt, pcov = scipy.optimize.curve_fit(func, X, y) - regressions.append((popt.flatten().tolist(), None)) + regressions.append((popt.flatten().tolist(), None)) - except Exception as e: - - pass + except Exception as e: + + pass - if 'exp' in args: # formula: a e ^ (b(x + c)) + d + if 'exp' in args: # formula: a e ^ (b(x + c)) + d - try: + try: - def func(x, a, b, c, d): + def func(x, a, b, c, d): - return a * np.exp(b*(x + c)) + d + return a * np.exp(b*(x + c)) + d - popt, pcov = scipy.optimize.curve_fit(func, X, y) + popt, pcov = scipy.optimize.curve_fit(func, X, y) - regressions.append((popt.flatten().tolist(), None)) + regressions.append((popt.flatten().tolist(), None)) - except Exception as e: + except Exception as e: - pass + pass - if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... - - inputs = np.array([inputs]) - outputs = np.array([outputs]) + if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... + + inputs = np.array([inputs]) + outputs = np.array([outputs]) - plys = [] - limit = len(outputs[0]) + plys = [] + limit = len(outputs[0]) - for i in range(2, limit): + for i in range(2, limit): - model = sklearn.preprocessing.PolynomialFeatures(degree = i) - model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) - model = model.fit(np.rot90(inputs), np.rot90(outputs)) + model = sklearn.preprocessing.PolynomialFeatures(degree = i) + model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) + model = model.fit(np.rot90(inputs), np.rot90(outputs)) - params = model.steps[1][1].intercept_.tolist() - params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) - params.flatten() - params = params.tolist() - - plys.append(params) + params = model.steps[1][1].intercept_.tolist() + params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) + params.flatten() + params = params.tolist() + + plys.append(params) - regressions.append(plys) + regressions.append(plys) - if 'sig' in args: # formula: a tanh (b(x + c)) + d + if 'sig' in args: # formula: a tanh (b(x + c)) + d - try: + try: - def func(x, a, b, c, d): + def func(x, a, b, c, d): - return a * np.tanh(b*(x + c)) + d + return a * np.tanh(b*(x + c)) + d - popt, pcov = scipy.optimize.curve_fit(func, X, y) + popt, pcov = scipy.optimize.curve_fit(func, X, y) - regressions.append((popt.flatten().tolist(), None)) + regressions.append((popt.flatten().tolist(), None)) - except Exception as e: - - pass + except Exception as e: + + pass - return regressions + return regressions class Metrics: - def elo(self, starting_score, opposing_score, observed, N, K): + def elo(self, starting_score, opposing_score, observed, N, K): - return Elo.calculate(starting_score, opposing_score, observed, N, K) + return Elo.calculate(starting_score, opposing_score, observed, N, K) - def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + def glicko2(self, starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): - player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) + player = Glicko2.Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) - player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) + player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) - return (player.rating, player.rd, player.vol) + return (player.rating, player.rd, player.vol) - def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + def trueskill(self, teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] - team_ratings = [] + team_ratings = [] - for team in teams_data: - team_temp = () - for player in team: - player = Trueskill.Rating(player[0], player[1]) - team_temp = team_temp + (player,) - team_ratings.append(team_temp) + for team in teams_data: + team_temp = () + for player in team: + player = Trueskill.Rating(player[0], player[1]) + team_temp = team_temp + (player,) + team_ratings.append(team_temp) - return Trueskill.rate(team_ratings, ranks=observations) + return Trueskill.rate(team_ratings, ranks=observations) class RegressionMetrics(): - def __new__(cls, predictions, targets): + def __new__(cls, predictions, targets): - return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) + return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) - def r_squared(self, predictions, targets): # assumes equal size inputs + def r_squared(self, predictions, targets): # assumes equal size inputs - return sklearn.metrics.r2_score(targets, predictions) + return sklearn.metrics.r2_score(targets, predictions) - def mse(self, predictions, targets): + def mse(self, predictions, targets): - return sklearn.metrics.mean_squared_error(targets, predictions) + return sklearn.metrics.mean_squared_error(targets, predictions) - def rms(self, predictions, targets): + def rms(self, predictions, targets): - return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) + return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) class ClassificationMetrics(): - def __new__(cls, predictions, targets): + def __new__(cls, predictions, targets): - return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) + return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) - def cm(self, predictions, targets): + def cm(self, predictions, targets): - return sklearn.metrics.confusion_matrix(targets, predictions) + return sklearn.metrics.confusion_matrix(targets, predictions) - def cr(self, predictions, targets): + def cr(self, predictions, targets): - return sklearn.metrics.classification_report(targets, predictions) + return sklearn.metrics.classification_report(targets, predictions) @jit(nopython=True) def mean(data): - return np.mean(data) + return np.mean(data) @jit(nopython=True) def median(data): - return np.median(data) + return np.median(data) @jit(nopython=True) def stdev(data): - return np.std(data) + return np.std(data) @jit(nopython=True) def variance(data): - return np.var(data) + return np.var(data) @jit(nopython=True) def npmin(data): - return np.amin(data) + return np.amin(data) @jit(nopython=True) def npmax(data): - return np.amax(data) + return np.amax(data) @jit(forceobj=True) def kmeans(data, n_clusters=8, init="k-means++", n_init=10, max_iter=300, tol=0.0001, precompute_distances="auto", verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm="auto"): - kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) - kernel.fit(data) - predictions = kernel.predict(data) - centers = kernel.cluster_centers_ + kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) + kernel.fit(data) + predictions = kernel.predict(data) + centers = kernel.cluster_centers_ - return centers, predictions + return centers, predictions @jit(forceobj=True) def pca(data, n_components = None, copy = True, whiten = False, svd_solver = "auto", tol = 0.0, iterated_power = "auto", random_state = None): - kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) + kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) - return kernel.fit_transform(data) + return kernel.fit_transform(data) @jit(forceobj=True) def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = "default", max_depth = None): #expects *2d data and 1d labels - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) - model = model.fit(data_train,labels_train) - predictions = model.predict(data_test) - metrics = ClassificationMetrics(predictions, labels_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) + model = model.fit(data_train,labels_train) + predictions = model.predict(data_test) + metrics = ClassificationMetrics(predictions, labels_test) - return model, metrics + return model, metrics class KNN: - def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + def knn_classifier(self, data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.neighbors.KNeighborsClassifier() - model.fit(data_train, labels_train) - predictions = model.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsClassifier() + model.fit(data_train, labels_train) + predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetrics(predictions, labels_test) - def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): - data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) - model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) - model.fit(data_train, outputs_train) - predictions = model.predict(data_test) + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) + model.fit(data_train, outputs_train) + predictions = model.predict(data_test) - return model, RegressionMetrics(predictions, outputs_test) + return model, RegressionMetrics(predictions, outputs_test) class NaiveBayes: - def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): + def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetrics(predictions, labels_test) - def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): + def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetrics(predictions, labels_test) - def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): + def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetrics(predictions, labels_test) - def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): + def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) - model.fit(data_train, labels_train) - predictions = model.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetrics(predictions, labels_test) class SVM: - class CustomKernel: + class CustomKernel: - def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): + def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): - return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) - class StandardKernel: + class StandardKernel: - def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): + def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): - return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) - class PrebuiltKernel: + class PrebuiltKernel: - class Linear: + class Linear: - def __new__(cls): + def __new__(cls): - return sklearn.svm.SVC(kernel = 'linear') + return sklearn.svm.SVC(kernel = 'linear') - class Polynomial: + class Polynomial: - def __new__(cls, power, r_bias): + def __new__(cls, power, r_bias): - return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) + return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) - class RBF: + class RBF: - def __new__(cls, gamma): + def __new__(cls, gamma): - return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) + return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) - class Sigmoid: + class Sigmoid: - def __new__(cls, r_bias): + def __new__(cls, r_bias): - return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) + return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) - def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs + def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs - return kernel.fit(train_data, train_outputs) + return kernel.fit(train_data, train_outputs) - def eval_classification(self, kernel, test_data, test_outputs): + def eval_classification(self, kernel, test_data, test_outputs): - predictions = kernel.predict(test_data) + predictions = kernel.predict(test_data) - return ClassificationMetrics(predictions, test_outputs) + return ClassificationMetrics(predictions, test_outputs) - def eval_regression(self, kernel, test_data, test_outputs): + def eval_regression(self, kernel, test_data, test_outputs): - predictions = kernel.predict(test_data) + predictions = kernel.predict(test_data) - return RegressionMetrics(predictions, test_outputs) + return RegressionMetrics(predictions, test_outputs) def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) - kernel.fit(data_train, labels_train) - predictions = kernel.predict(data_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) + kernel.fit(data_train, labels_train) + predictions = kernel.predict(data_test) - return kernel, ClassificationMetrics(predictions, labels_test) + return kernel, ClassificationMetrics(predictions, labels_test) def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): - data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) - kernel.fit(data_train, outputs_train) - predictions = kernel.predict(data_test) + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) + kernel.fit(data_train, outputs_train) + predictions = kernel.predict(data_test) - return kernel, RegressionMetrics(predictions, outputs_test) + return kernel, RegressionMetrics(predictions, outputs_test) class CorrelationTests: - def anova_oneway(self, *args): #expects arrays of samples + def anova_oneway(self, *args): #expects arrays of samples - results = scipy.stats.f_oneway(*args) - return {"F-value": results[0], "p-value": results[1]} + results = scipy.stats.f_oneway(*args) + return {"F-value": results[0], "p-value": results[1]} - def pearson(self, x, y): + def pearson(self, x, y): - results = scipy.stats.pearsonr(x, y) - return {"r-value": results[0], "p-value": results[1]} + results = scipy.stats.pearsonr(x, y) + return {"r-value": results[0], "p-value": results[1]} - def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): + def spearman(self, a, b = None, axis = 0, nan_policy = 'propagate'): - results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) - return {"r-value": results[0], "p-value": results[1]} + results = scipy.stats.spearmanr(a, b = b, axis = axis, nan_policy = nan_policy) + return {"r-value": results[0], "p-value": results[1]} - def point_biserial(self, x,y): + def point_biserial(self, x,y): - results = scipy.stats.pointbiserialr(x, y) - return {"r-value": results[0], "p-value": results[1]} + results = scipy.stats.pointbiserialr(x, y) + return {"r-value": results[0], "p-value": results[1]} - def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): + def kendall(self, x, y, initial_lexsort = None, nan_policy = 'propagate', method = 'auto'): - results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) - return {"tau": results[0], "p-value": results[1]} + results = scipy.stats.kendalltau(x, y, initial_lexsort = initial_lexsort, nan_policy = nan_policy, method = method) + return {"tau": results[0], "p-value": results[1]} - def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): + def kendall_weighted(self, x, y, rank = True, weigher = None, additive = True): - results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) - return {"tau": results[0], "p-value": results[1]} + results = scipy.stats.weightedtau(x, y, rank = rank, weigher = weigher, additive = additive) + return {"tau": results[0], "p-value": results[1]} - def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): + def mgc(self, x, y, compute_distance = None, reps = 1000, workers = 1, is_twosamp = False, random_state = None): - results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) - return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value + results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) + return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value class StatisticalTests: - def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): + def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'): - results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.ttest_1samp(a, popmean, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} - def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): + def ttest_independent(self, a, b, equal = True, nan_policy = 'propagate'): - results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.ttest_ind(a, b, equal_var = equal, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} - def ttest_statistic(self, o1, o2, equal = True): + def ttest_statistic(self, o1, o2, equal = True): - results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.ttest_ind_from_stats(o1["mean"], o1["std"], o1["nobs"], o2["mean"], o2["std"], o2["nobs"], equal_var = equal) + return {"t-value": results[0], "p-value": results[1]} - def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): + def ttest_related(self, a, b, axis = 0, nan_policy='propagate'): - results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.ttest_rel(a, b, axis = axis, nan_policy = nan_policy) + return {"t-value": results[0], "p-value": results[1]} - def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): + def ks_fitness(self, rvs, cdf, args = (), N = 20, alternative = 'two-sided', mode = 'approx'): - results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) - return {"ks-value": results[0], "p-value": results[1]} + results = scipy.stats.kstest(rvs, cdf, args = args, N = N, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} - def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): + def chisquare(self, f_obs, f_exp = None, ddof = None, axis = 0): - results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) - return {"chisquared-value": results[0], "p-value": results[1]} + results = scipy.stats.chisquare(f_obs, f_exp = f_exp, ddof = ddof, axis = axis) + return {"chisquared-value": results[0], "p-value": results[1]} - def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): + def powerdivergence(self, f_obs, f_exp = None, ddof = None, axis = 0, lambda_ = None): - results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) - return {"powerdivergence-value": results[0], "p-value": results[1]} + results = scipy.stats.power_divergence(f_obs, f_exp = f_exp, ddof = ddof, axis = axis, lambda_ = lambda_) + return {"powerdivergence-value": results[0], "p-value": results[1]} - def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): - - results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) - return {"ks-value": results[0], "p-value": results[1]} + def ks_twosample(self, x, y, alternative = 'two_sided', mode = 'auto'): + + results = scipy.stats.ks_2samp(x, y, alternative = alternative, mode = mode) + return {"ks-value": results[0], "p-value": results[1]} - def es_twosample(self, x, y, t = (0.4, 0.8)): + def es_twosample(self, x, y, t = (0.4, 0.8)): - results = scipy.stats.epps_singleton_2samp(x, y, t = t) - return {"es-value": results[0], "p-value": results[1]} + results = scipy.stats.epps_singleton_2samp(x, y, t = t) + return {"es-value": results[0], "p-value": results[1]} - def mw_rank(self, x, y, use_continuity = True, alternative = None): + def mw_rank(self, x, y, use_continuity = True, alternative = None): - results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) - return {"u-value": results[0], "p-value": results[1]} + results = scipy.stats.mannwhitneyu(x, y, use_continuity = use_continuity, alternative = alternative) + return {"u-value": results[0], "p-value": results[1]} - def mw_tiecorrection(self, rank_values): + def mw_tiecorrection(self, rank_values): - results = scipy.stats.tiecorrect(rank_values) - return {"correction-factor": results} + results = scipy.stats.tiecorrect(rank_values) + return {"correction-factor": results} - def rankdata(self, a, method = 'average'): + def rankdata(self, a, method = 'average'): - results = scipy.stats.rankdata(a, method = method) - return results + results = scipy.stats.rankdata(a, method = method) + return results - def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test + def wilcoxon_ranksum(self, a, b): # this seems to be superceded by Mann Whitney Wilcoxon U Test - results = scipy.stats.ranksums(a, b) - return {"u-value": results[0], "p-value": results[1]} + results = scipy.stats.ranksums(a, b) + return {"u-value": results[0], "p-value": results[1]} - def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): + def wilcoxon_signedrank(self, x, y = None, zero_method = 'wilcox', correction = False, alternative = 'two-sided'): - results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.wilcoxon(x, y = y, zero_method = zero_method, correction = correction, alternative = alternative) + return {"t-value": results[0], "p-value": results[1]} - def kw_htest(self, *args, nan_policy = 'propagate'): + def kw_htest(self, *args, nan_policy = 'propagate'): - results = scipy.stats.kruskal(*args, nan_policy = nan_policy) - return {"h-value": results[0], "p-value": results[1]} + results = scipy.stats.kruskal(*args, nan_policy = nan_policy) + return {"h-value": results[0], "p-value": results[1]} - def friedman_chisquare(self, *args): + def friedman_chisquare(self, *args): - results = scipy.stats.friedmanchisquare(*args) - return {"chisquared-value": results[0], "p-value": results[1]} + results = scipy.stats.friedmanchisquare(*args) + return {"chisquared-value": results[0], "p-value": results[1]} - def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): + def bm_wtest(self, x, y, alternative = 'two-sided', distribution = 't', nan_policy = 'propagate'): - results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) - return {"w-value": results[0], "p-value": results[1]} + results = scipy.stats.brunnermunzel(x, y, alternative = alternative, distribution = distribution, nan_policy = nan_policy) + return {"w-value": results[0], "p-value": results[1]} - def combine_pvalues(self, pvalues, method = 'fisher', weights = None): + def combine_pvalues(self, pvalues, method = 'fisher', weights = None): - results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) - return {"combined-statistic": results[0], "p-value": results[1]} + results = scipy.stats.combine_pvalues(pvalues, method = method, weights = weights) + return {"combined-statistic": results[0], "p-value": results[1]} - def jb_fitness(self, x): + def jb_fitness(self, x): - results = scipy.stats.jarque_bera(x) - return {"jb-value": results[0], "p-value": results[1]} + results = scipy.stats.jarque_bera(x) + return {"jb-value": results[0], "p-value": results[1]} - def ab_equality(self, x, y): + def ab_equality(self, x, y): - results = scipy.stats.ansari(x, y) - return {"ab-value": results[0], "p-value": results[1]} + results = scipy.stats.ansari(x, y) + return {"ab-value": results[0], "p-value": results[1]} - def bartlett_variance(self, *args): + def bartlett_variance(self, *args): - results = scipy.stats.bartlett(*args) - return {"t-value": results[0], "p-value": results[1]} + results = scipy.stats.bartlett(*args) + return {"t-value": results[0], "p-value": results[1]} - def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): + def levene_variance(self, *args, center = 'median', proportiontocut = 0.05): - results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) - return {"w-value": results[0], "p-value": results[1]} + results = scipy.stats.levene(*args, center = center, proportiontocut = proportiontocut) + return {"w-value": results[0], "p-value": results[1]} - def sw_normality(self, x): + def sw_normality(self, x): - results = scipy.stats.shapiro(x) - return {"w-value": results[0], "p-value": results[1]} + results = scipy.stats.shapiro(x) + return {"w-value": results[0], "p-value": results[1]} - def shapiro(self, x): + def shapiro(self, x): - return "destroyed by facts and logic" + return "destroyed by facts and logic" - def ad_onesample(self, x, dist = 'norm'): + def ad_onesample(self, x, dist = 'norm'): - results = scipy.stats.anderson(x, dist = dist) - return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - - def ad_ksample(self, samples, midrank = True): + results = scipy.stats.anderson(x, dist = dist) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + + def ad_ksample(self, samples, midrank = True): - results = scipy.stats.anderson_ksamp(samples, midrank = midrank) - return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} + results = scipy.stats.anderson_ksamp(samples, midrank = midrank) + return {"d-value": results[0], "critical-values": results[1], "significance-value": results[2]} - def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): + def binomial(self, x, n = None, p = 0.5, alternative = 'two-sided'): - results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) - return {"p-value": results} + results = scipy.stats.binom_test(x, n = n, p = p, alternative = alternative) + return {"p-value": results} - def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): + def fk_variance(self, *args, center = 'median', proportiontocut = 0.05): - results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) - return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value + results = scipy.stats.fligner(*args, center = center, proportiontocut = proportiontocut) + return {"h-value": results[0], "p-value": results[1]} # unknown if the statistic is an h value - def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): + def mood_mediantest(self, *args, ties = 'below', correction = True, lambda_ = 1, nan_policy = 'propagate'): - results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) - return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} + results = scipy.stats.median_test(*args, ties = ties, correction = correction, lambda_ = lambda_, nan_policy = nan_policy) + return {"chisquared-value": results[0], "p-value": results[1], "m-value": results[2], "table": results[3]} - def mood_equalscale(self, x, y, axis = 0): + def mood_equalscale(self, x, y, axis = 0): - results = scipy.stats.mood(x, y, axis = axis) - return {"z-score": results[0], "p-value": results[1]} + results = scipy.stats.mood(x, y, axis = axis) + return {"z-score": results[0], "p-value": results[1]} - def skewtest(self, a, axis = 0, nan_policy = 'propogate'): + def skewtest(self, a, axis = 0, nan_policy = 'propogate'): - results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} + results = scipy.stats.skewtest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} - def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): + def kurtosistest(self, a, axis = 0, nan_policy = 'propogate'): - results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} + results = scipy.stats.kurtosistest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} - def normaltest(self, a, axis = 0, nan_policy = 'propogate'): + def normaltest(self, a, axis = 0, nan_policy = 'propogate'): - results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) - return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file + results = scipy.stats.normaltest(a, axis = axis, nan_policy = nan_policy) + return {"z-score": results[0], "p-value": results[1]} \ No newline at end of file diff --git a/analysis-master/analysis/metrics/elo.py b/analysis-master/analysis/metrics/elo.py index 3c8ef2e0..16ba4c59 100644 --- a/analysis-master/analysis/metrics/elo.py +++ b/analysis-master/analysis/metrics/elo.py @@ -2,6 +2,6 @@ import numpy as np def calculate(starting_score, opposing_score, observed, N, K): - expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) - return starting_score + K*(np.sum(observed) - np.sum(expected)) \ No newline at end of file + return starting_score + K*(np.sum(observed) - np.sum(expected)) \ No newline at end of file diff --git a/analysis-master/analysis/metrics/glicko2.py b/analysis-master/analysis/metrics/glicko2.py index 66c0df94..068534aa 100644 --- a/analysis-master/analysis/metrics/glicko2.py +++ b/analysis-master/analysis/metrics/glicko2.py @@ -1,99 +1,99 @@ import math class Glicko2: - _tau = 0.5 + _tau = 0.5 - def getRating(self): - return (self.__rating * 173.7178) + 1500 + def getRating(self): + return (self.__rating * 173.7178) + 1500 - def setRating(self, rating): - self.__rating = (rating - 1500) / 173.7178 + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 - rating = property(getRating, setRating) + rating = property(getRating, setRating) - def getRd(self): - return self.__rd * 173.7178 + def getRd(self): + return self.__rd * 173.7178 - def setRd(self, rd): - self.__rd = rd / 173.7178 + def setRd(self, rd): + self.__rd = rd / 173.7178 - rd = property(getRd, setRd) - - def __init__(self, rating = 1500, rd = 350, vol = 0.06): + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): - self.setRating(rating) - self.setRd(rd) - self.vol = vol - - def _preRatingRD(self): + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): - self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) - - def update_player(self, rating_list, RD_list, outcome_list): + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): - rating_list = [(x - 1500) / 173.7178 for x in rating_list] - RD_list = [x / 173.7178 for x in RD_list] + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] - v = self._v(rating_list, RD_list) - self.vol = self._newVol(rating_list, RD_list, outcome_list, v) - self._preRatingRD() - - self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) - - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * \ - (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - self.__rating += math.pow(self.__rd, 2) * tempSum - - - def _newVol(self, rating_list, RD_list, outcome_list, v): + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): - i = 0 - delta = self._delta(rating_list, RD_list, outcome_list, v) - a = math.log(math.pow(self.vol, 2)) - tau = self._tau - x0 = a - x1 = 0 - - while x0 != x1: - # New iteration, so x(i) becomes x(i-1) - x0 = x1 - d = math.pow(self.__rating, 2) + v + math.exp(x0) - h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ - / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) - h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ - (math.pow(self.__rating, 2) + v) \ - / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ - * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) - x1 = x0 - (h1 / h2) + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) - return math.exp(x1 / 2) - - def _delta(self, rating_list, RD_list, outcome_list, v): + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): - tempSum = 0 - for i in range(len(rating_list)): - tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) - return v * tempSum - - def _v(self, rating_list, RD_list): + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): - tempSum = 0 - for i in range(len(rating_list)): - tempE = self._E(rating_list[i], RD_list[i]) - tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) - return 1 / tempSum - - def _E(self, p2rating, p2RD): + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): - return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ - (self.__rating - p2rating))) - - def _g(self, RD): + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): - return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) - - def did_not_compete(self): + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): - self._preRatingRD() \ No newline at end of file + self._preRatingRD() \ No newline at end of file diff --git a/analysis-master/analysis/metrics/trueskill.py b/analysis-master/analysis/metrics/trueskill.py index 116357df..e287336d 100644 --- a/analysis-master/analysis/metrics/trueskill.py +++ b/analysis-master/analysis/metrics/trueskill.py @@ -9,484 +9,484 @@ from six import iterkeys import copy try: - from numbers import Number + from numbers import Number except ImportError: - Number = (int, long, float, complex) + Number = (int, long, float, complex) inf = float('inf') class Gaussian(object): - #: Precision, the inverse of the variance. - pi = 0 - #: Precision adjusted mean, the precision multiplied by the mean. - tau = 0 + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 - def __init__(self, mu=None, sigma=None, pi=0, tau=0): - if mu is not None: - if sigma is None: - raise TypeError('sigma argument is needed') - elif sigma == 0: - raise ValueError('sigma**2 should be greater than 0') - pi = sigma ** -2 - tau = pi * mu - self.pi = pi - self.tau = tau + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau - @property - def mu(self): - return self.pi and self.tau / self.pi + @property + def mu(self): + return self.pi and self.tau / self.pi - @property - def sigma(self): - return math.sqrt(1 / self.pi) if self.pi else inf + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf - def __mul__(self, other): - pi, tau = self.pi + other.pi, self.tau + other.tau - return Gaussian(pi=pi, tau=tau) + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) - def __truediv__(self, other): - pi, tau = self.pi - other.pi, self.tau - other.tau - return Gaussian(pi=pi, tau=tau) + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) - __div__ = __truediv__ # for Python 2 + __div__ = __truediv__ # for Python 2 - def __eq__(self, other): - return self.pi == other.pi and self.tau == other.tau + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau - def __lt__(self, other): - return self.mu < other.mu + def __lt__(self, other): + return self.mu < other.mu - def __le__(self, other): - return self.mu <= other.mu + def __le__(self, other): + return self.mu <= other.mu - def __gt__(self, other): - return self.mu > other.mu + def __gt__(self, other): + return self.mu > other.mu - def __ge__(self, other): - return self.mu >= other.mu + def __ge__(self, other): + return self.mu >= other.mu - def __repr__(self): - return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) - def _repr_latex_(self): - latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) - return '$%s$' % latex + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex class Matrix(list): - def __init__(self, src, height=None, width=None): - if callable(src): - f, src = src, {} - size = [height, width] - if not height: - def set_height(height): - size[0] = height - size[0] = set_height - if not width: - def set_width(width): - size[1] = width - size[1] = set_width - try: - for (r, c), val in f(*size): - src[r, c] = val - except TypeError: - raise TypeError('A callable src must return an interable ' - 'which generates a tuple containing ' - 'coordinate and value') - height, width = tuple(size) - if height is None or width is None: - raise TypeError('A callable src must call set_height and ' - 'set_width if the size is non-deterministic') - if isinstance(src, list): - is_number = lambda x: isinstance(x, Number) - unique_col_sizes = set(map(len, src)) - everything_are_number = filter(is_number, sum(src, [])) - if len(unique_col_sizes) != 1 or not everything_are_number: - raise ValueError('src must be a rectangular array of numbers') - two_dimensional_array = src - elif isinstance(src, dict): - if not height or not width: - w = h = 0 - for r, c in iterkeys(src): - if not height: - h = max(h, r + 1) - if not width: - w = max(w, c + 1) - if not height: - height = h - if not width: - width = w - two_dimensional_array = [] - for r in range(height): - row = [] - two_dimensional_array.append(row) - for c in range(width): - row.append(src.get((r, c), 0)) - else: - raise TypeError('src must be a list or dict or callable') - super(Matrix, self).__init__(two_dimensional_array) + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) - @property - def height(self): - return len(self) + @property + def height(self): + return len(self) - @property - def width(self): - return len(self[0]) + @property + def width(self): + return len(self[0]) - def transpose(self): - height, width = self.height, self.width - src = {} - for c in range(width): - for r in range(height): - src[c, r] = self[r][c] - return type(self)(src, height=width, width=height) + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) - def minor(self, row_n, col_n): - height, width = self.height, self.width - if not (0 <= row_n < height): - raise ValueError('row_n should be between 0 and %d' % height) - elif not (0 <= col_n < width): - raise ValueError('col_n should be between 0 and %d' % width) - two_dimensional_array = [] - for r in range(height): - if r == row_n: - continue - row = [] - two_dimensional_array.append(row) - for c in range(width): - if c == col_n: - continue - row.append(self[r][c]) - return type(self)(two_dimensional_array) + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) - def determinant(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can calculate a determinant') - tmp, rv = copy.deepcopy(self), 1. - for c in range(width - 1, 0, -1): - pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) - pivot = tmp[r][c] - if not pivot: - return 0. - tmp[r], tmp[c] = tmp[c], tmp[r] - if r != c: - rv = -rv - rv *= pivot - fact = -1. / pivot - for r in range(c): - f = fact * tmp[r][c] - for x in range(c): - tmp[r][x] += f * tmp[c][x] - return rv * tmp[0][0] + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] - def adjugate(self): - height, width = self.height, self.width - if height != width: - raise ValueError('Only square matrix can be adjugated') - if height == 2: - a, b = self[0][0], self[0][1] - c, d = self[1][0], self[1][1] - return type(self)([[d, -b], [-c, a]]) - src = {} - for r in range(height): - for c in range(width): - sign = -1 if (r + c) % 2 else 1 - src[r, c] = self.minor(r, c).determinant() * sign - return type(self)(src, height, width) + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) - def inverse(self): - if self.height == self.width == 1: - return type(self)([[1. / self[0][0]]]) - return (1. / self.determinant()) * self.adjugate() + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() - def __add__(self, other): - height, width = self.height, self.width - if (height, width) != (other.height, other.width): - raise ValueError('Must be same size') - src = {} - for r in range(height): - for c in range(width): - src[r, c] = self[r][c] + other[r][c] - return type(self)(src, height, width) + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) - def __mul__(self, other): - if self.width != other.height: - raise ValueError('Bad size') - height, width = self.height, other.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = sum(self[r][x] * other[x][c] - for x in range(self.width)) - return type(self)(src, height, width) + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) - def __rmul__(self, other): - if not isinstance(other, Number): - raise TypeError('The operand should be a number') - height, width = self.height, self.width - src = {} - for r in range(height): - for c in range(width): - src[r, c] = other * self[r][c] - return type(self)(src, height, width) + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) - def __repr__(self): - return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) - def _repr_latex_(self): - rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] - latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) - return '$%s$' % latex + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex def _gen_erfcinv(erfc, math=math): - def erfcinv(y): - """The inverse function of erfc.""" - if y >= 2: - return -100. - elif y <= 0: - return 100. - zero_point = y < 1 - if not zero_point: - y = 2 - y - t = math.sqrt(-2 * math.log(y / 2.)) - x = -0.70711 * \ - ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) - for i in range(2): - err = erfc(x) - y - x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) - return x if zero_point else -x - return erfcinv + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv def _gen_ppf(erfc, math=math): - erfcinv = _gen_erfcinv(erfc, math) - def ppf(x, mu=0, sigma=1): - return mu - sigma * math.sqrt(2) * erfcinv(2 * x) - return ppf + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf def erfc(x): - z = abs(x) - t = 1. / (1. + z / 2.) - r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( - 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( - 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( - -0.82215223 + t * 0.17087277 - ))) - ))) - ))) - return 2. - r if x < 0 else r + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r def cdf(x, mu=0, sigma=1): - return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) def pdf(x, mu=0, sigma=1): - return (1 / math.sqrt(2 * math.pi) * abs(sigma) * - math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) ppf = _gen_ppf(erfc) def choose_backend(backend): - if backend is None: # fallback - return cdf, pdf, ppf - elif backend == 'mpmath': - try: - import mpmath - except ImportError: - raise ImportError('Install "mpmath" to use this backend') - return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) - elif backend == 'scipy': - try: - from scipy.stats import norm - except ImportError: - raise ImportError('Install "scipy" to use this backend') - return norm.cdf, norm.pdf, norm.ppf - raise ValueError('%r backend is not defined' % backend) + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) def available_backends(): - backends = [None] - for backend in ['mpmath', 'scipy']: - try: - __import__(backend) - except ImportError: - continue - backends.append(backend) - return backends + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends class Node(object): - pass + pass class Variable(Node, Gaussian): - def __init__(self): - self.messages = {} - super(Variable, self).__init__() + def __init__(self): + self.messages = {} + super(Variable, self).__init__() - def set(self, val): - delta = self.delta(val) - self.pi, self.tau = val.pi, val.tau - return delta + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta - def delta(self, other): - pi_delta = abs(self.pi - other.pi) - if pi_delta == inf: - return 0. - return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) - def update_message(self, factor, pi=0, tau=0, message=None): - message = message or Gaussian(pi=pi, tau=tau) - old_message, self[factor] = self[factor], message - return self.set(self / old_message * message) + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) - def update_value(self, factor, pi=0, tau=0, value=None): - value = value or Gaussian(pi=pi, tau=tau) - old_message = self[factor] - self[factor] = value * old_message / self - return self.set(value) + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) - def __getitem__(self, factor): - return self.messages[factor] + def __getitem__(self, factor): + return self.messages[factor] - def __setitem__(self, factor, message): - self.messages[factor] = message + def __setitem__(self, factor, message): + self.messages[factor] = message - def __repr__(self): - args = (type(self).__name__, super(Variable, self).__repr__(), - len(self.messages), '' if len(self.messages) == 1 else 's') - return '<%s %s with %d connection%s>' % args + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args class Factor(Node): - def __init__(self, variables): - self.vars = variables - for var in variables: - var[self] = Gaussian() + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() - def down(self): - return 0 + def down(self): + return 0 - def up(self): - return 0 + def up(self): + return 0 - @property - def var(self): - assert len(self.vars) == 1 - return self.vars[0] + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] - def __repr__(self): - args = (type(self).__name__, len(self.vars), - '' if len(self.vars) == 1 else 's') - return '<%s with %d connection%s>' % args + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args class PriorFactor(Factor): - def __init__(self, var, val, dynamic=0): - super(PriorFactor, self).__init__([var]) - self.val = val - self.dynamic = dynamic + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic - def down(self): - sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) - value = Gaussian(self.val.mu, sigma) - return self.var.update_value(self, value=value) + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) class LikelihoodFactor(Factor): - def __init__(self, mean_var, value_var, variance): - super(LikelihoodFactor, self).__init__([mean_var, value_var]) - self.mean = mean_var - self.value = value_var - self.variance = variance + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance - def calc_a(self, var): - return 1. / (1. + self.variance * var.pi) + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) - def down(self): - # update value. - msg = self.mean / self.mean[self] - a = self.calc_a(msg) - return self.value.update_message(self, a * msg.pi, a * msg.tau) + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) - def up(self): - # update mean. - msg = self.value / self.value[self] - a = self.calc_a(msg) - return self.mean.update_message(self, a * msg.pi, a * msg.tau) + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) class SumFactor(Factor): - def __init__(self, sum_var, term_vars, coeffs): - super(SumFactor, self).__init__([sum_var] + term_vars) - self.sum = sum_var - self.terms = term_vars - self.coeffs = coeffs + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs - def down(self): - vals = self.terms - msgs = [var[self] for var in vals] - return self.update(self.sum, vals, msgs, self.coeffs) + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) - def up(self, index=0): - coeff = self.coeffs[index] - coeffs = [] - for x, c in enumerate(self.coeffs): - try: - if x == index: - coeffs.append(1. / coeff) - else: - coeffs.append(-c / coeff) - except ZeroDivisionError: - coeffs.append(0.) - vals = self.terms[:] - vals[index] = self.sum - msgs = [var[self] for var in vals] - return self.update(self.terms[index], vals, msgs, coeffs) + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) - def update(self, var, vals, msgs, coeffs): - pi_inv = 0 - mu = 0 - for val, msg, coeff in zip(vals, msgs, coeffs): - div = val / msg - mu += coeff * div.mu - if pi_inv == inf: - continue - try: - # numpy.float64 handles floating-point error by different way. - # For example, it can just warn RuntimeWarning on n/0 problem - # instead of throwing ZeroDivisionError. So div.pi, the - # denominator has to be a built-in float. - pi_inv += coeff ** 2 / float(div.pi) - except ZeroDivisionError: - pi_inv = inf - pi = 1. / pi_inv - tau = pi * mu - return var.update_message(self, pi, tau) + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) class TruncateFactor(Factor): - def __init__(self, var, v_func, w_func, draw_margin): - super(TruncateFactor, self).__init__([var]) - self.v_func = v_func - self.w_func = w_func - self.draw_margin = draw_margin + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin - def up(self): - val = self.var - msg = self.var[self] - div = val / msg - sqrt_pi = math.sqrt(div.pi) - args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) - v = self.v_func(*args) - w = self.w_func(*args) - denom = (1. - w) - pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom - return val.update_value(self, pi, tau) + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) #: Default initial mean of ratings. MU = 25. @@ -503,405 +503,405 @@ DELTA = 0.0001 def calc_draw_probability(draw_margin, size, env=None): - if env is None: - env = global_env() - return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 def calc_draw_margin(draw_probability, size, env=None): - if env is None: - env = global_env() - return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta def _team_sizes(rating_groups): - team_sizes = [0] - for group in rating_groups: - team_sizes.append(len(group) + team_sizes[-1]) - del team_sizes[0] - return team_sizes + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes def _floating_point_error(env): - if env.backend == 'mpmath': - msg = 'Set "mpmath.mp.dps" to higher' - else: - msg = 'Cannot calculate correctly, set backend to "mpmath"' - return FloatingPointError(msg) + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) class Rating(Gaussian): - def __init__(self, mu=None, sigma=None): - if isinstance(mu, tuple): - mu, sigma = mu - elif isinstance(mu, Gaussian): - mu, sigma = mu.mu, mu.sigma - if mu is None: - mu = global_env().mu - if sigma is None: - sigma = global_env().sigma - super(Rating, self).__init__(mu, sigma) + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) - def __int__(self): - return int(self.mu) + def __int__(self): + return int(self.mu) - def __long__(self): - return long(self.mu) + def __long__(self): + return long(self.mu) - def __float__(self): - return float(self.mu) + def __float__(self): + return float(self.mu) - def __iter__(self): - return iter((self.mu, self.sigma)) + def __iter__(self): + return iter((self.mu, self.sigma)) - def __repr__(self): - c = type(self) - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) - return '%s(mu=%.3f, sigma=%.3f)' % args + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args class TrueSkill(object): - def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None): - self.mu = mu - self.sigma = sigma - self.beta = beta - self.tau = tau - self.draw_probability = draw_probability - self.backend = backend - if isinstance(backend, tuple): - self.cdf, self.pdf, self.ppf = backend - else: - self.cdf, self.pdf, self.ppf = choose_backend(backend) + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) - def create_rating(self, mu=None, sigma=None): - if mu is None: - mu = self.mu - if sigma is None: - sigma = self.sigma - return Rating(mu, sigma) + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) - def v_win(self, diff, draw_margin): - x = diff - draw_margin - denom = self.cdf(x) - return (self.pdf(x) / denom) if denom else -x + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x - def v_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - numer = self.pdf(b) - self.pdf(a) - return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) - def w_win(self, diff, draw_margin): - x = diff - draw_margin - v = self.v_win(diff, draw_margin) - w = v * (v + x) - if 0 < w < 1: - return w - raise _floating_point_error(self) + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) - def w_draw(self, diff, draw_margin): - abs_diff = abs(diff) - a, b = draw_margin - abs_diff, -draw_margin - abs_diff - denom = self.cdf(a) - self.cdf(b) - if not denom: - raise _floating_point_error(self) - v = self.v_draw(abs_diff, draw_margin) - return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom - def validate_rating_groups(self, rating_groups): - # check group sizes - if len(rating_groups) < 2: - raise ValueError('Need multiple rating groups') - elif not all(rating_groups): - raise ValueError('Each group must contain multiple ratings') - # check group types - group_types = set(map(type, rating_groups)) - if len(group_types) != 1: - raise TypeError('All groups should be same type') - elif group_types.pop() is Rating: - raise TypeError('Rating cannot be a rating group') - # normalize rating_groups - if isinstance(rating_groups[0], dict): - dict_rating_groups = rating_groups - rating_groups = [] - keys = [] - for dict_rating_group in dict_rating_groups: - rating_group, key_group = [], [] - for key, rating in iteritems(dict_rating_group): - rating_group.append(rating) - key_group.append(key) - rating_groups.append(tuple(rating_group)) - keys.append(tuple(key_group)) - else: - rating_groups = list(rating_groups) - keys = None - return rating_groups, keys + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys - def validate_weights(self, weights, rating_groups, keys=None): - if weights is None: - weights = [(1,) * len(g) for g in rating_groups] - elif isinstance(weights, dict): - weights_dict, weights = weights, [] - for x, group in enumerate(rating_groups): - w = [] - weights.append(w) - for y, rating in enumerate(group): - if keys is not None: - y = keys[x][y] - w.append(weights_dict.get((x, y), 1)) - return weights + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights - def factor_graph_builders(self, rating_groups, ranks, weights): - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - size = len(flatten_ratings) - group_size = len(rating_groups) - # create variables - rating_vars = [Variable() for x in range(size)] - perf_vars = [Variable() for x in range(size)] - team_perf_vars = [Variable() for x in range(group_size)] - team_diff_vars = [Variable() for x in range(group_size - 1)] - team_sizes = _team_sizes(rating_groups) - # layer builders - def build_rating_layer(): - for rating_var, rating in zip(rating_vars, flatten_ratings): - yield PriorFactor(rating_var, rating, self.tau) - def build_perf_layer(): - for rating_var, perf_var in zip(rating_vars, perf_vars): - yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) - def build_team_perf_layer(): - for team, team_perf_var in enumerate(team_perf_vars): - if team > 0: - start = team_sizes[team - 1] - else: - start = 0 - end = team_sizes[team] - child_perf_vars = perf_vars[start:end] - coeffs = flatten_weights[start:end] - yield SumFactor(team_perf_var, child_perf_vars, coeffs) - def build_team_diff_layer(): - for team, team_diff_var in enumerate(team_diff_vars): - yield SumFactor(team_diff_var, - team_perf_vars[team:team + 2], [+1, -1]) - def build_trunc_layer(): - for x, team_diff_var in enumerate(team_diff_vars): - if callable(self.draw_probability): - # dynamic draw probability - team_perf1, team_perf2 = team_perf_vars[x:x + 2] - args = (Rating(team_perf1), Rating(team_perf2), self) - draw_probability = self.draw_probability(*args) - else: - # static draw probability - draw_probability = self.draw_probability - size = sum(map(len, rating_groups[x:x + 2])) - draw_margin = calc_draw_margin(draw_probability, size, self) - if ranks[x] == ranks[x + 1]: # is a tie? - v_func, w_func = self.v_draw, self.w_draw - else: - v_func, w_func = self.v_win, self.w_win - yield TruncateFactor(team_diff_var, - v_func, w_func, draw_margin) - # build layers - return (build_rating_layer, build_perf_layer, build_team_perf_layer, - build_team_diff_layer, build_trunc_layer) + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) - def run_schedule(self, build_rating_layer, build_perf_layer, - build_team_perf_layer, build_team_diff_layer, - build_trunc_layer, min_delta=DELTA): - if min_delta <= 0: - raise ValueError('min_delta must be greater than 0') - layers = [] - def build(builders): - layers_built = [list(build()) for build in builders] - layers.extend(layers_built) - return layers_built - # gray arrows - layers_built = build([build_rating_layer, - build_perf_layer, - build_team_perf_layer]) - rating_layer, perf_layer, team_perf_layer = layers_built - for f in chain(*layers_built): - f.down() - # arrow #1, #2, #3 - team_diff_layer, trunc_layer = build([build_team_diff_layer, - build_trunc_layer]) - team_diff_len = len(team_diff_layer) - for x in range(10): - if team_diff_len == 1: - # only two teams - team_diff_layer[0].down() - delta = trunc_layer[0].up() - else: - # multiple teams - delta = 0 - for x in range(team_diff_len - 1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(1) # up to right variable - for x in range(team_diff_len - 1, 0, -1): - team_diff_layer[x].down() - delta = max(delta, trunc_layer[x].up()) - team_diff_layer[x].up(0) # up to left variable - # repeat until to small update - if delta <= min_delta: - break - # up both ends - team_diff_layer[0].up(0) - team_diff_layer[team_diff_len - 1].up(1) - # up the remainder of the black arrows - for f in team_perf_layer: - for x in range(len(f.vars) - 1): - f.up(x) - for f in perf_layer: - f.up() - return layers + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers - def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - group_size = len(rating_groups) - if ranks is None: - ranks = range(group_size) - elif len(ranks) != group_size: - raise ValueError('Wrong ranks') - # sort rating groups by rank - by_rank = lambda x: x[1][1] - sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), - key=by_rank) - sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] - for x, (g, r, w) in sorting: - sorted_rating_groups.append(g) - sorted_ranks.append(r) - # make weights to be greater than 0 - sorted_weights.append(max(min_delta, w_) for w_ in w) - # build factor graph - args = (sorted_rating_groups, sorted_ranks, sorted_weights) - builders = self.factor_graph_builders(*args) - args = builders + (min_delta,) - layers = self.run_schedule(*args) - # make result - rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) - transformed_groups = [] - for start, end in zip([0] + team_sizes[:-1], team_sizes): - group = [] - for f in rating_layer[start:end]: - group.append(Rating(float(f.var.mu), float(f.var.sigma))) - transformed_groups.append(tuple(group)) - by_hint = lambda x: x[0] - unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), - key=by_hint) - if keys is None: - return [g for x, g in unsorting] - # restore the structure with input dictionary keys - return [dict(zip(keys[x], g)) for x, g in unsorting] + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] - def quality(self, rating_groups, weights=None): - rating_groups, keys = self.validate_rating_groups(rating_groups) - weights = self.validate_weights(weights, rating_groups, keys) - flatten_ratings = sum(map(tuple, rating_groups), ()) - flatten_weights = sum(map(tuple, weights), ()) - length = len(flatten_ratings) - # a vector of all of the skill means - mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) - # a matrix whose diagonal values are the variances (sigma ** 2) of each - # of the players. - def variance_matrix(height, width): - variances = (r.sigma ** 2 for r in flatten_ratings) - for x, variance in enumerate(variances): - yield (x, x), variance - variance_matrix = Matrix(variance_matrix, length, length) - # the player-team assignment and comparison matrix - def rotated_a_matrix(set_height, set_width): - t = 0 - for r, (cur, _next) in enumerate(zip(rating_groups[:-1], - rating_groups[1:])): - for x in range(t, t + len(cur)): - yield (r, x), flatten_weights[x] - t += 1 - x += 1 - for x in range(x, x + len(_next)): - yield (r, x), -flatten_weights[x] - set_height(r + 1) - set_width(x + 1) - rotated_a_matrix = Matrix(rotated_a_matrix) - a_matrix = rotated_a_matrix.transpose() - # match quality further derivation - _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix - _atsa = rotated_a_matrix * variance_matrix * a_matrix - start = mean_matrix.transpose() * a_matrix - middle = _ata + _atsa - end = rotated_a_matrix * mean_matrix - # make result - e_arg = (-0.5 * start * middle.inverse() * end).determinant() - s_arg = _ata.determinant() / middle.determinant() - return math.exp(e_arg) * math.sqrt(s_arg) + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) - def expose(self, rating): - k = self.mu / self.sigma - return rating.mu - k * rating.sigma + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma - def make_as_global(self): - return setup(env=self) + def make_as_global(self): + return setup(env=self) - def __repr__(self): - c = type(self) - if callable(self.draw_probability): - f = self.draw_probability - draw_probability = '.'.join([f.__module__, f.__name__]) - else: - draw_probability = '%.1f%%' % (self.draw_probability * 100) - if self.backend is None: - backend = '' - elif isinstance(self.backend, tuple): - backend = ', backend=...' - else: - backend = ', backend=%r' % self.backend - args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, - self.beta, self.tau, draw_probability, backend) - return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' - 'draw_probability=%s%s)' % args) + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): - if env is None: - env = global_env() - ranks = [0, 0 if drawn else 1] - teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) - return teams[0][0], teams[1][0] + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] def quality_1vs1(rating1, rating2, env=None): - if env is None: - env = global_env() - return env.quality([(rating1,), (rating2,)]) + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) def global_env(): - try: - global_env.__trueskill__ - except AttributeError: - # setup the default environment - setup() - return global_env.__trueskill__ + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, - draw_probability=DRAW_PROBABILITY, backend=None, env=None): - if env is None: - env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) - global_env.__trueskill__ = env - return env + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): - return global_env().rate(rating_groups, ranks, weights, min_delta) + return global_env().rate(rating_groups, ranks, weights, min_delta) def quality(rating_groups, weights=None): - return global_env().quality(rating_groups, weights) + return global_env().quality(rating_groups, weights) def expose(rating): - return global_env().expose(rating) \ No newline at end of file + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/analysis/regression.py b/analysis-master/analysis/regression.py index e899e9ff..423f47e4 100644 --- a/analysis-master/analysis/regression.py +++ b/analysis-master/analysis/regression.py @@ -9,38 +9,38 @@ __version__ = "1.0.0.004" # changelog should be viewed using print(analysis.regression.__changelog__) __changelog__ = """ - 1.0.0.004: - - bug fixes - - fixed changelog - 1.0.0.003: - - bug fixes - 1.0.0.002: - -Added more parameters to log, exponential, polynomial - -Added SigmoidalRegKernelArthur, because Arthur apparently needs - to train the scaling and shifting of sigmoids - 1.0.0.001: - -initial release, with linear, log, exponential, polynomial, and sigmoid kernels - -already vectorized (except for polynomial generation) and CUDA-optimized + 1.0.0.004: + - bug fixes + - fixed changelog + 1.0.0.003: + - bug fixes + 1.0.0.002: + -Added more parameters to log, exponential, polynomial + -Added SigmoidalRegKernelArthur, because Arthur apparently needs + to train the scaling and shifting of sigmoids + 1.0.0.001: + -initial release, with linear, log, exponential, polynomial, and sigmoid kernels + -already vectorized (except for polynomial generation) and CUDA-optimized """ __author__ = ( - "Jacob Levine ", - "Arthur Lu " + "Jacob Levine ", + "Arthur Lu " ) __all__ = [ - 'factorial', - 'take_all_pwrs', - 'num_poly_terms', - 'set_device', - 'LinearRegKernel', - 'SigmoidalRegKernel', - 'LogRegKernel', - 'PolyRegKernel', - 'ExpRegKernel', - 'SigmoidalRegKernelArthur', - 'SGDTrain', - 'CustomTrain' + 'factorial', + 'take_all_pwrs', + 'num_poly_terms', + 'set_device', + 'LinearRegKernel', + 'SigmoidalRegKernel', + 'LogRegKernel', + 'PolyRegKernel', + 'ExpRegKernel', + 'SigmoidalRegKernelArthur', + 'SGDTrain', + 'CustomTrain' ] import torch @@ -52,169 +52,169 @@ device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" #todo: document completely def set_device(self, new_device): - device=new_device + device=new_device class LinearRegKernel(): - parameters= [] - weights=None - bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def forward(self,mtx): - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return torch.matmul(self.weights,mtx)+long_bias + parameters= [] + weights=None + bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,mtx)+long_bias class SigmoidalRegKernel(): - parameters= [] - weights=None - bias=None - sigmoid=torch.nn.Sigmoid() - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def forward(self,mtx): - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) + parameters= [] + weights=None + bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) class SigmoidalRegKernelArthur(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - sigmoid=torch.nn.Sigmoid() - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias class LogRegKernel(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias class ExpRegKernel(): - parameters= [] - weights=None - in_bias=None - scal_mult=None - out_bias=None - def __init__(self, num_vars): - self.weights=torch.rand(num_vars, requires_grad=True, device=device) - self.in_bias=torch.rand(1, requires_grad=True, device=device) - self.scal_mult=torch.rand(1, requires_grad=True, device=device) - self.out_bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] - def forward(self,mtx): - long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) - long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) - return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias class PolyRegKernel(): - parameters= [] - weights=None - bias=None - power=None - def __init__(self, num_vars, power): - self.power=power - num_terms=self.num_poly_terms(num_vars, power) - self.weights=torch.rand(num_terms, requires_grad=True, device=device) - self.bias=torch.rand(1, requires_grad=True, device=device) - self.parameters=[self.weights,self.bias] - def num_poly_terms(self,num_vars, power): - if power == 0: - return 0 - return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) - def factorial(self,n): - if n==0: - return 1 - else: - return n*self.factorial(n-1) - def take_all_pwrs(self, vec, pwr): - #todo: vectorize (kinda) - combins=torch.combinations(vec, r=pwr, with_replacement=True) - out=torch.ones(combins.size()[0]).to(device).to(torch.float) - for i in torch.t(combins).to(device).to(torch.float): - out *= i - if pwr == 1: - return out - else: - return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) - def forward(self,mtx): - #TODO: Vectorize the last part - cols=[] - for i in torch.t(mtx): - cols.append(self.take_all_pwrs(i,self.power)) - new_mtx=torch.t(torch.stack(cols)) - long_bias=self.bias.repeat([1,mtx.size()[1]]) - return torch.matmul(self.weights,new_mtx)+long_bias + parameters= [] + weights=None + bias=None + power=None + def __init__(self, num_vars, power): + self.power=power + num_terms=self.num_poly_terms(num_vars, power) + self.weights=torch.rand(num_terms, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def num_poly_terms(self,num_vars, power): + if power == 0: + return 0 + return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) + def factorial(self,n): + if n==0: + return 1 + else: + return n*self.factorial(n-1) + def take_all_pwrs(self, vec, pwr): + #todo: vectorize (kinda) + combins=torch.combinations(vec, r=pwr, with_replacement=True) + out=torch.ones(combins.size()[0]).to(device).to(torch.float) + for i in torch.t(combins).to(device).to(torch.float): + out *= i + if pwr == 1: + return out + else: + return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) + def forward(self,mtx): + #TODO: Vectorize the last part + cols=[] + for i in torch.t(mtx): + cols.append(self.take_all_pwrs(i,self.power)) + new_mtx=torch.t(torch.stack(cols)) + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,new_mtx)+long_bias def SGDTrain(self, kernel, data, ground, loss=torch.nn.MSELoss(), iterations=1000, learning_rate=.1, return_losses=False): - optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) - data_cuda=data.to(device) - ground_cuda=ground.to(device) - if (return_losses): - losses=[] - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - losses.append(ls.item()) - ls.backward() - optim.step() - return [kernel,losses] - else: - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - ls.backward() - optim.step() - return kernel + optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iterations=1000, return_losses=False): - data_cuda=data.to(device) - ground_cuda=ground.to(device) - if (return_losses): - losses=[] - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data) - ls=loss(pred,ground) - losses.append(ls.item()) - ls.backward() - optim.step() - return [kernel,losses] - else: - for i in range(iterations): - with torch.set_grad_enabled(True): - optim.zero_grad() - pred=kernel.forward(data_cuda) - ls=loss(pred,ground_cuda) - ls.backward() - optim.step() - return kernel \ No newline at end of file + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data) + ls=loss(pred,ground) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel \ No newline at end of file diff --git a/analysis-master/analysis/titanlearn.py b/analysis-master/analysis/titanlearn.py index b69d36e3..eb01b476 100644 --- a/analysis-master/analysis/titanlearn.py +++ b/analysis-master/analysis/titanlearn.py @@ -11,112 +11,112 @@ __version__ = "2.0.1.001" #changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: - 2.0.1.001: - - removed matplotlib import - - removed graphloss() - 2.0.1.000: - - added net, dataset, dataloader, and stdtrain template definitions - - added graphloss function - 2.0.0.001: - - added clear functions - 2.0.0.000: - - complete rewrite planned - - depreciated 1.0.0.xxx versions - - added simple training loop - 1.0.0.xxx: - -added generation of ANNS, basic SGD training + 2.0.1.001: + - removed matplotlib import + - removed graphloss() + 2.0.1.000: + - added net, dataset, dataloader, and stdtrain template definitions + - added graphloss function + 2.0.0.001: + - added clear functions + 2.0.0.000: + - complete rewrite planned + - depreciated 1.0.0.xxx versions + - added simple training loop + 1.0.0.xxx: + -added generation of ANNS, basic SGD training """ __author__ = ( - "Arthur Lu ," - "Jacob Levine ," - ) + "Arthur Lu ," + "Jacob Levine ," + ) __all__ = [ - 'clear', - 'net', - 'dataset', - 'dataloader', - 'train', - 'stdtrainer', - ] + 'clear', + 'net', + 'dataset', + 'dataloader', + 'train', + 'stdtrainer', + ] import torch from os import system, name import numpy as np def clear(): - if name == 'nt': - _ = system('cls') - else: - _ = system('clear') + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') class net(torch.nn.Module): #template for standard neural net - def __init__(self): - super(Net, self).__init__() - - def forward(self, input): - pass + def __init__(self): + super(Net, self).__init__() + + def forward(self, input): + pass class dataset(torch.utils.data.Dataset): #template for standard dataset - - def __init__(self): - super(torch.utils.data.Dataset).__init__() - - def __getitem__(self, index): - pass - - def __len__(self): - pass + + def __init__(self): + super(torch.utils.data.Dataset).__init__() + + def __getitem__(self, index): + pass + + def __len__(self): + pass def dataloader(dataset, batch_size, num_workers, shuffle = True): - return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) def train(device, net, epochs, trainloader, optimizer, criterion): #expects standard dataloader, whch returns (inputs, labels) - dataset_len = trainloader.dataset.__len__() - iter_count = 0 - running_loss = 0 - running_loss_list = [] + dataset_len = trainloader.dataset.__len__() + iter_count = 0 + running_loss = 0 + running_loss_list = [] - for epoch in range(epochs): # loop over the dataset multiple times + for epoch in range(epochs): # loop over the dataset multiple times - for i, data in enumerate(trainloader, 0): + for i, data in enumerate(trainloader, 0): - inputs = data[0].to(device) - labels = data[1].to(device) + inputs = data[0].to(device) + labels = data[1].to(device) - optimizer.zero_grad() + optimizer.zero_grad() - outputs = net(inputs) - loss = criterion(outputs, labels.to(torch.float)) - - loss.backward() - optimizer.step() + outputs = net(inputs) + loss = criterion(outputs, labels.to(torch.float)) + + loss.backward() + optimizer.step() - # monitoring steps below + # monitoring steps below - iter_count += 1 - running_loss += loss.item() - running_loss_list.append(running_loss) - clear() + iter_count += 1 + running_loss += loss.item() + running_loss_list.append(running_loss) + clear() - print("training on: " + device) - print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) - print("current batch loss: " + str(loss.item)) - print("running loss: " + str(running_loss / iter_count)) - - return net, running_loss_list - print("finished training") + print("training on: " + device) + print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) + print("current batch loss: " + str(loss.item)) + print("running loss: " + str(running_loss / iter_count)) + + return net, running_loss_list + print("finished training") def stdtrainer(net, criterion, optimizer, dataloader, epochs, batch_size): - device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - net = net.to(device) - criterion = criterion.to(device) - optimizer = optimizer.to(device) - trainloader = dataloader - - return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file + net = net.to(device) + criterion = criterion.to(device) + optimizer = optimizer.to(device) + trainloader = dataloader + + return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file diff --git a/analysis-master/analysis/visualization.py b/analysis-master/analysis/visualization.py index 72358662..0d52c0f5 100644 --- a/analysis-master/analysis/visualization.py +++ b/analysis-master/analysis/visualization.py @@ -10,25 +10,25 @@ __version__ = "1.0.0.000" #changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: - 1.0.0.000: - - created visualization.py - - added graphloss() - - added imports + 1.0.0.000: + - created visualization.py + - added graphloss() + - added imports """ __author__ = ( - "Arthur Lu ," - "Jacob Levine ," - ) + "Arthur Lu ," + "Jacob Levine ," + ) __all__ = [ - 'graphloss', - ] + 'graphloss', + ] import matplotlib.pyplot as plt def graphloss(losses): - x = range(0, len(losses)) - plt.plot(x, losses) - plt.show() \ No newline at end of file + x = range(0, len(losses)) + plt.plot(x, losses) + plt.show() \ No newline at end of file diff --git a/analysis-master/setup.py b/analysis-master/setup.py index f290c88d..043ca845 100644 --- a/analysis-master/setup.py +++ b/analysis-master/setup.py @@ -3,24 +3,24 @@ import setuptools requirements = [] with open("requirements.txt", 'r') as file: - for line in file: - requirements.append(line) + for line in file: + requirements.append(line) setuptools.setup( - name="analysis", - version="1.0.0.012", - author="The Titan Scouting Team", - author_email="titanscout2022@gmail.com", - description="analysis package developed by Titan Scouting for The Red Alliance", - long_description="", - long_description_content_type="text/markdown", - url="https://github.com/titanscout2022/tr2022-strategy", - packages=setuptools.find_packages(), - install_requires=requirements, - license = "GNU General Public License v3.0", - classifiers=[ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", - ], - python_requires='>=3.6', + name="analysis", + version="1.0.0.012", + author="The Titan Scouting Team", + author_email="titanscout2022@gmail.com", + description="analysis package developed by Titan Scouting for The Red Alliance", + long_description="", + long_description_content_type="text/markdown", + url="https://github.com/titanscout2022/tr2022-strategy", + packages=setuptools.find_packages(), + install_requires=requirements, + license = "GNU General Public License v3.0", + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', ) \ No newline at end of file diff --git a/data analysis/data.py b/data analysis/data.py index b7fec8b5..9b075e5e 100644 --- a/data analysis/data.py +++ b/data analysis/data.py @@ -4,99 +4,99 @@ import pandas as pd import time def pull_new_tba_matches(apikey, competition, cutoff): - api_key= apikey - x=requests.get("https://www.thebluealliance.com/api/v3/event/"+competition+"/matches/simple", headers={"X-TBA-Auth_Key":api_key}) - out = [] - for i in x.json(): - if (i["actual_time"] != None and i["actual_time"]-cutoff >= 0 and i["comp_level"] == "qm"): - out.append({"match" : i['match_number'], "blue" : list(map(lambda x: int(x[3:]), i['alliances']['blue']['team_keys'])), "red" : list(map(lambda x: int(x[3:]), i['alliances']['red']['team_keys'])), "winner": i["winning_alliance"]}) - return out + api_key= apikey + x=requests.get("https://www.thebluealliance.com/api/v3/event/"+competition+"/matches/simple", headers={"X-TBA-Auth_Key":api_key}) + out = [] + for i in x.json(): + if (i["actual_time"] != None and i["actual_time"]-cutoff >= 0 and i["comp_level"] == "qm"): + out.append({"match" : i['match_number'], "blue" : list(map(lambda x: int(x[3:]), i['alliances']['blue']['team_keys'])), "red" : list(map(lambda x: int(x[3:]), i['alliances']['red']['team_keys'])), "winner": i["winning_alliance"]}) + return out def get_team_match_data(apikey, competition, team_num): - client = pymongo.MongoClient(apikey) - db = client.data_scouting - mdata = db.matchdata - out = {} - for i in mdata.find({"competition" : competition, "team_scouted": team_num}): - out[i['match']] = i['data'] - return pd.DataFrame(out) + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.matchdata + out = {} + for i in mdata.find({"competition" : competition, "team_scouted": team_num}): + out[i['match']] = i['data'] + return pd.DataFrame(out) def get_team_pit_data(apikey, competition, team_num): - client = pymongo.MongoClient(apikey) - db = client.data_scouting - mdata = db.pitdata - out = {} - return mdata.find_one({"competition" : competition, "team_scouted": team_num})["data"] + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.pitdata + out = {} + return mdata.find_one({"competition" : competition, "team_scouted": team_num})["data"] def get_team_metrics_data(apikey, competition, team_num): - client = pymongo.MongoClient(apikey) - db = client.data_processing - mdata = db.team_metrics - return mdata.find_one({"competition" : competition, "team": team_num}) + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.team_metrics + return mdata.find_one({"competition" : competition, "team": team_num}) def unkeyify_2l(layered_dict): - out = {} - for i in layered_dict.keys(): - add = [] - sortkey = [] - for j in layered_dict[i].keys(): - add.append([j,layered_dict[i][j]]) - add.sort(key = lambda x: x[0]) - out[i] = list(map(lambda x: x[1], add)) - return out + out = {} + for i in layered_dict.keys(): + add = [] + sortkey = [] + for j in layered_dict[i].keys(): + add.append([j,layered_dict[i][j]]) + add.sort(key = lambda x: x[0]) + out[i] = list(map(lambda x: x[1], add)) + return out def get_match_data_formatted(apikey, competition): - client = pymongo.MongoClient(apikey) - db = client.data_scouting - mdata = db.teamlist - x=mdata.find_one({"competition":competition}) - out = {} - for i in x: - try: - out[int(i)] = unkeyify_2l(get_team_match_data(apikey, competition, int(i)).transpose().to_dict()) - except: - pass - return out + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = unkeyify_2l(get_team_match_data(apikey, competition, int(i)).transpose().to_dict()) + except: + pass + return out def get_pit_data_formatted(apikey, competition): - client = pymongo.MongoClient(apikey) - db = client.data_scouting - mdata = db.teamlist - x=mdata.find_one({"competition":competition}) - out = {} - for i in x: - try: - out[int(i)] = get_team_pit_data(apikey, competition, int(i)) - except: - pass - return out + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = get_team_pit_data(apikey, competition, int(i)) + except: + pass + return out def push_team_tests_data(apikey, competition, team_num, data, dbname = "data_processing", colname = "team_tests"): - client = pymongo.MongoClient(apikey) - db = client[dbname] - mdata = db[colname] - mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "data" : data}, True) + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "data" : data}, True) def push_team_metrics_data(apikey, competition, team_num, data, dbname = "data_processing", colname = "team_metrics"): - client = pymongo.MongoClient(apikey) - db = client[dbname] - mdata = db[colname] - mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "metrics" : data}, True) + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "metrics" : data}, True) def push_team_pit_data(apikey, competition, variable, data, dbname = "data_processing", colname = "team_pit"): - client = pymongo.MongoClient(apikey) - db = client[dbname] - mdata = db[colname] - mdata.replace_one({"competition" : competition, "variable": variable}, {"competition" : competition, "variable" : variable, "data" : data}, True) + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "variable": variable}, {"competition" : competition, "variable" : variable, "data" : data}, True) def get_analysis_flags(apikey, flag): - client = pymongo.MongoClient(apikey) - db = client.data_processing - mdata = db.flags - return mdata.find_one({flag:{"$exists":True}}) + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.flags + return mdata.find_one({flag:{"$exists":True}}) def set_analysis_flags(apikey, flag, data): - client = pymongo.MongoClient(apikey) - db = client.data_processing - mdata = db.flags - return mdata.replace_one({flag:{"$exists":True}}, data, True) \ No newline at end of file + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.flags + return mdata.replace_one({flag:{"$exists":True}}, data, True) \ No newline at end of file diff --git a/data analysis/get_team_rankings.py b/data analysis/get_team_rankings.py index cec2aa08..3ab03263 100644 --- a/data analysis/get_team_rankings.py +++ b/data analysis/get_team_rankings.py @@ -4,56 +4,56 @@ import pymongo import operator def load_config(file): - config_vector = {} - file = an.load_csv(file) - for line in file[1:]: - config_vector[line[0]] = line[1:] + config_vector = {} + file = an.load_csv(file) + for line in file[1:]: + config_vector[line[0]] = line[1:] - return (file[0][0], config_vector) + return (file[0][0], config_vector) def get_metrics_processed_formatted(apikey, competition): - client = pymongo.MongoClient(apikey) - db = client.data_scouting - mdata = db.teamlist - x=mdata.find_one({"competition":competition}) - out = {} - for i in x: - try: - out[int(i)] = d.get_team_metrics_data(apikey, competition, int(i)) - except: - pass - return out + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = d.get_team_metrics_data(apikey, competition, int(i)) + except: + pass + return out def main(): - apikey = an.load_csv("keys.txt")[0][0] - tbakey = an.load_csv("keys.txt")[1][0] + apikey = an.load_csv("keys.txt")[0][0] + tbakey = an.load_csv("keys.txt")[1][0] - competition, config = load_config("config.csv") + competition, config = load_config("config.csv") - metrics = get_metrics_processed_formatted(apikey, competition) + metrics = get_metrics_processed_formatted(apikey, competition) - elo = {} - gl2 = {} + elo = {} + gl2 = {} - for team in metrics: + for team in metrics: - elo[team] = metrics[team]["metrics"]["elo"]["score"] - gl2[team] = metrics[team]["metrics"]["gl2"]["score"] + elo[team] = metrics[team]["metrics"]["elo"]["score"] + gl2[team] = metrics[team]["metrics"]["gl2"]["score"] - elo = {k: v for k, v in sorted(elo.items(), key=lambda item: item[1])} - gl2 = {k: v for k, v in sorted(gl2.items(), key=lambda item: item[1])} + elo = {k: v for k, v in sorted(elo.items(), key=lambda item: item[1])} + gl2 = {k: v for k, v in sorted(gl2.items(), key=lambda item: item[1])} - for team in elo: + for team in elo: - print("teams sorted by elo:") - print("" + str(team) + " | " + str(elo[team])) + print("teams sorted by elo:") + print("" + str(team) + " | " + str(elo[team])) - print("*"*25) + print("*"*25) - for team in gl2: + for team in gl2: - print("teams sorted by glicko2:") - print("" + str(team) + " | " + str(gl2[team])) + print("teams sorted by glicko2:") + print("" + str(team) + " | " + str(gl2[team])) main() \ No newline at end of file diff --git a/data analysis/superscript.py b/data analysis/superscript.py index ac1ec4a5..05562c19 100644 --- a/data analysis/superscript.py +++ b/data analysis/superscript.py @@ -7,81 +7,81 @@ __version__ = "0.0.5.002" # changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: - 0.0.5.002: - - made changes due to refactoring of analysis - 0.0.5.001: - - text fixes - - removed matplotlib requirement - 0.0.5.000: - - improved user interface - 0.0.4.002: - - removed unessasary code - 0.0.4.001: - - fixed bug where X range for regression was determined before sanitization - - better sanitized data - 0.0.4.000: - - fixed spelling issue in __changelog__ - - addressed nan bug in regression - - fixed errors on line 335 with metrics calling incorrect key "glicko2" - - fixed errors in metrics computing - 0.0.3.000: - - added analysis to pit data - 0.0.2.001: - - minor stability patches - - implemented db syncing for timestamps - - fixed bugs - 0.0.2.000: - - finalized testing and small fixes - 0.0.1.004: - - finished metrics implement, trueskill is bugged - 0.0.1.003: - - working - 0.0.1.002: - - started implement of metrics - 0.0.1.001: - - cleaned up imports - 0.0.1.000: - - tested working, can push to database - 0.0.0.009: - - tested working - - prints out stats for the time being, will push to database later - 0.0.0.008: - - added data import - - removed tba import - - finished main method - 0.0.0.007: - - added load_config - - optimized simpleloop for readibility - - added __all__ entries - - added simplestats engine - - pending testing - 0.0.0.006: - - fixes - 0.0.0.005: - - imported pickle - - created custom database object - 0.0.0.004: - - fixed simpleloop to actually return a vector - 0.0.0.003: - - added metricsloop which is unfinished - 0.0.0.002: - - added simpleloop which is untested until data is provided - 0.0.0.001: - - created script - - added analysis, numba, numpy imports + 0.0.5.002: + - made changes due to refactoring of analysis + 0.0.5.001: + - text fixes + - removed matplotlib requirement + 0.0.5.000: + - improved user interface + 0.0.4.002: + - removed unessasary code + 0.0.4.001: + - fixed bug where X range for regression was determined before sanitization + - better sanitized data + 0.0.4.000: + - fixed spelling issue in __changelog__ + - addressed nan bug in regression + - fixed errors on line 335 with metrics calling incorrect key "glicko2" + - fixed errors in metrics computing + 0.0.3.000: + - added analysis to pit data + 0.0.2.001: + - minor stability patches + - implemented db syncing for timestamps + - fixed bugs + 0.0.2.000: + - finalized testing and small fixes + 0.0.1.004: + - finished metrics implement, trueskill is bugged + 0.0.1.003: + - working + 0.0.1.002: + - started implement of metrics + 0.0.1.001: + - cleaned up imports + 0.0.1.000: + - tested working, can push to database + 0.0.0.009: + - tested working + - prints out stats for the time being, will push to database later + 0.0.0.008: + - added data import + - removed tba import + - finished main method + 0.0.0.007: + - added load_config + - optimized simpleloop for readibility + - added __all__ entries + - added simplestats engine + - pending testing + 0.0.0.006: + - fixes + 0.0.0.005: + - imported pickle + - created custom database object + 0.0.0.004: + - fixed simpleloop to actually return a vector + 0.0.0.003: + - added metricsloop which is unfinished + 0.0.0.002: + - added simpleloop which is untested until data is provided + 0.0.0.001: + - created script + - added analysis, numba, numpy imports """ __author__ = ( - "Arthur Lu ", - "Jacob Levine ", + "Arthur Lu ", + "Jacob Levine ", ) __all__ = [ - "main", - "load_config", - "simpleloop", - "simplestats", - "metricsloop" + "main", + "load_config", + "simpleloop", + "simplestats", + "metricsloop" ] # imports: @@ -95,273 +95,273 @@ import time import warnings def main(): - warnings.filterwarnings("ignore") - while(True): + warnings.filterwarnings("ignore") + while(True): - current_time = time.time() - print("[OK] time: " + str(current_time)) + current_time = time.time() + print("[OK] time: " + str(current_time)) - start = time.time() - config = load_config(Path("config/stats.config")) - competition = an.load_csv(Path("config/competition.config"))[0][0] - print("[OK] configs loaded") + start = time.time() + config = load_config(Path("config/stats.config")) + competition = an.load_csv(Path("config/competition.config"))[0][0] + print("[OK] configs loaded") - apikey = an.load_csv(Path("config/keys.config"))[0][0] - tbakey = an.load_csv(Path("config/keys.config"))[1][0] - print("[OK] loaded keys") + apikey = an.load_csv(Path("config/keys.config"))[0][0] + tbakey = an.load_csv(Path("config/keys.config"))[1][0] + print("[OK] loaded keys") - previous_time = d.get_analysis_flags(apikey, "latest_update") + previous_time = d.get_analysis_flags(apikey, "latest_update") - if(previous_time == None): + if(previous_time == None): - d.set_analysis_flags(apikey, "latest_update", 0) - previous_time = 0 + d.set_analysis_flags(apikey, "latest_update", 0) + previous_time = 0 - else: + else: - previous_time = previous_time["latest_update"] + previous_time = previous_time["latest_update"] - print("[OK] analysis backtimed to: " + str(previous_time)) + print("[OK] analysis backtimed to: " + str(previous_time)) - print("[OK] loading data") - start = time.time() - data = d.get_match_data_formatted(apikey, competition) - pit_data = d.pit = d.get_pit_data_formatted(apikey, competition) - print("[OK] loaded data in " + str(time.time() - start) + " seconds") + print("[OK] loading data") + start = time.time() + data = d.get_match_data_formatted(apikey, competition) + pit_data = d.pit = d.get_pit_data_formatted(apikey, competition) + print("[OK] loaded data in " + str(time.time() - start) + " seconds") - print("[OK] running tests") - start = time.time() - results = simpleloop(data, config) - print("[OK] finished tests in " + str(time.time() - start) + " seconds") + print("[OK] running tests") + start = time.time() + results = simpleloop(data, config) + print("[OK] finished tests in " + str(time.time() - start) + " seconds") - print("[OK] running metrics") - start = time.time() - metricsloop(tbakey, apikey, competition, previous_time) - print("[OK] finished metrics in " + str(time.time() - start) + " seconds") + print("[OK] running metrics") + start = time.time() + metricsloop(tbakey, apikey, competition, previous_time) + print("[OK] finished metrics in " + str(time.time() - start) + " seconds") - print("[OK] running pit analysis") - start = time.time() - pit = pitloop(pit_data, config) - print("[OK] finished pit analysis in " + str(time.time() - start) + " seconds") + print("[OK] running pit analysis") + start = time.time() + pit = pitloop(pit_data, config) + print("[OK] finished pit analysis in " + str(time.time() - start) + " seconds") - d.set_analysis_flags(apikey, "latest_update", {"latest_update":current_time}) - - print("[OK] pushing to database") - start = time.time() - push_to_database(apikey, competition, results, pit) - print("[OK] pushed to database in " + str(time.time() - start) + " seconds") + d.set_analysis_flags(apikey, "latest_update", {"latest_update":current_time}) + + print("[OK] pushing to database") + start = time.time() + push_to_database(apikey, competition, results, pit) + print("[OK] pushed to database in " + str(time.time() - start) + " seconds") - clear() + clear() def clear(): - - # for windows - if name == 'nt': - _ = system('cls') + + # for windows + if name == 'nt': + _ = system('cls') - # for mac and linux(here, os.name is 'posix') - else: - _ = system('clear') + # for mac and linux(here, os.name is 'posix') + else: + _ = system('clear') def load_config(file): - config_vector = {} - file = an.load_csv(file) - for line in file: - config_vector[line[0]] = line[1:] + config_vector = {} + file = an.load_csv(file) + for line in file: + config_vector[line[0]] = line[1:] - return config_vector + return config_vector def simpleloop(data, tests): # expects 3D array with [Team][Variable][Match] - return_vector = {} - for team in data: - variable_vector = {} - for variable in data[team]: - test_vector = {} - variable_data = data[team][variable] - if(variable in tests): - for test in tests[variable]: - test_vector[test] = simplestats(variable_data, test) - else: - pass - variable_vector[variable] = test_vector - return_vector[team] = variable_vector + return_vector = {} + for team in data: + variable_vector = {} + for variable in data[team]: + test_vector = {} + variable_data = data[team][variable] + if(variable in tests): + for test in tests[variable]: + test_vector[test] = simplestats(variable_data, test) + else: + pass + variable_vector[variable] = test_vector + return_vector[team] = variable_vector - return return_vector + return return_vector def simplestats(data, test): - data = np.array(data) - data = data[np.isfinite(data)] - ranges = list(range(len(data))) + data = np.array(data) + data = data[np.isfinite(data)] + ranges = list(range(len(data))) - if(test == "basic_stats"): - return an.basic_stats(data) + if(test == "basic_stats"): + return an.basic_stats(data) - if(test == "historical_analysis"): - return an.histo_analysis([ranges, data]) + if(test == "historical_analysis"): + return an.histo_analysis([ranges, data]) - if(test == "regression_linear"): - return an.regression(ranges, data, ['lin']) + if(test == "regression_linear"): + return an.regression(ranges, data, ['lin']) - if(test == "regression_logarithmic"): - return an.regression(ranges, data, ['log']) + if(test == "regression_logarithmic"): + return an.regression(ranges, data, ['log']) - if(test == "regression_exponential"): - return an.regression(ranges, data, ['exp']) + if(test == "regression_exponential"): + return an.regression(ranges, data, ['exp']) - if(test == "regression_polynomial"): - return an.regression(ranges, data, ['ply']) + if(test == "regression_polynomial"): + return an.regression(ranges, data, ['ply']) - if(test == "regression_sigmoidal"): - return an.regression(ranges, data, ['sig']) + if(test == "regression_sigmoidal"): + return an.regression(ranges, data, ['sig']) def push_to_database(apikey, competition, results, pit): - for team in results: + for team in results: - d.push_team_tests_data(apikey, competition, team, results[team]) + d.push_team_tests_data(apikey, competition, team, results[team]) - for variable in pit: + for variable in pit: - d.push_team_pit_data(apikey, competition, variable, pit[variable]) + d.push_team_pit_data(apikey, competition, variable, pit[variable]) def metricsloop(tbakey, apikey, competition, timestamp): # listener based metrics update - elo_N = 400 - elo_K = 24 + elo_N = 400 + elo_K = 24 - matches = d.pull_new_tba_matches(tbakey, competition, timestamp) + matches = d.pull_new_tba_matches(tbakey, competition, timestamp) - red = {} - blu = {} + red = {} + blu = {} - for match in matches: + for match in matches: - red = load_metrics(apikey, competition, match, "red") - blu = load_metrics(apikey, competition, match, "blue") + red = load_metrics(apikey, competition, match, "red") + blu = load_metrics(apikey, competition, match, "blue") - elo_red_total = 0 - elo_blu_total = 0 + elo_red_total = 0 + elo_blu_total = 0 - gl2_red_score_total = 0 - gl2_blu_score_total = 0 + gl2_red_score_total = 0 + gl2_blu_score_total = 0 - gl2_red_rd_total = 0 - gl2_blu_rd_total = 0 + gl2_red_rd_total = 0 + gl2_blu_rd_total = 0 - gl2_red_vol_total = 0 - gl2_blu_vol_total = 0 + gl2_red_vol_total = 0 + gl2_blu_vol_total = 0 - for team in red: + for team in red: - elo_red_total += red[team]["elo"]["score"] + elo_red_total += red[team]["elo"]["score"] - gl2_red_score_total += red[team]["gl2"]["score"] - gl2_red_rd_total += red[team]["gl2"]["rd"] - gl2_red_vol_total += red[team]["gl2"]["vol"] + gl2_red_score_total += red[team]["gl2"]["score"] + gl2_red_rd_total += red[team]["gl2"]["rd"] + gl2_red_vol_total += red[team]["gl2"]["vol"] - for team in blu: + for team in blu: - elo_blu_total += blu[team]["elo"]["score"] + elo_blu_total += blu[team]["elo"]["score"] - gl2_blu_score_total += blu[team]["gl2"]["score"] - gl2_blu_rd_total += blu[team]["gl2"]["rd"] - gl2_blu_vol_total += blu[team]["gl2"]["vol"] + gl2_blu_score_total += blu[team]["gl2"]["score"] + gl2_blu_rd_total += blu[team]["gl2"]["rd"] + gl2_blu_vol_total += blu[team]["gl2"]["vol"] - red_elo = {"score": elo_red_total / len(red)} - blu_elo = {"score": elo_blu_total / len(blu)} + red_elo = {"score": elo_red_total / len(red)} + blu_elo = {"score": elo_blu_total / len(blu)} - red_gl2 = {"score": gl2_red_score_total / len(red), "rd": gl2_red_rd_total / len(red), "vol": gl2_red_vol_total / len(red)} - blu_gl2 = {"score": gl2_blu_score_total / len(blu), "rd": gl2_blu_rd_total / len(blu), "vol": gl2_blu_vol_total / len(blu)} + red_gl2 = {"score": gl2_red_score_total / len(red), "rd": gl2_red_rd_total / len(red), "vol": gl2_red_vol_total / len(red)} + blu_gl2 = {"score": gl2_blu_score_total / len(blu), "rd": gl2_blu_rd_total / len(blu), "vol": gl2_blu_vol_total / len(blu)} - if(match["winner"] == "red"): + if(match["winner"] == "red"): - observations = {"red": 1, "blu": 0} + observations = {"red": 1, "blu": 0} - elif(match["winner"] == "blue"): + elif(match["winner"] == "blue"): - observations = {"red": 0, "blu": 1} + observations = {"red": 0, "blu": 1} - else: + else: - observations = {"red": 0.5, "blu": 0.5} + observations = {"red": 0.5, "blu": 0.5} - red_elo_delta = an.Metrics.elo(red_elo["score"], blu_elo["score"], observations["red"], elo_N, elo_K) - red_elo["score"] - blu_elo_delta = an.Metrics.elo(blu_elo["score"], red_elo["score"], observations["blu"], elo_N, elo_K) - blu_elo["score"] + red_elo_delta = an.Metrics.elo(red_elo["score"], blu_elo["score"], observations["red"], elo_N, elo_K) - red_elo["score"] + blu_elo_delta = an.Metrics.elo(blu_elo["score"], red_elo["score"], observations["blu"], elo_N, elo_K) - blu_elo["score"] - new_red_gl2_score, new_red_gl2_rd, new_red_gl2_vol = an.Metrics.glicko2(red_gl2["score"], red_gl2["rd"], red_gl2["vol"], [blu_gl2["score"]], [blu_gl2["rd"]], [observations["red"], observations["blu"]]) - new_blu_gl2_score, new_blu_gl2_rd, new_blu_gl2_vol = an.Metrics.glicko2(blu_gl2["score"], blu_gl2["rd"], blu_gl2["vol"], [red_gl2["score"]], [red_gl2["rd"]], [observations["blu"], observations["red"]]) + new_red_gl2_score, new_red_gl2_rd, new_red_gl2_vol = an.Metrics.glicko2(red_gl2["score"], red_gl2["rd"], red_gl2["vol"], [blu_gl2["score"]], [blu_gl2["rd"]], [observations["red"], observations["blu"]]) + new_blu_gl2_score, new_blu_gl2_rd, new_blu_gl2_vol = an.Metrics.glicko2(blu_gl2["score"], blu_gl2["rd"], blu_gl2["vol"], [red_gl2["score"]], [red_gl2["rd"]], [observations["blu"], observations["red"]]) - red_gl2_delta = {"score": new_red_gl2_score - red_gl2["score"], "rd": new_red_gl2_rd - red_gl2["rd"], "vol": new_red_gl2_vol - red_gl2["vol"]} - blu_gl2_delta = {"score": new_blu_gl2_score - blu_gl2["score"], "rd": new_blu_gl2_rd - blu_gl2["rd"], "vol": new_blu_gl2_vol - blu_gl2["vol"]} + red_gl2_delta = {"score": new_red_gl2_score - red_gl2["score"], "rd": new_red_gl2_rd - red_gl2["rd"], "vol": new_red_gl2_vol - red_gl2["vol"]} + blu_gl2_delta = {"score": new_blu_gl2_score - blu_gl2["score"], "rd": new_blu_gl2_rd - blu_gl2["rd"], "vol": new_blu_gl2_vol - blu_gl2["vol"]} - for team in red: + for team in red: - red[team]["elo"]["score"] = red[team]["elo"]["score"] + red_elo_delta + red[team]["elo"]["score"] = red[team]["elo"]["score"] + red_elo_delta - red[team]["gl2"]["score"] = red[team]["gl2"]["score"] + red_gl2_delta["score"] - red[team]["gl2"]["rd"] = red[team]["gl2"]["rd"] + red_gl2_delta["rd"] - red[team]["gl2"]["vol"] = red[team]["gl2"]["vol"] + red_gl2_delta["vol"] + red[team]["gl2"]["score"] = red[team]["gl2"]["score"] + red_gl2_delta["score"] + red[team]["gl2"]["rd"] = red[team]["gl2"]["rd"] + red_gl2_delta["rd"] + red[team]["gl2"]["vol"] = red[team]["gl2"]["vol"] + red_gl2_delta["vol"] - for team in blu: + for team in blu: - blu[team]["elo"]["score"] = blu[team]["elo"]["score"] + blu_elo_delta + blu[team]["elo"]["score"] = blu[team]["elo"]["score"] + blu_elo_delta - blu[team]["gl2"]["score"] = blu[team]["gl2"]["score"] + blu_gl2_delta["score"] - blu[team]["gl2"]["rd"] = blu[team]["gl2"]["rd"] + blu_gl2_delta["rd"] - blu[team]["gl2"]["vol"] = blu[team]["gl2"]["vol"] + blu_gl2_delta["vol"] + blu[team]["gl2"]["score"] = blu[team]["gl2"]["score"] + blu_gl2_delta["score"] + blu[team]["gl2"]["rd"] = blu[team]["gl2"]["rd"] + blu_gl2_delta["rd"] + blu[team]["gl2"]["vol"] = blu[team]["gl2"]["vol"] + blu_gl2_delta["vol"] - temp_vector = {} - temp_vector.update(red) - temp_vector.update(blu) + temp_vector = {} + temp_vector.update(red) + temp_vector.update(blu) - for team in temp_vector: + for team in temp_vector: - d.push_team_metrics_data(apikey, competition, team, temp_vector[team]) + d.push_team_metrics_data(apikey, competition, team, temp_vector[team]) def load_metrics(apikey, competition, match, group_name): - group = {} + group = {} - for team in match[group_name]: + for team in match[group_name]: - db_data = d.get_team_metrics_data(apikey, competition, team) + db_data = d.get_team_metrics_data(apikey, competition, team) - if d.get_team_metrics_data(apikey, competition, team) == None: + if d.get_team_metrics_data(apikey, competition, team) == None: - elo = {"score": 1500} - gl2 = {"score": 1500, "rd": 250, "vol": 0.06} - ts = {"mu": 25, "sigma": 25/3} + elo = {"score": 1500} + gl2 = {"score": 1500, "rd": 250, "vol": 0.06} + ts = {"mu": 25, "sigma": 25/3} - #d.push_team_metrics_data(apikey, competition, team, {"elo":elo, "gl2":gl2,"trueskill":ts}) + #d.push_team_metrics_data(apikey, competition, team, {"elo":elo, "gl2":gl2,"trueskill":ts}) - group[team] = {"elo": elo, "gl2": gl2, "ts": ts} + group[team] = {"elo": elo, "gl2": gl2, "ts": ts} - else: + else: - metrics = db_data["metrics"] + metrics = db_data["metrics"] - elo = metrics["elo"] - gl2 = metrics["gl2"] - ts = metrics["ts"] + elo = metrics["elo"] + gl2 = metrics["gl2"] + ts = metrics["ts"] - group[team] = {"elo": elo, "gl2": gl2, "ts": ts} + group[team] = {"elo": elo, "gl2": gl2, "ts": ts} - return group + return group def pitloop(pit, tests): - return_vector = {} - for team in pit: - for variable in pit[team]: - if(variable in tests): - if(not variable in return_vector): - return_vector[variable] = [] - return_vector[variable].append(pit[team][variable]) + return_vector = {} + for team in pit: + for variable in pit[team]: + if(variable in tests): + if(not variable in return_vector): + return_vector[variable] = [] + return_vector[variable].append(pit[team][variable]) - return return_vector + return return_vector main() diff --git a/data analysis/visualize_pit.py b/data analysis/visualize_pit.py index afd10e20..9fddd259 100644 --- a/data analysis/visualize_pit.py +++ b/data analysis/visualize_pit.py @@ -8,20 +8,20 @@ import pymongo # %% def get_pit_variable_data(apikey, competition): - client = pymongo.MongoClient(apikey) - db = client.data_processing - mdata = db.team_pit - out = {} - return mdata.find() + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.team_pit + out = {} + return mdata.find() # %% def get_pit_variable_formatted(apikey, competition): - temp = get_pit_variable_data(apikey, competition) - out = {} - for i in temp: - out[i["variable"]] = i["data"] - return out + temp = get_pit_variable_data(apikey, competition) + out = {} + for i in temp: + out[i["variable"]] = i["data"] + return out # %% @@ -40,16 +40,16 @@ i = 0 for variable in pit: - ax[i].hist(pit[variable]) - ax[i].invert_xaxis() + ax[i].hist(pit[variable]) + ax[i].invert_xaxis() - ax[i].set_xlabel('') - ax[i].set_ylabel('Frequency') - ax[i].set_title(variable) + ax[i].set_xlabel('') + ax[i].set_ylabel('Frequency') + ax[i].set_title(variable) - plt.yticks(np.arange(len(pit[variable]))) + plt.yticks(np.arange(len(pit[variable]))) - i+=1 + i+=1 plt.show() From 29aa3787d6205182a0a6710b319e3382b97e51e7 Mon Sep 17 00:00:00 2001 From: ltcptgeneral <35508619+ltcptgeneral@users.noreply.github.com> Date: Fri, 1 May 2020 22:59:54 -0500 Subject: [PATCH 5/5] analysis.py v 1.2.0.005 --- analysis-master/analysis/analysis.py | 77 ++++++++++++++++------------ 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/analysis-master/analysis/analysis.py b/analysis-master/analysis/analysis.py index c4cce961..e938cc4f 100644 --- a/analysis-master/analysis/analysis.py +++ b/analysis-master/analysis/analysis.py @@ -7,10 +7,18 @@ # current benchmark of optimization: 1.33 times faster # setup: -__version__ = "1.2.0.004" +__version__ = "1.2.0.005" # changelog should be viewed using print(analysis.__changelog__) __changelog__ = """changelog: + 1.2.0.005: + - moved random_forrest_regressor and random_forrest_classifier to RandomForrest class + - renamed Metrics to Metric + - renamed RegressionMetrics to RegressionMetric + - renamed ClassificationMetrics to ClassificationMetric + - renamed CorrelationTests to CorrelationTest + - renamed StatisticalTests to StatisticalTest + - reflected rafactoring to all mentions of above classes/functions 1.2.0.004: - fixed __all__ to reflected the correct functions and classes - fixed CorrelationTests and StatisticalTests class functions to require self invocation @@ -282,19 +290,18 @@ __all__ = [ 'z_normalize', 'histo_analysis', 'regression', - 'Metrics', - 'RegressionMetrics', - 'ClassificationMetrics', + 'Metric', + 'RegressionMetric', + 'ClassificationMetric', 'kmeans', 'pca', 'decisiontree', 'KNN', 'NaiveBayes', 'SVM', - 'random_forest_classifier', - 'random_forest_regressor', - 'CorrelationTests', - 'StatisticalTests', + 'RandomForrest', + 'CorrelationTest', + 'StatisticalTest', # all statistics functions left out due to integration in other functions ] @@ -470,7 +477,7 @@ def regression(inputs, outputs, args): # inputs, outputs expects N-D array return regressions -class Metrics: +class Metric: def elo(self, starting_score, opposing_score, observed, N, K): @@ -497,7 +504,7 @@ class Metrics: return Trueskill.rate(team_ratings, ranks=observations) -class RegressionMetrics(): +class RegressionMetric(): def __new__(cls, predictions, targets): @@ -515,7 +522,7 @@ class RegressionMetrics(): return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) -class ClassificationMetrics(): +class ClassificationMetric(): def __new__(cls, predictions, targets): @@ -583,7 +590,7 @@ def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = " model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) model = model.fit(data_train,labels_train) predictions = model.predict(data_test) - metrics = ClassificationMetrics(predictions, labels_test) + metrics = ClassificationMetric(predictions, labels_test) return model, metrics @@ -596,7 +603,7 @@ class KNN: model.fit(data_train, labels_train) predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetric(predictions, labels_test) def knn_regressor(self, data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): @@ -605,7 +612,7 @@ class KNN: model.fit(data_train, outputs_train) predictions = model.predict(data_test) - return model, RegressionMetrics(predictions, outputs_test) + return model, RegressionMetric(predictions, outputs_test) class NaiveBayes: @@ -616,7 +623,7 @@ class NaiveBayes: model.fit(data_train, labels_train) predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetric(predictions, labels_test) def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): @@ -625,7 +632,7 @@ class NaiveBayes: model.fit(data_train, labels_train) predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetric(predictions, labels_test) def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): @@ -634,7 +641,7 @@ class NaiveBayes: model.fit(data_train, labels_train) predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetric(predictions, labels_test) def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): @@ -643,7 +650,7 @@ class NaiveBayes: model.fit(data_train, labels_train) predictions = model.predict(data_test) - return model, ClassificationMetrics(predictions, labels_test) + return model, ClassificationMetric(predictions, labels_test) class SVM: @@ -693,33 +700,35 @@ class SVM: predictions = kernel.predict(test_data) - return ClassificationMetrics(predictions, test_outputs) + return ClassificationMetric(predictions, test_outputs) def eval_regression(self, kernel, test_data, test_outputs): predictions = kernel.predict(test_data) - return RegressionMetrics(predictions, test_outputs) + return RegressionMetric(predictions, test_outputs) -def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): +class RandomForrest: - data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) - kernel.fit(data_train, labels_train) - predictions = kernel.predict(data_test) + def random_forest_classifier(self, data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): - return kernel, ClassificationMetrics(predictions, labels_test) + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) + kernel.fit(data_train, labels_train) + predictions = kernel.predict(data_test) -def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): + return kernel, ClassificationMetric(predictions, labels_test) - data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) - kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) - kernel.fit(data_train, outputs_train) - predictions = kernel.predict(data_test) + def random_forest_regressor(self, data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): - return kernel, RegressionMetrics(predictions, outputs_test) + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) + kernel.fit(data_train, outputs_train) + predictions = kernel.predict(data_test) -class CorrelationTests: + return kernel, RegressionMetric(predictions, outputs_test) + +class CorrelationTest: def anova_oneway(self, *args): #expects arrays of samples @@ -756,7 +765,7 @@ class CorrelationTests: results = scipy.stats.multiscale_graphcorr(x, y, compute_distance = compute_distance, reps = reps, workers = workers, is_twosamp = is_twosamp, random_state = random_state) return {"k-value": results[0], "p-value": results[1], "data": results[2]} # unsure if MGC test returns a k value -class StatisticalTests: +class StatisticalTest: def ttest_onesample(self, a, popmean, axis = 0, nan_policy = 'propagate'):