Coding in the Classroom

Jennifer Klay jklay@calpoly.edu

California Polytechnic State University, San Luis Obispo

Description

Coding and computer programming are essential skills for 21st century learners but how can we provide opportunities to build such skills in the classroom? Open source tools and programming languages with simple and natural syntax provide one avenue. In this workshop I will introduce participants to the Python programming language using the IPython/Jupyter notebook. I will present ways to help students learn to practice algorithmic thinking, to work together to problem solve, and to apply the computer to solve problems that interest them. In addition, a variety of useful resources for developing lessons and integrating coding into existing curricula will be discussed.

Audience: Aspiring or early-career K-12 STEM teachers hoping to include research/computing in the classroom.

Assumed programming experience: None

The complete set of materials for this workshop are available online at Github.


1. Introduction

This notebook provides a roadmap to help teachers bring coding and programming concepts to the classroom through project-based learning. With some basic programming skills you can tackle a wide array of interesting and instructive problems.

What are some skills that we wish students to gain?

  • Practice algorithmic thinking
  • Break complex problems into smaller, more manageable parts
  • Work together to problem solve
  • Figure out how to get unstuck/find help when things don't work as expected
  • Apply the computer to problems they want to solve
  • Find/identify interesting problems the computer can help them solve

There are several methods for helping students build their programming skills. In particular, encouraging them to program in pairs or groups will help them brainstorm solutions while decomposing a problem into the sequence of steps taken to solve a problem - an algorithm. Once they have the steps of a solution outlined, they can attempt to put their process into the syntax of a computing language.

There are some best practices for writing effective programs that even seasoned coders don't always follow. Nevertheless most programmers would agree that the following set of practices are helpful and effective. One thing you can do to encourage students is to ask them to periodically evaluate their own work throughout the development process and demonstrate how they are applying these practices. Eventually they won't need to be prompted, as the practices will become second-nature.

Some best practices for writing effective programs

  • Program together (in pairs or groups)
  • Deconstruct a problem into simple steps and create a scaffold of the full program from the start - some parts will be empty or placeholders until their logic can be filled in.
  • Develop "pseudo-code" (an informal description of the algorithm that uses the structural conventions of a programming language, but is intended for human reading rather than machine reading) for more complex components to work out the logic
  • Fill in the "guts" of the program components
  • Test each component individually to verify it gives expected results with a known input and output
  • Document the code as it is developed, describing the purpose of all functions, their inputs and outputs, and the purpose of the full program and how it all fits together
  • Demonstrate the program works and (to the best of your ability to judge) gives meaningful results

These basic goals and guidelines are applicable to any programming language or platform. For beginning coders, it is critical to make the entry point as easily accessible as possible within a framework that also provides a comprehensive set of tools that will enable them to expand beyond the simplest concepts. The tools suggested here provide such a framework.


2. Tools

The IPython Notebook (the "I" in IPython stands for interactive) is an interactive computational environment, in which you can combine code execution, rich text, mathematics, plots and rich media. It is a great platform for learning to program and for solving and presenting complex problems.

The notebook runs inside a web browser and can be easily installed with the Python programming language and a comprehensive library of scientific computing tools using the "Anaconda" package provided FREE by Continuum Analytics.

In 2015, the IPython Notebook evolved into Project Jupyter, which now manages the language-agnostic parts of the notebook and provides one uniform platform for working in several different computing languages, including Python.

If you are viewing this notebook via a weblink, you won't be able to interact with it, but if you install Anaconda, you can code along with us by opening the notebook from the IPython Notebook server.

The Python programming language is an excellent tool for general-purpose programming, with a highly readable syntax, rich and powerful data types (strings, lists, sets, dictionaries, arbitrary length integers, etc) and a very comprehensive standard library. The language is easy to learn and because it is an interpreted language, commands are executed in real time without the need to compile a full program.

There is a whole ecosystem of additional libraries provided with Anaconda that can be used for mathematical and scientific computing, which will help you efficiently represent multidimensional datasets, solve linear algebra systems, perform general matrix manipulations (an essential building block of virtually all technical computing), and visually represent and interact with data of many different types.

