# -*- -*-
"""
Author: <NAME>
This is multivariate linear regression implementation from scratch
"""
import matplotlib.pyplot as plt
import numpy
## Statistical Function
def list_multiplication(dm1,dm2):
	if length(dm1) == length(dm2):
		return [b*c for b,c in zip(dm1,dm2)]
	elif length(dm1) == 1 or length(dm2) == 1:
		if length(dm1) == 1:
			r = dm1[0]
			c = dm2
		elif length(dm2) == 1:
			r = dm2[0]
			c = dm1
		return [i*r for i in c]
	else:
		print("shape is not same for list multiplication")
		raise ValueError

def ArithmeticMean(li=[],lower=[],upper=[],frequency=[]):
	if li == []:
		for i in range(length(lower)):
			midpoint =(lower[i]+upper[i])/2
			li = appends(li,midpoint)
	else:
		del lower
		del upper
		del frequency
		frequency = []
	
	if frequency == []:
		for i in range(length(li)):
			frequency = appends(frequency,1)
	sumn = 0
	iteration = 0
	for i in range(length(li)):
		sumn += (li[i]*frequency[i])
		iteration  += frequency[i]
	return float(sumn)/iteration

def sample_standard_deviation(li):
	return (sum([(i - ArithmeticMean(li))**2 for i in li])/(length(li)-1))**(1/float(2))
## Statistical Function ends

## String Function
def length(li):
	iteration = 0
	for c in li:
		iteration += 1
	return iteration

def split(string,spliting_char = ','):
	#Spliting Functions by comma, or character. Default spliting character is comma.
	word = ""
	li = []
	iteration = 0

	for c in string:
		if c != spliting_char:
			word += c
			#if c == '':
			#	c = null_string
		else:			
			li = appends(li,word)
			word = ""
		iteration += 1
	#if word != "":
	li = appends(li, word)
	return li

def strip(string,strip_chars = [" "]):
	iters = True
	iteration = 0
	result = ""
	rstat = False
	for c in string:
		
		for d in strip_chars:
			if c != d:
				rstat = True
			else:
				rstat = False
				break

		if rstat == True:
			result += c
			rstat = False
		

		iteration += 1
	return result

def appends(lst, obj, index = -2):
	if index == -2:# -2 for accepting 0 value to be passed as it represents begining of the array
		index = length(lst)

	if type(obj) is list:
		return lst[:index] + obj + lst[index:]
	else:
		return lst[:index] + [obj] + lst[index:]

def conv_type(obj,type_var):
	dts = ["int","float","str"]
	st = 0
	for c in dts:
		if type_var == c:
			st = 1
			break

	if st != 1:
		raise Exception('No avaiable conversion type passed')

	if type(obj) is list:
		#print("list")
		pass
	elif type(obj) is str:
		print("string")
	elif type(obj) is int:
		print("Integer")
	elif type(obj) is float:
		print("Float")
	else:
		print("else %s" % type(obj))

	lists = []
	callables = eval(type_var)
	for c in obj:
		try:
			lists = appends(lists,callables(c))
		except ValueError:
			lists = appends(lists,c)

	return lists
## String Function ends


## Data Manipulation
def getIndex(li,val):
	#indexes = [i for i,x in enumerate(li) if x == val]
	#indexes[-1]
	iteration = 0
	for c in li:
		if(val == li[iteration]):
			return iteration
		iteration += 1
	return False
def reference_reverse_normalize(ypure,y_pred):
	y_PRED = []
	ystd = sample_standard_deviation(ypure)
	ymean = ArithmeticMean(ypure) 
	for c in range(length(y_pred)):
		y_reverse = y_pred[c]* ystd + ymean
		y_PRED.append(y_reverse)
	return y_PRED


def df_size(df):#obs
	row = length(df[0])
	column = length(df)
	return row, column


def transpose(dm):
	n,m = df_size(dm)
	dm_n = create_dataframe(m,n)
	for c in range(m):
		it = 0
		for b in dm[c]:
			#print("dm_n[%s][%s] = %s" % (it,c,b))
			dm_n[it][c] = b
			it += 1
	return dm_n

def create_dataframe(m,n):#obs
	'''
	df = []
	
		
	df_r = []
	for i in range(m):
		df_r = appends(df_r,0)

	for c in range(n):
		df = appends(df,[df_r])
	'''	
	df=[]
	df = [[None]*m for _ in range(n)]
	return df



