Due: Thursday, October 16, 2014 11:59 PM

- Problem 1: Sabermetrics
- Problem-2: $K$-Nearest-Neighbors and Cross Validation
- Problem 3: The Curse and Blessing of Higher Dimensions

In this assignment you will be using regression and classification to explore different data sets.

**First**: You will use data from before 2002 in the Sean Lahman's Baseball Database to create a metric for picking baseball players using linear regression. This is same database we used in Homework 1. This database contains the "complete batting and pitching statistics from 1871 to 2013, plus fielding statistics, standings, team stats, managerial records, post-season data, and more". Documentation provided here.

http://saberseminar.com/wp-content/uploads/2012/01/saber-web.jpg

**Second**: You will use the famous iris data set to perform a $k$-neareast neighbor classification using cross validation. While it was introduced in 1936, it is still one of the most popular example data sets in the machine learning community. Wikipedia describes the data set as follows: "The data set consists of 50 samples from each of three species of Iris (Iris setosa, Iris virginica and Iris versicolor). Four features were measured from each sample: the length and the width of the sepals and petals, in centimetres." Here is an illustration what the four features measure:

http://sebastianraschka.com/Images/2014_python_lda/iris_petal_sepal.png

**Third**: You will investigate the influence of higher dimensional spaces on the classification using another standard data set in machine learning called the The digits data set. This data set is similar to the MNIST data set discussed in the lecture. The main difference is, that each digit is represented by an 8x8 pixel image patch, which is considerably smaller than the 28x28 pixels from MNIST. In addition, the gray values are restricted to 16 different values (4 bit), instead of 256 (8 bit) for MNIST.

**Finally**: In preparation for Homework 4, we want you to read through the following articles related to predicting the 2014 Senate Midterm Elections.

- Nate Silver's Methodology at while at NYT
- How The FiveThirtyEight Senate Forecast Model Works
- Pollster Ratings v4.0: Methodology
- Pollster Ratings v4.0: Results
- Nate Silver versus Sam Wang
- More Nate Silver versus Sam Wang
- Nate Silver explains critisims of Sam Wang
- Background on the feud between Nate Silver and Sam Wang
- Are there swing voters?

In [1]:

```
# special IPython command to prepare the notebook for matplotlib
%matplotlib inline
import os
from IPython import display
import requests
#import StringIO # not working in python 3
import zipfile
import numpy as np
import pandas as pd # pandas
import matplotlib.pyplot as plt # module for plotting
# If this module is not already installed, you may need to install it.
# You can do this by typing 'pip install seaborn' in the command line
import seaborn as sns
import sklearn
import sklearn.datasets
import sklearn.cross_validation
import sklearn.decomposition
import sklearn.grid_search
import sklearn.neighbors
import sklearn.metrics
```

Using data preceding the 2002 season pick 10 offensive players keeping the payroll under $20 million (assign each player the median salary). Predict how many games this team would win in a 162 game season.

In this problem we will be returning to the Sean Lahman's Baseball Database that we used in Homework 1. From this database, we will be extract five data sets containing information such as yearly stats and standing, batting statistics, fielding statistics, player names, player salaries and biographical information. You will explore the data in this database from before 2002 and create a metric for picking players.

Load in these CSV files from the Sean Lahman's Baseball Database. For this assignment, we will use the 'Teams.csv', 'Batting.csv', 'Salaries.csv', 'Fielding.csv', 'Master.csv' tables. Read these tables into separate pandas DataFrames with the following names.

CSV file name | Name of pandas DataFrame |
---|---|

Teams.csv | teams |

Batting.csv | players |

Salaries.csv | salaries |

Fielding.csv | fielding |

Master.csv | master |

In [17]:

```
### Your code here ###
url = "http://seanlahman.com/files/database/baseballdatabank-2017.1.zip"
r = requests.get(url, stream=True)
# Total size in bytes.
total_size = int(r.headers['Content-Length'])
with open("data/baseballdatabank-2017.1.zip", 'wb') as fd:
for chunk in r.iter_content(chunk_size=256):
fd.write(chunk)
print(f"file succesfully downloaded")
print(os.listdir("data"))
```

