Python Tutorial: Level 1

Originally created for the OVGU Cognitive Neuroscience Master's course H.3: Projektseminar Fall 2019

Updated August 2020

Taught by Reshanne Reeder

First, let's create some variables to work with:

In [55]:
subject_nr=2
subject_name='sub2'

You can perform operations on variables, and numerical variables can be used in operations just like regular numbers:

In [56]:
print(subject_nr*2)
print(subject_nr * subject_nr)
4
4

Operations on text variables work a bit differently:

In [15]:
print(subject_name * subject_nr * 2)
print(subject_name + subject_name)
sub2sub2sub2sub2
sub2sub2

If you want to see a space between text, you can simply add a space in quotations " ":

In [16]:
print(subject_name + " " + subject_name)
sub2 sub2

You can also add multiple spaces automatically with the multiplication operation. Note that if you want to use "print" to see your output, you have to add two sets of parentheses here: one around all the information to be multiplied (subject_name + " ") and one around all the information to be printed ((subject_name + " ") * 5):

In [17]:
print((subject_name + " ")*5)
sub2 sub2 sub2 sub2 sub2 

You can also perform operations on lists:

In [18]:
subject_nrs = [1,2,3,4,5]
subject_names = ['sub1','sub2','sub3','sub4','sub5']
print(subject_nrs + subject_names)
[1, 2, 3, 4, 5, 'sub1', 'sub2', 'sub3', 'sub4', 'sub5']

You can only use operations that make sense, like combining two lists with "+". If you try to multiply a list by a non-integer, python will throw an error:

In [19]:
print(subject_nrs * subject_names)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-2e17d3ba3acf> in <module>
----> 1 print(subject_nrs * subject_names)

TypeError: can't multiply sequence by non-int of type 'list'

However, you can multiply lists by an integer:

In [20]:
print(subject_nrs*5)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

You can also perform operations on lists with a variable, if the variable is an integer:

In [21]:
print(subject_nrs*subject_nr)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

There are many other ways of manipulating lists with built-in functions. For example:

In [22]:
subject_nrs.remove(4) #removes a value from a list 
print(subject_nrs)

subject_nrs.append(6) #appends a new number to the end of the list
print(subject_nrs)

subject_nrs.extend([7,8,9]) #joins multiple lists together into 1 list
print(subject_nrs)
[1, 2, 3, 5]
[1, 2, 3, 5, 6]
[1, 2, 3, 5, 6, 7, 8, 9]

Let's go back to our original example of combining lists:

In [23]:
print(subject_nrs + subject_names)
[1, 2, 3, 5, 6, 7, 8, 9, 'sub1', 'sub2', 'sub3', 'sub4', 'sub5']

In this example, the subject number and subject name correspond to one another. So we might want to look at the two lists next to each other to make sure they match up. We can do this with zip:

In [24]:
print(zip(subject_nrs, subject_names))
<zip object at 0x7fce9812fa48>

"Zip" is just like it sounds - it zips two or more lists together. In Python3, "zip" turns the lists into an iterable object instead of a mega-list, so you don't automatically get to see the output. To see the result of the zip, we have to turn the object into a list to see the output, using "list":

In [25]:
print(list(zip(subject_nrs, subject_names)))
[(1, 'sub1'), (2, 'sub2'), (3, 'sub3'), (5, 'sub4'), (6, 'sub5')]

All lists that you zip should have the same length. If your lists are different lengths, zip will cut off items from the longer list to make them equal, and you don't want this to happen:

In [26]:
subject_ages = [21, 33, 20]
print(list(zip(subject_nrs, subject_ages)))
[(1, 21), (2, 33), (3, 20)]

Zip is very useful if you want to balance multiple experimental conditions. For example, say you have 2 directories for two categories of images ('faces','houses') and within each directory are 5 images per category ('im1.png','im2.png','im3.png','im4.png','im5.png'). You want to tell your experiment to go into the "faces" directory, find im1.png, and present it on the screen (and so on and so forth for all 10 trials). It's good to first create lists of all the images that will be shown on the different trials ahead of time, so you don't have to write in your experiment line by line "First, go into the faces directory and show image 1. Second, go into the faces directory and show image 2..." You can create two lists of all the faces and houses you will show ahead of time, and draw from that list during your experiment. Remember with "zip", lists must be of equal size, so we create our lists like this:

In [27]:
cats = ['faces']*5 + ['houses']*5
imgs = ['im1.png', 'im2.png', 'im3.png', 'im4.png', 'im5.png']*2
print(cats)
print(imgs)
['faces', 'faces', 'faces', 'faces', 'faces', 'houses', 'houses', 'houses', 'houses', 'houses']
['im1.png', 'im2.png', 'im3.png', 'im4.png', 'im5.png', 'im1.png', 'im2.png', 'im3.png', 'im4.png', 'im5.png']

(There are easier ways of creating these lists with loops, but we will get to that in Level2.)

In [28]:
catimgs = list(zip(cats,imgs))
print(catimgs)
[('faces', 'im1.png'), ('faces', 'im2.png'), ('faces', 'im3.png'), ('faces', 'im4.png'), ('faces', 'im5.png'), ('houses', 'im1.png'), ('houses', 'im2.png'), ('houses', 'im3.png'), ('houses', 'im4.png'), ('houses', 'im5.png')]

