\documentclass[../../book.tex]{subfiles}
\graphicspath{{\subfix{.}}}
\begin{document}

\chapter{Data Types}
\label{DataTypes}

As noted in Chapter \ref{Chap:N:00ExpDes} and \ref{Computers}, \textit{everything} that a computer operates on can ultimately be framed as numbers. At the same time, most people make a distinction between multiple categories of data, like words versus numbers, integers versus numbers with decimals, and between single objects and groups of objects. We do this because it helps us set expectations for how  data will behave and what we can do with it. Most people have no difficulty carrying out multiplication between integers (2$\times$4 = 8) or between integers and numbers with decimals (2$\times$3.8 = 6.4), but will find multiplication between words to be non-sensical (apathy$\times$magenta=???). 

One way to frame this treatment is to say that there are different `types' of data, and that these types have different possible behaviors. Modern programming languages try to capture this instinctive view of data by explicitly defining types of data, and then using these types to determine behavior. In this chapter, we will discuss the built-in data types in Python, some of the data types commonly added from libraries\index{libraries}, and their basic behaviors. The goal is not to be comprehensive, but to provide enough detail to understand the examples presented throughout this book, avoid common errors that are encountered when data type behavior is ignored, and also prepare you to be able to read further on this topic. 

\setcounter{section}{-1}  % Start subsections from 0

\section{Types that deal with numbers}
\setcounter{subsection}{-1}  % Start subsections from 0

Since everything in your computer is a number, it is probably no surprise that there are several \cite{data_type}s that deal with numbers. Here, we consider each in turn.


\subsection{Numeric types are used for `normal' math}
\setcounter{subsubsection}{-1}  % Start subsections from 0

As scientists, we are fairly familiar with integers, numbers with decimals, and numbers that include multiples of the square root of -1. Python has built in types for all three of these: \lstinline{int}, \lstinline{float}, and \lstinline{complex}.


\subsubsection{\texttt{int} and \texttt{float}}
The Python type for integers is \lstinline{int}\index{data types!integers}. This holds the `whole' numbers, both positive and negative. These are numbers that are most useful for counting. We naturally say things like: `I own 5 shirts.'  So, a quick rule of thumb is that if you are counting things, you should be using the \lstinline{int} type. 