In [3]:

```
z = zipfile.ZipFile("data/baseballdatabank-2017.1.zip")
# extract into the data dir
z.extractall("data")
z.close()
data_dir = "data/baseballdatabank-2017.1/core/"
print(os.listdir(data_dir))
```

CSV file name | Name of pandas DataFrame |
---|---|

Teams.csv | teams |

Batting.csv | players |

Salaries.csv | salaries |

Fielding.csv | fielding |

Master.csv | master |

In [4]:

```
read_files = ['Teams.csv', 'Batting.csv', 'Salaries.csv', 'Fielding.csv', 'Master.csv']
teams, players, salaries, fielding, master = (pd.read_csv(data_dir + f) for f in read_files)
```

Calculate the median salary for each player and create a pandas DataFrame called `medianSalaries`

with four columns: (1) the player ID, (2) the first name of the player, (3) the last name of the player and (4) the median salary of the player. Show the head of the `medianSalaries`

DataFrame.

In [5]:

```
### Your code here ###
salaries.head()
```

Out[5]:

In [6]:

```
master[["playerID", "nameFirst", "nameLast"]].head()
```

Out[6]:

First to group the salaries by median:

In [7]:

```
medianSalaries = salaries.groupby("playerID").median().reset_index()
print(medianSalaries.shape)
medianSalaries.head()
```

Out[7]:

Now to add in the first and last name

In [8]:

```
medianSalaries = medianSalaries.merge(master[["playerID", "nameFirst", "nameLast"]], on="playerID")
print(medianSalaries.shape)
medianSalaries.head()
```

Out[8]:

In [9]:

```
medianSalaries.drop(["yearID"], axis=1, inplace=True)
```

The final dataframe:

In [10]:

```
print(medianSalaries.shape)
medianSalaries.head()
```

Out[10]:

Now, consider only team/season combinations in which the teams played 162 Games. Exclude all data from before 1947. Compute the per plate appearance rates for singles, doubles, triples, HR, and BB. Create a new pandas DataFrame called `stats`

that has the teamID, yearID, wins and these rates.

**Hint**: Singles are hits that are not doubles, triples, nor HR. Plate appearances are base on balls plus at bats.

looking at the readme file for the data needed for this q:

teams table:

- "G" is games played
- "W" is wins
- "yearID" is the year of the game
- "H" is all hits
- "2B" is doubles
- "3B" is triples
- "HR" is homeruns
- "BB" is base on balls
- "AB" is at bats

So first, getting a df for all teams which played 162 games from 1947 onwards:

In [11]:

```
### Your code here ###
stats = teams[teams["yearID"] >= 1947]
stats = stats[stats["G"]==162]
print(stats.shape)
stats.head()
```

Out[11]:

Now to drop all the un-needed columns

In [12]:

```
data_cols = ["teamID", "yearID", "W", "H", "2B", "3B", "HR", "BB", "AB"]
stats = stats[data_cols]
stats.head(2)
```

Out[12]:

In [13]:

```
stats["singles"] = stats["H"] - stats["2B"] - stats["3B"] - stats["HR"]
stats["plates"] = stats["BB"] + stats["AB"]
stats.head()
```

Out[13]:

Compute the per plate appearance rates for singles, doubles, triples, HR, and BB

In [14]:

```
for col in ["singles","2B","3B","HR","BB"]:
stats[col] = stats[col] / stats["plates"]
stats.head()
```

Out[14]:

So the final stats is:

In [15]:

```
stats = stats[["teamID", "yearID", "W", "singles","2B","3B","HR","BB"]]
stats.head()
```

Out[15]:

Is there a noticeable time trend in the rates computed computed in Problem 1(c)?

In [171]:

```
### Your code here ###
sns.pairplot(data=stats, x_vars="yearID",
y_vars=["W", "singles", "2B", "3B", "HR", "BB"], size=5, kind="reg")
```

Out[171]:

From the pairplots It looks like the singles and third base hits are decreasing over time while the second base hits are going up. Home runs interesting enough have a sudden shift upwards - probably linked to some kind of rule change, like allowing heavier bats or a more hittable ball.

Using the `stats`

DataFrame from Problem 1(c), adjust the singles per PA rates so that the average across teams for each year is 0. Do the same for the doubles, triples, HR, and BB rates.

In [172]:

```
### Your code here ###
# to get the mean for each year:
stats_mean_by_year = stats.groupby("yearID").mean()
stats_mean_by_year.head()
```

Out[172]:

I tried sklearns preprocessing, but that doesn't work in this case, so we need our own function:

In [225]:

```
def meanNormalizeRates(df):
subRates = df[["singles","2B","3B","HR","BB"]]
df[["singles","2B","3B","HR","BB"]] = subRates - subRates.mean(axis=0)
return df
statsNorm = stats.groupby('yearID').apply(meanNormalizeRates)
statsNorm.head()
```

Out[225]:

Build a simple linear regression model to predict the number of wins from the average adjusted singles, double, triples, HR, and BB rates. To decide which of these terms to include fit the model to data from 2002 and compute the average squared residuals from predictions to years past 2002. Use the fitted model to define a new sabermetric summary: offensive predicted wins (OPW). Hint: the new summary should be a linear combination of one to five of the five rates.

In [298]:

```
### Your code here ###
from sklearn import linear_model
from sklearn.metrics import mean_squared_error
# the test train datasets
mask = statsNorm["yearID"]<=2002
xtrain = statsNorm[["singles","2B","3B","HR","BB"]][mask]
xtest = statsNorm[["singles","2B","3B","HR","BB"]][~mask]
ytrain = statsNorm["W"][mask]
ytest = statsNorm["W"][~mask]
print(ytrain.shape,ytest.shape, regr.predict(xtrain).shape, regr.predict(xtest).shape)
regr = linear_model.LinearRegression()
regr.fit(xtrain, ytrain)
mean_squared_error(ytest, regr.predict(xtest))
```

Out[298]:

Taking a look to see how far the predictions are from the actual:

In [371]:

```
sns.regplot(ytest, regr.predict(xtest))
sns.regplot(ytrain, regr.predict(xtrain));
```

In [370]:

```
#plt.fill_between(x=range(384), y1=ytest, y2=regr.predict(xtest), interpolate=True)
plt.plot(range(384), ytest, label="Actual Y test values", alpha=0.4, color="g", lw=2, ls="-")
plt.plot(range(384), regr.predict(xtest), label="Predicted Y test values", alpha=0.3, color="r")
```

Out[370]:

In [354]:

```
plt.fill_between(x=range(ytrain.shape[0]), y1=ytrain, y2=regr.predict(xtrain),
interpolate=True)
plt.plot(range(ytrain.shape[0]), ytrain, label="Actual Y train values", alpha=0.3, color="g")
plt.plot(range(ytrain.shape[0]), regr.predict(xtrain), label="Predicted Y train values", alpha=0.3, color="r")
```

Out[354]:

** Your answer here: ** the mse is 83
The predictions have less varibality than actual - they are on trend, so to speak from looking at their plots, but the actual values spike higher and lower than the predicted.

Now we will create a similar database for individual players. Consider only player/year combinations in which the player had at least 500 plate appearances. Consider only the years we considered for the calculations above (after 1947 and seasons with 162 games). For each player/year compute singles, doubles, triples, HR, BB per plate appearance rates. Create a new pandas DataFrame called `playerstats`

that has the playerID, yearID and the rates of these stats. Remove the average for each year as for these rates as done in Problem 1(e).

In [624]:

```
### Your code here ###
playerstats = players[players["yearID"] >= 1947]
playerstats = playerstats[playerstats["G"]>=160]
# adding in the two new columns
playerstats["PA"] = playerstats["AB"] + playerstats["BB"]
playerstats["1B"] = playerstats["H"] - playerstats["2B"] - playerstats["3B"] - playerstats["HR"]
# convert numbers into rates per appearance
for col in ["1B", "2B", "3B", "HR", "BB"]:
playerstats[col] = playerstats[col] / playerstats["PA"]
# we only want these cols
playerstats = playerstats[["playerID", "yearID", "1B", "2B", "3B", "HR", "BB"]]
print(players.shape, playerstats.shape)
playerstats.head()
```

Out[624]:

Now to normalize per year:

In [625]:

```
def meanNormalizeRates(df):
"""takes in a DF and the columns to normalize"""
cols_to_norm = ["1B", "2B", "3B", "HR", "BB"]
tempDf = df[cols_to_norm]
df[cols_to_norm] = tempDf - tempDf.mean(axis=0)
return df
playerstats = playerstats.groupby('yearID').apply(meanNormalizeRates)
playerstats.shape
```

Out[625]:

Show the head of the `playerstats`

DataFrame.

In [626]:

```
### Your code here ###
playerstats.head()
```

Out[626]:

Using the `playerstats`

DataFrame created in Problem 1(g), create a new DataFrame called `playerLS`

containing the player's lifetime stats. This DataFrame should contain the playerID, the year the player's career started, the year the player's career ended and the player's lifetime average for each of the quantities (singles, doubles, triples, HR, BB). For simplicity we will simply compute the avaerage of the rates by year (a more correct way is to go back to the totals).

playerstats from 1g has most of the info, and to get the career start, stop we can either search for the min and mix year for each player or just use the "debut" and "finalGame" columns from the master table.

- debut Date that player made first major league appearance
- finalGame Date that player made first major league appearance (blank if still active)

In [627]:

```
master[["playerID", "debut", "finalGame"]].head(2)
```

Out[627]:

In [628]:

```
### Your code here ###
playerLS = playerstats.groupby("playerID").mean().drop("yearID", axis=1).reset_index()
playerLS.head()
```

Out[628]:

In [629]:

```
playerLS = playerLS.merge(master[["playerID", "debut", "finalGame"]], how="inner", on="playerID")
playerLS.head()
```

Out[629]:

Show the head of the `playerLS`

DataFrame.

In [11]:

```
### Your code here ###
```

Compute the OPW for each player based on the average rates in the `playerLS`

DataFrame. You can interpret this summary statistic as the predicted wins for a team with 9 batters exactly like the player in question. Add this column to the playerLS DataFrame. Call this colum OPW.

offensive predicted wins (OPW)

In [630]:

```
playerLS["OPW"] = regr.predict(playerLS[["1B", "2B", "3B", "HR", "BB"]])
playerLS.head()
```

Out[630]:

Add four columns to the `playerLS`

DataFrame that contains the player's position (C, 1B, 2B, 3B, SS, LF, CF, RF, or OF), first name, last name and median salary.

The fielding table contains the position 'POS', and we alread have the name and median salary in the df `medianSalaries`

.

First adding the median salaries to the playerLs df:

In [631]:

```
### Your code here ###
playerLS = playerLS.merge(medianSalaries, how="inner", on="playerID")
playerLS.head()
```

Out[631]:

Now to add in the POS of each player from the fielding table. This is trickier than it looks as the fielding table contains the fielding position for each year, and the playerLS table has only one entry per player. So I'm going to go with the most frequently used fielding position for each player and add that to the playerLS table.

In [632]:

```
from collections import Counter
def findPosition(df):
"""takes in a df, and for each row returns a POS by selecting
the most frequent POS for that player from the fielding table"""
playerID = df["playerID"]
positions = Counter(fielding[fielding["playerID"]==playerID]["POS"])
return positions.most_common(1)[0][0]
#apply the function to each row:
playerLS["Pos"] = playerLS.apply(findPosition, axis=1)
print(playerLS.shape)
playerLS.head()
```

Out[632]:

Show the head of the `playerLS`

DataFrame.

In [14]:

```
### Your code here ###
```

Subset the `playerLS`

