<jupyter_start><jupyter_text>Day 1: Welcome to the Python Neuro Bootcamp!!! What is Python? Python is a widely used programming language for general-purpose programming, created by <NAME> and first released in 1991. Python is an *interpreted* language, meaning that it will read your code, line-by-line, and "translate" it into binary (compile) every time it's run. In contrast, *compiled* languages require you to write the full program first before running it. While compiled languages generally run faster, interpreted languages can often approach the speed of compiled languages with proper use of third-party libraries. One advantage of interpreted languages is that you can use an interactive prompt to execute single lines of code and view the results immediately. This allows you to write and test, line by line, your code.Python emphasizes code readability and a syntax that allows programmers to express concepts in fewer lines of code than might be used in languages such as C++ or Java. Writing "*Pythonian*" code means writing clear, documented code that other people (and you in six months) will understand. The goal of this bootcamp is to introduce you to Python by working through some neuro data sets that we selected based on your interests. But before we jump into hands-on analysis, we will go quickly through some basic concepts of Python programming. ** A quick note about Python versions. ** Python has a few different versions available. The most common ones are Version 2 and Version 3. There are a few key differences between these versions which can cause usability issues (*i.e.*, some code written for Python 3 won't run with Python 2 without revisions). **This course is using Python 3.** We will provide some additional resources and information about version 2 on Day 5.  What are Python's main strengths and why did we choose it for this course?- Python is FREE!- Python is becoming the *de facto* standard programming language in Neuroscience.- There is a lot of support online, and it is easy to find an answer to your questions just by Googling it.- It is designed to be readable (if you write it to be so!)  What is the Jupyter Notebook and why are we using it for this course?This is a Jupyter Notebook, a space where you can combine text (this is [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)) and code. It is an awesome educational tool! The best part is that you will be able to interact with anything that is written here, test out code, take notes, and make it your own.**Remember to save changes that you have made to your Jupyter Notebook before navigating away from the page!** To do this in the notebook, hit the save button in the upper left of the web browswer. To close the notebook, close the tab. To close jupyter notebook in the terminal, press control + C (you may lose any unsaved changes). For more information about the Jupyter Notebook and how to use it, check out the [complete guide](https://jupyter.brynmawr.edu/services/public/dblank/Jupyter%20Notebook%20Users%20Manual.ipynb). Where to get help:* [Python HELP](https://www.python.org/about/help/) - Collection of links to documentation if you are new to Python* [Python documentation](https://docs.python.org/3/) - Comprehensive Python documentation for version 3.* **`help()` function** - calling the built-in `help([object]`) function will invoke the python documentation.  * More on other [built-in functions](https://docs.python.org/3/library/functions.html) in Python 3 * [Tutorial](https://docs.python.org/3/tutorial/index.html) - Comprehensive Python tutorial - feel free to browse if you are hungry for more after this course! * [Library Reference](https://docs.python.org/3/library/index.html) - Extensive information about the standard Python library * [Scientific Python stack documentation: numpy, scipy, matplotlib](https://scipy.org/docs.html) - Information about some science-specific Python packages* [Stack Overflow](https://stackoverflow.com/) - A general forum commonly used to asked programming questions. Search for answers to your questions before posting your questions!MATLAB-to-Python cheat sheets:  * http://mathesaurus.sourceforge.net/matlab-numpy.html* http://mathesaurus.sourceforge.net/matlab-python-xref.pdf   Remember that:* Python is an interpreted language and it executes statements as it receives them.* It ignores lines that begin with '``'. These are called 'comments'. * Comments are great, so write them as you go along. You think you will remember, but actually you won't! :) Comments are used to document what the code is doing and why you wrote it that way.* Learning how to code is similar to learning any other language. It will take time and dedication to master it. Don't be frustrated, just go for it and learn from those errors!  Outline of this notebookToday we will go through some basic topics related to coding in Python:[1.1 Intro to Python data types](intro)* [1.11 Numbers](numbers)* [1.12 Strings](string)* [1.13 Lists](lists)* [1.14 Booleans, if, elif, else statements](booleans)* [1.15 Tuples](tuples)* [1.16 Dictionaries](dictionaries)    [1.2 Handling errors](errors) [1.3 Easy exercise to tie it all together](exercise)[1.4 Appendix](appendix)  1.1 Intro to Python data types  1.11 Numbers Python has various "types" of numbers (numeric literals). We will mainly focus on **integers** (such as 1, 2, 38, -255) and **floating point numbers** (such as 7.6, -0.2628, 2.44e10, or 9e23).<jupyter_code>int_number = 56<jupyter_output><empty_output><jupyter_text>In the example above, we created an object named `int_number` with the value 56. If we want to know what type this object is, we can ask Python using the built-in `type()` function:<jupyter_code>type(int_number)<jupyter_output><empty_output><jupyter_text>Let's look at some easy basic arithmetic operations<jupyter_code>a = 1 + 3           # addition
b = 1 - 3           # subtraction
c = 10.0 * 4        # multiplication 
d = 10.0 / 4        # division
e = 8.5**3          # exponent
f = 173 % 12        # modulo
g = 1e2             # scientific notation  
h = 5 * (c + a)     # parentheses for operator precedence<jupyter_output><empty_output><jupyter_text>To view any of the values stored in the variables assigned above in a Jupyter Notebook, we can either simply type the object's name (a, b, c, etc.) or use the `print()` function to specifically tell Python to print that object.<jupyter_code>d
print(d)<jupyter_output><empty_output><jupyter_text>The practical difference is that if you need to display more than one object, you cannot simply type the objects name sequentially because you'll get only the latest one. For example:<jupyter_code>d
e
g<jupyter_output><empty_output><jupyter_text>You only get the output of `g`. To get them all, you'll have to specifically tell Python to print them all out:<jupyter_code>print(d)
print(e)
print(g)<jupyter_output><empty_output><jupyter_text>1.12 Strings Strings are used in Python to record text information, such as a name, description, or sentence. Strings in Python are actually a sequence of individual characters (including invisible characters like spaces). We can create an object to hold a string or use them directly.<jupyter_code># Assign the text 'Hello Neurocoders' to the variable my_string:
my_string = "Hello, Neurocoders!"
print(my_string)
type(my_string)
# Using strings directly:
print('Hello again!')<jupyter_output><empty_output><jupyter_text>Let's go over some basic and fun string manipulations.Something very useful is figuring out the length (*i.e.*, number of characters) of a string. To do that you can use another built-in function `len()`.<jupyter_code>len(my_string)