The other type of basic number that we use are those that contain decimals. These are not the counting numbers: we would never say "I have 5.0 shirts." Instead, we tend to use these in calculations that are not simply counting. In Python, we call these numbers floats,\footnote{The terminology comes from the term `floating point.'} and the type is \lstinline{float}\index{data types!float}. 


There are a few ways to create each of these data types.  We can directly assign them to \cite{variable}s, using the assignment operator (=). 

\begin{pyin}
    i = 2
\end{pyin}
\begin{pyin}
    f = 3.8
\end{pyin}

This will result in the creation of two variables, one that points to an \lstinline{int} data type with a value of 2 and one that points to a \lstinline{float} data type with a value of 3.8. 

You will note that we didn't tell Python what type to use.  Instead, it made a guess, based on the form of the data we gave it. We can check to make sure that the correct type was assigned using the \lstinline{type()} \cite{function}.

\begin{pyin}
    print(type(i))
\end{pyin}
\begin{pyout}
    int
\end{pyout}
\begin{pyin}
    print(type(f))
\end{pyin}
\begin{pyout}
    float
\end{pyout}


Rather than leaving Python to guess at the data type we want, we can  explicitly tell Python what data type we want, using a function.\footnote{When used to create the initial instance of a type, the function is also called a `constructor.'} The function to create an integer is \lstinline{int()}, while the function to create a float is \lstinline{float()}. We can give these at the time of creating the data, or we can also use them to make one data type another. In the language of programming, this is known as \textit{coercion}. Here are examples of coercing our \lstinline{int} to a \lstinline{float}, and vice versa. 

\begin{pyin}
int_from_float = int(f)
\end{pyin}
\begin{pyin}
float_from_int = float(i)
\end{pyin}

We can get these values as follows:
\begin{pyin}
print(int_from_float)
\end{pyin}
\begin{pyout}
3
\end{pyout}
\begin{pyin}
print(float_from_int)
\end{pyin}
\begin{pyout}
2.0
\end{pyout}

A few things to note here: first, when we use \lstinline{int()} on a float, the value of the float is truncated, rather than rounded. If we want to round instead, we would need to use the \lstinline{round()} function, which returns an integer (when no \cite{keyword_argument}s are supplied), after rounding. 

\begin{pyin}
int_from_float_2 = round(f)
\end{pyin}
\begin{pyin}
print(int_from_float_2)
\end{pyin}
\begin{pyout}
4
\end{pyout}

Second, floats \textit{always} display at least one decimal place. This is one way that Python indicates that an object is a \lstinline{float}, which is why Python represents the second value as 2.0. 

\subsubsection{\texttt{complex}}

Python can also handle complex numbers.  These are numbers that contain some factor of the square root of -1. Though we will not use them in this book, depending on the nature of your research, you might. The type for these numbers is \lstinline{complex}\index{data types!complex}.

We can create complex numbers in similar ways to above. For instance, we can directly write the value we want, and leave Python to automatically assign the type:

\begin{pyin}
c1 = 2.1 + 3j
\end{pyin}

Here, the inclusion of the square root of -1 (i.e. $j$) is how Python knows this is a complex number.\footnote{Many people are more familiar with the use of $i$ for the square root of -1, but $j$ is also common.} However we can also use the function \lstinline{complex()} to create a complex number. In this case, the function accepts two values (called \cite{argument}s), the first is assigned to the real part of the complex number, and the second to the imaginary part of the complex number.

\begin{pyin}
c2 = complex(2.1, 3)
\end{pyin}
And c1 and c2 will have equivalent values. It is important to realize that we can also provide the \lstinline{complex()} function a single argument, in which case it is assigned to the real part, and the complex part is assigned a value of 0.

\begin{pyin}
c3 = complex(7)
\end{pyin}
\begin{pyin}
print(c3)
\end{pyin}
\begin{pyout}
7+0j
\end{pyout}

We can try to coerce a number to a complex using the same function, providing either one or two values. 

\begin{pyin}
c4 = complex(i, f)
\end{pyin}
\begin{pyin}
print(c4)
\end{pyin}
\begin{pyout}
2+3.8j
\end{pyout}

There are two more things to note about complex types.  First, we can access the real and imaginary parts separately. We do this by appending \text{.real} or \text{.imaginary} to the number. In other words: they are \cite{attribute}s of the \lstinline{complex} data type.

\begin{pyin}
real = c4.real
\end{pyin}
\begin{pyin}
imaginary = c4.imaginary
\end{pyin}

The second thing to note is that the numbers stored in the complex are floats. You can see this if we print the values of for real component. 

\begin{pyin}
print(real)
\end{pyin}
\begin{pyout}
2.0
\end{pyout}

Thus, even though Python had not printed our complex number with a decimal place, it still views this number as a float, when pulled away from the complex number. 

\subsubsection{using numeric types}

We will conclude this discussion of the `common' math types by noting that they can be used with the familiar mathematical operations. Though there are \textit{many} possible mathematical operations, Python includes only 12 (though more can be added with \acite{libraries}.  Of these 12, we will only use 7 in this book: addition, subtraction, multiplication, division, exponentiation, absolute value, round and modulo.\footnote{If you are unfamiliar with modulo, it returns the remainder of a division problem, as explained here: https://simple.wikipedia.org/wiki/Modulo\_operation} You will note that this list \textit{does not} include logarithms. In order to use logarithms, we will need to import a library.  Many libraries include this operation, but the one we will use most often is Numpy. Table \ref{tab:DataTypes:MathOperations} presents these operations, the symbol or function used to implement it in Python, and an example of its use.

And this concludes our introduction to the `normal' math types.  However, as we are about to see, Python has others. 

\begin{table}[H]
    \centering
    \caption{Operations that can be performed on numerical data in Python. Those above the double horizontal line are built-in to Python.}
    \label{tab:DataTypes:MathOperations}
    \begin{tabular}{l | c | l}
        \textbf{operation} & \textbf{symbol} / \textbf{function} & \textbf{example} \\ 
        \hline
        addition        & +             & 2 + 3.8 = 5.8 \\
        subtraction     & -             & 2 - 3.8 = -1.8 \\
        multiplication  & *             & 2 $\cdot$ 3.8 = 7.6 \\ 
        division        & /             & 2 / 3.8 = 0.5263157894736842 \\
        exponentiation  & **            & $2^{3.8}$ = 13.928809012737984 \\
        absolute value  & abs()         & abs(-1.8) = 1.8 \\
        round           & round()       & round(-1.8) = -2 \\
        modulo (remainder) & \%            & 7\%5 = 2 \\
        \hline
        \hline
        ln              & numpy.log()   & log($10^7)$ = 2.5649493574615367\\
        log$_{10}$      & numpy.log10() & log$_{10}$($10^7$) = 7\\
        \hline
    \end{tabular}
\end{table}






\subsection{The \texttt{boolean} type is used when evaluating truth statements}

Another type of `number' that we commonly encounter is the boolean\index{data types!boolean}, which in Python is called \lstinline{bool} and which hold `truth values.' In Python, a \lstinline{bool} can have one of two values: \lstinline{True} or \lstinline{False}. Please note that capitalization is critical here: TRUE, true, FALSE, and false are \textbf{not} proper values for the \lstinline{bool} type.

We can create a boolean directly.
\begin{pyin}
T = True
\end{pyin}
\begin{pyin}
F = False
\end{pyin}

But we can also attain booleans by performing \cite{operation}s or \cite{function}s on data. Booleans are often also associated with certain `boolean' operations that test if a particular situation is true or not. 

For instance, what if we wanted to know if 4 was less than 5? We can use the boolean operator '$<$' within a \cite{conditional_statement} to test this.

\begin{pyin}
4<5
\end{pyin}
\begin{pyout}
True
\end{pyout}
Here we say that the operation '\cite{return}ed' a value of \lstinline{False}.

There are other questions we can ask, and they are summarized in Table \ref{tab:DataTypes:BooleanOperations}. When looking at this table, there are two kinds of operations. Those that are above the double line are typically applied to non-booleans, but result in a boolean. That is, we will often check if one number is larger than another, or if the value of one variable is equal to the value of another. 

However, below the double line, we find boolean operators are mostly used to operate on booleans (also returning booleans), for instance checking to see if both in a pair of booleans are True (\lstinline{and}), if at least one  in a pair of booleans is True (\lstinline{or}), or changing the value of a boolean (\lstinline{not}). 

\begin{pyin}
3 < 5 and 5 < 3
\end{pyin}
\begin{pyout}
False
\end{pyout}
\begin{pyin}
3 < 5 or 5 < 3
\end{pyin}
\begin{pyout}
True
\end{pyout}
\begin{pyin}
not 3 < 5
\end{pyin}
\begin{pyout}
False
\end{pyout}

Python also includes the xor operator, which is different from how we use "or" in everyday discourse. When we say "or" we mean that either one, or the other, or \textit{both} can be true (i.e., at least one value is true).  This is the way that the operator \lstinline{or} works.  However, in logic, there is an ''exclusive or (xor)'', which is only True if one or the other value is True, but \textit{not both}. The operator for this in Python is \lstinline{^} (the `caret' character). Its use can be illustrated as follows:
\begin{pyin}
not 3 < 5 ^ 7 < 2
\end{pyin}
\begin{pyout}
True
\end{pyout}
\begin{pyin}
not 8 < 1 ^ 7 < 3
\end{pyin}
\begin{pyout}
False
\end{pyout}
\begin{pyin}
not 3 < 5 ^ 1 < 9
\end{pyin}
\begin{pyout}
False
\end{pyout}

Booleans will be most often encountered in \cite{control_structure}s, like \lstinline{if} and \lstinline{while} loops (See Chapter \ref{FlowControl}). Outside of that, they can be hidden in the operations of other functions, like \lstinline{in} and \lstinline{numpy.loadtext()}. These will be flagged as needed in the text. 

\begin{table}[H]
\caption{Operations that can be performed using Boolean logic.}
\label{tab:DataTypes:BooleanOperations}
    \centering
    \begin{tabular}{l | c | c | l}
        operation & symbol & example & returns \\
        \hline
        \hline
        equality            & == & 3 == 3.0 & True \\
        inequality          & != & 2 != 2 & False \\
        greater than        &  \\
        greater or equal to & \\
        less than           &\\
        less or equal to    & \\ 
        \hline \hline
        and                 & \texttt{and}  & True and False & False\\
        or                  & \texttt{or}   & True and False & True \\
        xor                 & \^{} & True \^{} True & False \\
        not                 & not & not True & False \\ 
        \hline
        
    \end{tabular}
\end{table}




\subsection{The binary types allow direct access of computer memory}

Though the basic unit of computer number is the bit (often represented by 0 or 1) there is no data type in Python that corresponds directly to 0 and 1.\footnote{Though one could try to conceive that the booleans discussed above are 0 and 1, they are not actually 0 and 1, in the way computers perceive them. For instance, a single 0 or 1 should occupy 1 bit of \cite{memory}. In Python \lstinline{True} and \lstinline{False} commonly occupy 24 \textit{bytes} of memory.} Instead, the smallest element that Python provides access to is the byte. Python provides the means to access, store, and manipulate these bytes using three types:  \lstinline{byte}, \lstinline{bytearray}, and \lstinline{memoryview}. Though these exist, it is unlikely that you will use them, instead, you will be working with data types that map onto more familiar numbers. For this reason, we will not expound on these further here, mentioning them only for completeness.


\subsection{The \lstinline{NoneType} lets us represent the value of nothing}

The number 0 was not invented until around 500 CE. It must have seemed like a strange idea: how to represent not having anything? Why even bother? Nevertheless, it was a useful invention: allowing us to express somewhat strange ideas like, ``having'' 0 shirts.

The utility of indicating nothing carries over to programming and, in Python, we represent nothing with the \lstinline{NoneType}\index{data types!NoneType}. This type has only one value: \lstinline{None}. Note the capitalization: neither NONE nor none are proper values. This type is a way for Python to note a difference between the number 0 and there being actually...nothing. As a more concrete example: \lstinline{None} also provides the means to discriminate between a collection of items that contains 0 objects (i.e., the collection exists, but is empty) and one that does not exist (so the value is \lstinline{None}). This is akin to discriminating between a bank acount that has no money in it, and one that does not yet exist---clearly a useful thing to be able to do.

We can create NoneType objects directly:

\begin{pyin}
n = None
\end{pyin} 

This approach can be used to create variables that are placeholders (they will later hold data), or maybe to indicate that there is no value to yet assign. 

Another means to generate them by assigning a variable to the results of a \cite{function} or \cite{operation} that does not \cite{return} a value. For instance, the \lstinline{print()} function in Python does not return any value, so, if we try to assign the results of a print function to a variable, it will result in \lstinline{None}. 

\begin{pyin}
p = print("HI!")
\end{pyin}
\begin{pyin}
print(p)
\end{pyin}
\begin{pyout}
None
\end{pyout}

We realize this discussion is somewhat abstract, but it is easier to understand the need for \lstinline{None}, within more complex code examples. We will point out the occurrences as they happen in the book. For now, we note that this concludes the tour of built in Python data types that hold only one value at a time. Now we turn to those data types that can hold multiple values simultaneously. 



\section{Collections let us hold multiple elements, and have new behaviors}
\setcounter{subsection}{-1}  % Start subsections from 0

It is rare in life that we only deal with single objects. Instead, it is often quite convenient to collect multiple values together---sometimes even grouping different types of data. We might make a grocery list in which we have multiple ingredient names, combined with numbers indicating how much to buy. Next we explore how Python enables such behavior. We will first discuss on type of collection (the list) in order to illustrate some behaviors, and then proceed to consider all the collections that are in the base version of Python (more can be added by libraries). 

\subsection{The list, and behaviors of collections}

There are many types of data in Python that allow us to hold several values ( `elements'). However, before we discuss them in general, we need to introduce some new behaviors.  To do so, we will introduce the Python \lstinline{list}\index{data types!list} type.

A Python list is a data type that can hold any number of elements, of any type.  A list can hold integers, floats, booleans, or even other lists. Any data type we discuss here can be an entry in a Python list. Lists can be created directly by placing zero or more values, separated by commas, between square brackets. 

\begin{pyin}
l1 = [None, 4, True]
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
[None, 4, True]
\end{pyout}

Alternatively, we can build long lists from short lists, using the repetition operator (*). If we were using numeric types, this would be multiplication, but for lists, we simply repeat the list the specified number of times.

\begin{pyin}
l2 = l1 *2
\end{pyin}
\begin{pyin}
print(l2)
\end{pyin}
\begin{pyout}
[None, 4, True, None, 4, True]
\end{pyout}
Where you can see that we have a new list that now repeats the first list twice.  This approach can be nice when you want to have a list that contains a large number of the same entry.  For instance, imagine we wanted to have a list that held the integer 1, repeated 100 times.  You could simply use:
\begin{pyin}
l1 = [1]*100
\end{pyin}

The fact that the list is a data type with multiple elements within it introduces several new behaviors.  There are three that we feel are critical to understand, and we discuss next. 

\subsection{Iterable}

Some (but not all) data types that hold multiple elements are iterable (see Table \ref{DataTypes:tab:types}). When a data type is iterable, this means that we can run through the elements in the data type one at a time. This operation is known as `iteration.' There are several common built-in Python functions that rely on this property. Again, there are three that we think are critical to understand. They are:
\begin{itemize}

    \item{len()}

    This function returns the length of the iterable, which is equal to the number of elements it holds. 
    
\begin{pyin}
len(l1)
\end{pyin}
\begin{pyout}
3
\end{pyout}
    
    If you give this function a data type that is not iterable (for instance, a float), then it will return an exception.
    
\begin{pyin}
len(2.9)
\end{pyin}
\begin{pyout}
object of type 'float' has no len()
\end{pyout}
    
    
    \item {in}
    
    This operator checks to see if an element is already present within the object. This works by iterating over every element and evaluating the boolean operation `=='. If the element is present, a value of \lstinline{True} is returned, otherwise a value of \lstinline{False} is returned. 
    
\begin{pyin}
4 in l1
\end{pyin}
\begin{pyout}
True
\end{pyout}
    
    If you try to use \lstinline{in} on a non-iterable data type, Python generates an exception:
\begin{pyin}
2 in 2.9
\end{pyin}
\begin{pyout}
argument of type `float' is not iterable
\end{pyout}
    
    
    
    \item {for}
    
    This is most commonly used as part of a \cite{control_structure} called a '\lstinline{for} \cite{loop}'\footnote{For more information, you can read Chapter \ref{FlowControl}} and is used in conjunction with a variable, the \lstinline{in} operator, and an iterable object. The behavior is that we iterate through the list, each element in the iterable is returned, and assigned to the variable. We can then use that variable however we like before we move on to the next element. For instance, we might print the value of the element.
    
\begin{pyin}
for i in l1:
    print(i)
\end{pyin}
\begin{pyout}
None
\end{pyout}
\begin{pyout}
4
\end{pyout}
\begin{pyout}
True
\end{pyout}
    
    You can see that this \lstinline{for} loop has taken each element in our iterable (\lstinline{l1}) in turn, passed it to the variable \lstinline{i}, and then printed this variable, before moving on to the next element.  Once we have done this for every element, the loop stops. 

    There are some interesting formatting in this example, such as the colon at the end of the first line, and the indentation of the second line. For a detailed description of this, please see Chapter \ref{FlowControl}. For now, we will just consider the output. 
    
    If we try to use this on a non-iterable object, Python generates an error. 
    
\begin{pyin}
for i in 6.0:
    print(i)
\end{pyin}
\begin{pyout}
'float' object is not iterable
\end{pyout}
    
    \item{* , **}
    These operators are known as `unpacking' operators. What they do is return all the elements within the iterable object. This is different than getting the value of the iterable object:
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
[None, 4, True]
\end{pyout}
\begin{pyin}
print(*l1)
\end{pyin}
\begin{pyout}
None 4 True
\end{pyout}
    Note that the first input results in printing the actual list (an object containing three elements), while the second returns the actual values of these elements. In a sense, this is a bit like running a \lstinline{for} loop and getting the return elements all at once, though it is certainly used differently than in \lstinline{for} loops. 
    
    One important point to make here is that the * operator is used with \textit{almost} every iterable object. However, there is one iterable type where it does not work: the dictionary.  We will discuss dictionaries below, but for here we simply note that the correct unpacking operator for dictionaries is **.    
\end{itemize}

\begin{table}[H]
    \centering
    \caption{Date types and their properties.}
    \label{tab:DataTypes:DataProperties}
    \begin{tabular}{lcccc}
    
    \multicolumn{5}{c}{built-in types}\\
    type                & page  & iterable  & slicable     & mutable   \\\hline
    \hline
    
    \texttt{nonetype}   &       &           &                     & \\\hline
    \texttt{bool}       &       &&&\\\hline
    \texttt{int}        &       &&&\\\hline
    \texttt{float}      &       &&&\\\hline
    \texttt{complex}    &       &&&\\\hline

    \texttt{byte}       &       & \texttt{True}        &                  & \\\hline
    \texttt{bytearray}  &       & \texttt{True}        & \texttt{True}                & \texttt{True}\\\hline

    \texttt{list}       &       & \texttt{True}        & \texttt{True}                & \texttt{True}\\\hline
    \texttt{tuple}      &       & \texttt{True}        & \texttt{True}                & \\ \hline
    \texttt{str}        &       & \texttt{True}        & \texttt{True}                & \\\hline

    \texttt{dict}       &       & \texttt{True}        &                   & \texttt{True}\\\hline
    \texttt{set}        &       & \texttt{True}        &                   & \texttt{True}\\\hline
    \texttt{frozenset}  &       & \texttt{True}        && \\ 
    \hline

    \hline
    \multicolumn{5}{c}{types that can be added using libraries}\\\hline
    \hline
    
    \texttt{numpy array}&       & \texttt{True}        & \texttt{True}                & \texttt{True}\\ \hline
    %\texttt{datetime}   &       &&&\\\hline
    \texttt{Pillow Image}&       & \texttt{True}        & \texttt{True}                & \\ \hline
    \texttt{Plotly Figure}&       & \texttt{True}        &  & \\ \hline
    \hline
    
    \end{tabular}
    \label{DataTypes:tab:types}
\end{table}


\subsection{Slicable}

Some (but not all) data types that hold multiple elements are slicable (see Table \ref{DataTypes:tab:types}). When a data type is slicable, it means that the elements held within it are assigned an integer index\footnote{This index is also known as a `subscript.' However, subscript is the more general term for how we can refer to elements in an object. There are non-numeric subscripts.  For example, the subscripts for dictionaries are strings and are called `keys.' In this book the behavior of data types with numeric subscripts (a.k.a., indices) is useful enough that we focus on this aspect.} that can be used to refer to that element \textit{and} which can be used to create new sub-collections of the items.\footnote{There are a few objects that do have integer subscripts, but cannot be sliced.  For instance, the \lstinline{byte} data type. However, since we do not use the byte type in this book, we are not dwelling on this distinction.} For both historical reasons and reasons of computational efficiency, index counting starts at 0.  In Python, we can also use negative numbers to count from the end backwards.  Because 0 is already used for the first index, we start from -1.  This is illustrated in the following image and examples. 

\begin{figure}[H]
    \centering
    \includegraphics[width = 2in]{ChapTechnical/DataTypes/Figures/ListIndices.png}     \caption{Schematic showing how index numbering is assigned to a list. }
    \label{fig:DataTypes:IndexNumbering}
\end{figure}
  


\begin{pyin}
l1[0]
\end{pyin}
\begin{pyout}
None
\end{pyout}
\begin{pyin}
l1[-2]
\end{pyin}
\begin{pyout}
4
\end{pyout}

We can also use these numbers to extract sub-collections. If we supply two numbers separated by a colon, then Python will return the elements that have the first index (inclusive) through the last index (exclusive). That is, we get the element corresponding to the first index, but do not get the element corresponding to the last number, rather we get the element corresponding to the last number minus 1.
\begin{pyin}
l1[1:-1]
\end{pyin}
\begin{pyout}
[4, True]
\end{pyout}

This is known as `slicing.' If you wish to \cite{slice} from some initial index to the end of the list, you can simply use a colon, but leave the final number out:
\begin{pyin}
l1[1:]
\end{pyin}
\begin{pyout}
[4, True]
\end{pyout}

Similarly, if you want to start at the beginning of the list, you can also omit the initial number, before the colon:
\begin{pyin}
l1[:-2]
\end{pyin}
\begin{pyout}
[None]
\end{pyout}

If you try to use indexes on an object that is not slicable (either to get a single object or a slice), Python will generate an exception.

\begin{pyin}
6.0[1]
\end{pyin}
\begin{pyout}
float' object is not subscriptable
\end{pyout}

Here, Python is letting you know that you cannot use the subscript (index) you supplied. It is also using `subscritble' as a stand-in for slicable.  We prefer to use the term slicable, since there are data types in Python that are subscriptable, but not slicable (i.e., \lstinline{bytearrays}), so we think the terminology we have selected is more accurate. 



\subsection{Mutable}
\setcounter{subsubsection}{-1}  % Start subsections from 0

Some (but not all) data types that hold multiple elements are mutable (see Table \ref{DataTypes:tab:types}).  If an object can be modified \textit{after} it is created, then it is called \textit{mutable}. Otherwise, it is \textit{immutable}. To understand what this means, and how it works, we need to consider how Python keeps track of variable and values.  

When you make an assignment to a variable in Python, what is happening is that Python is writing that variable somewhere in its memory, \textit{then} it points the variable to that \textit{place in memory}. If we think about assigning the value of 2.0 to the variable f1, 
\begin{pyin}
f1 = 2.0
\end{pyin}
we can envision it something like this:

\begin{center}
    \includegraphics[width = 1in]{ChapTechnical/DataTypes/Figures/Immutable1.png}
\end{center}
where the grey box is symbolizing a place in memory.

If you then assign a new value to f1, 
\begin{pyin}
f1 = "a"
\end{pyin}
Python creates a \textit{new} value in a different place in the memory, and then points f1 to that instead. 

\begin{center}
    \includegraphics[width = 1in]{ChapTechnical/DataTypes/Figures/Immutable2.png}
\end{center} 

This is the behavior of a data type that is \textit{immutable.} That is, the value of the actual object does not change.  Instead, completely new values are created and the variable now points to that new object. 

Mutable objects work differently.  If an object is mutable, then we can change the value in the same place of memory, without changing where our variable points. 

The list we have been working with is mutable.  When we assign a list to Python, 
\begin{pyin}
l1 = ["red", 4, True]
\end{pyin}
we assign the overall list a value in memory, and point the variable to it.

\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable1.png}
\end{center}
But this points to the entire \textit{list}, not the things inside it. So, if we change the elements inside it, maybe using indexes to choose which one:
\begin{pyin}
l[1] = "a"
\end{pyin}
the variable still points to the same place in memory where the list is.  In fact, even the items in the list are locations in memory that are kept track of internally in the list object.
\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable2.png}
\end{center}
And printing the list gives this new value at position 1. 
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", "a", True]
\end{pyout}
Because we can change the elements in the list, we say the list is mutable. 

Now, the reason to make this distinction between mutable and immutable is because it changes some behaviors in interesting ways. Let us first consider a case for immutable objects.
\begin{pyin}
f1 = 2.0
f2 = f1
f1 = "a"
print(f1)
\end{pyin}
\begin{pyout}
"a"
\end{pyout}
\begin{pyin}
print(f2)
\end{pyin}
\begin{pyout}
2.0
\end{pyout}
What has happened here is that we created the value 2.0, and pointed \lstinline{f1} at it.  Then we pointed \lstinline{f2} at the same value as \lstinline{f1}, meaning that \textit{we pointed \lstinline{f2} at the same object as \lstinline{f1}}. 
\begin{center}
    \includegraphics[width = 1in]{ChapTechnical/DataTypes/Figures/Immutable3.png}
\end{center} 
Then we made a new object "a" and pointed f1 at it. Now we have f1 and f2 pointing at different objects, with different values. 
\begin{center}
    \includegraphics[width = 1in]{ChapTechnical/DataTypes/Figures/Immutable4.png}
\end{center} 
This is the behavior for \textit{immutable} objects. If we look at the same set of operations for a mutable object, we will get a different behavior. 
\begin{pyin}
l1 = ["red", 4, True]
l2 = l1
\end{pyin}
Now we have created a list object, and pointed \lstinline{l1} at it.  Then we tell \lstinline{l2} to point at the same object as \lstinline{l1}. Now we once again have two variables pointing at the same object in memory. 
\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable3.png}
\end{center}
The difference occurs when we change the elements of the mutable list. In this case, changing the second element. 