class DataFrame(object):
	'''This is Onnorokom General Library'''    
	def __init__(self, columns=[], dataframe = ['0']):
		#sequence checked
		self.dataframe = dataframe
		self.shape = self.framesize
		#self.T = self.trans
		self.columns = columns
		
		if self.dataframe != ['0']:
			self.shape = self.framesize
			self.T = self.trans
			if columns == []:
				self.columns = self.erase_col_names()
			

	def __del__(self):
		classname = self.__class__.__name__
		#print ('%s class destroyed' % (classname))

	def __str__(self):
		#return str(self.columns) + str(self.dataframe)
		#print(self.dataframe) #List representation
		strs = "Dataframe Representation\n"
		
		def str_sp(strs,space=10):
			
			for c in range(space - length(strs)):
				strs += " "
			return strs

		for c in self.columns:
			strs += str_sp(c)
		strs += "\n"
		for c in range( length(self.dataframe[0]) ):
			for d in self.dataframe:
				strs += str_sp( str(d[c]) )
				
			strs += "\n"
		return strs
	def __getitem__(self,name):
		if type(name) == int:
			
			return self.dataframe[name]
		elif type(name) == str:
			if name.isdigit() == True:
				return self[int(name)]
			
			return self.dataframe[getIndex(self.columns,name)]
		elif isinstance(name, slice):
			return self.dataframe[name]
	def __iter__(self):
	    return iter(self.columns)
        
	def normalize(self,change_self=False):
		#we need to normalize the features using mean normalization
		df = []
		for c in self.dataframe:
			mean= ArithmeticMean(c) 
			std = sample_standard_deviation(c)
			_ =[ (a - mean)/std for a in c]
			df = appends(df,[_])
		if change_self == True:
			self.dataframe = df
		return df
	def conv_type(self,var_type,change_self=False):
		callables = eval(var_type)
		df = []
		col = []
		for c in self.dataframe:
			for i in c:

				col = appends(col,callables(i))
			df = appends(df,[col])
			col = []

		if change_self == True:
			self.dataframe = df
		return df
    	
	def ix(self):
		pass
	def iloc(self):
		pass
	def row(self,rowindex):
		row = []
		for c in self.columns:
			row.append(self[c][rowindex])
		return row
	def new(self,m,n,elm=''):
		if elm == '':
			elm = None
		df=[]
		df = [[elm]*m for _ in range(n)]
		#if change_self == True:
		return self.set_object(df)
	def concat(self,dm1,dm2,axis=0):
		#axis
		#[0 - row merge]
		#[1 - column merge]
		dm = []
		m,n = dm1.framesize
		x,y = dm2.framesize
		b = dm1.tolist
		d = dm2.tolist
		if axis == 0:
			if n != y:
				print('ValueError: all the input array dimensions except for the concatenation axis must match exactly')
				raise ValueError
			for c,a in zip(b,d):
				dm = appends(dm,[c+a])
		elif axis == 1:
			if m != x:
				print('ValueError: all the input array dimensions except for the concatenation axis must match exactly')
				raise ValueError
			
			for c in b:###
				dm = appends(dm,[c])
			
			for c in d:###
				dm = appends(dm,[c])
		return self.set_object(dm)
	def transpose(self,change_self=False):
		selfs = self.__class__()
		dm = self.dataframe
		n,m = self.size()
		dm_n = create_dataframe(m,n)
		for c in range(m):
			it = 0
			for b in dm[c]:
				#print("dm_n[%s][%s] = %s" % (it,c,b))
				dm_n[it][c] = b
				it += 1
		if change_self == True:
			self.dataframe = dm_n
			self.erase_col_names()
			#self.T = dm_n #previous_code 5-4-18
			#self.T = self.trans #previous_code 5-4-18
		
		#sequence for returing self after dataframe calculation
		selfs.dataframe	= dm_n
		selfs.shape = selfs.framesize
		selfs.columns = selfs.erase_col_names()
		return selfs
		#sequence for returing self after dataframe calculation
	def erase_col_names(self):
		self.columns = [str(c) for c in range(length(self.dataframe))]
		return self.columns
	def columns(self):
		return [ c for c in self.columns]
	def size(self,ob=[]):
		if ob == []:
			return length(self.dataframe[0]),length(self.dataframe)
		else:
			return length(ob[0]),length(ob)
	def read_csv(self,input_file,columns=[]):
		with open(input_file,'r') as file:#Taking file input as stream, handly for iterators and reading stream object saves memory losses
			data = file.readlines()#reading line by line

		first_line = split( strip(data[0],[' ','\n']) ,",")


		header = [c for c in range(0,length(first_line))]			
		
		if first_line[0].isdigit() == False:
			self.columns = first_line
			del data[0]
		else:
			self.columns = conv_type(header,"str")

		df = [[] for d in header]

		for c in data:
			line = split( strip(c,[' ','\n']) ,',')
			for d in header:
				df[d] = appends(df[d],line[d])

		if columns == []:
			columns = self.columns
		else:
			self.columns = columns
		
		#sequence for returing self after dataframe assigning
		self.dataframe = df
		self.shape = self.framesize
		self.T = self.trans
		#sequence for returing self after dataframe assigning

		return self
	def __sub__(self,dm2):
		dm1 = self
		selfs = self.__class__()
		m,n = dm1.shape
		x,y = dm2.shape
		dm=[]
		a = dm1.tolist
		b = dm2.tolist
		
		if m == x and n == y:
			j = 0
			for c in range(n):
				i = 0
				col = []
				for r in range(m):
					si = a[j][i] - b[j][i]
					i+=1
					col.append(si)
				j+=1	
				dm.append(col)
			return self.set_object(dm)
		else:
			print("Matrice Shape is not same for substract",dm1.shape,dm2.shape)
			raise ValueError
	def __add__(self,dm2):
		dm1 = self
		selfs = self.__class__()
		m,n = dm1.shape
		x,y = dm2.shape
		dm=[]
		a = dm1.tolist
		b = dm2.tolist
		
		if m == x and n == y:
			j = 0
			for c in range(n):
				i = 0
				col = []
				for r in range(m):
					si = a[j][i] + b[j][i]
					i+=1
					col.append(si)
				j+=1	
				dm.append(col)
			return self.set_object(dm)
		else:
			print("Matrice Shape is not same for substract",dm1.shape,dm2.shape)
			raise ValueError
	def __gt__(self, other):
		pass
	def __lt__(self, other):
		pass
	def __ge__(self, other):
		pass
	def __le__(self, other):
		pass
	def two2oneD(self):
		if self.shape[0] == 1:
			return self.T[0]
		elif self.shape[1] == 1:
			return self[0]
		else:
			print("Column/row != 1, can not converted to 1D list")

	def dot(self,dm1,dm2):
		a,b=dm1.shape
		m,n=dm2.shape
		if(b==m):
			dm_n = []
			it = 0
			for c in dm2.tolist:
				col = []
				for i in (dm1.T).tolist:
					col = appends(  col,sum( list_multiplication(i,c) )  )
				dm_n = appends(dm_n,[col])
				it+=1
			return self.set_object(dm_n)
		else:
			print("Shape is not same for matrice multiplication -> ",dm1.shape,dm2.shape)
			raise ValueError
	def __mul__(self,dm2):
		dm1 = self
		#used inverse dataframe to convert numpy array representation
		#then used numpy broadcasting
		c1 = (dm1.shape[0]==dm2.shape[0]) and (dm1.shape[1] == 1 or dm2.shape[1] == 1)
		c2 = (dm1.shape[1]==dm2.shape[1]) and (dm1.shape[0] == 1 or dm2.shape[0] == 1)
		if dm1.shape == dm2.shape or c1:
			dm = []
			dm1 = (dm1.T).tolist
			dm2 = (dm2.T).tolist
			for r1,r2 in zip(dm1,dm2):
				dm.append(list_multiplication(r1,r2))
			#they are equal, or
			#one of them is 1
			return self.set_object(transpose(dm))
		elif c2:
			dm = []
			dm1 = (dm1.T).tolist
			dm2 = (dm2.T).tolist
			for r1 in dm1:
				dm = [list_multiplication(r1,r2) for r2 in dm2]
			#they are equal, or
			#one of them is 1
			return self.set_object(transpose(dm))
		else:
			print("cross is not allowed")
			raise ValueError
	def sum(self,axis=0):
		if axis == 0:
			dm = [[sum(c)] for c in self.tolist]
		elif axis == 1:
			dm = [[sum(self.row(d)) for d in range(self.shape[0])]]
		return self.set_object(dm)
		
	def sum_np(self,axis=0):
		return self.sum(axis).two2oneD()
	def __float__(self):
		if self.shape == (1,1):
			return self[0][0]
	def __pow__(self,power):
		dm = [ [r**power for r in c] for c in self.tolist]
		return self.set_object(dm)
	def dftolist(self):
		return self.dataframe
	def dataA(self,dataframe):
		self.dataframe = dataframe
		return self
	def set_object(self,dm):
		selfs = self.__class__()
		#sequence for returing self after dataframe calculation
		selfs.dataframe	= dm
		selfs.shape = selfs.framesize
		selfs.T = selfs.trans
		selfs.columns = selfs.erase_col_names()
		return selfs
		#sequence for returing self after dataframe calculation

	framesize = property(size)
	tolist = property(dftolist)
	trans = property(transpose)
	prop_var =property(set_object)


## ML Function
def minResidual(pure , pred):
	"""returns minimum error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)

	return min(E)
def meanResidual(pure , pred):
	"""returns average error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)
	import numpy as np
	return np.mean(E)

def maxResidual(pure , pred):
	"""returns maximum error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)

	return max(E)

def give_time_series(x,y):
	"""Rearrange X,Y value pairs or points according to X's order"""
	xall = []
	yall = []
	for x1,y1 in sorted(zip(x,y)):
		xall.append(x1)
		yall.append(y1)
	return (xall,yall)

def plot_error_distance(x,y_pred,y_actual):
	"""Plot error distance or residual"""
	for [a,b,c] in [ [x,y_r,y_p] for x,y_r,y_p in zip(x,y_pred,y_actual) ]:
		plt.plot([a,a],[b,c],color='y',label='residual')

def MLVR(XDATA,YDATA,xreference=0,residual=1,xlabel='',ylabel='',title='',alpha = 0.01,iters = 1000,plot=1):	
	"""Does Multivariant Linear Regression
	properties:
		XDATA = The Feature Dataframe
		YDATA = The Target Dataframe
		xreference = 1/0 -> The column index in XDATA for ploting graph
		xlabel = Label for X in Graph
		ylabel = Label for Y in Graph
		title = title for graph]
		alpha = Learning rate for model
		iters = the number of iteration to train the model
	"""
	XDATA.conv_type('float',change_self=True)
	xpure = XDATA[xreference]
	XDATA.normalize(change_self=True)

	YDATA.conv_type('float',change_self=True)
	ypure = YDATA.tolist[0]
	YDATA.normalize(change_self=True)

	X=XDATA
	y=YDATA

	df =DataFrame()
	ones = df.new(X.shape[0],1,elm=1.)
	X = df.concat(ones,X,axis=1)
	
	theta = DataFrame().new(1,length(X.columns),elm=0.)
	
	def computeCost(X,y,theta):
		dot_product = DataFrame().dot(X,theta.T)	
		return float(    (  (dot_product - y)**2  ).sum(axis=0)    )/(2 * X.shape[0])
	
	def gradientDescent(X,y,theta,iters,alpha):
		#cost = np.zeros(iters)
		cost = []
		for i in range(iters):			
			dot_product = DataFrame().dot(X,theta.T)
			derivative = DataFrame(dataframe = [[(alpha/X.shape[0])]])  *  ( X*(dot_product - y) ).sum(axis = 0 ) 
			theta = theta - derivative			
			cost.append( computeCost(X, y, theta) ) #cost[i] = computeCost(X, y, theta)
		return theta,cost

	def print_equation(g):
		stra = "Estimated equation, y = %s"%g[0]
		g0 = g[0]
		del g[0]
		for c in range(length(g)):
			stra += " + %s*x%s"%(g[c],c+1)
		print(stra)

	def predict_li(XDATA,g):
		g0 = g[0]
		del g[0]
		y_pred = []			
		for row in range(XDATA.shape[0]):
			suma = 0
			suma += sum(list_multiplication( g , XDATA.row(row) ) )
			yres = g0 + suma
			y_pred.append(yres)	
		return y_pred

	g,cost = gradientDescent(X,y,theta,iters,alpha)
	finalCost = computeCost(X,y,g)
	#g = g.T
	g = g.two2oneD()
	print("Thetas = %s"%g) #print("cost = ",cost)
	print("finalCost = %s" % finalCost)
	gN = g[:]
	print_equation(gN)

	gN = g[:]	
	y_pred = predict_li(XDATA,gN)	
	
	y_PRED = reference_reverse_normalize(ypure,y_pred)
	emin,emean,emax = minResidual(ypure , y_PRED),meanResidual(ypure , y_PRED),maxResidual(ypure , y_PRED)
	
	print("Min,Mean,Max residual = %s, %s, %s"%( emin,emean,emax ) )
	print("Residual Min - Max Range = %s"%(emax-emin))
	print("Residual range percentage = %s" %((emax-emin)/(max(ypure) - min(ypure))) )
	
	print("Residual mean percentage = %s" %(emean/ArithmeticMean(ypure)) )




	#-- If finalcost is lowest mean Residual or mean Error distance also will be lowest


	#y_pred = [g[0] + g[1]*my_data[0][c] + g[2]*my_data[1][c] for c in range(my_data.shape[0])]
	y_actual = YDATA.tolist[0]
	x = XDATA[xreference]

	if plot == 1:
		fig, ax = plt.subplots()  
		ax.plot(numpy.arange(iters), cost, 'r')  
		ax.set_xlabel('Iterations')  
		ax.set_ylabel('Cost')  
		ax.set_title('Error vs. Training Epoch')  
		plt.show()

		x_a, y_a = give_time_series(xpure,y_PRED)#give_time_series(x,y_pred)
		plt.plot(x_a,y_a,color='r',marker='.',label='Prediction')

		x_a, y_a = give_time_series(xpure,ypure)#give_time_series(x,y_actual)
		plt.plot(x_a,y_a,color='g',marker='.',label='Real')
		
		if residual == 1:
			plot_error_distance(xpure,y_PRED,ypure)

		plt.xlabel(xlabel)
		plt.ylabel(ylabel)
		plt.title(title)
		plt.legend()
		plt.show()
	else:
		print('plot off')
		
	return finalCost


