Lecture 02
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.
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
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.
Python is built around an object-oriented class system—every value is an object with a type, including numbers, strings, and containers.
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).
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().
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.
Python’s common built-in value types are similar, but each value is an individual object rather than a vector.
| 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 |
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.
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).
R integers are 32-bit and overflow to NA (with a warning), Python integers have arbitrary precision.
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).
R’s double and Python’s float use binary floating-point representations, so many decimal values cannot be represented exactly.
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).
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.
Both languages promote to the more “general” type when mixing numeric types (logical / bool → integer / int → double / float → complex),
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).
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.
R strings can contain newlines directly (or via \n), in Python a literal must be triple quoted (""" or ''') to span multiple lines.
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.
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,
Atomic vectors have flat underlying storage—nested calls to c() combine their contents rather than create a nested structure,
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.
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.)
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.
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.
R uses the as.*() family of functions for explicit conversion, Python uses the type constructors (int(), float(), str(), bool(), etc.),
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.
R uses NA to represent missing values in its data structures.
What may not be obvious is that there are different NAs for the different atomic types.
As NAs represent missing values (most) calculations using them return a missing value.
A useful mental model for NAs 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.
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.
NA, NaN, and InfNULL in RNULL represents the absence of an object or value. Unlike NA, it has length zero and is removed when combined with an atomic vector.
None in PythonBase 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.
Unlike NA, None does not propagate through calculations (it raises an error) and it has no type-specific variants.
nan and inf are available (as floats) via float() or the math module. Note that division by zero is always an error in Python (even for floats) rather than producing inf.
Testing is done with math.isnan(), math.isinf(), and math.isfinite() and, as in R, nan is not equal to itself (or anything else).
What is the type of the following R vectors? Explain why they have that type.
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?
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).
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).
Since lists are mutable the stored values can be changed, removed, or added to (in place).
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.
To make an independent copy of a Python list, use .copy():
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.
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.
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,
Next week we will see how numpy arrays bring R-style vectorized operations to Python.
| 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, … |
| Operation | R | Python |
|---|---|---|
| exponent | ^ |
** |
| integer division | %/% |
// |
| modulus | %% |
% |
| and / or / not | &, |, ! |
and, or, not |
| value equality | ==, identical() |
== |
| object identity | — | is |
| membership | %in% |
in |
| string concat | paste0() |
+ |
| interpolation | glue::glue() |
f-strings |
| raw strings | r"(...)" |
r"..." |
R uses 1-based indexing and negative indexes exclude elements, Python uses 0-based indexing and negative indexes count from the end.
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.
Without running the code, predict the output (or error) of each of the following in both R and Python.
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.
Sta 523 - Fall 2026