Sunday, September 25, 2011

Saving MySQL Queries to CSV

Alright, didn't get any posts in last week and need to work on that.  Some of it has to do with a new computer and realizing this fantasy football stat endeavor does take more time than I thought.  Some of it is my lack of expertise in Python, and little time to do that analysis.  One little note for knowledge sake is the command to save a MySQL query to a CSV file, this way I can play with the stats in a spreadsheet. Here is the code:

mysql> SELECT * INTO OUTFILE '/tmp/pbp2010.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\' LINES TERMINATED BY '\n' FROM pbp2010 WHERE 1;

Be sure you have permission to write into "tmp".  The table in this is example is "pbp2010".

Sunday, September 11, 2011

Game Day Score Distribution

In my post below I provided a distribution over season total stats.  Here is the distribution when you histogram the scores for each individual game.
The mean and standard deviation is (7.2, 6.9).   I also have the histogram for kickers:
with mean and standard deviation of (7.7,4.1).  This distribution is approaching normal without the negative values, it's also interesting that the standard deviation is smaller than offensive players, an indication that kickers provide more consistent points per game. 

Lastly, here is the Defense scoring per game:
with mean and standard deviation of (6.7,5.4).


Python OrderedDict

Just a Python programming tip for those of us who are new to the language.  When I started writing the code to extract the stats and insert into sql I discovered that the dict class in Python does not maintain the order of the keys.  This means I cannot loop over a dictionary of stats to insert into different player classes (offense, kickers, and defense).  I discovered OrderedDict, which does keep the order.  Very useful for making your code cleaner.  Here is an example of the differences running in the Python shell:

>>> from collections import *
>>> stats = {'Game':0,'Player':'foo','Tds':1}
>>> for key in stats: print key
... 
Tds
Player
Game
>>> ostats = OrderedDict([('Game',0),('Player','foo'),('Tds',1)])
>>> for okey in ostats: print okey
... 
Game
Player
Tds

Saturday, September 10, 2011

Getting some results

My previous post was all about setting up MySQL and importing the stats through Python into the SQL databases.  The next steps took me a bit more time.  In fact, I'm still having a bit of trouble getting the stats to add up to those shown on NFL websites.  I don't think it's a problem with the data, just my abilities to code and understand the data.  There are subtleties in extracting stats, a few examples:
  • When a QB kneels it counts as a rush, and the yardage goes against the QB's rushing yards
  • For punts and kickoffs the receiving team is on Offense, yet for an onside kick the kicking team is on Offense
  • And few others I can't remember off the top of my head
But extracting stats for most players like Tom Brady provides correct results.  Sometimes the stats differ, and I think it has to do with some additional rules like those above.  But these are things I can work out as I go, and with all of the stats (mostly correct) I can start looking at stats.  I will forgo putting up the Python code that extracts the stats as it's not very interesting to really look at unless you want to see how I applied the logic to extract a stat.  

Alright, here's to my first plot.  First, here's a list of the tables in my mysql:

mysql> show tables;
+---------------------+
| Tables_in_pbp       |
+---------------------+
| game_index          |
| play_by_play        |
| player_roster       |
| yahoo_defense_stats |
| yahoo_kicker_stats  |
| yahoo_offense_stats |
+---------------------+
6 rows in set (0.01 sec)

The first three are related to the stats I loaded from CSV files (see previous post).  The last three are the stats for each player for each game played (currently just 2010 season) for Offense, Kicker, and Defense players.  The code below extracts the score (for the Yahoo league scoring) and plots a histogram.  It uses code I wrote in "queryStats.py" that has the functions to extract the stats from the yahoo databases and calculate the score for a single game or for all games in a year.  The plot created is only on the offensive player stats.  Here's the plot from the code below:


First, there are a lot of players with zero points, here's the plot when removing those players.


This is what I expect to see, most players don't provide a lot of scores and only a few (e.g. Tom Brady) provide scores in the 300s.  Here's the plot when the bins are bigger:

Now it's easier to see that approximately 300 players provide only around 60 points or less in 2010, while approximately 100 players provide 150 points or more.  Basically there aren't very many Tom Brady's, or this year's star Aaron Rodgers, in the NFL.  The sample mean of the distribution is 68 with a standard deviation of 67, but  clearly this distribution is not normal and looks exponential.  The next post will look at this distribution for game day points.

The code is below.  One side note, the yahoo databases I created only provide a GAME ID and NAME to identify the player.  Unfortunately this was not enough to distinguish between 'C.Johnson' of Detroit and 'C.Johnson' of Carolina.  I did a joint search between the GAME INDEX database that has the teams playing for a specific GAME ID to mitigate this problem.  Clearly this code below will struggle when Carolina and Detroit play each other.  Not sure if I need to redesign the databases (it takes a few hours to extract the stats for each game) or improve my sql search since most of the information is there to disambiguate the search.

import numpy as np
import MySQLdb as mdb
import queryStats as qstats
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

class Players:
    def __init__(self,name,pos,team):
        self.name = name
        self.pos = pos
        self.team = team
        self.stats = None
        self.score = 0
    def getScore(self,conn):
        if self.pos != 'DEF' or self.pos !='K':
            player = qstats.Player(name)
        elif self.pos == 'K':
            player = qstats.Kicker(name)
        else:
            player = qstats.Defense(name)

        self.stats = player

        db = player.db
        sql = "SELECT %s.* \
        FROM game_index,%s \
        WHERE game_index.GAME_ID=%s.GAME_ID \
        AND (game_index.H=\'%s\' OR game_index.V=\'%s\') \
        AND %s.NAME=\"%s\";"%(db,db,db,team,team,db,name)
        try:
            cursor = conn.cursor()
            cursor.execute(sql)
            games = cursor.fetchall()
            for game in games:
                player.addGameStats(game)
            self.score = player.getScore()
            cursor.close()
        except mdb.Error,e:
            print sql 
            print e


if __name__ == '__main__':
    conn = mdb.connect(host='localhost',user='foo',passwd='som_pass', db='pbp');
    
    qbsql = "SELECT DISTINCT NAME,POS,TEAM FROM player_roster WHERE POS='QB' OR POS='WR1' OR POS='WR2' OR POS='TE' OR POS='RB'"
    score = []
    try:
        cursor = conn.cursor()
        cursor.execute(qbsql)
        qbs = cursor.fetchall()
        for qb in qbs:
            name = qb[0]
            pos = qb[1]
            team = qb[2]
            player = Players(name,pos,team)
            player.getScore(conn)
            score.append(player.score)        
        cursor.close()
    except mdb.Error,e:
        print sqlstr
        print e
    conn.close()

    plt.figure(facecolor='w',dpi=80)
    n, bins, patches = plt.hist(score, 50)
    plt.grid(True)
    plt.title('Offense Fantasy Scoring 2010')
    plt.xlabel('Fantasy Points')
    plt.ylabel('Number of Players')
    plt.show()


Sunday, September 4, 2011

Reading NFL Stat data into a database