def main():	
	my_data = DataFrame().read_csv('sample_inputs/home.txt',columns=["size","bedroom","price"])
	
	XDATA = DataFrame(dataframe= my_data[0:2],columns=['size','bedroom'])
	YDATA = DataFrame(dataframe= [my_data[2]])
	
	MLVR(XDATA,YDATA,xreference=0,residual=1,xlabel='serial,length',ylabel='views',title='Youtube View MLVR',alpha = 0.01,iters = 1000)

if __name__ == "__main__":
    main()

# -*- -*-
"""
Author: <NAME>
This is multivariate linear regression implementation from scratch
"""
import matplotlib.pyplot as plt
import numpy
## Statistical Function
def list_multiplication(dm1,dm2):
	if length(dm1) == length(dm2):
		return [b*c for b,c in zip(dm1,dm2)]
	elif length(dm1) == 1 or length(dm2) == 1:
		if length(dm1) == 1:
			r = dm1[0]
			c = dm2
		elif length(dm2) == 1:
			r = dm2[0]
			c = dm1
		return [i*r for i in c]
	else:
		print("shape is not same for list multiplication")
		raise ValueError

def ArithmeticMean(li=[],lower=[],upper=[],frequency=[]):
	if li == []:
		for i in range(length(lower)):
			midpoint =(lower[i]+upper[i])/2
			li = appends(li,midpoint)
	else:
		del lower
		del upper
		del frequency
		frequency = []
	
	if frequency == []:
		for i in range(length(li)):
			frequency = appends(frequency,1)
	sumn = 0
	iteration = 0
	for i in range(length(li)):
		sumn += (li[i]*frequency[i])
		iteration  += frequency[i]
	return float(sumn)/iteration

def sample_standard_deviation(li):
	return (sum([(i - ArithmeticMean(li))**2 for i in li])/(length(li)-1))**(1/float(2))
## Statistical Function ends

