User Tools

Site Tools


aperrot_rlyazidi:lyazidi_reda_perrot_alexandre

Visual Analytics Course

Projet.py
# Powered by Python 2.7
 
# To cancel the modifications performed by the script
# on the current graph, click on the undo button.
 
# Some useful keyboards shortcuts : 
#   * Ctrl + D : comment selected lines.
#   * Ctrl + Shift + D  : uncomment selected lines.
#   * Ctrl + I : indent selected lines.
#   * Ctrl + Shift + I  : unindent selected lines.
#   * Ctrl + Return  : run script.
#   * Ctrl + F  : find selected text.
#   * Ctrl + R  : replace selected text.
#   * Ctrl + Space  : show auto-completion dialog.
 
from tulip import *
import os
import math
# the updateVisualization(centerViews = True) function can be called
# during script execution to update the opened views
 
# the pauseScript() function can be called to pause the script execution.
# To resume the script execution, you will have to click on the "Run script " button.
 
# the runGraphScript(scriptFile, graph) function can be called to launch another edited script on a tlp.Graph object.
# The scriptFile parameter defines the script name to call (in the form [a-zA-Z0-9_]+.py)
 
# the main(graph) function must be defined 
# to run the script on the current graph
 
global viewLabel
global viewColor
 
def shadeOfGrey(n):
	return tlp.Color(n,n,n)
 
