---
title: "(A very brief)<br/>Introduction to Python"
subtitle: "Lecture 02"
author: "Dr. Colin Rundel"
footer: "Sta 663 - Spring 2025"
format:
  revealjs:
    theme: slides.scss
    transition: fade
    slide-number: true
    self-contained: true
execute:
  echo: true
---

```{r setup, message=FALSE, warning=FALSE, include=FALSE}
options(
  width=80
)
```

```{r hooks, include=FALSE}
local({
  hook_old <- knitr::knit_hooks$get("error")  # save the old hook
  knitr::knit_hooks$set(error = function(x, options) {
    x = sub("## \n## Detailed traceback:\n.*$", "", x)
    x = sub("Error in py_call_impl\\(.*?\\)\\: ", "", x)
    hook_old(x, options)
  })
})
```


# Basic types

## Type system basics

Like R, Python is a dynamically typed language but the implementation details are very different as it makes extensive use of an object oriented class system for implementation

:::: {.columns .small}
::: {.column width='50%'}
```{python}
True
1
1.0
1+1j
"string"
```
:::

::: {.column width='50%' .fragment}
```{python}
type(True)
type(1)
type(1.0)
type(1+1j)
type("string")
```
:::
::::

::: {.aside}
Note - all of these types are exclusively *scalar* values, more on vector types to come.
:::

## Dynamic types

As a dynamically typed langiage most basic operations will attempt to coerce object to a consistent type appropriate for the operation.

:::: {.columns .small}
::: {.column width='50%'}
**Boolean operations:**
```{python}
1 and True
0 or 1
not 0
not (0+0j)
not (0+1j)
```
:::

::: {.column width='50%' .fragment}
**Comparisons:**
```{python error=TRUE}
5. > 1
5. == 5
1 > True
(1+0j) == 1
"abc" < "ABC"
```
:::
::::

## Mathematical operations



:::: {.columns}
::: {.column width='50%'}
```{python}
1 + 5
1 + 5.
1 * 5.
True * 5
(1+0j) - (1+1j)
```
:::

::: {.column width='50%' .fragment}
```{python}
5 / 1.
5 / 2
5 // 2
5 % 2
7 ** 2
```
:::
::::

Note that the default numeric type in python is an *integer* rather than a *double*, but floats will generally take precedence over integers.

## Coercion errors

Python is not quite as liberal as R when it comes to type coercion,

::: {.small}
```{python error=TRUE}
"abc" > 5
"abc" + 5
```
:::
. . .

::: {.small}
```{python}
"abc" + str(5)
```
:::

. . .

::: {.small}
```{python error=TRUE}
"abc" ** 2
```
:::

. . .

::: {.small}
```{python error=TRUE}
"abc" * 3
```
:::


## Casting

Explicit casting between types can be achieved using the types as functions, e.g. `int()`, `float()`, `bool()`, or `str()`.

:::: {.columns .small}
::: {.column width='50%'}
```{python error=TRUE}
float("0.5")
float(True)
int(1.1)
int("2")
int("2.1")
```
:::

::: {.column width='50%' .fragment}
```{python error=TRUE}
bool(0)
bool("hello")
str(3.14159)
str(True)
```
:::
::::

## Variable assignment

When using Python it is important to think of variable assignment as the process of attaching a name to an object (literal, data structure, etc.)

:::: {.columns}
::: {.column width='50%'}
```{python}
x = 100
x

x = "hello"
x

ß = 1 + 2 / 3
ß
```
:::