## String Function
def length(li):
	iteration = 0
	for c in li:
		iteration += 1
	return iteration

def split(string,spliting_char = ','):
	#Spliting Functions by comma, or character. Default spliting character is comma.
	word = ""
	li = []
	iteration = 0

	for c in string:
		if c != spliting_char:
			word += c
			#if c == '':
			#	c = null_string
		else:			
			li = appends(li,word)
			word = ""
		iteration += 1
	#if word != "":
	li = appends(li, word)
	return li

def strip(string,strip_chars = [" "]):
	iters = True
	iteration = 0
	result = ""
	rstat = False
	for c in string:
		
		for d in strip_chars:
			if c != d:
				rstat = True
			else:
				rstat = False
				break

		if rstat == True:
			result += c
			rstat = False
		

		iteration += 1
	return result

def appends(lst, obj, index = -2):
	if index == -2:# -2 for accepting 0 value to be passed as it represents begining of the array
		index = length(lst)

	if type(obj) is list:
		return lst[:index] + obj + lst[index:]
	else:
		return lst[:index] + [obj] + lst[index:]

def conv_type(obj,type_var):
	dts = ["int","float","str"]
	st = 0
	for c in dts:
		if type_var == c:
			st = 1
			break

	if st != 1:
		raise Exception('No avaiable conversion type passed')

	if type(obj) is list:
		#print("list")
		pass
	elif type(obj) is str:
		print("string")
	elif type(obj) is int:
		print("Integer")
	elif type(obj) is float:
		print("Float")
	else:
		print("else %s" % type(obj))

	lists = []
	callables = eval(type_var)
	for c in obj:
		try:
			lists = appends(lists,callables(c))
		except ValueError:
			lists = appends(lists,c)

	return lists
## String Function ends


## Data Manipulation
def getIndex(li,val):
	#indexes = [i for i,x in enumerate(li) if x == val]
	#indexes[-1]
	iteration = 0
	for c in li:
		if(val == li[iteration]):
			return iteration
		iteration += 1
	return False
def reference_reverse_normalize(ypure,y_pred):
	y_PRED = []
	ystd = sample_standard_deviation(ypure)
	ymean = ArithmeticMean(ypure) 
	for c in range(length(y_pred)):
		y_reverse = y_pred[c]* ystd + ymean
		y_PRED.append(y_reverse)
	return y_PRED


def df_size(df):#obs
	row = length(df[0])
	column = length(df)
	return row, column


def transpose(dm):
	n,m = df_size(dm)
	dm_n = create_dataframe(m,n)
	for c in range(m):
		it = 0
		for b in dm[c]:
			#print("dm_n[%s][%s] = %s" % (it,c,b))
			dm_n[it][c] = b
			it += 1
	return dm_n

def create_dataframe(m,n):#obs
	'''
	df = []
	
		
	df_r = []
	for i in range(m):
		df_r = appends(df_r,0)

	for c in range(n):
		df = appends(df,[df_r])
	'''	
	df=[]
	df = [[None]*m for _ in range(n)]
	return df



