00001 """Record of bike trips for data analysis.
00002
00003 Demonstration app for dataplot.
00004
00005 """
00006
00007 import datetime,pdb,os
00008
00009 from dataplot import plotmodels as models
00010 from django.db import connection
00011 from django.db.models import Q
00012 from dataplot.bike.R import ThereAndBackPlot
00013 from dataplot import R,GenericPlot
00014
00015 try:
00016 from dataplot import matplotlib
00017 except ImportError:
00018 matplotlib=None
00019
00020 def tup_to_minutes(h,m,s):
00021 return h*60 + m + float(s)/60
00022
00023 PAIRED_RIDES_SQL="""
00024 select x.date,x.hours,x.minutes,x.seconds,y.hours,y.minutes,y.seconds
00025 from bike_ride as x join bike_ride as y on x.date=y.date where
00026 x.origin_id=%s and x.destination_id=%s and
00027 y.origin_id=%s and y.destination_id=%s order by date;
00028 """
00029
00030 class Location(models.Model):
00031 """Origin or destination of a bike ride.
00032
00033 """
00034 short_name=models.CharField(
00035 max_length=100,
00036 blank=True,
00037 null=False,
00038 default='',
00039 )
00040 address=models.TextField(blank=True,null=False,default='')
00041
00042 class Admin:
00043 pass
00044
00045 def __str__(self):
00046 return self.short_name.capitalize()
00047
00048 DATAPLOTS=(
00049 R.CorrScatter,
00050 )
00051
00052 def CorrScatter_args(self):
00053 cursor = connection.cursor()
00054 cursor.execute(PAIRED_RIDES_SQL,[self.id,self.o.id,self.o.id,self.id])
00055 rows = cursor.fetchall()
00056 return {
00057 'x':[tup_to_minutes(*d[1:4]) for d in rows],
00058 'xlab':'%s->%s'%(self.short_name,self.o.short_name),
00059 'y':[tup_to_minutes(*d[4:7]) for d in rows],
00060 'ylab':'%s->%s'%(self.o.short_name,self.short_name),
00061 'main':"Correlation in ride times for rides on same day?",
00062 }
00063
00064 def related_locations(self):
00065 return Location.objects.filter(
00066 Q(ride_origins__destination__id__exact=self.id)|
00067 Q(ride_destinations__origin__id__exact=self.id)).exclude(
00068 id=self.id).distinct()
00069
00070 def related_locations_dict(self):
00071
00072 li=[]
00073 for loc in self.related_locations():
00074 loc.compare(self)
00075 li.append({
00076 'from_here_to_there':Ride.objects.filter(
00077 origin__id__exact=self.id,
00078 destination__id__exact=loc.id,
00079 ).count(),
00080 'from_there_to_here':Ride.objects.filter(
00081 origin__id__exact=loc.id,
00082 destination__id__exact=self.id,
00083 ).count(),
00084 'l':loc,
00085 })
00086 return li
00087
00088 h=4
00089 w=6
00090 def compare(self,o):
00091 """Initialize plots to summarize rides to another location.
00092
00093 o: other location
00094
00095 """
00096 self.o=o
00097 self.to_o=self.ride_origins.filter(destination__id__exact=o.id)
00098 self.from_o=self.ride_destinations.filter(origin__id__exact=o.id)
00099 pre='%s-to-%s-'%(self.short_name,o.short_name)
00100 self.hist=R.Histogram(
00101 pre+'hist',
00102 self.get_hist_args,
00103 h=self.h,
00104 w=self.w,
00105 )
00106 self.qqnorm=R.NormalQQPlot(
00107 pre+'qqnorm',
00108 self.get_qqnorm_args,
00109 h=self.h,
00110 w=self.w,
00111 )
00112 self.custom=ThereAndBackPlot(
00113 pre+'custom',
00114 self.get_there_and_back_args,
00115 )
00116
00117 def get_there_and_back_args(self):
00118 """kwargs to pass to the R function for plotting.
00119
00120 """
00121 rides=list(self.to_o)+list(self.from_o)
00122 values=[ride.there_and_back_dict() for ride in rides]
00123
00124 df=R.values_to_df(values)
00125 return {'d':df}
00126
00127 def get_hist_args(self):
00128 xlim=self.get_hist_xlim()
00129 breaks=range(xlim[0],xlim[1],3)
00130 minutes=[ride.get_minutes() for ride in self.to_o]
00131 return {
00132 'x':minutes,
00133 'main':'Rides from %s to %s'%(self,self.o),
00134 'xlab':'Ride length (minutes)',
00135 'xlim':xlim,
00136 'breaks':breaks,
00137 'freq':False,
00138 'ylim':[0,0.2],
00139 }
00140
00141 def get_qqnorm_args(self):
00142 return {
00143 'y':[ride.get_minutes() for ride in self.to_o],
00144 'ylim':self.get_hist_xlim(),
00145 }
00146
00147 def get_hist_xlim(self):
00148 minutes=[
00149 ride.get_minutes() for ride in self.to_o
00150 ]+[
00151 ride.get_minutes() for ride in self.from_o
00152 ]
00153 return [int(min(minutes)-1),int(max(minutes)+3)]
00154
00155 class Bike(models.Model):
00156 """Parameters of bike -- different bikes may be different speeds.
00157
00158 """
00159 short_name=models.CharField(
00160 max_length=100,
00161 blank=True,
00162 null=False,
00163 default='',
00164 )
00165 description=models.TextField(blank=True,null=False,default='')
00166
00167 class Admin:
00168 pass
00169
00170 def __str__(self):
00171 return self.short_name.capitalize()
00172
00173 class RideManager(models.Manager):
00174 """Used for making global ride plots and importing data.
00175
00176 """
00177
00178 def create_from_file_default(self):
00179 """Create new bike rides based on default dataset.
00180
00181 """
00182 self.create_from_file(os.path.join(
00183 os.path.dirname(__file__),'bikelog.txt'))
00184
00185 def create_from_file(self,filename):
00186 """Create new bike rides based on records in a text file.
00187
00188 """
00189 print "Loading data from %s"%filename
00190 lines=open(filename).readlines()
00191 for line in lines[1:]:
00192 print line
00193 ride=Ride()
00194 ride.get_data_from_line(line)
00195 ride.save()
00196
00197 def __init__(self):
00198 self.odoplot=R.TimeSeries(
00199 'allrides',
00200 self.get_odoplot_args,
00201 )
00202 if matplotlib:
00203 self.corr=matplotlib.Scatter(
00204 'samedaycorr-matplotlib',
00205 self.get_corr_args,
00206 )
00207 self.corrr=R.SquareScatter(
00208 'samedaycorr-R',
00209 self.get_corr_args,
00210 default_kwargs={'fit_lty':1,'lty.x.y':2,'one.to.one':True}
00211 )
00212
00213
00214 def get_odoplot_args(self):
00215 """Get arguments for the time series plot.
00216
00217 If no data present, create some from the default dataset.
00218
00219 """
00220 qs=self.all()
00221 if not qs:
00222 self.create_from_file_default()
00223 qs=self.all()
00224 return {
00225 'd':[ride.date.strftime("%s") for ride in qs],
00226 'y':[ride.distance for ride in qs],
00227 'transform':'cumulative',
00228 'label.interval':'day',
00229 'main':'Cumulative distance rode by Toby Dylan Hocking over time',
00230 'ylab':'Distance (miles)',
00231 'xlab':'Day of ride',
00232 }
00233
00234 def get_date_dict(self):
00235 """Sort rides based on date in a dictionary.
00236
00237 """
00238 qs=self.all()
00239 di={}
00240 for ride in qs:
00241 k=ride.date
00242 if k not in di:
00243 di[k]=[]
00244 di[k].append(ride.average_speed)
00245 return di
00246
00247 def get_corr_args(self):
00248 """Arguments for making the daily correlation scatterplot.
00249
00250 """
00251 tups=[li for li in self.get_date_dict().values() if len(li)==2]
00252 return {
00253 'x':[t[0] for t in tups],
00254 'y':[t[1] for t in tups],
00255 'main':"2 speeds (miles/hour) for rides on the same day",
00256 'xlab':'',
00257 'ylab':'',
00258 }
00259
00260 def from_to(self,org,dest):
00261 """Shortcut for searching from and to by short_name.
00262
00263 """
00264 return self.filter(
00265 origin__short_name__exact=org,
00266 destination__short_name__exact=dest,
00267 )
00268
00269 class Ride(models.Model):
00270 """Ride and associated statistics.
00271
00272 I can type these into Django's admin interface easily (data from
00273 my bike's navigational computer).
00274
00275 """
00276 date=models.DateField(blank=False,null=False)
00277 bike=models.ForeignKey(Bike,blank=False,null=False)
00278 origin=models.ForeignKey(
00279 Location,blank=False,null=False,related_name='ride_origins')
00280 destination=models.ForeignKey(
00281 Location,blank=False,null=False,related_name='ride_destinations')
00282 distance=models.FloatField(blank=False,null=False)
00283 fastest_speed=models.FloatField(blank=False,null=False)
00284 average_speed=models.FloatField(blank=False,null=False)
00285 hours=models.IntegerField(blank=False,null=False)
00286 minutes=models.IntegerField(blank=False,null=False)
00287 seconds=models.IntegerField(blank=False,null=False)
00288 back_flats=models.IntegerField(blank=False,null=False)
00289 front_flats=models.IntegerField(blank=False,null=False)
00290
00291 objects=RideManager()
00292 DO_NOT_CACHE_MANAGER_PLOTS=False
00293
00294 MANAGER_DATAPLOTS=(
00295 (R.CorrScatter,{'get_plot_args':{'x':'fastest_speed','y':'average_speed'}}),
00296 )
00297
00298 class Admin:
00299 list_display=[
00300 'date',
00301 'bike',
00302 'origin',
00303 'destination',
00304 'distance',
00305 'average_speed',
00306 'fastest_speed',
00307 ]
00308
00309 def __str__(self):
00310 return "%s(%s->%s)=%s"%(
00311 self.date,
00312 self.origin,
00313 self.destination,
00314 self.distance,
00315 )
00316
00317 def get_minutes(self):
00318 """Calculate ride time in minutes.
00319
00320 This considers hours and seconds columns too.
00321
00322 """
00323 return tup_to_minutes(self.hours,self.minutes,self.seconds)
00324
00325 def there_and_back_dict(self):
00326 """Return a dict for input to there and back Rplot.
00327
00328 """
00329 return {
00330 'date':self.date.strftime("%Y-%m-%d"),
00331 'from':self.origin.short_name,
00332 'to':self.destination.short_name,
00333 'm':self.get_minutes(),
00334 }
00335
00336 def get_data_from_line(self,line):
00337 """Import 1 row of data from a line in a text file.
00338
00339 The line looks something like:
00340 2007-06-06 road sangamo home 9.783 30 15.1 0 39 7
00341
00342 """
00343
00344 for f, i in zip(self._meta.get_data_holders()[1:],line.split()):
00345 if f.rel:
00346
00347 obj, created = f.rel.to.objects.get_or_create(short_name=i)
00348 val=obj.id
00349 else:
00350 val=i
00351 setattr(self,f.attname,val)
00352
00353 self.date=datetime.date(*[int(i) for i in self.date.split("-")])
00354