from nltk.corpus import stopwords as nltk_stop_words
from nltk.corpus import words
from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
#from sklearn.model_selection import cross_val_score
from sklearn.cross_validation import cross_val_score
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.svm import LinearSVC
from sklearn.linear_model import SGDClassifier
import pandas as pd
import datetime
import numpy as np
from time import time
from sklearn.grid_search import GridSearchCV

"""
Explanation: Сентимент-анализ отзывов на товары (простая версия)
Выполним классификацию отзывов по тональности на готовой обучающей выборке
End of explanation
"""


def read_data():
    X_train = []
    X_test = []
    id_test = []
    y_train = []
    with open('products_sentiment_train.tsv') as f:
        for line in f:
            parts = line.rsplit('\t', 1)

            X_train.append(parts[0].strip())
            y_train.append(parts[1].strip())
    
    with open('products_sentiment_test.tsv') as f:
        f.readline()
        for line in f:
            parts = line.split('\t', 1)
            id_test.append(parts[0].strip())
            X_test.append(parts[1].strip())                    

    return X_train, y_train, id_test, X_test

"""
Explanation: Определим необходимые функции. 
Чтение данных и разделение на обучающую и тестовую выборки.
End of explanation
"""


def predict(predictor, data_train, y, id_test, data_test, cv_score=None):
    predictor.fit(data_train, y)
    prediction = predictor.predict(data_test)
    #print predictor
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    filepath_prediction = 'data/prediction-%s-data.csv' % timestamp
    filepath_description = 'data/prediction-%s-estimator.txt' % timestamp

    # Create a dataframe with predictions and write it to CSV file   
    predictions_df = pd.DataFrame(data=prediction, columns=['y'])
    predictions_df.to_csv(filepath_prediction, sep=',', index_label='Id')

    # Write a short description of the classifier that was used
    f = open(filepath_description, 'w')
    f.write(str(predictor))
    score = '\nCross-validation score %.8f' % cv_score    
    f.write(score)
    f.close()

"""
Explanation: Обучение и классификация выбранным классификатором, сохранение результатов в файл с временной меткой в названии для удобства.
End of explanation
"""


def get_pipeline_and_params_1():
    pipeline = Pipeline([
        ('vect', CountVectorizer()),
        ('logreg', LogisticRegression()),
    ])
    parameters = {
        'vect__max_df': (0.6, 0.8, 1.0),
        'vect__min_df': (0, 1, 2, 5),
        'vect__stop_words': ('english', None),
        'vect__ngram_range': ((1, 1), (1, 2)),  # unigrams or bigrams        
        'logreg__C': (0.0001, 0.01, 1),
        'logreg__penalty': ('l2', 'l1'),        
    }
    return pipeline, parameters

"""
Explanation: Далее описаны различные конфигурации для выделения признаков и классификации, использовавшиеся в экспериментах.
Для каждой конфигурации также указывается набор параметров и перечень значений, использовавшихся для подбора наилучшего сочетания.
Вариант-1 с признаками на основе счетчика слов и классификация логистической регрессией.
End of explanation
"""


def get_pipeline_and_params_2():
    pipeline = Pipeline([
        ('tfidf', TfidfVectorizer()),
        ('logreg', LogisticRegression()),
    ])
    parameters = {
        'tfidf__max_df': (0.6, 0.8, 1.0),
        'tfidf__min_df': (0, 5, 10, 15),
        'tfidf__ngram_range': ((1, 1), (1, 2), (1,3), (2,3)),  # unigrams or bigrams
        #'tfidf__use_idf': (True, False),
        'tfidf__norm': ('l1', 'l2'),   
        'logreg__C': (0.0001, 0.01, 1),
        'logreg__penalty': ('l2', 'l1'),  
    }
    return pipeline, parameters

"""
Explanation: Вариант-2 с признаками на основе частотности слов (TF-IDF) и классификация логистической регрессией.
End of explanation
"""


def get_pipeline_and_params_3():
    # gives 0.7895 on cross-validation and 0.835 on Kaggle. done in 108.629s
    pipeline = Pipeline([
        ('vect', CountVectorizer()),
        ('tfidf', TfidfTransformer()),
        ('clf', SGDClassifier()),
    ])
    parameters = {
        'vect__max_df': (0.5, 0.75, 1.0),
        #'vect__max_features': (None, 5000, 10000, 50000),
        'vect__ngram_range': ((1, 3), (1, 2)),  # unigrams or bigrams
        #'tfidf__use_idf': (True, False),
        'tfidf__norm': ('l1', 'l2'),
        'clf__alpha': (0.00001, 0.000001),
        'clf__penalty': ('l2', 'elasticnet'),
        'clf__n_iter': (10, 50, 80),
    }
    return pipeline, parameters