class DataFrame(object):
	'''This is Onnorokom General Library'''    
	def __init__(self, columns=[], dataframe = ['0']):
		#sequence checked
		self.dataframe = dataframe
		self.shape = self.framesize
		#self.T = self.trans
		self.columns = columns
		
		if self.dataframe != ['0']:
			self.shape = self.framesize
			self.T = self.trans
			if columns == []:
				self.columns = self.erase_col_names()
			

	def __del__(self):
		classname = self.__class__.__name__
		#print ('%s class destroyed' % (classname))

	def __str__(self):
		#return str(self.columns) + str(self.dataframe)
		#print(self.dataframe) #List representation
		strs = "Dataframe Representation\n"
		
		def str_sp(strs,space=10):
			
			for c in range(space - length(strs)):
				strs += " "
			return strs

		for c in self.columns:
			strs += str_sp(c)
		strs += "\n"
		for c in range( length(self.dataframe[0]) ):
			for d in self.dataframe:
				strs += str_sp( str(d[c]) )
				
			strs += "\n"
		return strs
	def __getitem__(self,name):
		if type(name) == int:
			
			return self.dataframe[name]
		elif type(name) == str:
			if name.isdigit() == True:
				return self[int(name)]
			
			return self.dataframe[getIndex(self.columns,name)]
		elif isinstance(name, slice):
			return self.dataframe[name]
	def __iter__(self):
	    return iter(self.columns)
        
	def normalize(self,change_self=False):
		#we need to normalize the features using mean normalization
		df = []
		for c in self.dataframe:
			mean= ArithmeticMean(c) 
			std = sample_standard_deviation(c)
			_ =[ (a - mean)/std for a in c]
			df = appends(df,[_])
		if change_self == True:
			self.dataframe = df
		return df
	def conv_type(self,var_type,change_self=False):
		callables = eval(var_type)
		df = []
		col = []
		for c in self.dataframe:
			for i in c:

				col = appends(col,callables(i))
			df = appends(df,[col])
			col = []

		if change_self == True:
			self.dataframe = df
		return df
    	
	def ix(self):
		pass
	def iloc(self):
		pass
	def row(self,rowindex):
		row = []
		for c in self.columns:
			row.append(self[c][rowindex])
		return row
	def new(self,m,n,elm=''):
		if elm == '':
			elm = None
		df=[]
		df = [[elm]*m for _ in range(n)]
		#if change_self == True:
		return self.set_object(df)
	def concat(self,dm1,dm2,axis=0):
		#axis
		#[0 - row merge]
		#[1 - column merge]
		dm = []
		m,n = dm1.framesize
		x,y = dm2.framesize
		b = dm1.tolist
		d = dm2.tolist
		if axis == 0:
			if n != y:
				print('ValueError: all the input array dimensions except for the concatenation axis must match exactly')
				raise ValueError
			for c,a in zip(b,d):
				dm = appends(dm,[c+a])
		elif axis == 1:
			if m != x:
				print('ValueError: all the input array dimensions except for the concatenation axis must match exactly')
				raise ValueError
			
			for c in b:###
				dm = appends(dm,[c])
			
			for c in d:###
				dm = appends(dm,[c])
		return self.set_object(dm)
	def transpose(self,change_self=False):
		selfs = self.__class__()
		dm = self.dataframe
		n,m = self.size()
		dm_n = create_dataframe(m,n)
		for c in range(m):
			it = 0
			for b in dm[c]:
				#print("dm_n[%s][%s] = %s" % (it,c,b))
				dm_n[it][c] = b
				it += 1
		if change_self == True:
			self.dataframe = dm_n
			self.erase_col_names()
			#self.T = dm_n #previous_code 5-4-18
			#self.T = self.trans #previous_code 5-4-18
		
		#sequence for returing self after dataframe calculation
		selfs.dataframe	= dm_n
		selfs.shape = selfs.framesize
		selfs.columns = selfs.erase_col_names()
		return selfs
		#sequence for returing self after dataframe calculation
	def erase_col_names(self):
		self.columns = [str(c) for c in range(length(self.dataframe))]
		return self.columns
	def columns(self):
		return [ c for c in self.columns]
	def size(self,ob=[]):
		if ob == []:
			return length(self.dataframe[0]),length(self.dataframe)
		else:
			return length(ob[0]),length(ob)
	def read_csv(self,input_file,columns=[]):
		with open(input_file,'r') as file:#Taking file input as stream, handly for iterators and reading stream object saves memory losses
			data = file.readlines()#reading line by line

		first_line = split( strip(data[0],[' ','\n']) ,",")


		header = [c for c in range(0,length(first_line))]			
		
		if first_line[0].isdigit() == False:
			self.columns = first_line
			del data[0]
		else:
			self.columns = conv_type(header,"str")

		df = [[] for d in header]

		for c in data:
			line = split( strip(c,[' ','\n']) ,',')
			for d in header:
				df[d] = appends(df[d],line[d])

		if columns == []:
			columns = self.columns
		else:
			self.columns = columns
		
		#sequence for returing self after dataframe assigning
		self.dataframe = df
		self.shape = self.framesize
		self.T = self.trans
		#sequence for returing self after dataframe assigning

		return self
	def __sub__(self,dm2):
		dm1 = self
		selfs = self.__class__()
		m,n = dm1.shape
		x,y = dm2.shape
		dm=[]
		a = dm1.tolist
		b = dm2.tolist
		
		if m == x and n == y:
			j = 0
			for c in range(n):
				i = 0
				col = []
				for r in range(m):
					si = a[j][i] - b[j][i]
					i+=1
					col.append(si)
				j+=1	
				dm.append(col)
			return self.set_object(dm)
		else:
			print("Matrice Shape is not same for substract",dm1.shape,dm2.shape)
			raise ValueError
	def __add__(self,dm2):
		dm1 = self
		selfs = self.__class__()
		m,n = dm1.shape
		x,y = dm2.shape
		dm=[]
		a = dm1.tolist
		b = dm2.tolist
		
		if m == x and n == y:
			j = 0
			for c in range(n):
				i = 0
				col = []
				for r in range(m):
					si = a[j][i] + b[j][i]
					i+=1
					col.append(si)
				j+=1	
				dm.append(col)
			return self.set_object(dm)
		else:
			print("Matrice Shape is not same for substract",dm1.shape,dm2.shape)
			raise ValueError
	def __gt__(self, other):
		pass
	def __lt__(self, other):
		pass
	def __ge__(self, other):
		pass
	def __le__(self, other):
		pass
	def two2oneD(self):
		if self.shape[0] == 1:
			return self.T[0]
		elif self.shape[1] == 1:
			return self[0]
		else:
			print("Column/row != 1, can not converted to 1D list")

	def dot(self,dm1,dm2):
		a,b=dm1.shape
		m,n=dm2.shape
		if(b==m):
			dm_n = []
			it = 0
			for c in dm2.tolist:
				col = []
				for i in (dm1.T).tolist:
					col = appends(  col,sum( list_multiplication(i,c) )  )
				dm_n = appends(dm_n,[col])
				it+=1
			return self.set_object(dm_n)
		else:
			print("Shape is not same for matrice multiplication -> ",dm1.shape,dm2.shape)
			raise ValueError
	def __mul__(self,dm2):
		dm1 = self
		#used inverse dataframe to convert numpy array representation
		#then used numpy broadcasting
		c1 = (dm1.shape[0]==dm2.shape[0]) and (dm1.shape[1] == 1 or dm2.shape[1] == 1)
		c2 = (dm1.shape[1]==dm2.shape[1]) and (dm1.shape[0] == 1 or dm2.shape[0] == 1)
		if dm1.shape == dm2.shape or c1:
			dm = []
			dm1 = (dm1.T).tolist
			dm2 = (dm2.T).tolist
			for r1,r2 in zip(dm1,dm2):
				dm.append(list_multiplication(r1,r2))
			#they are equal, or
			#one of them is 1
			return self.set_object(transpose(dm))
		elif c2:
			dm = []
			dm1 = (dm1.T).tolist
			dm2 = (dm2.T).tolist
			for r1 in dm1:
				dm = [list_multiplication(r1,r2) for r2 in dm2]
			#they are equal, or
			#one of them is 1
			return self.set_object(transpose(dm))
		else:
			print("cross is not allowed")
			raise ValueError
	def sum(self,axis=0):
		if axis == 0:
			dm = [[sum(c)] for c in self.tolist]
		elif axis == 1:
			dm = [[sum(self.row(d)) for d in range(self.shape[0])]]
		return self.set_object(dm)
		
	def sum_np(self,axis=0):
		return self.sum(axis).two2oneD()
	def __float__(self):
		if self.shape == (1,1):
			return self[0][0]
	def __pow__(self,power):
		dm = [ [r**power for r in c] for c in self.tolist]
		return self.set_object(dm)
	def dftolist(self):
		return self.dataframe
	def dataA(self,dataframe):
		self.dataframe = dataframe
		return self
	def set_object(self,dm):
		selfs = self.__class__()
		#sequence for returing self after dataframe calculation
		selfs.dataframe	= dm
		selfs.shape = selfs.framesize
		selfs.T = selfs.trans
		selfs.columns = selfs.erase_col_names()
		return selfs
		#sequence for returing self after dataframe calculation

	framesize = property(size)
	tolist = property(dftolist)
	trans = property(transpose)
	prop_var =property(set_object)


