import pandas as pd
from scipy.ndimage import gaussian_filter
from sklearn.ensemble import RandomForestClassifier
from sklearn.cross_validation import cross_val_score
from sklearn.metrics import accuracy_score
import numpy as np
%matplotlib inline

"""
Explanation: Goal: Classify smartphone location as driver-side or passenger-side based on  sensor data
End of explanation
"""


df = pd.read_csv('../Data/shaneiphone_exp2_processed.csv', index_col='DateTime')

# Use only userAcceleration and gyroscope data, since these features are expected to generalize well.
xyz = ['X', 'Y', 'Z']
measures = ['userAcceleration', 'gyroscope']
basefeatures = [i + j for i in measures for j in xyz]
features = [i + j for i in measures for j in xyz]

# Add Gaussian smoothed features
smoothfeatures = []
for i in features:
    df[i + 'sm'] = gaussian_filter(df[i], 3)
    df[i + '2sm'] = gaussian_filter(df[i], 100)
    smoothfeatures.append(i + 'sm')
    smoothfeatures.append(i + '2sm')
features.extend(smoothfeatures)

# Generate Jerk signal
jerkfeatures = []
for i in features:
    diffsignal = np.diff(df[i])
    df[i + 'jerk'] = np.append(0, diffsignal)
    jerkfeatures.append(i + 'jerk')
features.extend(jerkfeatures)

# assign class labels
class0 = (df.index > '2015-08-25 14:35:00') & \
        (df.index < '2015-08-25 14:42:00')

class1 = (df.index > '2015-08-25 14:43:00') & \
        (df.index < '2015-08-25 14:48:00')

df['class'] = -1
df['class'][class0] = 0
df['class'][class1] = 1

# separate into quarters for train and validation
q1 = df[(df.index <= '2015-08-25 14:38:30') & 
        (df.index > '2015-08-25 14:33:00')]
q2 = df[(df.index > '2015-08-25 14:38:30') & 
        (df.index <= '2015-08-25 14:42:00')]
q3 = df[(df.index > '2015-08-25 14:43:00') & 
        (df.index <= '2015-08-25 14:45:30')]
q4 = df[(df.index > '2015-08-25 14:45:30') & 
        (df.index <= '2015-08-25 14:48:00')]
traindf = pd.concat([q1, q3])
validationdf = pd.concat([q2, q4])

# check for NaNs in the dataframes
print(traindf.isnull().sum().sum())
print(validationdf.isnull().sum().sum())

# drop NaNs
traindf = traindf.dropna()
validationdf = validationdf.dropna()

# Make the training and validation sets
X_train = traindf[features].values
y_train = traindf['class'].values
X_test = validationdf[features].values
y_test = validationdf['class'].values

# train a random forest
clf = RandomForestClassifier(n_estimators=200)

# get the 5-fold cross-validation score
scores = cross_val_score(clf, X_train, y_train, cv=5)
print(scores, scores.mean(), scores.std())

# apply model to test set
clf.fit(X_train, y_train)
predict_y = clf.predict(X_test)

# obtain accuracy score
testscore = accuracy_score(y_test, predict_y)
print("Accuracy score on test set: %6.3f" % testscore)

"""
Explanation: Load the processed sensor data for car trip (see "Process Smartphone Sensor Data" jupyter notebook).  On this trip, I drove my car from home to Censio and back and used SensorLog on my iPhone to track the trip.  The total time for the trip was about 15 minutes.
End of explanation
"""


# Inspect feature importances
for i, ifeature in enumerate(features):
    print(ifeature + ': %6.4f' % clf.feature_importances_[i])

"""
Explanation: 88% accuracy on the training set and 60% accuracy on the test set means we're overfitting the data.  Accelerometer data should be the key here, since a phone on the driver side will experience a different centripetal acceleration than a phone on the passenger side during turns.  But accelerometer data is super noisy!
End of explanation
"""


# compare bus gyroscopeZ2sm and car gyroscopeZ2sm
#q1['gyroscopeXsm'].plot(color='blue', figsize=(12,6), kind='hist', bins=40, alpha=0.4) # car
#q3['gyroscopeXsm'].plot(color='green', kind='hist', bins=40, alpha=0.4) # bus
q1['gyroscopeXsm'].plot(color='blue', figsize=(12,6)) # car
q3['gyroscopeXsm'].plot(color='green') # bus

"""
Explanation: The smoothed gyroscopeX data is the most useful feature.  This is further confirmation of over-fitting, since this feature corresponds to pitch angle rotation which should be negligible in this dataset (the pitch of the car never changed significantly during the drive).
End of explanation
"""


# Generate Fourier Transform of features
fftfeatures = []
for i in features:
    reals = np.real(np.fft.rfft(df[i]))
    imags = np.imag(np.fft.rfft(df[i]))
    complexs = [reals[0]]
    n = len(reals)
    if n % 2 == 0:
        complexs.append(imags[0])
    for j in range(1, n - 1):
        complexs.append(reals[j])
        complexs.append(imags[j])
    complexs.append(reals[j])
    if len(df) > len(complexs):
        complexs.append(imags[j])
    df['f' + i] = complexs
    fftfeatures.append('f' + i)
features.extend(fftfeatures)

# separate into quarters for train and validation
q1 = df[(df.index <= '2015-08-25 14:38:30') & 
        (df.index > '2015-08-25 14:33:00')]
q2 = df[(df.index > '2015-08-25 14:38:30') & 
        (df.index <= '2015-08-25 14:42:00')]
q3 = df[(df.index > '2015-08-25 14:43:00') & 
        (df.index <= '2015-08-25 14:45:30')]
q4 = df[(df.index > '2015-08-25 14:45:30') & 
        (df.index <= '2015-08-25 14:48:00')]
traindf = pd.concat([q1, q3])
validationdf = pd.concat([q2, q4])

# Make the training and validation sets
X_train = traindf[fftfeatures].values
y_train = traindf['class'].values
X_test = validationdf[fftfeatures].values
y_test = validationdf['class'].values

# train a random forest
clf = RandomForestClassifier(n_estimators=200)

# get the 5-fold cross-validation score
scores = cross_val_score(clf, X_train, y_train, cv=5)
print(scores, scores.mean(), scores.std())

# apply model to test set
clf.fit(X_train, y_train)
predict_y = clf.predict(X_test)

# obtain accuracy score
testscore = accuracy_score(y_test, predict_y)
print("Accuracy score on test set: %6.3f" % testscore)

"""
Explanation: This seems like an enormous difference in pitch angle and is really hard to understand.  My guess is that it is somehow an artifact of the experimental setup. Perhaps the quaternion rotation of gyroscopeXYZ is not quite perfect.
Another interesting avenue to pursue is features in Fourier space
End of explanation
"""


# Inspect feature importances
for i, ifeature in enumerate(fftfeatures):
    print(ifeature + ': %6.4f' % clf.feature_importances_[i])

"""
Explanation: Better accuracy on the test set: 73%.  We are still overfitting here, since we got 92% accuracy on the training set.  Using the Fourier domain seems to be helpful.
End of explanation
"""


