# Type conversion

r_bridge converts values automatically when they cross the Python ↔ R boundary.
All conversions are **bidirectional**.

## Python → R

| Python type | R type |
|---|---|
| `None` | `NULL` |
| `bool` | `logical(1)` |
| `int` | `integer(1)` |
| `float` | `numeric(1)` |
| `str` | `character(1)` |
| `complex` | `complex(1)` |
| `float('nan')` | `NaN` |
| `float('inf')` | `Inf` |
| `list` (homogeneous bools) | `logical` vector |
| `list` (homogeneous ints) | `integer` vector |
| `list` (homogeneous floats) | `numeric` vector |
| `list` (homogeneous strings) | `character` vector |
| `dict` | named `list()` |
| `numpy.ndarray` 1-D | atomic vector (dtype determines R type) |
| `numpy.ndarray` 2-D | `matrix` |
| `pandas.DataFrame` | `data.frame` |
| `pandas.Series` | named vector |
| `pandas.Categorical` | `factor` |
| `datetime.datetime` | `POSIXct` (UTC) |
| `datetime.date` | `Date` |

## R → Python

| R type | Python type |
|---|---|
| `NULL` | `None` |
| `logical(1)` | `bool` |
| `logical(n>1)` | `numpy.ndarray(bool)` |
| `integer(1)` | `int` |
| `integer(n>1)` | `numpy.ndarray(int32)` |
| `numeric(1)` | `float` |
| `numeric(n>1)` | `numpy.ndarray(float64)` |
| `character(1)` | `str` |
| `character(n>1)` | `list[str]` |
| `list` (named) | `dict` |
| `list` (unnamed) | `list` |
| `data.frame` | `pandas.DataFrame` |
| `matrix` | `numpy.ndarray` (2-D) |
| `factor` | `pandas.Categorical` |
| `Date` | `datetime.date` |
| `POSIXct` / `POSIXlt` | `datetime.datetime` (UTC) |
| `NA` | `float('nan')` / `pd.NA` / `None` (context-dependent) |
| `NaN` | `float('nan')` |
| `Inf` / `-Inf` | `float('inf')` / `float('-inf')` |

## Overriding the default conversion

Pass `result_type_hint` to any `get`, `call`, or `eval` call to force a
specific output type:

```python
# default: numpy array
r.call("c", 1, 2, 3)

# force a plain Python list
r.call("c", 1, 2, 3, result_type_hint="list")

# force a scalar
r.call("sum", [1, 2, 3], result_type_hint="scalar")

# skip deserialisation entirely
r.call("mean", [1, 2, 3], result_type_hint="raw")
# → {'__type__': 'numeric_scalar', 'value': 2.0}
```

## Special value tagging

Values that have no direct JSON representation are transmitted as tagged dicts:

```json
{"__type__": "integer",         "value": 42}
{"__type__": "nan"}
{"__type__": "inf",             "sign": 1}
{"__type__": "complex",         "r": 1.0, "i": 2.0}
{"__type__": "na",              "na_type": "real"}
{"__type__": "factor",          "levels": ["a","b"], "codes": [0,1], "ordered": false}
{"__type__": "matrix",          "nrow": 2, "ncol": 3, "data": [...], "byrow": true}
{"__type__": "dataframe",       "columns": {...}}
{"__type__": "posixct",         "iso8601": "2026-03-21T00:00:00Z"}
{"__type__": "date",            "iso8601": "2026-03-21"}
```

Custom tags can be registered — see [Advanced](advanced.md).
