---
title: "Types in<br/>R & Python"
subtitle: "Lecture 02"
author: "Dr. Colin Rundel"
footer: "Sta 523 - Fall 2026"
format:
  revealjs:
    theme: slides.scss
    transition: fade
    slide-number: true
    self-contained: true
execute:
  echo: true
  warning: true
engine: knitr
---


```{r setup}
#| message: False
#| warning: False
#| include: False
options(
  width=80
)
```

```{python py_setup}
import math
```


# Type systems

## Dynamic typing

Both R and Python are *dynamically typed* languages - the type of a value is determined at runtime and a variable (name) is not restricted to a single type.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
x = 1
typeof(x)
x = "one"
typeof(x)
```
:::

::: {.column width='50%'}
```{python}
x = 1
type(x)
x = "one"
type(x)
```
:::
::::

. . .

Where the languages differ is in their fundamental unit of data,

* in R (almost) everything is a *vector*

* in Python individual values are separate *objects* that can be grouped into *container* objects

::: {.aside}
*Dynamic* describes when types are checked; it does not mean that values lack types or that every operation between types is allowed.
:::


## Vectors in R

The fundamental building block for data in R is the vector—a collection of related values.

. . .

R has two types of vectors:

* atomic vectors (*vectors*) — homogeneous collections with a single underlying type (e.g. `logical`, `integer`, `double`, or `character`).

* generic vectors (*lists*) — heterogeneous collections that can contain any R object, including other lists, allowing hierarchical or tree-like structures.

. . .

There are no scalars in R - a single value like `1` is just a vector of length 1.

::: {.small}
```{r}
length(1)
length(c(1, 2, 3))
```
:::


## Objects in Python

Python is built around an object-oriented class system—*every* value is an object with a type, including numbers, strings, and containers.

:::: {.columns .small}
::: {.column width='50%'}
```{python}
1
type(1)
"one"
type("one")
```
:::

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

. . .

Individual objects can be collected into general-purpose *containers* such as `list`, which can be heterogeneous and nested. Base Python has no direct equivalent to R's atomic vectors - that role is filled by `numpy` arrays (more on these next week).


# Basic types

## R's atomic types

R has six atomic vector types, we can check the type of any object using `typeof()`, `mode()` (a higher level grouping of types), or `class()`.

<br/>

| `typeof()`  |  `mode()`  | `class()`  | Example        |
|:------------|:-----------|:-----------|:---------------|
| logical     |  logical   | logical    | `TRUE`         |
| double      |  numeric   | numeric    | `1.5`, `2`     |
| integer     |  numeric   | integer    | `1L`, `1:3`    |
| character   |  character | character  | `"hello"`      |
| complex     |  complex   | complex    | `1+2i`         |
| raw         |  raw       | raw        | `as.raw(255)`  |

There are additional types in R, e.g. `list`, `closure`, `environment`, etc. which we will see in the coming lectures. See `?typeof` for more information.

::: {.aside}
We will talk more about the difference between `typeof()`, `mode()`, and `class()` later.
:::


## Python's basic types

Python's common built-in value types are similar, but each value is an individual object rather than a vector.

<br/>

| Type      | `type()`   | Example        |
|:----------|:-----------|:---------------|
| boolean   | `bool`     | `True`         |
| integer   | `int`      | `1`            |
| float     | `float`    | `1.5`, `2.0`   |
| complex   | `complex`  | `1+2j`         |
| string    | `str`      | `"hello"`      |
| none      | `NoneType` | `None`         |

::: {.aside}
There is also `bytes` (similar to R's `raw`), as well as container types such as `list`, `dict`, and `set`. We will introduce lists later in this lecture.
:::


## Logical / boolean values

:::: {.columns .small}
::: {.column width='50%'}
```{r}
typeof(TRUE)
typeof(FALSE)
TRUE + TRUE
sum(c(TRUE, FALSE, TRUE))
```
:::

::: {.column width='50%'}
```{python}
type(True)
type(False)
True + True
sum([True, False, True])
```
:::
::::

. . .

In both languages boolean values behave like the integers `1` and `0` when used in arithmetic. In Python this is explicit - `bool` is a subclass of `int`.

::: {.aside}
R lets you use `T` and `F` as shortcuts for `TRUE` and `FALSE` - this is bad practice as these are just global variables which can be overwritten (e.g. `T = FALSE`). Python's `True` and `False` are keywords and cannot be reassigned.
:::


## Numeric values

:::: {.columns .small}
::: {.column width='50%'}
```{r}
typeof(1.5)
typeof(7)
typeof(7L)
typeof(1:3)
```
:::

::: {.column width='50%'}
```{python}
type(1.5)
type(7)
type(7.0)
type(7.)
```
:::
::::

. . .

The *default* numeric type differs between the languages - a bare literal like `7` is a `double` in R but an `int` in Python. In R, integer literals require an `L` suffix, in Python floats require a decimal point (or an exponent, e.g. `1e3`).


## Integers

R integers are 32-bit and overflow to `NA` (with a warning), Python integers have arbitrary precision.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
.Machine$integer.max
.Machine$integer.max + 1L
.Machine$integer.max + 1
2^100
```
:::