class ChessVisu:
 
	def __init__(self, graph):
		self.graph = graph
		self.monthProperty = graph.getIntegerProperty("month")
		self.scoreProperty = graph.getDoubleProperty("score")
		self.viewLabel =  graph.getStringProperty("viewLabel")
		self.viewLayout =  graph.getLayoutProperty("viewLayout")
		self.monthlyScore = graph.getDoubleVectorProperty("Monthly Score")
		self.progression = graph.getDoubleProperty("Progression")
		self.voisins = graph.getIntegerProperty("NbVoisins")
		self.GreyLevel = graph.getIntegerProperty("GreyLevel")
		self.defaultNodeSize = tlp.Size(100.0)
 
 
		#player nodes
		self.players = {}
 
		#creating the graph
		dataPath = os.getcwd()+"/training_data.csv"
		print dataPath
		source = open(dataPath)
		source.readline()
		for line in source :
			words = line.rstrip('\n').split(',')
			month = int(words[0])
			white = int(words[1])
			black = int(words[2])
			score = float(words[3])
 
			if not white in self.players :
				self.players[white] = graph.addNode()
				self.viewLabel[self.players[white]] = words[1]
			if not black in self.players :
				self.players[black] = graph.addNode()
				self.viewLabel[self.players[black]] = words[2]
			currentEdge = graph.addEdge(self.players[white], self.players[black])
			self.monthProperty[currentEdge] = month
			self.scoreProperty[currentEdge] = score
		graph.setName("Chess games")
		self.graph.computeLayoutProperty("Circular", self.viewLayout)
 
		for node in self.graph.getNodes():
			self.monthlyScore[node] = 100*[0]
			self.voisins[node] = graph.deg(node)
 
	def setDefaultNodeSize(f) :
		assert type(f) == "float"
		self.defaultNodeSize = tlp.Size(f)
 
	def createMonthGraphs(self):
		self.monthGraph = {}
		monthRoot = graph.addSubGraph("Months")
		for edge in graph.getEdges():
			month = self.monthProperty[edge]
			if not month in self.monthGraph:
				self.monthGraph[month] = monthRoot.addSubGraph(str(month))
			subGraph = self.monthGraph[month]
			subGraph.addNode(self.graph.source(edge))
			subGraph.addNode(self.graph.target(edge))
			subGraph.addEdge(edge)
 
	def computeMeanScores(self):
		#calculating total score for players
		for node in graph.getNodes() :
			score = 0
			for outEdge in graph.getOutEdges(node) :
				score += self.scoreProperty[outEdge]
			for inEdge in graph.getInEdges(node) :
				score += 1 - self.scoreProperty[inEdge]
			self.scoreProperty[node] = score/graph.deg(node)
 
 
	def getScoreClass(self, node):
		return math.trunc(self.scoreProperty[node]*10)/10.0
 
	def createScoresGraphs(self):
		print "creating scores graphs"
		subGraph = self.graph.addSubGraph("Scores")
		self.scoreGraphs = {}
		for node in self.graph.getNodes():
			scoreClass = self.getScoreClass(node)
			if not scoreClass in self.scoreGraphs:
				self.scoreGraphs[scoreClass] = subGraph.addSubGraph(str(scoreClass))
			self.scoreGraphs[scoreClass].addNode(node)
		for edge in self.graph.getEdges():
			srcScore = self.getScoreClass(self.graph.source(edge))
			trgScore = self.getScoreClass(self.graph.target(edge))
			if srcScore == trgScore:
				self.scoreGraphs[srcScore].addEdge(edge)
 
	def computeMonthlyScores(self):
		for node in self.graph.getNodes():
			total = 100*[0.0]
			nb = 100*[0]
			for edge in self.graph.getInEdges(node):
				month = self.monthProperty[edge]-1
				total[month] -= self.scoreProperty[edge]
				nb[month] += 1
			for edge in self.graph.getOutEdges(node):
				month = self.monthProperty[edge]-1
				total[month] += self.scoreProperty[edge]
				nb[month] += 1
			for month in range(len(total)):
				if nb[month] > 0:
					self.monthlyScore.setNodeEltValue(node, month, total[month]/nb[month])
				else:
					self.monthlyScore.setNodeEltValue(node, month, 0.0)
		for month, mG in self.monthGraph.iteritems():
			nbBestThisMonth = 0
			if self.monthGraph.has_key(month-1):	
				for node in mG.getNodes():	
					score1 = self.monthlyScore[node][month-2]
					score2 = self.monthlyScore[node][month-1]
					diff = score2-score1
					self.progression[node] += diff
					color = mG.getLocalColorProperty("viewBorderColor")
					if diff > 0:
						color[node] = tlp.Color(0,255,0)
						nbBestThisMonth += 1
					elif diff < 0:
						color[node] = tlp.Color(255,0,0)
					else:
						color[node] = tlp.Color(0,0,255)
 
	def setColorNodeIntoPlayerFavoriteColor(self,node) :
		nbblacks = self.numberOfTimePlayedBlack(node)
		allmatches = self.numberOfMatches(node)
		numColor = nbblacks * 255 / allmatches
		GreyLevel = self.graph.getIntegerProperty("GreyLevel")
		shade = shadeOfGrey(numColor)
		color = graph.getColorProperty("viewColor")
		GreyLevel[node] = numColor
		color[node] = shade
 
	def setColorAllNodesIntoPlayerFavoriteColor(self) :
		for n in graph.getNodes() :
			self.setColorNodeIntoPlayerFavoriteColor(n)
 
 
	def numberOfTimePlayedBlack(self,node) :
		return self.graph.indeg(node)
 
	def numberOfMatches(self,node) :
		return self.graph.deg(node)
 
	def iteratorSize(self, iterator) :
		nb =0
		for i in iterator:
			nb += 1
		return nb
 
	def setSizeNodeAccordingToScore(self,node) :
		size = graph.getSizeProperty("viewSize")
		size[node] = self.defaultNodeSize * self.scoreProperty[node] + 1
 
	def setAllNodesSizeAccordingToScore(self) :
		for n in graph.getNodes() :
			self.setSizeNodeAccordingToScore(n)
 
	def getAverageLevelOfOppenents(self, node) :
		nb = 0.0
		score = 0.0
		for n in self.graph.getInOutNodes(node) :
			nb += 1
			score += self.scoreProperty[n]
		return score / nb
 
	def getEdges(self) :
		return self.graph.getEdges()
 