\begin{pyin}
l1[1] = "a"
print(l1)
\end{pyin}
\begin{pyout}
["red", "a", True]
\end{pyout}
\begin{pyin}
print(l2)
\end{pyin}
\begin{pyout}
["red", "a", True]
\end{pyout}

The values for \textit{both} \lstinline{l1} and \lstinline{l2} have changed!  Actually, that is not quite accurate. What has happened is that we changed the element within a single \textit{mutable} object in memory, and we simply had both \lstinline{l1} and \lstinline{l2} pointed at this same object. 

\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable4.png}
\end{center}

A common problem people new to Python encounter is assigning two variables to the same mutable objects, when they think they have assigned the variable to two different objects. Then, they change the first object, and unwittingly also change the second. 

What if we don't want to point two things at the same mutable object in memory?  Well, when we make the second assignment, we need to copy the list to a new memory position, so that we have two list objects. This is done using the copy module and \lstinline{copy()} or \lstinline{copy.deepcopy()}. The difference between these is that copy makes a copy of only the outermost list (known as a `shallow copy'), while deepcopy makes a copy of the outerlist and all sublists that might exist (also known as recursive copying). We believe that the most common thing that you will want to do is deepcopy (even when not strictly nessesary), so we will just recommend you use that, unless you have a compelling reason not to. 

\begin{pyin}
l1 = ["red", 4, True]
\end{pyin}
\begin{pyin}
import copy
\end{pyin}
\begin{pyin}
l2 = copy.deepcopy(l1)
\end{pyin}
\begin{pyin}
l1[1] = "a"
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", "a", True]
\end{pyout}
\begin{pyin}
print(l2)
\end{pyin}
\begin{pyout}
["red", 4, True]
\end{pyout}
Here we can see that, even though we altered \lstinline{l1}, we didn't alter \lstinline{l2}, this is because the \lstinline{copy.deepcopy()} function created a completely new list, and then we assigned this list to \lstinline{l2}.  
\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable7.png}
\end{center}
Then we only modified the object assigned to \lstinline{l1}. 
\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Mutable8.png}
\end{center}




A final interesting behavior we consider here is with regard to the Python \lstinline{method} \lstinline{.sort()} and the \lstinline{function} \lstinline{sorted()}. These attempt to change the order of objects within a iterable object so that they follow a particular order (i.e., alphabetical).  The difference is that \lstinline{sort()} does this `in place', meaning that it tries to reorder the items within the collection.  However, this is a behavior that is only possible for mutable objects.  Since immutable objects cannot be altered after their creation, you would need to make a copy of this object that is sorted, and this is what sorted() does. In this book, in order to avoid confusion, we are going to always use sorted().  Though this is slower and less memory efficient than \lstinline{sort()}, this will not matter for the types of programs we are writing. An example of its usage is:
\begin{pyin}
l3 = [3, 5, 1]
\end{pyin}
\begin{pyin}
l4 = sorted(l3)
\end{pyin}
\begin{pyin}
print(p4)
\end{pyin}
\begin{pyout}
[1, 3, 5]
\end{pyout}

We can understand this pictorially as well:

\begin{center}
    \includegraphics[width = 2.5in]{ChapTechnical/DataTypes/Figures/Sorted1.png}
\end{center}

We note that the \lstinline{sorted()} function accepts other \cite{argument}s, the most useful of which is the keyword \lstinline{reverse}, when set to \textbf{True}, the function returns the collection sorted in the reverse order. 



\subsubsection{Growing and shrinking lists}

Because lists are mutable, we can adjust them dynamically.  We have already seen how we can change single elements of a list, but we can also add elements to the list, or remove elements. 

If we want to grow a list, we have a few options. 
\begin{itemize}
    \item [\texttt{.append()}] is a method that adds a single element to the \textit{end} of a list. If you supply a list, then you add that entire list as a single element. 

\begin{pyin}
l1 = ['red', 4, True]
\end{pyin}
\begin{pyin}
l1.append('hi')
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", 4, True, 'hi']
\end{pyout}

    You can see that the element was added to the list, and we didn't need to assign the result to a new list. This behavior is described as modifying the list `in place.'

    \item [\texttt{.insert()}] is a method with two \cite{positional_argument}s. This method adds the supplied element at the specified index. All elements that were at this index and later are shifted one index greater. As with \lstinline{append()}, we can supply only a single new element and the modification happens to the list in place. 

\begin{pyin}
l1 = ['red', 4, True]
\end{pyin}
\begin{pyin}
l1.insert('yo', 1)
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", 'yo',  4, True]
\end{pyout}
Here, the element `yo' was added at the index 1. 
    

    \item [\texttt{.extend()}] is a method that allows us to append multiple elements to the end of a list. In this case, we supply a list (or other iterable object) that will be added to the end.

\begin{pyin}
l1 = ['red', 4, True]
\end{pyin}
\begin{pyin}
l1.extend(["oh", "hi", 9])
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", 'yo',  4, True, "oh", "hi", 9]
\end{pyout}

    \item [\texttt{+}] is an operator that allows us to join to together two lists (called `concatenation').  The second list is added to the end of the first list. We can string together multiple lists this way, but concatenation results in the production of a new list, meaning that we have to assign the results to a variable, if we want to use it. \label{Page:T:DataTypes:Concatenation}

\begin{pyin}
l1 = ['red', 4, True]
\end{pyin}
\begin{pyin}
l2 = l1 + ["this", "is", "list", 2] + ["third", "list"]
\end{pyin}
\begin{pyin}
print(l2)
\end{pyin}
\begin{pyout}
["red", 'yo',  4, True, "this", "is", "list", 2, "third", "list"]
\end{pyout}
    Where we concatenated three lists together. 
\end{itemize}

\vspace{1em}
There are also ways to remove elements from a list. These are:
\begin{itemize}
    \item [\texttt{.remove()}] this method iterates through the list and removes the first instance of the supplied value. This is done on the list in place. 

    \begin{pyin}
        l2 = ["red", 'yo',  4, True, "this", "is", "list", 2, "third", "list"]
    \end{pyin}
    \begin{pyin}
        l2.remove("list")
    \end{pyin}
    \begin{pyin}
        print(l2)
    \end{pyin}
    \begin{pyout}
        l2 = ["red", 'yo',  4, True, "this", "is", 2, "third", "list"]
    \end{pyout}


    \item [\texttt{.pop()}] this removes an item from a list at a specified index.  This is done in place and the method also returns the value at that index. This is useful, because you can then assign this to a variable.

\begin{pyin}
l1 = ["red", 'yo',  4, True]
\end{pyin}
\begin{pyin}
popped = l1.pop(2)
\end{pyin}
\begin{pyin}
print(l1)
\end{pyin}
\begin{pyout}
["red", 'yo', True]
\end{pyout}
\begin{pyin}
print(popped)
\end{pyin}
\begin{pyout}
4
\end{pyout}
    
\end{itemize}




\subsection{Moving on to other collection data types}

With an understanding of iterable, slicable, and mutable, we are ready to explore the rest of our data types. If you wish to see a table that lists all the types, and if they have these properties, please refer to Table \ref{DataTypes:tab:types}.



\subsection{sequence types hold things in specific orders (list, tuple, range)}
\setcounter{subsubsection}{-1}  % Start subsections from 0

\subsubsection{\texttt{list}}
The Python \lstinline{list} was already discussed in detail above, we only mention it here, to note that it is a member of the sequence family.

\subsubsection{\texttt{tuple}}

The \lstinline{tuple}\index{data types!tuple} is closely related to the \lstinline{list}. We create tuples by placing the items you wish, separated by commas, between parentheses.

\begin{pyin}
t1 = (8.0, None, "love")
\end{pyin}
\begin{pyin}
print(t1)
\end{pyin}
\begin{pyout}
(8.0, None, "love")
\end{pyout}
When printing, Python indicates that the data type is a list by placing the values, separated by commas, between parentheses. 

The tuple can hold values of any type, and it is iterable and slicable. Where it differs from the list is that a tuple is immutable.  So, once we create it, we cannot change it, or any of the elements within it. If we try to do so, Python will generate an error. 
\begin{pyin}
t1[1] = "all you need"
\end{pyin}
\begin{pyout}
'tuple' object does not support item assignment
\end{pyout}


\subsubsection{\texttt{range}}

The last sequence type is \lstinline{range}. This data type is iterable, slicable, and immutable.  Unlike list and tuple, \lstinline{range} only holds one data type: the \text{range} type.  We realize this seems circular, but... to some extent it really is. Python was defined so that there is a specific kind of data, known as range. If we try to dig into it, we might conceive of the range type as representing an immutable sequence of integers, and so it might seem a bit like a tuple. However, it is \textit{not} a tuple. It is a sequence of numbers of the \lstinline{range} type.

This distinction is perhaps best illustrated by discussing how \lstinline{range} types are created and used. We create a range type by using the function \lstinline{range()}, and then providing it between 1 and 3 integer \cite{argument}s. The most explicit usage is with three values. The first value specifies the starting point of our number list, the second specifies the stopping point, and the third specifies the step size. One thing to note is that the start value is inclusive and the stop value is exclusive. We can also count in the positive or negative direction, depending on if the step size is positive or negative.

\begin{pyin}
r1 = range (1, 6, 2)
\end{pyin}
This will return a range object that has the numbers 1, 3, 5. 
\begin{pyin}
r2 = range (2, -9, -3)
\end{pyin}
This will return an object that has the numbers 2, -1, -4, -7.
However, these numbers are not directly represented when we call the full object.  Instead, if you print the value, you get:
\begin{pyin}
print(r1)
\end{pyin}
\begin{pyout}
range(1, 6, 2)
\end{pyout}
So, Python does not directly produce the sequence of integers. Instead, the \lstinline{range} \cite{object} contains instructions on how to produce the numbers we specified. These instructions are followed and the numbers are then generated when needed.  For this reason, \lstinline{range} is also called a `generator.' 

Because the instructions do allow the \lstinline{range} to produce numbers on demand, we can check to see if a number is in the range object:
\begin{pyin}
3 in r1
\end{pyin}
\begin{pyout}
True
\end{pyout}
Additionally, we can use indices to access the numbers, by using the \cite{index} we would use if the numbers where in a \lstinline{list} or \lstinline{tuple} data type.  Since the range places numbers in order, we can see that the second number in our sequence should be 3.
\begin{pyin}
r1[1]
\end{pyin}
\begin{pyout}
3
\end{pyout}

Returning to creating a \lstinline{range} object, if we use only two numbers they are used as the start and stop values, and the step size is assumed to be 1. Thus, we can only count forwards by 1.
\begin{pyin}
r3 = range(-1, 3)
\end{pyin}
This will produce an object with the numbers -1, 0, 1, 2. Also, if we print a range object where the increment is 1, then it will not print this value.
\begin{pyin}
print(r3)
\end{pyin}
\begin{pyout}
range(-1, 3)
\end{pyout}

If we use only a single number, then that number is used as the stop value, while the start value is assumed to be 0 and the stepsize is assumed to be 1.
\begin{pyin}
r4 = range(4)        
\end{pyin}
This will produce an object with the numbers 0, 1, 2, 3. If we print the range object, we will get the first value (0) in the print.
\begin{pyin}
print(r4)
\end{pyin}
\begin{pyout}
range(0, 4)
\end{pyout}
Finally, if you specify conditions for which no values are found, then an empty object will be returned. 
\begin{pyin}
r5 = range(1, -3, 1)
\end{pyin}
This returns an empty value, because there are no numbers between 1 and -3 that can be arrived at using a positive stepsize. Printing the range will still tell us what we asked for:
\begin{pyin}
print(r5)
\end{pyin}
\begin{pyout}
range(1, -3)
\end{pyout}
But we can verify this is empty by asking for the length of the range object:
\begin{pyin}
len(r5)
\end{pyin}
\begin{pyout}
0
\end{pyout}
And the length is zero, because the object is empty. 

Now, at first glance it might seem strange to have a range object; why not just have a list with the numbers we want? There are three answers to this question. First, it is less verbose to use range than to make a list.  For instance, getting the numbers 1 to 100 using range is simple: range(1, 101, 1). If you created a list with these numbers, it will take more typing.  Now you might ask: why does Python return a range object, rather than just create a list or tuple from the range command?  The answer is that the range data type can provide faster processing and use less memory, though it should be noted that \textit{none} of the programs we write in this book would noticeably benefit from such considerations. If you go on to write much more complex programs, however, this consideration might become important. Now you might \textit{still} think that you would want these elements as a list or tuple instead of a range type, which brings us to the third answer, and the next part of this section: Python doesn't need to have range be a \lstinline{list} or \lstinline{tuple}, because we can easily convert it to one.  In fact, we can easily convert between any of our sequence types. 

\subsubsection{Converting between sequence types}

Converting to a list or to a tuple is straightforward: we simply use \lstinline{list()} or \lstinline{tuple()} \citea{function}s on the sequence data type we wish to convert.

\begin{pyin}
t1 = tuple(['red', 4, True])
\end{pyin}
\begin{pyin}
t2 = tuple(range(1,10,2))
\end{pyin}
\begin{pyin}
l1 = list((8.0, None, "love"))
\end{pyin}
\begin{pyin}
l2 = list(range(3, -9, -3))
\end{pyin}
The first two these creates a tuple, first from list and then from a range.  The last two create lists, first from a tuple and then from a range. 

While, in some special cases, it is possible to create a range object from a list of tuple, we are not going to encounter the need to do so.  Thus, in the interest of both clarity and space, we are not going to dsicuss how to do so. 



\subsection{Set types are used to ensure we don't duplicate items}

Imagine that you and a friend are going to host a party and you are going to meet up to shop.  However, you both independently make a grocery list beforehand.  You have some different ideas regarding what you want to buy:

\textbf{you}: beer, chips, salsa, paper plates

\textbf{friend}: soda, paper plates, ice cream

Now, when you meet up to shop, what will you do? You probably don't need double the amount of paper plates, so you will just combine the lists, keeping only the unique elements. 

\textbf{two buy}: beer, soda, ships, salsa, paper plates, ice cream.


In mathematical parlance, you have just created a new set by forming the union of your individual sets. 

This is the fundamental behavior of sets: they contain only unique elements (elements do not repeat) and one can perform set operations on them, like union. 

In Python, there are two built in types with these behaviors: set and frozen set. 

The simplest way to make objects of this data type is to make a list and convert it to them using \lstinline{set()} of \lstinline{frozenset()} functions.\footnote{The type \lstinline{set} can also be created using curly brackets, though we will not use this approach in our book. } Because a set cannot repeat values, any duplicate values will be discarded. 

\begin{pyin}
s = set(["one", 2.0, 3, 2.0])
\end{pyin}
\begin{pyin}
print(s)
\end{pyin}
\begin{pyout}
{"one", 2.0, 3}
\end{pyout}
\begin{pyin}
fs = frozenset([1, 2, 3.0, 2.0])
\end{pyin}
\begin{pyin}
print(fs)
\end{pyin}
\begin{pyout}
{1, 2, 3}
\end{pyout}

In the example of the \lstinline{frozenset} you will have \textit{either} 2 or 2.0, but not both.  The set treats numbers with the same value as the same, even if they have different type. However, cannot know which data type will be retained because sets are unordered: the order items are held and added is not preserved. This also means that, if you are implementing these examples on your own computer, you might get a slightly different ordering of elements.  That is \lstinline{print(fs)} could have also returned \lstinline{\{2, 1, 3\}}, or any other order of the elements.

One once we have these sets, we can perform set operations on them. In Python, there are 4 built-in operators to use between sets: union, intersection, difference, symmetric difference.


\vspace{1em}\noindent
\textbf{union (\lstinline{|})}:\footnote{If you are not used to coding, you might struggle to find out how to make this "vertical bar." If you have a US keyboard, then it is on the same key as the `$\backslash$' character.} this operation returns a set that contains all the unique elements across both sets. This can be thought of as combining the two sets, and the removing duplicate copies of any elements:
\begin{pyin}
s | fs
\end{pyin}
\begin{pyout}
{1, 2, 3.0, 'one'}
\end{pyout}
Where you can see we have only the elements that were in common, and only one copy of each. 

\vspace{1em}\noindent
\textbf{intersection \lstinline{(\&)}:} this operation returns a set that contains all the elements present in \textit{both} lists.
\begin{pyin}
s \& fs
\end{pyin}
\begin{pyout}
{2.0, 3}
\end{pyout}



\vspace{1em}\noindent
\textbf{difference \lstinline{(-)}}: this operation returns those elements that are present in the first set, but not the second:
\begin{pyin}
s - fs
\end{pyin}
\begin{pyout}
{"one"}
\end{pyout}
\begin{pyin}
fs - s
\end{pyin}
\begin{pyout}
frozenset{1}
\end{pyout}
Here you can also see that the type of set returned is dictated by the type of set for the first set used. 


\vspace{1em}\noindent
\textbf{symmetric difference (\lstinline{\^})}: This operation returns a set that contains all the elements that are present in only a single set. Thus, it can be thought of as performing the union of both possible differences: (s - fs) | (s - fs)

\begin{pyin}
s \lstinline{\^} fs
\end{pyin}
\begin{pyout}
{1, "one"}
\end{pyout}

All of the above are useful operations, but do come at the cost of some of the other useful behaviors we have seen so far. Namely, sets are not slicable. So we cannot refer to elements by their index, if we try, we will get an error. 

\begin{pyin}
s[1]
\end{pyin}
\begin{pyout}
'set' object is not subscriptable
\end{pyout}
\begin{pyin}
fs[0]
\end{pyin}
\begin{pyout}
'frozenset' object is not subscriptable
\end{pyout}

However, both sets and frozensets are iterable, and so we can use functions like \lstinline{in} and \lstinline{len()}.

The difference between (\lstinline{set}) and (\lstinline{frozenset}) is that the first is mutable and the second immutable. 

We will not explicitly encounter sets in the narrative portion of the book, though you might in the real world. 



%
% Text and strings
%
\subsection{The text type lets us represent language}
\setcounter{subsubsection}{-1}  % Start subsections from 0