SciPy (pronounced "Sigh-Pie") is the premiere package for scientific computing in Python. It has many, many useful tools, which are central to scientific computing tasks you'll come across.

The NumPy library (pronounced "Numb-Pie") forms the base layer for the entire SciPy ecosystem.

Matplotlib is a Python 2D plotting library which produces publication quality figures in a variety of hardcopy formats and interactive environments across platforms. When you need to view or present data, it is invaluable.

These libraries, or their individual components, can be imported and used in the IPython notebook.

To write and execute code within the notebook, we use a code cell (the default). We will look at the details of the syntax later. For now, here is our first code cell, in which we import some of these libraries for later use:


In [ ]:
#Comments begin with #

#Allow graphics to render inside the notebook
%pylab inline 

#import packages we might want to use
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp

To execute a cell:

  • Shift-Enter: run cell and move to new cell below.
  • Control-Enter: run cell and stay in the cell.

3. Notebook basics

There are two primary different kinds of cell types - "code" and "markdown". The cell in which this text was typed is a markdown cell. A code cell is one in which computer instructions or code can be typed. To execute a cell, hit Shift-Enter.

Markdown cells use the Markdown formatting system to allow you to include formatted text, such as italic and bold, or to create bulleted or numbered lists:

  • a list element
  • another list element
  1. a numbered list element
  2. another numbered list element

The view of these cells is determined by whether they have been executed or not. Double-clicking any executed markdown cell will bring it into "edit" mode. Try it with one of these cells.

Mathematical expressions can be rendered using LaTeX formatting (pronounced "Lah-Tech") to give full-featured symbolic representation:

$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

The LaTeX "code" to create that equation is

$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

LaTeX equation typesetting within the IPython Notebook is very helpful for displaying equations and directly connecting them with the computer code that implements them numerically.

Images can be included in markdown cells using HTML tags like

<img src="img/Cats.jpg" width=200>

or they can be included in code cells using the display features of IPython.


In [ ]:
from IPython.display import Image
Image(filename='img/Cats.jpg')

It is also possible to display images linked from sites around the web:


In [ ]:
Image(url='http://python.org/images/python-logo.gif')

How about embedding Youtube videos in the notebook using the video tag?


In [ ]:
from IPython.display import YouTubeVideo
YouTubeVideo('4disyKG7XtU')

These features enable the notebook to provide a rich development environment for projects that incorporate computer code but are not (necessarily) all about the code. In fact, the notebook lets you expand the notion of a computer "program" to include context and commentary side-by-side with the code to help readers/users better understand the purpose and results of the computer code.

Now that we have seen some of the notebook's capability in action, let's write some code.


4. Writing Code

A short workshop is not enough time to fully introduce all of programming to beginners, but we can learn some of the basics by trying them out. Here is a brief tour of simple code concepts implemented as code statements in Python.

4.1 Basic coding syntax and logic

Print a message:


In [ ]:
print "Hello World"

Create a variable and assign it a value:


In [ ]:
z = 5

Print the value stored in the variable:


In [ ]:
print z

Change the value of the variable with an arithmetic expression:


In [ ]:
z = z + 27
print z

You can request input from a user that can be stored in memory and used for other purposes:


In [ ]:
name = raw_input("Hi, what's your name? ");

In [ ]:
print "Hi, my name is",name

In Python, you can define a function and pass it arguments, then execute the function:


In [ ]:
def intro(name):
    print "Hi, my name is",name

To call the function, you type the name, with the argument(s) in parentheses as a code statement:


In [ ]:
intro("Jennifer")

here's a function that takes no arguments:


In [ ]:
def intro():
    name = raw_input("Hi, my name is ");

In [ ]:
intro()

You can control the execution of code statements with if ... else:


In [ ]:
if z > 10:
    print "Yay!"
else:
    print "Boo. :-("

In [ ]:
print z

For repetitive tasks, you can use a for loop:


In [ ]:
print "I can count to 10!"
for i in range(10):
    print i

Oops. Not quite what you were expecting?

Notice that in Python, indices start from 0, not 1, and count up to n-1.

I can create a list of items and iterate over them with a for loop:


In [ ]:
shopping_list = ["eggs","milk","bacon","bread","strawberries","yogurt","jam"]
for item in shopping_list:
    print item

