---
title: "Strings"
subtitle: "Ch. 14"
format: 
  html:
    code-fold: TRUE
toc: TRUE
theme: flatly
warning: FALSE
message: FALSE
---

## Creating strings

```{r}
library(tidyverse)
library(babynames)
```

![Notice the str_ format](str.png)

```{r}
string1 <- "This is a string"
string2 <- 'If I want to include a "quote" inside a string, I use single quotes'
```

### Escapes

```{r}
double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"
backslash <- "\\"
```

Beware that the printed representation of a string is not the same as the string itself because **the printed representation shows the escapes** (in other words, when you print a string, you can copy and paste the output to recreate that string). To see the raw contents of the string, use `str_view()`:

```{r}
x <- c(single_quote, double_quote, backslash)
x

str_view(x)
```
Other common special characters include `\n` (new line) and `\t` (tab). A few less common ones are below. Notice how `str_view` differs from `print()`. 

```{r}
x <- c("one\ntwo", "one\ttwo", "\u00b5", "\U0001f604")
x

str_view(x)
```

### Exercise

1. Create a string that contains the following values:

`He said "That's amazing!"`

## Common String Problem

You have some text you wrote that you want to combine with strings from a data frame. 

**For example, you might combine â€œHelloâ€ with a name variable to create a greeting.** We'll do this below.  First, here's `str_c`.

`str_c()` takes any number of vectors as arguments and returns a character vector:
 
```{r}
str_c("x", "y")

str_c("x", "y", "z")

str_c("Hello ", c("John", "Susan"))
```

Use `str_c` with `mutate`. 

```{r}
df <- tibble(name = c("Flora", "David", "Terra", NA))

df |> mutate(greeting = str_c("Hi ", name, "!"))
```

A more economical alternative is `str_glue()`.  You give it a single string that has a special feature: anything inside {} will be evaluated like itâ€™s outside of the quotes:

```{r}
df |> mutate(greeting = str_glue("Hi {name}!"))
```

## Summaries

`str_c()` and `str_glue()` work well with `mutate()` because their output is the same length as their inputs. What if you want a function that works well with `summarize()`, i.e. something that always returns a single string? Thatâ€™s the job of `str_flatten()`: it takes a character vector and combines each element of the vector into a single string:

```{r}
str_flatten(c("x", "y", "z"))

str_flatten(c("x", "y", "z"), ", ")

str_flatten(c("x", "y", "z"), ", ", last = ", and ")

```

`str_flatten()` works well with `summarize`

```{r}
df <- tribble(
  ~ name, ~ fruit,
  "Carmen", "banana",
  "Carmen", "apple",
  "Marvin", "nectarine",
  "Terence", "cantaloupe",
  "Terence", "papaya",
  "Terence", "mandarin"
)

df |>
  group_by(name) |> 
  summarize(fruits = str_flatten(fruit, ", "))
```

## Extracting data from strings

Itâ€™s very common for multiple variables to be crammed together into a single string. Here's how to extract them.

```{r eval = FALSE}
df |> separate_longer_delim(col, delim)
df |> separate_longer_position(col, width)
df |> separate_wider_delim(col, delim, names)
df |> separate_wider_position(col, widths)
```

### Separating into rows

```{r}
# the number of compoennts varies from row to row
df1 <- tibble(x = c("a,b,c", "d,e", "f"))

df1 |> 
  separate_longer_delim(x, delim = ",")
```

To separate along fixed widths ...

```{r}
df2 <- tibble(x = c("1211", "1314", "21"))
df2 |> 
  separate_longer_position(x, width = 2)
```

### Separating into columns

Separating a string into columns tends to be most useful when there are a fixed number of components in each string, and you want to spread them into columns. 

```{r}
# give names to the new columns created
df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("code", "edition", "year")
  )
```

### Exercise

1. What would a `NA` in the `names` vector above do?


`separate_wider_position()` works a little differently because you typically want to specify the width of each column. So you give it a named integer vector, where the name gives the name of the new column, and the value is the number of characters it occupies. 

```{r}
df4 <- tibble(x = c("202215TX", "202122LA", "202325CA")) 

df4 |> 
  separate_wider_position(
    x,
    widths = c(year = 4, age = 2, state = 2)
  )
```

### Diagnosing widening problems

See the [text](https://r4ds.hadley.nz/strings#diagnosing-widening-problems)

## Letters

`str_length()` tells you the number of letters in the string:

```{r}
str_length(c("a", "R for data science", NA))
```

You could use this with `count()` to find the distribution of lengths of US babynames and then with `filter()` to look at the longest names, which happen to have 15 letters:

```{r}
babynames |>
  count(length = str_length(name), wt = n)

babynames |> 
  filter(str_length(name) == 15) |> 
  count(name, wt = n, sort = TRUE)
```

### Subsetting

You can extract parts of a string using `str_sub(string, start, end)`, where start and end are the positions where the substring should start and end. The start and end arguments are inclusive, so the length of the returned string will be end - start + 1:

```{r}
x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
```

### Exercise

1. What do negative numbers do in `str_sub()`?


Here's how to find the first and last letters of each name in `babynames`.
```{r}
babynames |> 
  mutate(
    first = str_sub(name, 1, 1),
    last = str_sub(name, -1, -1)
  )
```

### Exercises

1.When computing the distribution of the length of babynames, why did we use wt = n?

2. Use `str_length()` and `str_sub()` to extract the middle letter from each baby name. What will you do if the string has an even number of characters?

3. Are there any major trends in the length of babynames over time? What about the popularity of first and last letters?