As some of us will have experienced, quite a few budding data analysts in training will come to Python via numpy and pandas, heading towards seaborn and dash, perhaps from past experience in R or Matlab, perhaps not.
They've never yet had a core Python class, but are likely to have one in the near future, if they decide to stay within the Python ecosystem and/or stick with data science.
I've got such a cohort coming my way and thought this time I would develop some more explicit background content on Python, the core language, which I'm calling "warm-up notebooks" and so far doing in Jupyter.
Here are some pedagogical features I incorporate that might be of interest to others in a similar position of needing to develop curriculum:
1. mention numpy and pandas early
There's nothing conceptually that hard about a rectangle of numbers, addressable by row and column. The mechanisms of installing 3rd party packages may be addressed.
Introduce range, then why not arange and linspace for compare and contrast purposes.
I go straight to numpy and pandas in notebook 2. To see the sequence, see here (home base for the class):
I'm still adding notebooks.
2. functions are just another type of object
In exploring the various built-in types, with Python allowing us to make new types, it becomes useful to see individual functions as instances of the function type, where list, tuple, int, str.... function are all built-in types.
We don't use keyword class to define a function, but keyword def (or maybe lambda).
What makes function instances stand out is they're callable. But so are instances of classes implementing __call__.
[1] def f(x):
return x * x # return x times itself
[2] type(f)
function
[3] issubclass(type(f), object)
True
[4] isinstance(f, type(f))
True
3. Use "composition of functions" to motivate drive a decorator syntax intro
I've been doing this for a while now. Most students will remember composition of functions from high school, and if not, it's an easy concept.
def f(x):
return 2 * x
def g(x):
return x**2
print("f after g:", f(g(arg)))
print("g after f:", g(f(arg)))
Output:
f after g: 200
g after f: 400
What's not so easy, an may require active tutoring, is passing a function into Composer type like this:
# where decorator syntax will come in handy
f = Composer(f)
g = Composer(g)
print("f after g:", (f * g)(arg))
print("g after f:", (g * f)(arg))
Output (same answers):
f after g: 200
g after f: 400
In other words, we repurpose the __mul__ operator (*) to become the compose operator, which in LaTeX is usually \circ.
Kirby