Variables#

A simple problem as motivation - spike detection#

To have a problem to start with, let’s assumer we want to detect the spikes in this voltage trace.

A spike is a peak in the voltage trace exceeding a threshold value.

More specifically, we are interested in when the spikes occur - the spikes times.

How would you solve this?#

Let’s break this down into a sequence of simple steps, like a recipe:

  1. weigh 100 grams flour

  2. take 3 eggs

  3. mix eggs and floor in a bowl

  4. beat it!

Now try to explain to your grandma or a 5-year old how you would extract the time of each spike:

What do we need to know to detect spikes using python?#

  • Present data in code (individual voltage values, manipulate them and store the results) - variables

  • Compare variables (voltage to threshold) - boolean values

  • Perform different actions based on the value of a variable (only keep the position if the voltage exceeds the threshold) - if-else statements

  • Present and access data in a time series of voltage values - lists

  • Perform an action for each element in a sequence of values (inspect voltage values one-by-one) - for loops

  • Separate data and logic so we can use the same code for new recordings - functions

  • Apply this to multi data files

  • Plot and save the results

Presenting data in code#

Why have variables?#

You can work directly with numbers and use python as a calculator:

1 + 4
5

Try it yourself by clicking into the cell above and changing the numbers.

You can execute the code using Control+Enter.

Variables abstract away specific data “types”/”roles” from the specific data values#

Working with numeric values directly isn’t very general, since you tie your code to specific numeric values.

Variables are a way to make your code more general (and thereby more useful), by separating specific data values from the general computation. You can think of the variable as a storage container: it can store information that you can access via its name and manipulate in your program.

A variable is created with the syntax name = value.

  • name can be any combination of characters, underscores and numbers (as long as it does not start with a number)

  • value can be a number, text (or an arbitrary “python object”).

That way, you can express your program’s computation in general terms - as the manipulation of variables.

What is a Variable? In computer programming, a variable has a name and contains a value. A variable is like a box. If you labeled the box as toys and put a yo-yo inside it, in programming terms, toys is the variable name, and yo-yo is the value.

In math, the = is used to test for equality (\(a = b\)). In python, we use it for assigning values to variables. Double equal signs == test for equality in python.

Computing firing rates#

Back to neuroscience: Say we want to compute the firing rate of a neuron. We have counted the number of spikes (132) our neuron fired during a recording of 16 seconds. The firing rates - the number of spikes per second (1/s=Hz) - is given by the ratio of both:

132 / 16
8.25

But this is

  1. not very general and

  2. not very readable.

First, we directly tie the computation - calculating the ratio - to the data values, if we want to compute the firing rate for another neuron, we have to meddle with the code. This becomes more relevant if the computation becomes more complicated.

Second, it’s unclear what 132 / 16 means without knowing the context of the code - these numbers could mean anything.

Variables with informative names provide context - it’s clear from looking at the code what is being computed!

A variable is created and assigned a value, using the = character, like in math: x = 10. Here, x is the name of the variable and 10 is it’s value.

We can assign the number of spikes and the duration of experiments to two variables, n_spikes and duration and compute the firing rate as their ratio:

n_spikes = 132
duration = 16  # seconds
firing_rate = n_spikes / duration  # 1/seconds=Hz
firing_rate
8.25

Note 1: The # character allows you to comment your code - everything following # will be treated as a comment and not as code that is executed by python.

Note 2: The value of the last line in a cell, in this case firing_rate, is printed below the cell automatically.

By changing the value of the different variables, n_spikes and duration, you can run the same computation, n_spikes / duration, on different data (for instance, different trials from the same experiment). This is useful if the computation is more complicated.

Whenever you type a variable name in code it will be replaced by the value (in this case, the number) it is referring to.

Above, the expression firing_rate = n_spikes / duration is treated the following by Python:

  1. The value of the variable n_spikes is looked up as 132

  2. n_spikes in n_spikes / duration is replaced by 132. The expression is now 132 / duration

  3. the value of the variable duration is looked up as 16

  4. duration in 132 / duration is replaced by 16. The expression is now 132 / 16

  5. The computation is performed and the result, 8.25 assigned the variable firing_rate.

Clicker question “my_var” (Click me!)

What will be the value of x after executing this code?

my_var = 2
my_var = my_var + 1

Dealing with text#

How about data that is not numeric, like names?

Simply assigning letters to a variable does not work - this will throw an error:

name_of_the_whale = Keiko
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 name_of_the_whale = Keiko

NameError: name 'Keiko' is not defined

This is because letters and words are interpreted as variable names. In the above example, python assumes that Keiko is a variable and looks up the value of the Keiko variable, but a variable with the name Keiko does not exist! Hence the NameError

To tell python that the value of your variable is text, we wrap it in '...' or "...". Variables with text as values are called strings.

name_of_the_whale = 'Keiko'
speed = "Mach 2"
year = "1968"

Note on errors When you write “invalid” code, python does not make your computer crash, but generates an error with a (sometimes cryptic) description of the cause of the error. In many programming languages, errors are often called exceptions.

Note: Functions#

To print the value of variables in the notebook, we relied on jupyter’s functionality of printing the result of the last line in a cell. To print the value of variables whenever you want, you can use print, which is something new: a function. More about functions (and how to write them yourself) later - they are a great way of structuring and reusing your code and hiding away complexity.

For now, you only need to know this:

  • To print the value of a variable put the variable name in parentheses after print: print(variable_name).

  • To print the value of multiple variables, separate them by commas: print(variable_name1, variable_name2).

  • You can also print text directly, for instance, to combine descriptive text with printing a variable: print("This value of the aptly named variable 'variable_name1' is", variable_name1)

Working with numerical variables - arithmetics on numbers#

Back to numbers. To perform computations, you need mathematical operations. These are common (and not so common) operations defined in python:

Symbol

Computation performed

+

addition

-

subtraction

*

multiplication

/

division

**

power (\(2^3\)=2**3)

//

floor division (yields the integer part of the division)

%

modulo (yields the remainder of the division)

# Examples
a = 16
b = 3


print(a + b, '= a + b (addition)')
print(a - b, '= a - b (subtraction)')
print(a * b, '= a * b (multiplication)')
print(a / b, '= a / b (division)')
print(a ** b, ' = a ** b (power)')

print('Plus some weird stuff that we will ignore for now:')

print(a // b, '= a // b (Floor Division yields the integer part of the quotient.)')
print(a % b, '= a % b (Modulo yields the remainder of division.)')
19 = a + b (addition)
13 = a - b (subtraction)
48 = a * b (multiplication)
5.333333333333333 = a / b (division)
4096  = a ** b (power)
Plus some weird stuff that we will ignore for now:
5 = a // b (Floor Division yields the integer part of the quotient.)
1 = a % b (Modulo yields the remainder of division.)

Manipulating variables#

With that we can create and manipulate variables, and print intermediate results.

Clicker question “cdcc” (Click me!)

The code below will print four numbers. Can you predict what will be printed?

c = 1
print(c)

d = c + 5
print(d)

print(c)
c = c + 8
print(c)

Types of variables#

Variables come in different flavors - so far, we have encountered two kinds of variables: numbers and text.

Kinds of variables are called types. These are the most important ones:

  • boolean variables: bool- only two values: True or False

  • integer numbers: int - number without a decimal point (1, 2, 103241)

  • floating point numbers: float - number with a decimal point (3.141)

  • (complex numbers - not covered here: complex)

  • string: str: sequence of characters (“yes”, ‘no’)

These types of variables exist in almost any programming language!

Determining the type of variables#

Wrong variable types can be a source of errors in your code. Below, an error is raised because one cannot add a variable of type string to a number in python. It is therefore often useful to determine the type of a variable.

a = 15.6
b = '12.4'
a + b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 3
      1 a = 15.6
      2 b = '12.4'
----> 3 a + b

TypeError: unsupported operand type(s) for +: 'float' and 'str'

The type function returns the type of a variable:

type(a), type(b)
(float, str)

Changing variable types#

You can change the type of a variable - cast it to another type - using bool, int, float, str as functions, with the variable you want to cast as the argument:

b_as_str = '12.4'
b_cast_to_float = float(b_as_str)  # cast a from a string to a floating point
type(b_as_str), type(b_cast_to_float)
(str, float)

You can turn a boolean or a number into a text using str:

str(True), str(10)
('True', '10')

You can turn a string to a number using int or float. But this works only if the string variable contains only numerical data:

age_as_str = '10'
age_as_number = int(age_as_str)
print(age_as_str, age_as_number)
type(age_as_str), type(age_as_number)
10 10
(str, int)

Careful - casting sometimes works in unexpected ways. For instance, any non-zero number and non-empty string is True in python.

print('bool(0) =', bool(0))
print('bool(1.6) =', bool(1.6))
print('bool(10) =', bool(10))
print('bool(-1) =', bool(-1))
print('bool("True") =', bool("True"))
print('bool("False") =', bool("False"))
print('bool('') =', bool(''))
bool(0) = False
bool(1.6) = True
bool(10) = True
bool(-1) = True
bool("True") = True
bool("False") = True
bool() = False

Casting does not work for all values of variables. For instance, if the string variable contains non-numeric characters, letters or even just a blank space, python produces a ValueError because the value of the argument is not “right”:

age_as_str = '10years'
age_as_number = int(age_as_str)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[13], line 2
      1 age_as_str = '10years'
----> 2 age_as_number = int(age_as_str)

ValueError: invalid literal for int() with base 10: '10years'
age_as_str = '10 0'
age_as_number = int(age_as_str)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[14], line 2
      1 age_as_str = '10 0'
----> 2 age_as_number = int(age_as_str)

ValueError: invalid literal for int() with base 10: '10 0'

Naming variables#

You can name a variable almost anything you want.

It needs to start with a letter or “_” and can contain alphanumeric characters (letters or numbers) plus underscores (“_”):

# Valid names
cool_variable = 10
_cool_variable = 10
password_123 = 'bad'
# Invalid names
10th_number = 3.34
  Cell In[16], line 2
    10th_number = 3.34
     ^
SyntaxError: invalid decimal literal
a+b = 27
  Cell In[17], line 1
    a+b = 27
    ^
SyntaxError: cannot assign to expression here. Maybe you meant '==' instead of '='?

Certain key words, however, are reserved for the language (no need to remember them - just keep this in mind so you know how to interpret the resulting error): and, as, assert, break, class, continue, def, del, elif, else, except, exec, finally, for, from, global, if, import, in, is, lambda, not, or, pass, raise, return, try, while, with, yield

# Example
for = 3
  Cell In[4], line 2
    for = 3
        ^
SyntaxError: invalid syntax

Names should be short (but not too short) and descriptive#

  • a is very short but meaningless - it is unclear what type of information a stores.

  • concentration or subject_name is a bit longer but its meaning is evident from the name.

  • name_of_the_first_subject_on_monday_morning is too long.

a = 1  # assign value 1 to variable with name a
concentration = 0.1  # mM
subject_name = "Mabel"  # better variable name as it is descriptive
name_of_the_first_subject_on_monday_morning = "Thelonious"  # too long

Keeping track of variables and their values#

How do you keep track of variables and their values? The kernel is the thing that executes your code. It also stores your namespace - all variables and code that you have executed so far and that are stored in your computer memory.

If you re-run your code cells out of order, it may be hard to know what’s in your namespace. In that case, you can clear and re-launch the kernel, to start from scratch, with a clean namespace. This will erase any variables stored in memory and reset python to its original state.

You can list the contents of your namespace with %whos:

%whos
Variable                                      Type     Data/Info
----------------------------------------------------------------
a                                             int      1
age_as_number                                 int      10
age_as_str                                    str      10 0
b                                             str      12.4
b_as_str                                      str      12.4
b_cast_to_float                               float    12.4
concentration                                 float    0.1
cool_variable                                 int      10
duration                                      int      16
firing_rate                                   float    8.25
n_spikes                                      int      132
name_of_the_first_subject_on_monday_morning   str      Thelonious
name_of_the_whale                             str      Keiko
password_123                                  str      bad
speed                                         str      Mach 2
subject_name                                  str      Mabel
year                                          str      1968

Messing up and restoring the namespace#

It is surprisingly easy to mess up you namespace. For instance, you can “overwrite” function names with variables in python.

Remember the print function? Let’s create a variable named print and assign a value to it:

print = 'no, never do this!'

Now let’s print the value of the print variable:

print(print)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[2], line 1
----> 1 print(print)

TypeError: 'str' object is not callable

That fails, as does

print(10)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 print(10)

TypeError: 'str' object is not callable

Clicker question “namespace”(Click me!)

What do you do now?

Why is this happening? Let’s first inspect the namespace. This reveals that print is of type string now, and not a function.

%whos
<function print(*args, sep=' ', end='\n', file=None, flush=False)>

After restarting the kernel, we will see that the type of print is now “builtin_function_or_method”:

print(type(print))  # this will return "builtin_function_or_method"
<class 'builtin_function_or_method'>

Re-using and updating variables#

Alice and Bob count mosquitoes. Alice, to store her count, creates a variable with name n_alice_mosquitoes and value 1 (she has counted only one mosquito so far).

n_alice_mosquitoes = 1  # create a new variable `n_alice_mosquitoes` and assign the value 1 to `n_alice_mosquitoes`
# n_alice_mosquitoes
%whos

n_mosquitoes = n_tim_mosquitoes + 20
print(n_mosquitoes)
Variable             Type    Data/Info
--------------------------------------
n_alice_mosquitoes   int     1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 5
      2 # n_alice_mosquitoes
      3 get_ipython().run_line_magic('whos', '')
----> 5 n_mosquitoes = n_tim_mosquitoes + 20
      6 print(n_mosquitoes)

NameError: name 'n_tim_mosquitoes' is not defined

Now she can perform a computation using n_alice_mosquitoes. For instance, Bob has counted 10 more mosquitoes - so we add 10 to her own count and store the result in a new variable, n_mosquitoes, for the total count:

Clicker question “mosquitoes 1” (Click me!)

What will be the value of n_mosquitoes after executing this code after having run the cell above?

n_mosquitoes = n_alice_mosquitoes + 10
print(n_mosquitoes)

What is going on here?

In the above expression, n_alice_mosquitoes + 10, python sees the variable name, n_alice_mosquitoes, and looks up it’s value, 1 This works since we have created and assigned a value to n_alice_mosquitoes in the previous cell.

Python then replaces the name n_alice_mosquitoes with the value 1 and performs the computation (it evaluates the expression).

n_alice_mosquitoes + 10 is evaluated as 1 + 10.

n_mosquitoes = n_alice_mosquitoes + 10 means that the result of the operation, the value 11, is saved in a new variable, with name n_mosquitoes.

Note that above we exploited that what you do in one cell transfers to the next:

  • we defined n_alice_mosquitoes in one cell

  • and used it in a computation in the next cell

Clicker question “mosquitoes 2” (Click me!)

What will be the value of n_mosquitoes after executing this code after having run the first and second cells above?

n_mosquitoes = n_tim_mosquitoes + 20
print(n_mosquitoes)

This will throw an error, as we cannot use a variable we have not defined before: Python does not know what n_tim_mosquitoes refers to!

The fact that we can re-use variables across cells allows us to successively build up our analysis code, cell by cell.

Spike detection with python#

  • Present data in code (individual voltage values, manipulate them and store the results) - variables

  • Compare variables (voltage to threshold) - boolean values

  • Perform different actions based on the value of a variable (only keep the position if the voltage exceeds the threshold) - if-else statements

  • Present and access data in a time series of voltage values - lists

  • Perform an action for each element in a sequence of values (inspect voltage values one-by-one) - for loops

  • Separate data and logic so we can use the same code for new recordings - functions

  • Apply this to multi data files

  • Plot and save the results