I can access individual items from their location in the list using an index to that location:


In [ ]:
print shopping_list[2]

In [ ]:
print "What's for breakfast?"
print shopping_list[2] + shopping_list[0]

Let's try that again:


In [ ]:
print "What's for breakfast?"
print shopping_list[0] + " and " + shopping_list[2]

I can also slice through the list


In [ ]:
print shopping_list[1:4]

This shows the "1th", "2th", and "3th" elements, or start to end-1. Note the difference between this and traditional ordinal numbering:

  • "0th" (Zero-th) = "First"
  • "1th" (One-th) = "Second"
  • "2th" (Two-th) = "Third"
  • "3th" (Three-th) = "Fourth"
  • and so on...

When slicing a list (or array), you can leave one or the other index blank to tell it to start at the beginning or go to the end. The result will be similar to before:


In [ ]:
#Slice from 0th to the 1th element:
print shopping_list[:2]

In [ ]:
#Slice from 0th (first) to the n-1th (last) element:
print shopping_list[:]

In [ ]:
#Slice from 2th (third) to n-1th (last) element:
print shopping_list[2:]

You can also access elements backward from the end using negative numbers:


In [ ]:
#Print the last element:
print shopping_list[-1]

In [ ]:
#Print the second-to-last element:
print shopping_list[-2]

or designate a step size to jump over certain elements:


In [ ]:
#Print every other element:
print shopping_list[::2]

In [ ]:
#Print every third element (skip 2):
print shopping_list[::3]

How about printing them backward?


In [ ]:
#Iterate from the last to the first element in steps of 1:
print shopping_list[-1::-1]

Python lists can contain elements of any data type - strings of characters (e.g. words, sentences, etc.), whole numbers (called "integers" or ints), decimal numbers (called "floating point numbers" or floats), other lists, etc.

There are other kinds of containers in Python - dictionaries, deques, queues - each with different features and uses. We won't look into them further here, but if you are interested, consult the documentation or an introductory text such as ThinkPython by Allen Downey.

4.2 NumPy Arrays

NumPy arrays are better containers for purely numerical data.


In [ ]:
#Create an array of 10 values between 0 and 10-1
arr = np.arange(10)

In [ ]:
print arr

In [ ]:
#Create an array of 10 evenly-spaced values between 0 and 10
arr2 = np.linspace(0,10,10)

In [ ]:
print arr2

The elements of the arrays can be accessed with the same indexing and slicing as we used before:


In [ ]:
#How many elements are in the array?
print len(arr)

In [ ]:
#Print the 5-th element (sixth in the list):
print arr[5]

NumPy arrays can be used for simple or complex mathematical calculations.


In [ ]:
x = np.linspace(0, 2*np.pi, 300)
y = np.sin(x**2)

Here, x and y are just arrays of numbers that represent the value of the function $y(x)=\sin(x^2)$ at each of the 300 values of $x$ between 0 and 2$\pi$.

4.3 Matplotlib

You can use matplotlib to create visual representations of such data:


In [ ]:
plt.plot(x, y)
plt.title("A little chirp");

Graphs in matplotlib have many attributes that you can customize (see the documentation). The developers also provide a gallery of plots to showcase the wide variety of visual representations that are available.

No one can keep all of the functions and fine layout control commands in their brain. Often when I need to make a plot, I go to the gallery page and browse the images until I find one that is similar to what I want to create and then I copy the code and modify it to suit my needs.

When you use the Matplotlib gallery to develop (or "template") a figure, you can very easily load the source code into your notebook and then modify it as needed to fit your specific needs using the Python "magic" command, %load.

Try it now. After the code is loaded, just execute the cell to see the output.


In [ ]:
#Execute this cell to see the histogram example plot code, then execute again to make the plot.
%load http://matplotlib.org/mpl_examples/statistics/histogram_demo_multihist.py

4.4 IPython Widgets

Exploring data is much more fun when you can directly interact with it. IPython widgets provide a great way to do just that. Here is an example with two sine curves - one is a pure sine wave, the other is the superposition of two waves with different frequency but the same amplitude. You can interactively explore how the functions change when the parameters are changed.