DataFrame for players active in 2002 and 2003 and played at least three years. Plot and describe the relationship bewteen the median salary (in millions) and the predicted number of wins.

First, converting to datetime:

In [633]:

```
### Your code here ###
playerLS["debut"] = pd.to_datetime(playerLS["debut"])
playerLS["finalGame"] = pd.to_datetime(playerLS["finalGame"])
print(playerLS.shape)
playerLS.head(2)
```

Out[633]:

Building a mask to select:

In [634]:

```
mask = (((playerLS["finalGame"].dt.year - playerLS["debut"].dt.year) >= 3) &
(playerLS["debut"].dt.year <= 2002) &
(playerLS["finalGame"].dt.year >= 2003))
playeractive = playerLS[mask]
print(playeractive.shape)
playeractive.head(3)
```

Out[634]:

In [635]:

```
x = np.log(playeractive["salary"])
sns.lmplot("salary", "OPW", data=playeractive, logx=True)
plt.ylim(0,250)
sns.lmplot("salary", "OPW", data=playeractive, hue="Pos", size=6, fit_reg=True)
plt.ylim(0,250)
```

Out[635]:

in the first chart, there is a lot of OPW variation at the same salary, with a steady inrease with salary. Though the guy on the far right seems vastly overpaid for his OPW. The second chart does a linear regression per position, where there is a lot more varition, though this dataset doesn't have enough points to really say.

Pick one players from one of each of these 10 position C, 1B, 2B, 3B, SS, LF, CF, RF, DH, or OF keeping the total median salary of all 10 players below 20 million. Report their averaged predicted wins and total salary.

In [812]:

```
### Your code here ###
columns = ["playerID", "Pos", "salary", "OPW"]
salary_cap = 20*np.power(10, 6)
def make_team(salary_cap=salary_cap):
df = pd.DataFrame()
for pos in ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH", "OF"]:
df_temp = playeractive[playeractive["Pos"]==pos]
if df_temp.shape[0] > 0:
df = df.append(df_temp.sample())
return df
scores = []
best_team=make_team()
# going to make a bunch of random teams with the salary cap less then 20M
for i in range(150):
df = make_team()
while df["salary"].sum() >= salary_cap:
df = make_team()
score = df["OPW"].mean()
scores.append(score)
if score >= max(scores):
best_team = df
# The team with the highest mean OPW as per above:
print(f"Salary: {best_team['salary'].sum():,.0f}, Mean OPW: {best_team.OPW.mean():.2f}")
best_team
```

Out[812]:

In [813]:

```
sns.distplot(scores)
plt.axvline(max(scores), color="r");
```

Note: redo this later, but good enough for now.

What do these players outperform in? Singles, doubles, triples HR or BB?

so we need to know which of `["1B","2B","3B","HR","BB"]`

contributes most to OPW.

In [816]:

```
regr2 = linear_model.LinearRegression()
regr2.fit(best_team[["1B","2B","3B","HR","BB"]], best_team["OPW"])
regr2.coef_
```

Out[816]:

In [817]:

```
df = best_team.copy()
df[["1B","2B","3B","HR","BB"]] = df[["1B","2B","3B","HR","BB"]] * regr2.coef_ * 100
df
```

Out[817]:

In [818]:

```
for pos in ["1B","2B","3B","HR","BB"]:
print(pos, df[pos].mean())
```

** Your answer here: ** BB seems to be contributing the most to OPW

A better way would be to use a bigger data set to get the regression coef:

In [819]:

```
regr3 = linear_model.LinearRegression()
regr3.fit(playerLS[["1B","2B","3B","HR","BB"]], playerLS["OPW"])
df = best_team.copy()
df[["1B","2B","3B","HR","BB"]] = df[["1B","2B","3B","HR","BB"]] * regr3.coef_ * 100
for pos in ["1B","2B","3B","HR","BB"]:
print(pos, df[pos].mean())
```

*Write a brief discussion of your conclusions to the questions and tasks above in 100 words or less.*

there was a lot of data wrangling into the shape we wanted for analysis.

