It currently takes about 10 minutes to process ~16k teams and ~81k games. I could soon have ~17k teams with ~160k, and multiple sports. I run this as a cron job overnight and store the results in a serialized gzipped array which is processed upon each pageload to sort teams by states for displaying and actually calculate the RPI (add WP, OWP and OOWP together) which works well for speed for displaying purposes. I'm only concerned with trying to speed up this calculation:
If you need more information about what the RPI (Ratings Percentage Index) is, this is a good primer. Please note, I have modified from this formula to include a weighting factor (function reduction_factor() ) so it is slightly different that that page states.
Here is my base function:
// *********************************************************************************************************************************
// function to calculate RPI given a sportid and year, optional cutoff date
// *********************************************************************************************************************************
function rpi($sportid, $year, $date = FALSE, $my_db, $sport_array){
//figure out if this is a year-spanning sport
$year_display = $year;
$date_start = $year.$sport_array[$sportid]['start_date'];
$date_end = $year.$sport_array[$sportid]['end_date'];
if ($sport_array[$sportid]['end_year_plus']){
$year_display = $year.'-'.($year+1);
$date_end = ($year+1).$sport_array[$sportid]['end_date'];
}
if ($date) $date_end = $date;
elseif ($date_end > date("Y-m-d")) $date_end = date("Y-m-d");
$rpiteamarray = array();
$results = $my_db->query("SELECT `teamid` FROM `team` WHERE `jv` = '0' AND `teamid` > 0;");
while ($row = $results->fetchRow(MDB2_FETCHMODE_ASSOC)){
$teamid = $row['teamid'];
if (!array_key_exists($teamid, $rpiteamarray)) $rpiteamarray[$teamid] = new rpiteam($teamid, $sportid, $year, $my_db, $date_start, $date_end);
if ($rpiteamarray[$teamid]->total_games() == 0) continue;
$rpi[$teamid]['numgames'] = $rpiteamarray[$teamid]->total_games();
}
unset($results, $row);
foreach ($rpi as $teamid => $value){
$rpi[$teamid]['wp'] = $rpiteamarray[$teamid]->wp();
$rpi[$teamid]['owp'] = $rpiteamarray[$teamid]->owp($rpiteamarray);
$rpi[$teamid]['oowp'] = $rpiteamarray[$teamid]->oowp($rpiteamarray);
}
return $rpi;
}
Here is my rpiteam class:
<?php
class rpiteam {
protected $sportid, $levelid, $levelid_state, $year, $wins, $loss, $tie, $total, $wp, $db, $opponentsarray, $date_start, $date_end;
function __construct($teamid, $sportid, $year, $db, $date_start, $date_end) {
if ($teamid == '') return null;
if ($year == '') return null;
if ($sportid == '') return null;
$this->db = $db;
$this->sportid = $sportid;
$this->teamid = $teamid;
$this->year = $year;
$this->date_start = $date_start;
$this->date_end = $date_end;
$row = $this->db->query("SELECT `levelid`,`levelid_state` FROM `team_class` WHERE `teamid` = $teamid AND `sportid` = $sportid AND `year` = $year;")->fetchRow(MDB2_FETCHMODE_ASSOC);
$this->levelid = $row['levelid'];
$this->levelid_state = $row['levelid_state'];
$this->opponentsarray = array();
}
public function wins(){
if ($this->wins) return $this->wins;
$this->wins = $this->db->query("SELECT COUNT(`winner`) FROM `game` WHERE `sportid` = $this->sportid AND `winner` = $this->teamid AND `date` BETWEEN '$this->date_start' AND '$this->date_end';")->fetchOne();
return $this->wins;
}
public function loss(){
if ($this->loss) return $this->loss;
$this->loss = ($this->total_games())-($this->wins());
return $this->loss;
}
public function total_games(){
if ($this->total) return $this->total;
//echo "SELECT COUNT(`winner`) FROM `game` WHERE `sportid` = $this->sportid AND `winner` IS NOT NULL AND (`hometeam` = $this->teamid OR `awayteam` = $this->teamid) AND `date` BETWEEN '$this->date_start' AND '$this->date_end';";
$this->total = $this->db->query("SELECT COUNT(`winner`) FROM `game` WHERE `sportid` = $this->sportid AND `winner` IS NOT NULL AND (`hometeam` = $this->teamid OR `awayteam` = $this->teamid) AND `date` BETWEEN '$this->date_start' AND '$this->date_end';")->fetchOne();
return $this->total;
}
public function get_opponentsarray(){
if ($this->opponentsarray) return $this->opponentsarray;
$opponentsarray = array();
$result = $this->db->query("SELECT `hometeam`,`awayteam`,`winner` FROM `game` WHERE `sportid` = $this->sportid AND `winner` IS NOT NULL AND (`hometeam` = $this->teamid OR `awayteam` = $this->teamid) AND `date` BETWEEN '$this->date_start' AND '$this->date_end' ORDER BY `date` ASC;");
while ($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC)){
if ($row['hometeam'] == $this->teamid){
if ($row['winner'] == $this->teamid) $opponentsarray[$row['awayteam']] = 'W';
elseif ($row['winner'] == 0) $opponentsarray[$row['hometeam']] = 'T';
else $opponentsarray[$row['awayteam']] = 'L';
}
elseif ($row['awayteam'] == $this->teamid){
if ($row['winner'] == $this->teamid) $opponentsarray[$row['hometeam']] = 'W';
elseif ($row['winner'] == 0) $opponentsarray[$row['hometeam']] = 'T';
else $opponentsarray[$row['hometeam']] = 'L';
}
}
$this->opponentsarray = $opponentsarray;
return $this->opponentsarray;
}
public static function reduction_factor($levelid){
switch ($levelid){
case 66: return 1.00;
case 55: return 1.00;
case 44: return 0.90;
case 33: return 0.75;
case 22: return 0.60;
case 11: return 0.45;
case 9: return 0.40;
case 8: return 0.35;
case 7: return 0.30;
case 6: return 0.15;
default: return 1.00;
}
}
// *********************************************************************************************************************************
// Calculate WP (winning percentage)
// *********************************************************************************************************************************
public function wp(){
if ($this->wp) return $this->wp;
if ($this->total_games() == 0) {
$this->wp = 0;
return $this->wp;
}
$reduce = $this->reduction_factor($this->levelid_state);
$this->wp = round($this->wins()/$this->total_games() * $reduce, 4);
return $this->wp;
}
// *********************************************************************************************************************************
// Calculate WP (winning percentage) with exclusion
// *********************************************************************************************************************************
public function wp_exclude($team_excluding, $rpiteamarray){
$opponentsarray = $this->get_opponentsarray();
$wins = 0;
$total = 0;
foreach ($opponentsarray as $key => $value){
if ($key == $team_excluding) continue;
++$total;
if ($value == 'W') ++$wins;
elseif ($value == 'T') $wins = $wins + 0.5;
}
if ($total == 0) return 0;
return round($wins/$total * $this->reduction_factor($this->levelid_state), 4);
}
// *********************************************************************************************************************************
// Calculate OWP (opponents winning percentage)
// *********************************************************************************************************************************
public function owp($rpiteamarray){
$opponentsarray = $this->get_opponentsarray();
$owp = 0;
$total = 0;
foreach ($opponentsarray as $key => $value){
++$total;
$owp += $rpiteamarray[$key]->wp_exclude($this->teamid, $rpiteamarray);
}
if ($total == 0) return 0;
return round($owp/$total, 4);
}
// *********************************************************************************************************************************
// Calculate OWP (opponents winning percentage)
// *********************************************************************************************************************************
public function oowp($rpiteamarray){
$opponentsarray = $this->get_opponentsarray();
$oowp = 0;
$total = 0;
foreach ($opponentsarray as $key => $value){
$owp_opponentsarray = $rpiteamarray[$key]->get_opponentsarray();
foreach ($owp_opponentsarray as $owp_key => $owp_value){
++$total;
$oowp += $rpiteamarray[$owp_key]->wp();
}
}
if ($total == 0) return 0;
return round($oowp/$total, 4);
}
}
I use PHP with PEAR MDB2.
Is there a good way to prepare the queries used in the class? That is, can I prepare the queries once somehow, so when they are called ~16k times each it helps?
I haven't run the numbers, but I think it's querying the database 4x(num teams) or ~64k times.
Any other improvements possible?
(
hometeam` = $this->teamid ORawayteam
= $this->teamid)` were the biggest hit to performance. Each of these queries took 0.5 to 1.1 seconds. Changing this query dba.stackexchange.com/questions/45219/… makes it now take <0.002 seconds, so total execution went from 900 minutes to 2 minutes. \$\endgroup\$