func mapToPerformanceTestSeries2(name string, predictedJourneyTimes, actualJourneyTimes map[time.Time]time.Duration) PerformanceTestSeries2 { departureTimes := make([]time.Time, len(predictedJourneyTimes)) durations := make([]time.Duration, len(predictedJourneyTimes)) i := 0 /* For calculating the root mean squared error, and mean absolute error */ diffInSeconds := make([]float64, 0) absDiffInSeconds := make([]float64, 0) for departureTime, duration := range predictedJourneyTimes { departureTimes[i] = departureTime durations[i] = duration actualJourneyTime := actualJourneyTimes[departureTime] diffInSeconds = append(diffInSeconds, (duration - actualJourneyTime).Seconds()) absDiffInSeconds = append(absDiffInSeconds, math.Abs((duration - actualJourneyTime).Seconds())) i++ } rmse, err := statistics.RootMeanSquare(absDiffInSeconds) if err != nil { /* If lack of data points simply assume 0 */ logger.GetLogger().Error(err.Error()) rmse = 0 } mae, err := statistics.Mean(absDiffInSeconds) if err != nil { /* If lack of data points simply assume 0 */ logger.GetLogger().Error(err.Error()) mae = 0 } mad, err := statistics.Median(absDiffInSeconds) if err != nil { /* If lack of data points simply assume 0 */ logger.GetLogger().Error(err.Error()) mad = 0 } variance, err := statistics.Variance(diffInSeconds) if err != nil { /* If lack of data points simply assume 0 */ logger.GetLogger().Error(err.Error()) variance = 0 } return PerformanceTestSeries2{ SeriesName: name, X: departureTimes, Y: durations, Count: len(durations), RMSE: time.Duration(rmse) * time.Second, MAE: time.Duration(mae) * time.Second, MAD: time.Duration(mad) * time.Second, Variance: time.Duration(variance) * time.Second, } }
func computeDelayCharacteristicsForHour(hour int, times []time.Time, actualDurations []time.Duration, historicDurations []time.Duration) map[time.Duration]map[int]time.Duration { // Extract times and durations relavant to the hour (and < hour + 2 hours) truncatedTimes := make([]time.Time, 0) truncatedActualDurations := make([]time.Duration, 0) truncatedHistoricDurations := make([]time.Duration, 0) for i, t := range times { if hour <= t.Hour() && t.Hour() < hour+int(continuousObservationPeriod.Hours()) { truncatedTimes = append(truncatedTimes, t) truncatedActualDurations = append(truncatedActualDurations, actualDurations[i]) truncatedHistoricDurations = append(truncatedHistoricDurations, historicDurations[i]) } } // Build delay characteristics table delayCharacteristics := make(map[time.Duration]map[int]time.Duration) for _, delayThreshold := range DelayThresholds { delayCharacteristicsPerThreshold := make(map[int]time.Duration) earlyCharacteristicsPerThreshold := make(map[int]time.Duration) for _, busCountThreshold := range DelayBusCountThresholds { delaysLasting := make([]float64, 0) delayFlag := false currentDelayCount := 0 var startOfDelay, endOfDelay time.Time for i, currentTime := range truncatedTimes { actualDuration := truncatedActualDurations[i] historicDuration := truncatedHistoricDurations[i] // Skip if comparison can't be made if historicDuration == 0 { continue } if delayFlag { // Handle during delay here (special case if delay still occuring at last point in time) if actualDuration < historicDuration+delayThreshold || i == len(truncatedTimes)-1 { // End of delay endOfDelay = currentTime // Safety check no bus overtakes, and not artifically large if endOfDelay.After(startOfDelay) && endOfDelay.Sub(startOfDelay) < 24*time.Hour { delayDuration := endOfDelay.Sub(startOfDelay) delaysLasting = append(delaysLasting, delayDuration.Seconds()) } // Reset flags delayFlag = false currentDelayCount = 0 } } else { // Handle no delay / delay not yet found here if actualDuration >= historicDuration+delayThreshold { // Accumulate until delay found if currentDelayCount < busCountThreshold { currentDelayCount++ continue } startOfDelay = currentTime delayFlag = true } currentDelayCount = 0 } } averageLastingTime, _ := statistics.Mean(delaysLasting) delayCharacteristicsPerThreshold[busCountThreshold] = time.Duration(averageLastingTime) * time.Second // Do the same for early earlyLasting := make([]float64, 0) earlyFlag := false currentEarlyCount := 0 var startOfEarly, endOfEarly time.Time for i, currentTime := range truncatedTimes { actualDuration := truncatedActualDurations[i] historicDuration := truncatedHistoricDurations[i] // Skip if comparison can't be made if historicDuration == 0 { continue } if earlyFlag { // Handle during delay here (special case if early still occuring at last point in time) if actualDuration > historicDuration-delayThreshold || i == len(truncatedTimes)-1 { // End of early endOfEarly = currentTime // Safety check no bus overtakes, and not artifically large if endOfEarly.After(startOfEarly) && endOfEarly.Sub(startOfEarly) < 24*time.Hour { earlyDuration := endOfEarly.Sub(startOfEarly) earlyLasting = append(earlyLasting, earlyDuration.Seconds()) } // Reset flags earlyFlag = false currentEarlyCount = 0 } } else { // Handle no early / early not yet found here if actualDuration <= historicDuration-delayThreshold { // Accumulate until early found if currentEarlyCount < busCountThreshold { currentEarlyCount++ continue } startOfEarly = currentTime earlyFlag = true } currentEarlyCount = 0 } } averageEarly, _ := statistics.Mean(earlyLasting) earlyCharacteristicsPerThreshold[busCountThreshold] = time.Duration(averageEarly) * time.Second } delayCharacteristics[delayThreshold] = delayCharacteristicsPerThreshold delayCharacteristics[-delayThreshold] = earlyCharacteristicsPerThreshold } return delayCharacteristics }
/* Writes to .html file for resulting performance graph */ func writePerformanceChart2(wr io.Writer, summary PerformancePlotSummary2, templatePath string) { funcMap := template.FuncMap{ // Prettifies x and y axes into js format "formatXAxis": func(i int, slice []time.Time) string { str := fmt.Sprintf("['x%v'", i) for i := 0; i < len(slice); i++ { str += ", " + fmt.Sprintf("new Date(Date.UTC(%v, %v, %v, %v, %v, %v))", slice[i].Year(), int(slice[i].Month())-1, slice[i].Day(), slice[i].Hour(), slice[i].Minute(), slice[i].Second()) } str += "]" return str }, "formatYAxis": func(seriesName string, slice []time.Duration) string { str := fmt.Sprintf("['%v'", seriesName) for i := 0; i < len(slice); i++ { minutes := int(slice[i].Minutes()) if minutes == 0 { str += ", null" } else { str += ", " + strconv.Itoa(minutes) } } str += "]" return str }, "formatRMSE": func(rmse time.Duration) string { if rmse > 0 { return rmse.String() } return "NA" }, "getOverallRMSE": func(heuristic string, method performanceeval.Method, charts []PerformanceChart2) string { percentageRMSEs := make([]float64, 0) for _, chart := range charts { /* First find out error between tfl and actual */ var tflHeuristicError time.Duration for _, series := range chart.MultipleSeries { if series.SeriesName == "Tfl Expected Time" { switch heuristic { case "RMSE": tflHeuristicError = series.RMSE case "MAE": tflHeuristicError = series.MAE case "MAD": tflHeuristicError = series.MAD case "Variance": tflHeuristicError = series.Variance default: logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic) } } } /* Find out error between method and actual */ var heuristicError time.Duration for _, series := range chart.MultipleSeries { if series.SeriesName == method.String() { switch heuristic { case "RMSE": heuristicError = series.RMSE case "MAE": heuristicError = series.MAE case "MAD": heuristicError = series.MAD case "Variance": heuristicError = series.Variance default: logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic) } } } /* Skip if either is invalid */ if heuristicError == 0 || tflHeuristicError == 0 { continue } /* Calculate percentage error as that of tfl */ percentError := (tflHeuristicError.Seconds() - heuristicError.Seconds()) / tflHeuristicError.Seconds() percentageRMSEs = append(percentageRMSEs, percentError) } overallPercentRMSE, err := statistics.Mean(percentageRMSEs) if err != nil { return "NA" } return fmt.Sprintf("%0.2f%%", overallPercentRMSE*100) }, /* Counts the number of journeys which are superior than the tfl expected time for a particular prediction method */ "getSuperiorCount": func(heuristic string, method performanceeval.Method, charts []PerformanceChart2) int { count := 0 for _, chart := range charts { /* First find out error between tfl and actual */ var tflHeuristicError time.Duration for _, series := range chart.MultipleSeries { if series.SeriesName == "Tfl Expected Time" { switch heuristic { case "RMSE": tflHeuristicError = series.RMSE case "MAE": tflHeuristicError = series.MAE case "MAD": tflHeuristicError = series.MAD case "Variance": tflHeuristicError = series.Variance default: logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic) } } } /* Find out error between method and actual */ var heuristicError time.Duration for _, series := range chart.MultipleSeries { if series.SeriesName == method.String() { switch heuristic { case "RMSE": heuristicError = series.RMSE case "MAE": heuristicError = series.MAE case "MAD": heuristicError = series.MAD case "Variance": heuristicError = series.Variance default: logger.GetLogger().Panicf("Unknown heuristic: %v", heuristic) } } } /* Skip if either is invalid */ if heuristicError == 0 || tflHeuristicError == 0 { continue } if heuristicError < tflHeuristicError { count++ } else if heuristicError > tflHeuristicError { count-- } } return count }, /* Returns the average % count of data points available of a particular prediction method with respect to the actual times*/ "getAverageAvailableDataPoints": func(method performanceeval.Method, charts []PerformanceChart2) string { percentages := make([]float64, 0) for _, chart := range charts { /* First find out count for actual series */ var actualCount int for _, series := range chart.MultipleSeries { if series.SeriesName == "Actual Time" { actualCount = series.Count } } /* Find out count for method*/ var methodCount int for _, series := range chart.MultipleSeries { if series.SeriesName == method.String() { methodCount = series.Count } } /* Prevent zero numerator or denominator */ if methodCount != 0 && actualCount != 0 { percentages = append(percentages, float64(methodCount)/float64(actualCount)) } } averagePercentage, err := statistics.Mean(percentages) if err != nil { return "NA" } return fmt.Sprintf("%.2f%%", averagePercentage*100) }, } t, err := template.New(path.Base(templatePath)).Funcs(funcMap).ParseFiles(templatePath) if err != nil { logger.GetLogger().Panic(err) } err = t.Execute(wr, summary) if err != nil { logger.GetLogger().Panic(err) } }
/* Returns map[string]map[string]map[string]int ( map : fromStop => (map : toStop => (map : "estimate" => estimate in seconds "stddev" => standard deviation in seconds "delayBusCount" => bus count as evidence of delay "delayExpectedToLast" => how long delay is expected to last in seconds ) ) ) the estimated journey times, standard deviation, between map of neighbouring stops for mixed, historic and real time. */ func getEstimates(adjacentStops map[string]map[string]bool) (map[string]map[string]map[string]int, map[string]map[string]map[string]int, map[string]map[string]map[string]int) { departureTime := time.Now().In(timezone.Get()) timeRanges := map[time.Time]time.Time{ departureTime.AddDate(0, 0, -7).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -7).Add(30 * time.Minute), departureTime.AddDate(0, 0, -14).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -14).Add(30 * time.Minute), departureTime.AddDate(0, 0, -21).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -21).Add(30 * time.Minute), } realTimeEstimates := make(map[string]map[string]map[string]int) mixedEstimates := make(map[string]map[string]map[string]int) /* Select historic and real time estimates from database */ historicEstimates := selectdb.SelectHistoricEstimates(timeRanges) realTimeData := selectdb.SelectRealTimeEstimates(realTimeWindow) /* Combine historic and real time data */ for fromStop, fromStopDetails := range historicEstimates { for toStop, historicDetails := range fromStopDetails { historicEstimate := time.Duration(historicDetails["estimate"]) * time.Second /* Extract real time data and lookup delay characteristics, skip for cases lacking real time estimate */ delayBusCount := 0 realTimeFromStopDetails, exists := realTimeData[fromStop] if !exists { continue } realTimeDetails, exists := realTimeFromStopDetails[toStop] if !exists { continue } realTimeCount, _ := realTimeDetails["count"] if realTimeCount < 3 { continue } realTimeRecords := make([]time.Duration, realTimeCount) for i := 1; i <= realTimeCount; i++ { // Since real time records are already reversed, reverse it again for ascending order in time, // so that handleDelayReverseLookup will work fine realTimeRecord := time.Duration(realTimeDetails[strconv.Itoa(realTimeCount+1-i)]) * time.Second realTimeRecords[i-1] = realTimeRecord } /* Calculate real time estimate using last three buses averaged */ realTimeLastThreeInSeconds := make([]float64, 3) for j := 0; j < 3; j++ { realTimeLastThreeInSeconds[j] = realTimeRecords[len(realTimeRecords)-1-j].Seconds() } realTimeEstimateInSeconds, _ := statistics.Mean(realTimeLastThreeInSeconds) _, exists = realTimeEstimates[fromStop] if !exists { realTimeEstimates[fromStop] = make(map[string]map[string]int) mixedEstimates[fromStop] = make(map[string]map[string]int) } realTimeEstimates[fromStop][toStop] = map[string]int{ "estimate": int(realTimeEstimateInSeconds), } /* Get mixed estimates below */ shouldUseHistoric, delayBusCount, delayExpectedToLast := performanceeval.HandleDelayReverseLookup( fromStop, toStop, departureTime, delayLastingThreshold, historicEstimate, realTimeRecords) if shouldUseHistoric { mixedEstimates[fromStop][toStop] = historicEstimates[fromStop][toStop] mixedEstimates[fromStop][toStop]["delayBusCount"] = delayBusCount } else { mixedEstimates[fromStop][toStop] = map[string]int{ "estimate": int(realTimeEstimateInSeconds), "delayBusCount": delayBusCount, "delayExpectedToLast": int(delayExpectedToLast.Seconds()), "delay": 1, } } } } return mixedEstimates, historicEstimates, realTimeEstimates }