In [2]:
from enum import Enum
class AccountType(Enum):
SAVINGS = 1
CHECKING = 2
An Enum stands for an enumeration, it's a convenient way for you to define lists of things. Typing:
In [3]:
AccountType.SAVINGS
Out[3]:
returns a Python representation of an enumeration. You can compare these account types:
In [4]:
AccountType.SAVINGS == AccountType.SAVINGS
Out[4]:
In [5]:
AccountType.SAVINGS == AccountType.CHECKING
Out[5]:
To get a string representation of an Enum, you can use:
In [6]:
AccountType.SAVINGS.name
Out[6]:
Constructor is BankAccount(self, owner, accountType)
where owner
is a string representing the name of the account owner and accountType
is one of the AccountType enums
Methods withdraw(self, amount)
and deposit(self, amount)
to modify the account balance of the account
Override methods __str__
to write an informative string of the account owner and the type of account, and __len__
to return the balance of the account
In [7]:
class BankAccount():
def __init__(self,owner,accountType):
self.owner=owner
self.accountType=accountType
self.balance=0
def withdraw(self,amount):
if amount<0:
raise ValueError("amount<0")
if self.balance<amount:
raise ValueError("withdraw more than balance")
self.balance-=amount
def deposit(self,amount):
if amount<0:
raise ValueError("amount<0")
self.balance+=amount
def __str__(self):
return "owner:{!s} account type:{!s}".format(self.owner,self.accountType.name)
def __len__(self):
return self.balance
In [8]:
myaccount=BankAccount("zhaizhai",AccountType.CHECKING)
In [9]:
print(myaccount.balance)
Constructor BankUser(self, owner)
where owner
is the name of the account.
Method addAccount(self, accountType)
- to start, a user will have no accounts when the BankUser object is created. addAccount
will add a new account to the user of the accountType
specified. Only one savings/checking account per user, return appropriate error otherwise
Methods getBalance(self, accountType)
, deposit(self, accountType, amount)
, and withdraw(self, accountType, amount)
for a specific AccountType.
Override __str__
to have an informative summary of user's accounts.
In [10]:
class BankUser():
def __init__(self,owner):
self.owner=owner
self.SavingAccount=None
self.CheckingAccount=None
def addAccount(self,accountType):
if accountType==AccountType.SAVINGS:
if self.SavingAccount==None:
self.SavingAccount=BankAccount(self.owner,accountType)
else:
print("more than one saving account!")
raise AttributeError("more than one saving account!")
elif accountType==AccountType.CHECKING:
if self.CheckingAccount==None:
self.CheckingAccount=BankAccount(self.owner,accountType)
else:
print("more than one checking account!")
raise AttributeError("more than one checking account!")
else:
print("no such account type!")
raise ValueError("no such account type!")
def getBalance(self,accountType):
if accountType==AccountType.SAVINGS:
if self.SavingAccount==None:
print("saving account not exist")
raise AttributeError("saving account not exist")
else:
return self.SavingAccount.balance
elif accountType==AccountType.CHECKING:
if self.CheckingAccount==None:
print("checking account not exist")
raise AttributeError("checking account not exist")
else:
return self.CheckingAccount.balance
else:
print("no such account type!")
raise AttributeError("no such account type!")
def deposit(self,accountType,amount):
if accountType==AccountType.SAVINGS:
if self.SavingAccount==None:
print("saving account not exist")
raise AttributeError("saving account not exist")
else:
return self.SavingAccount.deposit(amount)
elif accountType==AccountType.CHECKING:
if self.CheckingAccount==None:
print("checking account not exist")
raise AttributeError("checking account not exist")
else:
return self.CheckingAccount.deposit(amount)
else:
print("no such account type!")
raise AttributeError("no such account type!")
def withdraw(self,accountType,amount):
if accountType==AccountType.SAVINGS:
if self.SavingAccount==None:
print("saving account not exist")
raise AttributeError("saving account not exist")
else:
return self.SavingAccount.withdraw(amount)
elif accountType==AccountType.CHECKING:
if self.CheckingAccount==None:
print("checking account not exist")
raise AttributeError("checking account not exist")
else:
return self.CheckingAccount.withdraw(amount)
else:
print("no such account type!")
raise AttributeError("no such account type!")
def __str__(self):
s="owner:{!s}".format(self.owner)
if self.SavingAccount!=None:
s=s+"account type: Saving balance:{:.2f}".format(self.SavingAccount.balance)
if self.CheckingAccount!=None:
s=s+"account type: Checking balance:{:.2f}".format(self.CheckingAccount.balance)
return s
In [11]:
newuser=BankUser("zhaizhai")
print(newuser)
newuser.addAccount(AccountType.SAVINGS)
print(newuser)
newuser.deposit(AccountType.SAVINGS,2)
newuser.withdraw(AccountType.SAVINGS,1)
print(newuser)
newuser.withdraw(AccountType.CHECKING,1)
Write some simple tests to make sure this is working. Think of edge scenarios a user might try to do.
Finally, we are going to rewrite a closure to use our bank account. We will make use of the input function which takes user input to decide what actions to take.
Write a closure called ATMSession(bankUser) which takes in a BankUser object. Return a method called Interface that when called, would provide the following interface:
First screen for user will look like:
Enter Option:
1)Exit
2)Create Account
3)Check Balance
4)Deposit
5)Withdraw
Pressing 1 will exit, any other option will show the options:
Enter Option:
1)Checking
2)Savings
If a deposit or withdraw was chosen, then there must be a third screen:
Enter Integer Amount, Cannot Be Negative:
This is to keep the code relatively simple, if you'd like you can also curate the options depending on the BankUser object (for example, if user has no accounts then only show the Create Account option), but this is up to you. In any case, you must handle any input from the user in a reasonable way that an actual bank would be okay with, and give the user a proper response to the action specified.
Upon finishing a transaction or viewing balance, it should go back to the original screen
In [183]:
def ATMSession(bankUser):
def Interface():
option1=input("Enter Options:\
1)Exit\
2)Creat Account\
3)Check Balance\
4)Deposit\
5)Withdraw")
if option1=="1":
Interface()
return
option2=input("Enter Options:\
1)Checking\
2)Saving")
if option1=="2":
if option2=="1":
bankUser.addAccount(AccountType.CHECKING)
Interface()
return
elif option2=="2":
bankUser.addAccount(AccountType.SAVINGS)
Interface()
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="3":
if option2=="1":
print(bankUser.getBalance(AccountType.CHECKING))
Interface()
return
elif option2=="2":
print(bankUser.getBalance(AccountType.SAVINGS))
Interface()
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="4":
option3=input("Enter Interger Amount, Cannot be Negative:")
if option2=="1":
bankUser.deposit(AccountType.CHECKING,int(option3))
Interface()
return
elif option2=="2":
bankUser.deposit(AccountType.SAVINGS,int(option3))
Interface()
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="5":
option3=input("Enter Interger Amount, Cannot be Negative:")
if option2=="1":
bankUser.withdraw(AccountType.CHECKING,int(option3))
Interface()
return
elif option2=="2":
bankUser.withdraw(AccountType.SAVINGS,int(option3))
Interface()
return
else:
print("no such account type")
raise AttributeError("no such account type")
print("no such operation")
raise AttributeError("no such operation")
return Interface
In [ ]:
myATM=ATMSession(newuser)
myATM()
In [14]:
print(newuser)
We will be grading this problem with a test suite. Put the enum, classes, and closure in a single file named Bank.py. It is very important that the class and method specifications we provided are used (with the same capitalization), otherwise you will receive no credit.
In [169]:
%%file bank.py
from enum import Enum
class AccountType(Enum):
SAVINGS = 1
CHECKING = 2
class BankAccount():
def __init__(self,owner,accountType):
self.owner=owner
self.accountType=accountType
self.balance=0
def withdraw(self,amount):
if type(amount)!=int:
raise ValueError("not integer amount")
if amount<0:
raise ValueError("amount<0")
if self.balance<amount:
raise ValueError("withdraw more than balance")
self.balance-=amount
def deposit(self,amount):
if type(amount)!=int:
raise ValueError("not integer amount")
if amount<0:
raise ValueError("amount<0")
self.balance+=amount
def __str__(self):
return "owner:{!s} account type:{!s}".format(self.owner,self.accountType.name)
def __len__(self):
return self.balance
def ATMSession(bankUser):
def Interface():
option1=input("Enter Options:\
1)Exit\
2)Creat Account\
3)Check Balance\
4)Deposit\
5)Withdraw")
if option1=="1":
return
option2=input("Enter Options:\
1)Checking\
2)Saving")
if option1=="2":
if option2=="1":
bankUser.addAccount(AccountType.CHECKING)
return
elif option2=="2":
bankUser.addAccount(AccountType.SAVINGS)
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="3":
if option2=="1":
print(bankUser.getBalance(AccountType.CHECKING))
return
elif option2=="2":
print(bankUser.getBalance(AccountType.SAVINGS))
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="4":
option3=input("Enter Interger Amount, Cannot be Negative:")
if option2=="1":
bankUser.deposit(AccountType.CHECKING,int(option3))
return
elif option2=="2":
bankUser.deposit(AccountType.SAVINGS,int(option3))
return
else:
print("no such account type")
raise AttributeError("no such account type")
if option1=="5":
option3=input("Enter Interger Amount, Cannot be Negative:")
if option2=="1":
bankUser.withdraw(AccountType.CHECKING,int(option3))
return
elif option2=="2":
bankUser.withdraw(AccountType.SAVINGS,int(option3))
return
else:
print("no such account type")
raise AttributeError("no such account type")
print("no such operation")
raise AttributeError("no such operation")
return Interface
Consider the multivariate linear model:
$$y = X\beta + \epsilon$$where $y$ is a length $n$ vector, $X$ is an $m \times p$ matrix, and $\beta$ is a $p$ length vector of coefficients.
OLS Regression seeks to minimize the following cost function:
$$\|y - \beta\mathbf {X}\|^{2}$$The best fit coefficients can be obtained by:
$$\hat{\beta} = (X^T X)^{-1}X^Ty$$where $X^T$ is the transpose of the matrix $X$ and $X^{-1}$ is the inverse of the matrix $X$.
Ridge Regression introduces an L2 regularization term to the cost function:
$$\|y - \beta\mathbf {X}\|^{2}+\|\Gamma \mathbf {x} \|^{2}$$Where $\Gamma = \alpha I$ for some constant $\alpha$ and the identity matrix $I$.
The best fit coefficients can be obtained by: $$\hat{\beta} = (X^T X+\Gamma^T\Gamma)^{-1}X^Ty$$
Lasso Regression introduces an L1 regularization term and restricts the total number of predictor variables in the model. The following cost function: $${\displaystyle \min _{\beta _{0},\beta }\left\{{\frac {1}{m}}\left\|y-\beta _{0}-X\beta \right\|_{2}^{2}\right\}{\text{ subject to }}\|\beta \|_{1}\leq \alpha.}$$
does not have a nice closed form solution. For the sake of this exercise, you may use the sklearn.linear_model.Lasso class, which uses a coordinate descent algorithm to find the best fit. You should only use the class in the fit() method of this exercise (ie. do not re-use the sklearn for other methods in your class).
The $R^2$ score is defined as: $${R^{2} = {1-{SS_E \over SS_T}}}$$
Where:
$$SS_T=\sum_i (y_i-\bar{y})^2, SS_R=\sum_i (\hat{y_i}-\bar{y})^2, SS_E=\sum_i (y_i - \hat{y_i})^2$$where ${y_i}$ are the original data values, $\hat{y_i}$ are the predicted values, and $\bar{y_i}$ is the mean of the original data values.
Write a class called Regression
with the following methods:
$fit(X, y)$: Fits linear model to $X$ and $y$.
$get\_params()$: Returns $\hat{\beta}$ for the fitted model. The parameters should be stored in a dictionary.
$predict(X)$: Predict new values with the fitted model given $X$.
$score(X, y)$: Returns $R^2$ value of the fitted model.
$set\_params()$: Manually set the parameters of the linear model.
This parent class should throw a NotImplementedError
for methods that are intended to be implemented by subclasses.
In [133]:
class Regression():
def __init__(self,X,y):
self.X=X
self.y=y
self.alpha=0.1
def fit(self,X,y):
return
def get_params(self):
return self.beta
def predict(self,X):
import numpy as np
return np.dot(X,self.beta)
def score(self,X,y):
return 1-np.sum((y-self.predict(X))**2)/np.sum((y-np.mean(y))**2)
def set_params(self,alpha):
self.alpha=alpha
In [134]:
class OLSRegression(Regression):
def fit(self):
import numpy as np
X=self.X
y=self.y
self.beta=np.dot(np.dot(np.linalg.pinv(np.dot(np.transpose(X),X)),np.transpose(X)),y)
In [135]:
ols1=OLSRegression([[2],[3]],[[1],[2]])
ols1.fit()
ols1.predict([[2],[3]])
Out[135]:
In [136]:
X=[[2],[3]]
y=[[1],[2]]
beta=np.dot(np.dot(np.linalg.pinv(np.dot(np.transpose(X),X)),np.transpose(X)),y)
In [137]:
class RidgeRegression(Regression):
def fit(self):
import numpy as np
X=self.X
y=self.y
self.beta=np.dot(np.dot(np.linalg.pinv(np.dot(np.transpose(X),X)+self.alpha**2),np.transpose(X)),y)
return
In [167]:
ridge1=RidgeRegression([[2],[3]],[[1],[2]])
ridge1.fit()
ridge1.predict([[2],[3]])
Out[167]:
In [168]:
ridge1.score([[2],[3]],[[1],[2]])
Out[168]:
In [174]:
class LassoRegression(Regression):
def fit(self):
from sklearn.linear_model import Lasso
myLs=Lasso(self.alpha)
myLs.fit(self.X,self.y)
self.beta=myLs.coef_.reshape((-1,1))
self.beta0=myLs.intercept_
return
def predict(self,X):
import numpy as np
return np.dot(X,self.beta)+self.beta0
In [175]:
lasso1=LassoRegression([[2],[3]],[[1],[2]])
lasso1.fit()
lasso1.predict([[2],[3]])
Out[175]:
In [176]:
lasso1.score([[2],[3]],[[1],[2]])
Out[176]:
In [177]:
from sklearn.linear_model import Lasso
myLs=Lasso(alpha=0.1)
myLs.fit([[2],[3]],[[1],[1]])
beta=np.array(myLs.coef_)
print(beta.reshape((-1,1)))
beta0=myLs.intercept_
print(beta0)
You will use the Boston dataset for this part.
Instantiate each of the three models above. Using a for loop, fit (on the training data) and score (on the testing data) each model on the Boston dataset.
Print out the $R^2$ value for each model and the parameters for the best model using the get_params()
method. Use an $\alpha$ value of 0.1.
Hint: You can consider using the sklearn.model_selection.train_test_split
method to create the training and test datasets.
In [181]:
from sklearn.datasets import load_boston
from sklearn.model_selection import KFold
from sklearn.metrics import r2_score
import statsmodels.api as sm
import numpy as np
boston=load_boston()
boston_x=boston.data
boston_y=boston.target
kf=KFold(n_splits=2)
kf.get_n_splits(boston)
ols1_m=0
ridge1_m=0
lasso1_m=0
for train_index, test_index in kf.split(boston_x):
X_train, X_test = boston_x[train_index], boston_x[test_index]
y_train, y_test = boston_y[train_index], boston_y[test_index]
y_train=y_train.reshape(-1,1)
y_test=y_test.reshape(-1,1)
ols1=OLSRegression(sm.add_constant(X_train),y_train)
ols1.fit()
ols1_m+=ols1.score(sm.add_constant(X_test),y_test)
print("OLS score:",ols1.score(sm.add_constant(X_test),y_test))
ridge1=RidgeRegression(sm.add_constant(X_train),y_train)
ridge1.fit()
ridge1_m+=ridge1.score(sm.add_constant(X_test),y_test)
print("ridge score:",ridge1.score(sm.add_constant(X_test),y_test))
lasso1=LassoRegression(X_train,y_train)
lasso1.fit()
lasso1_m+=lasso1.score(X_test,y_test)
print("lasso score:",lasso1.score(X_test,y_test))
break
print(ols1_m,ridge1_m,lasso1_m)
ols1.get_params()
Out[181]:
We can evaluate how the models perform for various values of $\alpha$. Calculate the $R^2$ scores for each model for $\alpha \in [0.05, 1]$ and plot the three lines on the same graph. To change the parameters, use the set_params()
method. Be sure to label each line and add axis labels.
In [179]:
ols_r=[]
ridge_r=[]
lasso_r=[]
alpha_l=[]
for alpha_100 in range(5,100,5):
alpha=alpha_100/100
alpha_l.append(alpha)
for train_index, test_index in kf.split(boston_x):
X_train, X_test = boston_x[train_index], boston_x[test_index]
y_train, y_test = boston_y[train_index], boston_y[test_index]
y_train=y_train.reshape(-1,1)
y_test=y_test.reshape(-1,1)
ols1=OLSRegression(sm.add_constant(X_train),y_train)
ols1.set_params(alpha)
ols1.fit()
ols_r.append(ols1.score(sm.add_constant(X_test),y_test))
ridge1=RidgeRegression(sm.add_constant(X_train),y_train)
ridge1.set_params(alpha)
ridge1.fit()
ridge_r.append(ridge1.score(sm.add_constant(X_test),y_test))
lasso1=LassoRegression(X_train,y_train)
lasso1.set_params(alpha)
lasso1.fit()
lasso_r.append(lasso1.score(X_test,y_test))
break
In [180]:
import matplotlib.pyplot as plt
plt.plot(alpha_l,ols_r,label="linear regression")
plt.plot(alpha_l,ridge_r,label="ridge")
plt.plot(alpha_l,lasso_r,label="lasso")
plt.xlabel("alpha")
plt.ylabel("$R^{2}$")
plt.title("the relation of R squared with alpha")
plt.legend()
plt.show()
In [ ]:
In [ ]: