{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson 27: Numpy arrays and operations with them\n", "\n", "(c) 2018 Justin Bois. With the exception of pasted graphics, where the source is noted, this work is licensed under a [Creative Commons Attribution License CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). All code contained herein is licensed under an [MIT license](https://opensource.org/licenses/MIT).\n", "\n", "This document was prepared at [Caltech](http://www.caltech.edu) with financial support from the [Donna and Benjamin M. Rosen Bioengineering Center](http://rosen.caltech.edu).\n", "\n", "\n", "\n", "*This tutorial was generated from a Jupyter notebook. You can download the notebook [here](l27_numpy_arrays.ipynb).*\n", "\n", "

" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import altair as alt\n", "\n", "import bootcamp_utils" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We just got an introduction to NumPy and SciPy. The packages are extensive. At the center is the NumPy array data type. We will explore this data type in this tutorial. It is worth noting that under the hood of many of the operations we do with Pandas `DataFrame`s are done with NumPy arrays. As you understand how NumPy arrays work, you will also better understand what Pandas is doing.\n", "\n", "As it is always more fun to work with a real biological application, we will populate our NumPy arrays with data. In their 2011 [paper in PLoS ONE](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0025840), Harvey and Orbidans measured the cross-sectional area of *C. elegans* eggs that came from mothers who had a high concentration of food and from mothers of a low concentration of food. I digitized the data from their plots, and they are available in the file `data/c_elegans_egg_xa.csv` in the bootcamp repository." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extracting Numpy arrays from Pandas data frames\n", "\n", "NumPy has a primitive function for loading in data from text files, `np.loadtxt()`, but with Panda's `read_csv()`, there is really no reason to ever use it. So, we will load in the (tidy) data using Pandas." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
foodarea (sq. um)
0high1683
1high2061
2high1792
3high1852
4high2091
\n", "
" ], "text/plain": [ " food area (sq. um)\n", "0 high 1683\n", "1 high 2061\n", "2 high 1792\n", "3 high 1852\n", "4 high 2091" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_csv('data/c_elegans_egg_xa.csv', comment='#')\n", "\n", "# Take a look\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's a good idea, since we're using Altair, to change the column name for the area to exclude the dot." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "df = df.rename(columns={'area (sq. um)': 'area (sq um)'})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Just for fun, let's make a quick plot. We can do this because we went high level first." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.vegalite.v2+json": { "$schema": "https://vega.github.io/schema/vega-lite/v2.4.3.json", "config": { "point": { "filled": true, "opacity": 0.3 }, "view": { "height": 300, "width": 400 } }, "data": { "values": [ { "__jitter": -0.18601242476385216, "area (sq um)": 1683, "food": "high" }, { "__jitter": -0.19588448167425795, "area (sq um)": 2061, "food": "high" }, { "__jitter": 0.026993988266302005, "area (sq um)": 1792, "food": "high" }, { "__jitter": -0.11169301153564214, "area (sq um)": 1852, "food": "high" }, { "__jitter": 0.06385862244341062, "area (sq um)": 2091, "food": "high" }, { "__jitter": -0.1556356232074438, "area (sq um)": 1781, "food": "high" }, { "__jitter": 0.10619050922026235, "area (sq um)": 1912, "food": "high" }, { "__jitter": -0.017110496018028443, "area (sq um)": 1802, "food": "high" }, { "__jitter": -0.10053322661339466, "area (sq um)": 1751, "food": "high" }, { "__jitter": -0.09017222573156919, "area (sq um)": 1731, "food": "high" }, { "__jitter": -0.16361610383798586, "area (sq um)": 1892, "food": "high" }, { "__jitter": -0.16898212675309107, "area (sq um)": 1951, "food": "high" }, { "__jitter": 0.037142301652123205, "area (sq um)": 1809, "food": "high" }, { "__jitter": -0.15555502933842577, "area (sq um)": 1683, "food": "high" }, { "__jitter": 0.04682896877557824, "area (sq um)": 1787, "food": "high" }, { "__jitter": 0.1907779229714205, "area (sq um)": 1840, "food": "high" }, { "__jitter": 0.055471547853575276, "area (sq um)": 1821, "food": "high" }, { "__jitter": 0.15381124355306797, "area (sq um)": 1910, "food": "high" }, { "__jitter": 0.13807974911153903, "area (sq um)": 1930, "food": "high" }, { "__jitter": 0.13573054296649734, "area (sq um)": 1800, "food": "high" }, { "__jitter": -0.08593586997423866, "area (sq um)": 1833, "food": "high" }, { "__jitter": 0.06217174309000273, "area (sq um)": 1683, "food": "high" }, { "__jitter": 0.07463042546469506, "area (sq um)": 1671, "food": "high" }, { "__jitter": 0.15866381119486606, "area (sq um)": 1680, "food": "high" }, { "__jitter": 0.09249148808084062, "area (sq um)": 1692, "food": "high" }, { "__jitter": 0.1456136466763019, "area (sq um)": 1800, "food": "high" }, { "__jitter": 0.13102384621278762, "area (sq um)": 1821, "food": "high" }, { "__jitter": -0.03906961417318802, "area (sq um)": 1882, "food": "high" }, { "__jitter": -0.013285077018703684, "area (sq um)": 1642, "food": "high" }, { "__jitter": 0.12461584580543833, "area (sq um)": 1749, "food": "high" }, { "__jitter": -0.11215888038197451, "area (sq um)": 1712, "food": "high" }, { "__jitter": -0.011351927711327148, "area (sq um)": 1661, "food": "high" }, { "__jitter": 0.00019029401250558742, "area (sq um)": 1701, "food": "high" }, { "__jitter": 0.0499577283654033, "area (sq um)": 2141, "food": "high" }, { "__jitter": 0.17897211408019398, "area (sq um)": 1863, "food": "high" }, { "__jitter": -0.1160808505842239, "area (sq um)": 1752, "food": "high" }, { "__jitter": -0.08213780301322618, "area (sq um)": 1740, "food": "high" }, { "__jitter": 0.009833180798598518, "area (sq um)": 1721, "food": "high" }, { "__jitter": -0.08225441224413794, "area (sq um)": 1660, "food": "high" }, { "__jitter": -0.05105233887508548, "area (sq um)": 1930, "food": "high" }, { "__jitter": -0.16624018530808354, "area (sq um)": 2030, "food": "high" }, { "__jitter": -0.14952530527616142, "area (sq um)": 1851, "food": "high" }, { "__jitter": 0.1970675541391319, "area (sq um)": 2131, "food": "high" }, { "__jitter": -0.10196512412359815, "area (sq um)": 1828, "food": "high" }, { "__jitter": 0.8064558535554716, "area (sq um)": 1840, "food": "low" }, { "__jitter": 1.1684307302699484, "area (sq um)": 2090, "food": "low" }, { "__jitter": 0.8677268571673435, "area (sq um)": 2169, "food": "low" }, { "__jitter": 0.8569663195478624, "area (sq um)": 1988, "food": "low" }, { "__jitter": 0.97741753873965, "area (sq um)": 2212, "food": "low" }, { "__jitter": 1.020295501724004, "area (sq um)": 2339, "food": "low" }, { "__jitter": 1.1734002060473334, "area (sq um)": 1989, "food": "low" }, { "__jitter": 1.0621946774028368, "area (sq um)": 2144, "food": "low" }, { "__jitter": 1.1406070726064168, "area (sq um)": 2290, "food": "low" }, { "__jitter": 1.1128266783559368, "area (sq um)": 1920, "food": "low" }, { "__jitter": 0.8138527249939662, "area (sq um)": 2280, "food": "low" }, { "__jitter": 1.1323514301983895, "area (sq um)": 1809, "food": "low" }, { "__jitter": 1.0982958302793537, "area (sq um)": 2158, "food": "low" }, { "__jitter": 1.046304935725333, "area (sq um)": 1800, "food": "low" }, { "__jitter": 1.0141490502411714, "area (sq um)": 2133, "food": "low" }, { "__jitter": 1.0221901318897237, "area (sq um)": 2060, "food": "low" }, { "__jitter": 0.813953533438544, "area (sq um)": 2160, "food": "low" }, { "__jitter": 1.0380859722956888, "area (sq um)": 2001, "food": "low" }, { "__jitter": 0.9497687191981147, "area (sq um)": 2030, "food": "low" }, { "__jitter": 1.0957158892465173, "area (sq um)": 2088, "food": "low" }, { "__jitter": 0.9841775978341435, "area (sq um)": 1951, "food": "low" }, { "__jitter": 0.8370262963019697, "area (sq um)": 2460, "food": "low" }, { "__jitter": 0.9000979283210087, "area (sq um)": 2021, "food": "low" }, { "__jitter": 0.9829628882270973, "area (sq um)": 2010, "food": "low" }, { "__jitter": 1.0609658402905318, "area (sq um)": 2139, "food": "low" }, { "__jitter": 0.8000532438045769, "area (sq um)": 2160, "food": "low" }, { "__jitter": 1.1827727806471802, "area (sq um)": 2106, "food": "low" }, { "__jitter": 1.17262954323727, "area (sq um)": 2171, "food": "low" }, { "__jitter": 0.8129523305927425, "area (sq um)": 2113, "food": "low" }, { "__jitter": 1.0377930490088356, "area (sq um)": 2179, "food": "low" }, { "__jitter": 0.9868822007155247, "area (sq um)": 1890, "food": "low" }, { "__jitter": 1.160920125297981, "area (sq um)": 2179, "food": "low" }, { "__jitter": 0.9088765976538461, "area (sq um)": 2021, "food": "low" }, { "__jitter": 1.06653291796239, "area (sq um)": 1969, "food": "low" }, { "__jitter": 0.8957387041930729, "area (sq um)": 2150, "food": "low" }, { "__jitter": 1.171065913192848, "area (sq um)": 1900, "food": "low" }, { "__jitter": 1.0722209401437661, "area (sq um)": 2267, "food": "low" }, { "__jitter": 1.0018558690898236, "area (sq um)": 1711, "food": "low" }, { "__jitter": 1.0825657320822843, "area (sq um)": 1901, "food": "low" }, { "__jitter": 1.0130060072362734, "area (sq um)": 2114, "food": "low" }, { "__jitter": 0.9050381329989383, "area (sq um)": 2112, "food": "low" }, { "__jitter": 1.0987061715713733, "area (sq um)": 2361, "food": "low" }, { "__jitter": 0.9982923052667696, "area (sq um)": 2130, "food": "low" }, { "__jitter": 0.89799145904877, "area (sq um)": 2061, "food": "low" }, { "__jitter": 0.8068640364651295, "area (sq um)": 2121, "food": "low" }, { "__jitter": 0.9798778869629317, "area (sq um)": 1832, "food": "low" }, { "__jitter": 1.0207258370413532, "area (sq um)": 2210, "food": "low" }, { "__jitter": 1.0803220971224452, "area (sq um)": 2130, "food": "low" }, { "__jitter": 0.9978503031570902, "area (sq um)": 2153, "food": "low" }, { "__jitter": 0.9265860508026453, "area (sq um)": 2009, "food": "low" }, { "__jitter": 0.8473602452019033, "area (sq um)": 2100, "food": "low" }, { "__jitter": 0.9277743488202809, "area (sq um)": 2252, "food": "low" }, { "__jitter": 0.832126283138602, "area (sq um)": 2143, "food": "low" }, { "__jitter": 1.1568353181672906, "area (sq um)": 2252, "food": "low" }, { "__jitter": 1.0105119316836175, "area (sq um)": 2222, "food": "low" }, { "__jitter": 1.119745765588489, "area (sq um)": 2121, "food": "low" }, { "__jitter": 0.8061264629267176, "area (sq um)": 2409, "food": "low" } ] }, "encoding": { "color": { "field": "food", "type": "nominal" }, "tooltip": {}, "x": { "axis": { "grid": false, "labels": false, "ticks": false, "title": null, "values": [ 0, 1 ] }, "field": "__jitter", "type": "quantitative" }, "y": { "field": "area (sq um)", "scale": { "zero": false }, "title": "area (µm²)", "type": "quantitative" } }, "height": 300, "mark": "point", "width": 150 }, "image/png": "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", "text/plain": [ "\n", "\n", "If you see this message, it means the renderer has not been properly enabled\n", "for the frontend that you are using. For more information, see\n", "https://altair-viz.github.io/user_guide/troubleshooting.html\n" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bootcamp_utils.altair_jitter(df,\n", " encode_x=alt.X('food:N'), \n", " encode_y=alt.Y('area (sq um):Q',\n", " title='area (µm²)', \n", " scale=alt.Scale(zero=False)),\n", " height=300,\n", " width=150).configure_point(filled=True,\n", " opacity=0.3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It looks like worms that eat more food have smaller eggs.\n", "\n", "If we wanted to extract the measurements for worms with high food, we can do so using Boolean indexing in Pandas." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "pandas.core.series.Series" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high = df.loc[df['food']=='high', 'area (sq um)']\n", "\n", "# Take a look at the data type\n", "type(xa_high)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result is a Pandas `Series`, which is kind of like a single-column `DataFrame`. If we want to convert this to a Numpy array, we use the `.values` attribute." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "numpy.ndarray" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high = df.loc[df['food']=='high', 'area (sq um)'].values\n", "\n", "type(xa_high)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have a Numpy array! Let's pull out the low food cross sectional areas as well." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "xa_low = df.loc[df['food']=='low', 'area (sq um)'].values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now let's take a look at these arrays." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1683, 2061, 1792, 1852, 2091, 1781, 1912, 1802, 1751, 1731, 1892,\n", " 1951, 1809, 1683, 1787, 1840, 1821, 1910, 1930, 1800, 1833, 1683,\n", " 1671, 1680, 1692, 1800, 1821, 1882, 1642, 1749, 1712, 1661, 1701,\n", " 2141, 1863, 1752, 1740, 1721, 1660, 1930, 2030, 1851, 2131, 1828])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1840, 2090, 2169, 1988, 2212, 2339, 1989, 2144, 2290, 1920, 2280,\n", " 1809, 2158, 1800, 2133, 2060, 2160, 2001, 2030, 2088, 1951, 2460,\n", " 2021, 2010, 2139, 2160, 2106, 2171, 2113, 2179, 1890, 2179, 2021,\n", " 1969, 2150, 1900, 2267, 1711, 1901, 2114, 2112, 2361, 2130, 2061,\n", " 2121, 1832, 2210, 2130, 2153, 2009, 2100, 2252, 2143, 2252, 2222,\n", " 2121, 2409])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_low" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use these arrays as examples to learn about Numpy arrays." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Operators and NumPy arrays\n", "\n", "We saw in the previous tutorial that NumPy arrays are a special data type. They have well-defined ways in which our familiar operators work with them. Let's learn about this by example." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scalars and NumPy arrays\n", "\n", "We'll start with multiplying by an array by a constant. Say we wanted to convert the units of the cross sectional area from µm$^2$ to mm$^2$. This means we have to divide every entry by 10$^6$ (or multiply by 10$^{-6}$). Multiplication by a scalar works elementwise on NumPy arrays. Check it out." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.001683, 0.002061, 0.001792, 0.001852, 0.002091, 0.001781,\n", " 0.001912, 0.001802, 0.001751, 0.001731, 0.001892, 0.001951,\n", " 0.001809, 0.001683, 0.001787, 0.00184 , 0.001821, 0.00191 ,\n", " 0.00193 , 0.0018 , 0.001833, 0.001683, 0.001671, 0.00168 ,\n", " 0.001692, 0.0018 , 0.001821, 0.001882, 0.001642, 0.001749,\n", " 0.001712, 0.001661, 0.001701, 0.002141, 0.001863, 0.001752,\n", " 0.00174 , 0.001721, 0.00166 , 0.00193 , 0.00203 , 0.001851,\n", " 0.002131, 0.001828])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high / 1e6" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that `1e6` is how we represent numbers in Python in scientific notation, and that dividing the NumPy array by this number resulted in every entry in the array being divided. The `+`, `-`, and `*` operators all work in this way. For example:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2683, 3061, 2792, 2852, 3091, 2781, 2912, 2802, 2751, 2731, 2892,\n", " 2951, 2809, 2683, 2787, 2840, 2821, 2910, 2930, 2800, 2833, 2683,\n", " 2671, 2680, 2692, 2800, 2821, 2882, 2642, 2749, 2712, 2661, 2701,\n", " 3141, 2863, 2752, 2740, 2721, 2660, 2930, 3030, 2851, 3131, 2828])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high + 1000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Booleans with NumPy arrays and scalars\n", "\n", "Let's see what happens when we compare a NumPy array to a scalar." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, False, True, True, False, True, True, True, True,\n", " True, True, True, True, True, True, True, True, True,\n", " True, True, True, True, True, True, True, True, True,\n", " True, True, True, True, True, True, False, True, True,\n", " True, True, True, True, False, True, False, True])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high < 2000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We get an array of Booleans! The comparison is elementwise. This is important to know because we cannot use these comparisons with an **`if`** clause." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mxa_high\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m2000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Nothing to print, really. This will just be an error.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" ] } ], "source": [ "if xa_high > 2000:\n", " print('Nothing to print, really. This will just be an error.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's take the advice from the exception and use the `.any()` or `.all()` operators." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Check if any values are biggern than 2000\n", "(xa_high > 2000).any()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember, the expresson `(xa_high > 2000)` is itself a NumPy array of Booleans. The `any()` method returns `True` if *any* of the entries in that array are `True`. Similarly, the `all()` method returns `True` if *all* entries in the array are `True`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(xa_high > 2000).all()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yup! At least one cross sectional area is greater than 2000 µm$^2$ but not all of them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Equality checks with NumPy arrays\n", "\n", "Remember, you should never use the equality operator (`==`) with `float`s. Fortunately, NumPy offers a couple nice functions to check if two numbers are *almost* equal. This helps deal with the numerical precision issues when comparing `float`s. The `np.isclose()` function checks to see if two numbers are close in value, and this is really useful in writing tests. It works elementwise for NumPy arrays." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Compare two numbers\n", "np.isclose(1.3, 1.29999999999)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False, False,\n", " False, True, False, False, False, False, False, True, False,\n", " False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Compare an array to a scalar\n", "np.isclose(xa_high, 1800)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A couple cross sectional areas are 1800 µm$^2$. The `np.allclose()` function checks to see if all values in a NumPy array are close." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.allclose(xa_high, 1800)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Operators with two NumPy arrays\n", "\n", "We can apply operators with two NumPy ararys. Let's give it a whirl. (This is meaningless in the context of the actual data contained in these arrays, but it's an operation we need to understand.)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "operands could not be broadcast together with shapes (44,) (57,) ", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mxa_high\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mxa_low\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (44,) (57,) " ] } ], "source": [ "xa_high + xa_low" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yikes! The exception tells us that the two arrays we are using the operator on need to have the same shape. This makes sense: if we are going to do element-by-element addition, the arrays better have the same number of elements. To continue with our operators on two arrays, we'll slice the longer NumPy array. The basic slicing syntax is the same as for strings, lists, and tuples." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Just take the first elements\n", "xa_low_slice = xa_low[:len(xa_high)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok, let's try adding arrays again." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3523, 4151, 3961, 3840, 4303, 4120, 3901, 3946, 4041, 3651, 4172,\n", " 3760, 3967, 3483, 3920, 3900, 3981, 3911, 3960, 3888, 3784, 4143,\n", " 3692, 3690, 3831, 3960, 3927, 4053, 3755, 3928, 3602, 3840, 3722,\n", " 4110, 4013, 3652, 4007, 3432, 3561, 4044, 4142, 4212, 4261, 3889])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high + xa_low_slice" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great! We get element-by-element addition. The same happens for the other operators we've discussed. `np.isclose()` also operates element-by-element." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False, False,\n", " False, False, False, False, False, False, False, False])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.isclose(xa_high, xa_low_slice)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Slicing NumPy arrays\n", "\n", "We already saw that we can slice NumPy arrays like lists and tuples. Here are a few examples." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1828, 2131, 1851, 2030, 1930, 1660, 1721, 1740, 1752, 1863, 2141,\n", " 1701, 1661, 1712, 1749, 1642, 1882, 1821, 1800, 1692, 1680, 1671,\n", " 1683, 1833, 1800, 1930, 1910, 1821, 1840, 1787, 1683, 1809, 1951,\n", " 1892, 1731, 1751, 1802, 1912, 1781, 2091, 1852, 1792, 2061, 1683])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Reversed array\n", "xa_high[::-1]" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1852, 1751, 1683, 1930, 1680, 1642, 2141, 1660, 1828])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Every 5th element, starting at index 3\n", "xa_high[3::5]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1892, 1951, 1809, 1683, 1787, 1840, 1821, 1910, 1930, 1800, 1833])" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Entries 10 to 20\n", "xa_high[10:21]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fancy indexing\n", "\n", "NumPy arrays also allow **fancy indexing**, where we can slice out specific values. For example, say we wanted indices 1, 19, and 6 (in that order) from `xa_high`. We just index with a list of the indices we want." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2061, 1800, 1912])" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high[[1, 19, 6]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of a list, we could also use a NumPy array." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2061, 1800, 1912])" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xa_high[np.array([1, 19, 6])]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a very nice feature, we can use Boolean indexing with Numpy arrays, just like with Pandas using `.loc`. Say we only want the egg cross sectional areas that are greater than 2000 µm$^2$." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2061, 2091, 2141, 2030, 2131])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Just slice out the big ones\n", "xa_high[xa_high > 2000]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to know the indices where the values are high, we can use the `np.where()` function." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([ 1, 4, 33, 40, 42]),)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.where(xa_high > 2000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## NumPy arrays are mutable\n", "\n", "Yes, NumPy arrays are mutable. Let's look at some consequences." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 6, 4])" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Make an array\n", "my_ar = np.array([1, 2, 3, 4])\n", "\n", "# Change an element\n", "my_ar[2] = 6\n", "\n", "# See the result\n", "my_ar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's try working attaching another variable to the NumPy array." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 6, 9])" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Attach a new variable\n", "my_ar2 = my_ar\n", "\n", "# Set an entry using the new variable\n", "my_ar2[3] = 9\n", "\n", "# Does the original change? (yes.)\n", "my_ar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see how messing with NumPy in functions affects things." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.1, 0.2, 0.3, 0.4])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Re-instantiate my_ar\n", "my_ar = np.array([1, 2, 3, 4]).astype(float)\n", "\n", "# Function to normalize x (note that /= works with mutable objects)\n", "def normalize(x):\n", " x /= np.sum(x)\n", "\n", "# Pass it through a function\n", "normalize(my_ar)\n", "\n", "# Is it normalized even though we didn't return anything? (Yes.)\n", "my_ar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, be careful when writing functions. What you do to your NumPy array inside the function will happen outside of the function as well." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Slices of NumPy arrays are **views**, not copies\n", "\n", "A very important distinction between NumPy arrays and lists is that slices of NumPy arrays are **views** into the original NumPy array, NOT copies." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4]\n", "[1 9 3 4]\n" ] } ], "source": [ "# Make list and array\n", "my_list = [1, 2, 3, 4]\n", "my_ar = np.array(my_list)\n", "\n", "# Slice out of each\n", "my_list_slice = my_list[1:-1]\n", "my_ar_slice = my_ar[1:-1]\n", "\n", "# Mess with the slices\n", "my_list_slice[0] = 9\n", "my_ar_slice[0] = 9\n", "\n", "# Look at originals\n", "print(my_list)\n", "print(my_ar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Messing with an element of a slice of a NumPy array messes with that element in the original! This is not the case with lists. Let's issue a warning.\n", "\n", "
\n", "
Slices of NumPy arrays are **views**, not copies.
\n", "
\n", "\n", "Fortunately, you can make a copy of an array using the `np.copy()` function." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Make a copy\n", "xa_high_copy = np.copy(xa_high)\n", "\n", "# Mess with an entry\n", "xa_high_copy[10] = 2000\n", "\n", "# Check equality\n", "np.allclose(xa_high, xa_high_copy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, messing with an entry in the copy did not affect the original." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Indexing 2D NumPy arrays\n", "\n", "NumPy arrays need not be one-dimensional. We'll create a two-dimensional NumPy array by reshaping our `xa_high` array from having shape `(44,)` to having shape `(11, 4)`. That is, it will become an array with 11 rows and 4 columns." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1683, 2061, 1792, 1852],\n", " [2091, 1781, 1912, 1802],\n", " [1751, 1731, 1892, 1951],\n", " [1809, 1683, 1787, 1840],\n", " [1821, 1910, 1930, 1800],\n", " [1833, 1683, 1671, 1680],\n", " [1692, 1800, 1821, 1882],\n", " [1642, 1749, 1712, 1661],\n", " [1701, 2141, 1863, 1752],\n", " [1740, 1721, 1660, 1930],\n", " [2030, 1851, 2131, 1828]])" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# New 2D array using the reshape() method\n", "my_ar = xa_high.reshape((11, 4))\n", "\n", "# Look at it\n", "my_ar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that it is represented as an array made out of a list of lists. If we had a list of lists, we would index it like this:\n", "\n", " list_of_lists[i][j]" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Make list of lists\n", "list_of_lists = [[1, 2], [3, 4]]\n", "\n", "# Pull out value in first row, second column\n", "list_of_lists[0][1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Though this will work with NumPy arrays, this is *not* how NumPy arrays are indexed. They are indexed much more conveniently." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2061" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_ar[0,1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We essentially have a tuple in the indexing brackets. Now, say we wanted the second row (indexing starting at 0)." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1751, 1731, 1892, 1951])" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_ar[2,:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use Boolean indexing as before." ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2061, 2091, 2141, 2030, 2131])" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_ar[my_ar > 2000]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that this gives a one-dimensional list of the entries greater than 2000. If we wanted indices where this is the case, we can again use `np.where()`." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([ 0, 1, 8, 10, 10]), array([1, 0, 1, 0, 2]))" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.where(my_ar > 2000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tuple of NumPy arrays is how we would index using fancy indexing to pull those values out using fancy indexing." ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2061, 2091, 2141, 2030, 2131])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_ar[(np.array([ 0, 1, 8, 10, 10]), np.array([1, 0, 1, 0, 2]))]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "NumPy arrays can be of arbitrary integer dimension, and these principles extrapolate to 3D, 4D, etc., arrays." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Concatenating arrays\n", "\n", "Let's say we want to study all cross sectional areas and don't care if the mother was well-fed or not. We would want to concatenate our arrays. The `np.concatenate()` function accomplishes this. We simply have to pass it a tuple containing the NumPy arrays we want to concatenate." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1683, 2061, 1792, 1852, 2091, 1781, 1912, 1802, 1751, 1731, 1892,\n", " 1951, 1809, 1683, 1787, 1840, 1821, 1910, 1930, 1800, 1833, 1683,\n", " 1671, 1680, 1692, 1800, 1821, 1882, 1642, 1749, 1712, 1661, 1701,\n", " 2141, 1863, 1752, 1740, 1721, 1660, 1930, 2030, 1851, 2131, 1828,\n", " 1840, 2090, 2169, 1988, 2212, 2339, 1989, 2144, 2290, 1920, 2280,\n", " 1809, 2158, 1800, 2133, 2060, 2160, 2001, 2030, 2088, 1951, 2460,\n", " 2021, 2010, 2139, 2160, 2106, 2171, 2113, 2179, 1890, 2179, 2021,\n", " 1969, 2150, 1900, 2267, 1711, 1901, 2114, 2112, 2361, 2130, 2061,\n", " 2121, 1832, 2210, 2130, 2153, 2009, 2100, 2252, 2143, 2252, 2222,\n", " 2121, 2409])" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "combined = np.concatenate((xa_high, xa_low))\n", "\n", "# Look at it\n", "combined" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }