my_variable = 10

"""
Explanation: Table of Contents
<p><div class="lev1 toc-item"><a href="#Welcome!" data-toc-modified-id="Welcome!-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Welcome!</a></div><div class="lev1 toc-item"><a href="#What-is-Python?-" data-toc-modified-id="What-is-Python?--2"><span class="toc-item-num">2&nbsp;&nbsp;</span>What is Python? <a id="whatispython"></a></a></div><div class="lev2 toc-item"><a href="#Python-(the-language)" data-toc-modified-id="Python-(the-language)-21"><span class="toc-item-num">2.1&nbsp;&nbsp;</span>Python (the language)</a></div><div class="lev3 toc-item"><a href="#What's-the-deal-with-whitespace?" data-toc-modified-id="What's-the-deal-with-whitespace?-211"><span class="toc-item-num">2.1.1&nbsp;&nbsp;</span>What's the deal with whitespace?</a></div><div class="lev2 toc-item"><a href="#Python-(the-interpreter)" data-toc-modified-id="Python-(the-interpreter)-22"><span class="toc-item-num">2.2&nbsp;&nbsp;</span>Python (the interpreter)</a></div><div class="lev1 toc-item"><a href="#Variables,-Objects,-Operators,-and-Naming" data-toc-modified-id="Variables,-Objects,-Operators,-and-Naming-3"><span class="toc-item-num">3&nbsp;&nbsp;</span>Variables, Objects, Operators, and Naming</a></div><div class="lev2 toc-item"><a href="#Data-types" data-toc-modified-id="Data-types-31"><span class="toc-item-num">3.1&nbsp;&nbsp;</span>Data types</a></div><div class="lev3 toc-item"><a href="#Booleans" data-toc-modified-id="Booleans-311"><span class="toc-item-num">3.1.1&nbsp;&nbsp;</span>Booleans</a></div><div class="lev3 toc-item"><a href="#Integers" data-toc-modified-id="Integers-312"><span class="toc-item-num">3.1.2&nbsp;&nbsp;</span>Integers</a></div><div class="lev3 toc-item"><a href="#Floats" data-toc-modified-id="Floats-313"><span class="toc-item-num">3.1.3&nbsp;&nbsp;</span>Floats</a></div><div class="lev3 toc-item"><a href="#Strings" data-toc-modified-id="Strings-314"><span class="toc-item-num">3.1.4&nbsp;&nbsp;</span>Strings</a></div><div class="lev2 toc-item"><a href="#An-aside:-What's-the-deal-with-those-underscores???" data-toc-modified-id="An-aside:-What's-the-deal-with-those-underscores???-32"><span class="toc-item-num">3.2&nbsp;&nbsp;</span>An aside: What's the deal with those underscores???</a></div><div class="lev1 toc-item"><a href="#Collections-of-objects" data-toc-modified-id="Collections-of-objects-4"><span class="toc-item-num">4&nbsp;&nbsp;</span>Collections of objects</a></div><div class="lev2 toc-item"><a href="#Lists" data-toc-modified-id="Lists-41"><span class="toc-item-num">4.1&nbsp;&nbsp;</span>Lists</a></div><div class="lev2 toc-item"><a href="#Tuples" data-toc-modified-id="Tuples-42"><span class="toc-item-num">4.2&nbsp;&nbsp;</span>Tuples</a></div><div class="lev2 toc-item"><a href="#Sets" data-toc-modified-id="Sets-43"><span class="toc-item-num">4.3&nbsp;&nbsp;</span>Sets</a></div><div class="lev2 toc-item"><a href="#Dictionaries" data-toc-modified-id="Dictionaries-44"><span class="toc-item-num">4.4&nbsp;&nbsp;</span>Dictionaries</a></div><div class="lev1 toc-item"><a href="#Control-structures" data-toc-modified-id="Control-structures-5"><span class="toc-item-num">5&nbsp;&nbsp;</span>Control structures</a></div><div class="lev2 toc-item"><a href="#if-elif-else" data-toc-modified-id="if-elif-else-51"><span class="toc-item-num">5.1&nbsp;&nbsp;</span>if-elif-else</a></div><div class="lev2 toc-item"><a href="#For-loops" data-toc-modified-id="For-loops-52"><span class="toc-item-num">5.2&nbsp;&nbsp;</span>For loops</a></div><div class="lev2 toc-item"><a href="#While-loops" data-toc-modified-id="While-loops-53"><span class="toc-item-num">5.3&nbsp;&nbsp;</span>While loops</a></div><div class="lev1 toc-item"><a href="#Functions" data-toc-modified-id="Functions-6"><span class="toc-item-num">6&nbsp;&nbsp;</span>Functions</a></div><div class="lev2 toc-item"><a href="#Built-in-Python-functions" data-toc-modified-id="Built-in-Python-functions-61"><span class="toc-item-num">6.1&nbsp;&nbsp;</span>Built-in Python functions</a></div><div class="lev2 toc-item"><a href="#Custom-functions" data-toc-modified-id="Custom-functions-62"><span class="toc-item-num">6.2&nbsp;&nbsp;</span>Custom functions</a></div><div class="lev3 toc-item"><a href="#Required-arguments-vs-optional-arguments" data-toc-modified-id="Required-arguments-vs-optional-arguments-621"><span class="toc-item-num">6.2.1&nbsp;&nbsp;</span>Required arguments vs optional arguments</a></div><div class="lev1 toc-item"><a href="#Modules" data-toc-modified-id="Modules-7"><span class="toc-item-num">7&nbsp;&nbsp;</span>Modules</a></div><div class="lev2 toc-item"><a href="#The-standard-library" data-toc-modified-id="The-standard-library-71"><span class="toc-item-num">7.1&nbsp;&nbsp;</span>The standard library</a></div><div class="lev2 toc-item"><a href="#Third-party-libraries-and-the-Python-Package-Index" data-toc-modified-id="Third-party-libraries-and-the-Python-Package-Index-72"><span class="toc-item-num">7.2&nbsp;&nbsp;</span>Third party libraries and the Python Package Index</a></div><div class="lev1 toc-item"><a href="#Wrapping-up" data-toc-modified-id="Wrapping-up-8"><span class="toc-item-num">8&nbsp;&nbsp;</span>Wrapping up</a></div><div class="lev2 toc-item"><a href="#Other-resources" data-toc-modified-id="Other-resources-81"><span class="toc-item-num">8.1&nbsp;&nbsp;</span>Other resources</a></div>

# Welcome!

This is a 4 week (8 hour) course that will introduce you to the basics of handling, manipulating, exploring, and modeling data with Python. This notebook is a review of some Python language essentials. We'll go over this material during the first class, but taking a look at it before then will help you out, especially if you haven't used Python before.

The environment you're in right now is called a Jupyter notebook. [Project Jupyter](http://jupyter.org/) is an interactive environment that data scientists use for collaboration and communication. Each cell in the notebook can either contain text or code (often Python, but R, Julia, and lots of other languages are supported). This allows you to seamlessly weave explanations and plots into your code.

Each cell in a notebook can be executed independently, but declarations persist across cells. For example, I can define a variable in one cell...
End of explanation
"""


print(my_variable)

"""
Explanation: ... and then access that variable in a later cell:
End of explanation
"""


a = 10
b = 15
print(a + b)

"""
Explanation: We'll be using Jupyter notebooks extensively in this class. I'll give a more detailed introduction during the first class, but for now, the most important thing is to understand how to run code in the notebook.
As I mentioned above, there are two fundamental types of cells in a notebook - text (i.e. markdown) and code. When you click on a code cell, you should see a cursor appear in the cell that allows you to edit the code in that cell. A cell can have multiple lines - to begin a new line, press Enter. When you want to run the cell's code, press Shift+Enter.
Try changing the values of the numbers that are added together in the cell below, and observe how the output changes:
End of explanation
"""


import this

"""
Explanation: You can also edit the text in markdown cells. To display the editable, raw markdown in a text cell, double click on the cell. You can now put your cursor in the cell and edit it directly. When you're done editing, press Shift+Enter to render the cell into a more readable format.
Try editing text cell below with your name:
Make some edits here -> Hello, my name is Nick!
To change whether a cell contains text or code, use the drop-down in the toolbar. When you're in a code cell, it will look like this:

and when you're in a text cell, it will look like this:

Now that you know how to navigate the notebook, let's review some basic Python.
What is Python? <a id='whatispython'></a>
This is actually a surprisingly tricky question! There are (at least) two answers:

A language specification
A program on your computer that interprets and executes code written to that language specification

Python (the language)
Python is an open source programming language that is extremely popular in the data science and web development communities. The roots of its current popularity in data science and scientific computing have an interesting history, but suffice to say that it's darn near impossible to be a practicing data scientist these days without at least being familiar with Python.
The guiding principles behind the design of the Python language specification are described in "The Zen of Python", which you can find here or by executing:
End of explanation
"""