So now we have 1 zipped list of tuples. If you check the length of the list, you can see that python considers each tuple as a single item:

In [29]:
print(len(catimgs))
10

So you have 10 tuples, each pointing toward the directory name and image number you want to present for a separate trial. The first string in the tuple is the name of the directory you want to point your experiment to, and the second string is the name of the image inside that directory that you want to show. By the way, when you are counterbalancing conditions, it is good to first define everything in a nice order, like the lists shown above. However, once you have ensured all your conditions are balanced, you will want to randomize the order for the actual experiment. You can randomize a zipped list with the numpy function "random" like this:

In [30]:
import numpy as np
np.random.shuffle(catimgs)
print(catimgs)
[('houses', 'im3.png'), ('faces', 'im1.png'), ('houses', 'im2.png'), ('houses', 'im4.png'), ('houses', 'im5.png'), ('faces', 'im5.png'), ('faces', 'im3.png'), ('faces', 'im4.png'), ('faces', 'im2.png'), ('houses', 'im1.png')]

The "shuffle" function shuffles the order of tuples in a list, without shuffling the strings within each tuple. This is important -- although you want to shuffle the order of conditions, you don't want to mess up the specific directory-image pairs you have defined previously.

Now you have your shuffled list. So how do we tell the experiment which string in each tuple is the directory and which is the image number? We have to use indexing. To find the index of an item in a list, use square brackets [ ]:

In [31]:
print(catimgs[0])
('houses', 'im3.png')

Remember that python indexing starts at 0. Printing catimgs[0] finds the first tuple in the list. To select the first string in the first tuple, you use the square brackets twice: the first is the index of the tuple in the list, and the second is the index of the string in the tuple. The syntax looks like this:

In [32]:
print(catimgs[0][0])
houses

You can also print the second string in the first tuple:

In [33]:
print(catimgs[0][1])
im3.png

But if you accidentally try to access a non-existent third string in the tuple...

In [34]:
print(catimgs[0][2])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-34-a4ce9097b071> in <module>
----> 1 print(catimgs[0][2])

IndexError: tuple index out of range

...python tells you the index is out of range. If you get an error like this, it means you tried to access an index that is out of the range of items you have in your tuple.

You can even find the index of individual letters that appear in a string:

In [35]:
print(catimgs[0][0][0])
h

If you want to print the last letter in the string but don't want to bother counting the letters:

In [36]:
print(catimgs[0][0][-1])
s

Using the minus sign, you can index backwards from the end of the string. So you can also print the second to last letter like this:

In [37]:
print(catimgs[0][0][-2])
e

So to summarize:

In [38]:
print(catimgs[0]) #level 1 indexing
print(catimgs[0][0]) #level 2 indexing
print(catimgs[0][0][0]) #level 3 indexing
('houses', 'im3.png')
houses
h

Finally, let's go back to our list of subject_nrs, which is now a list of numbers 1-9, but with the 4 removed:

In [39]:
print(subject_nrs)
[1, 2, 3, 5, 6, 7, 8, 9]

Say we want to put the 4 back into the list. We can do this with a built-in python function called "insert" that takes 2 arguments: 1.) the index, and 2.) the value you want to insert at that index. Like this:

In [40]:
subject_nrs.insert(3,4) #insert(index,value) - remember python indexing starts at 0
print(subject_nrs)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

As for indexing in dictionaries, it's a little different from indexing in lists. As mentioned in level0, items stored in a dictionary do not have a set placement index:

In [41]:
my_dict = {'one':1,'two':2, 'three':3}
print(my_dict)
{'one': 1, 'two': 2, 'three': 3}

In this case, you do not index by placement, but rather by label:

In [42]:
print(my_dict['one'])
print(my_dict['two'])
print(my_dict['three'])
1
2
3

Nifty!

If you want to access multiple indices at once, you can use slicing. Slicing is exactly what it sounds like -- it slices the item (using a colon : ). You can also define with an index where you want to start or end the slice. First, let's define a new list variable to demonstrate how to slice:

In [51]:
slicy = [1,2,3,4,5,6,7,8,9,10]

print(slicy[:]) #print the entire slice
print(slicy[:3]) #print a slice up until index 3
print(slicy[3:]) #start at index 3 and print the rest of the slice
print(slicy[3:6]) #start at index 3 and print until index 6
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3]
[4, 5, 6, 7, 8, 9, 10]
[4, 5, 6]

You can also slice in steps (similar to "arange" which we learned in level 0). The syntax for this is [start:stop:step] :

In [52]:
print(slicy[2:10:2])
[3, 5, 7, 9]

This means, start with the item with an index of 2 and print every other item (step=2) up to the item with an index of 10.

You can also just tell python to start at the beginning ":" and end at the end ":", but print every other item:

In [53]:
print(slicy[::2])
[1, 3, 5, 7, 9]

You can also print the whole list backwards by using "-1" as the step:

In [54]:
print(slicy[::-1])
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

In this case, the syntax means "start at the end, and end at the beginning, going backwards by 1 item each step". Slicing works with strings, as well -- you can slice letters in a word, or words in a sentence (but don't forget that spaces are treated as indices, so the string "I love python") has 13 indices (11 characters + 2 spaces).