One type of data that we have used, but not discussed are \cite{string}s.  In Python, the name of this type is \lstinline{str}\index{data type!string}, and is created by placing text between two ' or ''' characters or between a series of three of these (i.e., ''' or """). The reason for having either single characters or triple characters is that the triple characters allow for declaration of strings that span multiple lines. 

\begin{pyin}
s1 = 'hi'
\end{pyin}

\begin{pyin}
s2 = "hello"
\end{pyin}
\begin{pyin}
s3 = '''Hello there.
Nice to meet you'''
\end{pyin}
\begin{pyin}
s4 = """Howdy there partner.
You from these parts?"""
\end{pyin}
All of these create valid strings, which we can see by printing them.
\begin{pyin}
s1
\end{pyin}
\begin{pyout}
hi
\end{pyout}
\begin{pyin}
print(s2)
\end{pyin}
\begin{pyout}
hello
\end{pyout}
\begin{pyin}
print(s3)
\end{pyin}
\begin{pyout}
Hello there. Nice to meet you.
\end{pyout}
\begin{pyin}
print(s4)
\end{pyin}
\begin{pyout}
Howdy there partner. You from these parts?
\end{pyout}
Here you can see that the line breaks used when we used the triple characters are preserved in the output. 

We can also create a string from another type of object, using the \lstinline{str()} function.  

\begin{pyin}
s5 = str(2.0)
\end{pyin}
\begin{pyin}
s5
\end{pyin}
\begin{pyout}
`2.0'
\end{pyout}
Where the fact that the 2.0 is held between single quotations indicates it is a string, not a float.  We can see this by trying to perform an operation that is only valid for numerical types, such as exponentiation. 

\begin{pyin}
s5**2
\end{pyin}
\begin{pyout}
unsupported operand type(s) for ** or pow(): 'str' and 'int'
\end{pyout}

In some sense, strings can be thought of as a tuple of characters.  We say this, because the string type is iterable and slicable, but not mutable---just as are tuples. Because they are iterable, one can use operators like \lstinline{in} and \lstinline{len()} on them. 

\begin{pyin}
len(s4)
\end{pyin}
\begin{pyout}
29
\end{pyout}
Because they are slicable, we can get information by index.

\begin{pyin}
s4[2]
\end{pyin}
\begin{pyout}
'l'
\end{pyout}
Where we have to remember that indexing starts at 0.

And because they are immutable, we cannot change them once they are created.

\begin{pyin}
s4[2] = "e"
\end{pyin}
\begin{pyout}
str' object does not support item assignment
\end{pyout}

Also, because strings act like they are sequences, we can use the concatenation (\lstinline{+}) and repetition (\lstinline{*}) operators.\footnote{For a discussion of these please see the entry on lists on page \ref{Page:T:DataTypes:Concatenation}} 

\begin{pyin}
s5 = s1 + s2
\end{pyin}
\begin{pyin}
s5
\end{pyin}
\begin{pyout}
'hi hello'
\end{pyout}
\begin{pyin}
s6 = s1 * 6
\end{pyin}
\begin{pyin}
s6
\end{pyin}
\begin{pyout}
'hi hi hi hi hi hi'
\end{pyout}
At this point, you might be wondering exactly how strings differ from tuples. Perhaps the most important difference is that, while tuples can hold any data type (including strings), strings can only hold an ordered collection of characters. By characters, we mean the objects used to represent text, such as those used by ASCII. The default encoding for Python is UTF-8. If we try to explicitly pass some other object within a string, the entire sequence simply gets interpreted as a string. 

\begin{pyin}
"I have int(2) apples."
\end{pyin}
\begin{pyout}
'I have int(2) apples.'
\end{pyout}

But this brings us to another important difference for strings: strings do some interpreting of the characters that they are given.  That is, what you type into a string is not always what you get back. This (and generally working with text) is going to be so important that we have an entire chapter on it (Chapter \ref{Text}).  So, if you want to know how to work with strings more, please go there. 



\subsection{The dictionary type allows us to map, or connect, values}
\setcounter{subsubsection}{-1}  % Start subsections from 0

Our final built-in data type is the dictionary; called \lstinline{dict} in Python. The dictionary mutable. It is iterable, but has some unique behaviors. It is also non-slicable, but can be made to respond to indexes. Both of these somewhat unusual behaviors will be explained below. 

A dictionary is made up of a collection of \cite{key}:\cite{value} pairs. Perhaps the most useful way to conceive of this is as two paired lists. There is a list of keys and a list of values.  Each entry in our list of keys is associated with exactly one entry in our list of values. We say that the values are `mapped' onto the keys. 

The list of values is quite similar to a list in that each item can be any data type, including another dictionary. However, the list of keys can only hold string type data. Thus, the keys can be thought of as names, in some sense. 

There are several ways to make a dictionary. The one most often used is to place key:value pairs, separated by commas, between curly brackets. When doing so, the keys need to be placed between quotations to indicate they are strings. 

\begin{pyin}
    d1 = \{"integer":7, "float":3.14, "list":["hi", "there", True]\}
\end{pyin}
This dictionary contains three entries: an \lstinline{int}, a \lstinline{float}, and a \lstinline{list}.

One can also make a dictionary using the \lstinline{dict()} \cite{function} in two different ways. First, one gives a list of keys and values separated by the = operator. 

\begin{pyin}
d2 = dict(name='John', age=25, city='New York')
\end{pyin}

In this case, we do not need to explicitly make the key a string---the thing we type will be automatically converted to a string by Python.

In yet another approach to making a dictionary, one provides a list of two-item lists or tuples.  Python converts the two-item lists into key-value pairs.
\begin{pyin}
d3 = dict([('name', 'Ben'), ('age', 45), ('city', 'State College')])
\end{pyin}

There is one last way, which is to convert two lists into key:value pairs, using the \lstinline{zip()} function, and converting the zip object to a dictionary.

\begin{pyin}
z4 = zip(["greeting", "year", "month"], ["hi", 2023, "June"])
\end{pyin}
\begin{pyin}
d4 = dict(z4)
\end{pyin}
Though this goes through an intermediate stage of using \lstinline{zip()},\footnote{Perhaps the best explaination of how \lstinline{zip()} works can be found in Section\ref{Synchronizing_iteration_through_multiple_collections_using_zip} of the narrative section of this book.} it is an efficient way to take two longs lists and convert them into a dictionary. 

We also wish to note that you can merge dictionaries using the unpacking operator (\lstinline{**}).  The unpacking operator will return key:value pairs for a dictionary, and so we can use this behavior to combine dictionaries:
\begin{pyin}
merged_dict = {**d1, **d2}
\end{pyin}
\begin{pyin}
print(merged_dict)
\end{pyin}
\begin{pyout} 
{'integer':7, 'float':3.14, 'list':['hi', 'there', True], 'name':'John', 'age':25, 'city':'New York'}
\end{pyout}
Where we can see that the two dictionaries are now combined into a single dictionary, and we can see how Python prints dictionaries. 

At this point, it might seem like dictionaries are just pointlessly complex lists. However, as we start to describe how information in accessed from dictionaries, we hope their utility will become apparent. 

\subsubsection{Accessing information from dictionaries}

If you have a dictionary, then you can get lists of both the key and the values, using the \lstinline{keys()} and \lstinline{values()} \cite{methods} respectively. 

\begin{pyin}
d2.keys()
\end{pyin}
\begin{pyout}
['name', 'age', 'city']
\end{pyout}
\begin{pyin}
d2.values()
\end{pyin}
\begin{pyout}
['John', 25, 'New York']
\end{pyout}

The most standard means to use dictionaries is to use the key to access the value:

\begin{pyin}
d1["float"]
\end{pyin}
\begin{pyout}
3.14
\end{pyout}

And we can combine these two to access values, by iterating through keys...
\begin{pyin}
for k in d2.keys():
    print(d2[k])
\end{pyin}
\begin{pyout}
John
25
New York
\end{pyout}

There are two things to note here. First, the default behavior for interating through a dictionary is to return the keys, so you don't actually need to specify that you want the keys.  However, this does help with readability.  Second, one could just iterate through the list returned by the \lstinline{.values()} method, however, doing so side-steps the utility of the mapping supplied by the dictionary. If you just want a list of values, you might as well use a list, which is sliceable as well. 

But this implies that there is some advantage to the mapping.  And there is. The nice thing about a dictionary is that you have one set of things that you know the name for (keys) and they can point to things that you don't know the values for (values).  

In science, imagine that you were fitting data to a line, and you get a slope and intercept.  We could hold these in a dictionary, that has keys of "slope" and "intercept." Now we have a single object that holds all the information for the line, rather than two variables. Also, even though the values of slope and intercept may change, we will always want to know what these values are. Thus, in some sense you can think of dictionaries of collections of variables, that will point to values you want to use later. 

We will see more clear examples in this book, but you hopefully start to see the power and utility of dictionaries. 



\section{Common types added by libraries}

The above discussion was limited to those types that are built-in to Python.  However, it is often the case that we will be using types that are not built in. We will keep each of the following extremely brief.  However, it is worth a brief word on why we introduce each of these. 

\section{Numpy arrays allow for vectorization of data}
The Numpy array looks a bit like a list, in that it is iterable, slicable, and mutable.  However, there are a few key differences between it and the list. 
\begin{itemize}
    \item \textit{it is not straightforward for numpy arrays hold any type of data. They can only hold a single data type at a time.} While mixing data types can be done, we will not show examples of how to do this, because we think the power of arrays is most obvious when they hold a single type of data.  

    \item \textit{You can apply an operation to every element of a numpy array without using a for loop.} Thus, if you had an array (\lstinline{a1}) that held the values 1, 2, 3 and you wanted to multiply all of them by 5, you could simple use \lstinline{a1*5}.  

    \item \textit{It is must slower to add an element to a Numpy array than to a Python list. } This is important to understand, because there are times you want to grow a collection and so if you ultimately want the final collection to be a numpy array, it is going to be significantly more efficient to grow the collection as a list, and then convert it to a numpy array. 

\end{itemize}





\section{the Pillow Image type lets you store, manipulate, and display images}

There will be times you want to manipulate images or show them in results (i.e., as an insert in a plot). Pillow Image type can hold the data from an image and allow you to do these operations. It handles bitmaps and so is storing information pixel by pixel.  It is iterable, slicable, but not mutable. 

\section{the Plotly figure lets you build and display plots of data.} I doubt we have to convince you why plotting data is a good idea, so we will just point out that we are going to teach you to use the Plotly library, and the final product when doing so is a Figure object that holds the data, and all other information required to produce a well-formatted figure. This is discussed much more in the technical chapter on Plotly (Chapter \ref{ch:plotly}).



\end{document}

% \section{datetime lets you store time and date information}
% If you were going to try to represent a specific moment in time, you would want to create an object that represented years, months, days, hours, minutes, seconds, and so on. But not only would you need to represent, these, you would need rules for understanding how to subtract them. You would need to know that years can be subtracted as regular numbers, but that they sometime hold 365 days and sometimes 366.  You would need to remember that some months have 30 days, while other are 31, and one is 28 or 29. You need to know that there are 24 hours in a day, but that we sometimes use just 1-12 to represent them, that there are 60 minutes in and hour, and 60 seconds in a minute.

% The \lstinline{datetime} type does this for you. If you read the chapter on this type, you will see that it allows you to flexibly move between representations of time, and perform math on these representations. 