"""
#### In this notebook, we show a full GPU pipleline from ETL to XGB training. We compare two xgboost models with different depth and number of trees. The notebook is adapted from [FARES SAYAH's wonderful notebook](https://www.kaggle.com/faressayah/credit-card-fraud-detection-anns-vs-xgboost). 
"""
import cudf
import cupy
import xgboost as xgb

import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline
sns.set_style("whitegrid")
data = cudf.read_csv("/kaggle/input/creditcardfraud/creditcard.csv")
data['Class'] = data['Class'].astype('float32')
data.head()
"""
# 2. Exploratory Data Analysis

"""
print("Number of missing values:")
data.isnull().sum().sum()
"""
### The only non-transformed variables to work with are:
- `Time`
- `Amount`
- `Class` (1: fraud, 0: not_fraud)
"""
count_classes = data['Class'].value_counts(sort=True)
count_classes
"""
Notice how imbalanced is our original dataset! Most of the transactions are non-fraud. If we use this dataframe as the base for our predictive models and analysis we might get a lot of errors and our algorithms will probably overfit since it will "assume" that most transactions are not fraud. But we don't want our model to assume, we want our model to detect patterns that give signs of fraud!
"""
fraud = data[data['Class']==1]
normal = data[data['Class']==0]

print(f"Shape of Fraudulant transactions: {fraud.shape}")
print(f"Shape of Non-Fraudulant transactions: {normal.shape}")
"""
How different are the amount of money used in different transaction classes?
"""
a_fraud = fraud.Amount.describe().to_frame().transpose()
a_normal = normal.Amount.describe().to_frame().transpose()
amount = cudf.concat([a_fraud,a_normal],axis=0)
amount.index = ['fraud','normal']
print("Amount: fraud vs normal")
amount
"""
Do fraudulent transactions occur more often during certain time frame ?
"""
t_fraud = fraud.Time.describe().to_frame().transpose()
t_normal = normal.Time.describe().to_frame().transpose()
time = cudf.concat([t_fraud,t_normal],axis=0)
time.index = ['fraud','normal']
print("Time: fraud vs normal")
time
# plot the time feature
plt.figure(figsize=(10,8))

plt.subplot(2, 2, 1)
plt.title('Time Distribution (Seconds)')

sns.distplot(data['Time'].values.get(), color='blue');

#plot the amount feature
plt.subplot(2, 2, 2)
plt.title('Distribution of Amount')
sns.distplot(data['Amount'].values.get(),color='blue');
# data[data.Class == 0].Time.hist(bins=35, color='blue', alpha=0.6)
plt.figure(figsize=(12, 10))

plt.subplot(2, 2, 1)
data[data.Class == 1].Time.to_pandas().hist(bins=35, color='blue', alpha=0.6, label="Fraudulant Transaction")
plt.legend()

plt.subplot(2, 2, 2)
data[data.Class == 0].Time.to_pandas().hist(bins=35, color='blue', alpha=0.6, label="Non Fraudulant Transaction")
plt.legend()
"""
By seeing the distributions we can have an idea how skewed are these features, we can also see further distributions of the other features. There are techniques that can help the distributions be less skewed which will be implemented in this notebook in the future.

Doesn't seem like the time of transaction really matters here as per above observation.
Now let us take a sample of the dataset for out modelling and prediction
"""
"""
# 3. Data Pre-processing

`Time` and `Amount` should be scaled as the other columns.
"""
from cuml.model_selection import train_test_split
from cuml.preprocessing import StandardScaler

scalar = StandardScaler()

X = data.drop('Class', axis=1)
y = data.Class

X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                    test_size=0.3, random_state=42)
y_train.mean(), y_test.mean()
print(f"TRAINING: X_train: {X_train.shape}, y_train: {y_train.shape}\n{'_'*55}")
print(f"TESTING: X_test: {X_test.shape}, y_test: {y_test.shape}")
"""
# XGB Training
"""
"""
#### Model 1 (big): 
- `max_depth = 10`
- `num_trees = 300`
"""
max_depth = 10
num_trees = 300

params = {
            'eta':0.1,
            'objective': 'binary:logistic',
            'eval_metric': 'error',
            'tree_method': 'gpu_hist',
            'max_depth': max_depth,
            'predictor': 'gpu_predictor'
        }
dtrain = xgb.DMatrix(data=X_train, label=y_train)
dtest = xgb.DMatrix(data=X_test, label=y_test)
watchlist = [(dtrain, 'train'), (dtest, 'test')] 

bst = xgb.train(params, dtrain=dtrain,
    num_boost_round=num_trees,evals=watchlist,
    verbose_eval=100)
"""
#### Model 2 (small): 
- `max_depth = 3`
- `num_trees = 50`
"""
max_depth = 3
num_trees = 50

params = {
            'eta':0.1,
            'objective': 'binary:logistic',
            'eval_metric': 'error',
            'tree_method': 'gpu_hist',
            'max_depth': max_depth,
            'predictor': 'gpu_predictor'
        }
dtrain = xgb.DMatrix(data=X_train, label=y_train)
dtest = xgb.DMatrix(data=X_test, label=y_test)
watchlist = [(dtrain, 'train'), (dtest, 'test')] 

bst = xgb.train(params, dtrain=dtrain,
    num_boost_round=num_trees,evals=watchlist,
    verbose_eval=10)