In [ ]:
from IPython.html.widgets import interact

In [ ]:
def sin_plot(A=5.0,f1=5.0,f2=10.):
    x = np.linspace(0,2*np.pi,1000)
    #pure sine curve
    y = A*np.sin(f1*x)
    #superposition of sine curves with different frequency
    #but same amplitude
    y2 = A*(np.sin(f1*x)+np.sin(f2*x))
    plt.plot(x,y,x,y2)
    plt.xlim(0.,2.*np.pi)
    plt.ylim(-10.,10.)
    plt.grid()
    plt.show()
    
v3 = interact(sin_plot,A=(0.,10.), f1=(1.0,10.0), f2=(1.0,10.0))

Here's another interactive plot that allows you to randomly sample (x,y) pairs within a circle of radius $r$. The interact object lets you increase or decrease the number of samples in the circle.


In [ ]:
def scatter_plot(r=0.5, n=27):
    t = np.random.uniform(0.0,2.0*np.pi,n)
    rad = r*np.sqrt(np.random.uniform(0.0,1.0,n))
    x = np.empty(n)
    y = np.empty(n)
    x = rad*np.cos(t)
    y = rad*np.sin(t)
    fig = plt.figure(figsize=(4,4),dpi=80)
    plt.scatter(x,y)
    plt.xlim(-1.,1.)
    plt.ylim(-1.,1.)
    plt.show()
    
v2 = interact(scatter_plot,r=(0.0,1.0), n=(1,1000))

A last fun example of using widgets can be found in the accompanying notebook on the Quantum double-slit experiment.

4.5 When things go awry

Making mistakes when programming is an unavoidable part of the experience. It can be very frustrating. You might spend an hour trying to track down the source of a "bug" in a program, convinced that the computer is at fault. I know I have done that countless times. But computers are only as "smart" as their programmers. Like lemmings, they will blindly follow our instructions. It is pretty much always our fault. Get used to that.

Occasionally, the computer will spit out an error message or give back a gibberish answer, alerting us to our mistake. Sometimes we're not so lucky.

Bug-hunting (or "debugging") is part of the job. And part of debugging is being able to understand error messages. Let's generate some errors to get a feel for dealing with them.


In [ ]:
pirnt "Hello
print "World " + z-5

This is an example of a syntax error. We forgot to close the quotes around our message. Python error messages are (usually) informative and tell you what the problem is and where it occurred. We can fix this problem by closing our quotes.


In [ ]:
pirnt "Hello"
print "World " + z-5

Oops. We misspelled print, giving us another syntax error. In this case the message is not quite as clear about what the problem is, but it isn't too hard to spot the mistake.

Notice that Python doesn't execute the second statement in the cell when it encounters an error in the first line. Execution stops at the first sign of an error. Keeping the number of lines of code in cells or functions short makes tracking errors much easier.

Notice also that the Notebook provides syntax highlighting - Python keywords, like print, and numbers appear in green, quoted strings appear in red, operators like + and - appear in purple and variables are black. This can be helpful for spotting errors quickly.

Let's correct our spelling.


In [ ]:
print "Hello"
print "World " + z-5

Now what? Here is a type error. We tried to perform the "+" operation on a string and an integer, which is not allowed. To correct this we could force the integer object to be treated as a string by casting it as one:


In [ ]:
print "Hello"
print "World " + str(z-5)

Let's move on to another error.


In [ ]:
ff = m*z + b

This is an example of a runtime error. The syntax is correct, but in this case some of the variables have not yet been defined. Let's try that again.


In [ ]:
m = 0.5
b = -3.

In [ ]:
print ff

Because of the previous error, ff was never defined. We have to either re-execute the cell above or re-define ff now that the other variables have been defined.


In [ ]:
ff = m*z + b

In [ ]:
print z

Runtime errors are annoying but at least they announce themselves. What happens if we make a logical error?


In [ ]:
def cube_root(x):
    '''This function takes a number, x, 
       computes the cube root, 
       and returns the result
    '''
    return np.power(x,1/3)

In [ ]:
print cube_root(8)