What is the optimal $k$ for predicting species using $k$-nearest neighbor classification on the four features provided by the iris dataset.

In this problem you will get to know the famous iris data set, and use cross validation to select the optimal $k$ for a $k$-nearest neighbor classification. This problem set makes heavy use of the sklearn library. In addition to Pandas, it is one of the most useful libraries for data scientists! After completing this homework assignment you will know all the basics to get started with your own machine learning projects in sklearn.

Future lectures will give further background information on different classifiers and their specific strengths and weaknesses, but when you have the basics for sklearn down, changing the classifier will boil down to exchanging one to two lines of code.

The data set is so popular, that sklearn provides an extra function to load it:

In [507]:

```
#load the iris data set
iris = sklearn.datasets.load_iris()
X = iris.data
Y = iris.target
X.shape, Y.shape
```

Out[507]:

Split the data into a train and a test set. Use a random selection of 33% of the samples as test data. Sklearn provides the `train_test_split`

function for this purpose. Print the dimensions of all the train and test data sets you have created.

See train_test_split docs, version linked above is depracated.

In [970]:

```
### Your code here ###
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.33)
x_train.shape, x_test.shape, y_train.shape, y_test.shape
```

Out[970]:

Examine the data further by looking at the projections to the first two principal components of the data. Use the `TruncatedSVD`

function for this purpose, and create a scatter plot. Use the colors on the scatter plot to represent the different classes in the target data.

In [975]:

```
### Your code here ###
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD()
x_train_centered = x_train-np.mean(x_train, axis=0)
x_test_centered = x_test-np.mean(x_test, axis=0)
x_pca = svd.fit_transform(x_train_centered)
plt.scatter(x_pca[:,0], x_pca[:,1], c=y_train, cmap=plt.cm.Vega10,
s=(2+y_train)*20)
plt.title("Principle component plot")
plt.colorbar()
plt.xlabel("PC 1"), plt.ylabel("PC 2");
```

In the lecture we discussed how to use cross validation to estimate the optimal value for $k$ (the number of nearest neighbors to base the classification on). Use ** ten fold cross validation** to estimate the optimal value for $k$ for the iris data set.

**Note**: For your convenience sklearn does not only include the KNN classifier, but also a grid search function. The function is called grid search, because if you have to optimize more than one parameter, it is common practice to define a range of possible values for each parameter. An exhaustive search then runs over the complete grid defined by all the possible parameter combinations. This can get very computation heavy, but luckily our KNN classifier only requires tuning of a single parameter for this problem set.

In [976]:

```
### Your code here ###
from sklearn.neighbors import KNeighborsClassifier
neigh = KNeighborsClassifier()
neigh.fit(x_train_centered, y_train)
np.mean(neigh.predict(x_test_centered)==y_test)
```

Out[976]:

Pretty good accuracy right of the bat! now to find the optimal K using GridSearchCV:

In [977]:

```
from sklearn.model_selection import GridSearchCV
k_values = [k for k in range(1,25)]
parameters = {'n_neighbors': k_values}
clf = GridSearchCV(KNeighborsClassifier(), parameters, cv=10)
clf.fit(x_train_centered, y_train)
```

Out[977]:

Visualize the result by plotting the score results versus values for $k$.

In [978]:

```
### Your code here ###
#plt.plot(clf.param_grid["n_neighbors"], clf.cv_results_["mean_test_score"], linewidth=2)
plt.errorbar(clf.param_grid["n_neighbors"], clf.cv_results_["mean_test_score"],
clf.cv_results_["std_test_score"], lw=3, elinewidth=2, ecolor="g")
plt.ylim(0.8,1.05)
plt.title("K vs accuracy")
plt.xticks(k_values)
plt.xlabel("K value"), plt.ylabel("Accuracy")
# highlight the best value
best_k = clf.best_params_["n_neighbors"]
plt.scatter(best_k, clf.best_score_, alpha=0.2, s=300, c="g")
plt.annotate(f"Best k: {best_k}, Best score: {clf.best_score_}", color="r",
arrowprops=dict(facecolor='r', shrink=0.05),
xy=(best_k, clf.best_score_), xytext=(best_k+2, 0.88));
```