def main(graph): 
	viewBorderColor =  graph.getColorProperty("viewBorderColor")
	viewBorderWidth =  graph.getDoubleProperty("viewBorderWidth")
	viewColor =  graph.getColorProperty("viewColor")
	viewFont =  graph.getStringProperty("viewFont")
	viewFontSize =  graph.getIntegerProperty("viewFontSize")
	viewLabel =  graph.getStringProperty("viewLabel")
	viewLabelColor =  graph.getColorProperty("viewLabelColor")
	viewLabelPosition =  graph.getIntegerProperty("viewLabelPosition")
	viewLabelRotation =  graph.getDoubleProperty("viewLabelRotation")
	viewLayout =  graph.getLayoutProperty("viewLayout")
	viewMetaGraph =  graph.getGraphProperty("viewMetaGraph")
	viewMetric =  graph.getDoubleProperty("viewMetric")
	viewRotation =  graph.getDoubleProperty("viewRotation")
	viewSelection =  graph.getBooleanProperty("viewSelection")
	viewShape =  graph.getIntegerProperty("viewShape")
	viewSize =  graph.getSizeProperty("viewSize")
	viewSrcAnchorShape =  graph.getIntegerProperty("viewSrcAnchorShape")
	viewSrcAnchorSize =  graph.getSizeProperty("viewSrcAnchorSize")
	viewTexture =  graph.getStringProperty("viewTexture")
	viewTgtAnchorShape =  graph.getIntegerProperty("viewTgtAnchorShape")
	viewTgtAnchorSize =  graph.getSizeProperty("viewTgtAnchorSize")
 
	visu = ChessVisu(graph)
	visu.setColorAllNodesIntoPlayerFavoriteColor()
 
	visu.computeMeanScores()
	visu.createMonthGraphs()
	visu.createScoresGraphs()
	visu.computeMonthlyScores()
	visu.setAllNodesSizeAccordingToScore()
	total = 0.0
	#for a white player
	nbloss = 0.0
	nbWins = 0.0
	nbDraw = 0.0
	for e in visu.getEdges() :
		total +=1
		if visu.scoreProperty[e] == 1 :
			nbWins +=1
		elif visu.scoreProperty[e] == 0 :
			nbloss +=1
		else :
			nbDraw +=1
	print 'for a white player in %'
	print 'nbWins: ' + str((nbWins/total *100))
	print 'nbloss: ' + str((nbloss/total *100))
	print 'nbDraw: ' + str((nbDraw/total *100))

Bordeaux roadmap / Lyazidi Reda & Alexandre Perrot's corner

Introduction

Problématique

Nous avons choisi de travailler sur un jeu de données portant sur des parties d'échecs. Nous avons axé notre travail sur différentes problématiques autour de ce jeu de données. Nous avons tout d'abord voulu voir s'il existait des groupes de joueurs, c'est à dire des joueurs jouant exclusivement entre eux. Ensuite, nous nous sommes demandés comment repérer les meilleurs joueurs et s'il existait une relation entre eux. Nous avons aussi voulu repérer les joueurs s'améliorant au fur et à mesure des parties. Enfin, nous nous sommes demandé quelle pouvait être l'influence de la couleur choisie par les joueurs sur l'issue d'une partie et plus généralement le rapport entre le choix de couleur d'un joueur et son niveau.

Jeu de données

Notre jeu de données est fourni sous la forme d'un fichier .csv, dont chaque ligne correspond à une partie. On y trouve le mois durant lequel la partie a été jouée (de 1 à 100), le numéro du joueur blanc, celui du joueur noir et enfin le score de la partie. Pour cette dernière information, un 1 correspond à une victoire des blancs, un 0 à une victoire des noirs et un 0.5 à un match nul. Les données s'étalent sur 100 mois et regroupent 65053 parties jouées par 8631 joueurs.

