Methods
Average Dominance Index (ADI)
- _average_dominance_index.average_dominance_index(self) dict
Average Dominance Index (ADI) from an interaction dataframe/matrix.
Parameters
Returns
- average_dominance_index_dictdict
Average dominance indices, keys are individual names derived from either the Dataframe or name sequence (user provided, see class module for more details) and values are ADI scores (rounded to 4 decimal places)
See also
https://numpy.org/doc/stable/reference/generated/numpy.nansum.html https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html
Notes
The average dominance index of an individual is the average of all its dominance indices with all its interaction partners. A higher value indicates a higher dominance in the group. (Hemelrijk et al, 2005)
References
The Construction of Dominance Order: Comparing Performance of Five Methods Using an Individual Based Model C. K. Hemelrijk, J. Wantia and L. Gygax, Behaviour Vol. 142, No. 8 (Aug., 2005), pp. 1037-1058 doi: 10.1163/156853905774405290
Example:
1 mat = np.array([[0, 6, 9, 8, 5],
2 [0, 0, 4, 6, 0],
3 [0, 2, 0, 4, 7],
4 [1, 0, 5, 0, 3],
5 [0, 0, 2, 3, 0]], dtype='float32')
6 hier_mat = Hierarchia(mat, name_seq=['a', 'b', 'c','d','e'])
7 average_dominance_indices = hier_mat.average_dominance_index()
8 print(average_dominance_indices)
Result:
{'a': 0.9722, 'b': 0.5556, 'c': 0.3889, 'd': 0.2917, 'e': 0.2407}
David’s Scores
- _davids_score.davids_score(self, method: str = 'Pij', normalize: bool = False, order: bool = True) dict
David’s scores from an interaction dataframe/matrix.
Parameters
- param method:
str Valid arguments are ‘Dij’ and ‘Pij’. The method stands for the initial matrix state, the ‘Dij’ method use the corrected version for chance for dyadic dominance index (ref. de Vries (2006)). The ‘Pij’ method use the proportion of wins to compute David’s Scores
- param normalize:
bool Normalization of the David’s scores using formula of NormDS = (DS+N(N −1)/2)/N (ref. de Vries (2006)) (False)
- param order:
bool If True, the resulting dictionary is sorted in descending order by David’s scores (True)
Returns
- davids_score_dictdict
David’s scores, keys are individual names derived from either the Dataframe or name sequence (user provided, see class module for more details) and values are David’s scores (rounded to 4 decimal places)
See also
https://numpy.org/doc/stable/reference/generated/numpy.nansum.html https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html
Notes
It is proposed that for ranking objects or players in an incomplete paired-comparison experiment or tournament with at most one comparison per pair, the score of a player, C, be the total number of (a) wins of players defeated by C minus losses of players to whom C lost, plus (b) C’s wins minus C’s losses. A tied match counts as half a win plus half a loss. More general tournaments can be treated similarly. (David, 1987)
References
David, H. A. 1987. Ranking from unbalanced paired-comparison data. Biometrika, 74, 432–436.
Gammell MP, de Vries H, Jennings DJ, Carlin CM, Hayden TJ (2003). “David’s score: a more appropriate dominance ranking method than Clutton-Brock et al.’s index.” Animal Behaviour, 66, 601-605. doi: 10.1006/anbe.2003.2226.
de Vries H, Stevens JMG, Vervaecke H (2006). “Measuring and testing the steepness of dominance hierarchies.” Animal Behaviour, 71, 585-592. doi: 10.1016/j.anbehav.2005.05.015.
Example:
1 mat = np.array([[0, 6, 9, 8, 5],
2 [0, 0, 4, 6, 0],
3 [0, 2, 0, 4, 7],
4 [1, 0, 5, 0, 3],
5 [0, 0, 2, 3, 0]], dtype='float32')
6 hier_mat = Hierarchia(mat, name_seq=['a', 'b', 'c','d','e'])
7 davids_scores = hier_mat.davids_score()
8 print(davids_scores)
Result:
{'a': 8.4444, 'b': 1.6111, 'c': -2.3333, 'd': -3.6667, 'e': -4.0556}
ELO Rating
- _elo.elo(self, start_value: float = 1000, K: float = 100, normal_probability: bool = False) dict
Elo rating from an interaction dataframe.
Parameters
- param start_value:
float Parameter of the Elo function that determines the initial scores. It does not have an effect on relative differences after the calculation. (1000)
- param K:
float Parameter of the Elo function that acts as a factor and determines the speed at which scores change after an interaction. Optimization might be a good idea but not implemented yet. (100)
- param normal_probability:
bool Adjust the calculation of expected win/loss probabilities; default is Logistic, the normal probabilities are calculated using standard normal tables. For normalised probabilities, refer to https://handbook.fide.com (False)
Returns
- elo_dictdict
Elo ratings, keys are individual names derived from the Dataframe and values are Elo scores (rounded to 4 decimal places)
See also
https://mathworld.wolfram.com/Erfc.html https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.erfc.html
Notes
From Wikipedia:
The Elo rating system is a method for calculating the relative skill levels of players in zero-sum games such as chess. It is named after its creator Arpad Elo, a Hungarian-American physics professor. The Elo system was originally invented as an improved chess-rating system over the previously used Harkness system, but is also used as a rating system in association football, American football, baseball, basketball, pool, table tennis, Go, board games such as Scrabble and Diplomacy, and esports.
The difference in the ratings between two players serves as a predictor of the outcome of a match. Two players with equal ratings who play against each other are expected to score an equal number of wins. A player whose rating is 100 points greater than their opponent’s is expected to score 64%; if the difference is 200 points, then the expected score for the stronger player is 76%.
References
Elo, A. E. 1978. The Rating of Chess Players, Past and Present. New York: Arco.
Albers, P. C. H. & de Vries, H. 2001. Elo-rating as a tool in the sequential estimation of dominance strengths. Animal Behaviour, 61, 489-495. (DOI: 10.1006/anbe.2000.1571)
The Hierarchia class should be initiated with a Pandas Dataframe since the method relies on the order of the interactions.
_elo.elo() will raise an exception, if Hierarchia class initiated with a matrix.
Example:
1df = pd.DataFrame({'winner': ['c', 'a', 'a', 'b', 'd', 'b', 'a', 'c', 'b'],
2 'loser': ['a', 'b', 'b', 'a', 'c', 'd', 'b', 'b', 'a']})
3hierarchia = Hierarchia(df, 'winner', 'loser')
4elo_ratings = hierarchia.elo(start_value=1000, K=100, normal_probability=False)
5print(elo_ratings)
Result:
{'a': 971.0724, 'b': 993.3937, 'c': 1040.421, 'd': 995.113}
Randomized ELO Rating
- _randomized_elo.randomized_elo(self, start_value: float = 1000, K: float = 100, n: int = 1000, normal_probability: bool = False) dict
Randomized Elo rating from an interaction dataframe/matrix.
Parameters
- param start_value:
float Parameter of the Elo function that determines the initial scores. It does not have an effect on relative differences after the calculation. (1000)
- param K:
float Parameter of the Elo function that acts as a factor and determines the speed at which scores change after an interaction. Optimization might be a good idea but not implemented yet. (100)
- param n:
integer Parameter to adjust number of iterations for random ordering. Higher numbers result in more stable Elo ratings. The number of random orders. (1000)
- param normal_probability:
bool Adjust the calculation of expected win/loss probabilities; default is Logistic, the normal probabilities are calculated using standard normal tables. For normalised probabilities, refer to https://handbook.fide.com (False)
Returns
- randomized_elo_dictdict
Randomized Elo ratings, keys are individual names derived from either the Dataframe or name sequence (user provided, see class module for more details) and values are Randomized Elo scores (rounded to 4 decimal places)
See also
https://mathworld.wolfram.com/Erfc.html https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.erfc.html
Notes
A practical guide for inferring reliable dominance hierarchies and estimating their uncertainty However, most behavioural studies assume that individual dominance rank is relatively stable over time. We propose an improvement of the original Elo-rating based on randomizing the order in which interactions occurred (n = 1 000 randomizations throughout). Randomising the order that interactions are recorded produces slightly different individual Elo-ratings each time, from which we can calculate a mean individual rank. This method also allows estimating the 95% range of individual ranks when run on a single interaction dataset. (Sánchez-Tójar et al, 2017)
References
Elo, A. E. 1978. The Rating of Chess Players, Past and Present. New York: Arco.
Sánchez-Tójar, Alfredo & Schroeder, Julia & Farine, Damien. (2017). A practical guide for inferring reliable dominance hierarchies and estimating their uncertainty. bioRxiv. 111146. 10.1101/111146.
Example:
1 mat = np.array([[0, 6, 9, 8, 5],
2 [0, 0, 4, 6, 0],
3 [0, 2, 0, 4, 7],
4 [1, 0, 5, 0, 3],
5 [0, 0, 2, 3, 0]], dtype='float32')
6 hier_mat = Hierarchia(mat, name_seq=['a', 'b', 'c','d','e'])
7 randomized_elo_ratings = hier_mat.randomized_elo(start_value=1000, K=100, n=2000, normal_probability=True)
8 print(randomized_elo_ratings)
Result (random process):
{'a': 1373.0278, 'b': 1070.3446, 'c': 910.9598, 'd': 857.1167, 'e': 788.5511}
I&SI (1998)
- _ISI98.ISI98(self, runs: int = 1000, verbose: bool = False) dict
I&SI 1998 from an interaction dataframe/matrix.
Parameters
- param runs:
int Parameter to adjust number of iterations for random ordering. Higher numbers result increase chance of finding the best sequence. Original paper suggests as low as 100 runs but our package use default 1000. (1000)
- param verbose:
bool Print initial, intermediate and final inconsistencies, strength of inconsistencies and matrices. Used for detailed output of computational process. (False)
Returns
- best_seqdict
Rankings from the I&SI 1998 algorithm, optimality cannot be guaranteed due to random search but given high number of runs, it should be stable. The indices are used as keys and values are the ranks.
See also
https://numpy.org/doc/stable/reference/generated/numpy.triu.html
Notes
Two criteria are used in a prioritized way in reorganizing the dominance matrix to find an order that is most consistent with a linear hierarchy: first, minimization of the numbers of inconsistencies and, second, minimization of the total strength of the inconsistencies. The linear ordering procedure, which involves an iterative algorithm based on a generalized swapping rule, is feasible for matrices of up to 80 individuals. The procedure can be applied to any dominance matrix, since it does not make any assumptions about the form of the probabilities of winning and losing. The only assumption is the existence of a linear or near-linear hierarchy which can be verified by means of a linearity test. (de Vries, 1998)
References
de Vries, H. 1998 Finding a dominance order most consistent with a linear hierarchy: a new procedure and review. Animal Behaviour, 55, 827-843
Example:
1 mat = np.array([[0, 6, 9, 8, 5],
2 [0, 0, 4, 6, 0],
3 [0, 2, 0, 4, 7],
4 [1, 0, 5, 0, 3],
5 [0, 0, 2, 3, 0]], dtype='float32')
6 hier_mat = Hierarchia(mat, name_seq=['a', 'b', 'c','d','e'])
7 ISI98_ranks = hier_mat.ISI98(runs=1000, verbose=True)
8 print(ISI98_ranks)
Result:
Initial Phase
-------------
Initial number of inconsistencies: 1
Initial number of inconsistencies: 1
End of Iterative Phase
-------------
Optimal or near-optimal linear ranking is found!
Best sequence after iterative phase: ['a', 'b', 'd', 'c', 'e']
Matrix after iterative phase:
[[0 6 8 9 5]
[0 0 6 4 0]
[1 0 0 5 3]
[0 2 4 0 7]
[0 0 3 2 0]]
Final Phase
-------------
Final number of inconsistencies: 0
Final number of inconsistencies: 0
Best Sequence: ['a', 'b', 'd', 'c', 'e']
Matrix after final phase:
[[0 6 8 9 5]
[0 0 6 4 0]
[1 0 0 5 3]
[0 2 4 0 7]
[0 0 3 2 0]]
{'a': 0, 'b': 1, 'd': 2, 'c': 3, 'e': 4}
ADAGIO
- _adagio.adagio(self, preprocessing: bool = False, plot_network: bool = False, rank: str = 'topological') dict
ADAGIO from an interaction dataframe/matrix.
Parameters
- param preprocessing:
bool preprocessing of the initial matrix according to the original paper [Douglas, 2016]. For a given dominance network, the preprocessed weight of the edge between the individual A that appears dominant and the individual B that appears subordinate is set to the difference in the number of interactions won by A and the number of interactions won by B. (False)
- param plot_network:
bool Simple network plot of the derived directed graph network from the dataframe/matrix. (False)
- param rank:
str Final ranking algorithm after the ADAGIO iterations. Three options are possible; ‘topological’ that yields topological order of the final matrix. ‘top’ and ‘bottom’ are self-explanatory and can be found at original paper. (topological)
Returns
- rank_dictdict
Rankings from the ADAGIO algorithm, ranked based on the ranking parameter chosen. Keys are identification of individuals (see name_seq) and the values are the ranks.
See also
https://networkx.org/documentation/networkx-1.9/reference/generated/networkx.algorithms.components.strongly_connected.strongly_connected_components.html https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.dag.topological_sort.html
Notes
ADAGIO, for assessing the structure of dominance networks. ADAGIO computes dominance hierarchies, in the form of directed acyclic graphs, to represent the dominance relations of a given group of animals. Thus far, most methods for computing dominance ranks assume implicitly that the dominance relation is a total order of the individuals in a group. ADAGIO does not assume or require this to be always true, and is hence more appropriate for analysing dominance hierarchies that are not strongly linear. (Douglas, 2017)
References
Douglas, P. H., Ngomo, A. C. N., & Hohmann, G. (2017). A novel approach for dominance assessment in gregarious species: ADAGIO. Animal Behaviour, 123, 21-32.
Example:
1 mat = np.array([[0, 6, 9, 8, 5],
2 [0, 0, 4, 6, 0],
3 [0, 2, 0, 4, 7],
4 [1, 0, 5, 0, 3],
5 [0, 0, 2, 3, 0]], dtype='float32')
6 hier_mat = Hierarchia(mat, name_seq=['a', 'b', 'c','d','e'])
7 adagio_ranks = hier_mat.adagio(preprocessing=True, plot_network=False, rank='top')
8 print(adagio_ranks)
Result:
{'a': 0, 'b': 1, 'd': 2, 'c': 3, 'e': 4}