## ML Function
def minResidual(pure , pred):
	"""returns minimum error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)

	return min(E)
def meanResidual(pure , pred):
	"""returns average error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)
	import numpy as np
	return np.mean(E)

def maxResidual(pure , pred):
	"""returns maximum error distance or residual"""
	E = []
	for c in range(length(pure)):
		absolute_distance = abs(pure[c] - pred[c])
		E.append(absolute_distance)

	return max(E)

def give_time_series(x,y):
	"""Rearrange X,Y value pairs or points according to X's order"""
	xall = []
	yall = []
	for x1,y1 in sorted(zip(x,y)):
		xall.append(x1)
		yall.append(y1)
	return (xall,yall)

def plot_error_distance(x,y_pred,y_actual):
	"""Plot error distance or residual"""
	for [a,b,c] in [ [x,y_r,y_p] for x,y_r,y_p in zip(x,y_pred,y_actual) ]:
		plt.plot([a,a],[b,c],color='y',label='residual')

def MLVR(XDATA,YDATA,xreference=0,residual=1,xlabel='',ylabel='',title='',alpha = 0.01,iters = 1000,plot=1):	
	"""Does Multivariant Linear Regression
	properties:
		XDATA = The Feature Dataframe
		YDATA = The Target Dataframe
		xreference = 1/0 -> The column index in XDATA for ploting graph
		xlabel = Label for X in Graph
		ylabel = Label for Y in Graph
		title = title for graph]
		alpha = Learning rate for model
		iters = the number of iteration to train the model
	"""
	XDATA.conv_type('float',change_self=True)
	xpure = XDATA[xreference]
	XDATA.normalize(change_self=True)

	YDATA.conv_type('float',change_self=True)
	ypure = YDATA.tolist[0]
	YDATA.normalize(change_self=True)

	X=XDATA
	y=YDATA

	df =DataFrame()
	ones = df.new(X.shape[0],1,elm=1.)
	X = df.concat(ones,X,axis=1)
	
	theta = DataFrame().new(1,length(X.columns),elm=0.)
	
	def computeCost(X,y,theta):
		dot_product = DataFrame().dot(X,theta.T)	
		return float(    (  (dot_product - y)**2  ).sum(axis=0)    )/(2 * X.shape[0])
	
	def gradientDescent(X,y,theta,iters,alpha):
		#cost = np.zeros(iters)
		cost = []
		for i in range(iters):			
			dot_product = DataFrame().dot(X,theta.T)
			derivative = DataFrame(dataframe = [[(alpha/X.shape[0])]])  *  ( X*(dot_product - y) ).sum(axis = 0 ) 
			theta = theta - derivative			
			cost.append( computeCost(X, y, theta) ) #cost[i] = computeCost(X, y, theta)
		return theta,cost

	def print_equation(g):
		stra = "Estimated equation, y = %s"%g[0]
		g0 = g[0]
		del g[0]
		for c in range(length(g)):
			stra += " + %s*x%s"%(g[c],c+1)
		print(stra)

	def predict_li(XDATA,g):
		g0 = g[0]
		del g[0]
		y_pred = []			
		for row in range(XDATA.shape[0]):
			suma = 0
			suma += sum(list_multiplication( g , XDATA.row(row) ) )
			yres = g0 + suma
			y_pred.append(yres)	
		return y_pred

	g,cost = gradientDescent(X,y,theta,iters,alpha)
	finalCost = computeCost(X,y,g)
	#g = g.T
	g = g.two2oneD()
	print("Thetas = %s"%g) #print("cost = ",cost)
	print("finalCost = %s" % finalCost)
	gN = g[:]
	print_equation(gN)

	gN = g[:]	
	y_pred = predict_li(XDATA,gN)	
	
	y_PRED = reference_reverse_normalize(ypure,y_pred)
	emin,emean,emax = minResidual(ypure , y_PRED),meanResidual(ypure , y_PRED),maxResidual(ypure , y_PRED)
	
	print("Min,Mean,Max residual = %s, %s, %s"%( emin,emean,emax ) )
	print("Residual Min - Max Range = %s"%(emax-emin))
	print("Residual range percentage = %s" %((emax-emin)/(max(ypure) - min(ypure))) )
	
	print("Residual mean percentage = %s" %(emean/ArithmeticMean(ypure)) )




	#-- If finalcost is lowest mean Residual or mean Error distance also will be lowest


	#y_pred = [g[0] + g[1]*my_data[0][c] + g[2]*my_data[1][c] for c in range(my_data.shape[0])]
	y_actual = YDATA.tolist[0]
	x = XDATA[xreference]

	if plot == 1:
		fig, ax = plt.subplots()  
		ax.plot(numpy.arange(iters), cost, 'r')  
		ax.set_xlabel('Iterations')  
		ax.set_ylabel('Cost')  
		ax.set_title('Error vs. Training Epoch')  
		plt.show()

		x_a, y_a = give_time_series(xpure,y_PRED)#give_time_series(x,y_pred)
		plt.plot(x_a,y_a,color='r',marker='.',label='Prediction')

		x_a, y_a = give_time_series(xpure,ypure)#give_time_series(x,y_actual)
		plt.plot(x_a,y_a,color='g',marker='.',label='Real')
		
		if residual == 1:
			plot_error_distance(xpure,y_PRED,ypure)

		plt.xlabel(xlabel)
		plt.ylabel(ylabel)
		plt.title(title)
		plt.legend()
		plt.show()
	else:
		print('plot off')
		
	return finalCost


