import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

"""
Explanation: Linear regression using Batch Gradient Descent
Building linear regression from ground up
End of explanation
"""


class linear_regression():
       
    def __init__(self):
        self.weights = None
        self.learning_rate = None
        self.epochs = None
        self.trainx = None
        self.trainy = None
        self.costcurve = []
        print('Status: Model Initialized')
    
    def train(self, trainx, trainy, learning_rate, epochs):
        # np.random.seed(10)
        self.trainx = trainx
        self.trainy = trainy
        self.learning_rate = learning_rate
        self.epochs = epochs
        
        # self.weights = np.random.randn(self.trainx.shape[1]+1)
        self.weights = np.random.uniform(low=0.0, high=inputx.shape[1]**0.5, size=inputx.shape[1]+1)
        self.trainx = np.append(self.trainx,np.ones((self.trainx.shape[0],1)), axis=1)
        
        for epoch in range(epochs):
            output = np.dot(self.trainx, self.weights)
            output = np.reshape(output, (output.shape[0],1))
            error = np.subtract(self.trainy, output)
            total_error = np.sum(error)
            cost = np.mean(error**2)
            self.costcurve.append([epoch+1, cost])
            gradients = (self.learning_rate / self.trainx.shape[0]) * np.dot(error.T, self.trainx)
            gradients = np.reshape(gradients, (gradients.T.shape[0],))
            self.weights += gradients
            print('step:{0} \n cost:{1}'.format(epoch+1, cost))
            
        # return self.weights
    
    def plotCostCurve(self):
        costcurvearray = np.array(self.costcurve)
        plt.plot(costcurvearray[:,0],costcurvearray[:,1])
        plt.xlabel('Epochs')
        plt.ylabel('Cost')
        plt.show()
    
    def predict(self, validatex):
        validatex_new = np.append(validatex,np.ones((validatex.shape[0],1)), axis=1)
        predict = np.dot(validatex_new, self.weights)
        return np.reshape(predict, (predict.shape[0],1))
        
            

"""
Explanation: Our Linear Regression Model
This python class contains our Linear Regression model/algo. We use the OOP's concepts to impart the model's behaviour  to a python class. This class contains methods to train, predict and plot the cost curve.
End of explanation
"""


mpg_data = np.genfromtxt('mpg.txt', delimiter=',', dtype='float')
print(mpg_data.shape)

mpg_data = mpg_data[~np.isnan(mpg_data).any(axis=1)]

inputx = mpg_data[:,1:8]

for i in range(inputx.shape[1]):
    inputx[:,i] = (inputx[:,i]-np.min(inputx[:,i]))/(np.max(inputx[:,i])-np.min(inputx[:,i]))

inputy = np.reshape(mpg_data[:,0],(mpg_data.shape[0],1))

model = linear_regression()
model.train(inputx, inputy, 0.01, 1000)

print(model.weights)

"""
Explanation: Ingesting Sample Data
We are using the MPG dataset from UCI Datasets to test our implementation. http://archive.ics.uci.edu/ml/datasets/Auto+MPG
End of explanation
"""


model.plotCostCurve()

"""
Explanation: Visualizing the Cost per Step
Plotting a cost curve explains to us what is happening in our gradient descent. Whether our model converges or not. It also helps to understand the point after which we need not expend resources to train.
End of explanation
"""


model.predict(inputx)

"""
Explanation: Predicting Values
Here we are using our input dataset to predict the Y with our final model. In actual scenarios there is proper process to do this.
End of explanation
"""