Hmmm... What went wrong here? Everything looks okay. This is an example of a logical error. The syntax is correct, the code executed, but the output is not what we expect it to be. (The cube-root of 8 is 2: 2*2*2=8.)

How come this function doesn't work? Python is a dynamically-typed language, which means it tries to guess what the data type of the object you are defining is and then applies the rules of those objects for subsequent calculations. Integers are whole numbers of type int. Decimal numbers are represented in the computer as floating point numbers or floats. When Python computes the fraction with the "/" operator on integers, it assumes you want an integer result. The "/" operator on integers rounds down to the nearest whole integer:


In [ ]:
5/4

How do we avoid logical errors like this one? For mathematical calculations, you can explicitly define the data type of the numbers you use. If you want a floating-point result, use floating-point numbers by adding a decimal point to the end of the number:


In [ ]:
a = 3
b = 4
print a/b

In [ ]:
c = 3.
d = 4.
print c/d

How do we avoid logical errors in general?

Never make a logical error.

Just kidding. The only way to minimize logical errors is to always test your code with a known result whenever possible. In the case of the cube_root function, we knew something was wrong with the logic because the cube-root of 8 should be 2. When we got a different value than expected, we knew we had a problem to track down and correct. The purpose of testing as a programming "best practice" is to minimize logical errors like this one.

Fix the cube_root function and test that it gives sensible results.

4.6 Getting help

When IPython needs to display additional information it will automatically invoke a pager at the bottom of the screen. You can get help with IPython in general by typing a question mark in a code cell and executing it:


In [ ]:
?

Or get help on a particular function with, e.g.


In [ ]:
np.arange?

There is extensive documentation for Python, IPython, NumPy, SciPy, Matplotlib, etc. on the web. Chances are, your question has been asked and answered somewhere already. Google is your friend. A few places to look when you get stuck:


5. Applications

Now that we we have seen some of the basics, let's try using what we learned to solve a simple problem.

5.1 Project Euler

Project Euler is an online repository of simple and hard math puzzles that can be solved with the computer. These are great for practicing problem-solving, algorithmic thinking, and coding. Let's try a simple one together.

Multiples of 3 and 5

Problem 1

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

https://projecteuler.net/problem=1

Work with a partner to brainstorm a solution to this problem by breaking it into logical steps. Type your set of steps into a markdown cell as a bulleted list. Then try to implement the code for these steps in a code cell. Verify your solution works for the case where N = 10 before trying N = 1000.

Steps:


In [ ]:
#Your code here

5.2 Project Ideas

Much of scientific computing involves reading and understanding existing programs written by others so that they can be adapted to solve different problems. Learning to recognize the underlying structure and logic of a program regardless of the language that the program is written in is an essential skill. Once you have the ability to deconstruct and understand programs you can adapt them to your needs.

I have included a few project examples here that don't require complex domain-specific knowledge (e.g. biology, chemistry, physics) to grasp and understand. Yet these projects can be adapted or applied to interesting problems in various domains.

Some of the project notebooks include code while others are outlines of projects with varying degrees of detailed instructions. You (and your students) probably won't be ready to really try the project notebooks until you have a good basic set of Python coding skills. You can obtain these by a variety of means, but I can suggest three different resources that I have either personally used or had students successfully use to build code skills. These are:

For notebooks with example/tutorial code, I recommend creating a separate notebook and typing the commands by hand. This forces you to think about each command as you go and will also force you to deal with syntax errrors and mistakes, which is a good thing. Do NOT copy/paste. If you do copy/paste, you might as well not even bother, because you won't be learning how the program works. This style of learning is the method employed by Learn Python The Hard Way. Don't be fooled by the name. It is actually a really good book that is very easy to follow. It is only considered "hard" because you do a lot of practice work - like scales on the piano or free-throws in basketball. The book is also pretty entertaining.

On to the projects...

5.2.1 Battleship

Battleship is a simple game of hunt and destroy that you may have already played at some point. It can be easily adapted to the computer. The Codecademy course on Python walks you step-by-step through the development of such a game with text-based graphics. This version builds on that using a module called ipythonblocks to represent the game visually.

Battleship Project Notebook

5.2.2 Schelling Model

