'''
File "NFLtest.py" by KWR for CSE199, Fall 2022.  File for Internet and Data Homework 2.
Requires: file "NFLTeams.xml" accessible via web (for Python Trinket web use)
or in same directory or otherwise listed in an IDE as a peer project file.
Requires the "pandas" and "sklearn" packages, which are on the UB CSE machines
and accessible via Python 3 Trinket but may be absent from some Python 3 downloads.

Usage: Runs automatically in Python 3 Trinket, should work similarly in other web apps.
Command-line usage: python3 NFLtest.py       (no other arguments)

Illustrates simple linear regression and the R^2 measure of correlation.
Tries to "explain" Y = the number of seasons since an NFL team last won a 
playoff game using X = the number of head coaches the team has had since 1990
and then by trying Z = the population of the team's media market (in millions).
Which is more correlated to Y---is it X or Z?  Are the correlations significant?
'''

from __future__ import division
import numpy as np
import matplotlib
matplotlib.use('Agg')  # Used for linux. To make sure the backend is a non-gui backend
from matplotlib import pyplot as plt
import scipy
import scipy.stats, scipy.optimize, scipy.special
from scipy.optimize import curve_fit
from pylab import *
import pandas as pd
from sklearn import linear_model

import sys
import re
import xml.etree.ElementTree as ET
from html.parser import HTMLParser
from traceback import print_exc
#import urllib
import urllib.request    #seems required in 2022



'''
If we don't get "matplotlib" working easily in a "sandbox" or "fiddle" environment
where you can see the plot without a separate viewer, we can use this crude ASCII
text plotting routine.  Plotted points are rounded to the nearest "step" value.
'''
def crudeplot(XX,YY,xc,yc,xname,yname):
    X = np.asfarray(XX)
    Y = np.asfarray(YY)
    numpts = len(X)
    maxX = float(max(X))
    maxY = float(max(Y))
    minX = float(min(X))
    minY = float(min(Y))
    Yvals = [None for k in range(yc)]
    xstep = (maxX - minX)/(xc - 1.0)
    ystep = (maxY - minY)/(yc - 1.0)
    plotz = [[' ' for j in range(xc)] for i in range(yc)]
    for i in range(numpts):
        xcol = round(float((X[i] - minX)/xstep))
        ycol = round(float((maxY - Y[i])/ystep))
        Yvals[ycol] = Y[i]
        plotz[ycol][xcol] = '*'
    plotstr = "\n/" + str(minY) + " ... " + yname + " ... " + str(maxY) + "\n"
    for i in range(yc):
        yval = "  " + str(float(Yvals[i])) if Yvals[i] is not None else ""
        plotstr += '|' + "".join(plotz[i]) + yval + "\n"
    plotstr += '\\' + "".join(['-' for k in range(xc)]) + "\n"
    plotstr += " " + str(minX) + " ... " + xname + " ... " + str(maxX) + "\n"
    return plotstr

'''
Parser for XML nested no further than a simple 2-diemnsional table.
Formats the table with named column fields in a "Pandas DataFrame" object.
Later we extract columns into simple Python arrays.
'''

def xml2df(xml_data, attributesAreFields, convertNumeric=True):
    root = ET.XML(xml_data) # element tree
    #root = ET.parse(xml_data)
    all_records = []
    for i, child in enumerate(root):
        if attributesAreFields:
            all_records.append(child.attrib)
        else:
            record = {}
            for subchild in child:
                record[subchild.tag] = subchild.text
                all_records.append(record)
    
    df = pd.DataFrame(all_records)
    if convertNumeric:
        df = df.apply(pd.to_numeric, errors='ignore')   #needed for Python3 Trinket
        #df = df.convert_objects(convert_numeric=True)    #needed on CSE machines
    return df
    

# main

#location = 'NFLTeams.xml'
location = "https://www.cse.buffalo.edu/~regan/cse199/NFLTeams.xml"
#location = "https://www.cse.buffalo.edu/~regan/cse199/NFLTeamsNoCIN.xml"   #2017
#location = "https://www.cse.buffalo.edu/~regan/cse199/NFLTeamsNoDET.xml"   #use in 2022

#you might need to vary the next lines on alternate systems
if location.startswith('http'):
      #source = urllib.urlopen(location).read().decode('utf-8').split('\n')
      source = urllib.request.urlopen(location).read().decode('utf-8')
      #source = requests.get(location).text.split('\n')   # Python 3 Trinket
else:
      source = open(location, 'r').read()   #.decode('utf-8')   #don't!
      