Alright, I went to www.armchairanalysis.com and downloaded the play-by-play data that includes a list of all games (with weather, scores, etc.) and a player roster for the 2008-2010 season (I think it's new for them).  The downloads come in three files:
  • GameIndex.csv
  • PBPData.csv
  • Rosters.csv
  • PDF explaining the columns of each file
The goal will be to write Python code to create tables and insert data into MySQL.  For python I will have to also utilize the MySQL Python Package.  First I need to setup my SQL database.  I use a Mac, after installing MySQL and starting the server, I opened up a command prompt and did the following:

> mysql -u root
mysql> CREATE USER 'foo'@'localhost' IDENTIFIED BY 'foo';
mysql> SET PASSWORD FOR 'foo'@'localhost' = PASSWORD('som_pass')
mysql> GRANT ALL ON *.* TO 'user'@'localhost' IDENTIFIED BY 'user';
mysql> CREATE DATABASE pbp;

Once I setup the database I wrote code (see here to format your code in a blog) to create tables and insert CSV data into MySQL.  Any comments on how to improve this code would be helpful.  I did not provide all the code for the column names (inserted ... where I deleted code) for brevity.  I got the column names by copying what was in the PDF document explaining the columns.  If anyone knows of a good way to automatically does this based on the first line of the CSV file, let me know.

import MySQLdb as mdb
import sys

class ArmChairSQL:
    def createGameIndexTable(self,conn):
        try:
            cursor = conn.cursor()
            cursor.execute("DROP TABLE IF EXISTS game_index;")
            cursor.execute("CREATE TABLE IF NOT EXISTS \
            game_index(GAME_ID INT(10),\
            SEAS INT(4),\
            WK INT(2),\
            ...,\
            PFN INT(3))")
            
            conn.commit()
            cursor.close()            
        except mdb.Error, e:
            print e
            sys.exit(1)
            
            
    def createPlayByPlayTable(self,conn):
        try:
            cursor = conn.cursor()
            cursor.execute("DROP TABLE IF EXISTS play_by_play;")        
            cursor.execute("CREATE TABLE IF NOT EXISTS \
            play_by_play(GAME_ID INT(3),\
            PLAY_ID INT(3),\
            DETAIL VARCHAR(150),\
            OFF VARCHAR(3),\
            DEF VARCHAR(3),\
            ...,\
            BLK_NAM VARCHAR(20))")
        
            conn.commit()
            cursor.close()
        except mdb.Error, e:        
            print e
            sys.exit(1)

    def createPlayerRosterTable(self,conn):
        try:
            cursor = conn.cursor()
            cursor.execute("DROP TABLE IF EXISTS player_roster;")        
            cursor.execute("CREATE TABLE IF NOT EXISTS \
            player_roster(GAME_ID INT(3),\
            SEAS INT(4),\
            WEEK INT(4),\
            DAY CHAR(3),\
            TEAM VARCHAR(4),\
            POS VARCHAR(4),\
            ...,\
            RATING INT(3))")
            
            conn.commit()
            cursor.close()
        except mdb.Error, e:  
            print e
            sys.exit(1)

    def loadGameIndexData(self,conn,data):
        sql = "LOAD DATA LOCAL INFILE '%s' INTO TABLE game_index \
        FIELDS TERMINATED BY ',' \
        OPTIONALLY ENCLOSED BY '\"' \
        LINES TERMINATED BY '\\n' IGNORE 1 LINES;"  % data

        try:
            cursor = conn.cursor()
            cursor.execute(sql)
            conn.commit()
        except mdb.Error, e:
            print mdb.Error
            sys.exit(1)
            
    def loadPlayByPlayData(self,conn,data):
        sql = "LOAD DATA LOCAL INFILE '%s' INTO TABLE play_by_play \
        FIELDS TERMINATED BY ',' \
        OPTIONALLY ENCLOSED BY '\"' \
        LINES TERMINATED BY '\\n' IGNORE 1 LINES;"  % data

        try:
            cursor = conn.cursor()
            cursor.execute(sql)        
            conn.commit()
        except mdb.Error, e:
            print e
            sys.exit(1)

    def loadPlayerRosterData(self,conn,data):
        sql = "LOAD DATA LOCAL INFILE '%s' INTO TABLE player_roster \
        FIELDS TERMINATED BY ',' \
        OPTIONALLY ENCLOSED BY '\"' \
        LINES TERMINATED BY '\\n' IGNORE 1 LINES;"  % data

        try:
            cursor = conn.cursor()
            cursor.execute(sql)        
            conn.commit()
        except mdb.Error, e:
            print e
            sys.exit(1)


if __name__ == '__main__':        
    conn = mdb.connect(host='localhost',user='foo',passwd='', db='pbp');
    acsql = ArmChairSQL()

    print 'Creating and Loading Game Index Data'
    acsql.createGameIndexTable(conn)
    acsql.loadGameIndexData(conn,'GameIndex.csv')

    print 'Creating and Loading Play-by-Play Data'
    acsql.createPlayByPlayTable(conn)
    acsql.loadPlayByPlayData(conn,'PBPData.csv')

    print 'Creating and Loading Roster Data'
    acsql.createPlayerRosterTable(conn)
    acsql.loadPlayerRosterData(conn,'Rosters.csv')

    conn.close()

At the command prompt I execute the file by:

> python LoadArmChairSQL.py 

Unfortunately I get the following warnings (I show one for example):

> LoadArmChairSQL.py:330: Warning: Incorrect integer value: '' for column 'TEMP' at row 42 

I know it has to do with empty data in the CSV (just commas next to each other) while I'm expecting an integer.  Searches on the internet did not provide a simple answer to this, I'm sure there is an approach that does not involve editing the CSV file.

Below is an example of how to see if the data is loaded into the database.  The SELECT command extracts all Players Names and Defenses where an interception happened during the regular season in 2010.  Phew... I had to search on the internet to figure that out.  One thing I want to figure out is how to link the databases since they all use "GAME ID".

> mysql -u foo
mysql> USE pbp;
mysql> SHOW TABLES;
mysql> DESCRIBE game_index;
mysql> SELECT play_by_play.PSR_NAM,play_by_play.DEF FROM play_by_play,game_index WHERE play_by_play.GAME_ID=game_index.GAME_ID AND game_index.SEAS=2010 AND play_by_play.INT_NAM<>'' AND game_index.WK<=17;

Where do I find stats?

I knew this would be difficult, and was mainly the problem behind my failure to getting anything up and going before my drafts.  I looked on the NFL, Yahoo, and ESPN sites to see what I could get.  I tried the method of cut and paste tables into Excel, but this turned out to be very tedious and not very robust.  Further scouring of the inter-webs led me to www.advancednflstats.com.  This site is very informative on the role of stats, and most importantly contains play-by-play data from 2000-2010 in a CSV file. This seemed promising until a discovered how hard it would be to automatically extract what happened in each play (was it a pass, who intercepted the ball, how many rushing yards did the player get on the rush?).  I tried to learn Regular Expressions, but not sure that would get me all the way.  Plus I didn't have a player list and what position they played.  After some searching for a player list, I hit the jackpot of stats data at http://www.armchairanalysis.com/.  This site has all the data from 2000-2010, in fact it has more data than you will ever need.  Although, to get live 2011 data you have to pay approximately $600!  Wow, I guess it would make since if your company would benefit from live stats and you didn't want to pay anyone to extract the play-by-play data yourself.  Clearly something I wouldn't put my money in.  But for past data, I have all I need.  Next is to read the data into SQL via Python (yes, I know I don't have to do this, but it falls into goals 1 and 2) above.

An endeavor into fantasy football predictions....

When fantasy football came about, all I could think was: "Who would want to do this?".  I was wrong clearly.  But I still avoided it, choosing to enjoy just watching the game as it is.  Yet, somehow I got myself onto two teams this year!  Being an engineer and someone who enjoys math, I had to try and find a way to draft my players.  I failed miserably at getting anywhere near a model I intended to make, and just drafted live with my friends with no clear strategy other than choose defenses and kickers last. Maybe a model wouldn't work, but I feel I still should try.  I don't have any real hobbies, so maybe this could be something to pursue for awhile.  Things I want to accomplish with this project:

  1. Learn Python, from what I can tell this is a powerful scripting language.  For those of us who use Matlab daily, we know we need something better.  Python seems to have what we Matlabers need
  2. Learn MySQL, I guess this is more of a necessity than a want, but adds to the resume
  3. Develop a Monte Carlo simulation to predict the best team for fantasy football based on past statistics.  Clear this opens up a can of worms with questions like: "How does past performance predict the future?" or "Should I Monte Carlo over match ups between offensive player and defenses on their schedule?".
Clearly I'll need to expand on my goals for number 3 to keep me focused on an end goal.