Verify that the grid search has indeed chosen the right parameter value for $k$.

In [979]:

```
### Your code here ###
clf.best_params_
```

Out[979]:

Test the performance of our tuned KNN classifier on the test set.

In [980]:

```
### Your code here ###
# simple way to check:
clf.best_estimator_.score(x_test_centered, y_test)
```

Out[980]:

looking at the cross_val score:

In [981]:

```
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf.best_estimator_, x_test_centered, y_test, cv=5)
scores.mean(), scores.std()
```

Out[981]:

*Write a brief discussion of your conclusions to the questions and tasks above in 100 words or less.*

We look at classifying the iris data set, and KNN did a pretty accurate job, with the ideal K =1. But why the diff b/w the score and the cross_val score?

In this problem we will investigate the influence of higher dimensional spaces on the classification. The data set is again one of the standard data sets from sklearn. The digits data set is similar to the MNIST data set discussed in the lecture. The main difference is, that each digit is represented by an 8x8 pixel image patch, which is considerably smaller than the 28x28 pixels from MNIST. In addition, the gray values are restricted to 16 different values (4 bit), instead of 256 (8 bit) for MNIST.

First we again load our data set.

In [864]:

```
digits = sklearn.datasets.load_digits()
X = digits.data
Y = digits.target
X.shape, Y.shape
```

Out[864]:

Start with the same steps as in Problem 2. Split the data into train and test set. Use 33% of the samples as test data. Print the dimensions of all the train and test data sets you created.

In [865]:

```
### Your code here ###
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.33)
x_train.shape, x_test.shape, y_train.shape, y_test.shape
```

Out[865]:

Similar to Problem 2(b), create a scatter plot of the projections to the first two PCs. Use the colors on the scatter plot to represent the different classes in the target data. How well can we separate the classes?

**Hint**: Use a `Colormap`

in matplotlib to represent the diferent classes in the target data.

In [982]:

```
### Your code here ###
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD()
x_train = x_train-np.mean(x_train, axis=0)
x_test = x_test-np.mean(x_test, axis=0)
x_pca = svd.fit_transform(x_train-np.mean(x_train, axis=0))
plt.scatter(x_pca[:,0], x_pca[:,1], c=y_train, cmap=plt.cm.Vega10)
plt.colorbar()
plt.title("Principle component plot")
plt.xlabel("PC 1"), plt.ylabel("PC 2");
```

Create individual scatter plots using only two classes at a time to explore which classes are most difficult to distinguish in terms of class separability. You do not need to create scatter plots for all pairwise comparisons, but at least show one.

In [983]:

```
NUMS = 4 # make plots for digits from 0 to this number
# setting up the number combinations
from itertools import combinations
combs = np.array([i for i in combinations(range(NUMS),2)])
fig = plt.figure(figsize=(16,16))
sns.despine(left=True, bottom=True)
cmap = plt.cm.get_cmap('tab10')
grid_size = (NUMS, NUMS)
axes = [] # holds all the subplots
i = 0 # the axis number
for x, y in combs:
# first we need to seperate out the x and y digits
mask_x = np.ma.masked_where(y_train==x, y_train).mask
mask_y = np.ma.masked_where(y_train==y, y_train).mask
axes.append(plt.subplot2grid(grid_size, (x,y)))
axes[i].set_title(f"{x} vs {y}", fontsize=18)
# scatter plot for digit x
axes[i].scatter(x_pca[:,0][mask_x], x_pca[:,1][mask_x],
c=[cmap(i/10) for i in y_train[mask_x]], label=str(x))
# scatter plot for digit y
axes[i].scatter(x_pca[:,0][mask_y], x_pca[:,1][mask_y],
c=[cmap(i/10) for i in y_train[mask_y]], label=str(y))
axes[i].legend(loc="lower right", facecolor="grey", frameon=True, framealpha=0.15)
i += 1
fig.tight_layout()
```