NFLdf = xml2df(source, True)

# Illustrating that arrays can do +- or */ with scalars
Yarr = 2021 - np.array(NFLdf['ylpw'])   # changed from 'lastPlayoffWin'
#Zarr = np.array(NFLdf['pop'])/1000000.0
Zarr = np.array(NFLdf['pop'].str.replace(',', '').astype(int))/1000000.0

#2022 NOTE^^^^: Commas inside integers may not be parsed automatically on some systems
#The Yarr line still seems to be accepted as-is, since the years don't have commas.


# Extend a DataFrame with new columns---the "NFLdf = " part is needed
NFLdf = NFLdf.assign(drought=Yarr, mpop=Zarr)

# Columns of the new data object still need to be cast as DataFrames
X = pd.DataFrame(NFLdf['cs2010'])   # X = pd.DataFrame(NFLdf.cs2010) works too
Y = pd.DataFrame(NFLdf.drought)   # or = pd.DataFrame(NFLdf['drought']) but not pd.DataFrame(Yarr)
Z = pd.DataFrame(NFLdf.mpop)      # or could use Zarr

# When you make a table with 2 columns the casts start to make sense
W = pd.DataFrame(NFLdf, columns=['mpop','cs2010'])

print(crudeplot(NFLdf['cs2010'],Yarr,61,27,"Coaches since 2010","Years since last playoff win"))
print(crudeplot(Zarr,Yarr,61,27,"Population in millions","Years since last playoff win"))


lm = linear_model.LinearRegression()

model1 = lm.fit(X,Y)
intercept1 = model1.intercept_[0]
slope1 = model1.coef_[0][0]
score1 = model1.score(X,Y)

pred1 = model1.predict(X)
fig1ax = NFLdf.plot(kind='scatter', x='cs2010', y='drought', color='blue')

plt.plot(X, pred1, color='green', linewidth=1)  # here could use NFLdf.cs2010 w/o the cast instead of X
plt.axis([0, 1+NFLdf.cs2010.max(), -1, 1+NFLdf.drought.max()])   # But here X does NOT work!
plt.xticks(np.arange(1+NFLdf.cs2010.max()))
plt.yticks(np.arange(1+NFLdf.drought.max()))
plt.xlabel('Number of coaches since 2010')
plt.ylabel('Number of seasons since last playoff win')
plt.title('Playoff Drought Versus Coaching Changes')

savefig('XYplot.png')


model2 = lm.fit(Z,Y)    # Though called "Z", the source variable still comes first
intercept2 = model2.intercept_[0]
slope2 = model2.coef_[0][0]
score2 = model1.score(Z,Y)

pred2 = model2.predict(Z)
fig2ax = NFLdf.plot(kind='scatter', x='mpop', y='drought', color='blue')

plt.plot(Z, pred2, color='green', linewidth=1)  
plt.axis([0, 1+NFLdf.mpop.max(), -1, 1+NFLdf.drought.max()])   # But here Z does NOT work!
plt.xticks(np.arange(1+NFLdf.mpop.max()))
plt.yticks(np.arange(1+NFLdf.drought.max()))
plt.xlabel('Media market population in millions')
plt.ylabel('Number of seasons since last playoff win')
plt.title('Playoff Drought Versus Media Market Size')

savefig('ZYplot.png')

model3 = lm.fit(W,Y)
intercept3 = model3.intercept_[0]
slope31 = model3.coef_[0][0]
slope32 = model3.coef_[0][1]
score3 = model3.score(W,Y)

print("\nIgnore any warning(s) above")
print()
print()
print("Results for drought versus coaches:")
print("Drought in years = ", format(intercept1,'.3f'), " + ", format(slope1,'.3f'), \
      "*(number of coaches since 2010) with score R^2 = ", format(score1,'.4f'),sep='')
      #format(model1.score(pd.DataFrame(NFLdf.cs90), pd.DataFrame(NFLdf.drought)),'.4f'),sep='')
print()
print("Results for drought versus population:")
print("Drought in years = ", format(intercept2,'.3f'), " + ", format(slope2,'.3f'), \
      "*(media market pop in millions) with score R^2 = ", format(score2,'.4f'),sep='')
print()
print("Results for drought versus both:")
print("Drought in years = ", format(intercept3,'.3f'), " + ", format(slope31,'.3f'), \
      "*(media market pop in millions) + ", format(slope32,'.3f'), \
      "*(cs2010) with score R^2 = ", format(score3,'.4f'),sep='')
print()