Pour modéliser l'ensemble des données, nous avons choisi de représenter chaque joueur par un noeud et chaque partie par une arête. Les arêtes sont orientées du joueur noir vers le joueur blanc et étiquetées avec le mois et le score de la partie. Cette représentation s'est révélée très pratique pour manipuler les données, mais peu satisfaisante pour visualiser les résultats de l'étude.

Résultats

Nous avons été capables d'identifier visuellement des groupes de joueurs en se restreignant aux parties d'un même mois, mais sur l'ensemble des données, peu de joueurs ne font pas partie du groupe principal. Pour identifier les meilleurs joueurs, nous avons simplement calculé le score moyen de chaque joueur. En séparant le graphe par niveau de joueurs, nous avons pu dégager les propriétés suivantes à propos des niveaux des joueurs : les meilleurs joueurs tout comme les plus mauvais ne jouent pas entre eux et on constate que la plupart des parties (environ les deux tiers) sont jouées entre joueurs de niveaux différents. De plus, on constate un qu'il existe très peu de joueur au score moyen entre 0.8 et 1.0, à l'exception des joueurs ayant gagné toutes leurs parties (avec donc un score moyen d'exactement 1.0).

Concernant la dimension temporelle du jeu de données, bien que nous ayons réussi à déterminer les joueurs s'améliorant ou régressant, nous n'avons pu les caractériser en les croisant avec d'autres facteurs, faute de visualisation adaptée. En revanche, nous avons obtenu des résultats inattendus à propos de la couleur choisie. Nous avons tout d'abord calculé la préférence de chaque joueur pour une couleur ou pour une autre. Nous avons tout d'abord pu constater que seul les meilleurs joueurs avaient une préférence tranchée, mais qui ne semblait pas favoriser une couleur particulière dans l'ensemble. Nous avons alors comparé les histogrammes de choix de couleur de chaque niveau de joueur. Il en est ressorti que les meilleurs joueurs favorisaient en fait le noir.

Recherche

Groupes de joueurs

En cherchant à mettre en évidence des groupes de joueurs, nous avons utilisé un algorithme de layout force-directed. Ceci nous a permis d'isoler facilement les différentes composantes connexes des graphes. Nous l'avons appliqué aux graphes mensuels avec succès. Nous avons pu dégager pour chaque mois des groupes de joueurs. Voici quelques exemples :

groupe-mois2.png groupe-mois5.png groupe-mois82.png

En revanche, lorsqu'appliqué au graphe entier, on constate que la grande majorité des joueurs font en fait partie d'un énorme groupe central, entouré de tout petits groupes de quelques noeuds seulement :

graphe-total.png

Meilleurs joueurs

Pour connaitre le niveau d'un joueur, nous avons calculé son score moyen. Il peut être obtenu en de la façon suivante : somme des résultats des arêtes sortantes plus la somme de (1-resultat) des arêtes entrantes. En effet les arêtes sortantes correspondes aux parties où le joueur à joué blanc, le résultat de la partie peut donc être exploité directement. En revanche, les arêtes entrantes sont des parties où le joueur a joué noir, or le résultat sur l'arête et donné pour les blancs, il doit donc être inversé.

Une fois ce score moyen calculé, nous avons pu répartir les joueur en classes suivant leur score. Nous avons alors constaté ce à quoi nous nous attendions : les meilleurs joueurs comme les plus mauvais ne jouent pas entre eux. De plus seul un tiers des parties sont jouées au sein de la même classe de score. Le reste présente donc des affrontements déséquilibrés.

Voici un histogramme généré avec tulip pour montrer la répartition des joueurs en fonction du score :

On constate de suite qu'il y a une grande quantité de joueurs avec 0.5 pour score moyen. On constate aussi qu'il y a beaucoup plus de mauvais joueurs que de très bons et qu'il y a un creux entre 0.8 et 1.0, qui ne se retrouve pas en symétrique entre 0.0 et 0.2.

