def chi2_adaptive_binning(features_0,features_1,number_of_splits_list,systematics_fraction=0.0,title = "title", name="name", PLOT = True, DEBUG = False, transform='StandardScalar'):
	"""This function takes in two 2D arrays with all features being columns"""

	max_number_of_splits = np.max(number_of_splits_list)
	#determine how many data points are in each sample
	no_0=features_0.shape[0]
	no_1=features_1.shape[0]

	print("features_0.shape : ", features_0.shape)
	no_dim = features_0.shape[1]
	#Give all samples in file 0 the label 0 and in file 1 the feature 1
	label_0=np.zeros((no_0,1))
	label_1=np.ones((no_1,1))

	#Create an array containing samples and features.
	data_0=np.c_[features_0,label_0]
	data_1=np.c_[features_1,label_1]

	features= np.r_[features_0,features_1]
	labels=    np.r_[label_0,   label_1]

	data=np.r_[data_0,data_1]
	data_same=np.c_[features,labels]

	#print("data : ",data)
	#print("data_same : ", data_same)
	#print("np.sum(data!=data_same) : ",np.sum(data!=data_same))
	assert np.sum(data!=data_same)==0
	assert (no_dim == data.shape[1]-1)

	if no_dim==2:
		plt.scatter(features[:,0],features[:,1], 0.1)
		plt.savefig('test.png')
		plt.clf()

	if transform=='StandardScalar':
		features = preprocessing.scale(features)
		data = np.c_[features,labels]

        if transform=='uniform':
		#data_new2 = data[:,0]
                data_new  = norm_highD_searchsorted(data[:,0])
                for D in range(1,no_dim):
                        temp = norm_highD_searchsorted(data[:,D])
                        data_new = np.c_[data_new,temp]
			#data_new2= np.c_[data_new2,data[:,D]]
		data_new = np.c_[data_new, np.r_[label_0,label_1]]
		#data_new2= np.c_[data_new2,np.r_[label_0,label_1]]

		print("data : ", data)
		data = data_new
		print("data new : ", data)
		#print("data_new2 : ", data_new2)
		#print("np.sum(data!=data_new2) : ",np.sum(data!=data_new2))

	
	np.random.shuffle(data)

	assert (no_dim == data.shape[1]-1)
	labels=data[:,-1]

	X_values= data[:,:-1]
	X_max   = np.amax(data,axis=0)[:-1] 
	X_min   = np.amin(data,axis=0)[:-1]
	X_total_width = (np.subtract(X_max,X_min))
	del data

	if transform=='fill01':
		#Scaling	
		X_values = X_values - X_min[None,:]
		X_values = X_values / X_total_width[None,:]
	
	if True:
		X_min = [0.]*no_dim
		X_total_width = [1.]*no_dim
	#b = X_values[:,0]
	#print("b[b[:]>2].shape[0] : \n", b[b[:]>2].shape[0] )	
	data = np.concatenate((X_values, labels[:,None]), axis=1)

	if no_dim==2:
		plt.scatter(data[:,0],data[:,1],0.1)
		plt.savefig('test_scaled.png')

	#print("X_values.shape : ",X_values.shape)
	starting_boundary = []
	for i in range(no_dim):
		starting_boundary.append([0.0,1.0])
	#Each key has the following stricture: # of splits and for each split if it was closer (a) or further away from (b) the origin. The original bin is "0"
	#For example "2ab" means it is the bin that was closer to the origin for the first split and further away for the second one.
	bin_boundaries_dict = {'0' : np.array(starting_boundary)}
	bin_points_dict = {'0' : data}

	for split_number in range(1,1+max_number_of_splits):
		for bin_key, bin_boundary in bin_boundaries_dict.items():
			if str(split_number-1) in bin_key:
				variances= np.var(bin_points_dict[bin_key][:,:-1], axis=0)
				#print("\nvariances : ", variances)
				dim_to_be_sliced = np.argmax(variances)
				#print("dim_to_be_sliced : ",dim_to_be_sliced)
				#print("bin_points_dict[bin_key] : ",bin_points_dict[bin_key])
				#print("bin_points_dict[bin_key][:,dim_to_be_sliced] : ",bin_points_dict[bin_key][:,dim_to_be_sliced])
				median = np.median(bin_points_dict[bin_key][:,dim_to_be_sliced])
				#print("median : ",median)

				a_bin_boundary, b_bin_boundary = bin_boundary.copy(), bin_boundary.copy()
				#print("a_bin_boundary : ",a_bin_boundary)
				a_bin_boundary[dim_to_be_sliced,1] = median
				b_bin_boundary[dim_to_be_sliced,0] = median
				bin_boundaries_dict[str(split_number)+bin_key[1:]+'a'] = a_bin_boundary
				bin_boundaries_dict[str(split_number)+bin_key[1:]+'b'] = b_bin_boundary
				
				a_points, b_points = [],[] 
				for event_number in range(bin_points_dict[bin_key].shape[0]):
					if bin_points_dict[bin_key][event_number,dim_to_be_sliced] < median: a_points.append(bin_points_dict[bin_key][event_number,:].tolist())
					else: b_points.append(bin_points_dict[bin_key][event_number,:].tolist())
				
				bin_points_dict[str(split_number)+bin_key[1:]+'a'] = np.array(a_points)
				bin_points_dict[str(split_number)+bin_key[1:]+'b'] = np.array(b_points)
				#If a bin contains no particles it should be deleted
				if len(a_points)==0: 
					del bin_points_dict[str(split_number)+bin_key[1:]+'a']
					del bin_boundaries_dict[str(split_number)+bin_key[1:]+'a']
				if len(b_points)==0: 
					del bin_points_dict[str(split_number)+bin_key[1:]+'b']
					del bin_boundaries_dict[str(split_number)+bin_key[1:]+'b']

	if PLOT:	pickle.dump( bin_boundaries_dict, open( "bin_boundaries_dict.p", "wb" ) )	
	bins_sample01_dict= {}
	signed_Scp2_dict= {}
	results_list = []

	for number_of_splits in number_of_splits_list:
		print("\nnumber_of_splits : ",number_of_splits,"\nsystematics_fraction : ",systematics_fraction)
		bins_sample0, bins_sample1 = [] , [] 
		for bin_key, bin_points in bin_points_dict.items():
			if str(number_of_splits) in bin_key:
				labels_in_bin = bin_points[:,-1]
				#print("labels_in_bin : ",labels_in_bin)
				bin_sample0 = np.count_nonzero( labels_in_bin == 0)
				bin_sample1 = np.count_nonzero( labels_in_bin == 1)
				#print("bin_sample0 : ",bin_sample0) 
				#print("bin_sample1 : ",bin_sample1) 
				#simulate uncertainties 
				if(systematics_fraction*float(bin_sample0)!=0.): bin_sample0 += int(round(np.random.normal(0.,systematics_fraction*float(bin_sample0))))
				if(systematics_fraction*float(bin_sample1)!=0.): bin_sample1 += int(round(np.random.normal(0.,systematics_fraction*float(bin_sample1))))
				bins_sample01_dict[bin_key]=[bin_sample0,bin_sample1]
				signed_Scp2_dict[bin_key] = np.square(float(bin_sample1-bin_sample0))/(float(bin_sample1)+float(bin_sample0)+np.square(float(bin_sample1)*systematics_fraction)+np.square(float(bin_sample1)*systematics_fraction))*np.sign(bin_sample1-bin_sample0)
				#print("\n\nbin_sample0 : ",bin_sample0, "\n bins_sample0 : ", bins_sample0 )
				#print("type(bin_sample0) : ",type(bin_sample0), "  type(bins_sample0) : ",type(bins_sample0))
				bins_sample0.append(bin_sample0)
				#print("  bins_sample0 : ", bins_sample0, "\n\n" )
				bins_sample1.append(bin_sample1)
		bins_sample0, bins_sample1 = np.array(bins_sample0,dtype=float), np.array(bins_sample1, dtype=float)
		print("bins_sample0 : ",bins_sample0,"\n bins_sample1 : ",bins_sample1)
		#element wise subtraction and division
		Scp2 = ((bins_sample1-bins_sample0)**2)/ (bins_sample1+bins_sample0+(systematics_fraction*bins_sample1)**2+(systematics_fraction*bins_sample0)**2 )
		#Scp2 =  np.divide(np.square(np.subtract(bins_sample1,bins_sample0)),np.add(bins_sample1,bins_sample0))
		if DEBUG:
			print(Scp2)

		#nansum ignores all the contributions that are Not A Number (NAN)
		Chi2 = np.nansum(Scp2)
		if DEBUG:
			print("Chi2")
			print(Chi2)
		dof=bins_sample0.shape[0]-1
	
		pvalue= 1 - stats.chi2.cdf(Chi2,dof)
		
		print("\nThe p value for Scp2 = ",Scp2," and Chi2 = ", Chi2, " is ",pvalue,"\n\n")
		if DEBUG:
			print(bins_sample0)
			print(bins_sample1)
			print("Chi2/dof : {0}".format(str(Chi2/dof)))

			print("pvalue : {0}".format(str(pvalue)))

		results_list.append(pvalue)

		if PLOT: 
			if no_dim==1: chi2_plots.adaptive_binning_1Dplot(bin_boundaries_dict,data,number_of_splits,title+" "+str(no_dim) + "D "+str(number_of_splits)+ " splits ",name+"_"+str(no_dim) + "D_chi2_"+str(number_of_splits)+"_splits")
			if no_dim==2: chi2_plots.adaptive_binning_2Dplot(bin_boundaries_dict,signed_Scp2_dict,number_of_splits,X_values,title+" "+str(no_dim) + "D"+str(number_of_splits)+ " splits ",name+"_"+str(no_dim) + "D_chi2_"+str(number_of_splits)+"_splits", X_min= X_min,X_total_width=X_total_width )
                        if no_dim>1:  chi2_plots.adaptive_binning_2D1Dplot(bin_boundaries_dict,bins_sample01_dict,number_of_splits,X_values,title+" "+str(no_dim) + "D"+str(number_of_splits)+ " splits ",name+"_"+str(no_dim) + "D_chi2_"+str(number_of_splits)+"_splits", no_dim)



	return results_list