Hey all, inspired by Samual talking about different chances to win a match depending on your team color, I put some effort into data-mining the Xonstats DB to have a closer look at this issue
I came up with a small Python script which filters & processes the data from all matches on Xonstats, and uses ROOT to generate two plots for each gametype: The first one shows the number of games played per server and per map (one axis each for server and map; the color shows the number of games). The second one shows the relative difference of the number of games won between red and blue. It is defined simply as ratio = (wins_red - wins_blue ) / total_games. Therefore, -1 means all games were won by red, and +1 means all were won by blue. This is also represented by the color in the according plot.
Note that I filtered out most of the Insta+Hook servers, mainly because the hook may even out map asymmetries and the like. Normal Insta and Overkill is still included.
For example, one can see that on some (probably public) servers, red wins a majority of games. Smilecythe already suggested that the reason could be people joining the red team by default. On the pickup servers, that idea should not apply though.
One of my personal ideas to explain these red-blue asymmetry is – apart from obvious asymmetries in the maps (which we don't have except for maybe a small number of maps) – a chirality effect ("handed-ness"), meaning that it could make a difference if you have make a left or right turn when you attack.
Please tell me what you think of this, if you have any ideas etc. I would love to see some discussion on this IMHO very interesting topic =)
Link to all plots (ctf, tdm, ca,kh) in full-resolution: http://imgur.com/a/b047B/all
UPDATE: Added plot where win ratio is weighted by number of games (it turned out that most of the extreme values in the win ratio plot are caused by a really small number of games played).
UPDATE 2: Fixed kinda misleading colorscheme for the values. Using an even number of colors shifted the perceived mean value towards red, giving the wrong impression in the plots.
Python script to generate that data (input is simply a CSV file):
I came up with a small Python script which filters & processes the data from all matches on Xonstats, and uses ROOT to generate two plots for each gametype: The first one shows the number of games played per server and per map (one axis each for server and map; the color shows the number of games). The second one shows the relative difference of the number of games won between red and blue. It is defined simply as ratio = (wins_red - wins_blue ) / total_games. Therefore, -1 means all games were won by red, and +1 means all were won by blue. This is also represented by the color in the according plot.
Note that I filtered out most of the Insta+Hook servers, mainly because the hook may even out map asymmetries and the like. Normal Insta and Overkill is still included.
For example, one can see that on some (probably public) servers, red wins a majority of games. Smilecythe already suggested that the reason could be people joining the red team by default. On the pickup servers, that idea should not apply though.
One of my personal ideas to explain these red-blue asymmetry is – apart from obvious asymmetries in the maps (which we don't have except for maybe a small number of maps) – a chirality effect ("handed-ness"), meaning that it could make a difference if you have make a left or right turn when you attack.
Please tell me what you think of this, if you have any ideas etc. I would love to see some discussion on this IMHO very interesting topic =)
Link to all plots (ctf, tdm, ca,kh) in full-resolution: http://imgur.com/a/b047B/all
UPDATE: Added plot where win ratio is weighted by number of games (it turned out that most of the extreme values in the win ratio plot are caused by a really small number of games played).
UPDATE 2: Fixed kinda misleading colorscheme for the values. Using an even number of colors shifted the perceived mean value towards red, giving the wrong impression in the plots.
Python script to generate that data (input is simply a CSV file):
Code:
#!/usr/bin/env python
import ROOT
import sys
from collections import OrderedDict
from math import log
from array import array
TEAMS = { 5: 'red', 14: 'blue' }
#GAMETYPE = "ctf"
GAMETYPE = sys.argv[1]
MIN_GAMEID = 1
MAP_BLACKLIST = [
]
SERVER_BLACKLIST = [
#"[MoN] Vehicle",
#"[MoN] Overkill",
#"Overkill",
#"Overkill Mod",
"{Minsta+Hook}",
"[Minsta|Hook]",
"[MinstaGib|Hook]",
"MinstaGib+Hook",
"Minsta+Hook",
"Instagib+Hook",
]
NMAPS = 50
NSERVERS = 50
NCOLORS = 99
CANVAS_SIZE = 2000
CRED = "\033[1;31m"
CBLUE = "\033[1;34m"
CWHITE = "\033[1;37m"
CNONE = "\033[0m"
winner_by_map = OrderedDict()
winner_by_server = OrderedDict()
winner_by_both = {}
class Winner:
def __init__(self):
self.total = 0
self.red = 0
self.blue = 0
def ratio(self):
if self.total > 0:
return float(self.red - self.blue) / float(self.total)
return None
total_count = 0
with open("red_vs_blue.csv",'r') as f:
for line in f.readlines():
fields = line.strip().split("\t")
game_id, game_type, server_id, server_name, map_id, map_name, winner_tid = tuple(fields)
skip = False
if game_type != GAMETYPE:
skip = True
if game_id < MIN_GAMEID:
skip = True
for m in MAP_BLACKLIST:
if m in map_name:
skip = True
break
for s in SERVER_BLACKLIST:
if s in server_name:
skip = True
break
if skip:
continue
try:
winner_team = TEAMS[int(winner_tid)]
except:
winner_team = None
map_name = map_name[:60]
server_name = server_name[:60]
if not map_name in winner_by_map:
winner_by_map[map_name] = Winner()
if not server_name in winner_by_server:
winner_by_server[server_name] = Winner()
if not map_name in winner_by_both:
winner_by_both[map_name] = {}
if not server_name in winner_by_both[map_name]:
winner_by_both[map_name][server_name] = Winner()
total_count += 1
winner_by_map[map_name].total += 1
winner_by_server[server_name].total += 1
winner_by_both[map_name][server_name].total += 1
if winner_team == 'red':
winner_by_map[map_name].red += 1
winner_by_server[server_name].red += 1
winner_by_both[map_name][server_name].red += 1
elif winner_team == 'blue':
winner_by_map[map_name].blue += 1
winner_by_server[server_name].blue += 1
winner_by_both[map_name][server_name].blue += 1
maps = sorted(winner_by_map.items(), key=lambda x: -x[1].total)
servers = sorted(winner_by_server.items(), key=lambda x: -x[1].total)
print "Total Games:", total_count
print "Win Ratio by Map:"
for m,w in maps[:NMAPS]:
if w.ratio() < 0.:
print CRED,
elif w.ratio() > 0.:
print CBLUE,
else:
print CWHITE,
print " %-30s - T:%6d R:%6d B:%6d -> %6.5f" % (m[:30], w.total, w.red, w.blue, w.ratio() ),
print CNONE
print "Win Ratio by Server:"
for s,w in servers[:NSERVERS]:
if w.ratio() < 0.:
print CRED,
elif w.ratio() > 0.:
print CBLUE,
else:
print CWHITE,
print " %-30s - T:%6d R:%6d B:%6d -> %6.5f " % (s[:30], w.total, w.red, w.blue, w.ratio() ),
print CNONE
hGames = ROOT.TH2D("hGames", "Number of %s Games (%d total)" % (GAMETYPE.upper(), total_count), \
NMAPS, 0, NMAPS-1, NSERVERS, 0, NSERVERS-1)
hRatio = ROOT.TH2D("hRatio", "Red-Blue %s Win Ratio" % (GAMETYPE.upper()), \
NMAPS, 0, NMAPS-1, NSERVERS, 0, NSERVERS-1)
hRatioWeighted = ROOT.TH2D("hRatioWeighted", "Weighted Red-Blue %s Win Ratio" % (GAMETYPE.upper()), \
NMAPS, 0, NMAPS-1, NSERVERS, 0, NSERVERS-1)
taken = []
for map_name in [ m for m,w in maps[:NMAPS] ]:
for server_name in [ s for s,w in servers[:NSERVERS] ]:
try:
w = winner_by_both[map_name][server_name]
except:
continue
key = (map_name, server_name)
if key in taken:
print "Skipping map: %s server: %s" % (map_name,server_name)
continue
taken.append( key )
hGames.Fill( map_name, server_name, w.total )
hRatio.Fill( map_name, server_name, w.ratio() )
hRatioWeighted.Fill( map_name, server_name, w.ratio() * w.total/total_count )
ROOT.gStyle.SetOptStat(0)
gradientR = array('d', [ 1.00, 0.00, 1.00 ])
gradientG = array('d', [ 1.00, 1.00, 0.00 ])
gradientB = array('d', [ 1.00, 0.00, 0.00 ])
gradientS = array('d', [ 0.00, 0.50, 1.00 ])
ROOT.TColor.CreateGradientColorTable(len(gradientS), gradientS, gradientR, gradientG, gradientB, NCOLORS)
cGames = ROOT.TCanvas("cGames", "", CANVAS_SIZE, CANVAS_SIZE)
cGames.cd()
cGames.SetLogz()
pGames = cGames.GetPad(0)
pGames.SetLeftMargin(0.25)
pGames.SetRightMargin(0.1)
pGames.SetBottomMargin(0.25)
pGames.SetTopMargin(0.1)
hGames.SetContour(NCOLORS)
hGames.Draw("COLZ")
hGames.GetXaxis().SetLabelSize(0.015)
hGames.GetYaxis().SetLabelSize(0.015)
hGames.GetZaxis().SetLabelSize(0.03)
hGames.GetXaxis().LabelsOption("v")
hGames.GetYaxis().LabelsOption("h")
cGames.Modified()
cGames.Update()
cGames.SaveAs("red_vs_blue-%s_games.root" % GAMETYPE)
cGames.SaveAs("red_vs_blue-%s_games.png" % GAMETYPE)
gradientR = array('d', [ 1.00, 1.00, 0.00 ])
gradientG = array('d', [ 0.00, 1.00, 0.00 ])
gradientB = array('d', [ 0.00, 1.00, 1.00 ])
gradientS = array('d', [ 0.00, 0.50, 1.00 ])
ROOT.TColor.CreateGradientColorTable(len(gradientS), gradientS, gradientR, gradientG, gradientB, NCOLORS)
cRatio = ROOT.TCanvas("cRatio", "", CANVAS_SIZE, CANVAS_SIZE)
cRatio.cd()
#cRatio.SetLogz()
pRatio = cRatio.GetPad(0)
pRatio.SetLeftMargin(0.25)
pRatio.SetRightMargin(0.1)
pRatio.SetBottomMargin(0.25)
pRatio.SetTopMargin(0.1)
hRatio.SetContour(NCOLORS)
hRatio.Draw("COLZ")
hRatio.GetXaxis().SetLabelSize(0.015)
hRatio.GetYaxis().SetLabelSize(0.015)
hRatio.GetZaxis().SetLabelSize(0.03)
hRatio.GetXaxis().LabelsOption("v")
hRatio.GetYaxis().LabelsOption("h")
zrange = max(abs(hRatio.GetMinimum()), abs(hRatio.GetMaximum()))
hRatio.GetZaxis().SetRangeUser(-zrange,zrange)
cRatio.Modified()
cRatio.Update()
cRatio.SaveAs("red_vs_blue-%s_ratio.root" % GAMETYPE)
cRatio.SaveAs("red_vs_blue-%s_ratio.png" % GAMETYPE)
gradientR = array('d', [ 1.00, 1.00, 1.00, 0.00, 0.00 ])
gradientG = array('d', [ 1.00, 0.00, 1.00, 0.00, 1.00 ])
gradientB = array('d', [ 0.00, 0.00, 1.00, 1.00, 1.00 ])
gradientS = array('d', [ 0.00, 0.30, 0.50, 0.70, 1.00 ])
ROOT.TColor.CreateGradientColorTable(len(gradientS), gradientS, gradientR, gradientG, gradientB, NCOLORS)
cRatioWeighted = ROOT.TCanvas("cRatioWeighted", "", CANVAS_SIZE, CANVAS_SIZE)
cRatioWeighted.cd()
#cRatioWeighted.SetLogz()
pRatioWeighted = cRatioWeighted.GetPad(0)
pRatioWeighted.SetLeftMargin(0.25)
pRatioWeighted.SetRightMargin(0.1)
pRatioWeighted.SetBottomMargin(0.25)
pRatioWeighted.SetTopMargin(0.1)
hRatioWeighted.SetContour(NCOLORS)
hRatioWeighted.Draw("COLZ")
hRatioWeighted.GetXaxis().SetLabelSize(0.015)
hRatioWeighted.GetYaxis().SetLabelSize(0.015)
hRatioWeighted.GetZaxis().SetLabelSize(0.015)
hRatioWeighted.GetXaxis().LabelsOption("v")
hRatioWeighted.GetYaxis().LabelsOption("h")
zrange = max(abs(hRatioWeighted.GetMinimum()), abs(hRatioWeighted.GetMaximum()))
hRatioWeighted.GetZaxis().SetRangeUser(-zrange,zrange)
cRatioWeighted.Modified()
cRatioWeighted.Update()
cRatioWeighted.SaveAs("red_vs_blue-%s_ratio_weighted.root" % GAMETYPE)
cRatioWeighted.SaveAs("red_vs_blue-%s_ratio_weighted.png" % GAMETYPE)
Web: YouTube • SoundCloud • Flickr • zykure.de • [unconnected] IRC: #uc.xonotic #xonotic #xonotic.de #xonotic.pickup |