print('Entering the for loop:\n')
for count in range(10):
    print(count)
    print('Still in the for loop.')

print("\nNow I'm done with the for loop.")

"""
Explanation: Python syntax should be easy to write, but most importantly, well-written Python code should be easy to read. Code that follows these norms is called Pythonic. We'll touch a bit more on what it means to write Pythonic code in class.
What's the deal with whitespace?
A unique feature of Python is that whitespace matters, because it defines scope. Many other programming languages use braces or begin/end keywords to define scope. For example, in Javascript, you write a for loop like this:
var count;
for(count = 0; count &lt; 10; count++){
               console.log(count);
               console.log("&lt;br /&gt;");
            }
The curly braces here define the code executed in each iteration of the for loop. Similarly, in Ruby you write a for loop like this:
for count in 0..9
   puts "#{count}"
end
In this snippet, the code executed in each iteration of the for loop is whatever comes between the first line and the end keyword.
In Python, for loops look a bit different:
End of explanation
"""


thing_1 = 47    # define an int object
print(thing_1.__doc__)

thing_1 = 'blah'    # reassign thing_1 to an string object
print(thing_1.__doc__)

"""
Explanation: Note that there is no explicit symbol or keyword that defines the scope of code executed during each iteration - it's the indentation that defines the scope of the loop. When you define a function or class, or write a control structure like a for look or if statement, you should indent the next line (4 spaces is customary). Each subsequent line at that same level of indentation is considered part of the scope. You only escape the scope when you return to the previous level of indentation.
Python (the interpreter)
If you open up the terminal on your computer and type python, it runs a program that looks something like this:

This is a program called CPython (written in C, hence the name) that parses, interprets, and executes code written to the Python language standard. CPython is known as the "reference implementation" of Python - it is an open source project (you can download and build the source code yourself if you're feeling adventurous) run by the Python Software Foundation and led by Guido van Rossum, the original creator and "Benevolent Dictator for Life" of Python.
When you type simply python into the command line, CPython brings up a REPL (Read Execute Print Loop, pronounced "repple"), which is essentially an infinite loop that takes lines as you write them, interprets and executes the code, and prints the result.
For example, try typing
```



x = 'Hello world"
print(x)
```



in the REPL. After you hit Enter on the first line, the interpreter assigns the value "Hello world" to a string variable x. After you hit Enter on the second line, it prints the value of x.
We can accomplish the same result by typing the same code
x = "Hello world"
print(x)
into a file called test.py and running python test.py from the command line. The only difference is that when you provide the argument test.py to the python command, the REPL doesn't appear. Instead, the CPython interpreter interprets the contents of test.py line-by-line until it reaches the end of the file, then exits. We won't use the REPL much in this course, but it's good to be aware that it exists. In fact, behind the pretty front end, this Jupyter notebook is essentially just wrapping the CPython interpreter, executing commands line by line as we enter them.
So to review, "Python" sometimes refers to a language specification and sometimes refers to an interpreter that's installed on your computer. We will use the two definitions interchangeably in this course; hopefully, it should be obvious from context which definition we're referring to.
Variables, Objects, Operators, and Naming
One fundamental idea in Python is that everything is an object. This is different than some other languages like C and Java, which have fundamental, primitive data types like int and char. This means that things like integers and strings have attributes and methods that you can access. For example, if you want to read some documentation about an object my_thing, you can access its __doc__ attribute like this:
End of explanation
"""


dir(thing_1)

"""
Explanation: To learn more about what attributes and methods a given object has, you can call dir(my_object):
End of explanation
"""


thing_2 = 'abcd'
thing_3 = thing_1.__add__(thing_2)
print(thing_3)

"""
Explanation: That's interesting - it looks like the string object has a method called __add__. Let's see what it does -
End of explanation
"""


thing_4 = thing_1 + thing_2
print(thing_4)
print(thing_3 == thing_4)

"""
Explanation: So calling __add__ with two strings creates a new string that is the concatenation of the two originals. As an aside, there are a lot more methods we can call on strings - split, upper, find, etc. We'll come back to this.
The + operator in Python is just syntactic sugar for the __add__ method:
End of explanation
"""


int_1 = 11
int_2 = 22
sum_1 = int_1.__add__(int_2)
sum_2 = int_1 + int_2
print(sum_1)
print(sum_2)
print(sum_1 == sum_2)

"""
Explanation: Any object you can add to another object in Python has an __add__ method. With integer addition, this works exactly as we would expect:
End of explanation
"""


thing_1 + int_1

"""
Explanation: But it's unclear what to do when someone tries to add an int to a str:
End of explanation
"""


bool_1 = True
type(bool_1)

dir(bool_1)

bool_2 = False

bool_1 == bool_2

bool_1 + bool_2

bool_1 and bool_2

type(bool_1 * bool_2)

"""
Explanation: Data types
There are a few native Python data types, each of which we'll use quite a bit. The properties of these types work largely the same way as they do in other languages. If you're ever confused about what type a variable my_var is, you can always call type(my_var).
Booleans
Just like in other languages, bools take values of either True or False. All of the traditional Boolean operations are present:
End of explanation
"""


int_1 = 2
type(int_1)

dir(int_1)

int_2 = 3
print(int_1 - int_2)

int_1.__pow__(int_2)

int_1 ** int_2

"""
Explanation: Integers
Python ints are whole (positive, negative, or 0) numbers implemented as long objects of arbitrary size. Again, all of the standard operations are present:
End of explanation
"""


int_1 / int_2

type(int_1 / int_2)

int_1.__truediv__(int_2)

int_1.__divmod__(int_2)

"""
Explanation: One change from Python 2 to Python 3 is the default way that integers are divided. In Python 2, the result of 2/3 is 0, the result of 4/3 is 1, etc. In other words, dividing integers in Python 2 always returned an integer with any remainder truncated. In Python 3, the result of the division of integers is always a float, with a decimal approximation of the remainder included. For example:
End of explanation
"""


float_1 = 23.46
type(float_1)

dir(float_1)

float_2 = 3.

float_1 / float_2

"""
Explanation: Floats
Python floats are also consistent with other languages:
End of explanation
"""


int_1 < int_2

float_1 >= int_2

float_1 == float_2

"""
Explanation: With ints and floats, we can also do comparison operators like in other languages:
End of explanation
"""


str_1 = 'hello'
type(str_1)

dir(str_1)

"""
Explanation: Strings
End of explanation
"""


a = 'Hi'
print(a*5)

"""
Explanation: We already saw that the + operator concatenates two strings. Generalizing from this, what do you expect the * operator to do?
End of explanation
"""


# count the number of occurances of a sub-string
"Hi there I'm Nick".count('i')

# Find the next index of a substring
"Hi there I'm Nick".find('i')

"Hi there I'm Nick".find('i', 2)

# Insert variables into a string
digit = 7
'The digit "7" should appear at the end of this sentence: {}.'.format(digit)

another_digit = 15
'This sentence will have two digits at the end: {} and {}.'.format(digit, another_digit)

# Replace a sub-string with another sub-string
my_sentence = "Hi there I'm Nick"
my_sentence.replace('e', 'E')

my_sentence.replace('N', '')

"""
Explanation: There are a number of very useful methods built into Python str objects. A few that you might find yourself needing to use when dealing with text data include:
End of explanation
"""


my_list = ['a', 'b', 'c', 'a']

len(my_list)

my_list.append(1)
print(my_list)

"""
Explanation: There are plenty more useful string functions - use either the dir() function or Google to learn more about what's available.
So, to sum it up - basic data types like bool, int, float, and str are all objects in Python. The methods in each of these object classes define what operations can be done on them and how those operations are performed. For the sake of readability, however, many of the common operations like + and < are provided as syntactic sugar.
An aside: What's the deal with those underscores???
When we were looking at the methods in the various data type classes above, we saw a bunch of methods like __add__ and __pow__ with double leading underscores and double trailing underscores (sometimes shorted to "dunders"). As it turns out, underscores are a bit of a thing in Python. Idiomatic use dictates a few important uses of underscores in variable and function names:

Underscores are used to separate words in names. That is, idiomatic Python uses snake_case (my_variable) rather than camelCase (myVariable).
A single leading underscore (_my_function or _my_variable) denotes a function or variable that is not meant for end users to access directly. Python doesn't have a sense of strong encapsulation, i.e. there are no strictly "private" methods or variables like in Java, but a leading underscore is a way of "weakly" signaling that the entity is for private use only.
A single training underscore (type_) is used to avoid conflict with Python built-in functions or keywords. In my opinion, this is often poor style. Try to come up with a more descriptive name instead.
Double leading underscore and double trailing underscore (__init__, __add__) correspond to special variables or methods that correspond to some sort of "magic" syntax. As we saw above, the __add__ method of an object describes what the result of some_object + another_object is.

For lots more detail on the use of underscores in Python, check out this post.
Collections of objects
Single variables can only take us so far. Eventually, we're going to way to have ways of storing many individual variables in a single, structured format.
Lists
The list is one of the most commonly used Python data structures. A list is an ordered collection of (potentially heterogeneous) objects. Similar structures that exist in other languages are often called arrays.
End of explanation
"""


my_list[0]    # indexing in Python starts at 0!

my_list[4]

my_list[-1]    # negative indexes count backward from the end of the list

"""
Explanation: To access individual list elements by their position, use square brackets:
End of explanation
"""


type(my_list[0])

type(my_list[-1])

# let's do something crazy
my_list.append(my_list)
type(my_list[-1])

my_list

my_list[-1]

my_list[-1][-1]

my_list[-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1][-1]

"""
Explanation: Lists can hold arbitrary objects!
End of explanation
"""


my_list = ['a', 'b', 1]

my_list[0] = 'c'
my_list

my_list.remove(1)
my_list

"""
Explanation: Lists are also mutable objects, meaning that any part of them can be changed at any time. This makes them very flexible objects for storing data in a program.
End of explanation
"""


my_tuple = ('a', 'b', 1, 'a')

my_tuple[2]

my_tuple[0] = 'c'

my_tuple.append('c')

my_tuple.remove(1)

"""
Explanation: Tuples
A tuple in Python is very similar to a list, except that tuples are immutable. This means that once they're defined, they can't be changed. Otherwise, they act very much like lists.
End of explanation
"""


my_set = {'a', 'b', 1, 'a'}
print(my_set)    # note that order

my_set.add('c')
print(my_set)

"""
Explanation: Sets
A set in Python acts somewhat like a list that contains only unique objects.
End of explanation
"""


my_set[0]

"""
Explanation: Note above that the order of items in a set doesn't have the same meaning as in lists and tuples.
End of explanation
"""


my_list = ['a', 'a', 'a', 'a', 'b', 'b', 'b']
my_list

my_set = set(my_list)
len(my_set)

"""
Explanation: Sets are used for a couple reasons. Sometimes, finding the number of unique items in a list or tuple is important. In this case, we can convert the list/tuple to a set, then call len on the new set. For example,
End of explanation
"""


my_list = list(range(1000000))    # list of numbers 0 - 999,999
my_set = set(my_list)

%%timeit
999999 in my_list

%%timeit
999999 in my_set

"""
Explanation: The other reason is that the in keyword for testing a collection for membership of an object is much faster for a list than a set.
End of explanation
"""


my_dict = {'name': 'Nick',
           'birthday': 'July 13',
           'years_in_durham': 4}

my_dict['name']

my_dict['years_in_durham']

my_dict['favorite_restaurant'] = 'Mateo'
my_dict['favorite_restaurant']

my_dict['age']    # hey, that's personal. Also, it's not a key in the dictionary.

"""
Explanation: Any idea why there's such a discrepancy?
Dictionaries
The final fundamental data structure we'll cover is the Python dictionary (aka "hash" in some other languages). A dictionary is a map of keys to values.
End of explanation
"""


my_dict.keys()

my_dict.values()

"""
Explanation: In addition to accessing values by keys, you can retrieve the keys and values by themselves as lists:
End of explanation
"""


x = 3

if x < 2:
    print('x less than 2')
elif x < 4:
    print('x less than 4, greater than or equal to 2')
else:
    print('x greater than or equal to 4')

"""
Explanation: Note that if you're using Python 3.5 or earlier, the order that you insert key/value pairs into the dictionary doesn't correspond to the order they're stored in by default (we inserted favorite_restaurant after years_in_durham!). This default behavior was just recently changed in Python 3.6 (released in December 2016).
Control structures
As data scientists, we're data-driven people, and we want our code to be data-driven, too. Control structures are a way of adding a logical flow to your programs, making them reactive to different conditions. These concepts are largely the same as in other programming languages, so I'll quickly introduce the syntax here for reference without much comment.
if-elif-else
Like most programming languages, Python provides a way of conditionally evaluating lines of code.
End of explanation
"""


my_list = ['a', 'b', 'c']
for element in my_list:
    print(element)

