
def error(x_i, y_i):
	return y_i - predict(x_i, beta)

def squared_error(x_i, y_i, beta):
	return error(x_i, y_i, beta) ** 2

def squared_error_gradient(x_i, y_i, beta):
	"""the gradient (with respect to beta)
	corresponding to the ith squared error term"""
	return [-2 * x_ij * error(x_i, y_i, beta)
		for x_ij in x_i]
	
def multiple_r_squared(x,y,beta):
	sum_of_squared_errors = sum(error(x_i, y_i, beta) ** 2
		for x_i, y_i, in zip(x,y))
	return 1.0 - sum_of_squared_errors / total_sum_of_squares(y)

import random

def bootstrap_sample(data):
	"""randomly samples len(data) elements with replacement"""
	return [random.choice(data) for _ in data]

def bootstrap_statistics(data, stats_fn, num_samples):
	"""evaluates stats_fn on num_samples bootstrap samples from data"""
	return [stats_fn(bootstrap_sample(data))
		for _ in range(num_samples)]

def estimate_sample_beta(sample):
	"""sample is a list of pairs (x_i, y_i)"""
	x_sample, y_sample = zip(*sample) # magic unzipping trick
	return estimate_beta(x_sample, y_sample)

def p_value(beta_hat_j, sigma_hat_j):
	if beta_hat_j > 0:
		return 2*(1 - normal_cdf(beta_hat_j / sigma_hat_j))
	else:
		return 2 * normal_cdf(beta_hat_j / sigma_hat_j)

def ridge_penalty(beta, alpha):
	return alpha * dot(beta[1:], beta[1:])

def squared_error_ridge(x_i, y_i, beta, alpha):
	return error(x_i, y_i, beta) ** 2 + ridge_penalty(beta, alpha)

def ridge_penalty_gradient(beta, alpha):
	return [0] + [2 * alpha * beta_j for beta_j in beta[1:]]
