# Integer arithematic
1 + 1

# Integer division version floating point division
print (6 // 4, 6/ 4)

"""
Explanation: Today's Objectives
0. Cloning LectureNotes
1. Opening & Navigating the Jupyter Notebook
2. Data type basics
3. Loading data with pandas
4. Cleaning and Manipulating data with pandas
5. Visualizing data with pandas & matplotlib
0. Cloning Lecture Notes
The course materials are maintained on github. The next lecture will discuss github in detail. Today, you'll get minimal instructions to get access to today's lecture materials.

Open a terminal session
Type 'git clone https://github.com/UWSEDS/LectureNotes.git'
Wait until the download is complete
cd LectureNotes
cd 02_Procedural_Python

1. Opening and Navigating the IPython Notebook
We will start today with the interactive environment that we will be using often through the course: the Jupyter Notebook.
We will walk through the following steps together:


Download miniconda (be sure to get Version 3.6) and install it on your system (hopefully you have done this before coming to class)
   


Use the conda command-line tool to update your package listing and install the IPython notebook:


Update conda's listing of packages for your system:
   $ conda update conda
Install IPython notebook and all its requirements
   $ conda install jupyter notebook

Navigate to the directory containing the course material. For example:

$ cd LectureNotes/02_Procedural_Python
You should see a number of files in the directory, including these:
```
   $ ls
```

Type jupyter notebook in the terminal to start the notebook

$ jupyter notebook
If everything has worked correctly, it should automatically launch your default browser
   

Click on Lecture-Python-And-Data.ipynb to open the notebook containing the content for this lecture.

With that, you're set up to use the Jupyter notebook!
2. Data Types Basics
2.1 Data type theory

Components with the same capabilities are of the same type. 
For example, the numbers 2 and 200 are both integers.
A type is defined recursively. Some examples.
A list is a collection of objects that can be indexed by position.
A list of integers contains an integer at each position.
A type has a set of supported operations. For example:
Integers can be added
Strings can be concatented
A table can find the name of its columns
What type is returned from the operation?


In python, members (components and operations) are indicated by a '.'
If a is a list, the a.append(1) adds 1 to the list.

2.2 Primitive types
The primitive types are integers, floats, strings, booleans.
2.2.1 Integers
End of explanation
"""


# Have the full set of "calculator functions" but need the numpy package
import numpy as np
print (6.0 * 3, np.sin(2*np.pi))

# Floats can have a null value called nan, not a number
a = np.nan
3*a

"""
Explanation: 2.2.2 Floats
End of explanation
"""


# Can concatenate, substring, find, count, ...

a = "The lazy"
b = "brown fox"
print ("Concatenation: ", a + b)
print ("First three letters: " + a[0:3])
print ("Index of 'z': " + str(a.find('z')))

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


a_tuple = (1, 'ab', (1,2))
a_tuple

a_tuple[2]

"""
Explanation: 2.3 Tuples
A tuple is an ordered sequence of objects. Tuples cannot be changed; they are immuteable.
End of explanation
"""


a_list = [1, 'a', [1,2]]

a_list[0]

a_list.append(2)
a_list

a_list

dir(a_list)

help (a_list)

a_list.count(1)

"""
Explanation: 2.4 Lists
A list is an ordered sequence of objects that can be changed.
End of explanation
"""


dessert_dict = {}  # Empty dictionary
dessert_dict['Dave'] = "Cake"
dessert_dict["Joe"] = ["Cake", "Pie"]
print (dessert_dict)

dessert_dict["Dave"]

# This produces an error
dessert_dict["Bernease"] = {}
dessert_dict

dessert_dict["Bernease"] = {"Favorite": ["sorbet", "cobbler"], "Dislike": "Brownies"}

"""
Explanation: 2.5 Dictionaries
A dictionary is a kind of associates a key with a value. A value can be any object, even another dictionary.
End of explanation
"""


# A first name shell game
first_int = 1
second_int = first_int
second_int += 1
second_int

# What is first_int?
first_int

# A second name shell game
a_list = ['a', 'aa', 'aaa']
b_list = a_list
b_list.append('bb')
b_list

# What is a_list?
a_list

# Create a deep copy
import copy
# A second name shell game
a_list = ['a', 'aa', 'aaa']
b_list = copy.deepcopy(a_list)
b_list.append('bb')
print("b_list = %s" % str(b_list))
print("a_list = %s" % str(a_list))

"""
Explanation: 2.7 A Shakespearean Detour: "What's in a Name?"
Deep vs. Shallow Copies
A deep copy can be manipulated separately. A shallow copy is a pointer to the same data as the original.
End of explanation
"""


# Example 1 of name resolution in python
var = 10
def func(val):
    var = val + 1
    return val

# What is returned?
print("func(2) = %d" % func(2))
# What is var?
print("var = %d" % var)

# Example 2 of name resolution in python
var = 10
def func(val):
    return val + var

# What is returned?
print("func(2) = %d" % func(2))
# What is var?
print("var = %d" % var)

"""
Explanation: Key insight: Deep vs. Shallow Copies
* A deep copy can be manipulated separately from the original.
* A shallow copy cannot.
* Assigning a python immutable creates a deep copy. Non-immutables are shallow copies.
Name Resolution
The most common errors that you'll see in your python codes are:
* NameError
* AttributeError
A common error when using the bash shell is command not found.
Name resolution: Associating a name with code or data.
Resolving a name in the bash shell is done by searching the directories in the PATH environment variable. The first executable with the name is run.
End of explanation
"""


# A list and a dict are objects.
# dict has been implemented so that you see its values when you type
# the instance name.
# This is done with many python objects, like list.
a_dict = {'a': [1, 2], 'b': [3, 4, 5]}
a_dict

# You access the data and methods (codes) associated with an object by
# using the "." operator. These are referred to collectively
# as attributes. Methods are followed by parentheses;
# values (properties) are not.
a_dict.keys()

# You can discover the attributes of an object using "dir"
dir(a_dict)

"""
Explanation: Insights on python name resolution
* Names are assigned within a context.
* Context changes with the function and module.
  * Assigning a name in a function creates a new name.
  * Referencing an unassigned name in function uses an existing name.
2.7 Object Essentials
Objects are a "packaging" of data and code. Almost all python entities are objects.
End of explanation
"""


