Lecture notes. Written from the material of Berkeley Stat 243, licensed CC BY 4.0. These are notes, not a transcript: the material has been reorganised and rewritten. This adaptation carries the same licence, and the original is linked above.
27. Python Fundamentals for R Users¶
What this covers¶
This chapter is a working introduction to Python, written for students who already think in R. It walks through Python's core object types (lists, tuples, dictionaries), control flow, functions, the NumPy/SciPy stack for numerical work, pandas data frames, and enough of Python's class syntax to read a small object-oriented example. Nothing here assumes prior Python; what it does assume is R, since almost every idea below is introduced by contrast with how R does the same thing. It comes from a lab section built as a companion notebook to a longer written introduction, and is meant to be run interactively, one cell at a time.
Objects, mutability, and dynamic typing¶
A Python list is written with square brackets and can mix types freely:
Indexing starts at 0, unlike R's 1-based indexing:
Lists are mutable — you can reassign an element in place:
A tuple looks like a list but with parentheses, and it is immutable: the same kind of assignment that just worked on the list raises an error on a tuple, and the tuple is left unchanged.
Python variables are dynamically typed: a name is just a label that can be rebound to a value of any type, and the type travels with the value, not the name.
a = 'foobar'
print(a * 4) # string repetition — no direct R equivalent
print(len(a)) # length of the string
a = 3
print(a * 4) # now ordinary multiplication
print(len(a)) # error: an int has no length
The a * 4 line means something different depending on what a currently is — string repetition
versus numeric multiplication — because Python resolves * by asking the object what it means,
not by looking at a declared type.
Modules, packages, and imports¶
del(a) removes a name from the current namespace. Importing a module makes its contents available
only through the module's name — a custom script mytest.py, once imported with import mytest,
exposes mytest.hello() and mytest.a, but calling hello() or referring to a directly still
fails, because those names were never added to the current namespace. from mytest import *
fixes that by importing everything into the current namespace directly, so hello() and a work
unqualified afterward.
The same distinction applies to standard packages, and it is the point where Python most departs from R's habit of loading an entire library at once:
from math import cos
print(cos(0)) # works
print(sin(0)) # fails — sin was never imported
import math
print(math.cos(0), math.sin(0)) # both available, qualified by the module name
Importing under an alias is common and changes what name you must use to reach the package:
Appending ? after an object in Jupyter (e.g. np.ndim?) pulls up its documentation, the rough
analogue of ? or help() in R. Errors surface as Python tracebacks — importing a second script,
days.py, and calling one of its functions is used in the lab purely to show what that traceback
looks like and how to read it.
Numbers, tuples, and lists in more detail¶
Numeric literals carry their own type: type(1), type(1.1), and type(1 + 2j) give int,
float, and complex respectively, and functions from math (already imported above) work on any
of them, e.g. math.cos(0), math.cos(math.pi). Typing a partial name followed by tab in Jupyter
(e.g. x. or math.) lists the methods and functions available — a way of discovering the API
interactively rather than from a manual.
A tuple can also be written without parentheses, since it is the comma that creates the tuple:
Unpacking assigns the elements of a tuple to several names in one line:
Lists support the same slicing syntax as tuples and strings, start:stop:step, and support it more
flexibly since they are mutable:
dice = [1, 2, 3, 4, 5, 6]
dice.extend([7, 8]) # append several elements
dice.insert(3, 100) # insert one element at a position
dice = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
dice[0::3] # every third element starting from index 0
dice[1:4:2] # index 1 up to (not including) 4, step 2
Indexing past the end of a list (dice[6] on a 6-element list) raises an error rather than
returning a missing value — there is no implicit padding. A slice can also appear on the left of an
assignment, replacing a whole run of elements at once:
Dictionaries¶
A dictionary maps keys to values and is written with braces:
students = {"Jarrod Millman": ['A', 'B+', 'A-'],
"Thomas Kluyver": ['A-', 'A-'],
"Stefan van der Wait": 'and now for something completely different.'}
students.keys()
students.values()
students["Jarrod Millman"]
students["Jarrod Millman"][1] # value can itself be indexed further
As with other objects, typing students. followed by tab in Jupyter shows the methods a dictionary
supports.
Control flow¶
Python has no braces; a block is whatever is indented one level further than the line that opens
it (if, else, for, def, ...). That makes indentation semantically meaningful, and it is easy
to attach an else to the wrong if by getting the indentation wrong:
Here the else lines up with the outer if, so it only ever fires when x < 4; the inner if
has no else at all. Indent the else one level further and it belongs to the inner if instead:
Same text, different meaning — the lesson is to read indentation as structure, not as decoration.
for loops iterate directly over a sequence, with no explicit index variable needed:
for x in [1, 2, 3, 4]:
print(x)
for x in [1, 2, 3, 4]:
y = x * 2
print(y, end=" ") # end=" " suppresses the newline pandas/print adds by default
A subtlety worth noting: the loop variable is not scoped to the loop. After
y (and x) still exist afterward, holding the value from the final iteration.
List comprehensions¶
A list comprehension builds a list from a loop in one expression:
and can filter as it goes:
Functions¶
Functions are defined with def, and parameters can have default values:
Arguments can be supplied positionally or by keyword, and keyword arguments can be given out of order:
add(3) # y and absol take their defaults
add(3, 5) # positional
add(3, absol=True, y=5) # mixed
add(y=-5, x=3) # both by keyword, any order
But once you use a keyword argument, every argument after it must also be given by keyword —
add(y=-5, 3) is a syntax error, because Python cannot tell whether the trailing 3 should fill
x or some later parameter.
NumPy and SciPy¶
A plain Python list is not a numerical array — numpy.array converts one into something that
supports elementwise arithmetic and carries a fixed dtype:
A two-dimensional array is built from nested lists, with the element type fixed explicitly if needed:
x = np.array([[1, 2], [3, 4]], dtype=np.float64)
x * x # elementwise multiplication
x.dot(x) # matrix multiplication — a different operation from *
x.T # transpose
Linear algebra lives in np.linalg:
np.linalg.svd(x) # singular value decomposition
e = np.linalg.eig(x) # eigen decomposition
e[0] # eigenvalues
e[1][:, 0] # first eigenvector (a column of the returned matrix)
np.linspace(0, 1, 5) builds an evenly spaced sequence. Random sampling uses np.random, and
setting a seed makes it reproducible:
Boolean arrays are used to filter and to assign selectively:
pos = x > 0 # a boolean array, same shape as x
y = x[pos] # only the positive entries
x[pos] = 0 # zero out just those entries, in place
x[[1, 3, 4]] # "fancy" indexing — select by a list of positions
Elementwise functions such as np.cos(x) apply to a whole array at once. scipy.stats supplies
distributions, and a distribution's parameters can be passed either at each call or once, by
constructing a frozen distribution object:
import scipy.stats as st
st.norm.cdf(1.96, 0, 1)
st.norm.cdf(1.96, 0.5, 2)
st.norm(0.5, 2).cdf(1.96) # equivalent to the line above
Pandas¶
pandas provides a data-frame type built on top of NumPy. Reading a CSV and inspecting it:
A column can be pulled out by bracket or, if its name is a valid identifier, by attribute:
Row slicing and sorting work as expected:
.loc indexes by label, and can select rows and columns together:
Boolean filtering follows the same pattern as NumPy:
.apply runs a function over each column (or row) of a data frame:
Filtering rows and then modifying the result should go through .copy() — otherwise the filtered
object is only a view onto the original, and assigning a new column to it can behave unexpectedly:
groupby(...).transform(...) applies a function within each group and returns a result aligned
back to the original row order — useful for adding a per-group standardized column:
def stdize(vals):
return (vals - vals.mean()) / vals.std()
dat2007['lifeExpZ'] = dat2007.groupby('continent')['lifeExp'].transform(stdize)
Classes¶
A class bundles data and behaviour together. __init__ runs when an instance is created,
__repr__ controls how an instance prints, and ordinary methods take self as their first
parameter:
class Rectangle(object):
dim = 2 # class variable — shared by every instance, unless overridden
counter = 0
def __init__(self, height, width):
self.height = height # instance variable
self.width = width
self.set_diagonal()
Rectangle.counter += 1
def __repr__(self):
return "{0} by {1} rectangle".format(self.height, self.width)
def area(self, verbose=False):
if verbose:
print('Computing the area... ')
return self.height * self.width
def set_diagonal(self):
self.diagonal = pow(self.height**2 + self.width**2, 0.5)
x = Rectangle(10, 5)
dim is a class variable: every Rectangle shares it until an instance is given its own. Assigning
x.dim = 'foo' does not change Rectangle.dim for other instances — it creates an instance
attribute on x that shadows the class attribute for x alone. counter, by contrast, is
incremented through Rectangle.counter inside __init__, so every instance created shares and
updates the same class-level count: after creating two rectangles, both x.counter and y.counter
report the same total. Calling x.area() and Rectangle.area(x) do the same thing — the first is
shorthand for the second, with x supplied automatically as self.
Strings¶
The string module holds some useful constants, such as string.digits, and strings support the
same indexing and slicing as lists and tuples, including negative indices counted from the end and
a negative step for reversal:
import string
string.digits[1] # single character
string.digits[-1] # last character
string.digits[1:5] # slice
string.digits[1:5:2] # slice with step
string.digits[:5:-1] # from the end, stepping backward
Strings have their own methods (.upper(), and many more discoverable by tab-completion),
support + for concatenation and * for repetition, and are compared lexicographically with the
ordinary comparison operators:
string1 = "my string"
string1.upper()
string1 + "is your string"
"*" * 10
print(string1 > "ab")
print(string1 > "zz")
Like tuples, strings are immutable: you cannot assign into a slice of a string
(string1[3:5] = 'ts' fails). To change part of a string you build a new one by slicing and
concatenating around the part you want to replace:
Exercises¶
Numbers
- Compute \(\left(\left\lceil \frac{3}{4} \times 4 \right\rceil\right)^3\).
- Compute \(\sqrt{-1}\).
Tuples
- Store
x = 5andy = 6. Swap their values in a single line of code. How would you do this in R? - What happens when you multiply a tuple by a number? How is this different from the similar syntax in R?
- What's nice about using immutable objects?
Lists
- What do you get if you multiply a list of numbers by a number?
- What does the following tell you about copying and memory use in Python?
Control flow
- See what
[1, 2, 3] + 3returns. Explain what happened and why. - Use a list comprehension to add a scalar to every element of a list of scalars.
Functions
- Define a function that takes the square root of a number and, if requested by the user, sets the square root of a negative number to \(0\) instead of raising an error.
NumPy
- See what happens if you try to create a NumPy array from a mix of numbers and character strings.
- Try to add a vector to a matrix. How does the result compare to the same operation in R?
Pandas
- Use
pd.merge()to merge the continent-level mean life expectancy for 2007 back into the originaldat2007data frame.
Strings
Using x = 'The ant wants what all ants want.', and without changing x itself:
- Convert the string to all lower-case letters.
- Count the number of occurrences of the substring
ant. - Build a list of the words occurring in
x, with punctuation removed and every word lower-cased. - Using only string methods on
x, produceThe chicken wants what all chickens want. - Using indexing and the
+operator, produceThe tna wants what all ants want. - Produce the same string again, this time using a string method instead of indexing and
+. - What can you do with the
inandnot inoperators? What R operator is this most like, and how does it differ? - Work out how to check whether Python is explicitly counting characters when it evaluates
len(x). - Compare the time it takes to compute the length of a long string in Python versus in R. What does that suggest about what each language is doing behind the scenes?
Sources¶
Both notes files come from the same source notebook, sections/06/python_intro.ipynb, in the
berkeley-stat243/stat243-fall-2021 repository (licensed CC0-1.0), split at the point where the
notebook moves from general objects/data structures to strings specifically:
- Objects, variables, imports, numbers, tuples, lists, dictionaries, control flow, functions,
NumPy/SciPy, pandas, and classes:
sections/06/python_intro/01-introduction.md. - String indexing, slicing, and the string exercises:
sections/06/python_intro/02-strings.md.
No slide deck or lecture transcript was supplied for this chapter; it is built entirely from the lab notebook's code cells and inline commentary.
The notebook itself refers to material it does not contain: an accompanying HTML file with "more
details" that the notebook was written to illustrate and practice, and three data/script files used
by its exercises — mytest.py and days.py (custom scripts used in the imports section) and
gapminder.csv (the dataset used throughout the pandas section) — none of which were supplied
alongside the notebook.
← 26. Python for R Users · Contents · 28. R Practice: Structures, Functions, I/O →