Create winrate matrix from list of match results in python

Viewed 77

Characters named A, B, C, D, E play tons of games against each other for which I record their final result. Characters can also play against themselves. My DataFrame looks like this :

df = pd.DataFrame(data = [['A', 'B'], ['D', 'D'], ['E', 'A'], ['C', 'D'], ['E', 'D']], 
                  columns = ['Winner', 'Loser'])

I would like to build a win rate Matrix, where my columns are A, B, C, D, E, my rows index are also A, B, C, D, E, and each cell would be the win rate of the index vs the column. The diagonal would be mathematically 50%.

I don't have the right thinking to convert the dataframe to this matrix and would appreciate some help. Also note that I can't start aggregating the df to winrates with a casual groupby('Winner') as {['A', 'B'], ['A', 'B'], ['B', 'A']} should also be grouped together as a 66% winrate for A with a total of 3 games played.

2 Answers

Note: I've came up with another (arguably, much easier) way to obtain the requested output. Posting it here as a new answer, as I believe the approach to be sufficiently different from my other answer. Cf. the SO etiquette on this matter.


Here's an approach that relies on df.pivot and df.groupby to generate the requested matrix.

import pandas as pd
import numpy as np

data = {'Winner': {0: 'A', 1: 'D', 2: 'E', 3: 'C', 4: 'A', 5: 'B'},
 'Loser': {0: 'B', 1: 'D', 2: 'A', 3: 'D', 4: 'B', 5: 'A'}}

df = pd.DataFrame(data)

# set up a list for our index and columns
abc = list('ABCDE')

# apply `df.pivot`
# drop level 0 from multiindex cols: e.g. keep only 'A' in `('Winner', 'A')`
# reindex cols based on list `abc`, and turn values into bools with `notna()`
bools = df.pivot(index=None, columns=['Loser'], values=['Winner'])\
    .droplevel(0, axis=1).reindex(abc, axis=1).notna()

# use map to overwrite idx with values in `df['Winner']` and sort
bools.index = bools.index.map(df['Winner'])

# groupby index and get sum (i.e. count of all `True` vals)
summed_on_idx = bools.groupby(bools.index).sum()

# now also reindex based on list `abc` for index
summed_on_idx = summed_on_idx.reindex(abc).fillna(0)

# divide result by (sum result by its transposed version)
matrix = summed_on_idx/summed_on_idx.add(summed_on_idx.T)

# get rid of columns `name` ('Loser')
matrix.columns.name = None

print(matrix)

          A         B    C    D    E
A       NaN  0.666667  NaN  NaN  0.0
B  0.333333       NaN  NaN  NaN  NaN
C       NaN       NaN  NaN  1.0  NaN
D       NaN       NaN  0.0  0.5  NaN
E  1.000000       NaN  NaN  NaN  NaN

Solution below could perhaps be optimized, but it works:

  • Setup
import pandas as pd
import numpy as np

data = {'Winner': {0: 'A', 1: 'D', 2: 'E', 3: 'C', 4: 'A', 5: 'B'},
 'Loser': {0: 'B', 1: 'D', 2: 'A', 3: 'D', 4: 'B', 5: 'A'}}

df = pd.DataFrame(data)

print(df)

  Winner Loser
0      A     B
1      D     D
2      E     A
3      C     D
4      A     B
5      B     A
  • Phase 1: get win rates
# concat Winner/Loser
helper_df = df['Winner'].str.cat(df['Loser'])

# create dict with value counts per Winner/Loser-event
value_counts = helper_df.value_counts().to_dict()

# invert Winner/Loser, e.g. 'AB' -> 'BA'
helper_df = helper_df.to_frame()
helper_df['Loser'] = helper_df['Winner'].apply(lambda x: x[::-1])

# overwrite Winner/Loser with respective value_counts
helper_df = helper_df.applymap(value_counts.get)

# calc win_rates by dividing both cols by the sum over axis=0
win_rates = helper_df[['Winner','Loser']]\
    .div(helper_df.sum(axis=1), axis=0)['Winner'].to_numpy()

# result like: [0.66666667, 0.5, 1., 1., 0.66666667, 0.33333333]
  • Phase 2: get coordinates
# set up a list for our index and columns
abc = list('ABCDE')

# create dict with letters as keys, indices as values
d = {item: idx for idx, item in enumerate(abc)}

# apply to original df to get Winner/Loser as x, y-coordinates
df = df.applymap(d.get)
  • Phase 3: create array and assign to df
arr = np.empty((5, 5))
arr[:] = np.nan
# or simply `np.zeros((5,5))` if ok with `0`s for games that never occurred

# assign Winner/Loser as x, y
x, y = df.Winner.to_numpy(), df.Loser.to_numpy()

# populate arr at coordinates (and inverse) with rates
arr[x,y] = win_rates
arr[y,x] = 1-win_rates

# create df
matrix = pd.DataFrame(data=arr, index=abc, columns=abc)
print(matrix)

          A         B    C    D    E
A       NaN  0.666667  NaN  NaN  0.0
B  0.333333       NaN  NaN  NaN  NaN
C       NaN       NaN  NaN  1.0  NaN
D       NaN       NaN  0.0  0.5  NaN
E  1.000000       NaN  NaN  NaN  NaN

E.g.

  • "A" has a win rate of 66.7% against "B" (2 wins, 1 loss)
  • "D" has a win rate of 50% against "D"
Related