GRating 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
grating-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: GRating
3
+ Version: 0.0.1
4
+ Summary: Algorithm ranking library based on Bradley-Terry models
5
+ Author: Oscar A. Gonzalez Sanchez
6
+ Project-URL: Homepage, https://github.com/OscarAGonzalezSanchez/GRating
7
+ Keywords: optimization,ranking,metaheuristics
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: numpy
13
+ Requires-Dist: pandas
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: scipy
16
+
17
+ # readme = "README.md"
@@ -0,0 +1 @@
1
+ # readme = "README.md"
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "GRating"
7
+ version = "0.0.1"
8
+ description = "Algorithm ranking library based on Bradley-Terry models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+
12
+ dependencies = [
13
+ "numpy",
14
+ "pandas",
15
+ "matplotlib",
16
+ "scipy"
17
+ ]
18
+
19
+ authors = [
20
+ { name = "Oscar A. Gonzalez Sanchez" }
21
+ ]
22
+
23
+ keywords = [
24
+ "optimization",
25
+ "ranking",
26
+ "metaheuristics"
27
+ ]
28
+
29
+ classifiers = [
30
+ "Programming Language :: Python :: 3",
31
+ "License :: OSI Approved :: MIT License",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/OscarAGonzalezSanchez/GRating"
36
+
37
+ [tool.setuptools]
38
+ package-dir = {"" = "src"}
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: GRating
3
+ Version: 0.0.1
4
+ Summary: Algorithm ranking library based on Bradley-Terry models
5
+ Author: Oscar A. Gonzalez Sanchez
6
+ Project-URL: Homepage, https://github.com/OscarAGonzalezSanchez/GRating
7
+ Keywords: optimization,ranking,metaheuristics
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: numpy
13
+ Requires-Dist: pandas
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: scipy
16
+
17
+ # readme = "README.md"
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/GRating.egg-info/PKG-INFO
4
+ src/GRating.egg-info/SOURCES.txt
5
+ src/GRating.egg-info/dependency_links.txt
6
+ src/GRating.egg-info/requires.txt
7
+ src/GRating.egg-info/top_level.txt
8
+ src/grating/__init__.py
9
+ src/grating/grating.py
10
+ tests/test_rating.py
@@ -0,0 +1,4 @@
1
+ numpy
2
+ pandas
3
+ matplotlib
4
+ scipy
@@ -0,0 +1 @@
1
+ grating
@@ -0,0 +1,7 @@
1
+ from .grating import GRating, signif, create_scater_plot
2
+
3
+ __all__ = [
4
+ "GRating",
5
+ "signif",
6
+ "create_scater_plot"
7
+ ]
@@ -0,0 +1,431 @@
1
+ from matplotlib.ticker import FuncFormatter
2
+ from numpy.typing import NDArray
3
+ import matplotlib.pyplot as plt
4
+ from scipy.stats import gmean
5
+ import pandas as pd
6
+ import numpy as np
7
+ import os
8
+
9
+ class GRating():
10
+ def __init__(self):
11
+ pass
12
+
13
+ def create_experimentation_table(
14
+ self,
15
+ ruta: str,
16
+ filt: list[str] | None = None,
17
+ rettest: list[str] | None = None
18
+ ) -> None:
19
+ """
20
+ Crea una tabla de experimentación a partir de los archivos encontrados.
21
+
22
+ Parameters
23
+ ----------
24
+ ruta : str
25
+ Ruta raíz donde se buscarán los archivos.
26
+ filt : list[str], optional
27
+ Lista de filtros a aplicar durante la búsqueda.
28
+ rettest : list[str], optional
29
+ Lista de pruebas o resultados a retornar.
30
+
31
+ Returns
32
+ -------
33
+ None
34
+ """
35
+ # Algorithms names are extracted
36
+ self.algorithms = os.listdir(ruta)
37
+ for i, algo in enumerate(self.algorithms):
38
+ self.algorithms[i] = algo[:-4]
39
+
40
+ self.algorithms = sorted(self.algorithms)
41
+
42
+ # An array for loading the data is created
43
+ df = pd.DataFrame(pd.read_csv(f'{ruta}/{self.algorithms[0]}.csv'))
44
+ self.tests = df.columns
45
+
46
+ if filt != None:
47
+ test = []
48
+ for value in self.tests:
49
+ for accepted in filt:
50
+ if accepted in value:
51
+ test.append(value)
52
+ self.tests = test
53
+
54
+ if rettest != None:
55
+ test = []
56
+ for value in self.tests:
57
+ flag = True
58
+ for rejected in rettest:
59
+ if rejected in value:
60
+ flag = False
61
+ if flag:
62
+ test.append(value)
63
+ self.tests = test
64
+
65
+ self.runs = len(df)
66
+ self.fit_data = np.zeros((len(self.algorithms), len(self.tests), self.runs))
67
+
68
+ # Information is loaded in the structure
69
+ for i, algo in enumerate(self.algorithms):
70
+ print(f'cargando {algo} ... con {len(self.tests)} test')
71
+ df = pd.DataFrame(pd.read_csv(f'{ruta}/{algo}.csv'))
72
+ for j, test in enumerate(self.tests):
73
+ self.fit_data[i, j, :] = df[test]
74
+
75
+ def load_fit_data(
76
+ self,
77
+ data: np.ndarray
78
+ ) -> None:
79
+ """
80
+ Carga directamente una matriz de resultados ya procesada.
81
+
82
+ Parameters
83
+ ----------
84
+ data : np.ndarray
85
+ Arreglo tridimensional con forma
86
+ (algoritmos, pruebas, ejecuciones), donde cada elemento
87
+ representa el valor obtenido por un algoritmo en una
88
+ prueba específica durante una ejecución determinada.
89
+
90
+ Returns
91
+ -------
92
+ None
93
+ """
94
+ self.fit_data = data
95
+
96
+ def create_win_lose_table(
97
+ self,
98
+ method: str='permutation',
99
+ draw: bool = False,
100
+ samples: int = 30
101
+ ) -> None:
102
+ """
103
+ Genera la matriz de victorias y derrotas entre algoritmos.
104
+
105
+ Cada posición [i, j] almacena el número de veces que el
106
+ algoritmo i supera al algoritmo j según el criterio de
107
+ comparación seleccionado.
108
+
109
+ Parameters
110
+ ----------
111
+ method : str, optional
112
+ Método utilizado para realizar las comparaciones.
113
+
114
+ - 'permutation': compara todas las combinaciones posibles
115
+ entre ejecuciones.
116
+ - 'subsample': realiza una muestra aleatoria de comparaciones.
117
+
118
+ draw : bool, optional
119
+ Si es True, los empates se contabilizan otorgando medio
120
+ punto a cada algoritmo.
121
+
122
+ samples : int, optional
123
+ Número de comparaciones aleatorias cuando se utiliza el
124
+ método 'subsample'.
125
+
126
+ Returns
127
+ -------
128
+ None
129
+ """
130
+ if method =='permutation':
131
+ # The size of the data is extracted
132
+ self.n_algorithms, self.n_test, self.runs = self.fit_data.shape
133
+ # A victory table is generated
134
+ self.vict_loss = np.zeros((self.n_algorithms, self.n_algorithms))
135
+ self.vict_loss[:] = np.nan
136
+
137
+ # The complete combinatory of wins is extracted
138
+ for ia in range(self.n_algorithms):
139
+ for ib in range(self.n_algorithms):
140
+ if ia!=ib:
141
+ wins = 0
142
+ for j in range(self.n_test):
143
+ for m in range(self.runs):
144
+ wins += (self.fit_data[ia, j, m] < self.fit_data[ib, j, :]).sum()
145
+ self.vict_loss[ia, ib] = wins
146
+
147
+ if draw == True:
148
+ # A half point is given for draws
149
+ for ia in range(self.n_algorithms):
150
+ for ib in range(self.n_algorithms):
151
+ if ia!=ib:
152
+ wins = 0
153
+ for j in range(self.n_test):
154
+ for m in range(self.runs):
155
+ wins += (self.fit_data[ia, j, m] == self.fit_data[ib, j, :]).sum()
156
+ self.vict_loss[ia, ib] = wins/2
157
+ self.vict_loss[ib, ia] = wins/2
158
+
159
+ elif method =='subsample':
160
+ # The size of the data is extracted
161
+ self.n_algorithms, self.n_test, self.runs = self.fit_data.shape
162
+ # A victory table is generated
163
+ self.vict_loss = np.zeros((self.n_algorithms, self.n_algorithms))
164
+ self.vict_loss[:] = np.nan
165
+
166
+ # The subsample of random comparisons of wins is extracted
167
+ for ia in range(self.n_algorithms):
168
+ for ib in range(self.n_algorithms):
169
+ if ia!=ib:
170
+ j = np.random.randint(0, self.n_test, size=samples)
171
+ m1 = np.random.randint(0, self.runs, size=samples)
172
+ m2 = np.random.randint(0, self.runs, size=samples)
173
+ wins = (self.fit_data[ia, j, m1] < self.fit_data[ib, j, m2]).sum()
174
+ self.vict_loss[ia, ib] = wins
175
+
176
+ if draw == True:
177
+ # A half point is given for draws
178
+ for ia in range(self.n_algorithms):
179
+ for ib in range(self.n_algorithms):
180
+ if ia!=ib:
181
+ wins = 0
182
+ for j in range(self.n_test):
183
+ for m in range(self.runs):
184
+ wins += (self.fit_data[ia, j, m] == self.fit_data[ib, j, :]).sum()
185
+ self.vict_loss[ia, ib] = wins/2
186
+ self.vict_loss[ib, ia] = wins/2
187
+
188
+
189
+
190
+ def load_win_lose_table(
191
+ self,
192
+ table: NDArray[np.float64]
193
+ ) -> None:
194
+ """
195
+ Carga una matriz de victorias y derrotas.
196
+
197
+ Parameters
198
+ ----------
199
+ table : NDArray[np.float64]
200
+ Matriz cuadrada donde la posición [i, j] indica las
201
+ victorias del algoritmo i sobre el algoritmo j.
202
+
203
+ Returns
204
+ -------
205
+ None
206
+ """
207
+ self.vict_loss = table
208
+
209
+
210
+ def train_bt_model(
211
+ self,
212
+ resolution: float=6,
213
+ max_iter: int = 10000
214
+ ) -> tuple[list[str], NDArray[np.float64]]:
215
+ """
216
+ Fits a Bradley-Terry model using an iterative maximum likelihood
217
+ estimation procedure.
218
+
219
+ The algorithm repeatedly updates the model parameters until
220
+ convergence is achieved or the maximum number of iterations is
221
+ reached.
222
+
223
+ Parameters
224
+ ----------
225
+ resolution : float, optional
226
+ Number of significant digits required to declare convergence.
227
+ The fitting process stops when the maximum relative parameter
228
+ change falls below 10^(-resolution). Default is 6.
229
+
230
+ max_iter : int, optional
231
+ Maximum number of iterations allowed during the fitting
232
+ procedure. Default is 10000.
233
+
234
+ Returns
235
+ -------
236
+ tuple[list[str], NDArray[np.float64]]
237
+ A tuple containing:
238
+
239
+ - The algorithm names.
240
+ - The estimated Bradley-Terry ratings, normalized by their
241
+ geometric mean.
242
+ """
243
+ self.n_algorithms = self.vict_loss.shape[0]
244
+ self.p = np.ones(self.n_algorithms)
245
+
246
+ max_p_percent_change = np.inf
247
+ n = 0
248
+ p_old = np.ones(self.n_algorithms)
249
+ while max_p_percent_change>1/np.power(10, resolution) and n<max_iter:
250
+ n+=1
251
+ p_old[:] = self.p
252
+
253
+ for i in range(self.n_algorithms):
254
+ num = 0
255
+ den = 0
256
+ for j in range(self.n_algorithms):
257
+ if i!=j:
258
+ num+=self.vict_loss[i, j]*self.p[j]/(self.p[i]+self.p[j])
259
+ den+=self.vict_loss[j, i]/(self.p[i]+self.p[j])
260
+ self.p[i]=num/den
261
+
262
+ self.p /= gmean(self.p)
263
+
264
+ delta = np.abs(1-self.p/p_old)
265
+ max_p_percent_change = np.max(delta)
266
+ return self.algorithms, self.p
267
+
268
+ def __str__(self) -> str:
269
+ """
270
+ Devuelve una representación textual de los ratings calculados.
271
+
272
+ Returns
273
+ -------
274
+ str
275
+ Cadena con el nombre de cada algoritmo y su rating
276
+ correspondiente.
277
+ """
278
+ text=''
279
+ for i in range(len(self.algorithms)):
280
+ text += f'{self.algorithms[i]}: {self.p[i]}\n'
281
+ return text
282
+
283
+ def get_friedman_mean_rank(self) -> NDArray[np.float64]:
284
+ """
285
+ Calcula el ranking promedio de Friedman para cada algoritmo.
286
+
287
+ Primero se promedian las ejecuciones de cada prueba y después
288
+ se asigna un ranking relativo dentro de cada prueba. Finalmente
289
+ se obtiene el ranking medio de cada algoritmo.
290
+
291
+ Returns
292
+ -------
293
+ np.ndarray
294
+ Vector con el ranking promedio de Friedman para cada
295
+ algoritmo.
296
+ """
297
+ n_algorithms, n_test, n_runs = self.fit_data.shape
298
+
299
+ self.friedman_mean_rank = np.zeros(n_algorithms)
300
+ self.test_rank = np.zeros((n_algorithms, n_test))
301
+
302
+ self.mean_fit_data = self.fit_data.mean(axis=2)
303
+
304
+ for j in range(n_test):
305
+ algorithm_data = self.mean_fit_data[:,j]
306
+
307
+ #Getting the ranking of each algoritm
308
+ array = np.array(algorithm_data)
309
+ temp = array.argsort()
310
+ ranks = np.empty_like(temp)
311
+ ranks[temp] = np.arange(len(array))
312
+
313
+ self.test_rank[:, j] = ranks+1
314
+
315
+ self.friedman_mean_rank = self.test_rank.mean(axis=1)
316
+ return self.friedman_mean_rank
317
+
318
+
319
+ def create_scater_plot(
320
+ algorithms: list[str],
321
+ ratings: NDArray[np.float64],
322
+ rot: int | None = 45,
323
+ new_figure: bool | None = True
324
+ ) -> None:
325
+ """
326
+ Genera un gráfico de dispersión de los ratings obtenidos.
327
+
328
+ Parameters
329
+ ----------
330
+ algorithms : list[str]
331
+ Nombres de los algoritmos.
332
+
333
+ ratings : NDArray[np.float64]
334
+ Ratings asociados a cada algoritmo.
335
+
336
+ rot : int, optional
337
+ Rotación de las etiquetas del eje X.
338
+
339
+ new_figure : bool, optional
340
+ Si es True, crea una nueva figura antes de dibujar.
341
+
342
+ Returns
343
+ -------
344
+ None
345
+ """
346
+ if new_figure:
347
+ plt.Figure()
348
+
349
+ plt.scatter(ratings, np.arange(len(algorithms), 0, -1), c='Tab:Blue')
350
+
351
+ plt.yticks(np.arange(len(algorithms), 0, -1), algorithms)
352
+ plt.xticks()
353
+
354
+ plt.xscale('log')
355
+
356
+ ax = plt.gca()
357
+ ax.xaxis.set_minor_formatter(FuncFormatter(custom_minor_formatter))
358
+ ax.xaxis.set_major_formatter(FuncFormatter(custom_minor_formatter))
359
+
360
+ plt.tick_params(axis='x', which='both', bottom=True, labelbottom=True, rotation=rot)
361
+ plt.tick_params(axis='y', which='major', left=False)
362
+
363
+ plt.ylim(0, len(algorithms)+1)
364
+ plt.grid(True, axis='both', which='major', color='#BBBBBB', linestyle='--')
365
+ plt.ylabel('Algorithm')
366
+ plt.xlabel('G-Rating')
367
+ plt.tight_layout()
368
+ plt.show()
369
+
370
+ def custom_minor_formatter(
371
+ x: float,
372
+ pos: int
373
+ ) -> str | float:
374
+ """
375
+ Formateador personalizado para las marcas del eje logarítmico.
376
+
377
+ Muestra únicamente etiquetas para valores cuya mantisa sea
378
+ aproximadamente 1, 2, 3, 5 o 7.
379
+
380
+ Parameters
381
+ ----------
382
+ x : float
383
+ Valor de la marca.
384
+
385
+ pos : int
386
+ Posición de la marca dentro del eje.
387
+
388
+ Returns
389
+ -------
390
+ str | float
391
+ Texto que se mostrará en la etiqueta o cadena vacía
392
+ cuando la etiqueta deba ocultarse.
393
+ """
394
+ # Calculate the base coefficient (e.g., 200 -> 2, 0.5 -> 5)
395
+ # We do this to detect if the number 'starts' with 1, 2, or 5
396
+ base = np.log10(x)
397
+ decimal_part = base - np.floor(base)
398
+ coeff = 10 ** decimal_part
399
+
400
+ # Allow a small margin of error for floating point comparison
401
+ # We check if the number is close to 1, 2, or 5
402
+ if np.any([np.isclose(coeff, n, atol=0.1) for n in [1, 2, 3, 5, 7]]):
403
+ if base<0:
404
+ return np.round(x, decimals=int(-base)+1)
405
+ else:
406
+ return f'{int(x)}'
407
+ else:
408
+ return "" # Return empty string to hide the label
409
+
410
+
411
+ def signif(x: NDArray[np.float64], p: int) -> NDArray[np.float64]:
412
+ """
413
+ Redondea valores a una cantidad específica de cifras significativas.
414
+
415
+ Parameters
416
+ ----------
417
+ x : NDArray[np.float64]
418
+ Valor o conjunto de valores a redondear.
419
+
420
+ p : int
421
+ Número de cifras significativas deseadas.
422
+
423
+ Returns
424
+ -------
425
+ NDArray[np.float64]
426
+ Valores redondeados a p cifras significativas.
427
+ """
428
+ x = np.asarray(x)
429
+ x_positive = np.where(np.isfinite(x) & (x != 0), np.abs(x), 10**(p-1))
430
+ mags = 10 ** (p - 1 - np.floor(np.log10(x_positive)))
431
+ return np.round(x * mags) / mags
@@ -0,0 +1,11 @@
1
+ from grating import GRating
2
+
3
+ route = "../../data/Original_30D/best_fit"
4
+ model = GRating()
5
+ model.create_experimentation_table(route, filt=['original_30D_Ackley'], rettest=['Perm_0DB', 'Perm_DB'])
6
+ model.create_win_lose_table("permutation")
7
+ algorithms, G_rating = model.train_bt_model()
8
+ friedman_mean_rank = model.get_friedman_mean_rank()
9
+
10
+ print(algorithms, G_rating)
11
+ print(friedman_mean_rank)