"""
Explanation: Вариант-3 с признаками на основе частотности слов (TF-IDF) и классификатор на основе стохастического градиентного спуска.
End of explanation
"""


def get_pipeline_and_params_4():
    nltk_sw = nltk_stop_words.words('english')
    pipeline = Pipeline([
        ('vect', CountVectorizer()),
        ('tfidf', TfidfTransformer()),
        ('clf', LinearSVC()),
    ])
    parameters = {
        #'vect__max_df': (0.8, 0.9, 1),
        'vect__min_df': (0, 5, 10),
        #'vect__vocabulary': (None),
        'vect__stop_words': ('english', nltk_sw, None),
        'vect__max_features': (None, 5000, 10000, 50000),
        #'vect__analyzer' : ('word', 'char', 'char_wb',), 
        'vect__ngram_range': ((1,1), (1, 4), (1, 3)),  # unigrams or bigrams
        #'tfidf__use_idf': (True, False),
        #'tfidf__smooth_idf': (True, False),
        #'tfidf__norm': ('l1', 'l2'),
        #'tfidf__sublinear_tf': (True, False),
        #'clf__C': (0.00001, 0.001, 0.1, 1),        
    }
    return pipeline, parameters

"""
Explanation: Вариант-4 с признаками на основе частотности слов (TF-IDF) и классификация методом опорных векторов.
End of explanation
"""


def get_pipeline_and_params_5():
    wordlist = set(words.words())  
    nltk_sw = nltk_stop_words.words('english')
    pipeline = Pipeline([
        ('vect', CountVectorizer(vocabulary=wordlist)),
        ('tfidf', TfidfTransformer()),
        ('clf', SGDClassifier()),
    ])
    parameters = {
        'vect__strip_accents': ('ascii', None),
        'vect__analyzer': ('word', 'char_wb'),
        'vect__max_df': (0.8, 1.0),
        #'vect__min_df': (0, 1, 2),
        #'vect__max_features': (100, 200, 500, 1000, 2000, 5000),
        'vect__vocabulary': (None, wordlist),
        'vect__stop_words': ('english', nltk_sw, None),
        'vect__ngram_range': ((1, 3), (1, 1), (1, 2)),
        'tfidf__use_idf': (True,),
        'tfidf__norm': ('l1',),
        'clf__alpha': (0.000001,),
        'clf__penalty': ('l1',),
        #'clf__n_iter': (10, 50, 80),
    }
    return pipeline, parameters

"""
Explanation: Вариант-5 с признаками на основе частотности слов (TF-IDF) и классификатор на основе стохастического градиентного спуска с использванием корпуса английских слов, стоп-слов и анализатора частей слов (параметр "char_wb").
End of explanation
"""


def do_grid_search(pipeline, parameters, X_train, y_train):
    grid_search = GridSearchCV(pipeline, parameters, scoring='accuracy')
    t0 = time()
    grid_search.fit(X_train, y_train)
    print "done in %0.3fs" % (time() - t0)
    
    print("Best score: %.4f" % grid_search.best_score_)
    print("Best parameters set:")
    best_parameters = grid_search.best_estimator_.get_params()
    for param_name in sorted(parameters.keys()):
        print("\t%s: %r" % (param_name, best_parameters[param_name]))
    return grid_search

"""
Explanation: Функция перебора комбинаций параметров и определения наилучшей конфигурации с помощью GridSearchCV
End of explanation
"""


def do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params):      
    pipeline, parameters = get_pipeline_and_params()
    gs = do_grid_search(pipeline, parameters, X_train, y_train)
    predict(gs.best_estimator_, X_train, y_train, id_test, X_test, gs.best_score_)

"""
Explanation: Основная фунция, описывающая отдельный эксперимент: сформировать тестируюмую конфигирацию и наборы параметров, выполнить перебор для определения наилучших и выполнить классификацию тестовых данных с помощью лучшего классификатора.
End of explanation
"""


X_train, y_train, id_test, X_test = read_data() 

"""
Explanation: Чтение исходных данных
End of explanation
"""


do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params_1)

do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params_2)

do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params_3)

do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params_4)

do_experiment(X_train, y_train, id_test, X_test, get_pipeline_and_params_5)

"""
Explanation: Выполнение серии экспериментов, их результаты по точности при кросс-валидации и подобранные значения параметров.
End of explanation
"""


