from django.template.loader import render_to_string
from dataplot import R,GenericPlot
from dataplot import plotmodels as models
from R import * # soccer-specific R plots
#from django.db import models
import pdb,os
GenericPlot.enable_caching=True
R.SquareScatter.default_kwargs={'marginal':False}
PLAYER_STATS=(
'games',
'wins',
'win_percent',
'goals_per_game',
'team_goals_per_game',
'goals_allowed_per_game',
'differential',
)
class PlayerManager(models.Manager):
"""Adds plotting methods.
"""
def get_ts_data(self):
"""Plot args for making multiple time series of win%
We should replace this data filtering method with some
database-backed method, so statistics can be looked up and
plotted faster/easier.
"""
##pdb.set_trace()
data=[
dict(P.win_percent_plot_args(),name=P.name)
for P in self.plotable()]
for D in data:
# check for case all 0 or all 100 so we dont crash
if D['y'].count(0)+D['y'].count(100) - len(D['y']):
v=D['y'][0]
while v==0 or v==100: # ignore starting values
D['y'].pop(0)
D['d'].pop(0)
v=D['y'][0]
return data
def plotable(self):
return self.frequent_players()
def frequent_players(self):
qs=self.filter(games__gte=5)
if qs.count()==0:
qs=self.filter(games__gte=1)
return qs
def get_goals_and_victories_args(self):
qs=self.plotable()
return {
'x':[x.goals_per_game for x in qs],
'xlab':"Goals per game",
'y':[x.win_percent for x in qs],
'ylab':"Win percent",
'main':"Goals = victories?",
'ann':['%s\n%s'%(x.name,x.games) for x in qs],
}
def wins_diff_args(self):
qs=self.plotable()
return {
'y':[x.win_percent for x in qs],
'ylab':"Win percent",
'x':[x.differential for x in qs],
'xlab':"Differential (average number of points your team wins/loses by)",
'main':"Win percent versus differential",
'ann':[x.name for x in qs],
}
def SquareScatter_args(self):
qs=self.plotable()
return {
'y':[x.team_goals_per_game for x in qs],
'ylab':"Team goals per game",
'x':[x.goals_per_game for x in qs],
'xlab':"Goals per game",
'main':"Your contribution to your team's goals",
'ann':[x.name for x in qs],
}
class Player(models.Model):
name=models.CharField(
max_length=100,null=False,blank=True,default='',unique=True)
goals_per_game=models.FloatField(blank=True,null=True)
team_goals_per_game=models.FloatField(blank=True,null=True)
goals_allowed_per_game=models.FloatField(blank=True,null=True)
differential=models.FloatField(blank=True,null=True)
games=models.IntegerField(blank=True,null=True)
wins=models.IntegerField(blank=True,null=True)
win_percent=models.FloatField(blank=True,null=True)
#linear_model_stat=models.FloatField(blank=True,null=True)
objects=PlayerManager()
URLID='name'
class Meta:
ordering=[('name')]
MANAGER_DATAPLOTS=[
# When you just list a dataplot class in DATAPLOTS or
# MANAGER_DATAPLOTS, the plot will assume a default filename
# (here, PlayerManager/SquareScatter.pdf) but you need to
# write a data-gathering method for Player.objects (here, it's
# called SquareScatter_args)
R.SquareScatter,
# When you use the same dataplot class several times in the
# same DATAPLOTS or MANAGER_DATAPLOTS, then you need to assign
# each usage a unique attribute. Above, we just used
# R.SquareScatter, so below we need to specify a unique
# attribute. Thus the plot filename becomes
# PlayerManager/SquareScatter_wins_diff.pdf, and the
# data-gathering method is Player.objects.wins_diff_args
(R.SquareScatter,{'attribute':'wins_diff'}),
# Below we specify a 3rd SquareScatter plot for this
# MANAGER_DATAPLOTS, so we have to give it name unique from
# the previous 2 (offense_defense). Instead of defining a
# data-gathering method by writing a method of Player.objects,
# here we define a get_plot_args dictionary. This dictionary
# will be processed by Django-dataplot and will generate a
# data-gathering method: Player.objects.offense_defense_args.
# This method will return to R a dictionary similar to this
# get_plot_args dictionary, replacing each value with the
# attribute value for each player, or the result of a call to
# that attribute. Here we will have something like {
# 'x':[p.team_goals_per_game for p in Player.objects.all()],
# 'y':[p.inverse_goals_allowed() for p in Player.objects.all()],
# 'ann':[p.name for p in Player.objects.all()],
# 'main':'Defense v. offense'}
# Preference is 1. method call 2. attribute lookup 3. string value
(R.SquareScatter,{'attribute':'offense_defense','get_plot_args':{
'x':'team_goals_per_game',
'y':'inverse_goals_allowed',
'ann':'name',
'main':"Defense v. offense"
}}),
# Below we use get_plot_args again to specify the
# data-gathering method, which will be created by
# Django-dataplot as
# Player.objects.multi_time_series_args. Since this is the
# first multi_time_series class here, we don't need to specify
# in attribute. We specified a nonstandard plot size by using
# init_args, which are additional arguments that will be used
# when instantiating of the R.multi_time_series class.
(R.multi_time_series,{'get_plot_args':{
'x':'get_ts_data',
'digits':0,
'main':"Win percent over time",
'ylab':"Win percent",
'xlab':"Game date",
},'init_args':{'h':9,'w':6.5}}),
]
DATAPLOTS=[
(R.TimeSeries,{'qs':'scores_ordered','get_plot_args':{
'd':'game_seconds',
'y':'goals',
}}),
(R.TimeSeries,{'qs':'scores_by_date','attribute':'win_percent_plot',
'get_plot_args':{
'd':'game_seconds',
'y':'win_percent_until_now',
'ylab':'Win percent',
'xlab':'Game date',
'main':'Win percent change over time'
}}),
]
def get_absolute_url(self):
return '/foot/player/%s/'%self.id
def inverse_goals_allowed(self):
return 1/(self.goals_allowed_per_game+1)
def get_stats(self):
return [self.get_stat(at) for at in PLAYER_STATS]
def get_stat(self,at):
v=getattr(self,at)
if v==None:
return
stat={
'name':at.replace("_"," ").capitalize(),
'value':v,
}
for side in 'gt','lt','exact':
di={'%s__%s'%(at,side):v}
stat[side]=qs=Player.objects.filter(**di)
return stat
def scores_ordered(self):
return order_scores(self.score_set.all())
def scores_by_date(self):
return self.score_set.select_related().order_by('soccer_game.date')
def __unicode__(self):
return self.name
def save(self,*args,**kwargs):
self.cache()
super(Player,self).save(*args,**kwargs)
def save_plots(self): # forces remake of this players plots every save
try:
self.make_model_plots()
except:
pass
def cache(self):
scores=self.score_set.all()
N=self.games=scores.count()
if not N:
return
self.goals_per_game=float(sum([
sc.goals for sc in scores]))/N
# need to make sure cached values are updated first!
self.goals_allowed_per_game=float(sum([
sc.points_allowed() for sc in scores]))/N
self.team_goals_per_game=float(sum([
sc.team.points for sc in scores]))/N
diffs=[sc.differential() for sc in scores]
self.differential=float(sum(diffs))/N
# maybe use team.won to calc instead?
self.wins=sum([1 for d in diffs if d > 0])
self.win_percent=float(
self.get_win_percent_from_most_recent_score())
def get_win_percent_from_most_recent_score(self):
return self.score_set.order_by(
'-team__game__date')[0].win_percent_until_now()
def get_absolute_url(self):
return '/foot/player/%s/'%self.name
GAME_STATS=(
'hi_score',
'lo_score',
'differential',
)
class GameManager(models.Manager):
DEFAULT_DATAFILE=os.path.join(os.path.dirname(__file__),'games.txt')
TUPKEYS=('name','goals','own_goals')
def get_latest(self):
try:
return self.order_by('-date')[0]
except IndexError: # no data yet
self.create_from_file()
return self.order_by('-date')[0]
def create_from_file(self,filename=None):
"""Create new bike rides based on records in a text file.
"""
filename=filename or self.DEFAULT_DATAFILE
print "Loading data from %s"%filename
lines=open(filename).readlines()
for line in lines: # assume no header line
print line
self.create_from_line(line)
def create_from_line(self,line):
"""Create one game based on line of text datafile.
"""
from dataplot import GenericPlot
oldcache=GenericPlot.enable_caching
GenericPlot.enable_caching=False #
teamlists=[i.split() for i in line.split(',')]
G=Game.objects.create(date=teamlists[0].pop(0))
for L in teamlists:
T=G.team_set.create(name=L.pop(0))
like=[L[i::len(self.TUPKEYS)] for i in range(len(self.TUPKEYS))]
dictlist=[dict(zip(self.TUPKEYS,tup)) for tup in zip(*like)]
for D in dictlist:
D['player'],cr=Player.objects.get_or_create(name=D.pop('name'))
sc=T.score_set.create(**D)
T.save()
GenericPlot.enable_caching=oldcache
return G
def get_export_text(self):
"""Text for exported data file.
"""
return '\n'.join([g.export() for g in self.all()])
def export_games_to_file(self,filename=None):
"""Export data to a text file.
"""
filename=filename or self.DEFAULT_DATAFILE
text=self.get_export_text()
f=open(filename,'w')
f.write(text)
f.close()
class Game(models.Model):
date=models.DateField(blank=False,null=False)
hi_score=models.IntegerField(blank=True,null=True)
lo_score=models.IntegerField(blank=True,null=True)
differential=models.IntegerField(blank=True,null=True)
objects=GameManager()
URLID='date'
class Meta:
ordering=[('date')]
def export(self):
teams=self.teams_ordered()
teamtext=','.join([T.export() for T in teams])
return '%s %s'%(self.date,teamtext)
def teams_ordered(self):
return self.team_set.order_by('-points')
def cache(self):
try:
teams=self.teams_ordered()
self.hi_score,self.lo_score=[getattr(t,'points') for t in teams]
except:
pass
try:
self.differential=self.hi_score-self.lo_score
except:
pass
def __unicode__(self):
return '%s'%self.date
def get_absolute_url(self):
return '/foot/game/%s/'%self.date
def save(self,*args,**kwargs):
self.cache()
super(Game,self).save(*args,**kwargs)
def order_scores(qs):
return qs.select_related().order_by(
'-goals','own_goals','soccer_player.name')
class TeamManager(models.Manager):
def experienced_teams(self):
qs=self.exclude(game__team__score__player__games__lt=5).distinct()
return qs
class Team(models.Model):
name=models.CharField(max_length=100)
game=models.ForeignKey(Game)
goals=models.IntegerField(blank=True,null=True)
own_goals=models.IntegerField(blank=True,null=True)
points=models.IntegerField(blank=True,null=True)
won=models.BooleanField(blank=True,null=False,default=False)
tied=models.BooleanField(blank=True,null=False,default=False)
players=models.IntegerField(blank=True,null=True)
objects=TeamManager()
MANAGER_DATAPLOTS=(
#(team_linear_model,{'get_plot_args':{
#'y':'goals',
#'players':'get_player_names',
#}}),
)
def get_player_names(self):
return [sc.player.name for sc in self.score_set.all()]
def export(self):
scores=self.scores_ordered()
sctext=' '.join([
'%s %s %s'%(sc.player.name,sc.goals,sc.own_goals)
for sc in scores])
return '%s %s'%(self.name,sctext)
def summary(self):
return render_to_string(
'soccer/team_summary.html',{'t':self})
def scores_ordered(self):
p=getattr(self,'HILITE',None)
qs=order_scores(self.score_set.all())
if p:
for sc in qs:
sc.is_player=sc.player==self.HILITE
return qs
def cache(self):
"""Save both teams relevant to this game.
"""
self.other=self.other_team()
self.other.other=self
teams=(self,self.other)
for t in teams: # totals for each team
qs=t.score_set.all()
t.goals=sum([sc.goals for sc in qs])
t.own_goals=sum([sc.own_goals for sc in qs])
t.players=qs.count()
for t in teams: # points based on other teams totals
t.points=t.goals+t.other.own_goals
for t in teams:
t.won=t.points>t.other.points
t.tied=t.points==t.other.points
super(Team,t).save()
self.game.save()
for P in Player.objects.filter(
score__team__game__id__exact=self.game.id):
P.save()
Player.make_manager_plots()
return # team_linear_model buggy
Team.make_manager_plots()
D=Team.objects.team_linear_model.plot_fun_return_val
# Remake plots and save estimated LM parameter
for P in Player.objects.filter(name__in=D):
P.linear_model_stat=D[P.name]
P.save()
for P in Player.objects.exclude(name__in=D):
P.linear_model_stat=None
P.save()
self.game.save()
def other_team(self):
try:
return self.game.team_set.exclude(id=self.id)[0]
except:
pass
def save(self,*args,**kwargs):
if self.game.team_set.count()==2:
self.cache()
else:
super(Team,self).save(*args,**kwargs)
def __unicode__(self):
return '%s'%self.name
class Score(models.Model):
player=models.ForeignKey(Player,blank=True,null=False)
team=models.ForeignKey(Team)
goals=models.IntegerField(null=False,blank=False,default=0)
own_goals=models.IntegerField(null=False,blank=False,default=0)
def __unicode__(self):
return u'%s: %s'%(self.player,self.goals)
def game_seconds(self):
return self.team.game.date.strftime("%s")
def win_percent_until_now(self):
qs=self.player.score_set.filter(
team__game__date__lte=self.team.game.date)
wins=qs.filter(team__won__exact=True).count()
ties=qs.filter(team__tied__exact=True).count()
games=qs.count()
pct=(wins*1.0+ties*0.5)/games
return pct*100
def points_allowed(self):
return self.team.other_team().points
def differential(self):
return self.team.points-self.team.other_team().points
def player_summary(self):
self.team.HILITE=self.player
return self.team.summary()