The Schelling Model is an easy-to-understand agent-based model of racial segregation that also has analogues in biology, chemistry, and physics. It can be implemented with a simple set of logical rules applied to a collection of "agents" in a series of rounds. The model lends itself well to visual representation with ipythonblocks and interactive widgets.

Schelling Model Project Notebook

5.2.3 Counting Stars

Image processing, such as feature identification and extraction, is a cross-disciplinary research method. How does it work? This project presents a way to learn a simple underlying algorithm for counting features (stars in this case) in an image using basic Python. A second implementation that relies on the NumPy library is also provided.

5.2.4 Brownian Motion and Avogadro's Number

Brownian motion is the random erratic motion of small particles immersed in a medium (like water) that is caused by the millions of tiny water molecules colliding with the larger particles. Albert Einstein formulated a quantitative theory of Brownian motion that was confirmed experimentally by Jean Baptiste Perrin, providing the first direct evidence supporting the atomic nature of matter. An estimate of Avogadro's number can be obtained from Einstein's equations applied to Perrin's experiment.

This project involves using image processing (like that from the Counting Stars project) to observe and quantify these tiny motions from a sequence of movie frames of a system undergoing Brownian motion. A USB microscope can be used to capture video of milkfat globules suspended in water, providing both a programming project and an experimental data collection project.

Avogadro's Number Project Notebook

5.2.5 Data mining with Quandl

Data science is an emerging discipline that has taken off in the last few years. We have mountains of information waiting to be queried, sorted, understood. Marketing data, scientific data, sociological, political, demographic data. What can we learn by mining this data for patterns?

Quandl is an online data platform hosting data from hundreds of publishers on a single easy-to-use website. They make all the numerical data in the world available on their website in the exact format users want. They support open data, meaning that public data must be free, open, and accessible to all. They are an open platform - anybody can buy, sell, store or share data on Quandl.

There are many different kinds of projects that students could develop by imagining questions that could be answered with data. For example:

  • How do home prices in my zip code correlate with California drought cycles over the last 20 years?

What kinds of datasets are there? What kinds of questions can we ask them? You decide.

5.2.6 Other Projects

There are online repositories of interesting programming problems, such as the Stanford Nifty assignments, that you can search for other project ideas.

After your experience as a STAR fellow this summer, you may have interesting projects of your own to share with your class. The IPython notebook could help you get them involved with them.


6. Suggestions and Resources

In case you are thinking...

6.1 How do I help my students if I don't know what I am doing?

Programming is a tool to be used for solving interesting problems. The easiest way to get excited about learning to use a new tool is having a problem to which you wish to apply it. Find something that fits you or your class' interests and help them develop a solution.

You don't need to know the answers or have solved the problem already to help them learn how to attack it. Let them be creative and propose challenging projects. If a problem appears too complicated to solve, help them refactor the problem into something that CAN be solved. Once they solve the simple problem, they can add complexity.

This is research - trying to find solutions to unsolved (unsolvable?) problems. Your research mentor this summer won't know the answer to the research problem you are here to help them with (although they probably have a good starting point and a basic direction for you to follow). Likewise, you don't need to know all the answers for your students either.

Show your students what research is really like. Try to solve a complicated problem together.

6.2 Getting started

Where to begin for yourself and your students?

  • Install Anaconda's IPython distribution and start working with the notebook
  • Learn to program in Python, possibly with one of these three resources

  • Practice programming with Project Euler (independently, in pairs, in groups) on a regular basis.

  • Find a project that interests you and your students
    • Follow best practices for writing effective programs
      • Discuss as a group, try to decompose it into simple logical steps
      • Develop code for each step, testing and documenting as you go
    • Put it all together and answer a research question with your code
    • Create a complete record of your work in a notebook and share it (online, at a conference, science fair, etc.)
    • Repeat!

6.3 One-stop shop for resources linked in this notebook


7. Conclusion

This notebook and workshop provide a basic entry to using the IPython Notebook to introduce and reinforce programming skills in the classroom through project-based learning. There are so many ways to incorporate computing into learning, some general, some specific to particular subjects. In addition to the resources included in this notebook, there are lots of great examples all over the web. Find what interests you and your students, then develop the skills you need as you tackle a project together.

Happy coding!