{"id":19549339,"url":"https://github.com/cbrnr/bci-event-2021","last_synced_at":"2025-10-29T15:19:35.224Z","repository":{"id":80943688,"uuid":"419981298","full_name":"cbrnr/bci-event-2021","owner":"cbrnr","description":null,"archived":false,"fork":false,"pushed_at":"2023-02-16T16:32:48.000Z","size":64108,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-11-11T03:59:36.238Z","etag":null,"topics":["eeg","python","tutorial"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cbrnr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-10-22T05:51:05.000Z","updated_at":"2024-09-16T23:16:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"b3ebac51-82a6-4439-af25-e7641df7ec0f","html_url":"https://github.com/cbrnr/bci-event-2021","commit_stats":null,"previous_names":["cbrnr/bci-event-2021"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrnr%2Fbci-event-2021","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrnr%2Fbci-event-2021/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrnr%2Fbci-event-2021/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrnr%2Fbci-event-2021/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cbrnr","download_url":"https://codeload.github.com/cbrnr/bci-event-2021/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233093002,"owners_count":18623952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eeg","python","tutorial"],"created_at":"2024-11-11T03:59:48.200Z","updated_at":"2025-10-29T15:19:35.120Z","avatar_url":"https://github.com/cbrnr.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# EEG analysis with MNE-Python and MNELAB\n## Description\nIn this workshop, we will analyze EEG data in Python. We will use [MNE-Python](https://mne.tools), which is currently the most popular Python package for EEG/MEG analysis. In addition, we will showcase how simple tasks can be performed with [MNELAB](https://github.com/cbrnr/mnelab), a graphical user interface for MNE-Python. Using real-world EEG data, we will investigate both induced and evoked activity. Whereas ERD/ERS is used to quantify induced activity in specific frequency bands, we perform simple averaging over epochs to compute event-related potentials (ERPs). We will learn how to perform both types of analyses in this workshop. Some experience with Python is useful, but not required to follow along in this workshop, because we will start from scratch and learn how to set up a working Python environment for EEG analysis.\n\n## Setting up Python\n### Installing Python\nInstalling Python is simple:\n\n- On Windows and macOS, use the [official installers](https://www.python.org/) (make sure to add Python to the path).\n- On Linux, use your package manager (you probably have Python already installed).\n\n### Managing packages\nThis gives us a bare-bones Python environment with hardly any packages installed. We can use the [`pip` command line tool](https://pip.pypa.io/en/stable/) to manage Python packages. Let's find out which packages are currently installed. Open a terminal and enter the following command:\n\n    pip list\n\nThis list will probably be very short and only include `pip` and `setuptools`, two essential packages that are shipped with Python. It is likely that these packages are also already outdated. You can get a list of all outdated packages with:\n\n    pip list --outdated\n\nIf there are outdated packages, you can upgrade each package individually. For example, assuming that `setuptools` is outdated, you can update it with:\n\n    pip install --upgrade setuptools\n\n### Packages for EEG analysis\nNow we need to install packages that allow us to perform EEG analysis. We use `pip install` followed by the package name(s) we would like to install. Specifically, we need the following packages:\n\n    pip install mne mnelab pyxdf scikit-learn ipython\n\nOf course, `mne` and `mnelab` are required for MNE-Python and MNELAB. We also need `pyxdf` to add support for reading [XDF](https://github.com/sccn/xdf/wiki/Specifications) files and `scikit-learn` for training a classifier. The `ipython` package installs [IPython](https://ipython.org/), an enhanced interactive Python interpreter which is much more convenient to use than the default `python` interpreter.\n\n## Setting up Visual Studio Code\nA good editor is essential for writing Python code. Although you can use your favorite editor, this workshop uses Visual Studio Code with its Python extension.\n\n### Installing Visual Studio Code\nHead over to the [Visual Studio Code website](https://code.visualstudio.com/), download the latest version for your platform, and hit install.\n\n### Installing the Python extension\nStart Visual Studio Code and install the Python extension (available in the Extensions section in the left sidebar). After that, open the command palette (\u003ckbd\u003eCtrl\u003c/kbd\u003e\u003ckbd\u003eShift\u003c/kbd\u003e\u003ckbd\u003eP\u003c/kbd\u003e on Windows/Linux and \u003ckbd\u003e⌘\u003c/kbd\u003e\u003ckbd\u003eShift\u003c/kbd\u003e\u003ckbd\u003eP\u003c/kbd\u003e on macOS) and type in \"linter\". Select the \"Python: Select Linter\" command and choose \"flake8\". After that, a confirmation popup should appear, and you need to click on \"Install\" (this runs `pip install flake8` in the background, which you could also do manually on the command line if you want).\n\nNow we have everything we need to analyze EEG data! In Visual Studio Code, open a new terminal with the command \"Terminal: Create New Terminal\" (or \"View: Toggle Terminal\") and run `ipython`. We will use this interactive interpreter window to run our code.\n\n## ERD/ERS analysis with MNE-Python\n### Loading the data\nThe example motor imagery (MI) data set we will be analyzing is available in multiple [XDF](https://github.com/sccn/xdf/wiki/Specifications) files. MNE-Python does not support this file format out of the box, but we can use the [pyxdf](https://github.com/xdf-modules/pyxdf/tree/main) package and MNELAB to import the data.\n\n```python\nfrom pyxdf import match_streaminfos, resolve_streams\n```\n\nFirst, let's list all streams contained in a specific XDF file:\n\n```python\nresolve_streams(\"MI_BCI2021_AK_01.xdf\")\n```\n\nIn this particular case, there are only two streams. The first stream (with a `stream_id` of `1`) contains 35 EEG channels (sampled at 500Hz), whereas the second stream (with `stream_id` of `2`) contains markers.\n\nAlthough each stream ID is unique within a given XDF file, this might not be the case across multiple files. Indeed, the EEG stream is associated with a `stream_id` of `2` (not `1` as before) in the third example file:\n\n```python\nresolve_streams(\"MI_BCI2021_AK_03.xdf\")\n```\n\nWe will use the `read_raw_xdf()` function from MNELAB to import XDF files and convert the data to a standard format that MNE-Python understands (a so-called [`Raw` object](https://mne.tools/stable/generated/mne.io.Raw.html) which represents continuous data).\n\n```python\nfrom mnelab.io.xdf import read_raw_xdf\n```\n\nHowever, we need to specify the stream ID we would like to load, for example:\n\n```python\nraw = read_raw_xdf(\"MI_BCI2021_AK_01.xdf\", stream_ids=[1])\nraw = read_raw_xdf(\"MI_BCI2021_AK_03.xdf\", stream_ids=[2])\n```\n\nThis is cumbersome if all we really want to import is the EEG stream (and we don't care about its stream ID). In this case, we can use `pyxdf.match_streaminfos()` to automatically query the XDF file for the stream ID of the first EEG stream:\n\n```python\nstreams = resolve_streams(\"MI_BCI2021_AK_01.xdf\")\nstream_id = match_streaminfos(streams, [{\"type\": \"EEG\"}])[0]\nraw = read_raw_xdf(\"MI_BCI2021_AK_01.xdf\", stream_ids=[stream_id])\n```\n\nNote that the marker stream is automatically available as annotations associated with the `annotations` attribute:\n\n```python\nraw.annotations\n```\n\nHere's what the different annotation values mean for this particular file (this information is not standardized and needs to be retrieved from the documentation or someone familiar with the particular recording):\n- 1: trial start\n- 2: arrow pointing left\n- 3: arrow pointing right\n- 4: arrow pointing down\n- 8: trial end\n\nOther metadata associated with the `raw` object is available in the `info` attribute:\n\n```python\nraw.info\n```\n\nBefore we continue to inspect the data, let's load and concatenate all four example files into a single `Raw` object:\n\n```python\nimport mne\n\nraws = []\nfor fname in (\"MI_BCI2021_AK_01.xdf\", \"MI_BCI2021_AK_02.xdf\",\n              \"MI_BCI2021_AK_03.xdf\", \"MI_BCI2021_AK_04.xdf\"):\n    stream_id = match_streaminfos(resolve_streams(fname), [{\"type\": \"EEG\"}])[0]\n    raws.append(read_raw_xdf(fname, stream_ids=[stream_id], fs_new=500))\n\nraw = mne.concatenate_raws(raws)\ndel raws\n```\n\nNow `raw` contains data from all four files. Note that we pass `fs_new=500` to resample all EEG streams to exactly 500\u0026thinsp;Hz. This is necessary, because XDF stores EEG (and other time series) with time stamps and does not rely on an a prior fixed sampling frequency.\n\n### Preprocessing\nConcatenating our example files introduced new `\"BAD boundary\"` and `\"EDGE boundary\"` annotations:\n\n```python\nraw.annotations\n```\n\nWe can count the occurrence of each annotation type:\n\n```python\nimport numpy as np\n\nnp.unique(raw.annotations.description, return_counts=True)\n```\n\nThese new annotations indicate the boundaries between the original data, and we can safely ignore them in our analysis (meaning that we do not have to remove them explicitly).\n\nWe already saw that the data contains 35 channels. Let's take a look at the associated channel names:\n\n```python\nraw.info[\"ch_names\"]\n```\n\nThe last three channels are labeled `ACC_X`, `ACC_Y`, and `ACC_Z`, which implies that they contain acceleration data and not EEG. Therefore, we will remove them from further analysis:\n\n```python\nraw.drop_channels([\"ACC_X\", \"ACC_Y\", \"ACC_Z\"])\n```\n\nThe reference used in this recording is FCz, which is not part of the data channels (because by definition it is always equal to zero). Let's add this reference channel to the data, which will be useful when we later re-reference to other channels:\n\n```python\nraw.add_reference_channels(\"FCz\")\n```\n\nBecause we have standardized channel labels, we can assign a so-called montage (which contains channel locations in 3D space). We can use the built-in `\"easycap-M1\"` montage for this purpose:\n\n```python\nraw.set_montage(\"easycap-M1\")\n```\n\nHere's a visualization of all channel locations on a cartoon head:\n\n```python\nraw.plot_sensors(show_names=True)\n```\n\nLet's now re-reference to the average of TP9 and TP10:\n\n```python\nraw.set_eeg_reference([\"TP9\", \"TP10\"])\n```\n\nJust as a sanity check, the average of these two channels (which we just set as the new reference) should be zero:\n\n```python\nnp.allclose(raw.get_data([\"TP9\", \"TP10\"]).mean(axis=0), 0)\n```\n\nLet's take a look at the continuous EEG data:\n\n```python\nraw.plot(n_channels=33)\n```\n\nWe could use this browser to manually mark segments containing artifacts, but we'll skip that in the interest of time. Luckily, the data looks pretty clean anyway.\n\n### Epoching\nNext, we are going to epoch the data around events of interest (2, 3, and 4). First, we have to create events from existing annotations (these are two very similar concepts, but mostly for historical reasons creating epochs requires events and does not work with annotations).\n\n```python\nevents, _ = mne.events_from_annotations(raw)\n```\n\nEvents are represented as a NumPy array with three columns. The first column contains event onsets, the second column is almost always not interesting, and the last column contains event types. Because we are only concerned with event types 2, 3, and 4, we filter these events with a standard indexing operation:\n\n```python\nevents = events[np.in1d(events[:, 2], (2, 3, 4)), :]\n```\n\nWe should have 120 events, three events per epoch:\n\n```python\nevents.shape\n```\n\nUsing this event array, we can take the continuous data and create epochs ranging from 2 seconds before to 6 seconds after each event. In other words, an epoch comprises data from -2 to 6 seconds around an event. In addition, we will only consider three channels C3, Cz, and C4 in our analysis.\n\n```python\ntmin, tmax = -2, 6\nepochs = mne.Epochs(\n    raw,\n    events,\n    dict(left=2, right=3, feet=4),\n    tmin,\n    tmax,\n    picks=(\"C3\", \"Cz\", \"C4\"),\n    baseline=None,\n    preload=True\n)\n```\n\n### ERD/ERS maps\nFinally, we compute time/frequency ERD/ERS maps using `tfr_multitaper()` as follows:\n\n```python\nimport matplotlib.pyplot as plt\nfrom matplotlib.colors import TwoSlopeNorm\nfrom mne.time_frequency import tfr_multitaper\n\nfreqs = np.arange(2, 31)\nbaseline = -1.5, -0.5\nvmin, vmax = -1, 1.5\ncnorm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)\nfor event in epochs.event_id:  # for each condition\n    fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n    tfr, itc = tfr_multitaper(epochs[event], freqs=freqs, n_cycles=freqs)\n    tfr.crop(-1.5, 5.5).apply_baseline(baseline, mode=\"percent\")\n    itc.crop(-1.5, 5.5)\n    tfr.plot(\n        vmin=vmin,\n        vmax=vmax,\n        title=event,\n        axes=axes,\n        cmap=\"RdBu\",\n        cnorm=cnorm,\n        show=False\n    )\n    for i, ax in enumerate(axes):\n        ax.set_title(epochs.info[\"ch_names\"][i])\nplt.show()\n```\n\nThis includes frequencies from 2 to 30\u0026thinsp;Hz. The baseline interval ranges from -1.5 to -0.5\u0026thinsp;s. To avoid boundary effects, we crop the original time interval (-2 to 6\u0026thinsp;s) to -1.5 to 5.5\u0026thinsp;s. We also set the color range to values from -1 (maximum ERD) to 1.5 (maximum ERS). The `cnorm` object makes sure that white (the center color in the color map `\"RdBu\"`) is mapped to the value 0 (neither ERD nor ERS).\n\n### Classifying motor imagery\nIn BCI applications, brain activity needs to be classified in real-time. We will try to train a simple classifier on our example data using only two of the three motor imagery conditions, namely left hand versus feet. We will create our feature matrix `X` and label vector `y` from epoched data, this time retaining all channels:\n\n```python\nepochs = mne.Epochs(\n    raw,\n    events,\n    dict(left=2, feet=4),\n    tmin,\n    tmax,\n    baseline=None,\n    preload=True\n)\nX = epochs.get_data()\ny = epochs.events[:, 2]\n```\n\nAs an instructive example, we will use CSP on bandpass-filtered epochs in combination with a logistic regression classifier. Note that although CSP has been shown to work well with motor imagery data, there are improved methods that might yield better results. Furthermore, we will not tune any (hyper)parameters here, but simply use some default values. Finally, as we could already observe in the ERD/ERS maps, the imagery conditions do not seem to be particularly distinct from each other (but this is not surprising because patterns vary considerably across individuals).\n\nWith that out of the way, let's create a pipeline consisting of a bandpass filter (between 8–30\u0026thinsp;Hz), PCA (which retains the 30 largest components), CSP, and logistic regression. We are going to need the following imports:\n\n```python\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.decomposition import PCA\nfrom mne.decoding import (\n    CSP,\n    FilterEstimator,\n    UnsupervisedSpatialFilter,\n    cross_val_multiscore\n)\n```\n\n```python\nclf = make_pipeline(\n    FilterEstimator(epochs.info, 8, 30),\n    UnsupervisedSpatialFilter(PCA(30)),\n    CSP(),\n    LogisticRegression()\n)\n```\n\nNow we can train and evaluate this pipeline in a 10-fold cross-validation scheme:\n\n```python\nscores = cross_val_multiscore(clf, X, y, cv=10)\n```\n\nThe array `score` contains the classification accuracy for each fold, and we could compute the mean as follows:\n\n```python\nscores.mean()\n```\n\nA mean cross-validation accuracy of around 72% is not too shabby given that we used default parameters everywhere.\n\n## ERD/ERS analysis with MNELAB\nAlright, let's try to reproduce this workflow with the graphical user interface of MNELAB. Type `mnelab` or `python -m mnelab` in a terminal to start MNELAB.\n\n### Loading the data\nThe main window looks pretty empty initially. In fact, almost all commands are disabled until you load a data set. Let's start with loading the first example file. Click on the \"Open\" icon in the toolbar or select \"File\" – \"Open...\" and select the file \"MI_BCI2021_AK_01.xdf\" in the dialog window. Because XDF files can contain multiple streams, another dialog window appears listing all streams contained in the file (along with some basic information such as the name, type, number of channels, data format, and sampling frequency). Only the EEG stream can be selected (marker streams are automatically imported), so click on \"OK\".\n\nNow the MNELAB main window shows information on the currently active file (which is highlighted in the sidebar on the left).\n\nLet's now repeat this process for the remaining three example files. When you are done, you should see these four files in the sidebar (remember that only one file is active at a time).\n\nWe will now concatenate these data sets. First, let's select the first file in the sidebar. Then, select \"Edit\" – \"Append data...\", drag the three listed data sets from \"Source\" to \"Destination\", and confirm with \"OK\". A new data set in the sidebar appears named \"MI_BCI2021_AK_01 (appended)\". Let's rename it to \"MI_BCI2021_AK\" by double-clicking on the entry in the sidebar and editing the name accordingly.\n\nSince we don't need the four individual example data sets anymore, we can select and close each one of them (\"File\" – \"Close\").\n\n### Preprocessing\nNext, we drop the three acceleration channels by selecting \"Edit\" – \"Pick channels...\". In the dialog window, we select only those channels that we would like to pick. Or rather, since all channels are already selected we deselect the last three channels by holding down \u003ckbd\u003eCtrl\u003c/kbd\u003e (on Windows and Linux) or \u003ckbd\u003e⌘\u003c/kbd\u003e (on macOS) and clicking on each channel. After confirming with \"OK\", we are asked if we want to overwrite the existing data set – let's click on \"Yes\" (we don't need the previous data set anymore).\n\nThe data set name changed to \"MI_BCI2021_AK (channels dropped)\" – if you don't like the name, feel free to change it!\n\nNext up is re-referencing the data. First, we will add the current reference FCz as a normal channel and then reference all channels to the mean of TP9 and TP10. To do this, select \"Edit\" – \"Set reference...\". Select \"Channel(s)\", enter \"FCz\", and hit \"OK\" (overwriting the existing data set). Second, open the same dialog again and enter \"TP9,TP10\" in the \"Channel(s)\" field. Now the data is referenced to the average of those two channels.\n\nBecause we have channel labels according to the standardized extended 10–20 system, we can assign channel locations to these labels. Click on \"Edit\" – \"Set montage...\" and select \"easycap-M1\". To confirm if the assigned locations are correct, click on \"Plot\" – \"Channel locations\" to create a two-dimensional cartoon.\n\n### Epoching\nOur next step is to epoch the data. Historically, epoching requires events, but our data contains only annotations. Since events and annotations are two implementations of the same underlying idea, we can convert between these representations. In our case, we can select \"Tools\" – \"Create events from annotations\", which adds 360 events to the data. We can use these events to create epochs by selecting \"Tools\" – \"Create epochs...\". In the dialog window, we select events 2, 3, and 4, enter the values -2 and 6 in the fields labeled \"Interval around events\", and deselect \"Baseline correction\". Because we are only interested in C3, Cz, and C4, we then pick these channels using \"Edit\" – \"Pick channels...\".\n\n### ERD/ERS maps\nUsing these epochs, we can compute ERD/ERS maps by clicking on \"Plot\" – \"ERDS maps...\". In the dialog window, we enter 1\u0026thinsp;Hz to 31\u0026thinsp;Hz for the frequency range (step size 1\u0026thinsp;Hz), -1.5\u0026thinsp;s to 5.5\u0026thinsp;s for the time range (because we want to crop 0.5\u0026thinsp;s from the start and end to avoid boundary effects), and -1.5\u0026thinsp;s to -0.5\u0026thinsp;s for the baseline. This creates ERD/ERS maps for each event type (2, 3, and 4 in our case).\n\n## ERP analysis with MNE-Python\n### Loading the data\nThis time, our data set is stored in the Brainvision file format, which consists of three files (.eeg, .vhdr, and .vmrk). The .eeg file contains the EEG data, the .vhdr header file contains metadata such as channel names and sampling frequency, and the .vmrk file contains markers. We will use the function `mne.io.read_raw_brainvision()` to import the data, and we pass the name of the .vhdr file:\n\n```python\nraw = mne.io.read_raw_brainvision(\"VisualOddball_BCI2021_AK_01.vhdr\")\n```\n\n### Preprocessing\nLet's take a look at some properties of the data. We can get a quick overview by inspecting the `info` attribute and calling the `describe()` method:\n\n```python\nraw.info\nraw.describe()\n```\n\nWe can see that the data consists of 31 EEG channels, and sensor locations have been added automatically (because the `dig` field is populated). We also learn that the data was lowpass-filtered at 140\u0026thinsp;Hz, but apparently no highpass filter was used. The sampling frequency is 500\u0026thinsp;Hz. Let's check out the sensor locations (notice that signals are referenced to Cz, which is not part of the data array and therefore this channel also doesn't show up in the montage plot):\n\n```python\nraw.plot_sensors(show_names=True)\n```\n\nThe paradigm is a classic visual oddball paradigm with frequent (the letter \"o\") and rare (the letter \"x\") visual stimuli. The task of the participant was to count the rare letters \"x\". This should elicit a P300, which we will try to visualize in this analysis. Corresponding markers should already be available in the `annotations` attribute:\n\n```python\nraw.annotations\n```\n\nHere, \"Stimulus/S  1\" corresponds to the frequent event \"o\", whereas \"Stimulus/S  2\" corresponds to the rare event \"x\". We do not need the first annotation (which indicates the start of the paradigm), so we delete it:\n\n```python\nraw.annotations.delete(0)\nraw.annotations\n```\n\nBefore we epoch the data, let's apply a 0.1\u0026thinsp;Hz highpass filter to remove slow drifts. Because MNE does not load the data into memory by default, we need to explicitly do that now (or pass `preload=True` when loading the file):\n\n```python\nraw.load_data()\nraw.filter(0.1, None)\n```\n\nIn the interest of time, we skip the artifact correction step here, but the continuous data looks pretty nice already:\n\n```python\nraw.plot()\n```\n\n### Epoching\nNext, we'll create epochs from -200\u0026thinsp;ms to 800\u0026thinsp;ms relative to stimulus onset. This step also includes baseline correction (the time segment from -200\u0026thinsp;ms to 0\u0026thinsp;ms). Epoching requires events, which we need to generate from the existing annotations:\n\n```python\nevents, event_id = mne.events_from_annotations(raw)\n```\n\nIn addition to `events`, we also get a mapping of annotation names to event types in `event_id`:\n\n```python\nevent_id\n```\n\nHowever, we can take this opportunity to create epochs with more descriptive event names. Instead of passing `event_id` to the `Epochs` initializer, we use the following more informative mapping:\n\n```python\nepochs = mne.Epochs(raw, events, dict(o=1, x=2), tmin=-0.2, tmax=0.8, preload=True)\n```\n\nLet's take a look at this object:\n\n```python\nepochs\n```\n\nThis looks correct, we have 120 frequent and 30 rare events. Let's plot the epoched data:\n\n```python\nepochs.plot(events=events)\n```\n\nNow we should perform another round of artifact correction and drop epochs with large artifacts. To demonstrate one option how to find such bad epochs automatically, we will drop all epochs that contain signals larger than 150\u0026thinsp;µV (peak to peak):\n\n```python\nepochs.drop_bad(reject=dict(eeg=150e-6))\n```\n\n### Averaging\nWe are now ready to average epochs within the two conditions to compute evoked potentials using the `average()` method on a subset of epochs:\n\n```python\nfrequent = epochs[\"o\"].average()\nrare = epochs[\"x\"].average()\n```\n\nThere are several plotting methods for these evoked objects, for example:\n\n```python\nfrequent.plot(gfp=True)\nrare.plot_joint()\n```\n\nLet's focus on channel Pz and visualize both conditions in one plot:\n\n```python\nmne.viz.plot_compare_evokeds(dict(rare=rare, frequent=frequent), picks=\"Pz\")\n```\n\nThis shows a nice P300 for the rare condition. To improve this visualization further, we could add a 95% confidence interval around each evoked time course:\n\n```python\nmne.viz.plot_compare_evokeds(\n    dict(rare=list(epochs[\"x\"].iter_evoked()), frequent=list(epochs[\"o\"].iter_evoked())),\n    picks=\"Pz\"\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbrnr%2Fbci-event-2021","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcbrnr%2Fbci-event-2021","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbrnr%2Fbci-event-2021/lists"}