::: {.column width='50%' .fragment}
```{python}
a = b = 5
```{python}
a
b
```
:::
::::

. . .

::: {.aside}
Python variable names can be of any length, and must only contain letters, numbers and underscores. They may not begin with a number nor conflict with language keywords. Python 3 supports a subset of unicode for variable names.
:::

## string literals

Strings can be defined using several different approaches,

::: {.small}
```{python}
'allows embedded "double" quotes'
"allows embedded 'single' quotes"
```
:::

. . .

strings can also be triple quoted, using single or double quotes, which allows the string to span multiple lines and contain quote characters,

::: {.small}
```{python}
"""line one
line "two"
line 'three'"""
```
:::

::: aside
This last example are are particularly common with [docstrings](https://peps.python.org/pep-0257/)
:::

## Multiline strings

A single `\` can also be used to span a long string over multiple lines without including the newline

```{python}
"line one \
not line two \
not line three"
```

## f strings

As of Python 3.6 you can use f strings for string interpolation formatting (as opposed to `%`-formatting and the `format()` method).

```{python}
x = [0,1,2,3,4]
f"{x[::2]}"
f'{x[0]}, {x[1]}, ...'
f"From {min(x)} to {max(x)}"
f"{x} has {len(x)} elements"
```

::: {.aside}
See [PEP 498](https://peps.python.org/pep-0498/) for more details
:::


## raw strings

One other special type of string literal you will come across are raw strings (prefixed with `r`) - these are like regular strings except that `\` is treated as a literal character rather than an escape character.

:::: {.columns}
::: {.column width='50%'}
```{python}
print("ab\\cd")
print("ab\ncd")
print("ab\tcd")
```
:::

::: {.column width='50%'}
```{python}
print(r"ab\\cd")
print(r"ab\ncd")
print(r"ab\tcd")
```
:::
::::



## Special values

::: {.medium}
Base Python does not support missing values. Non-finite floating point values are available but somewhat awkward to use. There is also a `None` type which is similar in usage and functionality to `NULL` in R.
:::

:::: {.columns .small}
::: {.column width='50%'}
```{python error=TRUE}
1/0
1./0
nan
float("nan")
```
:::

::: {.column width='50%' .fragment}
```{python error=TRUE}
inf
float("-inf")
5 > float("inf")
5 > float("-inf")
```
:::
::::

. . .

:::: {.columns .small}
::: {.column width='50%'}
```{python error=TRUE}
None
```
:::

::: {.column width='50%'}
```{python}
type(None)
```
:::
::::



# Sequence types

## lists

Python lists are   *heterogenous*, *ordered*, *mutable* containers of objects (they are very similarly to lists in R).

:::: {.columns .small}
::: {.column width='50%'}
```{python}
[0,1,1,0]
[0, True, "abc"]
[0, [1,2], [3,[4]]]
```
:::

::: {.column width='50%' .fragment}
```{python}
x = [0,1,1,0]
type(x)

y = [0, True, "abc"]
type(y)
```
:::
::::


## Common operations

:::: {.columns}
::: {.column width='50%'}
```{python}
x = [0,1,1,0]
```

```{python}
2 in x
2 not in x
x + [3,4,5]
x * 2
```
:::

::: {.column width='50%' .fragment}
```{python}
len(x)
max(x)
x.count(1)
x.count("1")
```
:::
::::

See [here](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations) and [here](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types) for a more complete listing of functions and methods.


## list subsetting 

Elements of a list can be accessed using the `[]` method, element position is indicated using **0-based indexing**, and ranges of values can be specified using slices (`start:stop:step`).

::: {}
```{python}
x = [1,2,3,4,5,6,7,8,9]
```
:::

. . .

:::: {.columns}
::: {.column width='50%'}
```{python}
x[0]
x[3]
x[0:3]
```
:::

::: {.column width='50%' .fragment}
```{python}
x[3:]
x[-3:]
x[:3]
```
:::
::::

::: {.aside}
When using a slice the default values are `0:length(x):1`
:::

## slice with a step

::: {}
```{python}
x = [1,2,3,4,5,6,7,8,9]
```
:::

. . .

:::: {.columns}
::: {.column width='50%'}
```{python}
x[0:5:2]
x[0:6:3]
x[0:len(x):2]
```
:::

::: {.column width='50%' .fragment}
```{python}
x[0::2]
x[::2]
x[::-1]
```
:::
::::

## Exercise 1

Come up with a slice that will subset the following list to obtain the elements requested:

```{python}
d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

* Select only the odd values in this list

* Select every 3rd value starting from the 2nd element.

* Select every other value, in reverse order, starting from the 9th element.

* Select the 3rd element, the 5th element, and the 10th element



## mutability

Since lists are mutable the stored values can be changed,

```{python}
x = [1,2,3,4,5]
```

:::: {.columns}
::: {.column width='50%'}
```{python}
x[0] = -1
x

del x[0]
x

x.append(7)
x
```
:::


::: {.column width='50%' .fragment}
```{python}
x.insert(3, -5)
x

x.pop()
x

x.clear()
x
```
:::
::::

## lists, assignment, and mutability

When assigning an object a name (`x = ...`) you do not necessarily end up with an entirely new object, see the example below where both `x` and `y` are names that are attached to the same underlying object in memory.

```{python}
x = [0,1,1,0]
y = x

x.append(2)
```

. . .

What are the values of `x` and `y` now?

. . .

```{python}
x
y
```



## lists, assignment, and mutability

To avoid this we need to make an explicit copy of the object pointed to by `x` and point to it with the name `y`.

```{python}
x = [0,1,1,0]
y = x.copy()

x.append(2)
```

. . .

What are the values of `x` and `y` now?

. . .

```{python}
x
y
```


## Nested lists

Now lets look at happens when we have a list inside a list and make a change at either level.

::: {.small}
```{python}
x = [0, [1,2], [3,4]]
y = x
z = x.copy()

x[0] = -1
x[1][0] = 5
```
:::

. . .

What are the values of `x`, `y`, and `z` now?

. . . 


::: {.small}
```{python}
x
y
z
```
:::


## Value unpacking

lists (and other sequence types) can be unpacking into multiple variables when doing assignment,

:::: {.columns .small}
::: {.column width='50%'}
```{python}
x, y = [1,2]
```
```{python}
x
y
```
```{python}
x, y = [1, [2, 3]]
```
```{python}
x
y
```
:::

::: {.column width='50%' .fragment}
```{python}
x, y = [[0,1], [2, 3]]
```
```{python}
x
y
```
```{python}
(x1,y1), (x2,y2) = [[0,1], [2, 3]]
```
```{python}
x1
y1
x2
y2
```
:::
::::

## Extended unpacking


It is also possible to use extended unpacking via the `*` operator (in Python 3)

:::: {.columns}
::: {.column width='50%'}
```{python error=TRUE}
x, *y = [1,2,3]
```
```{python}
x
y
```
:::

::: {.column width='50%' .fragment}
```{python error=TRUE}
*x, y = [1,2,3]
```
```{python}
x
y
```
:::
::::

. . .

<br/>

If `*` is not used here, we get an error:

```{python error=TRUE}
x, y = [1,2,3]
```

## tuples

Python tuples are *heterogenous*, *ordered*, **immutable** containers of values. 

They are nearly identical to lists except that their values cannot be changed - you will most often encounter them as a tool for packaging multiple objects when returning from a function.


```{python}
(1, 2, 3)
(1, True, "abc")
(1, (2,3))
(1, [2,3])
```

## tuples are immutable

```{python error=TRUE}
x = (1,2,3)
```

```{python error=TRUE}
x[2] = 5

del x[2]

x.clear()
```

## Casting sequences 

It is possible to cast between sequence types

```{python}
x = [1,2,3]
y = (3,2,1)
```

```{python}
tuple(x)
list(y)
```

```{python}
tuple(x) == x
list(tuple(x)) == x
```


## Ranges

These are the last common sequence type and are a bit special - ranges are a *homogenous*, *ordered*, *immutable* "containers" of **integers**.

:::: {.columns}
::: {.column width='50%'}
```{python}
range(10)
range(0,10)
range(0,10,2)
range(10,0,-1)
```
:::

::: {.column width='50%' .fragment}
```{python}
list(range(10))
list(range(0,10))
list(range(0,10,2))
list(range(10,0,-1))
```
:::
::::

. . .

What makes ranges special is that `range(1000000)` does not store 1 million integers in memory but rather just three 3$^*$.


## Strings as sequences

In most of the ways that count we can think about Python strings as being *ordered* *immutable* containers of unicode characters and so much of the sequence type functionality we just saw can be applied to them.

::: {.small}
```{python}
x = "abc"
```
:::

:::: {.columns .small}
::: {.column width='50%'}
```{python}
x[0]
x[-1]
x[2:]
x[::-1]
```
:::

::: {.column width='50%' .fragment}
```{python}
len(x)
"a" in x
"bc" in x
x[0] + x[2] 
```
:::
::::

. . .

::: {.small}
```{python error=TRUE}
x[2] = "c"
```
:::

## String Methods

Because string processing is a common and important programming task, the `str` class implements a number of additional methods for these specific tasks. See [here](https://docs.python.org/3/library/stdtypes.html#string-methods) a list of methods.

```{python}
x = "Hello world! 1234"
```

. . .

:::: {.columns}
::: {.column width='50%'}
```{python}
x.find("!")
x.isalnum()
x.isascii()
x.lower()
```
:::

::: {.column width='50%' .fragment}
```{python}
x.swapcase()
x.title()
x.split(" ")
"|".join(x.split(" "))
```
:::
::::



## Exercise 2

String processing - take the string given below and apply the necessary methods to create the target string.

**Source:**
```{python eval=FALSE}
"the quick  Brown   fox Jumped  over   a Lazy  dog"
```

**Target:**
```{python eval=FALSE}
"The quick brown fox jumped over a lazy dog."
```

::: {.aside}
Hardcoding w/ magic numbers is perfectly acceptable here.
:::


## Set and Mapping types

We will discuss sets (`set`) and dictionaries (`dict`) in more detail next week.

Specifically we will discuss the underlying data structure behind these types (as well as lists and tuples) and when it is most appropriate to use each.
