{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"}],"dockerImageVersionId":30822,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Digital Signal Processing in Python: Filtering by Frequency\n\nThis is the accompanying notebook to [our Medium article on how to filter digital signals by frequency](https://medium.com/@protobioengineering/digital-signals-for-dumb-sses-part-6-how-to-remove-frequencies-from-a-signal-with-python-3c7c82b0a8b5).\n\nWe will take an [EEG signal](https://www.mayoclinic.org/tests-procedures/eeg/about/pac-20393875) (a brain wave recording) and filter it with each of these:\n* low-pass filter\n* high-pass filter\n* band-stop filter\n* band-pass filter\n* notch filter (a thin band-stop filter)\n\n## EEG Data\nWe will use some seizure-related EEG data, which was used in the [HMS Harmful Brain Activity Classification contest](https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/) on Kaggle.\n\nAn EEG, or [electroencephalogram](https://www.mayoclinic.org/tests-procedures/eeg/about/pac-20393875), is a recording of brain waves from the surface of a person's scalp. A technician or doctor will put dozens of wires on the person's head and then record the changes in voltage that these wires pick up. Our brains make some pretty stereotypical wave patterns when awake, asleep, thinking deeply, and so on, and we can record these as digital signals and then analyze them with Python (or any programming language)!\n\nAccording to the dataset's homepage, the EEG signals were collected at **200 samples per second** (200 Hz). **This knowledge is necessary to make accurate filters!**","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom scipy import signal","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:13.916447Z","iopub.execute_input":"2024-12-28T04:58:13.916848Z","iopub.status.idle":"2024-12-28T04:58:14.798793Z","shell.execute_reply.started":"2024-12-28T04:58:13.916822Z","shell.execute_reply":"2024-12-28T04:58:14.797563Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Open the EEG with Pandas\n\nThe EEG is in a parquet file, which we can open with Pandas.\nWe'll look at subject `3911565283`'s EEG. You can use any of the EEGs in the `test_eegs` or `train_eegs` folders from HMS's dataset if you'd like.","metadata":{}},{"cell_type":"code","source":"eeg = pd.read_parquet(\"/kaggle/input/hms-harmful-brain-activity-classification/test_eegs/3911565283.parquet\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:14.800297Z","iopub.execute_input":"2024-12-28T04:58:14.800931Z","iopub.status.idle":"2024-12-28T04:58:14.996907Z","shell.execute_reply.started":"2024-12-28T04:58:14.800892Z","shell.execute_reply":"2024-12-28T04:58:14.995741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''\nEach column in the dataset is the output from one channel in the EEG.\nEEGs typically have dozens of channels, with names that hint at their locations on the scalp.\nFor example, channel F3 is near the frontal lobe of the brain, channel O1 is near the occipital lobe of the brain, and so on.\n'''\neeg","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:14.999483Z","iopub.execute_input":"2024-12-28T04:58:14.999920Z","iopub.status.idle":"2024-12-28T04:58:15.037980Z","shell.execute_reply.started":"2024-12-28T04:58:14.999890Z","shell.execute_reply":"2024-12-28T04:58:15.036742Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Plot Some EEG Channels\n\nWe'll plot two EEG channels to get a feel for the data.","metadata":{}},{"cell_type":"code","source":"'''\nPlot two EEG channels on top of each other.\n`alpha` makes the F3 channel plot more transparent.\n'''\neeg['Fp1'].plot(label='Fp1')\neeg['F3'].plot(label='F3', alpha=0.7)\nplt.title('Channels Fp1 and F3 from Subject 3911565283\\'s EEG')\nplt.legend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.039681Z","iopub.execute_input":"2024-12-28T04:58:15.039973Z","iopub.status.idle":"2024-12-28T04:58:15.532070Z","shell.execute_reply.started":"2024-12-28T04:58:15.039948Z","shell.execute_reply":"2024-12-28T04:58:15.530691Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Zoom in on the EEG\n\nWe'll look more closely at one channel of the EEG to see what kinds of frequencies are present.\nWe will also use this smaller chunk of the data in our filters, so that the outputs of the filters are more easy to see.","metadata":{}},{"cell_type":"code","source":"# Plot the first 1000 elements from channel Fp1 of the EEG.\neeg['Fp1'][:1000].plot(label='Fp1')\nplt.title('Channel Fp1 from Subject 3911565283\\'s EEG')\nplt.legend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.533363Z","iopub.execute_input":"2024-12-28T04:58:15.533748Z","iopub.status.idle":"2024-12-28T04:58:15.785519Z","shell.execute_reply.started":"2024-12-28T04:58:15.533713Z","shell.execute_reply":"2024-12-28T04:58:15.784299Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Looking at the graph above,\nWe can see two kind of \"themes\" going on. It looks like, within this one signal, there's a low-frequency signal that meanders up and down every 400 data points or so on the X-axis, and in addition, there is a much higher frequency signal that adds a bunch of jagged edges to it. In reality, **this signal is made up of many frequencies**, but at first glance, we can pick out these two by eye. \n\nNext, we will try to pluck these frequencies out mathematically with Python and SciPy. Let's see what running each kind of frequency filter gets us.","metadata":{}},{"cell_type":"markdown","source":"## A Quick Note on Sample Rates and Cutoff Frequencies\n\nWhen making frequency filters, we can technically choose any frequency to be the cutoff frequency with one caveat: the cutoff frequency must be less than the sample rate. Specifically, it must be less than the `sample rate / 2`, which obeys [a rule called the Nyquist Theorem](https://www.techtarget.com/whatis/definition/Nyquist-Theorem)).\n\nIf you run a filter with SciPy and you get an error that your `critical frequency` (or `Wn`) is not within an acceptable range, make sure that whatever frequency you picked is less than half of whatever your sample rate (or `fs`) is.\n\nFor example, if the data's sample rate is `100` Hz, then the cutoff frequency can only be less than `50` (meaning `49` or below). If the data's sample rate is `5000` Hz, then the cutoff frequency can only be less than `2500` (meaning `2499` or below).","metadata":{}},{"cell_type":"markdown","source":"## Low-Pass Filter (cutoff at 2 Hz)\n\nThis filter gets rid of high frequencies, i.e. it lets low frequencies \"pass\" through. We can set the cutoff frequency to be technically any value we want, as long as it follows Nyquist, as explained in the previous section.\n\nWhat we will do in our code is:\n1. create the low-pass filter using SciPy's Butterworth filter function (`butter()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `sosfiltfilt()`\n\nThe output of `sosfiltfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the Butterworth filter with `butter()`, we have the following **important arguments**:\n* a filter order of `4` (determines how steep the cutoff is at the cutoff frequency)\n* a cutoff frequency or `Wn` of `2` Hz (AKA the \"critical frequency\")\n* an `output` of `sos` (which tells it to give us a filter that can be fed into SciPy's SOS filter functions, such as `sosfiltfilt()`)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)","metadata":{}},{"cell_type":"code","source":"# Create a low-pass Butterworth filter\n# It will have a cutoff frequency of 2 Hz and an order of 4. We will discuss order in future Medium articles / Kaggle notebooks.\nbutter_filter = signal.butter(N=4, Wn=2, output='sos', fs=200)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.786976Z","iopub.execute_input":"2024-12-28T04:58:15.787500Z","iopub.status.idle":"2024-12-28T04:58:15.795934Z","shell.execute_reply.started":"2024-12-28T04:58:15.787452Z","shell.execute_reply":"2024-12-28T04:58:15.794851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.796915Z","iopub.execute_input":"2024-12-28T04:58:15.797284Z","iopub.status.idle":"2024-12-28T04:58:15.809803Z","shell.execute_reply.started":"2024-12-28T04:58:15.797244Z","shell.execute_reply":"2024-12-28T04:58:15.808765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feed our new Butterworth low-pass filter and our EEG data into SciPy's sosfiltfilt() function\nlow_pass_filtered_eeg = signal.sosfiltfilt(butter_filter, eeg_data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.813224Z","iopub.execute_input":"2024-12-28T04:58:15.813636Z","iopub.status.idle":"2024-12-28T04:58:15.828521Z","shell.execute_reply.started":"2024-12-28T04:58:15.813597Z","shell.execute_reply":"2024-12-28T04:58:15.827169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(low_pass_filtered_eeg)\nplt.title('EEG after Low-Pass Filter (Cutoff of 2 Hz)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:15.830288Z","iopub.execute_input":"2024-12-28T04:58:15.830678Z","iopub.status.idle":"2024-12-28T04:58:16.207205Z","shell.execute_reply.started":"2024-12-28T04:58:15.830647Z","shell.execute_reply":"2024-12-28T04:58:16.206187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Low-Pass Filter (cutoff at 20 Hz)\n\nWe will do almost the same exact filter as above, except we'll let some higher frequencies through, up to 20 Hz.\n\nThis filter gets rid of high frequencies, i.e. it lets low frequencies \"pass\" through. We can set the cutoff frequency to be technically any value we want, whether it is 100 Hz, 1000 Hz, or 10,000 Hz.\n\nWhat we will do in our code is:\n1. create the low-pass filter using SciPy's Butterworth filter function (`butter()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `sosfiltfilt()`\n\nThe output of `sosfiltfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the Butterworth filter with `butter()`, we have the following **important arguments**:\n* a filter order of `4` (determines how steep the cutoff is at the cutoff frequency)\n* a cutoff frequency or `Wn` of `20` Hz (AKA the \"critical frequency\")\n* an `output` of `sos` (which tells it to give us a filter that can be fed into SciPy's SOS filter functions, such as `sosfiltfilt()`)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)","metadata":{}},{"cell_type":"code","source":"# Create a low-pass Butterworth filter\n# It will have a cutoff frequency of 20 Hz and an order of 4. We will discuss order in future Medium articles / Kaggle notebooks.\nbutter_filter = signal.butter(N=4, Wn=20, output='sos', fs=200)\n\n# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()\n\n# Feed our new Butterworth low-pass filter and our EEG data into SciPy's sosfiltfilt() function\nlow_pass_filtered_eeg_20Hz = signal.sosfiltfilt(butter_filter, eeg_data)\n\nplt.plot(low_pass_filtered_eeg_20Hz)\nplt.title('EEG after Low-Pass Filter (Cutoff of 20 Hz)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:16.208147Z","iopub.execute_input":"2024-12-28T04:58:16.208407Z","iopub.status.idle":"2024-12-28T04:58:16.469351Z","shell.execute_reply.started":"2024-12-28T04:58:16.208384Z","shell.execute_reply":"2024-12-28T04:58:16.467855Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## High-Pass Filter (cutoff at 2 Hz)\n\nWe'll do another filter that is basically the opposite of the first one. It will get rid of the big, meandering waves at 2Hz or below, while keeping the more jagged, higher frequency waves.\n\n**NOTE** the difference below where we tell the `butter()` function that we want a high-pass filter, by giving it the argument `btype='highpass'`. We didn't have to do this for low-pass filters, because `lowpass` is the default for `butter()`.\n\nThis filter gets rid of low frequencies, i.e. it lets high frequencies \"pass\" through. We can set the cutoff frequency to be technically any value we want, whether it is 100 Hz, 1000 Hz, or 10,000 Hz.\n\nWhat we will do in our code is:\n1. create the high-pass filter using SciPy's Butterworth filter function (`butter()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `sosfiltfilt()`\n\nThe output of `sosfiltfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the Butterworth filter with `butter()`, we have the following **important arguments**:\n* a filter order of `4` (determines how steep the cutoff is at the cutoff frequency)\n* a cutoff frequency or `Wn` of `20` Hz (AKA the \"critical frequency\")\n* an `output` of `sos` (which tells it to give us a filter that can be fed into SciPy's SOS filter functions, such as `sosfiltfilt()`)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)\n* again, we use `btype='highpass'` to tell `butter()` that we want a high-pass filter","metadata":{}},{"cell_type":"code","source":"# Create a high-pass Butterworth filter\n# It will have a cutoff frequency of 2 Hz and an order of 4. We will discuss order in future Medium articles / Kaggle notebooks.\nbutter_filter = signal.butter(N=4, Wn=2, output='sos', fs=200, btype='highpass')\n\n# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()\n\n# Feed our new Butterworth low-pass filter and our EEG data into SciPy's sosfiltfilt() function\nhigh_pass_filtered_eeg = signal.sosfiltfilt(butter_filter, eeg_data)\n\nplt.plot(high_pass_filtered_eeg)\nplt.title('EEG after High-Pass Filter (Cutoff of 2 Hz)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:16.470543Z","iopub.execute_input":"2024-12-28T04:58:16.470924Z","iopub.status.idle":"2024-12-28T04:58:16.818958Z","shell.execute_reply.started":"2024-12-28T04:58:16.470882Z","shell.execute_reply":"2024-12-28T04:58:16.817694Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Band-Pass Filter (Range of 40-80 Hz)\n\nThis filter will let a certain range of frequencies in the middle \"pass\" through it. Our example will allow any frequencies between 40 and 80 Hz in the final signal.\n\n**NOTE** the two main differences in the arguments that we give to `butter()` are:\n* we specify a range of cutoff frequencies rather than just one (for example, `Wn=[40, 80]`)\n* we tell it to do bandpass with the argument, `btype='bandpass'`\n\nWhat we will do in our code is:\n1. create the band-pass filter using SciPy's Butterworth filter function (`butter()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `sosfiltfilt()`\n\nThe output of `sosfiltfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the Butterworth filter with `butter()`, we give all of the following **important arguments**:\n* a filter order of `4` (determines how steep the cutoff is at the cutoff frequency)\n* two cutoff frequencies (or `Wn`) of `40` and `80` Hz (AKA the \"critical frequencies\")\n* an `output` of `sos` (which tells it to give us a filter that can be fed into SciPy's SOS filter functions, such as `sosfiltfilt()`)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)\n* again, we use `btype='bandpass'` to tell `butter()` that we want a band-pass filter","metadata":{}},{"cell_type":"code","source":"# Create a band-pass Butterworth filter\n# It will have a cutoff frequency of 40-80 Hz and an order of 4. We will discuss order in future Medium articles / Kaggle notebooks.\nbutter_filter = signal.butter(N=4, Wn=[40, 80], output='sos', fs=200, btype='bandpass')\n\n# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()\n\n# Feed our new Butterworth low-pass filter and our EEG data into SciPy's sosfiltfilt() function\nband_pass_filtered_eeg = signal.sosfiltfilt(butter_filter, eeg_data)\n\nplt.plot(band_pass_filtered_eeg)\nplt.title('EEG after Band-Pass Filter (Allow only 40-80 Hz)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:16.820156Z","iopub.execute_input":"2024-12-28T04:58:16.820425Z","iopub.status.idle":"2024-12-28T04:58:17.115542Z","shell.execute_reply.started":"2024-12-28T04:58:16.820403Z","shell.execute_reply":"2024-12-28T04:58:17.114184Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### The graph above doesn't appear that different from the high-pass filter.\nSometimes, when signals get filtered out, it doesn't appear immediately obvious how this changes the signal. However, this graph is still very different from the original EEG data that we derived it from, as shown below.","metadata":{}},{"cell_type":"code","source":"plt.plot(band_pass_filtered_eeg, label='Band-Pass Filtered')\nplt.plot(eeg_data, label='Original', alpha=0.7)\nplt.title('EEG Before and After Band-Pass Filter (Allow only 40-80 Hz)')\nplt.legend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:17.116657Z","iopub.execute_input":"2024-12-28T04:58:17.116932Z","iopub.status.idle":"2024-12-28T04:58:17.456228Z","shell.execute_reply.started":"2024-12-28T04:58:17.116909Z","shell.execute_reply":"2024-12-28T04:58:17.455213Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Band-Stop Filter (Range of 2-80 Hz)\n\nThis filter will stop a certain range of frequencies from passing through it. Our example will stop any frequencies between 2 and 80 Hz, leaving us with a signal that has only the big, wandering waves less than 2 Hz, combined with much higher frequencies wave of 80 Hz and above.\n\n**NOTE** the two main differences in the arguments that we give to `butter()` are:\n* we specify a range of cutoff frequencies rather than just one (for example, `Wn=[2, 80]`)\n* we tell it to do bandpass with the argument, `btype='bandpass'`\n\nWhat we will do in our code is:\n1. create the band-stop filter using SciPy's Butterworth filter function (`butter()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `sosfiltfilt()`\n\nThe output of `sosfiltfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the Butterworth filter with `butter()`, we give all of the following **important arguments**:\n* a filter order of `4` (determines how steep the cutoff is at the cutoff frequency)\n* two cutoff frequencies (or `Wn`) of `2` and `80` Hz (AKA the \"critical frequencies\")\n* an `output` of `sos` (which tells it to give us a filter that can be fed into SciPy's SOS filter functions, such as `sosfiltfilt()`)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)\n* again, we use `btype='bandstop'` to tell `butter()` that we want a band-stop filter","metadata":{}},{"cell_type":"code","source":"# Create a band-stop Butterworth filter\n# It will have a cutoff frequency range of 2-80 Hz and an order of 4. We will discuss order in future Medium articles / Kaggle notebooks.\nbutter_filter = signal.butter(N=4, Wn=[2, 80], output='sos', fs=200, btype='bandstop')\n\n# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()\n\n# Feed our new Butterworth low-pass filter and our EEG data into SciPy's sosfiltfilt() function\nband_stop_filtered_eeg = signal.sosfiltfilt(butter_filter, eeg_data)\n\nplt.plot(band_stop_filtered_eeg)\nplt.title('EEG after Band-Stop Filter (Reject Frequencies of 2-80 Hz)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:17.457233Z","iopub.execute_input":"2024-12-28T04:58:17.457553Z","iopub.status.idle":"2024-12-28T04:58:17.742860Z","shell.execute_reply.started":"2024-12-28T04:58:17.457513Z","shell.execute_reply":"2024-12-28T04:58:17.741683Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Notch Filter (Exclude 60 Hz)\n\nThis filter will stop a narrow range of frequencies, like a really thin band-stop filter. In this example, we will exclude 60 Hz, because this is the frequency of the electrical noise produced by the power grid in the United States. If you in most other places in the world, your power grid may have noise at 50 Hz. However, this data was recorded in the United States, so it will have noise at 60 Hz.\n\n**Unlike all of the other filters,** we will not be using a Butterworth filter nor the `sosfiltfilt()` function. Instead, we will use a specific notch filter function from SciPy, called `iirnotch()`. We'll then give the filter that this function creates, along with our data, to the `filtfilt()` function to perform the actual filter. The function `filtfilt()` is like `sosfiltfilt()`, except that it expects to receive the filter arguments in `b, a` format as opposed to `sos` format. We're doing this merely because that is the format that `iirnotch()`'s output is in, as it has no `output='sos'` option.\n\n**We could technically get this done** just by giving our band-stop filter a narrower range. However, the `iirnotch()` function was made specifically to produce high-quality notch filters that sharply exclude the exact frequencies we don't want without accidentally getting rid of the nearby frequencies that we do want. **Try both and see what works best for your digital signal!**\n\nWhat we will do in our code is:\n1. create the notch filter using SciPy's Butterworth filter function (`iirnotch()`)\n2. give this new filter as well as our EEG data to a separate filtering function, `filtfilt()`\n\nThe output of `filtfilt()` will be our data without any of the high frequencies that we didn't want.\n\n**ALSO NOTE** that when we create the notch filter with `iirnotch()`, we give all of the following **important arguments**:\n* `w0`: the frequency to get rid of, which is `60` Hz\n* the quality factor, `Q`, which determines how sharp the cutoff around the frequency will be (Q = cutoff frequency / the desired bandwidth to exclude which is often around 2)\n* a sample rate (or `fs`) of `200` Hz (because that is the sample rate that the EEG data's documentation said it was collected at)\n\n","metadata":{}},{"cell_type":"code","source":"# Create a notch filter to get rid of the 60 Hz frequency\n# Note how the output is two coefficients, B and A. We will give these to filtfilt() as separate arguments\nb, a = signal.iirnotch(w0=60, Q=30, fs=200)\n\n# Select the first few seconds of EEG data from channel Fp1 just to illustrate the filter\neeg_data = eeg['Fp1'][:1000].copy()\n\n# Feed our new notch filter and our EEG data into SciPy's filtfilt() function\n# Note how we give B and A (the coefficients of the filter from iirnotch()) to the filter function separately\nnotch_filtered_eeg = signal.filtfilt(b, a, eeg_data)\n\nplt.plot(notch_filtered_eeg)\nplt.title('EEG after Notch Filter (Reject 60 Hz Frequency)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:58:17.743881Z","iopub.execute_input":"2024-12-28T04:58:17.744553Z","iopub.status.idle":"2024-12-28T04:58:18.031868Z","shell.execute_reply.started":"2024-12-28T04:58:17.744519Z","shell.execute_reply":"2024-12-28T04:58:18.030524Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Our EEG didn't change dramatically,\nBut when it comes to medical digital signals, it is important that we don't accidentally interpret the 60 Hz signal from the power grid as a brain signal. That might lead us to make conclusions about the brain that are not true.","metadata":{}},{"cell_type":"markdown","source":"# Some Graphs for Comparison's Sake\n\nWe plotted some filtered and non-filtered graphs on top of each other for illustrative purposes","metadata":{}},{"cell_type":"code","source":"plt.plot(eeg_data, label='Original')\nplt.plot(low_pass_filtered_eeg, label='Low-Pass Filtered', linewidth=2.5)\nplt.title('EEG Before and After Low-Pass Filter (2 Hz)')\nplt.legend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-28T04:59:02.955871Z","iopub.execute_input":"2024-12-28T04:59:02.956312Z","iopub.status.idle":"2024-12-28T04:59:03.275488Z","shell.execute_reply.started":"2024-12-28T04:59:02.956267Z","shell.execute_reply":"2024-12-28T04:59:03.274403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}