.. _python-intro: ******************* Lab I: Python Intro ******************* Overview ======== What is Python? --------------- **Python** is a high-level, interpreted programming language, which in recent years has become the leading tool for analysis and visualization of Astronomical data sets. What is Anaconda? ----------------- **Anaconda** is a free-to-use software bundle that includes Python, Jupyter (a web-based interface to Python), plus many supporting packages. It is produced by Anaconda, Inc. What is Jupyter? ---------------- **Jupyter** allows Python to be run from within a web-browser, via interactive documents known as **notebooks**. A notebook is built from a number of discrete fragments called **cells**; each cell may contain either textual narrative or Python code. Installation ============ If you have not already done so, download and install Anaconda by following the appropriate instructions: * `Installing Anaconda Python on a Windows 10 PC `_ * `Installing Anaconda Python on a Mac `_ * `Installing Anaconda Python on a Linux PC `_ A First Notebook ================ With the Jupyter window open in your web browser, navigate to the ``astro_310`` folder that you created during the installation steps, and make a fresh notebook by selecting *Python 3* from the *Notebook* section of the *New* drop-down menu. Then, click on the title of the notebook and rename it from ``Untitled`` to ``first-notebook``. You're going to use this notebook to explore Python's basic functionality. A few points to bear in mind about notebooks: * To select a cell, click anywhere inside it. * To execute the currently-selected cell (i.e., pass its contents to Python for interpretation), press *Shift*\ +\ *Enter* (or *Shift*\ +\ *Return*) on your keyboard, or click the |play| *Run* icon at the top of the notebook. * To execute all cells in the notebook, elect *Cell*\ |rarrow|\ *Run All* from the menu at the top. * To insert a new cell below the currently-selected cell, click the |plus| icon at the top. * To delete the currently-selected cell, click the |scissors| icon at the top. * To interrupt the execution of a cell, click the |stop| icon at the top. * By default, newly created cells are interpreted as code. However, you can change them to text cells by clicking the *Code* drop-down menu at the top and selecting *Markdown*. A further, **important** point to bear in mind about Python in general, is that indentation and capitalization are significant --- if you get them wrong, then Python will complain! Importing Modules ----------------- **Modules** are add-ons that extend Python's basic functionality. To use them we need to import them. Cut and paste the following code into the first cell of your notebook: .. code:: # Lines like this, beginning with '#', are comments that are ignored # by Python. It's a good idea to comment your code to make it clear # what's going on # Import the numpy module to provide numerical functionality import numpy as np # Import the matplotlib.pyplot module to provide plotting functionality import matplotlib.pyplot as plt # Tell matplotlib.pyplot to do inline plots %matplotlib inline Then, execute the cell (*Shift*\ +\ *Enter* or *Shift*\ +\ *Return*). Variables --------- **Variables** are named locations where data can be stored. A variable has both a type (integer, float, list, etc.) and a value. In Python, types are 'dynamic' --- they can change during program execution. This can be convenient, but it can also be a pitfall if you're not aware. A variable name must start with a letter, it can't contain any illegal symbols or spaces, and it can't be one of the few words Python reserves. In a Jupyter notebook, when you type a word that Python reserves, it will change color. Create a new cell in your notebook, and execute this code in it: .. code:: # Create an integer variable x = 5 # Print it out and then print its type. print() is a built-in # Python functions that prints out a value; type() is another # built-in function that returns the type of a variable print(x) print(type(x)) # Create a float variable (for representing real numbers). Note the # decimal point! x = 5. print(x) print(type(x)) Numerical Operations -------------------- Numerical variables (integers, floats and complex numbers) support the four standard arithmetic operations (`+`, `-`, `*` and `/`) together with more-sophisticated functions. Try out the following code, executing each block in a separate cell: .. code:: # Addition with integers and floats (math on mixed types will # convert integers to float) myInteger = 2 myFloat = 2. print(myInteger + 3) print(myInteger + 3.) print(myFloat + 3) print(myFloat + 3.) .. code:: # Multiplication and division with integers and floats print(myInteger*6) print(myInteger/6) # Converts myInteger to float print(myInteger//6) # Doesn't convert myInteger to float print(myFloat*6) print(myFloat/6) .. code:: # Some other math operators and functions print(myFloat**2) # The ** operator raises to a power print(np.exp(myFloat)) # Exponentiation (provided by numpy module) print(np.log(myFloat)) # Natural logarithm (provided by numpy module) print(np.log10(myFloat)) # Base-10 logarithm (provided by numpy module) print(np.sin(myFloat)) # Sine (provided by numpy module) print(np.sqrt(myFloat)) # Square root (provided by numpy module) print(np.sqrt(-myFloat)) # This should produce a 'nan' (Not-a-Number) .. code:: # Create a complex variable myComplex = complex(-1.,2.) # The complex() function returns a complex number myComplex = -1. + 2.*1j # Same effect as previous command print(myComplex) print(type(myComplex)) print(np.sqrt(myComplex)) # The sqrt() function works as it should with complex numbers Strings ------- Variables can store more than just numerical data. For instance, variables with the **string** type contain sequences of characters: .. code:: # Create a string myString = 'This is my string' # Note quotes '' print(myString) print(type(myString)) print(len(myString)) # The len() function returns the length of a string Strings support **indexing** operations using square brackets ``[]``, allowing individual characters of a string to be accessed by their integer index. Indices start at ``0`` and run through to ``L-1``, where ``L`` is the length of the string: .. code:: # String indexing print(myString[0]) # Print the first character (index 0) print(myString[1]) # Print the second character (index 1) L = len(myString) print(myString[L-1]) # Print the last character print(myString[-1]) # Easier way to print the last character print(myString[-2]) # Print the second-to-last character Square brackets can also be used to for **slicing** operations, where we access a sequence of characters within a string (known as a **substring**): .. code:: # String slicing print(myString[5:10]) # Print characters with indices from 5 (inclusive) to 10 (exclusive) print(myString[:10]) # Print charcters from the start to index 10 (exclusive) print(myString[5:]) # Print characters from index 5 (inclusive) to the end print(myString[:]) # Print all characters Strings can be **concatenated** together using the ``+`` operator: .. code:: # String concatenation num = 7 numString = str(num) # The str() function converts a number to a string anotherString = 'My lucky number is ' + numString print(anotherString) Lists & Tuples --------------- Sometimes we want to store not just a single value in a variable, but a sequence of values. This is what **lists** are for: .. code:: # Create a list myList = ['eggs', 'potatoes', 42, 180., 'bucky'] # Note square brackets [] print(myList) print(type(myList)) print(len(myList)) # The len() function returns the length of a list Lists can be indexed, sliced and concatenated the same way as strings: .. code:: # List indexing, slicing and concatenation print(myList[1]) print(myList[-2]) print(myList[1:3]) print(myList[:3]) print(myList[3:]) anotherList = myList + [1+3*1j, 'eureka!'] print(anotherList) You can modify lists by indexing or slicing on the left-hand side of an assignment: .. code:: # Modify a list using indexing and slicing anotherList[0] = 'Ostrich eggs' anotherList[4:6] = [-1, -2] print(anotherList) **Tuples** are like lists in almost all respects, but they are created using a slightly different syntax, and can't be modified after creation: .. code:: # Create a tuple myTuple = (0, 1, 2., 3., 'four', 'five') # Note parentheses () print(myTuple) print(type(myTuple)) myTuple[0] = 'Nought' # Will cause an error Arrays ------ **Arrays** are a type provided by the ``numpy`` module. They are similar to lists, but support multidimensional indexing. They can, however, only contain numbers: .. code:: # Create an array myArray = np.array([0, 1, 2, 3, 4, 5]) # The np.array() function creates an array from a list print(myArray) print(type(myArray)) print(type(myArray[0])) print(myArray.shape) # The .shape operator returns the dimensions of an array Unlike lists, arrays support **array arithmetic** where operations apply to each array element in turn: .. code:: # Array arithmetic print(myArray*2) print(myArray + myArray) print(np.exp(myArray)) The ``numpy`` module provides a variety of alternative ways to create arrays: .. code:: # Alternative ways to create arrays anotherArray = np.arange(0., 6., 1.5) # From 0 (inclusive) to 6 (exclusive) in steps of 1.5 print(anotherArray) anotherArray = np.linspace(10., 20., 5) # From 10 to 20 (inclusive) with 5 values print(anotherArray) Dicts ----- **Dicts** (short for 'dictionaries') are Python's most flexible type. They store a group of values, but indexed by a 'key' (a string) rather than an integer: .. code:: # Define a dict using a sequence of key-value pairs myDict = {'width': 8.5, 'height': 11, 'shape':'rectangle'} # Note braces {} print(myDict) print(type(myDict)) # Print out keys and values (note that ordering is arbitrary) print(myDict.keys()) print(myDict.values()) Dicts support indexing via their key: .. code:: # Dict indexing print(myDict['width']) print(myDict['shape']) Loops ----- Often, we wish to iterate through the elements of an indexable type. We can do this using **loops**: .. code:: # Iterate through the elements of a list for item in myList: # This sets the variable 'item' to each of the elements in turn print(item) # Do the same using indexing for i in range(len(myList)): # The range() function returns a list of indices print(i, myList[i]) # Iterate through the elements of a dict for key, value in myDict.items(): print(key, value) Plotting -------- As the final step of this lab, let's use some functions from the ``matplotlib`` module to make a plot. First, let's create data for the plot: .. code:: # Create plot data x = np.linspace(0., 2.*np.pi, 25) # 0 to 2pi in 25 steps y = np.sin(x) Now create the plot plus all-important labels: .. code:: # Create a new figure plt.figure() # Plot y versus x plt.plot(x, y, color='b', label='line') # Line plot plt.scatter(x, y, color='r', label='scatter') # Scatter plot # Add axis labels and annotation plt.xlabel('x') plt.ylabel('sin(x)') plt.legend() Catching Up =========== If you fell behind in class, or are having difficulty with the cut-and-paste, then download a notebook containing the code for this lab from `here `__.