"""
Explanation: For loops
In Python, a for loop iterates over the contents of a container like a list. For example:
End of explanation
"""


for i in range(5):   # iterate over all integers (starting at 0) less than 5
    print(i)

for i in range(2, 6, 3):    # iterate over integers (starting at 2) less than 6, increasing by 3
    print(i)

"""
Explanation: To iterate for a specific number of times, you can create an iterator object with the range function:
End of explanation
"""


my_list = ['a', 'b', 'c']
idx = 0
while idx < len(my_list):
    print(my_list[idx])
    idx += 1

my_list = ['a', 'b', 'c']
for element in my_list:
    print(element)

"""
Explanation: While loops
Python also has the concept of while loops. From a stylistic reasons, while loops are used somewhat less often than for loops. For example, compare the two following blocks of code:
End of explanation
"""


my_list = range(1000000)

%%timeit
sum(my_list)

%%timeit
my_sum = 0
for element in my_list:
    my_sum += element
my_sum

"""
Explanation: There are occasionally other reasons for using while loops (waiting for an external input, for example), but we won't make extensive use of them in this course.
Functions
Of course, as data scientists, one of our most important jobs is to manipulate data in a way that provides insight. In other words, we need ways of taking raw data, doing some things to it, and returning nice, clean, processed data back. This is the job of functions!
Built-in Python functions
It turns out that Python has a ton of functions built in already. When we have a task that can be accomplished by a built-in function, it's almost always a good idea to use them. This is because many of the Python built-in functions are actually written in C, not Python, and C tends to be much faster for certain tasks.
https://docs.python.org/3.5/library/functions.html
End of explanation
"""


def double_it(x):
    return x * 2

double_it(5)

"""
Explanation: Some common mathematical functions that are built into Python:

sum
divmod
round
abs
max
min

And some other convenience functions, some of which we've already seen:

int, float, str, set, list, dict: for converting between data structures
len: for finding the number of elements in a data structure
type: for finding the type that an object belongs to

Custom functions
Of course, there are plenty of times we want to do something that isn't provided by a built-in. In that case, we can define our own functions. The syntax is quite simple:
End of explanation
"""


double_it('hello')   # remember 'hello' * 2 from before?

double_it({'a', 'b'})    # but there's no notion of multiplication for sets

"""
Explanation: Python has dynamic typing, which (in part) means that the arguments to functions aren't assigned a specific type:
End of explanation
"""


def multiply_them(x, y, extra_arg1=None, extra_arg2=None):
    if extra_arg1 is not None:
        print(extra_arg1)
    if extra_arg2 is not None:
        print(extra_arg2)
    
    print('multiplying {} and {}...'.format(x, y))
    return x * y

multiply_them(3, 5)

multiply_them(3, 5, extra_arg1='hello')

multiply_them(3, 5, extra_arg2='world', extra_arg1='hello')

multiply_them(extra_arg2='world', extra_arg1='hello', 3, 5)

"""
Explanation: Required arguments vs optional arguments
When defining a function, you can add defaults to arguments that you want to be optional. When defining and providing arguments, required arguments always go first, and the order they're provided in matters. Optional arguments follow, and can be passed by their keyword in any order.
End of explanation
"""


import random    # create (pseudo-) random numbers
random.random()

import math    # common mathematical functions that aren't built into base Python
print(math.factorial(5))

math.log10(100)

import statistics    # some basic summary statistics
my_list = [1, 2, 3, 4, 5]
statistics.mean(my_list)

statistics.median(my_list)

statistics.stdev(my_list)

"""
Explanation: Modules
Knowing how to create your own functions can be a rabbit hole - once you know that you can make Python do whatever you want it to do, it can be easy to go overboard. Good data scientists are efficient data scientists - you shouldn't reinvent the wheel by reimplementing a bunch of functionality that someone else worked hard on. Doing anything nontrivial can take a ton of time, and without spending even more time to write tests, squash bugs, and address corner cases, your code can easily end up being much less reliable than code that someone else has spent time perfecting.
Python has a very robust standard library of external modules that come with every Python installation. For even more specialized work, the Python community has also open-sourced tens of thousands of packages, any of which is a simple pip install away.
The standard library
The Python standard library is a collection of packages that ships with Python itself. In other words, it contains a bunch of code that you can import into code you're writing, but that you don't have to download separately after downloading Python.
Here are a few examples -
End of explanation
"""


