import pysmac
import sklearn.ensemble
import sklearn.neighbors
import sklearn.datasets
import sklearn.cross_validation
import numpy as np 
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, BaggingClassifier, AdaBoostClassifier, VotingClassifier

def read_data():
    train_file = open('income.train.txt')
    dev_file = open('income.dev.txt')
    test_file = open('income.test.txt')

    train_data = pd.read_csv(train_file, delimiter=',', header=None)
    dev_data = pd.read_csv(dev_file, delimiter=',', header=None)
    test_data = pd.read_csv(test_file, delimiter=',', header=None)

    cs = pd.concat([train_data, dev_data, test_data], keys=[0, 1, 2])

    encoded_data = pd.get_dummies(cs, columns=[1, 2, 3, 4, 5, 6, 8])

    train_file.close()
    dev_file.close()
    test_file.close()

    return encoded_data


def separate_data(encoded_data):

    label = encoded_data[[9]]

    encoded_data = encoded_data.drop([9], axis=1)

    train_data = encoded_data.xs(0)
    dev_data = encoded_data.xs(1)
    test_data = encoded_data.xs(2)

    train_label = label.xs(0)
    dev_label = label.xs(1)
    test_label = label.xs(2)

    return train_data, dev_data, test_data, train_label, dev_label, test_label


def encode_label_features(*ylabels):  # process data into numeric format
    enc = LabelEncoder()
    (y_train, y_dev, y_test) = (ylabels)

    y_train = enc.fit_transform(y_train.values.ravel())
    y_dev = enc.fit_transform(y_dev.values.ravel())
    y_test = enc.fit_transform(y_test.values.ravel())

    return y_train, y_dev, y_test


encoded_data = read_data()
X_train, X_dev, X_test, train_label, dev_label, test_label = separate_data(encoded_data)
Y_train, Y_dev, Y_test = encode_label_features(train_label, dev_label, test_label)

# We use a random classification data set generated by sklearn
# As commonly done, we use a train-test split to avoid overfitting.
# X,Y = sklearn.datasets.make_classification(1000, 20)
# X_train, X_test, Y_train, Y_test = \
# 	sklearn.cross_validation.train_test_split(X,Y, test_size=0.33, random_state=1)

# Here, SMAC can choose between to different models at each evaluation. To
# make the search more efficient, it is important to tell SMAC that some
# parameters are associated with certain classifiers
def choose_classifier(classifier, # which classifier to use
				# parameters for the tree based classifiers
                trees_n_estimators = None, trees_criterion = None, 
                trees_max_features = None, trees_max_depth = None,
                # the ones for k-nearest-neighbors
                knn_n_neighbors=None, knn_weights=None):
				#note that possibly inactive variables have to be optional
				#as pysmac does not assign a value for inactive variables
				#during the minimization phase
    if classifier == 'random_forest':
        predictor = sklearn.ensemble.RandomForestClassifier(
						trees_n_estimators, trees_criterion,
						trees_max_features, trees_max_depth)
    elif classifier == 'decision_tree':
        predictor = DecisionTreeClassifier(
						trees_n_estimators, trees_criterion,
						trees_max_features, trees_max_depth)
    elif classifier == 'extra_trees':
        predictor = sklearn.ensemble.ExtraTreesClassifier(
						trees_n_estimators, trees_criterion,
						trees_max_features, trees_max_depth)
    elif classifier == 'k_nearest_neighbors':
        predictor = sklearn.neighbors.KNeighborsClassifier(
						knn_n_neighbors, knn_weights)

    predictor.fit(X_train, Y_train)
    return -predictor.score(X_test, Y_test)

# defining all the parameters with respective defaults.
parameter_definition=dict(\
	trees_max_depth =  ("integer", [1,10],  4),
	trees_max_features=("integer", [1,20], 10),
	trees_n_estimators=("integer", [1,100],10 ,'log'),          
	trees_criterion =("categorical", ['gini', 'entropy'], 'entropy'),
	classifier  = ("ordinal", ['random_forest','extra_trees', 'decision_tree'], 'decision_tree'),
	# Usually you would make this a categorical, but to showcase all 
	# conditional clauses, let's pretend it's an ordinal parameter,
	# so we can use > and <.
	)

# here we define the dependencies between the parameters. the notation is
#   <child> | <parent> in { <parent value>, ... }
# and means that the child parameter is only active if the parent parameter
# takes one of the value in the listed set. The notation follows the SMAC
# manual one to one. Note there is no checking for correctness beyond
# what SMAC does. I.e., when you have a typo in here, you don't get any 
# meaningful output, unless you set  debug = True below!
conditionals = [ 'trees_max_depth    | classifier in {random_forest, extra_trees, decision_tree}',
                 'trees_max_features | classifier in {random_forest} || classifier == extra_trees',
                 'trees_n_estimators | classifier != k_nearest_neighbors',
                ]

# creation of the SMAC_optimizer object. Notice the optional debug flag
opt = pysmac.SMAC_optimizer( debug = 0,
							 working_directory = '/tmp/pysmac_test/', persistent_files=True, )

# first we try the sklearn default, so we can see if SMAC can improve the performance


# predictor = DecisionTreeClassifier()
# predictor.fit(X_train, Y_train)
# print('The default accuracy of the Decision Tree is %f'%predictor.score(X_test, Y_test))

# The minimize method also has optional arguments (more on that in the section on advanced configuration).
value, parameters = opt.minimize(choose_classifier,
                    500 , parameter_definition,
                    conditional_clauses = conditionals)

print('The highest accuracy found: %f'%(-value))
print('Parameter setting %s'%parameters)
