{"cells":[{"metadata":{"trusted":true,"_uuid":"5226a95ba6e6ea70ec33bfe1334afdbfdb3d62e3"},"cell_type":"code","source":"\"\"\"\nLargely followed methodology outlined in this paper:\n\"A Complex Classification Approach of Partial Discharges from Covered Conductors in Real Environment\"\n2017\nS. Mišák, J. Fulneček, T. Vantuch, T. Buriánek and T. Ježowicz\nPreprint version available here:\nhttps://www.dropbox.com/s/2ltuvpw1b1ms2uu/A%20Complex%20Classification%20Approach%20of%20Partial%20Discharges%20from%20Covered%20Conductors%20in%20Real%20Environment%20%28preprint%29.pdf?dl=0\n\nMore details of the methodology from the paper above are provided in this thesis:\n\"Analysis of Time Series Data\"\nTomáš Vantuch\n2018\nAvailable here:\nhttp://dspace.vsb.cz/bitstream/handle/10084/133114/VAN431_FEI_P1807_1801V001_2018.pdf\n\nBegan with kernel from:\nhttps://www.kaggle.com/jackvial/dwt-signal-denoising\nto start with Discrete Wavelet Transform denoising/peak identification\n\"\"\"\n\n# Import Libraries\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport seaborn as sns\nimport pyarrow.parquet as pq\nimport gc\nimport pywt\nfrom statsmodels.robust import mad\nimport scipy\nfrom scipy import signal\nfrom scipy.signal import butter, find_peaks\nfrom scipy import fftpack # Fast Fourier Transform functions\nimport os.path\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import confusion_matrix, make_scorer, matthews_corrcoef\nfrom sklearn.model_selection import cross_val_score\nimport warnings\n\n# Suppress pandas future warnings\nwarnings.simplefilter(action='ignore', category=FutureWarning)\n\n# Define data directory\ndata_dir = '../input'\n\n# Print scipy version - 1.1.0 at time of creation\nprint(scipy.__version__)","execution_count":2,"outputs":[{"output_type":"stream","text":"1.1.0\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"# Define Functions\n\n**Calculate Mean Absolute Deviation**  \nmaddest()\n\n**Synchronise All Waveforms**  \nsync_phase()\n\n**High Pass Filter**  \nhigh_pass_filter()\n\n**Discrete Wavelet Transform Denoising**  \ndenoise_signal()\n\n**Cancel False Peaks**  \ncancel_false_peaks()\n\n**Extract Peak and Valley Features:**  \npv_features()"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 800,000 data points taken over 20 ms\n# Grid operates at 50hz, 0.02 * 50 = 1, so 800k samples in 20 milliseconds will capture one complete cycle\nn_samples = 800000\n\n# Sample duration is 20 miliseconds\nsample_duration = 0.02\n\n# Sample rate is the number of samples in one second\n# Sample rate will be 40mhz\nsample_rate = n_samples * (1 / sample_duration)\n\n# time array support\nt = np.array([i / sample_rate for i in range(n_samples)])\n\n# frequency vector for FFT\nfreqs = fftpack.fftfreq(n_samples, d=1/sample_rate)\n\n# Mean Absolute Deviation\ndef maddest(d, axis=None):\n    return np.mean(np.absolute(d - np.mean(d, axis)), axis)\n\n# Synchronise all waveforms\n# Adapted from https://www.kaggle.com/fernandoramacciotti/sync-waves-with-fft-coeffs\ndef sync_phase(x, align_value=0.5):\n    \n    # fft\n    fft_coeffs = fftpack.fft(x)\n    \n    # asses dominant frequency\n    coeff_norms = np.abs(fft_coeffs)  # get norms (fft coeffs are complex)\n    max_idx = np.argmax(coeff_norms)\n    max_coeff = fft_coeffs[max_idx]  # get max coeff\n    max_freq = freqs[max_idx]  # assess which is the dominant frequency\n    max_amp = (coeff_norms[max_idx] / n_samples) * 2  # times 2 because there are mirrored freqs\n    if max_freq != 50:\n        # print('Dominant frequency is {:,.1f}Hz with amplitude of {:,.1f}\\n'.format(max_freq, max_amp))\n        # print('Signal ID is {:,.1f} and its target is {:,.1f}\\n'.format(metadata_train.signal_id[i], metadata_train.target[i]))\n        return x, max_freq\n    else:\n            \n        # phase shift\n        phase_shift = np.angle(max_coeff)\n    \n        # get angular phase vector\n        w = 2 * np.pi * t * max_freq + phase_shift\n        w_norm = np.mod(w / (2 * np.pi), 1) * 2  # range between cycle of 0-2\n    \n        # idx to roll\n        candidates = np.where(np.isclose(w_norm, align_value))\n        # since we are in discrete time, threre could be many values close to the desired one\n        # so take the median\n        origin = int(np.median(candidates))\n    \n        # roll/sync signal\n        sig_rolled = np.roll(x, n_samples - origin)\n        return sig_rolled, max_freq\n\n# High Pass Filter\n# Adapted from https://github.com/randxie/Kaggle-VSB-Baseline/blob/master/src/utils/util_signal.py\ndef high_pass_filter(x, low_cutoff=1000, sample_rate=sample_rate):  # 1000 is default, correctly set to 10^4 below \n    \n    # nyquist frequency (half the sample rate)\n    nyquist = 0.5 * sample_rate\n    norm_low_cutoff = low_cutoff / nyquist\n    \n    # Fault pattern usually exists in high frequency band. \n    # According to literature, the pattern is visible above 10^4 Hz.\n    # For digital filters, Wn is normalized from 0 to 1, \n    # where 1 is the Nyquist frequency, pi radians/sample (Wn is thus in half-cycles / sample).\n    sos = butter(10, Wn=[norm_low_cutoff], btype='highpass', output='sos')\n    filtered_sig = signal.sosfilt(sos, x)\n\n    return filtered_sig\n\n# Discrete Wavelet Transform Denoising\n# 1. Adapted from waveletSmooth function found here:\n# http://connor-johnson.com/2016/01/24/using-pywavelets-to-remove-high-frequency-noise/\n# 2. Threshold equation and using hard mode in threshold as mentioned\n# in section '3.2 denoising based on optimized singular values' from the Vantuch thesis \ndef denoise_signal(x, wavelet='db4', level=1):\n\n    # Decompose to get the wavelet coefficients\n    coeff = pywt.wavedec(x, wavelet, mode=\"per\" )\n    \n    # Calculate sigma for threshold as defined in the Vantuch thesis, using Mean Absolute Deviation\n    sigma = (1/0.6745) * maddest( coeff[-level] )  # eqn 3.8 \n\n    # Calculte the univeral threshold\n    uthresh = sigma * np.sqrt( 2*np.log( len( x ) ) )  # eqn 3.9\n    coeff[1:] = (pywt.threshold( i, value=uthresh, mode='hard' ) for i in coeff[1:] )\n    \n    # Reconstruct the signal using the thresholded coefficients\n    return pywt.waverec(coeff, wavelet, mode='per')\n\n# Cancel False Peaks\n# Such as those from corona discharges\n# Adapted from https://www.kaggle.com/jeffreyegan/vsb-power-line-fault-detection-approach\ndef cancel_false_peaks(signal, peak_indexes, min_height_fp=50):\n\n    false_peak_indexes = []\n    min_height_fp = min_height_fp\n    max_sym_distance = 20 \n    max_pulse_train = 750  \n    max_height_ratio = 1.25 \n    min_height_ratio = 0.25\n    for pk in range(len(peak_indexes)-1):\n        if not peak_indexes[pk] in false_peak_indexes:\n            if (signal[peak_indexes[pk]] > min_height_fp and signal[peak_indexes[pk+1]] < 0) and (peak_indexes[pk+1] - peak_indexes[pk]) < max_sym_distance:\n                # if 1 > abs(signal[peak_indexes[pk+1]])/abs(signal[peak_indexes[pk]]) > max_height_ratio:\n                if max_height_ratio > abs(signal[peak_indexes[pk+1]])/abs(signal[peak_indexes[pk]]) > min_height_ratio:\n                    for x in range(len(peak_indexes)):\n                        if peak_indexes[pk] <= peak_indexes[x] <= peak_indexes[pk]+max_pulse_train:\n                            false_peak_indexes.append(peak_indexes[x]) \n                        \n            if (signal[peak_indexes[pk]] < -min_height_fp and signal[peak_indexes[pk+1]] > 0) and (peak_indexes[pk+1] - peak_indexes[pk]) < max_sym_distance:\n                if max_height_ratio > abs(signal[peak_indexes[pk+1]])/abs(signal[peak_indexes[pk]]) > min_height_ratio:\n                    for x in range(len(peak_indexes)):\n                        if peak_indexes[pk] <= peak_indexes[x] <= peak_indexes[pk]+max_pulse_train:\n                            false_peak_indexes.append(peak_indexes[x])\n                            \n    true_peak_indexes = list(set(peak_indexes) - set(false_peak_indexes))\n    \n    return np.array(true_peak_indexes, dtype=np.int32), np.array(false_peak_indexes, dtype=np.int32)\n\n\n# Extract Peak and Valley Features\ndef pv_features(x_dn, pv_true, peaks, valleys, rel_height=0.2):\n\n    # p=peaks, v=valleys, h=height, w=width\n    true_peaks = np.array(list(set(peaks) & set(pv_true)), dtype=np.int32)  # intersection of true peaks/valleys and peaks\n    if true_peaks.shape[0] == 0:\n        p_n = 0 \n        ph_mean = 0\n        ph_max = 0\n        pw_mean = 0\n        pw_max = 0\n    else:\n        p_n = true_peaks.shape[0]\n        peak_heights, _ , _ = signal.peak_prominences(x_dn, true_peaks)\n        peak_heights = np.vstack((x_dn[true_peaks], peak_heights)).min(axis=0)\n        ph_mean = peak_heights.mean()\n        ph_max = peak_heights.max()\n        peak_widths, peak_width_heights, peak_left_ips , peak_right_ips = signal.peak_widths(x_dn, true_peaks, rel_height=rel_height)\n        pw_mean = peak_widths.mean()\n        pw_max = peak_widths.max()\n    \n    true_valleys = np.array(list(set(valleys) & set(pv_true)), dtype=np.int32)  # intersection of true peaks/valleys and valleys\n    if true_valleys.shape[0] == 0:\n        v_n = 0 \n        vh_mean = 0\n        vh_max = 0\n        vw_mean = 0\n        vw_max = 0\n    else:\n        v_n = true_valleys.shape[0]\n        valley_heights, _ , _ = signal.peak_prominences(-x_dn, true_valleys)\n        valley_heights = np.vstack((-x_dn[true_valleys], valley_heights)).min(axis=0)\n        vh_mean = valley_heights.mean()\n        vh_max = valley_heights.max()\n        valley_widths, valley_width_heights, valley_left_ips , valley_right_ips = signal.peak_widths(-x_dn, true_valleys, rel_height=rel_height)\n        vw_mean = valley_widths.mean()\n        vw_max = valley_widths.max()\n        \n    return np.array([p_n, ph_mean, ph_max, pw_mean, pw_max, v_n, vh_mean, vh_max, vw_mean, vw_max])\n    ","execution_count":3,"outputs":[]},{"metadata":{"_uuid":"d9a8df3c33016b94ae63d223de4cb652a5fe9dd9"},"cell_type":"markdown","source":"### Import Train Metadata"},{"metadata":{"trusted":true,"_uuid":"5cfda79d0648204193833b9a5aa1d4e8f4c90666","scrolled":true},"cell_type":"code","source":"metadata_train = pd.read_csv(data_dir + '/metadata_train.csv')\nmetadata_train.head()","execution_count":4,"outputs":[{"output_type":"execute_result","execution_count":4,"data":{"text/plain":"   signal_id  id_measurement  phase  target\n0          0               0      0       0\n1          1               0      1       0\n2          2               0      2       0\n3          3               1      0       1\n4          4               1      1       1","text/html":"<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>signal_id</th>\n      <th>id_measurement</th>\n      <th>phase</th>\n      <th>target</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>0</td>\n      <td>0</td>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>1</td>\n      <td>0</td>\n      <td>1</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>2</td>\n      <td>0</td>\n      <td>2</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>3</td>\n      <td>1</td>\n      <td>0</td>\n      <td>1</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>4</td>\n      <td>1</td>\n      <td>1</td>\n      <td>1</td>\n    </tr>\n  </tbody>\n</table>\n</div>"},"metadata":{}}]},{"metadata":{},"cell_type":"markdown","source":"**Check number of phases on which a fault occurs**  \nThe majority of faults (target=1) occur on all 3 phases"},{"metadata":{"trusted":true,"_uuid":"4e1a09820640ce2c05f46615d22b5e9484c673e7"},"cell_type":"code","source":"metadata_train.groupby([\"id_measurement\"]).sum()['target'].value_counts()","execution_count":5,"outputs":[{"output_type":"execute_result","execution_count":5,"data":{"text/plain":"0    2710\n3     156\n1      19\n2      19\nName: target, dtype: int64"},"metadata":{}}]},{"metadata":{"_uuid":"f88a87ebd7e6f322caad76783f54055e99b23cbd"},"cell_type":"markdown","source":"### Import Small Subset of Train Data for Exploration"},{"metadata":{"trusted":true,"_uuid":"4524f1ddb1414f8072a7f3d07189957ad6518532","scrolled":false},"cell_type":"code","source":"subset_train = pq.read_pandas(data_dir + '/train.parquet', columns=[str(i) for i in range(21)]).to_pandas()\nsubset_train.shape","execution_count":6,"outputs":[{"output_type":"execute_result","execution_count":6,"data":{"text/plain":"(800000, 21)"},"metadata":{}}]},{"metadata":{},"cell_type":"markdown","source":"### Plot the Target Variable Counts by Phase"},{"metadata":{"trusted":true,"_uuid":"1212477ae2d75474717fe2e0e36039ce146466cd"},"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))\nsns.countplot(x=\"target\", data=metadata_train, ax=ax1)\nsns.countplot(x=\"target\", data=metadata_train, hue=\"phase\", ax=ax2)","execution_count":7,"outputs":[{"output_type":"execute_result","execution_count":7,"data":{"text/plain":"<matplotlib.axes._subplots.AxesSubplot at 0x7f4bc608cd68>"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"<Figure size 1008x288 with 2 Axes>","image/png":"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\n"},"metadata":{}}]},{"metadata":{"_uuid":"d7922cec4a1654bf066b4dfa491f3e0ef3697822"},"cell_type":"markdown","source":"# Exploratory Plot 1\n**Plot the first six signals** (all 3 phases of the first 2 measurement IDs)  \nMeasurement ID 1 - no faults  \nMeasurement ID 2 -faults on all 3 phases  \nEach row of plots has the original signal (left), high pass signal (middle), and high pass and DWT denoised signal (right).  \nTwo rows are plotted for each signal: complete signal in the first row, and zoomed in signal in the second row.  "},{"metadata":{"trusted":true,"_uuid":"a137e0bb2e22894ce620388954e24312224eb057","_kg_hide-input":false,"_kg_hide-output":false},"cell_type":"code","source":"train_length = 6\nfor i in range(train_length):\n    signal_id = str(i)\n    meta_row = metadata_train[metadata_train['signal_id'] == i]\n    measurement = str(meta_row['id_measurement'].values[0])\n    signal_id = str(meta_row['signal_id'].values[0])\n    phase = str(meta_row['phase'].values[0])\n        \n    # Synchronize waveforms\n    x_sync, dominant_frequency = sync_phase(subset_train[signal_id])\n    \n    # Apply high pass filter with low cutoff of 10kHz, this will remove the low frequency 50Hz sinusoidal motion in the signal\n    x_hp = high_pass_filter(x_sync, low_cutoff=10000, sample_rate=sample_rate)\n    \n    # Apply denoising\n    x_dn = denoise_signal(x_hp, wavelet='haar', level=1)\n    \n    slice_size = 10000\n    font_size = 16\n    \n    fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(30, 10))\n    \n    ax[0, 0].plot(x_sync, alpha=0.5)\n    ax[0, 0].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[0, 0].legend(['Original'], fontsize=font_size)\n    \n    # Show smaller slice of the signal to get a better idea of the effect the high pass frequency filter is having on the signal\n    ax[1, 0].plot(x_sync[:slice_size], alpha=0.5)\n    ax[1, 0].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[1, 0].legend([f\"Original n: {slice_size}\"], fontsize=font_size)\n    \n    ax[0, 1].plot(x_hp, 'r', alpha=0.5)\n    ax[0, 1].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[0, 1].legend(['HP filter'], fontsize=font_size)\n    ax[1, 1].plot(x_hp[:slice_size], 'r', alpha=0.5)\n    ax[1, 1].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[1, 1].legend([f\"HP filter n: {slice_size}\"], fontsize=font_size)\n    \n    ax[0, 2].plot(x_dn, 'g', alpha=0.5)\n    ax[0, 2].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[0, 2].legend(['HP filter and denoising'], fontsize=font_size)\n    ax[1, 2].plot(x_dn[:slice_size], 'g', alpha=0.5)\n    ax[1, 2].set_title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}\", fontsize=font_size)\n    ax[1, 2].legend([f\"HP filter and denoising n: {slice_size}\"], fontsize=font_size)\n    \n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"73bbb0d538e304a841b7c4cd457c180a24207f90"},"cell_type":"markdown","source":"# Exploratory Plot 2\n**Plot the first 21 signals (filtered and denoised) zoomed in on 2000 out of 800000 points to see peak detail and canceled peaks**  \npeaks (and valleys) marked with a blue x  \ncanceled false peaks marked with a red x  \npeak widths and heights indicated by an orange line  \nfaults on all three phases of measurement ID 1 only \n\n"},{"metadata":{"trusted":true,"_uuid":"4274b7f9f430097544518303568e195d03805bab","scrolled":false},"cell_type":"code","source":"train_length = subset_train.shape[1]\n\nfor i in range(train_length):\n    signal_id = str(i)\n    meta_row = metadata_train[metadata_train['signal_id'] == i]\n    measurement = str(meta_row['id_measurement'].values[0])\n    signal_id = str(meta_row['signal_id'].values[0])\n    phase = str(meta_row['phase'].values[0])\n    target = str(meta_row['target'].values[0])\n    \n    subset_train_row = subset_train[signal_id]\n    \n    # Apply high pass filter with low cutoff of 10kHz, this will remove the low frequency 50Hz sinusoidal motion in the signal\n    x_hp = high_pass_filter(subset_train_row, low_cutoff=10000, sample_rate=sample_rate)\n    \n    # Apply denoising\n    x_dn = denoise_signal(x_hp, wavelet='haar', level=1)\n    \n    start = 116000\n    slice_size = 2000\n    \n    # Find peaks\n    peaks, peak_properties = find_peaks(x_dn[start:start+slice_size], height=0.1, width=0, rel_height=0.2)\n    valleys, valley_properties = find_peaks(-x_dn[start:start+slice_size], height=0.1, width=0, rel_height=0.2)\n    all_peaks = np.sort(np.concatenate((peaks, valleys)))\n    \n    # Cancel false peaks\n    true_pv, false_pv = cancel_false_peaks(x_dn[start:start+slice_size], all_peaks, min_height_fp=15)\n    true_peaks = np.array(list(set(peaks) & set(true_pv)), dtype=np.int32)  # intersection of true peaks/valleys and peaks\n    true_valleys = np.array(list(set(valleys) & set(true_pv)), dtype=np.int32)  # intersection of true peaks/valleys and valleys \n    \n    # Calculate heights and widths of true peaks\n    peak_heights, _ , _ = signal.peak_prominences(x_dn[start:start+slice_size], true_peaks)\n    peak_heights = np.vstack((x_dn[start+true_peaks], peak_heights)).min(axis=0)\n    peak_widths, peak_width_heights, peak_left_ips , peak_right_ips = signal.peak_widths(x_dn[start:start+slice_size], true_peaks, rel_height=0.2)\n    \n    valley_heights, _ , _ = signal.peak_prominences(-x_dn[start:start+slice_size], true_valleys)\n    valley_heights = np.vstack((-x_dn[start+true_valleys], valley_heights)).min(axis=0)\n    valley_widths, valley_width_heights, valley_left_ips , valley_right_ips = signal.peak_widths(-x_dn[start:start+slice_size], true_valleys, rel_height=0.2)\n    \n    # Plot\n    font_size = 16\n    plt.figure(figsize=(30,8))\n    plt.plot(x_dn[start:start+slice_size], 'g', alpha=0.5)\n    plt.scatter(true_peaks, x_dn[start+true_peaks], marker=\"x\", color=\"b\", label=\"True Peaks\")\n    plt.scatter(false_pv, x_dn[start+false_pv], marker=\"x\", color=\"red\", label=\"Cancelled Peaks\")\n    plt.vlines(x=true_peaks, ymin=x_dn[start+true_peaks] - peak_heights, ymax = x_dn[start+true_peaks], color = \"C1\")\n    plt.hlines(y=peak_width_heights, xmin=peak_left_ips, xmax=peak_right_ips, color = \"C1\")\n    ## Valleys\n    plt.scatter(true_valleys, x_dn[start+true_valleys], marker=\"x\", color=\"b\", label=\"True Valleys\")\n    plt.vlines(x=true_valleys, ymin=x_dn[start+true_valleys] , ymax = x_dn[start+true_valleys] + valley_heights, color = \"C1\")\n    plt.hlines(y=-valley_width_heights, xmin=valley_left_ips, xmax=valley_right_ips, color = \"C1\")\n    \n    plt.title(f\"measurement id: {measurement}, signal id: {signal_id}, phase: {phase}, target: {target}\", fontsize=font_size)\n    plt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1854dfcd4b242a80044087f4493e8a53fbd7acce"},"cell_type":"markdown","source":"# Load Train Dataset\n**Note: dataset occupies 6.5GB of RAM, but ~15GB is needed while loading the dataset**  \nIf you do not have this much RAM available, load and process the data in chunks as I have done with the larger test set below"},{"metadata":{"trusted":true,"_uuid":"e6e51c117b70d4a7a8c3dba730d1676a542adf60"},"cell_type":"code","source":"train = pq.read_pandas(data_dir + '/train.parquet').to_pandas()\ntrain.info()","execution_count":8,"outputs":[{"output_type":"stream","text":"<class 'pandas.core.frame.DataFrame'>\nRangeIndex: 800000 entries, 0 to 799999\nColumns: 8712 entries, 0 to 8711\ndtypes: int8(8712)\nmemory usage: 6.5 GB\n","name":"stdout"}]},{"metadata":{"_uuid":"e91404e186199b301121df0a138c8964847a2860"},"cell_type":"markdown","source":"# Train Set: Filter, Denoise, and Extract Features\n**Note: this can take over 30 minutes**\n"},{"metadata":{"trusted":true,"_uuid":"797f01f817d652a1f6a8520240331c0939b4bda5"},"cell_type":"code","source":"%%time\ntrain_length = train.shape[1]  # 8712\ntrain_features = np.empty((train_length, 44))\nheight = (2, 100)\nrel_height = 0.2\n\nfor i in range(train_length):\n    signal_id = str(i)\n    \n    # Synchronize phases\n    x_sync, dominant_frequency = sync_phase(train[signal_id])\n    \n    # Apply high pass filter with low cutoff of 10kHz\n    x_hp = high_pass_filter(x_sync, low_cutoff=10000, sample_rate=sample_rate)\n    \n    # Apply denoising\n    x_dn = denoise_signal(x_hp, wavelet='haar', level=1)\n    \n    # Peak & valley features, divided into 4 parts of the waveform\n    for j in range(4):\n        subset_x_dn = x_dn[j*200000:j*200000+200000]\n        p, _ = find_peaks(subset_x_dn, height=height, rel_height=rel_height)\n        v, _ = find_peaks(-subset_x_dn, height=height, rel_height=rel_height)\n        pv = np.sort(np.concatenate((p, v)))\n        # If there are no peaks, or the dominant frequancy is not 50Hz, return zeros\n        if pv.shape[0] == 0 or dominant_frequency != 50:\n            train_features[i,j*11:j*11+11] = np.zeros(11) \n        else:\n            # Cancel false peaks\n            pv_true, _ = cancel_false_peaks(subset_x_dn, pv, min_height_fp=15)\n            # peak and valley features\n            train_features[i,j*11] = pv_true.shape[0]\n            train_features[i,j*11+1:j*11+11] = pv_features(subset_x_dn, pv_true, p, v, rel_height=rel_height)\n\n# Add feature/column names\n# \"p\" -> peaks \n# \"v\" -> valleys\n# \"1\",\"2\",\"3\",\"4\" -> four parts/sections of the waveform\n# \"n\" -> count\n# \"h\" -> height\n# \"w\" -> width\n# e.g. \"pv1_n\" -> peak and valley count in the first section, \"vh3_mean\" -> mean valley heigh in the third section\nfeature_columns = [\"pv1_n\", \"p1_n\", \"ph1_mean\", \"ph1_max\", \"pw1_mean\", \"pw1_max\", \"v1_n\", \"vh1_mean\", \"vh1_max\", \"vw1_mean\", \"vw1_max\", \n                   \"pv2_n\", \"p2_n\", \"ph2_mean\", \"ph2_max\", \"pw2_mean\", \"pw2_max\", \"v2_n\", \"vh2_mean\", \"vh2_max\", \"vw2_mean\", \"vw2_max\", \n                   \"pv3_n\", \"p3_n\", \"ph3_mean\", \"ph3_max\", \"pw3_mean\", \"pw3_max\", \"v3_n\", \"vh3_mean\", \"vh3_max\", \"vw3_mean\", \"vw3_max\", \n                   \"pv4_n\", \"p4_n\", \"ph4_mean\", \"ph4_max\", \"pw4_mean\", \"pw4_max\", \"v4_n\", \"vh4_mean\", \"vh4_max\", \"vw4_mean\", \"vw4_max\",\n                  ]\n# Combine peak/vally features with names\ntrain_features = pd.DataFrame(train_features, columns=feature_columns)\n# Add signal_id and id_measurement columns\ntrain_features.insert(loc=0, column=\"signal_id\", value=metadata_train.signal_id)\ntrain_features.insert(loc=1, column=\"id_measurement\", value=metadata_train.id_measurement)\n# Save to CSV\ntrain_features.to_csv('train_features.csv', index=False)","execution_count":10,"outputs":[{"output_type":"stream","text":"CPU times: user 32min 39s, sys: 2min 25s, total: 35min 5s\nWall time: 35min 5s\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"### Remove Train from Memory"},{"metadata":{"trusted":true,"_uuid":"12f1924ea4f9096422a7681d8281cb541bfd10f4"},"cell_type":"code","source":"del train","execution_count":11,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plot Correlation Matrix"},{"metadata":{"trusted":true,"_uuid":"171dbb245dc3593c7c2dc693f0f18c8b8aecbca4"},"cell_type":"code","source":"features_corr = train_features[[\"p1_n\", \"ph1_mean\", \"ph1_max\", \"pw1_mean\", \"pw1_max\", \"v1_n\", \"vh1_mean\", \"vh1_max\", \"vw1_mean\", \"vw1_max\"]].copy()\n\nfeatures_corr.insert(loc=0, column=\"target\", value=metadata_train.target)\n\nblues = [\"#66D7EB\", \"#51ACC5\", \"#3E849E\", \"#2C5F78\", \"#1C3D52\", \"#0E1E2B\"]\ncor = features_corr.corr()\nf, ax = plt.subplots(figsize=(14, 8), dpi= 120, facecolor='w', edgecolor='k')\nsns.heatmap(cor, cmap=blues)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0d9c5ccbc2194e49c9a0dc585d914694841aa013"},"cell_type":"markdown","source":"## Plot Histograms of Features"},{"metadata":{"trusted":true,"_uuid":"92b835a9039a660dd487354c314c1a7e067b6a9f"},"cell_type":"code","source":"fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(30, 10))\n    \nax[0].hist(train_features[\"p1_n\"], bins='auto')\nax[0].set_title(\"Histogram of Peak Counts in 1st Section\", fontsize=16)\nax[1].hist(train_features[\"ph1_mean\"], bins='auto')\nax[1].set_title(\"Histogram of Mean Peak Heights in 1st Section\", fontsize=16)\nax[2].hist(train_features[\"ph1_max\"], bins='auto')\nax[2].set_title(\"Histogram of Max Peak Heights in 1st Section\", fontsize=16)","execution_count":16,"outputs":[{"output_type":"execute_result","execution_count":16,"data":{"text/plain":"Text(0.5, 1.0, 'Histogram of Max Peak Heights in 1st Section')"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"<Figure size 2160x720 with 3 Axes>","image/png":"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\n"},"metadata":{}}]},{"metadata":{},"cell_type":"markdown","source":"### Import Test Set Metadata"},{"metadata":{"trusted":true,"_uuid":"425979ddb861e205a42371ee3f9e53192c9b79e0"},"cell_type":"code","source":"metadata_test = pd.read_csv(data_dir + '/metadata_test.csv')\nmetadata_test.head()","execution_count":17,"outputs":[{"output_type":"execute_result","execution_count":17,"data":{"text/plain":"   signal_id  id_measurement  phase\n0       8712            2904      0\n1       8713            2904      1\n2       8714            2904      2\n3       8715            2905      0\n4       8716            2905      1","text/html":"<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>signal_id</th>\n      <th>id_measurement</th>\n      <th>phase</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>8712</td>\n      <td>2904</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>8713</td>\n      <td>2904</td>\n      <td>1</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>8714</td>\n      <td>2904</td>\n      <td>2</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>8715</td>\n      <td>2905</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>8716</td>\n      <td>2905</td>\n      <td>1</td>\n    </tr>\n  </tbody>\n</table>\n</div>"},"metadata":{}}]},{"metadata":{},"cell_type":"markdown","source":"# Test Set: Load, Filter, Denoise, and Extract Features \n**Note: this is done in 10 parts in order to not exceed available RAM**  "},{"metadata":{"trusted":true,"_uuid":"243c312f0ed6e97c348d4e83f18aef10ec54a6cf"},"cell_type":"code","source":"%%time\n\nfirst_index = metadata_test.signal_id[0]\nlength_test = metadata_test.shape[0]  #20337\nn_parts = 10\nstep_size = int(length_test/n_parts)\nlast_step = length_test % n_parts\n\n# Create a list of lists with start index and end index for each of the 10 parts and one for the last partial part\nstart_end = [[x, x+step_size] for x in range(first_index, length_test + first_index, step_size)]\nstart_end = start_end[:-1] + [[start_end[-1][0], start_end[-1][0] + last_step]]\n# print(start_end)\n\npeak_height = (2,100)\nrel_height = 0.2\ntest_features = np.empty((length_test, 44))\n\nfor start, end in start_end:\n    \n    subset_test = pq.read_pandas(data_dir + '/test.parquet', columns=[str(x) for x in range(start, end)]).to_pandas()\n    \n    for i in range(start, end):\n        signal_id = str(i)\n    \n        # Synchronize phases\n        x_sync, dominant_frequency = sync_phase(subset_test[signal_id])\n    \n        # Apply high pass filter with low cutoff of 10kHz\n        x_hp = high_pass_filter(x_sync, low_cutoff=10000, sample_rate=sample_rate)\n    \n        # Apply denoising\n        x_dn = denoise_signal(x_hp, wavelet='haar', level=1)\n    \n        # Peak & valley features, divided into 4 parts of the waveform\n        for j in range(4):\n            subset_x_dn = x_dn[j*200000:j*200000+200000]\n            p, _ = find_peaks(subset_x_dn, height=peak_height, rel_height=rel_height)\n            v, _ = find_peaks(-subset_x_dn, height=peak_height, rel_height=rel_height)\n            pv = np.sort(np.concatenate((p, v)))\n            # If there are no peaks, or the dominant frequancy is not 50Hz, return zeros\n            if pv.shape[0] == 0 or dominant_frequency != 50:\n                test_features[i-first_index, j*11:j*11+11] = np.zeros(11)\n            else:\n                # Cancel false peaks\n                pv_true, _ = cancel_false_peaks(subset_x_dn, pv, min_height_fp=15)\n                # Peak and valley features\n                test_features[i-first_index, j*11] = pv_true.shape[0]\n                test_features[i-first_index, j*11+1:j*11+11] = pv_features(subset_x_dn, pv_true, p, v, rel_height=rel_height)\n\n# Combine peak/vally features with names\ntest_features = pd.DataFrame(test_features, columns=feature_columns)\n# Add signal_id and id_measurement columns\ntest_features.insert(loc=0, column=\"signal_id\", value=metadata_test.signal_id)\ntest_features.insert(loc=1, column=\"id_measurement\", value=metadata_test.id_measurement)\n# Save features to CSV\ntest_features.to_csv('test_features.csv', index=False)\n","execution_count":18,"outputs":[{"output_type":"stream","text":"CPU times: user 1h 24min 36s, sys: 2min 36s, total: 1h 27min 12s\nWall time: 1h 27min 13s\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"# Fit Models\n**First Model: Random Forest**  \n**Second Model: Random Forest with all three phases grouped together**  \nThis is because about 80% of faults occur on all three phases in the train data, but this percentage was much lower for test set predictions from the first model  \n**Stacked Ensemble Model: Use Logistic Regression to combine the other two models\n"},{"metadata":{},"cell_type":"markdown","source":"## Random Forest Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n# Define features to use (all features at this time)\nmodeled_features = [\"p1_n\", \"ph1_mean\", \"ph1_max\", \"pw1_mean\", \"pw1_max\", \"v1_n\", \"vh1_mean\", \"vh1_max\", \"vw1_mean\", \"vw1_max\",\n                    \"p2_n\", \"ph2_mean\", \"ph2_max\", \"pw2_mean\", \"pw2_max\", \"v2_n\", \"vh2_mean\", \"vh2_max\", \"vw2_mean\", \"vw2_max\",\n                    \"p3_n\", \"ph3_mean\", \"ph3_max\", \"pw3_mean\", \"pw3_max\", \"v3_n\", \"vh3_mean\", \"vh3_max\", \"vw3_mean\", \"vw3_max\",\n                    \"p4_n\", \"ph4_mean\", \"ph4_max\", \"pw4_mean\", \"pw4_max\", \"v4_n\", \"vh4_mean\", \"vh4_max\", \"vw4_mean\", \"vw4_max\"\n                   ]\nX_train = train_features[modeled_features]\n# Define target\ny_train = metadata_train.target\n\n# Random Forest Classifier\nrandom_state = 1\nclass_weight = dict({0:0.5, 1:2.0})\nRF_classifier = RandomForestClassifier(bootstrap=True, class_weight=class_weight, criterion='gini',\n                                       max_depth=8, max_features='auto', max_leaf_nodes=None,\n                                       min_impurity_decrease=0.0, min_impurity_split=None,\n                                       min_samples_leaf=4, min_samples_split=10,\n                                       min_weight_fraction_leaf=0.0, n_estimators=300, n_jobs=-1,\n                                       oob_score=False, random_state=random_state, verbose=0, warm_start=False)\nRF_classifier.fit(X_train, y_train)\n\n# k-fold cross validation\nmcc_scorer = make_scorer(matthews_corrcoef)\nmcc = cross_val_score(estimator = RF_classifier,\n                             X = X_train,\n                             y = y_train,\n                             scoring=mcc_scorer,\n                             cv = 10)\nprint(\"MCC Mean = \", mcc.mean())\nprint(\"MCC SD = \", mcc.std())\n\n## Predict Test Set Target \nX_test = test_features[modeled_features]\ny_pred = RF_classifier.predict(X_test)","execution_count":27,"outputs":[{"output_type":"stream","text":"MCC Mean =  0.6014003698935049\nMCC SD =  0.08981433705031791\nCPU times: user 15.9 s, sys: 1.27 s, total: 17.1 s\nWall time: 31.9 s\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"## Grouped Random Forest: Three Phases Grouped Together"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create grouped train features - take the mean of the three phases\ngroup_train_features = train_features.groupby([\"id_measurement\"]).mean()\ngroup_train_features.shape\ngroup_X_train = group_train_features[modeled_features]\n# Create grouped test features - take the mean of the three phases\ngroup_test_features = test_features.groupby([\"id_measurement\"]).mean()\ngroup_test_features.shape\n# Define target\ngroup_y_train = metadata_train.groupby([\"id_measurement\"]).median()[\"target\"]  # take median target value\n\n# Grouped Random Forest Classifier\nrandom_state = 1\nclass_weight = dict({0:0.5, 1:2.0})\ngroup_RF_classifier = RandomForestClassifier(bootstrap=True, class_weight=class_weight, criterion='gini',\n                                             max_depth=8, max_features='auto', max_leaf_nodes=None,\n                                             min_impurity_decrease=0.0, min_impurity_split=None,\n                                             min_samples_leaf=4, min_samples_split=10,\n                                             min_weight_fraction_leaf=0.0, n_estimators=300, n_jobs=-1,\n                                             oob_score=False, random_state=random_state, verbose=0, warm_start=False)\ngroup_RF_classifier.fit(group_X_train, group_y_train)\n\n# k-fold cross validation\ngroup_mcc = cross_val_score(estimator = group_RF_classifier,\n                             X = group_X_train,\n                             y = group_y_train,\n                             scoring=mcc_scorer,\n                             cv = 10)\nprint(\"MCC Mean = \", group_mcc.mean())\nprint(\"MCC SD = \", group_mcc.std())\n\n# Grouped Test Set Predictions\ngroup_X_test = group_test_features[modeled_features]\ngroup_y_pred = group_RF_classifier.predict(group_X_test)\ngroup_y_pred = group_y_pred.repeat(3)\n","execution_count":28,"outputs":[{"output_type":"stream","text":"MCC Mean =  0.6315854420698475\nMCC SD =  0.09014647661276673\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"# Stacked Ensemble (Meta) Model \n**Logistic Regression**"},{"metadata":{"trusted":true,"_uuid":"270093edf1f85dc4ee6f1407cd10a8732a323ab8"},"cell_type":"code","source":"# Use first two RF models to make predictions on the training sets (only made predictions on the test sets earlier)\nmeta_X_train1 = RF_classifier.predict(X_train)\nmeta_X_train2 = np.repeat(group_RF_classifier.predict(group_X_train), 3)\nmeta_X_train = np.column_stack([meta_X_train1, meta_X_train2])\n# Define target\ny_train = metadata_train.target\n\n# Logistic Regression (Classifier)\nlog_classifier = LogisticRegression(random_state = 0)\nlog_classifier.fit(meta_X_train, y_train)\n\n# k-fold cross validation\nmeta_mcc = cross_val_score(estimator = log_classifier,\n                           X = meta_X_train,\n                           y = y_train,\n                           scoring=mcc_scorer,\n                           cv = 10)\nprint(\"MCC Mean = \", meta_mcc.mean())\nprint(\"MCC SD = \", meta_mcc.std())\n\n# Predict Test Set Target\nmeta_X_test = np.column_stack([y_pred, group_y_pred])\nmeta_y_pred = log_classifier.predict(meta_X_test)\nmeta_y_pred.mean()\n\n# Prepare Kaggle submission\noutput = pd.read_csv(data_dir + '/sample_submission.csv')\noutput['target'] = meta_y_pred\noutput.to_csv('submission3.csv', index=False)\n","execution_count":30,"outputs":[{"output_type":"stream","text":"MCC Mean =  0.8083652690425829\nMCC SD =  0.06764916230251698\n","name":"stdout"}]},{"metadata":{},"cell_type":"markdown","source":"**My final submission had an MCC of 0.61178**"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}