## Very Brief Introduction to Anaconda Scientific Python¶

2. Install Anaconda.
• IPython in the terminal, Python scripts in a text editor (e.g., Notepad++)
• Rodeo (here) (similar to RStudio)
• Spyder (comes with Anaconda, also similar to RStudio)
• PyCharm (here)
• Jupyter Notebook (what you're reading this in)
• other options...
4. Decide what you need to use programming for.
5. Start programming!

### Variable types & variable assignment¶

There are a number of basic variable types in Python.

• Booleans: True, False
• Numeric variables: int, float, long, complex

Use # to demarcate a comment, and use = to assign a value to a variable, e.g,:

In [1]:
v = True # Boolean with value True
w = False # Boolean with value False
x = 5 # int
y = 5.0 # float


You can use print() to display the value of a variable, or you can just type the variable and hit return:

In [2]:
print(v)

True

In [3]:
v

Out[3]:
True
In [4]:
print(w)

False

In [5]:
print(x)

5

In [6]:
print(y)

5.0


Some variables are sequences of smaller units.

• strings
• lists
• tuples

A simple way to create a string is with (single or double) quotation marks:

In [7]:
s = 'this is a string'
print(s)

this is a string


A simple way to create a list is to put elements inside square brackets (separating elements with commas). The elements of a list can be any type of variable, and elements can be different types within a list.

In [8]:
l = [v,w,x,s]
print(l)

[True, False, 5, 'this is a string']


A simple way to create a tuple is to put elements inside parentheses (separating by commas). Elements can be any type of variable, and need not be the same type as each other.

In [9]:
t = (v,w,x,s)
print(t)

(True, False, 5, 'this is a string')


Strings, lists, and tuples support indexing and slicing, which means that you can pick out bits and pieces of these variables. Indexing means indicating a single element of a string/list/tuple, and slicing means indicating multiple elements.

Python uses zero-based indexing (i.e., it starts counting indices at 0). It is useful to think of indices pointing to the space between and to the left of elements. When slicing, the element indicated by the starting index is included in the slice, whereas the element indicated by the ending index is not included. The starting and ending indices are separated by a colon.

So, for example, if we wanted to pick out the first t from our string s and assign it to a new variable r, we would do this:

In [10]:
r = s[0] # indexing the first element of s, assigning it to r
print(s)
print(r)

this is a string
t


If we want to pick out the sequence is a from s and assign it to r, we would do this:

In [11]:
r = s[5:9] # slicing elements 5 through 8 of s, assigning to r
print(r)

is a


Here is an illustration of the inclusion and exclusion of the left and right endpoints of a slice, respectively:

In [12]:
print(s)
print(s[3:12]) # slice from 3 up to, but not including, 12
print(s[3]) # index 3
print(s[12]) # index 12

this is a string
s is a st
s
r


Lists and tuples support indexing and slicing, too:

In [13]:
print(l) # list l assigned above
print(l[1]) # the second element of l
print(l[1:3]) # the second and third elements of l

[True, False, 5, 'this is a string']
False
[False, 5]


If your beginning index is 0, or if your ending index is -1 (i.e., the last element), you can leave it out:

In [14]:
print(l[:3]) # slice from 0 to 3
print(l[2:]) # slice from 2 to the end
print(l[:]) # the whole list

[True, False, 5]
[5, 'this is a string']
[True, False, 5, 'this is a string']


One important difference between lists and tuples is that lists are mutable, whereas tuples are not. This means that you can change lists (e.g., delete an element, make the list longer). You can't do this with tuples.

In [15]:
print(l)
l[2] = 7 # replace the third element with the integer 7
print(l)

[True, False, 5, 'this is a string']
[True, False, 7, 'this is a string']

In [16]:
print(t)
t[2] = 7 # try the same thing with the tuple t, get an error

(True, False, 5, 'this is a string')

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-7473557e6167> in <module>()
1 print(t)
----> 2 t[2] = 7 # try the same thing with the tuple t, get an error

TypeError: 'tuple' object does not support item assignment

As mentioned above, the elements of lists and tuples can be any data type (including lists and tuples, as well as data types we haven't learned about yet).

In [17]:
k = [l, t, x] # a list, a tuple, and a number
print(k)

[[True, False, 7, 'this is a string'], (True, False, 5, 'this is a string'), 5]


If you've programmed in other languages, one important fact about Python might confuse you and end up causing problems. Here's an illustration:

In [18]:
l[2] = 50 # change the third element of l to the number 50
print(k) # print the list k, the first element of which is the list l

[[True, False, 50, 'this is a string'], (True, False, 5, 'this is a string'), 5]


Note that I changed an element of the list l, and this showed up in the list k, which contains l as its first element. That is, the list that is the first element of k and the list l are both referring to the same underlying value. This doesn't happen with numeric types:

In [19]:
x = 10 # change the value of x
print(x)
print(k) # print the list k, which contains (the old) x

10
[[True, False, 50, 'this is a string'], (True, False, 5, 'this is a string'), 5]


One more very useful basic data type in Python is the dictionary. Like lists and tuples, dictionaries can contain multiple items of any type. However, unlike lists and tuples, dictionaries have keys and values, so instead of indexing or slicing, you retrieve elements by name:

In [20]:
d = {'fred':5, 'bill':l, 'kurt':w}
print(d)
print(d['kurt'])

{'kurt': False, 'bill': [True, False, 50, 'this is a string'], 'fred': 5}
False


Finally, Python also has basic set data type, which are unordered collections of unique elements. Because they are unordered, indexing and slicing don't work, but you can add or remove elements from a set using the add() and discard() or remove() methods (we'll talk about methods below). The elements of sets cannot be mutable.

In [21]:
u = set((1,2,2,3,'a','a','b','c','c','c'))
print(u)
print(u)
u.discard('c') # get rid of 'c' as an element
print(u)

{1, 2, 3, 'c', 'b', 'a'}
{1, 2, 3, 'c', 'b', 5, 'a'}
{1, 2, 3, 'b', 5, 'a'}


### Basic operations on variables¶

There are a number of basic operations that can be performed on different data types.

Basic math operators

addition: +

subtraction: -

multplication: *

division: /

division with flooring (rounding down): //

modulus: %

exponentiation: **

matrix multiplication: @ (we'll come back to this later)

In [22]:
5 + 3

Out[22]:
8
In [23]:
5 - 3

Out[23]:
2
In [24]:
5 * 3

Out[24]:
15
In [25]:
5 / 3

Out[25]:
1.6666666666666667
In [26]:
5 // 3

Out[26]:
1
In [27]:
5 % 3

Out[27]:
2
In [28]:
5**3

Out[28]:
125

The + and * operators also work with strings, lists, and tuples:

In [29]:
s + ' ' + s[5:]

Out[29]:
'this is a string is a string'
In [30]:
s*5

Out[30]:
'this is a stringthis is a stringthis is a stringthis is a stringthis is a string'
In [31]:
e = {'a':500000000}
h = ['hello',13,e]
k = h + l
print(h)
print(l)
print(k)

['hello', 13, {'a': 500000000}]
[True, False, 50, 'this is a string']
['hello', 13, {'a': 500000000}, True, False, 50, 'this is a string']

In [32]:
print(h*3)

['hello', 13, {'a': 500000000}, 'hello', 13, {'a': 500000000}, 'hello', 13, {'a': 500000000}]

In [33]:
print(t*3)

(True, False, 5, 'this is a string', True, False, 5, 'this is a string', True, False, 5, 'this is a string')

In [34]:
print(t+t)

(True, False, 5, 'this is a string', True, False, 5, 'this is a string')


There are a number of logical operators that are very useful. You can use == to test for equality, or != to test for unequal values.

In [35]:
x = 5
y = 6
x == y # test for equality of x and y, returns a Boolean

Out[35]:
False
In [36]:
x == y-1

Out[36]:
True
In [37]:
x != y # tests for inequality of x and y, returns a Boolean

Out[37]:
True

You can use <, <=, >, and >= to test for particular types of (strict or not) inequality.

In [38]:
x < y

Out[38]:
True
In [39]:
x > y

Out[39]:
False
In [40]:
x >= y-1

Out[40]:
True

The keywords and and or are reserved for indicating intersections and unions, respectively.

In [41]:
print(w)
print(v)

False
True

In [42]:
t = True
u = False
print(t and v)
print(t and u)

True
False

In [43]:
print(w or v)
print(w or u)

True
False


Non-Boolean variables can act as Booleans. Specifically, 0 and '' (i.e., an empty string) both evaluate to False. Any other number and any non-empty string all evaluate to True. Note, too, that Python checks the elements in order from left to right, returning a value as soon as it can determine the value of the whole expression. We can illustrate these facts here, but they won't be particularly important until later, when we talk about control flow.

In [44]:
0 and 5 # 0 is False, so 0 and 5 is False as soon as Python sees 0

Out[44]:
0
In [45]:
5 and 0 # 5 is True, but 0 is False, so Python doesn't know 5 and 0 is False until it gets to 0

Out[45]:
0
In [46]:
6 or 0 # 6 is True, so 6 or 0 is True

Out[46]:
6
In [47]:
0 or 6 # 0 is False, but 0 or 6 is True because 6 is True

Out[47]:
6
In [48]:
5 and 6 # 5 is True, but Python has to check both elements to be sure 5 and 6 is True

Out[48]:
6
In [49]:
5 or 6 # 5 is True, so 5 or 6 is True

Out[49]:
5

### Everything is an object in Python¶

Python is an object-oriented programming language. Part of what this means is that a many variables have methods, which are (pretty much) functions that "belong" to particular classes of variables (e.g., strings, tuples, dictionaries, etc).

You invoke a method by typing a . after a variable name and then typing the name of the method. In a Jupyter notebook, or in IPython, you can see what methods a variable has by typing . and hitting tab.

In [50]:
# list methods illustrated here with l.<tab>


If you want a copy of a list, you can use the copy() method. A copy will not automatically inherit changes to the original list.

In [51]:
l = [10,'hello',e] # make a new list called l
m = l.copy() # copy it, assign to m
n = l # assign l to n
m # look at the copy

Out[51]:
[10, 'hello', {'a': 500000000}]

The append() method appends an input argument to a list:

In [52]:
l[2] = 30 # redefine third element of l
l.append(5**4) # append 5**4 to l
print(l)
print(m) # the copy
print(n) # not a copy

[10, 'hello', 30, 625]
[10, 'hello', {'a': 500000000}]
[10, 'hello', 30, 625]


If you want to count the number of times an element occurs in a list, you can use the count() method:

In [53]:
l.append(False)
l.append(True)
print(l)
l.count(False)

[10, 'hello', 30, 625, False, True]

Out[53]:
1

The index() method returns the index of the input argument:

In [54]:
l.index(30)

Out[54]:
2

Dictionaries also have methods. keys() returns the keys of the dictionary, and values() returns the values:

In [55]:
d.keys()

Out[55]:
dict_keys(['kurt', 'bill', 'fred'])
In [56]:
d.values()

Out[56]:
dict_values([False, [True, False, 50, 'this is a string'], 5])

We will use methods quite a bit as we go on. For now, I just want you to know that they exist.

### A couple useful IPython features¶

In a notebook or IPython terminal, you can type %who to see all of your variables:

In [57]:
%who

d	 e	 h	 k	 l	 m	 n	 r	 s
t	 u	 v	 w	 x	 y


You can add variable types to see only that type of variable:

In [58]:
%who int

x	 y

In [59]:
%who str

r	 s

In [60]:
%who dict

d	 e


You can type %whos to get more information about the variables:

In [61]:
%whos

Variable   Type    Data/Info
----------------------------
d          dict    n=3
e          dict    n=1
h          list    n=3
k          list    n=7
l          list    n=6
m          list    n=3
n          list    n=6
r          str     is a
s          str     this is a string
t          bool    True
u          bool    False
v          bool    True
w          bool    False
x          int     5
y          int     6


You can type %ls to see what's in the directory you're working in on your computer:

In [62]:
%ls

Programming for CSD w01.ipynb


You can type %pwd to see what directory you're working in:

In [63]:
%pwd

Out[63]:
'/Users/noahsilbert/Google Drive/cincinnati/job/courses/2018 1 Spring/programming spring 2018/w01'

Here are all of the IPython "magics", for future reference.

## Introduction to functions¶

We've already seen a few functions, e.g., print(), some list and dictionary methods. A function is essentially just a block of reusable code that performs an action. Functions may or may not take inputs, and they may or may not return outputs.

It's easy to define your own functions. Here's a very simple function that takes two numbers, adds them together, and returns the sum:

In [64]:
def add_two_nums(n,m):
'''Returns the sum of inputs n and m'''
print(n)
print(m)
o = n + m
return o


The keyword def tells Python that what follows is a function definition. The next bit is the name of the function (i.e., the code that you will use to call that function). Any input arguments appear in the parentheses immediately after the function name, separated by commas if there are more than one. The def line ends with a colon, and the code inside the function must be indented - indentation is how Python knows what is inside the function and what is not. The string immediately below the name is the docstring, or the description of what the function does. Finally, the return statement tells Python what to give back as output.

IPython allows you to get docstrings very easily:

In [65]:
?add_two_nums

In [66]:
z = add_two_nums(x,y)
print(z)

5
6
11


As defined above, our function add_two_nums() requires input values. We can give a function default values if we want. If a function has any arguments with default values, they have to come after any that do not. Here's an example of a function with one required input and two inputs with default values.

In [67]:
def add_three_nums(a,b=5,c=7):
'''Returns the sum of inputs a, b, and c'''
print(a)
print(b)
print(c)
return a + b + c

In [68]:
add_three_nums(3)

3
5
7

Out[68]:
15
In [69]:
add_three_nums(3,4,2)

3
4
2

Out[69]:
9

Inputs without default values are specified by position (i.e., first, second, third, ... input argument). We can also specify all of the inputs by name, which can be helpful when reading code. In addition, we can use the reserved term None if we want a placeholder rather than a default value. Let's define a new function to illustrate:

In [70]:
def fancy_math(a=None,b=None,c=None):
'''returns (a+b)*c'''
return (a+b)*c


Using None like this, we have to give the function inputs if we want to avoid an error:

In [71]:
fancy_math()

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-71-f399896cf738> in <module>()
----> 1 fancy_math()

<ipython-input-70-2e3c7a7bb3bf> in fancy_math(a, b, c)
1 def fancy_math(a=None,b=None,c=None):
2     '''returns (a+b)*c'''
----> 3     return (a+b)*c

TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

We can still give inputs by position if we want:

In [72]:
fancy_math(2,3,4)

Out[72]:
20

Or we can give inputs by name in any order:

In [73]:
fancy_math(c=10,a=2,b=50)

Out[73]:
520