::: {.column width='50%'}
```{python}
2**31 - 1
2**31
2**100
```
:::
::::

. . .

In practice this rarely matters in R since the default numeric type is `double`, but it is one reason why R's `integer` type is used sparingly (e.g. for indexing and counts). Note that `numpy` and `pandas` behave more like R here, as they use fixed width integers (e.g. `int64`).


## Floating-point precision

R's `double` and Python's `float` use binary floating-point representations, so many decimal values cannot be represented exactly.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
0.1 + 0.2
0.1 + 0.2 == 0.3
all.equal(0.1 + 0.2, 0.3)
```
:::

::: {.column width='50%'}
```{python}
0.1 + 0.2
0.1 + 0.2 == 0.3

import math
math.isclose(0.1 + 0.2, 0.3)
```
:::
::::


## Complex numbers

::: {.medium}
Both languages have a built-in complex type - the main difference is the literal syntax, R uses `i` for the imaginary unit while Python uses `j` (the engineering convention).
:::

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
z = 1+2i; typeof(z)
c(Re(z), Im(z), Mod(z))
```
:::

::: {.column width='50%'}
```{python}
z = 1+2j; type(z)
(z.real, z.imag, abs(z))
```
:::
::::

. . .

::: {.medium}
With a real negative input, R's `sqrt()` returns `NaN` while Python's `math.sqrt()` raises an error. R requires complex input, whereas Python's `**` operator promotes the result to complex.
:::

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
sqrt(-1)
sqrt(-1+0i)
(-1)^0.5
```
:::

::: {.column width='50%'}
```{python}
#| error: true
math.sqrt(-1)
```
```{python}
#| error: true
sqrt(-1+0j)
```
```{python}
(-1) ** 0.5
```
:::
::::


## Numeric promotion / coercion

Both languages *promote* to the more "general" type when mixing numeric types (`logical` / `bool` → `integer` / `int` → `double` / `float` → `complex`),

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
typeof(1L + 1.5)
typeof(TRUE + 1L)
typeof(1L + 2i)
```
:::

::: {.column width='50%'}
```{python}
type(1 + 1.5)
type(True + 1)
type(1 + 2j)
```
:::
::::

. . .

For integer inputs, division (`/`) returns a `double` / `float` rather than an integer in both languages,

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
4L / 2L
typeof(4L / 2L)
```
:::

::: {.column width='50%'}
```{python}
4 / 2
type(4 / 2)
```
:::
::::


## Type predicates

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
is.integer(1)
is.integer(1L)
is.double(1)
is.numeric(1)
is.numeric(1L)
is.character("1")
```
:::

::: {.column width='50%'}
```{python}
isinstance(1, int)
isinstance(1.0, int)
isinstance(1.0, float)
isinstance(True, int)
isinstance("1", str)
```
:::
::::

. . .

`is.numeric()` checks the *mode* (`double` or `integer`) while the others check the *type*. Python's `isinstance()` respects class inheritance (hence `True` is an `int`).


# Strings