def main():	
	my_data = DataFrame().read_csv('sample_inputs/home.txt',columns=["size","bedroom","price"])
	
	XDATA = DataFrame(dataframe= my_data[0:2],columns=['size','bedroom'])
	YDATA = DataFrame(dataframe= [my_data[2]])
	
	MLVR(XDATA,YDATA,xreference=0,residual=1,xlabel='serial,length',ylabel='views',title='Youtube View MLVR',alpha = 0.01,iters = 1000)

if __name__ == "__main__":
    main()

# -*- -*- Author: <NAME> This is multivariate linear regression implementation from scratch ## Statistical Function ## Statistical Function ends ## String Function #Spliting Functions by comma, or character. Default spliting character is comma. #if c == '': #	c = null_string #if word != "": # -2 for accepting 0 value to be passed as it represents begining of the array #print("list") ## String Function ends ## Data Manipulation #indexes = [i for i,x in enumerate(li) if x == val] #indexes[-1] #obs #print("dm_n[%s][%s] = %s" % (it,c,b)) #obs df = [] 	 		 	df_r = [] 	for i in range(m): 		df_r = appends(df_r,0)  	for c in range(n): 		df = appends(df,[df_r]) This is Onnorokom General Library #sequence checked #self.T = self.trans #print ('%s class destroyed' % (classname)) #return str(self.columns) + str(self.dataframe) #print(self.dataframe) #List representation #we need to normalize the features using mean normalization #if change_self == True: #axis #[0 - row merge] #[1 - column merge] ### ### #print("dm_n[%s][%s] = %s" % (it,c,b)) #self.T = dm_n #previous_code 5-4-18 #self.T = self.trans #previous_code 5-4-18 #sequence for returing self after dataframe calculation #sequence for returing self after dataframe calculation #Taking file input as stream, handly for iterators and reading stream object saves memory losses #reading line by line #sequence for returing self after dataframe assigning #sequence for returing self after dataframe assigning #used inverse dataframe to convert numpy array representation #then used numpy broadcasting #they are equal, or #one of them is 1 #they are equal, or #one of them is 1 #sequence for returing self after dataframe calculation #sequence for returing self after dataframe calculation ## ML Function returns minimum error distance or residual returns average error distance or residual returns maximum error distance or residual Rearrange X,Y value pairs or points according to X's order Plot error distance or residual Does Multivariant Linear Regression 	properties: 		XDATA = The Feature Dataframe 		YDATA = The Target Dataframe 		xreference = 1/0 -> The column index in XDATA for ploting graph 		xlabel = Label for X in Graph 		ylabel = Label for Y in Graph 		title = title for graph] 		alpha = Learning rate for model 		iters = the number of iteration to train the model #cost = np.zeros(iters) #cost[i] = computeCost(X, y, theta) #g = g.T #print("cost = ",cost) #-- If finalcost is lowest mean Residual or mean Error distance also will be lowest #y_pred = [g[0] + g[1]*my_data[0][c] + g[2]*my_data[1][c] for c in range(my_data.shape[0])] #give_time_series(x,y_pred) #give_time_series(x,y_actual)