Amélioration des joueurs

La dimension temporelle a été la plus difficile à gérer dans ce jeu de données. En effet, tous les joueurs n'ont pas joué tous les mois. Nous pensions au départ calculer la différence entre les scores d'un mois sur l'autre, mais l'absence de données pour certains joueurs et certains mois rendait cette approche uniquement utilisable sur des mois consécutifs où le joueur a effectivement joué une partie. Nous avons alors décidé de changer de métrique pour mesurer l'amélioration des joueurs. Il était plus pertinent d'utiliser le score moyen du joueur jusqu'au mois en question comme score mensuel. Pour calculer l'amélioration, il suffit alors de faire la différence entre deux scores mensuels, même non consécutifs.

Graçe à ce nombre, nous avons ensuite colorié la bordure de chaque noeud dans chaque graphe mensuel pour refléter sa progression depuis le mois précédent (vert si positif, rouge si négatif, bleu si stagnation).

Influence de la couleur

Nous nous sommes enfin intéressés à l'influence que le choix d'une couleur a pu avoir sur l'issue de la partie.
Pour rappel: les blancs commencent ce qui influe sur le choix de la stratégie par conséquent l'issue de la partie.
Nous avons donc calculé le ratio de choix de la couleur noire pour chaque joueur. Il s'agit du ratio (degrés entrant / degrés) pour chaque noeud. On obtient une teinte de gris pour colorier le noeud en multipliant par 255. On peut donc maintenant visualiser sur le graphe la préférence des joueurs pour une couleur ou pour une autre. On constate comme attendu une répartition équitable entre les deux couleurs, puisqu'il n'y a pas de partie blanc/blanc ou noir/noir :

Nous avons ensuite réalisé un histogramme du score des parties :
On constate que les blancs (score 1) gagnent plus souvent que les noirs (score 0).

En croisant la préférence de couleur avec le score des joueurs, nous avons obtenu des résultats intéressants. Tout d'abord nous avons comparé les préférences des joueurs en fonction de leur niveau. On observe que seuls les meilleurs joueurs ont une préférence tranchée pour une couleur, sans en privilégier une en moyenne. Etrange si les blancs gagnent plus souvent.

Nous avons donc voulu confirmer ces résultats à l'aide d'histogrammes. Il en ressort que les joueurs moyens suivent le schéma général et n'ont pas de préférence :

En revanche, les meilleurs joueurs préfèrent massivement le noir :

Conclusion

Les traitements ayant pour but de mettre en valeur des résultats sur l'ensemble des données n'augmentaient guère la visibilité.
Grâce à la réalisation de sous-graphes a permis d'exploiter ces données sous un autre angle,
par exemple de remarquer que le nombres de parties par mois augmente énormément vers la fin (en moyenne 2000, contre 500 au début).
Le regroupement par score prouve que pour avoir une bonne moyenne les meilleurs joueurs ne s'affrontent pas entre eux.

On peut donc conclure que ce qui différencie les bons joueurs des mauvais est leur capacité à gagner avec la couleur qu'ils ont choisi.

Indication d'utilisation

Tout d'abord Tulip doit être ouvert dans le même répertoire que le fichier “training_data.csv”, ensuite il suffit de créer un graphe vide de charger le code puis de le lancer. Ainsi on obtient ce graphe:
\
Les histogrammes ont été généré via Tulip, grâce aux propriétés du graphe.
Le script génère les sous-graphes (1 par mois donc 100) et 10 de plus pour les scores (de 0 à 1 par pas de 10).
Chaque sous-graphe peut ainsi être exploité, notamment pour repérer les communautés sur chaque mois.

/net/html/perso/melancon/Visual_Analytics_Course/data/pages/aperrot_rlyazidi/lyazidi_reda_perrot_alexandre.txt · Last modified: 2013/01/10 15:43 by rlyazidi