{ "cells": [ { "cell_type": "markdown", "id": "cef88fcf-db66-4829-8942-9d6e563130b6", "metadata": {}, "source": [ "# Data Explorer\n", "\n", "\"Open   " ] }, { "cell_type": "markdown", "id": "87af53e9-6465-420e-89b9-68f2d9417a38", "metadata": {}, "source": [ "\n", "# Table of Contents\n", "\n", "- [Introduction](#intro)\n", "- [Setup](#setup)\n", "- [Part I. Event detection](#one)\n", "- Time series analyses:\n", " - [Part II. Rate](#two)\n", " - [Part III. ISI](#three)\n", " - [Part IV. Convolution](#four)" ] }, { "cell_type": "markdown", "id": "a752aef5-41be-40e5-bc41-4b3db9045e2e", "metadata": {}, "source": [ "\n", "# Electric Organ Discharge (EOD) Physiology\n", "\n", "As you explore your data, process and analyze it, think about some of the following questions:\n", "- Why were there two sets of differential electrodes (what if there had only been one)?\n", "- Why do events in the signal look different from each other (when do they look similar)?\n", "- How does the sampling rate of the Analog-to-Digital conversion effect the \"raw\" signal and my ability to observe EODs?\n", "- How are EOD events distributed through time?\n", "\n", "***First, go through this analysis using your group's 50kHz recording.*** \n", "Then, use other recordings as needed to address Prompts in your Responses notebook." ] }, { "cell_type": "markdown", "id": "69f4d981-e7af-4b05-8b24-7cf18797a788", "metadata": {}, "source": [ "\n", "# Setup" ] }, { "cell_type": "markdown", "id": "59f1d22f-f621-45d9-9500-c4c0e1c473c8", "metadata": {}, "source": [ "Import and define functions" ] }, { "cell_type": "code", "execution_count": null, "id": "f901b914-647b-4bae-8274-bcdf21f3f80e", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Run this code cell to import packages and define functions \n", "import numpy as np\n", "import pandas as pd\n", "import plotly.express as px\n", "import plotly.graph_objects as go\n", "from plotly.subplots import make_subplots\n", "from scipy import ndimage\n", "from scipy.signal import hilbert,medfilt,resample, find_peaks, unit_impulse\n", "import seaborn as sns\n", "from datetime import datetime,timezone,timedelta\n", "pal = sns.color_palette(n_colors=15)\n", "pal = pal.as_hex()\n", "import matplotlib.pyplot as plt\n", "import random\n", "\n", "from pathlib import Path\n", "\n", "from ipywidgets import widgets, interact, interactive\n", "%config InlineBackend.figure_format = 'retina'\n", "plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/master/nma.mplstyle\")\n", "\n", "print('Task completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "markdown", "id": "95163987-2803-4ede-b7fe-ee41c9275398", "metadata": {}, "source": [ "Mount Google Drive" ] }, { "cell_type": "code", "execution_count": null, "id": "b4449f83-e500-4eef-a49b-991af6c1b6f3", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Run this cell to mount your Google Drive.\n", "\n", "from google.colab import drive\n", "drive.mount('/content/drive')\n", "\n", "print('Task completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "markdown", "id": "2addcf90-10b1-4d8f-87d3-5a33e92193c0", "metadata": {}, "source": [ "Import data digitized with *Nidaq USB6211* and recorded using *Bonsai-rx* as a *.bin* file\n", "\n", "> If you would like sample this Data Explorer, but do not have data, you can download an example from [here](https://drive.google.com/file/d/10cxBdfnEwRv77-dwcReqHyjYv-uLODe4/view?usp=sharing) and then upload the file to Google Colab (or access the file through Drive after uploading it to your Drive). If you are using this example file, the samplerate was 50000 on two channels (each channel was a set of bipolar electrodes perpendicular to each other with a fisn in the middle). " ] }, { "cell_type": "code", "execution_count": null, "id": "547e3de8-1a40-4b00-8efd-3679dc8a99d0", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Specify the file path \n", "#@markdown to your recorded data on Drive (find the filepath in the colab file manager:\n", "\n", "filepath = \"full filepath goes here\" #@param \n", "# filepath = '/Users/kperks/Downloads/bribrach_samplerate100kHz.bin'\n", "\n", "#@markdown Specify the sampling rate and number of channels recorded.\n", "\n", "sampling_rate = 100000 #@param\n", "number_channels = 2 #@param\n", "\n", "# downsample = False #@param\n", "# newfs = 10000 #@param\n", "\n", "#@markdown After you have filled out all form fields, \n", "#@markdown run this code cell to import the data. \n", "\n", "filepath = Path(filepath)\n", "\n", "# No need to edit below this line\n", "#################################\n", "data = np.fromfile(Path(filepath), dtype = np.float64)\n", "data = data.reshape(-1,number_channels)\n", "data_dur = np.shape(data)[0]/sampling_rate\n", "print('duration of recording was %0.2f seconds' %data_dur)\n", "\n", "fs = sampling_rate\n", "# if downsample:\n", "# # newfs = 10000 #downsample emg data\n", "# chunksize = int(sampling_rate/newfs)\n", "# data = data[0::chunksize,:]\n", "# fs = int(np.shape(data)[0]/data_dur)\n", "\n", "time = np.linspace(0,data_dur,np.shape(data)[0])\n", "\n", "print('Data upload completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "code", "execution_count": null, "id": "c8f6f8b1-d153-471f-973c-e71eb4186621", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this code cell to plot your imported data.
\n", "#@markdown Use the range slider to scroll through the data in time.\n", "#@markdown Be patient with the range refresh... the more data you are plotting the slower it will be. \n", "\n", "slider = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,1),\n", " step= 0.05,\n", " readout=True,\n", " continuous_update=False,\n", " description='Data Time Range (s)',\n", " style = {'description_width': '200px'})\n", "slider.layout.width = '600px'\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(x):\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", " starti = int(x[0]*fs)\n", " stopi = int(x[1]*fs)\n", " ax.plot(time[starti:stopi], data[starti:stopi,:])\n", "\n", "w = interact(update_plot, x=slider);" ] }, { "cell_type": "markdown", "id": "c4a161eb-ebbd-40c4-ba93-c77d0bbceb84", "metadata": {}, "source": [ "For a more extensive ***RAW*** Data Explorer than the one provided in the above figure, use the [DataExplorer.py](https://raw.githubusercontent.com/neurologic/Neurophysiology-Lab/main/howto/Data-Explorer.py) application found in the [howto section](https://neurologic.github.io/Neurophysiology-Lab/howto/Dash-Data-Explorer.html) of the course website." ] }, { "cell_type": "markdown", "id": "355f1983-4a4e-4275-8bf3-cc8c9b8a25bb", "metadata": {}, "source": [ "\n", "# Part I. Event Detection\n", "\n", "To analyse the distribution of EOD events acrosst time, we need to figure out the times of each EOD. We will do this by detecting 'peaks' in the signal. Instead of detecting peaks on just one channel, we will use a combination of signals from both channels (do you remember why we would want to do this?). First, the signal on each channel is \"rectified\" (absolute value) and then the signal is summed across all channels (if you recorded more than one). Using this processed signal, we will detect peaks. \n", "\n", "Python has built-in algorithms for detecting \"peaks\" in a signal. However, it will detect *all* peaks. Therefore, we need to specify a **threshold** for peak detection (the minimum height that can count as a peak). " ] }, { "cell_type": "code", "execution_count": null, "id": "e2ad6e77-fcd4-484c-80b4-6dc7a21dd13e", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this cell to create an interactive plot with a slider to scroll \n", "#@markdown through the processed EOD signal from all measurement channels \n", "#@markdown and select an appropraite EOD event detection threshold\n", "\n", "y = data - np.median(data)\n", "y = np.sum(np.abs(y),1)\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,1),\n", " step= 0.1,\n", " readout=True,\n", " continuous_update=False,\n", " description='Time Range (s)',\n", " style = {'description_width': '200px'})\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_yrange = widgets.FloatRangeSlider(\n", " min=np.min(y)-0.5,\n", " max=np.max(y)+0.5,\n", " value=[np.min(y),np.max(y)],\n", " step=0.05,\n", " continuous_update=False,\n", " readout=True,\n", " description='yrange')\n", "slider_yrange.layout.width = '600px'\n", "\n", "slider_threshold = widgets.FloatSlider(\n", " min=np.min(y)-0.1,\n", " max=np.max(y)+0.1,\n", " value=np.mean(y)+np.std(y)*5,\n", " step=0.01,\n", " continuous_update=False,\n", " readout=True,\n", " description='event threshold',\n", " style = {'description_width': '200px'})\n", "slider_threshold.layout.width = '600px'\n", "\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(thresh_,xrange): #,yrange):\n", " if np.diff(xrange)>0:\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", "\n", " d = 0.0003*fs #minimum time allowed between distinct events\n", " r = find_peaks(y,height=thresh_,distance=d)\n", "\n", " eod_times = r[0]/fs\n", "\n", " starti = int(xrange[0]*fs)+1\n", " stopi = int(xrange[1]*fs)-1\n", " \n", " ax.plot(time[starti:stopi], y[starti:stopi], color='black')\n", "\n", " # ax.plot(tmp,color='black')\n", " ax.hlines(thresh_, time[starti],time[stopi],linestyle='--',color='green')\n", " ax.scatter(eod_times,[thresh_]*len(eod_times),marker='^',s=50,color='purple',zorder=3)\n", " # ax.set_ylim(yrange[0],yrange[1])\n", " ax.set_xlim(xrange[0],xrange[1])\n", "\n", " return eod_times\n", "\n", "w_events_ = interactive(update_plot, thresh_=slider_threshold, xrange=slider_xrange)#, yrange=slider_yrange);\n", "display(w_events_)" ] }, { "cell_type": "code", "execution_count": null, "id": "2afcb70a-2eae-4f13-8cab-9e60c569587f", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown After you are satisfied with your threshold setting, \n", "#@markdown run this cell to store the list of EOD times in a variable (an array) called \"eod_times\".\n", "eod_times = w_events_.result\n", "\n" ] }, { "cell_type": "markdown", "id": "48488038-904d-4424-9353-7e72cf2d844c", "metadata": {}, "source": [ "Once you know the times of each peak (each event), we can look at the waveforms of those events. To do this, we plot the signal at the event time and some duration before and after that event time. \n" ] }, { "cell_type": "code", "execution_count": null, "id": "13626631-33fc-4e50-911f-d49749a5b2a7", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Now, run this code cell to create an interactive plot to examine individual EOD events across channels.\n", "#@markdown You can select one channel at a time or hold 'command' or 'control' key on your keyboard while you mouse click to select multiple channels\n", "\n", "\n", "slider_xrange = widgets.FloatSlider(\n", " min=0.05,\n", " max=2,\n", " value=0.6,\n", " step=0.05,\n", " continuous_update=False,\n", " readout=True,\n", " description='xrange (ms)'\n", ")\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_yrange = widgets.FloatRangeSlider(\n", " min=np.min(np.min(data)),\n", " max=np.max(np.max(data)),\n", " value=[np.min(np.min(data)),np.max(np.max(data))],\n", " step=0.01,\n", " continuous_update=False,\n", " readout=False,\n", " description='yrange'\n", ")\n", "slider_yrange.layout.width = '600px'\n", "\n", "slider_eod = widgets.IntSlider(\n", " min=0,\n", " max=len(eod_times),\n", " value=0,\n", " step= 1,\n", " continuous_update=False,\n", " description='EOD number')\n", "slider_eod.layout.width = '600px'\n", "\n", "select_chan = widgets.SelectMultiple(\n", " options=np.arange(np.shape(data)[1]), # start with a single trial on a single bout... it will update when runs ,\n", " value=[0],\n", " #rows=10,\n", " description='Channels',\n", " disabled=False\n", ")\n", "\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(eodi,chan_list,xrange,yrange):\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", " \n", " eod_range = xrange/2\n", " win_ = int(eod_range/1000*fs)\n", " \n", " colors = ['green','purple']\n", " for chan in chan_list:\n", " events = np.asarray([data[(int(fs*t)-win_):(int(fs*t)+win_),chan] for t in eod_times \n", " if (((int(fs*t)-win_)>0) & ((int(fs*t)+win_)\n", "# Part II. Rate" ] }, { "cell_type": "markdown", "id": "bbd0ace1-b503-4e90-86b7-58fa7a16cc63", "metadata": {}, "source": [ "## Average rate\n", "\n", "First, think about how you would calculate the average EOD rate (given your detected EOD times and knowledge of the duration of your recording). \n", "Now, you can implement that calculation with computer code (in this case python). To do so, you will need the following tips...\n", "\n", "
\n", "Tips:\n", "
  • len(variable) : len() is a function used to get the number of elements in an array called *variable*
  • \n", "
  • + - * / are the symbols for addition, subtraction, multiplication, and division
  • \n", "
  • eod_times is a variable that contains the list of EOD times
  • \n", "
    \n", "\n", "In the code cell below, write code that would calculate the average EOD rate in your recording. Store the result as a variable called ```average_rate``` by replacing ```...``` with your equation. " ] }, { "cell_type": "code", "execution_count": null, "id": "8c9f8888-c8a5-4812-abf7-e8d9fd2b899f", "metadata": {}, "outputs": [], "source": [ "average_rate = ..." ] }, { "cell_type": "code", "execution_count": null, "id": "80c7eaad-db54-4393-a7a6-97944e2c28e4", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode:\"form\"}\n", "\n", "#@markdown Run this code cell to print the average rate calculated using your equation. \n", "print(f'The average EOD rate is {average_rate}')" ] }, { "cell_type": "markdown", "id": "65f836b8-6325-4920-b8c8-c9366daffbf5", "metadata": {}, "source": [ "## Subsampling (*bootstrapping*) the average rate\n", "\n", "In order to do statistics and compare EOD rates between different conditions or groups (for example, among different species of fish), we need more than just one estimate of the EOD rate. \n", "> Note that this week you will not explicitly do this statistical comparison. You will just apply this analysis to your own group's fish. \n", "\n", "*Bootstrapping* is an analytic technique used to *subsample* your data. \n", "\n", "In this case, we will calculate a set of EOD rate averages by subsampling the data. Then, we can get the mean and standard deviation of the average EOD rate across those samples/estimates. A *subsample* of the data is a smaller continuous section of the data. \n", "\n", "The script (hidden) in the code cell below randomly selects chunks of time (***duration***) from throughout the total recording. It repeats this random selection process ***N*** times (to provide N subsamples of the data). \n", "\n", "Running the code cell below generates an interactive plot in which you can control the number of subsamples taken from the data and the duration of each subsample. You will see a plot of the average rate of each subsample (each black point in the scatterplot), and the distribution (quantiles) of the set of subsamples in [boxplot](https://en.wikipedia.org/wiki/Box_plot) format." ] }, { "cell_type": "code", "execution_count": null, "id": "7846c49d-3d2e-482d-b8b2-a1a5562c303c", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this code cell to enable the interactive analysis.\n", "#@markdown Explore what changing these parameters does to your result.\n", "\n", "slider_duration = widgets.FloatSlider(\n", " min=0,\n", " max=np.min([15,data_dur/2]),\n", " value=1,\n", " step= 0.001,\n", " readout=True,\n", " continuous_update=False,\n", " description='sample duration (seconds)',\n", " style = {'description_width': '200px'})\n", "slider_duration.layout.width = '600px'\n", "\n", "slider_N = widgets.IntSlider(\n", " min=0,\n", " max=100,\n", " value=10,\n", " step= 1,\n", " readout=True,\n", " continuous_update=False,\n", " description='number of subsamples (N)',\n", " style = {'description_width': '200px'})\n", "slider_N.layout.width = '600px'\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(duration,N):\n", " \n", " rate_ = []\n", " for i in range(N):\n", " t = random.uniform(np.min(eod_times)+duration,np.max(eod_times)-duration)\n", " rate_.append(sum((eod_times>t) & (eod_times\n", "# Part III. ISI\n", "\n", "The time between events is called the *inter-event interval*. Since events in neurons are called *spikes* the metric is called an *inter-SPIKE interval* (***ISI***). In electric fish, the metric is also called the *IPI* (inter-*pulse* interval), which refers to each EOD event as a pulse. \n", "\n", "How would you calculate the ISI from your recording? \n", "Again, first think about it in terms of the equation you would use. Then, use the following tips to implement that equation. \n", "\n", "
    \n", "Tips: \n", "
  • np.diff(variable) : diff() is a numpy module that calculates the numerical difference between each element in a list named variable and returns the result as a list
  • \n", "
  • eod_times is a variable that contains the list of EOD times
  • \n", "
    \n", "\n", "In the code cell below, write a script that performs this calculation. \n", "Store the result as a variable called ```isi```\n" ] }, { "cell_type": "code", "execution_count": null, "id": "79b2d19a-5f07-4c70-b5be-bebd0eebd7c0", "metadata": {}, "outputs": [], "source": [ "isi = ..." ] }, { "cell_type": "code", "execution_count": null, "id": "dce9d126-ae1e-414d-ad00-1648f7cdf49b", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Running this code cell uses your equation to make a scatter plot of ISI at each EOD time.\n", "#@markdown The average isi across the recording will also be printed. Use the time range slider to look at more/less of your data at once. \n", "\n", "print(f'Average isi is {np.mean(isi):0.2f}.')\n", "\n", "# plot the isi at each EOD time.\n", "\n", "\n", "e_time = eod_times[1:]\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,1),\n", " step= 0.1,\n", " readout=True,\n", " continuous_update=False,\n", " description='Time Range (s)',\n", " style = {'description_width': '200px'})\n", "slider_xrange.layout.width = '600px'\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(xrange): #,yrange):\n", " if np.diff(xrange)>0:\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", "\n", " starti = int(xrange[0]*fs)+1\n", " stopi = int(xrange[1]*fs)-1\n", " \n", " mask = ((e_time>xrange[0])&(e_time\n", "# Part IV. Convolution\n", "\n", "Sometimes we want a \"smooth\" estimate of the eod rate/isi over time. \n", "\n", "By *convolving* a waveform with a time series, each event is transformed into a waveform. When all of these event waveforms are added together, you get a continuous signal instead of a discrete time series. This transformation is sometimes called \"smoothing\" and is required before some calculations can be made (such as correlation analysis). \n", "\n", "In this instance, we are using a ***Gaussian filter***. Therefore, the smoothness of the signal is controlled by a parameter called ***sigma*** that you can interactively control with a slider after running the code cell below. \n", "\n", "Details of the function used to accomplish this processing step can be found [at scipy.ndimage.gaussian_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html). For more general information on what a Gaussian is, you can consult the [wikipedia page on the Gaussian distribution](https://en.wikipedia.org/wiki/Normal_distribution). In this case, the *sigma* parameter of the Gaussian filter is equivalent to the *standard deviation* of a Gaussian distribution." ] }, { "cell_type": "code", "execution_count": null, "id": "26a83ac4-7eaa-4958-be9d-32412cdaa5b5", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this code cell to plot the smoothed signal from discrete EOD times. \n", "#@markdown Change the value of ```sigma``` (the standard deviation of the *gaussian kernel* in seconds) to see how that effects the smoothed signal.
    \n", "\n", "slider_sigma = widgets.FloatSlider(\n", " min=0,\n", " max=1,\n", " value=(0.1),\n", " step= 0.01,\n", " readout=True,\n", " continuous_update=False,\n", " description='sigma',\n", " style = {'description_width': '200px'})\n", "slider_sigma.layout.width = '600px'\n", "\n", "slider = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,1),\n", " step= 1,\n", " readout=True,\n", " continuous_update=False,\n", " description='Data Time Range (s)',\n", " style = {'description_width': '200px'})\n", "slider.layout.width = '600px'\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(x,sigma):\n", " \n", " filtered_fs = 1000\n", " sigma = sigma*filtered_fs\n", "\n", " eod_samps = [int(t*filtered_fs) for t in eod_times]\n", "\n", " filtered_time = np.linspace(0,data_dur,int(data_dur*filtered_fs))\n", " filtered_y = unit_impulse(len(filtered_time),eod_samps)\n", " filtered_y = ndimage.gaussian_filter1d(filtered_y,sigma=sigma)*filtered_fs\n", "\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", " starti = int(x[0]*filtered_fs)\n", " stopi = int(x[1]*filtered_fs)\n", " ax.plot(filtered_time[starti:stopi], filtered_y[starti:stopi])\n", " ax.set_xlabel('msec')\n", " ax.set_ylabel('a.u.')\n", "\n", "w = interact(update_plot, x=slider, sigma=slider_sigma);\n" ] }, { "cell_type": "markdown", "id": "71cba766-989d-4b71-b1c0-e2faaad33307", "metadata": {}, "source": [ "Great. That is it for the data processing and analysis tools for this week. You will be implementing these throughout the semester so you will get more practice. " ] }, { "cell_type": "markdown", "id": "7bf20276-7522-4ae5-bf16-a730a37a7059", "metadata": {}, "source": [ "
    \n", "Written by Dr. Krista Perks for courses taught at Wesleyan University." ] }, { "cell_type": "markdown", "id": "d39dace9-7831-4dca-af14-09987f7363c8", "metadata": {}, "source": [ "# Optional Extensions\n", "\n", "When you calculated the EOD rate, you could have done something even more fancy than using the known length of your data recording. \n", "\n", "
    \n", "\n", "

    This is a good time to introduce \"indexing\" lists in python, because eod_times is a list (with the earliest eod time in the first position of the list and the latest eod time in the last position of the list).

    \n", "

    The following table shows, by example, how you would get the value at each position in a list (L) by indexing the list. In this example, the values in the list are t0, t1, t2.

    \n", "
    \n", "\n", "Consider the list where ```L=[t0, t1, t2]```\n", "\n", "
    \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
    If you type: Then you will get: Because...
    L[2]t2you are asking for an offset of 2 positions (start at zero)
    L[-2]t1negative offsets count from the right
    L[1:][t1, t2]a colon \"slices\" a list, which means it returns a section of the list (in this case, from position 1 until the end)
    \n", "
    \n", " \n", "Finally, if you want to print the entire contents of a list to the output of a code cell, use the command ```print(list)``` where \"list\" can be the name of any list (in this case *eod_times*)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1a30994e-4c0d-4411-beb4-a174bc3d7311", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 5 }