In the previous lectures we learned about data types for numbers and strings. In this lecture we learn about more complicated data structures. One such structure is a list. To create a list we just need to type a sequence of values enclosed by square brackets. These values don't have to be the same type. For instance, in this example we have a list containing the string gene, and three float values that could be the p values. For instance, how significant is the gene expression level of this gene in three different conditions. Let's renumber this in list into a variable. We can call this variable gene_expression, for example. So, once we defined gene_expression, remember from previous lectures, we need variables to store the values of the different data types. When we create a list these values in the list are somewhere in the computer's memory. But they cannot access them unless we create a variable that remembers where they are. Therefore, our gene_expression variable is sort of a pointer to a location in memory. Just like strings, list variables are indexed starting from zero and we can access its individual list elements. Let's print the third element in gene_expression variable. Since elements in the list start from zero, the third element in gene_expression will have index two. Let's also print the last element in gene_expression. We can do this by using index minus one. So print gene_expression of minus one prints 7.33, e to the power on -08. We can not only access individual elements in a list, but we can also change their values. Let's say that we want to use a real gene name in our list instead of just a string gene. And let's say this is the name Lif, and we want to assign a new name to this first element of gene_expression by using the equal sign. If we print an expression variable now, we can see that its first element is now changed. What happens if we do this in a string? Let's try it. Suppose we have a variable called motif that is actually a string formed with the characters a, c, g and t. And sometimes when we don't know all the values of the bases in a DNA sequence, we might replace some of the As, Cs, or Gs or Ds with Ns. Like for instance here, in our motif, let's say we have an N at the beginning of the sequence, and we want to try to change it. So we'll say motif (0) equals a. Immediately Python will give an error message. Therefore we can't change strings. We say this in Python that strings are immutable data types, while lists are mutable data types. Just as we did with string, we can slice lists too. And the slice operation on a list or return on your list containing the requested elements in the slice. This means that the following slice, gene expression of -3, colon, nothing, square bracket, returns a new copy of the list starting from the second element in our list. Remember that the -3 represents the third from the last element in the list. As for strings, if we omit the values before or after the colon in the slice, the slice is extended to the beginning or the end of the list as needed. Therefore, we can return a whole new copy of the gene_expression list by applying a slice whose known value is specified before or after the colon. Let's do this. Calling slice operations on lists do not modify the list unless you specifically assign them new values. When you do an assignment to a slice that actually will change the list. Let's try it and assign a new value from a slice, starting with element with index one, in gen_ expression and ending at the third element in the list. As we see in this example, the two elements in the slice are now replaced by only one element. Similarly, by assigning an empty list to the slice of the whole list to clear the list as we show here. There are several operations that we can perform with lists. Just like strings, this can also be concatenated. We can concatenate two lists by using the sign plus, as we show here. Note that the value returned is a new list. And unless we assign it to a variable, we can not remember it. We can also call, for instance, the built in function, len, on the list. Here by calling len of gene_expression we get the value 3. Python also offers a built in del statement. And we can use this to remove any elements or slices from the list. For example we can write del gene_expression of 1 in between square brackets. And this will have the effect of deleting the second element in the list. The element is index one. All elements after the one deleted are shifted to replace the empty space in the list. And the gene_expression variable now references a list with only two elements. Just like strings lists are also objects. This means that they have a set of methods that are specifically defined to work on them. One such useful method is the method extend. Let's see what happens if we type gene_expression.extend, and use a new list as a parameter for the method extend. By checking the value of the gene_expression variable, we notice that the list contains the two additional elements that were in the list that we supplied as a parameter for the method extend. Therefore, unlike concatenation, extend actually modifies the list. The method count applied to a variable list returns the number of times the parameter given to the method count appears in the list. Let's print, for instance, the number of times the elements Lif and gene appear in gene_expression. The result is, of course, one and zero. Notice that you can also use count, to quickly check if a certain element appears in the list. And obviously if the count is zero, it means that the element in the list is not present. Let's try another useful method. This one is called reverse. Just as for string, we can check all of the methods by using the help, applied to list. gene_expression.reverse() will return the list of elements in reverse order. If you type type of list you will notice the amount of methods associated with the type lists two functions called append and pop. Using these two methods, we can easily treat lists as stacks. The stack is a simple but important example of an abstract data type. It is used to store elements where the last element in the list is the first one that goes out. Anyone with an overfull sink of dishes will know this. Basically that you put new elements on top of the dishes, and the first one that you take out is the last one you put in. Let's see how this would work in Python. First we'll create a variable called stack to store a list with the elements, a, b, c and d. Then we will use append to add an item to the top of the stack. Let's say that the new element is e. stack.append(e) will append e to the top of the list. What do we think it will happen if we, instead of a one single element we use a list. Try it in the Python interpreter right now. You will notice that the difference between append and extend is that append takes only one element, while extend takes a list as an argument. If you tried to append a list, then that list will actually become one element of the list. Let's try to remove, now, the item from the top of the stack, and we will use the method pop. So stack the pop. If we started in a variable called elem, we can check the value of elem and we see it is e, therefore, the last element we added to the stack. If you start programming, you will notice that sorting lists is an operation that you will do very often. There are two ways that you can accomplish this. One of them uses the built-in sorted function. Let's created a list called mylist and then apply the sorted function to it. Notice how Python knows the elements in mylist are number that need to be sorted numerically. Let's check how mylist variable looks now. We can see that nothing happened to the order of the elements. They stayed the same. Another way to sort the list is by calling the method sort. Let's try it. Let's call mylist.sort(). Notice here that you don't need to give any parameters to the method sort. So, mylist.sort() will give you the sorted list. And now if we check mylist, we notice that the elements in the list change. So, while sorted function doesn't change the order of the elements in the list, sort replaces the elements in the list in the sorted order. It's very useful to call the method sort, for instance if you are planning to do lots of sorting in your program. This'll be much quicker, especially if you don't need to remember the order those elements were entered. Sort also can work with other types of values, like for instance with string, where Python knows how to sort them. It will apply an order, based on the alphabetical order. So, let's try it. Let's take a variable again called mylist with some characters. For instance, let's say that they are bases from the DNA alphabet, like As, Ds, Gs and Cs, and try to sort them. So print(sorted(mylist)) prints that list in a sorted order. If you don't use numbers or strings, you might want to define how Python can sort those elements. But this is a much more complicated topic and we will not address it here.