---
title: "Compatibility with dplyr"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Compatibility with dplyr}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

linelist philosophy is to prevent you from accidentally losing valuable data, but to otherwise be totally transparent and not interfere with your workflow.

One popular ecosystem for data science workflow is the tidyverse and we are going the extra mile to ensure linelist compatibility with the tidyverse. All dplyr verbs are thoroughly tested in the `tests/test-compat-dplyr.R` file.

```{r}
library(linelist)
library(dplyr)

data("measles_hagelloch_1861", package = "outbreaks")

x <- make_linelist(
  measles_hagelloch_1861,
  id = "case_ID",
  date_onset = "date_of_prodrome",
  age = "age",
  gender = "gender"
)

head(x)
```

## Verbs operating on rows

linelist does not modify anything regarding the behaviour for row-operations. As such, it is fully compatible with dplyr verbs operating on rows out-of-the-box.
You can see in the following examples that linelist does not produce any errors, warnings or messages and its tags are conserved through dplyr operations on rows.

### `dplyr::arrange()` ✅

```{r}
x |>
  arrange(case_ID) |>
  head()
```

### `dplyr:distinct()` ✅

```{r}
x |>
  distinct() |>
  head()
```

### `dplyr::filter()` ✅

```{r}
x |>
  filter(age >= 10) |>
  head()
```

### `dplyr::slice()` ✅

```{r}
x |>
  slice(5:10)

x |>
  slice_head(n = 5)

x |>
  slice_tail(n = 5)

x |>
  slice_min(age, n = 3)

x |>
  slice_max(age, n = 3)

x |>
  slice_sample(n = 5)
```

## Verbs operating on columns

During operations on columns, linelist will:

- stay invisible and conserve tags if no tagged column is affected by the operation
- trigger `lost_tags_action()` if tagged columns are affected by the operation

### `dplyr::mutate()` ✓ (partial)

There is an incomplete compatibility with `dplyr::mutate()` in that simple renames without any actual modification of the column don't update the tags. In this scenario, users should rather use `dplyr::rename()`

Although `dplyr::mutate()` is not able to leverage to full power of linelist tags, linelist objects behave as expected the same way a data.frame would:

```{r}
# In place modification doesn't lose tags
x |>
  mutate(age = as.integer(age)) |>
  head()

# New columns don't affect existing tags
x |>
  mutate(major = age >= 18) |>
  head()

# .keep = "unused" generate expected tag loss conditions
x |>
  mutate(edad = age, .keep = "unused") |>
  head()
```

### `dplyr::pull()` ✅

`dplyr::pull()` returns a vector, which results, as expected, in the loss of the linelist class and tags:

```{r}
x |>
  pull(age)
```

### `dplyr::relocate()` ✅

```{r}
x |>
  relocate(date_of_prodrome, .before = 1) |>
  head()
```


### `dplyr::rename()` & `dplyr::rename_with()` ✅

`dplyr::rename()` is fully compatible out-of-the-box with linelist, meaning that tags will be updated at the same time that columns are renamed. This is possibly because it uses `names<-()` under the hood, which linelist provides a custom `names<-.linelist()` method for:

```{r}
x |>
  rename(edad = age) |>
  head()

x |>
  rename_with(toupper) |>
  head()
```

### `dplyr::select()` ✅

`dplyr::select()` is fully compatible with linelist, including when columns are renamed in a `select()`:

```{r}
# Works fine
x |>
  select(case_ID, date_of_prodrome, gender, age) |>
  head()

# Tags are updated!
x |>
  select(case_ID, date_of_prodrome, gender, edad = age) |>
  head()
```

## Verbs operating on groups ✘

Groups are not yet supported. Applying any verb operating on group to a linelist will silently convert it back to a data.frame or tibble.

## Verbs operating on data.frames

### `dplyr::bind_rows()` ✅

```{r}
dim(x)

dim(bind_rows(x, x))
```

### `dplyr::bind_cols()` ✘

`bind_cols()` is currently incompatible with linelist:

- Tags from the second element are lost
- Warnings are produced about lost tagged, even for tags that are not actually lost

```{r}
bind_cols(
  suppressWarnings(select(x, case_ID, date_of_prodrome)),
  suppressWarnings(select(x, age, gender))
) |>
  head()
```

### Joins ✘

Joins are currently not compatible with linelist as tags from the second element are silently dropped.

```{r}
full_join(
  suppressWarnings(select(x, case_ID, date_of_prodrome)),
  suppressWarnings(select(x, case_ID, age, gender))
) |>
  head()
```

## Verbs operating on multiple columns

### `dplyr::pick()` ✘

`pick()` makes tidyselect functions work in usually tidyselect-incompatible functions, such as:

```{r}
x |>
  dplyr::arrange(dplyr::pick(ends_with("loc"))) |>
  head()
```

As such, we could expect it to work with linelist custom tidyselect-like function: `has_tag()` but it's not the case since `pick()` currently strips out all attributes, including the `linelist` class and all tags.
This unclassing is documented in `?pick`:

> `pick()` returns a data frame containing the selected columns for the current group.