# Note that the count below includes spaces!<jupyter_output><empty_output><jupyter_text>IndexingIndexing is something that we need to learn from day 1! Doing it with strings is fun and easy. So, let's start indexing! IMPORTANT NOTE: Like most programming languages, in Python the first index is a __0__!!! In some languages (such as R) indexing starts at 1.  Conveniently, strings are sequences, which means Python can use indexes to call specific parts of the sequence. Let's learn how this works.Python uses brackets [ ] after an object to call its index.<jupyter_code># Show first element (in this case a letter)
my_string[0]

# now try different indexes<jupyter_output><empty_output><jupyter_text>SlicingWe can use a ":" to perform slicing which extracts a subset of the string. For example, we can use the `START:STOP` format to get a slice out of our string:<jupyter_code># Grab the first three letters of my_string
my_string[0:4]<jupyter_output><empty_output><jupyter_text>**Note** that the `STOP` element is excluded from the slicing. Here we're telling Python to grab everything from the 0th element up to the 3rd element, excluded. You'll notice this a lot in Python; statements are usually in the context of `"up to, but not including"` (slicing in Python is "upper-bound exclusive").If you want to grab everything from the beginning to a certain point or from a certain point to the end you can leave the `START` and `STOP` unspecified using the formats `:STOP` or `START:`An excellent discussion of why this slicing syntax (*i.e.*, starting numbering at 0 and excluding the `STOP` element) works so well is nicely summarized by Dijkstra in [this article](http://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html).<jupyter_code>my_string[:4]
# Now, slice out the last word of my_string, "Neurocoders!"
my_string[7:]<jupyter_output><empty_output><jupyter_text>**Note** that by doing this you did not change the content of the object `my_string`.<jupyter_code># let's check just in case
print(my_string)<jupyter_output><empty_output><jupyter_text>Sometimes it might be useful to start counting from the back. For instance, if we want to slice out the string "coders!", we could do:<jupyter_code>my_string[-7:]<jupyter_output><empty_output><jupyter_text>We can also use index and slice notation to grab elements of a sequence by a specified step size (the default is 1). To do this, we use the format `START:STOP:STEP` as shown below:<jupyter_code>my_string[0:18:2]<jupyter_output><empty_output><jupyter_text>or better:<jupyter_code>my_string[::2]
# Grab every third letter from the beginning to the end of my_string
my_string[::3]<jupyter_output><empty_output><jupyter_text>String PropertiesIt is important to note that strings have an important property known as *immutability*. This means that once a string is created, the elements within it cannot be changed or replaced.<jupyter_code># If you try to change the first character of my_string to 'X' for example...
my_string[0] = 'X'<jupyter_output><empty_output><jupyter_text>What we can do is manipulate strings using some basic operators, just as we did with numbers:The `+` sign will concatenate two strings and make a new (immutable) string:<jupyter_code>my_string = my_string + " Let's learn Python together!"
# this can also be shortened as:
# my_string += " Let's learn Python together!"
print(my_string)<jupyter_output><empty_output><jupyter_text>The operator `*` will repeat the preceding sequence for the specified number of times:<jupyter_code>letter = 'z'
print(letter*10)

letter = 'ab'
print(letter*6)<jupyter_output><empty_output><jupyter_text>Basic built-in string methodsProgramming languages consist of **data structures** and **functions**. Data structures store data. Functions perform actions on data or transfrom data, similar to mathematical functions. For example, many languages include a data structure that holds rational numbers and a function that tells us a number's square root.Python is an **"object-oriented" programming language**: everything in it is an *object* that can store data as well as functions that act on that data. The functions associated with an object are referred to as *methods*. Methods are no different than other functions in principle – objects were invented to help programmers group together related data and functions. We use (or call) methods by writing the object's name with a period, followed by the method's name, and any parameters passed to the method in paratheses. Thus, methods are in the form:> `object.method(parameters)`in which parameters are extra arguments we can pass into the method. You can press tab after the period and a list will pop up with the methods associated with that particular object.<jupyter_code># try identifying what methods are associated with the object my_string:
## write my_tring. and then press Tab<jupyter_output><empty_output><jupyter_text>Here are a few useful built-in methods for strings:<jupyter_code># Replace content
my_string.replace("Neurocoders", "Neuroscientists")
id(my_string)
# did the content of the original my_string change?
## Yes, we did use this method to change the content of the original my_string - you can 
## check this by using the function id(my_string) before and after you run this cell. Check
## out the Appendinx for more info on how Python assignments work.
# Upper Case a string
my_string.upper()
# Lower case
my_string.lower()
# Split the words 
my_string.split()
# Split by a specific element (doesn't include the element that was split on)
my_string.split('N')<jupyter_output><empty_output><jupyter_text>Note that what we originally assigned to the object `my_string` **does not change** when we apply a method. Instead, each of these methods created a new string that was displayed and then deleted. In order to save the new string, you would need to assign it to a new object:<jupyter_code>capitalized_my_string = my_string.upper()
print(capitalized_my_string)<jupyter_output><empty_output><jupyter_text>1.13 Lists Lists are objects and can be thought of the most general version of a *sequence* in Python. Lists are one of the most versatile data types in Python. Lists work similarly to strings – you can use the `len()` function to identify how many items are in a list, and square brackets [ ] to access specific data, with the first element at index 0.You create a list by putting square brackets around a comma-separated list of other Python items (and with itmes we mean any data type in Python as lists can contain heterogeneous collection of objects):<jupyter_code>my_list = [1, 2, 3]<jupyter_output><empty_output><jupyter_text>Unlike strings, lists are *mutable*, meaning the elements inside a list can be changed.<jupyter_code>my_list[0] = 'one'

# The operation above will change the content of my_list permanently!
print(my_list)<jupyter_output><empty_output><jupyter_text>**Note** that the content of `my_list` does **change** when you re-assign one element to something else.Remember what happens when we tried to do the same with `my_string`?<jupyter_code>my_string[0] = 'X'<jupyter_output><empty_output><jupyter_text>In this case we saw that we could use the method `.replace()` to create a new altered version of `my_string` that to be saved will need to be assigned to another object, `new_string`, but the content of the original `my_string` will not change because strings are *immutable*!<jupyter_code>new_string = my_string.replace('Hello', 'Howdy')
print(new_string)
print(my_string)<jupyter_output><empty_output><jupyter_text>Now back to lists! Indexing, slicing, and basic arithmetic manipulation work just as they did in strings. Try grabbing only the last element of `my_list`:<jupyter_code># Write here
my_list[-1]
# Since lists are mutable we can add elements to them by using the + sign
my_list + [4.0]
# or we can duplicate their content by multiplying them as if they were numbers
my_list * 3
# Now check what happened to my_list. Did it permanently change it's original content? 
print(my_list)

## No!<jupyter_output><empty_output><jupyter_text>To do so, you would have to reassign the list to make the change permanent.<jupyter_code># Reassign
my_list = my_list + [4.0]
print(my_list)<jupyter_output><empty_output><jupyter_text>Basic built-in list methodsWe can use the `append()` method to permanently add an item to the end of a list. `append()` adds a single element to the end of a list:<jupyter_code>my_list.append('five')
print(my_list)<jupyter_output><empty_output><jupyter_text>Just as with strings, you can access elements by indexing from the front of the list, using positive numbers, or from the back of the list, using negative numbers.<jupyter_code>print(my_list[2])
print(my_list[-2])<jupyter_output><empty_output><jupyter_text>`append()` is a very useful method. We can append any other object, including another list! That makes `my_list` a "nested list".<jupyter_code>my_list.append(["six",7])
print(my_list)<jupyter_output><empty_output><jupyter_text>Now you can get the nested list using:<jupyter_code>my_list[-1]<jupyter_output><empty_output><jupyter_text>Indexes may be combined to navigate to a particular element in a nested list:<jupyter_code>my_list[-1][0]<jupyter_output><empty_output><jupyter_text>If you want to append more than one element at the time without creating a new list, you can't just do:<jupyter_code>my_list.append(8, 9, 'ten')
print(my_list)<jupyter_output><empty_output><jupyter_text>You need to use anothe menthod called `extend()`:<jupyter_code>my_list.extend([8, 9, 'ten'])
print(my_list)<jupyter_output><empty_output><jupyter_text>This is the same as using "in-place addition":<jupyter_code>my_list += [11, 'twelve']
print(my_list)<jupyter_output><empty_output><jupyter_text>The `insert()` method injects a new element into a list at a particular index. So we could inject the number 8.5 into our list:<jupyter_code>my_list.insert(7, 8.5)
print(my_list)<jupyter_output><empty_output><jupyter_text>If instead you want to remove a specific element, you can use the `remove([value])` method:<jupyter_code>my_list.remove(8.5)
print(my_list)<jupyter_output><empty_output><jupyter_text>The `pop([index])` method is another way to remove an element from a list, but by index rather than by value.<jupyter_code>my_list.pop(7)
print(my_list)<jupyter_output><empty_output><jupyter_text>So this removed the element at index 7, returning the value that was removed so now you can use that value in some other operation.  This is similar to using the operator `del` as `del my_list[7]`. Note that `del` doesn't return the element, it just gets rid of it:<jupyter_code>del my_list[7]
print(my_list)<jupyter_output><empty_output><jupyter_text>By default `pop()` used without any arguments, pops off the last index:<jupyter_code>my_list.pop()
print(my_list)<jupyter_output><empty_output><jupyter_text>The `sort()` method called without any argument will sort the elements in ascending order:<jupyter_code>my_list.sort()
print(my_list)<jupyter_output><empty_output><jupyter_text>Note that there is also the `sorted()` function that is not specific to lists and has slightly different applicability. Check out [this documentation](https://docs.python.org/3/howto/sorting.html).The `reverse()` method reverses the elements of the list in-place:<jupyter_code>my_list.reverse()
print(my_list)<jupyter_output><empty_output><jupyter_text>**Exercise:**>- Create a list with three string elements:     `sentence = ['Python.', 'learning', 'am', 'I']`;- Reverse the order;- Use the string method `.join()` to create a string `my_string` and print `my_string`.Need a [hint](https://www.decalage.info/en/python/print_list) for that last point?<jupyter_code># Here goes your code
new_list = ['Python.', 'learning', 'am', 'I']
new_list.reverse()
my_string = ' '.join(new_list)
print(my_string)

## .join() is a string method that allows you to get a string out of a list of strings. 
## You can think of it as the opposite of .split() we saw earlier.<jupyter_output><empty_output><jupyter_text>[More on Lists](http://thomas-cokelaer.info/tutorials/python/lists.html) and more [list's methods](https://www.programiz.com/python-programming/methods/list/append)  1.14 Booleans, special values, and if, elif, and else statements <NAME> (1815–1864) was an English mathematician, educator, philosopher, and logician. He first defined an algebraic system of logic that is still used today (or at least his name is still used!). A **Boolean data type** is a data type that has two values (usually denoted True and False).Python comes with Booleans that may take on the values of either `True` or `False`.  Let's walk through a few quick examples of Booleans.<jupyter_code># Set object to be a boolean
a = True 
# Note that "True" capitalized is recognized by Python as a special character, 
# and it's case sensitive (true or TRUE won't work!)<jupyter_output><empty_output><jupyter_text>We can ask Python if `a` is actually `True`:<jupyter_code>a is True
b = False
b is True<jupyter_output><empty_output><jupyter_text>We can also ask Python to compare two numbers using "comparison operators". Python will answer with a boolean:<jupyter_code>1 > 2<jupyter_output><empty_output><jupyter_text>Booleans are very useful in programming. We will see this more in depth in the next days, but let's introduce you to the concept of **if, elif, and else Statements**:`if` statements in Python allow you to tell the computer to perform alternative actions based on a certain set of results.Verbally, we can imagine we are telling the computer:"Hey if this happens, do this"We can then expand the idea further with `elif` and `else` statements, which allow us to tell the computer:"Hey if this happens, do this. Else if another case happens, do this other thing. Else none of the above cases happened, do this last thing."We can write some "pseudocode" to illustrate this and then jump into coding it in Python:    if case1:        perform action1    elif case2:        perform action2    else:         perform action 3           Let's try a few examples:<jupyter_code>if a is True:
    print('It was true!')
if b is True:
    print('It was true!')<jupyter_output><empty_output><jupyter_text>Note that you have no output when you run the previous cell. This is because `b` is `False`. We can expand on this to print a message either way:<jupyter_code>if b is True:
    print('It was true!')
else:
    print('It was false!')<jupyter_output><empty_output><jupyter_text>We can expand on this even further to control for the case in which the object evaluated is not `True` or `False`:<jupyter_code>c = 5.2
if c is True:
    print('It was true!')
elif c is False:
    print('It was false!')
else: 
    print('It was not Boolean!')<jupyter_output><empty_output><jupyter_text>Now let's introduce **logic operators** syntax, starting with the double equals operator (`==`), which tests the equality of two objects and returns a boolean value.<jupyter_code># Example 1
person = 'Eli'

if person == 'Morgan':
    print('Welcome Morgan!')
elif person == 'Eli':
    print('Welcome Eli!')
else:
    print("Welcome! what's your name?") 
    
# Try changing the string assigned to the object "person" to your name to get the 
# else output message
# Example 2
# We have 20 hungry graduate students
students = 20
beer_cans = 20
pizzas = 10 # each pizza feeds 2 graduate students

if students > beer_cans or students > 2*pizzas:
    print("There is not enough pizza or beer!")
else:
    print('Time for a rocking party!')<jupyter_output><empty_output><jupyter_text>Python also has a special value known as `None`. None can be used in various contexts, but is typically used to indicate that a value does not exist or has not been initialized yet. In data science, it can be used to indicate missing data (as you will see in later sessions).<jupyter_code>person = None
    
# Test if the value is None
if person is None:
    print('We have not met before, what is your name?')
    
# Test if the value is not None
if person is not None:
    print('We have met. Hello ' + person + '!')<jupyter_output><empty_output><jupyter_text>While you can also use the double equals (`==`) operator to check whether a value is `None`, it is considered more "Pythonic" to test whether the value `is` None (more on this [here](https://docs.quantifiedcode.com/python-anti-patterns/readability/comparison_to_none.html)).<jupyter_code>print(None == None)
print(None is None)<jupyter_output><empty_output><jupyter_text>1.15 Tuples Tuples are very similar to lists but, unlike lists, they are *immutable* meaning they can not be changed. You can use tuples to present things that shouldn't be changed, such as days of the week, or dates on a calendar. Tuples are constructed by useing `()` with elements separated by commas. For example:<jupyter_code>my_tuple = (1,2,'foo')
len(my_tuple)<jupyter_output><empty_output><jupyter_text>Indexing works just like for lists:<jupyter_code>my_tuple[0]
# Slicing just like lists
my_tuple[-1]<jupyter_output><empty_output><jupyter_text>But since they are immutable, you can't reassign a value to an index. So if we try:<jupyter_code>my_tuple[-1] = 'goo'<jupyter_output><empty_output><jupyter_text>Basic built-in tuple methodsTuples have built-in methods, but not as many as lists do as they are less versatile. Just as we did before, we can explore them by putting a "." after our tuple and press Tab.<jupyter_code># Use index() to enter a value and return the index
my_tuple.index('foo')
# Use count() to count the number of times a value appears
my_tuple.count(1)<jupyter_output><empty_output><jupyter_text>Because of this immutability, tuples can't grow. Once a tuple is made we can not add to it.<jupyter_code>my_tuple.append(2.2)<jupyter_output><empty_output><jupyter_text>You may be wondering, *"Why bother using tuples when they have fewer available methods?"* Even if tuples are not used as often as lists in programming, they are used when immutability is necessary (for instance as `keys` in a dictionary, as we will see next). If in your program you are passing around an object and need to make sure it does not get changed, then tuple become your solution. This provides a convenient source of data integrity.  1.16 Dictionaries In any language, dictionaries associate words to words' definitions. Words might have multiple definitions, but all definitions are associated with a unique word's entry in the dictionary.This maps to the data structure very well: each entry is a `key:value` pair, in which keys are the words, and values are the definitions. If there are multiple definitions, you might instead have a list of definitions.One way to create an empty dictionary, is to use curly `{}` brackets.Let's create our first dictionary in Python:<jupyter_code>my_dict = {}<jupyter_output><empty_output><jupyter_text>We can then inject entries into the dictionary using indexing notation to set the value for a key. So for example, if the word (key) is `'include'` and the definition (value) is `'comprise or contain as part of a whole'`, then we create the entry like this:<jupyter_code>my_dict['include'] = 'comprise or contain as part of a whole.'
print(my_dict)<jupyter_output><empty_output><jupyter_text>We can go on and add another `key:value` entry to `my_dict`:<jupyter_code>my_dict['exclude'] = 'deny access from a place, group, or privilege.'
print(my_dict)<jupyter_output><empty_output><jupyter_text>Note the repeated structure of `key:value` in the dictionary. When you inspect a dictionary, it may print the keys in alphabetical order, this is not an intrinisc characteristic of a dictionary: **there is no order for the keys in a dictionary** (*i.e.*, dictionaries are *unordered* data types).The only guarantee that a dictionary makes is that you have a single key associated with a value in the dictionary. It is an unordered mapping of *unique* keys to values.To look up the `value` of a `key` in a dictionary, use indexing notation with the `key`:<jupyter_code>my_dict['exclude']
# now look up the other key
my_dict['include']
# What do you think will happen if we ask:
my_dict[0]

# Why?
## You can't index dictionaries as we did for other ordered collections such as strings,
## lists, and tuples<jupyter_output><empty_output><jupyter_text>If you do not know the keys of the dictionary, you can use the dict method `keys()`:<jupyter_code>print(my_dict.keys())<jupyter_output><empty_output><jupyter_text>**Examples using dictionaries:**Let's build a database with records holding the fields "first" as for first name, "last" as for last name, and "year" as year in grad school. The result of a query to this database might produce a list of dictionaries which looks something like this:<jupyter_code>students = [
    {'first': 'Daniela', 'last': 'Saderi', 'year': 6},
    {'first': 'Charlie', 'last': 'Heller', 'year': 2},
    {'first': 'Luci', 'last': 'Moore', 'year': 5}
]<jupyter_output><empty_output><jupyter_text>**Exercise:**>* What's the `type` of the object `students`? What's the type of the first element of the object `students`?* Use indexing to pull out the for the first element (0th index) of the object `students`* Use indexing to pull out the `value` associated to the `key` `'first'`* What do you notice about this structure?<jupyter_code># Write here
type(students) ## students is a list
type(students[0])  ## students is a list of dictionaries and the first element is a dict
students[0]
students[0]['first']<jupyter_output><empty_output><jupyter_text>Another way to create a dictionary is by calling the function `dict` with a list of tuples as `(key, value)` pairs.<jupyter_code>lab_inventory = dict([('gauze (box)', 4), ('q-tips (packages)', 3), 
                      ('alcohol bottles', 5), ('fine FST forceps', 10)])
print(lab_inventory)<jupyter_output><empty_output><jupyter_text>If there is a new delivery of 10 more boxes of gauze, here is how you add it:<jupyter_code># Access the key 'gauze (box) by indexing it as we did before, then use the "in place addition"
# to add 10 more elements to the value of that key
lab_inventory['gauze (box)'] += 10
print(lab_inventory)<jupyter_output><empty_output><jupyter_text>What Can be used as `key`?Values in dictionaries can be anything. Keys, on the other hand, must be immutable.* Integers and strings are very common keys* Floats (or even complex) can be used, but aren't recommended because of round-off errors* Tuples and frozensets (don't worry about them for now) are also allowed for keys* Lists and dictionaries are *not* allowed for keys because they are mutable.Let's see an example in which we would use tuples as keys:<jupyter_code>connections = {}<jupyter_output><empty_output><jupyter_text>If we have a connection, say, _from_ New York _to_ Seattle with 100 flights, then we create an entry for that in the dictionary with key `('New York', 'Seattle')` and value 100:<jupyter_code>connections[('New York', 'Seattle')] = 100<jupyter_output><empty_output><jupyter_text>We might also have 200 flights from Austin to New York, but 400 flights from New York to Austin:<jupyter_code>connections[('Austin', 'New York')] = 200
connections[('New York', 'Austin')] = 400
print(connections)<jupyter_output><empty_output><jupyter_text>Query the key `('New York', 'Austin')`:<jupyter_code># Your code goes here
connections[('New York', 'Austin')]<jupyter_output><empty_output><jupyter_text>Dictionary methodsDictionaries are objects, and so they have methods.Let's start with the `my_dict` dictionary example from before:<jupyter_code>print(my_dict)<jupyter_output><empty_output><jupyter_text>Accessing items with `get()`In addition to indexing a key to get out the value (`dict[key]`), you can also access values with the `get()` method. Get returns the value associated with a key, like indexing, but if the key doesn't exist it doesn't raise an error, but instead returns a default value `None`.<jupyter_code>my_dict.get('exclude')<jupyter_output><empty_output><jupyter_text>Removing items with keyword `del()`To remove an element from a dictionary, you can use the `del` keyword (same in lists).  You specify the key of the element that you want to remove using indexing notation:<jupyter_code>del my_dict['exclude']
print(my_dict)<jupyter_output><empty_output><jupyter_text>Removing items with method `pop()`You can also use the `pop()` method, similarly to the way we used it in lists. The `pop()` method expects the key that you want to remove and returns the corresponding value. This allows you to assign the value of the key that you pop out to another variable. You can also use `popitem()` to randomely remove any entry from your dictionary.<jupyter_code>my_dict.pop('include')
print(my_dict)<jupyter_output><empty_output><jupyter_text>Modifying dictionaries with `update()`Let's make a new dictionary called `student` with 3 keys* first: YOUR FIRST NAME (add a spelling mistake here)* last: YOUR LAST NAME* age: YOUR AGE  You can use `dict` or add one entry at the time after creating the empty dictionary.<jupyter_code># create your dict here
student = dict([('first', 'Danela'), ('last', 'Saderi'), ('age', 32)])
print(student)<jupyter_output><empty_output><jupyter_text>Then let's say we want to modify that entry with the spelling mistake, and we want to add the middle name. How do we do that?First we create a modification (sobstitute with your entries) and then we use update() to update the original dict:<jupyter_code>student_modification = {'first': 'Daniela', 'middle': 'Eli'}
student.update(student_modification)
print(student)<jupyter_output><empty_output><jupyter_text>More useful dict methodsLet's start out with a `cell_count` dict that has a collection of cell types that you can count in your field of view at a confocal:<jupyter_code>cell_count = {'neurons': 110, 'astrocytes': 500, 'uncategorized': 33}<jupyter_output><empty_output><jupyter_text>You can ask if there are pv positive neurons in your collection and Python will answer with a `False` or `True` boolean operetor:<jupyter_code>'pv+' in cell_count
# Ask if there are any neurons
'neurons' in cell_count<jupyter_output><empty_output><jupyter_text>If you want to know the list of cell types that are in the collection, you can use the `keys()` method:<jupyter_code>cell_count.keys()<jupyter_output><empty_output><jupyter_text>Similarly if you want to know about the values...<jupyter_code>cell_count.values()<jupyter_output><empty_output><jupyter_text>To get a list of the pairs of cell types and the corresponding number, you can use `items()`:<jupyter_code>cell_count.items()<jupyter_output><empty_output><jupyter_text>If you want to know the total number of cells, you could sum up the list of values:<jupyter_code>sum(cell_count.values())<jupyter_output><empty_output><jupyter_text>**Exercise:**   > * Add 100 neurons to the entry "neurons"* Add a new entry `'pv+':144`* Check your work printing the content of `cell_count`<jupyter_code># Write your answer here
cell_count['neurons'] += 100
cell_count['pv+'] = 144
print(cell_count)<jupyter_output><empty_output><jupyter_text>Lists *versus* dictionariesDictionaries are useful when there is a natural `key:value` pairing. An example could be a dateset in which the `keys` would naturally be sample IDs or an experiment dates, and `values` would be measurements. If you anticipate needing to search for specific values based on a specific key name, searching a dictionary is much more efficient than searching a list. In a list, you'd have to go through and check EVERY entry until you found the key you wanted but with a dictionary you can simply say `my_dictionary['key of interest']` to get your value.   1.2 Handling errors Everyone makes mistakes, and errors are common to every programmer, no matter their experience. To better prepare you on the inevitable, in this session we will try to understand what the different types of errors are and when you are likely to encounter them. Once you know why you get certain types of errors, they become much easier to fix.Errors in Python have a very specific form, called a *traceback*, meaning that Python will print the sequence of errors that led to the final error. For example:<jupyter_code>print(hello)<jupyter_output><empty_output><jupyter_text>Python tells us that this is a **`NameError`** that occured on line 1 of the cell. The error message is telling us is that there is no object named `hello` defined. To fix the error, we need to know what the code was meant to do – did we mean to write the word 'hello'? Then we forgot the quotes around `'hello'`. Or were we trying to print the content of an object that is called `hello`? If this is the case, we forgot to define the variable first or have a typo in our code.  We haven't spent much time on code structure in this course, but capitalization and spelling are very important in code that you write. Python views the words `hello`, `HELLO`, and `Hello` as distinct words. Also, there is no auto-correct, so these errors (or other typos such as typing `hellp` instead of `hello`) will all result in `NameErrors`.   Below, we will fix the error in each of these cases.<jupyter_code># The case where we meant to print the string 'hello'
print('hello')
# The case where we meant to print the object named hello 
# define the contents of the object hello
hello = 'Hello!'
# pass the object hello to the print function
print(hello)<jupyter_output><empty_output><jupyter_text>Now try to identify and correct the error in the next example:<jupyter_code>myNumber = 8
myNewNumber = 8 * 20
print(mynewNumber)<jupyter_output><empty_output><jupyter_text>**Questions: **> 1. What line was the error on?2. Can you spot what is wrong with the code?3. How can you fix it? Another common error is an **`IndexError`**. These are errors that occur when you try to access an element in an object (such as a string or a list) that doesn't exist. Let's revisit the string we defined at the beginning of this lesson.`my_string = "Hello, Neurocoders!"`Remember, in Python indexing starts at 0 and includes spaces. We can evaluate this with the `len()` function:<jupyter_code>my_string = "Hello, Neurocoders!"
len(my_string)<jupyter_output><empty_output><jupyter_text>What happens when you try to acces the last element by using the value 19? Why?<jupyter_code>my_letter = my_string[19]<jupyter_output><empty_output><jupyter_text>To fix an `IndexError`, you may want to inspect the object you are trying to work with with the `type` and `len` functions to identify the error and fix it. Try fixing the error in the cell below and correctly assign the last letter of `my_string` to the object `my_letter`:<jupyter_code>my_letter = my_string[19]<jupyter_output><empty_output><jupyter_text>Another common error is a **`TypeError`** which occurs when you attempt to use data types in ways they are not meant to be used. For example, remember that strings are immutable, meaning they cannot be changed. If you try to change an element of a string, you will recieve a `TypeError`. Run the cell below to see message:<jupyter_code>my_string[0] = 'x'<jupyter_output><empty_output><jupyter_text>Another case when you might encounter a `TypeError` is when mixing data types. As you may recall, you can use the `+` sign to add numeric variables, and you can also use the `+` sign to concatenate strings. However, you cannot use a `+` sign with a numeric and a string:<jupyter_code>a = 5
my_test = my_string + a<jupyter_output><empty_output><jupyter_text>The error message tells us that Python cannot convert an integer (int) into a string (str). To combine strings and numbers, you would need to explicitely ask Python to do a type conversion:<jupyter_code># convert the int stored in a to a string
a = str(a)

# concatenate: 

my_test = my_string + a 
print(my_test)<jupyter_output><empty_output><jupyter_text>[Software carpentry](https://southampton-rsg.github.io/2017-08-01-southampton-swc/novice/python/07-errors.html) has an entire lesson with exercises to better understand how to handle errors. Check it out!   1.3 In class exercises (Disclosure: The exercises below are inspired from the online course Enthougtht Training on Demand, Copyright 2008-2016, Enthought, Inc.) Playing with DNALet's work together at figuring out how to manipulate the following string, which happens to be the DNA sequence that encodes for the human histone cluster 1, H1b. DNA is made up of two sequences, the coding strand and the template strand. The sequence below is the **coding strand**, usually the only one stored in bio-informatics, since the template strand can be derived from the fact that an adenine (A) base always pairs with a thymine (T) and a cytosine (C) always pairs with a guanine (G).      A ACC TGC TCT TTA GAT TTC GAG CTT ATT CTC TTC TAG CAG TTT CTT GCC    ACC ATG TCG GAA ACC GCT CCT GCC GAG ACA GCC ACC CCA GCG CCG GTG GAG    AAA TCC CCG GCT AAG AAG AAG GCA ACT AAG AAG GCT GCC GGC GCC GGC GCT    GCT AAG CGC AAA GCG ACG GGG CCC CCA GTC TCA GAG CTG ATC ACC AAG GCT    GTG GCT GCT TCT AAG GAG CGC AAT GGC CTT TCT TTG GCA GCC CTT AAG AAG    GCC TTA GCG GCC GGT GGC TAC GAC GTG GAG AAG AAT AAC AGC CGC ATT AAG    CTG GGC CTC AAG AGC TTG GTG AGC AAG GGC ACC CTG GTG CAG ACC AAG GGC    ACT GGT GCT TCT GGC TCC TTT AAA CTC AAC AAG AAG GCG GCC TCC GGG GAA    GCC AAG CCC AAA GCC AAG AAG GCA GGC GCC GCT AAA GCT AAG AAG CCC GCG    GGG GCC ACG CCT AAG AAG GCC AAG AAG GCT GCA GGG GCG AAA AAG GCA GTG    AAG AAG ACT CCG AAG AAG GCG AAG AAG CCC GCG GCG GCT GGC GTC AAA AAG    GTG GCG AAG AGC CCT AAG AAG GCC AAG GCC GCT GCC AAA CCG AAA AAG GCA    ACC AAG AGT CCT GCC AAG CCC AAG GCA GTT AAG CCG AAG GCG GCA AAG CCC    AAA GCC GCT AAG CCC AAA GCA GCA AAA CCT AAA GCT GCA AAG GCC AAG AAG    GCG GCT GCC AAA AAG AAG TAG GAA GCT GGC GTG TGA AAA CCG CAA CAA AGC    CCC AAA GGC TCT TTT CAG AGC CAC CCAAs you all know, sequences of DNA and RNA are frequently represented by strings of letters corresponding to the bases:"A" adenine  "C" cytosine   "G" guanine  "T" thymine  "U" uracil (which replaces thymine in RNA)   Question 1When a gene is expressed, the first thing that happens is that the template strand is separated from the coding strand and gets associated with a messenger RNA (mRNA).  This sequence of mRNA is therefore almost the same as the coding sequence, except that the thymine bases (T) have been replaced by uracil bases (U).Compute the equivalent mRNA sequence to the sequence in this example.<jupyter_code>coding_sequence = (
    "AACCTGCTCTTTAGATTTCGAGCTTATTCTCTTCTAGCAGTTTCTTGCCACCATGTCGGAAACCGCTCCT" +
    "GCCGAGACAGCCACCCCAGCGCCGGTGGAGAAATCCCCGGCTAAGAAGAAGGCAACTAAGAAGGCTGCCG" +
    "GCGCCGGCGCTGCTAAGCGCAAAGCGACGGGGCCCCCAGTCTCAGAGCTGATCACCAAGGCTGTGGCTGC" +
    "TTCTAAGGAGCGCAATGGCCTTTCTTTGGCAGCCCTTAAGAAGGCCTTAGCGGCCGGTGGCTACGACGTG" +
    "GAGAAGAATAACAGCCGCATTAAGCTGGGCCTCAAGAGCTTGGTGAGCAAGGGCACCCTGGTGCAGACCA" +
    "AGGGCACTGGTGCTTCTGGCTCCTTTAAACTCAACAAGAAGGCGGCCTCCGGGGAAGCCAAGCCCAAAGC" +
    "CAAGAAGGCAGGCGCCGCTAAAGCTAAGAAGCCCGCGGGGGCCACGCCTAAGAAGGCCAAGAAGGCTGCA" +
    "GGGGCGAAAAAGGCAGTGAAGAAGACTCCGAAGAAGGCGAAGAAGCCCGCGGCGGCTGGCGTCAAAAAGG" +
    "TGGCGAAGAGCCCTAAGAAGGCCAAGGCCGCTGCCAAACCGAAAAAGGCAACCAAGAGTCCTGCCAAGCC" +
    "CAAGGCAGTTAAGCCGAAGGCGGCAAAGCCCAAAGCCGCTAAGCCCAAAGCAGCAAAACCTAAAGCTGCA" +
    "AAGGCCAAGAAGGCGGCTGCCAAAAAGAAGTAGGAAGCTGGCGTGTGAAAACCGCAACAAAGCCCCAAAG" +
    "GCTCTTTTCAGAGCCACCCA"
)<jupyter_output><empty_output><jupyter_text>Hint: See if there is a string method that lets you *replace* substrings.<jupyter_code># Your code goes here
mRNA_sequence = coding_sequence.replace("T", "U")
print(mRNA_sequence)<jupyter_output><empty_output><jupyter_text>Question 2A gene encodes a protein by specifying the aminoacids that compose it viagroups of 3 bases (codons).  In the usual genetic code thesequence "ATG" indicates the start of the encoding of the protein.Find the index of the start of the coding section in the coding sequence above.Hint: Look for a string method that helps you *find* a substring.<jupyter_code># Your code goes here
start_codon = coding_sequence.find('ATG')
print(start_codon)<jupyter_output><empty_output><jupyter_text>Question 3The end of the encoding of a protein is indicated by one of three "stop"codons: "TAA", "TAG" or "TGA".  In this case the stop is 'TAG'.  Find the'TAG' codon closest to the end of the coding sequence above.Hin: Look for a string method that helps you find a substring starting from the *right* of a string.<jupyter_code># Your code goes here
coding_sequence.rfind('TAG')<jupyter_output><empty_output><jupyter_text>Question 4Now we will look at a dictionary called `codon_table` that maps codons to their corresponding amino acid abbreviations (the stop codons are usually abbreviated by an `*`). Extract the abbreviation associated with the codon "AAG" and then print it in a statement that says "AAG is associated with the aminoacid X."<jupyter_code>codon_table = {
    'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L',
    'TCT': 'S', 'TCC': 'S', 'TCA': 'S', 'TCG': 'S',
    'TAT': 'Y', 'TAC': 'Y', 'TAA': '*', 'TAG': '*',
    'TGT': 'C', 'TGC': 'C', 'TGA': '*', 'TGG': 'W',

    'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L',
    'CCT': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P',
    'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q',
    'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R',

    'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M',
    'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T',
    'AAT': 'N', 'AAC': 'N', 'AAA': 'K', 'AAG': 'K',
    'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R',

    'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V',
    'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A',
    'GAT': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E',
    'GGT': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G',
}
# Your code goes here
codon = "AAG"
abbr = codon_table[codon]
print("'AAG' is associated to '{}' ".format(abbr))

## Check out the Appendix for more on printing string formatting<jupyter_output><empty_output><jupyter_text>Question 5Below is another dictionary named `amino_acid_table` that maps the aminoacid abbreviations to their full names.  Extract the full name of the aminoacid associated with the codon "CAA".<jupyter_code>amino_acid_table = {
    'A': "alanine",
    'C': "cystine",
    'D': "aspartic acid",
    'E': "glutamic acid",
    'F': "phenylalanine",
    'G': "glycine",
    'H': "histidine",
    'I': "isoleucine",
    'K': "lysine",
    'L': "leucine",
    'M': "methionine/start",
    'N': "asparagine",
    'P': "proline",
    'Q': "glutamine",
    'R': "arginine",
    'S': "serine",
    'T': "threonine",
    'V': "valine",
    'W': "tryptophan",
    'Y': "tyrosine",
    '*': "stop",
}

codon = "CAA"
# Your code goes here
abbr = codon_table[codon]
aa_name = amino_acid_table[abbr]
print "'CAA' encodes for {}".format(aa_name)<jupyter_output><empty_output><jupyter_text>1.4 Appendix  How Python Assignment WorksWhat happens when we assign a variable to an object?A simple example:    x = 11    y = x    y = 'boo'    print(x)Let's step through what's happening:<jupyter_code>x = 11<jupyter_output><empty_output><jupyter_text>In executing this line, we created an integer object `x` that somewhere it memory points to the value 12 when the right hand side of the assignment is evaluated, and then the equals assignment creates, or binds, a reference to it in the current namespace.We can see the namespace with IPython's `%whos` [magic command](http://ipython.readthedocs.io/en/stable/interactive/magics.html):<jupyter_code>%whos<jupyter_output><empty_output><jupyter_text>Every object in Python has a unique identifier which you can see with the `id()` built-in function:<jupyter_code>id(x)<jupyter_output><empty_output><jupyter_text>At any given moment all ids are unique.Let's look at the next line:<jupyter_code>y = x<jupyter_output><empty_output><jupyter_text>The righthand side is `x` that causes a look-up of the variable x in the local namespace, which returns the reference to the integer object. This is then bound to the variable `y` on the lefthand side.We can see the state of the namespace:<jupyter_code>id(y)<jupyter_output><empty_output><jupyter_text>Then...<jupyter_code>y = 'boo'<jupyter_output><empty_output><jupyter_text>... creates a new string object on the righthand side with the value `'boo'` in it, and the assignment binds it to the variable `y` in the local namespace, removing the binding that `y` had to 11. Let's check our namespace out:<jupyter_code>%whos<jupyter_output><empty_output><jupyter_text>but we also see that y now refers to a completely different obejct:<jupyter_code>id(y)<jupyter_output><empty_output><jupyter_text>Finally the print statement looks up the object referred to by `x`, and displays a text representation of it:<jupyter_code>print(x)<jupyter_output><empty_output><jupyter_text>Assignment of a container objectLet's compare this with the following similar, but slightly different code:    x = [500, 501, 502]    y = x    y[1] = 600    print(x)    y = [700, 800]We start with:<jupyter_code>x = [500, 501, 502]<jupyter_output><empty_output><jupyter_text>Python evaluates the righthand side first, creating objects for the numbers 500, 501 and 502, and then creating a list object which has the references to those objects bound to the indices 0, 1 and 2 respectively. Finally, a reference to the new list object is bound to `x`.Checking the ids, you see we have:<jupyter_code>id(x)
id(x[0])
id(x[1])
id(x[2])<jupyter_output><empty_output><jupyter_text>There are 4 different objects for each value in the list.Then we have:<jupyter_code>y = x<jupyter_output><empty_output><jupyter_text>the variable reference on the right is evaluated and the object bound to the variable on the left.So our namespace looks like this:<jupyter_code>%whos<jupyter_output><empty_output><jupyter_text>but just like the simple example, `x` and `y` refer to the same object. Let's ask Python if this is true with a boolean statement:<jupyter_code>id(x) == id(y)<jupyter_output><empty_output><jupyter_text>So the next line is a little different. In previous assignments we have had a simple variable on the left, and assignment has meant "bind this variable in the namespace to the object on the right".  But here we have `y` with an index on the left.This can be informally thought of as being the equivalent of a function call something like:    set_item(y, 1, 600)This is saying that I need to set an item in `y`, at index `1` and with value `600`.So when we execute:<jupyter_code>y[1] = 600<jupyter_output><empty_output><jupyter_text>Python creates a new integer object with value 600 for the value on the right, a new integer object with value 1 for the index (actually, 1 already exists, but we can pretend we created it), and looks up the object referred to by `y`, which is the Python list.Then the `set_item` function changes the list so that index 1 is now bound to 600, and the binding of index 1 to 501 is dropped.So we have changed the contents of the list at index 1.  And when it comes time to print out x:<jupyter_code>print(x)<jupyter_output><empty_output><jupyter_text>we look up the object bound to `x`, which is our list. Now we never explicitly touched `x`: it used to be `[500, 501, 502]`, but when we print it out it's `[500, 600, 502]`.This is a feature of Python - you can have two variables pointing at the same container, it is done all the time and it can be quite useful, but it can lead to this sort of "action at a distance" where changes to one variable affect the value of another. But once you understand what is happening in memory, it isn't that surprising.Finally, what happens if we assign `y` to a new list?<jupyter_code>y = [700, 800]<jupyter_output><empty_output><jupyter_text>This is the standard assignment pattern, so this will create the object on the right and bind it to the specified object on the left. So we get new integer objects for 700 and 800, and a new list which has its first and second indices bound to those objects.  And then `y` is bound to that new list, breaking the previous binding for the variable, so `x` and `y` now refer to different objects.  String formatting String formatting is needed as soon as you want to control how text and data are displayed when printed in the terminal, in a data file or in a report that you need to generate. The recommeded way is by using the `format` method on a string. ExamplesThe strategy is to create a string that you want to print. Curly brackets will indicate where (and potentially how) to display variables that are provided to the format method:<jupyter_code>print('{} {} {}')
print('{} {} {}'.format("a", "b", "c"))
string = 'My favorite number is {}'
print(string.format(3.145926))<jupyter_output><empty_output><jupyter_text>The fields where variables are injected can be numbered too (they must be numbered in Python 2.6):<jupyter_code>print('{2} {1} {0}'.format("a", "b", "c"))
print('{color} {int_num} {float_num}'.format(int_num=10, float_num=1.5, color='blue'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08832837461388315, 'fraction_numerical': 0.008641437207451344, 'mean_word_length': 5.35094475869539, 'pattern_counts': {'":': 2, '<': 471, '<?xml version=': 0, '>': 479, 'https://': 14, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 23, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '117', 'dir': 'jupyter-structured-clean-dedup', 'id': '627766', 'license': 'MIT', 'n_tokens_mistral': 16613, 'n_tokens_neox': 15622, 'n_words': 6953, 'path': 'day-1/day_1-with-solutions.ipynb', 'repo_name': 'LBHB/python_neurobootcamp', 'size': '52785'}