## String literals

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
typeof("hello")
typeof('world')
"abc'123"
'abc"123'
"abc\"123"
```
:::

::: {.column width='50%'}
```{python}
type("hello")
type('world')
"abc'123"
'abc"123'
"abc\"123"
```
:::
::::

::: {.medium}
In both languages single or double quotes are fine (the opening and closing quote must match). Quote characters can be included by escaping or by using the non-matching quote.
:::



## Multi-line strings

R strings can contain newlines directly (or via `\n`), in Python a literal must be triple quoted (`"""` or `'''`) to span multiple lines.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
x = "line one
line 'two'"
x
cat(x)
```
:::

::: {.column width='50%'}
```{python}
x = """line one
line 'two'"""
x
print(x)
```
:::
::::

::: {.aside}
Triple quoted strings are particularly common in Python as [docstrings](https://peps.python.org/pep-0257/), we will see these when we discuss functions.
:::


## String interpolation

Python's f-strings (3.6+) evaluate arbitrary expressions inside `{}`. R has no built-in equivalent - `paste()` / `paste0()` are the base approach and the `glue` package provides similar `{}` interpolation.

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
x = c(1, 2, 3)
paste0("x has ", length(x), " elements")
glue::glue("x has {length(x)} elements")
glue::glue("From {min(x)} to {max(x)}")
```
:::

::: {.column width='50%'}
```{python}
x = [1, 2, 3]
"x has " + str(len(x)) + " elements"
f"x has {len(x)} elements"
f"From {min(x)} to {max(x)}"
```
:::
::::


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



# Coercion

## Atomic vectors are homogeneous

R's atomic vectors must contain values of a single type, so combining different types with `c()` forces *coercion* to the most general type present,

:::: {.columns .small}
::: {.column width='50%'}
```{r}
c(1, "Hello")
c(FALSE, 3L)
```
:::

::: {.column width='50%'}
```{r}
c(1.2, 3L)
c(FALSE, "Hello")
```
:::
::::

. . .

Atomic vectors have flat underlying storage—nested calls to `c()` combine their contents rather than create a nested structure,

::: {.small}
```{r}
c(1, c(2, c(3)))
```
:::

. . .

Base Python has no direct counterpart. Lists can mix types and be nested, like R's lists; `numpy` arrays are closer because they are homogeneous and choose a common data type. We will see these later.


## Implicit coercion in R

::: {.medium}
R is quite liberal about coercion - builtin operators and functions (e.g. `+`, `&`, `log()`, etc.) will generally attempt to coerce values to an appropriate type for the given operation (numeric for math, logical for logical, etc.)
:::

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
3.1 + 1L
5 + FALSE
log(TRUE)
```
:::

::: {.column width='50%'}
```{r}
TRUE & 7
FALSE | !5
TRUE == "TRUE"
1 == "1"
```
:::
::::

. . .

::: {.medium}
Arithmetic with strings is the exception, R will not coerce a character value to a number for math,
:::

::: {.small}
```{r}
#| error: true
#| output-location: column
"1" + 1
```
:::


## Implicit coercion in Python

Python is more conservative: it promotes values within the numeric types (`bool` → `int` → `float` → `complex`) but generally does not implicitly convert between strings and numbers. Equality comparisons between these types return `False`, while unsupported ordering and arithmetic operations raise an error.

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

::: {.column width='50%' .fragment}
```{python}
#| error: true
"abc" > 5
```
```{python}
#| error: true
"abc" + 5
```
```{python}
"abc" + str(5)
```
:::
::::


## Operator overloading in Python

Do be aware that Python has a number of useful / idiosyncratic overloads for some of the basic operators - depending on the types involved the same operator can do very different things.

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

::: {.column width='50%' .fragment}
```{python}
#| error: true
"abc" ** 2
```
```{python}
#| error: true
[1, 2] * [3, 4]
```
:::
::::


## Explicit coercion

R uses the `as.*()` family of functions for explicit conversion, Python uses the type constructors (`int()`, `float()`, `str()`, `bool()`, etc.),

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
as.logical(5.2)
as.integer(pi)
as.double("7.2")
as.integer("2.1")
as.integer("one")
```
:::

::: {.column width='50%'}
```{python}
bool(5.2)
int(3.14159)
float("7.2")
```
```{python}
#| error: true
int("2.1")
```
:::
::::

## Coercion to logical / bool

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
#| warning: true
as.logical(0)
as.logical("TRUE")
as.logical("T")
as.logical("yes")
as.logical("")
```
:::

::: {.column width='50%'}
```{python}
bool(0)
bool("False")
bool("")
bool([])
bool(None)
```
:::
::::

. . .

::: {.medium}
Python uses the idea of *truthiness* (`0`, `0.0`, `""`, `None`, and empty containers are *falsy*, almost everything else is *truthy*) throughout the language, e.g. in conditionals. For character input, R recognizes only `"TRUE"`, `"true"`, `"True"`, `"T"` (and their `FALSE` counterparts); other strings become `NA`.
:::


# Missing & special values

## Missing values in R

R uses `NA` to represent missing values in its data structures. 

What may not be obvious is that there are different `NA`s for the different atomic types.

:::: {.columns .small}
::: {.column width='30%'}
```{r}
NA
NA+1
NA+1L
c(NA,"")
```
:::

::: {.column width='30%'}
```{r}
typeof(NA)
typeof(NA+1)
typeof(NA+1L)
typeof(c(NA,""))
```
:::

::: {.column width='40%'}
```{r}
typeof(NA_character_)
typeof(NA_real_)
typeof(NA_integer_)
typeof(NA_complex_)
```
:::
::::


## NA stickiness

As `NA`s represent missing values (most) calculations using them return a missing value.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
1 + NA
1 / NA
NA * 5
```
:::

::: {.column width='50%'}
```{r}
sqrt(NA)
3^NA
sum(c(1, 2, 3, NA))
```
:::
::::

. . .

Aggregation / summarization functions (e.g. `sum()`, `mean()`, `sd()`, etc.) will often have an `na.rm` argument which *removes* the missing values from the calculation.

::: {.small}
```{r}
sum(c(1, 2, 3, NA), na.rm = TRUE)
mean(c(1, 2, 3, NA), na.rm = TRUE)
```
:::


## NAs are not always sticky

A useful mental model for `NA`s is to consider them as an unknown value that could take any of the possible values for a given type.

. . .

For numbers or characters this isn't helpful, but for a logical value it must either be `TRUE` or `FALSE` which is relevant for certain calculations.

. . .

:::: {.columns .small}
::: {.column width='50%'}
```{r}
TRUE & NA
FALSE & NA
```
:::

::: {.column width='50%' .fragment}
```{r}
TRUE | NA
FALSE | NA
```
:::
::::


## Special values (double)

R also has the non-finite values defined by the IEEE floating point standard (these are not unique to R): `NaN` (not a number), `Inf`, and `-Inf`.

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
-1 / 0
0 / 0
1/0 + 1/0
```
:::

::: {.column width='50%' .fragment}
```{r}
Inf - Inf
NaN / NA
NaN * NA
```
:::
::::

. . .

These are all `double` values, but their coercion behavior is not the same as other doubles,

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
typeof(Inf)
typeof(NaN)
```
:::

::: {.column width='50%'}
```{r}
as.integer(Inf)
as.integer(NaN)
```
:::
::::


## Testing for `NA`, `NaN`, and `Inf`

:::: {.columns .xsmall}
::: {.column width='33%'}
```{r}
is.na(NA)
is.na(NaN)
is.na(Inf)
```
:::

::: {.column width='33%'}
```{r}
is.nan(NA)
is.nan(NaN)
is.nan(Inf)
```
:::

::: {.column width='33%'}
```{r}
is.finite(NA)
is.finite(NaN)
is.finite(Inf)
```
:::
::::

. . .

Note that `is.na()` is `TRUE` for `NaN` but `is.nan()` is `FALSE` for `NA`. Comparisons involving `NA` or `NaN` always return `NA` so these predicates must be used instead of `==`.

::: {.small}
```{r}
NA == NA
NaN == NaN
c(1, NA, 3) == NA
```
:::


## `NULL` in R

`NULL` represents the absence of an object or value. Unlike `NA`, it has length zero and is removed when combined with an atomic vector.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
typeof(NULL)
length(NULL)
is.null(NULL)
```
:::

::: {.column width='50%'}
```{r}
c(1, NULL, 2)
list(1, NULL, 2)
```
:::
::::


## `None` in Python

Base Python has no built-in missing-value type. `None` is a singleton object (of type `NoneType`) used to represent "no value," making it closer to R's `NULL` than to `NA`.

:::: {.columns .small}
::: {.column width='50%'}
```{python}
print(None)
type(None)
x = None
x is None
```
:::

::: {.column width='50%' .fragment}
```{python}
#| error: true
None + 1
```
```{python}
[1, None, "a"]
```
:::
::::

. . .

Unlike `NA`, `None` does not propagate through calculations (it raises an error) and it has no type-specific variants.

::: {.aside}
Because `None` is a singleton, test for it with `is None` or `is not None` rather than `==` or `!=`. Missing values are instead handled by the data libraries - `numpy` uses `nan`, `pandas` has `pd.NA` and `NaT`, etc. More on these later in the course.
:::


## Non-finite values in Python

`nan` and `inf` are available (as `float`s) via `float()` or the `math` module. Note that division by zero is *always* an error in Python (even for floats) rather than producing `inf`.

:::: {.columns .xsmall}
::: {.column width='33%'}
```{python}
#| error: true
1 / 0
```
```{python}
float("inf")
float("nan")
```
:::

::: {.column width='33%' .fragment}
```{python}
math.inf
math.nan
math.inf - math.inf
```
:::

::: {.column width='33%' .fragment}
```{python}
math.isnan(math.nan)
math.isinf(-math.inf)
math.nan == math.nan
```
:::
::::

. . .

Testing is done with `math.isnan()`, `math.isinf()`, and `math.isfinite()` and, as in R, `nan` is not equal to itself (or anything else).


## Exercise 1

:::: {.columns .small}
::: {.column width='50%'}
#### Part 1

What is the type of the following R vectors? Explain why they have that type.

```{r}
#| eval: false
c(1, NA+1L, "C")
c(1L / 0, NA)
c(1:3, 5)
c(3L, NaN+1L)
c(NA, TRUE)
c(1L, NULL, NA)
```
:::

::: {.column width='50%'}
#### Part 2

Without running the code, what is the value (and type) of each of the following Python expressions?

```{python}
#| eval: false
True + 1.5
"1" * 3
int("3") + float("1.5")
bool("False")
int(True) / True
```
:::
::::

#### Part 3

Considering R's four most commonly used atomic types (`logical`, `integer`, `double`, and `character`), what is the implicit conversion hierarchy from highest to lowest priority? How does this compare to Python's numeric promotion rules?

```{r}
#| echo: false
countdown::countdown(minutes = 5)
```


# Python lists

## Lists

Python lists are *ordered*, *mutable* sequences that can contain objects of different types (very similar to lists in R, which we will see in a few lectures).

:::: {.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)
len(x)
2 in x
x + [3, 4, 5]
```
:::
::::

::: {.aside}
See Python's documentation for [common sequence operations](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations) and [mutable sequence operations](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types).
:::


## Indexing

Elements are accessed using `[]` with **0-based indexing**, negative indexes count from the end, and ranges of values can be specified using slices (`start:stop` where `stop` is *exclusive*).

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

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

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

::: {.aside}
We will cover slicing (and R's subsetting) in much more detail next week.
:::


## Mutability

Since lists are mutable the stored values can be changed, removed, or added to (in place).

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

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

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

::: {.aside}
`.append()`, `.insert()`, and `.pop()` are all examples of class methods (for lists) - more on these later.
:::


# Assignment & identity

## Assignment & copies

::: {.medium}
Ordinary R vectors use copy-on-modify semantics: after `y = x`, modifying `y` does not change `x`. Reference-style objects such as environments are exceptions. 

In Python, assignment binds a *name* to an *object*: both names refer to the same object, so modifying a mutable object through one name is visible through the other.
:::

:::: {.columns .xsmall}
::: {.column width='50%'}
```{r}
x = c(1, 2, 3)
y = x
y[1] = 100
```
```{r}
x
y
```
:::

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


## Shallow copies

::: {.medium}
To make an independent copy of a Python list, use `.copy()`:
:::

::: {.xsmall}
```{python}
x = [1, 2, 3]
y = x.copy()
y[0] = 100
x
```
:::

. . .

::: {.medium}
A list's `.copy()` method copies the outer list, but nested mutable objects remain shared.
:::

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

y[0][0] = 100
x
y
```
:::

::: {.aside}
Use `copy.deepcopy()` when the nested objects must also be copied.
:::


## Identity vs equality

::: {.medium}
Because multiple Python names can refer to the same object, Python distinguishes between *identity* (`is` - are these the same object?) and *equality* (`==` - do they have the same value?). The `id()` function returns an integer representing an object's unique identity.
:::

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

:::: {.columns .xsmall}
::: {.column width='50%'}
```{python}
x is y
x is z
x == z
```
:::

::: {.column width='50%' .fragment}
```{python}
id(x)
id(y)
id(z)
```
:::
::::

. . .

::: {.medium}
R's `identical()` tests strict value equality (including type and attributes), not object identity. For example, `identical(1L, 1)` is `FALSE` while `1L == 1` is `TRUE`. With ordinary copy-on-modify objects, identity rarely matters in R.
:::

# Comparing R & Python

## Scalars vs vectors

For numeric data, the biggest conceptual difference is that R represents even a single atomic value as a vector, while base Python distinguishes individual numeric values from containers. This affects how arithmetic on collections behaves,

:::: {.columns .small}
::: {.column width='50%'}
```{r}
x = c(1, 2, 3)
length(x)
x * 2
x + x
length(1)
```
:::

::: {.column width='50%'}
```{python}
#| error: true
x = [1, 2, 3]
len(x)
x * 2
x + x
len(1)
```
:::
::::

. . .

Next week we will see how `numpy` arrays bring R-style vectorized operations to Python.


## Type summary

::: {.medium}
| Concept        | R                                     | Python                               |
|:---------------|:--------------------------------------|:-------------------------------------|
| boolean        | `logical` (`TRUE`, `FALSE`)           | `bool` (`True`, `False`)             |
| integer        | `integer` (`1L`)                      | `int` (`1`)                          |
| real           | `double` (`1`, `1.5`)                 | `float` (`1.0`, `1.5`)               |
| complex        | `complex` (`1i`)                      | `complex` (`1j`)                     |
| text           | `character` (`"a"`)                   | `str` (`"a"`)                        |
| type of        | `typeof()`, `class()`, `is.*()`       | `type()`, `isinstance()`             |
| conversion     | `as.integer()`, `as.character()`, ... | `int()`, `str()`, ...                |
| missing        | `NA` (typed)                          | library-specific sentinels            |
| absent / null  | `NULL`                                | `None`                               |
| non-finite     | `Inf`, `-Inf`, `NaN`                  | `math.inf`, `-math.inf`, `math.nan`  |
| containers     | atomic vectors, lists                 | `list`, ...                          |
:::


## Operator summary

::: {.medium}
| Operation        | R                                             | Python                |
|:-----------------|:----------------------------------------------|:----------------------|
| exponent         | `^`                                           | `**`                  |
| integer division | `%/%`                                         | `//`                  |
| modulus          | `%%`                                          | `%`                   |
| and / or / not   | `&`, <code>&#124;</code>, `!`                 | `and`, `or`, `not`    |
| value equality   | `==`, `identical()`                           | `==`                  |
| object identity  | —                                             | `is`                  |
| membership       | `%in%`                                        | `in`                  |
| string concat    | `paste0()`                                    | `+`                   |
| interpolation    | `glue::glue()`                                | f-strings             |
| raw strings      | `r"(...)"`                                    | `r"..."`              |
:::

::: {.aside}
These are not exact equivalents: R's `&` and <code>&#124;</code> are vectorized and evaluate both sides, whereas Python's `and` and `or` short-circuit and return one of their operands. R also has the short-circuit operators `&&` and <code>&#124;&#124;</code>, which we will discuss with control flow next time.
:::


## Indexing

R uses **1-based** indexing and negative indexes *exclude* elements, Python uses **0-based** indexing and negative indexes count from the *end*.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
x = c("a", "b", "c", "d")
x[1]
x[4]
x[-1]
x[2:3]
```
:::

::: {.column width='50%'}
```{python}
x = ["a", "b", "c", "d"]
x[0]
x[3]
x[-1]
x[1:3]
```
:::
::::

. . .

Note that R's `2:3` *includes* both endpoints while Python's `1:3` slice *excludes* the stop value - both select the 2nd and 3rd elements here.

::: {.aside}
Much more on subsetting next week.
:::


## Exercise 2

Without running the code, predict the output (or error) of each of the following in *both* R and Python.

:::: {.columns .small}
::: {.column width='50%'}
```{r}
#| eval: false
x = c(1, 2, 3)
y = x
y[1] = "a"
x
y
```
```{r}
#| eval: false
TRUE + TRUE == 2
"2" == 2
as.integer("2") + 1L
0.1 + 0.2 == 0.3
```
```{r}
#| eval: false
c(1, 2.5, NA)
sum(c(1, 2.5, NA))
```
:::

::: {.column width='50%'}
```{python}
#| eval: false
x = [1, 2, 3]
y = x
y[0] = "a"
x
y
```
```{python}
#| eval: false
True + True == 2
"2" == 2
int("2") + 1
0.1 + 0.2 == 0.3
```
```{python}
#| eval: false
[1, 2.5, None]
sum([1, 2.5, None])
```
:::
::::

```{r}
#| echo: false
countdown::countdown(minutes = 5)
```


## Takeaways

* R's atomic vectors are homogeneous and vectorized; base Python uses individual objects and general-purpose containers.

* Types determine which operations are valid and when coercion occurs.

* `NA`, `NULL`, `None`, and `nan` represent different ideas.

* R vectors usually copy on modify; Python mutable objects can be shared by multiple names.
