19 문자열

19.1 들어가기

지금까지 세부사항을 배우지 않고 다량의 문자열을 사용했었다. 이제 문자열에 대해 깊이 들어가서 다루는 법을 배우고, 강력한 문자열 조작 도구를 익힐 시간이다.

여기에서 문자열과 문자 벡터에 관한 세부사항부터 살펴볼 것이다. 그 후 쉽고 편하게 사용할 수 있는 문자열 함수들에 대해 배울 것이다. 그 다음으로는, 문자열 내 패턴을 기술하는 도구인 정규표현식의 기초에 대해 논의할 것이다. 그리고 나서 문자열에서 데이터를 추출하는 도구들을 사용할 것이다. 이 장의 마지막에서는 영어가 아닌 다른 언어의 텍스트를 다룰 때 잘못될 수 있는 것들에 대해 짧게 논의하면서 마친다.

이 장은 다른 두 장과 짝을 이룬다. 정규표현식은 범위가 큰 주제이기 때문에, 20 장에서 다시 다룰 것이다. ?? 장에서 문자열을 데이터 분석 관점이 아닌 프로그래밍 관점에서 더 살펴볼 것이다.

19.1.1 준비하기

이 장에서 stringr 패키지의 함수를 사용할 것이다. 베이스 R 에도 같은 기능을 하는 (grepl(), gsub(), regmatches() 과 같은) 함수들이 있지만, stringr 은 일관성있도록 설계되었기 때문에 사용하기 더 쉽다. babynames 데이터에는 조작하기 재미있는 문자열들이 있다. 함께 살펴보자.

stringr 에 있는 함수의 이름은 모두 str_ 로 시작하기 때문에, stringr 함수를 사용하고 있는지 쉽게 알아차릴 수 있다. 공통된 str_ 접두사는 RStudio 이용자에게 특히 유용하다. str_ 을 타이핑하면 자동완성을 불러와서 모든 stringr 함수를 볼 수 있기 때문이다.

19.2 문자열 생성하기

지금까지 앞에서 문자열을 여러번 생성했지만, 세부사항을 논의하지는 않았었다. 우선, 문자열을 생성하는 기초적인 방법에는 두가지가 있다: 작은따옴표(')나 큰따옴표(")를 사용하는 것이다. 다른 언어와는 달리 두 동작에 차이가 없다. 하지만, tidyverse 스타일 가이드에서는 일관성 측면에서, 여러 개의 " 를 포함하는 문자열을 생성하는 경우를 제외하고는 항상 " 를 사용할 것을 추천한다.

string1 <- "문자열이다"
string2 <- '문자열 내에 "인용문"이 포함된 경우, 나는 작은 따옴표를 사용한다'

따옴표 닫는 것을 잊어 버린 경우, 연속문자(continuation character)인 + 가 나타난다.

> "닫는 따옴표가 없는 문자열이다
+    
+    
+ 도와줘요 갇혔어요

이 같은 일이 발생했다면 이스케이프키를 누르고 다시 시도하라!

19.2.1 이스케이프

작은따옴표 문자나 큰따옴표 문자를 문자열에 포함하려면 ‘벗어나기 (escape)’ 위해 \ (이스케이프 키)를 사용할 수 있다.

double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"

같은 원리로 역슬래시 문자를 포함하려면 "\\" 과 같이 두 번 입력해야 한다.

backslash <- "\\"

문자열의 출력표현형이 문자열 자체와 같지 않다는 것을 조심하라. 출력표현형은 이스케이프들을 보여주기 때문이다 (다른말로 하면, 문자열을 프린트할 때, 해당문자열을 재생성하기 위해 출력을 복사하여 붙여넣기할 수 있다). 문자열의 원 내용물을 보기 위해서는, str_view()6을 사용하면 된다:

x <- c(single_quote, double_quote, backslash)
x
#> [1] "'"  "\"" "\\"
str_view(x)
  • '
  • "
  • \

19.2.2 원 문자열

인용부호가 혹은 백슬래시 여러개가 있는 문자열을 생성하는 것은 복잡해진다. double_quotesingle_quote 변수를 정의하는 위의 코드청크를 포함하는 문자열을 생성해보자:

tricky <- "double_quote <- \"\\\"\" # or '\"'
single_quote <- '\\'' # or \"'\""
str_view(tricky)
  • double_quote <- "\"" # or '"'
    single_quote <- '\'' # or "'"

백슬래시가 정말 많다! (때로 이를 기울어진 이쑤시개 신드롬이라고 부른다.) 이스케이핑을 제거하기 위해 원 문자열7을 사용할 수 있다:

tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
writeLines(tricky)
#> double_quote <- "\"" # or '"'
#> single_quote <- '\'' # or "'"

원 문자열은 항상 r"( 로 시작하여 )" 로 끝난다. 문자열이 )" 를 포함한다면, r"[]"r"{}" 을 사용할 수 있고, 이것으로도 충분하지 않다면, 열고 닫는 쌍을 고유하게 만들기 위해 대시를 여러번 삽입할 수 있다. 예, `r"--()--", `r"---()---". 원 문자열은 충분한 유연성을 가지고 있어서, 어떤 텍스트도 다룰 수 있다.

19.2.3 기타 특수문자

\", \', \\ 외에도 편리한 특수 문자들이 매우 많다. 줄바꿈, "\n" , 탭, "\t" 은 가장 일반적인 것들이다. \u\U 로 시작하는 유니코드 이스케이프를 포함하는 문자열을 때로 보게 될 것이다. 이들은 모든 시스템에서 작동하는 비영어 문자들을 작성하는 방법이다:

x <- c("\u00b5", "\U0001f604")
x
#> [1] "µ"  "😄"
str_view(x)
  • µ
  • 😄

?'"' 을 입력하여 기타 특수 문자 전체 목록을 볼 수 있다.

19.2.4 벡터

문자열 여러 개를 c() 를 이용하여 문자형 벡터로 묶을 수 있다:

x <- c("first string", "second string", "third string")
x
#> [1] "first string"  "second string" "third string"

기술적로는, 문자열은 길이 1 인 문자형 벡터이지만, 이는 데이터 분석에서 큰 의미가 없다. 28 장에서 프로그래밍 관점에서 벡터를 다룰 때 이 개념을 더 상세하게 살펴볼 것이다.

“수작업” 문자열생성의 기초를 배웠으니, 다른 문자열로부터 문자열을 생성하는 상세사항으로 들어가 보자.

19.3 데이터에서 문자열 생성하기

다른 문자열들에서 문자열을 생성하는 것은 공통적인 문제이다. 일반적으로 작성한 문자열과 데이터에 있는 문자열변수를 결합하여 생성한다. 예를 들어, “Hello” 와 name 변수를 결합하여 인삿말을 생성할 수 있다. 우선, 이 작업을 쉽게 할 수 있는 두 가지 기술에 대해 논의할 것이다. 그리고 나서, 임의 개수의 문자열을 하나로 만들어서 문자형 벡터를 요약하는 조금 다른 경우에 대해 이야기 할 것이다.

19.3.1 str_c()

str_c()8 는 임의 개수의 벡터를 인수로 문자형 벡터를 반환한다:

str_c("x", "y")
#> [1] "xy"
str_c("x", "y", "z")
#> [1] "xyz"
str_c("Hello ", c("John", "Susan"))
#> [1] "Hello John"  "Hello Susan"

str_c()mutate()과 함께 사용되도록 설계되어서 재사용과 결측값에 관한 일반적인 tidyverse 규칙들을 준수한다:

df <- tibble(name = c("Timothy", "Dewey", "Mable", NA))
df %>% mutate(greeting = str_c("Hi ", name, "!"))
#> # A tibble: 4 × 2
#>   name    greeting   
#>   <chr>   <chr>      
#> 1 Timothy Hi Timothy!
#> 2 Dewey   Hi Dewey!  
#> 3 Mable   Hi Mable!  
#> 4 <NA>    <NA>

결측값을 다른 방식으로 디스플레이 시키고 싶다면 str_c() 내부나 외부에서 coalesce()를 사용하면 된다:

df %>% mutate(
  greeting1 = str_c("Hi ", coalesce(name, "you"), "!"),
  greeting2 = coalesce(str_c("Hi ", name, "!"), "Hi!")
)
#> # A tibble: 4 × 3
#>   name    greeting1   greeting2  
#>   <chr>   <chr>       <chr>      
#> 1 Timothy Hi Timothy! Hi Timothy!
#> 2 Dewey   Hi Dewey!   Hi Dewey!  
#> 3 Mable   Hi Mable!   Hi Mable!  
#> 4 <NA>    Hi you!     Hi!

19.3.2 str_glue()

고정문자열과 변수문자열 다수를 str_c() 로 합치고 있다면, "" 를 반복적으로 타이핑해야해서 코드의 전체 목적을 보기 어렵게 된다는 것을 깨달을 것이다. glue 패키지str_glue()9 를 통한 다른 방법이 있다. {} 이 포함된 하나의 문자열을 이 함수에 제공하면 된다. {} 내부의 것들은 문자열 바깥에 있는 것처럼 evaluate 될 것이다:

df %>% mutate(greeting = str_glue("Hi {name}!"))
#> # A tibble: 4 × 2
#>   name    greeting   
#>   <chr>   <glue>     
#> 1 Timothy Hi Timothy!
#> 2 Dewey   Hi Dewey!  
#> 3 Mable   Hi Mable!  
#> 4 <NA>    Hi NA!

{} 내부에서 유효한 R 코드를 사용할 수 있지만, 복잡한 계산을 해당되는 변수로 끄집어 내서 작업을 쉽게 확인할 수 있게 하는 것이 좋다.

일반적인 {} 를 문자열에 포함시키면 어떻게 되는지 궁금할 수 있다. 여기서 약간 다른 이스케이프 기술을 사용한다; \ 과 같은 특수 문자를 앞에 붙이는 대신, {} 를 두번 한다:

df %>% mutate(greeting = str_glue("{{Hi {name}!}}"))
#> # A tibble: 4 × 2
#>   name    greeting     
#>   <chr>   <glue>       
#> 1 Timothy {Hi Timothy!}
#> 2 Dewey   {Hi Dewey!}  
#> 3 Mable   {Hi Mable!}  
#> 4 <NA>    {Hi NA!}

19.3.3 str_flatten()

str_c()glue()mutate() 와 잘 작동하는데 출력이 입력과 같은 길이이기 때문이다. 함수를 summarize() 와 잘 작동하게 하려면, 즉, 항상 하나의 문자열을 반환시키려면 어떻게 해야하나? str_flatten() 이 바로 이 일을 한다: 문자열 벡터를 입력으로 벡터의 각 요소를 하나의 문자열로 결합한다.

str_flatten(c("x", "y", "z"))
#> [1] "xyz"
str_flatten(c("x", "y", "z"), ", ")
#> [1] "x, y, z"
str_flatten(c("x", "y", "z"), ", ", last = ", and ")
#> [1] "x, y, and z"

이렇게 하면 summarize() 와 잘 작동하게 된다:

df <- tribble(
  ~ name, ~ fruit,
  "Carmen", "banana",
  "Carmen", "apple",
  "Marvin", "nectarine",
  "Terence", "cantaloupe",
  "Terence", "papaya",
  "Terence", "madarine"
)
df %>%
  group_by(name) %>% 
  summarize(fruits = str_flatten(fruit, ", "))
#> # A tibble: 3 × 2
#>   name    fruits                      
#>   <chr>   <chr>                       
#> 1 Carmen  banana, apple               
#> 2 Marvin  nectarine                   
#> 3 Terence cantaloupe, papaya, madarine

19.3.4 Exercises

  1. Compare and contrast the results of paste0() with str_c() for the following inputs:

    str_c("hi ", NA)
    str_c(letters[1:2], letters[1:3])
  2. Convert between str_c() and glue()

  3. How to make {{{{ with glue?

19.4 패턴으로 작업

문자열에서부터 데이터를 추출하는 반대방향의 문제를 논의하기 전에, 정규표현식에 관해 살짝 길을 벗어나서 이야기할 필요가 있다. 정규표현식은 문자열의 패턴을 기술하는 매우 정갈한 언어이다. 정규표현식은 처음에는 겁이나고, 고양이가 키보드를 밟고 간 것처럼 보일 것이다. 하지만 다행스럽게도 이해도가 높아질수록 의미가 눈에 들어올 것이다.

정규표현식은 이야기가 길어지고, 컴퓨터과학 이론의 본체를 참조하는 것 만큼 용어가 유용하지는 않은데, “정규” 와 “표현식” 용어 모두 평소에 쓰는 의미와 거리가 있다. (번역다듬어야함) 실무에서 대부분의 사람들은 “regexs” 나 “regexps” 줄임말을 쓴다.

str_detect() 으로 간단한 질문에 답을 찾는 것부터 할 것이다: “이 패턴이 내 벡터 어딘가에 있는가?”. 그리고 나서, 정규표현식과 이를 사용하는 함수를 배워가면서 점진적으로 더 복잡한 문제를 대할 것이다.

19.4.1 매칭 탐지

정규표현식을 배우기 위해, 이를 사용하는, 아마 가장 단순한 함수부터 시작할 것이다: str_detect(). 문자형 벡터와 패턴을 입력으로 벡터의 각 요소에서 패턴이 발견되었는지 알려주는 논리형 벡터를 반환한다.

x <- c("apple", "banana", "pear")
str_detect(x, "e")
#> [1]  TRUE FALSE  TRUE
str_detect(x, "b")
#> [1] FALSE  TRUE FALSE
str_detect(x, "x")
#> [1] FALSE FALSE FALSE

str_detect() 은 첫번째 인수와 같은 길이를 같는 논리형 벡터를 반환하므로, filter() 와 짝을 잘 이룬다. 예를 들어, 다음 코드는 소문자 “x”를 포함하는 모든 이름을 찾는다:

babynames %>% filter(str_detect(name, "x"))
#> # A tibble: 16,317 × 5
#>    year sex   name          n      prop
#>   <dbl> <chr> <chr>     <int>     <dbl>
#> 1  1880 F     Roxie        62 0.000635 
#> 2  1880 F     Dixie        15 0.000154 
#> 3  1880 F     Roxanna       9 0.0000922
#> 4  1880 F     Texas         5 0.0000512
#> 5  1880 M     Alexander   211 0.00178  
#> 6  1880 M     Alex        147 0.00124  
#> # … with 16,311 more rows

논리형 벡터를 수치형 맥락에서 사용할 경우, FALSE 는 0 이 되고, TRUE 는 1 이 된다는 것을 명심하면서, str_detect() 를 요약하는데 사용할 수도 있다. sum(str_detect(x, pattern)) 는 패턴이 매칭되는 관측값의 개수를 알려줄 것이고, mean(str_detect(x, pattern)) 은 매칭이 되는 비율을 알려줄 것이다. 예를 들어, 다음 스니펫은 알파벳 “x” 를 포함하는 아기 이름비율을 연도별로 나누어 계산하고 시각화한다:

babynames %>% 
  group_by(year) %>% 
  summarise(prop_x = mean(str_detect(name, "x"))) %>% 
  ggplot(aes(year, prop_x)) + 
  geom_line()
A timeseries showing the proportion of baby names that contain the letter x 문자를 포함한 아기 이름 비율을 보여주는 시계열데이터. 1880 년에는 1000 명당 8 명에서 1980년 1000명당 4명으로 서서히 감소하다가 2019년에 1000 명당 16명으로 급격히 증가함.

(x 를 포함한 이름의 비율을 계산한 것임을 주목하라; x 를 포함한 이름을 받은 아이의 비율을 원한다면 가중평균을 수행해야한다).

19.4.2 정규표현식 들어가기

위와 같이 가장 간단한 패턴은 이그잭트(exact)이다: 패턴에서 문자형 서열을 그대로 포함하는 문자열을 매칭한다:

str_detect(c("x", "X"), "x")
#> [1]  TRUE FALSE
str_detect(c("xyz", "xza"), "xy")
#> [1]  TRUE FALSE

일반적으로, 임의의 문자나 숫자는 이그잭트 매칭할 것이지만, ., +, *, [, ], ? 와 같은 구두점(punctuation) 문자는 특별한 의미가 있다10. 예를 들어, . 는 임의의 문자에 매칭할 것이어서11, "a." 는 a 가 있고 임의의 문자가 따라 나오는 문자열에 매칭할 것이다:

str_detect(c("a", "ab", "ae", "bd", "ea", "eab"), "a.")
#> [1] FALSE  TRUE  TRUE FALSE FALSE  TRUE

어떻게 동작하는 것을 더 잘 이해하기 위해 str_view_all() 을 사용해 보자. 이 함수는 매치된 문자들을 <> 로 둘러싸고 파란색으로 보여준다:

str_view_all(c("a", "ab", "ae", "bd", "ea", "eab"), "a.")
  • a
  • ab
  • ae
  • bd
  • ea
  • eab

정규표현식은 강력하고 유연한 언어이다. 20 장에서 계속 살펴볼 것이다. 여기서는 가장 중요한 구성요소만 소개할 것이다: quantifiers and character classes.

Quantifiers 는 한 요소가 다른 패턴에 얼마나 많이 적용될 수 있는지를 컨트롤 한다: ? 는 패턴을 선택사항으로 만들고 (즉, 0 혹은 1 회 매치한다), + 는 패턴을 반복시키고 (즉, 적어도 한번 매치), * 을 패턴을 선택사항으로 만들거나 반복시킨다 (즉 0회를 포함하여 어떤 회수나 매칭한다).

# ab? matches an "a", optionally followed by a "b".
str_view_all(c("a", "ab", "abb"), "ab?")
  • a
  • ab
  • abb
# ab+ matches an "a", followed by at least one "b". str_view_all(c("a", "ab", "abb"), "ab+")
  • a
  • ab
  • abb
# ab* matches an "a", followed by any number of "b"s. str_view_all(c("a", "ab", "abb"), "ab*")
  • a
  • ab
  • abb

Character classes[] 가 정의하고 문자 집합을 매칭하게 한다, 예를 들어 [abcd] 는 “a”, “b”, “c”, “d” 중 하나(이상)와 매칭한다. ^ 로 시작하여 매치를 뒤집을 수도 있다: [^abcd] 는 “a”, “b”, “c”, “d” 를 제외한 어느 것과도 매칭한다. 특정 이름에 모음을 찾기 위해 이 개념을 사용할 수 있다:

names <- c("Hadley", "Mine", "Garrett")
str_view_all(names, "[aeiou]")
  • Hadley
  • Mine
  • Garrett

문자형 클래스와 quantifiers 를 결합할 수 있다. 자음을 찾는 다음 두개의 패턴 사이의 차이에 주목해 보자. 같은 문자가 매칭되었지만, 매치 개수는 다르다.

str_view_all(names, "[^aeiou]")
  • Hadley
  • Mine
  • Garrett
str_view_all(names, "[^aeiou]+")
  • Hadley
  • Mine
  • Garrett

정규표현식 사용법을 유용한 stringr 함수들과 함께 연습해 보자.

19.4.3 매치 카운트

str_count()str_detect() 의 변형이다 : 단순한 예/아니오가 아니라, 문자열에 얼마나 많이 매칭이 있는지 알려준다:

x <- c("apple", "banana", "pear")
str_count(x, "p")
#> [1] 2 0 1

str_count()mutate() 와 사용하는 것이 자연스럽다. 다음 예에서는 str_count() 를 문자 클래스와 사용하여 각 이름의 모음과 자음을 카운트한다.

babynames %>% 
  count(name) %>% 
  mutate(
    vowels = str_count(name, "[aeiou]"),
    consonants = str_count(name, "[^aeiou]")
  )
#> # A tibble: 97,310 × 4
#>   name          n vowels consonants
#>   <chr>     <int>  <int>      <int>
#> 1 Aaban        10      2          3
#> 2 Aabha         5      2          3
#> 3 Aabid         2      2          3
#> 4 Aabir         1      2          3
#> 5 Aabriella     5      4          5
#> 6 Aada          1      2          2
#> # … with 97,304 more rows

가까이 보면, 계산에서 빗나간 것이 있음을 알아차릴 것이다: “Aaban” 은 “a” 세 개를 포함하지만, 우리 요약 리포트에는 두 개의 모음만 있다. 정규표현식이 대소문자를 구분하기 때문이다. 이를 바로 잡는 방법으로 세 가지 방법이 있다:

  • Add the upper case vowels to the character class: str_count(name, "[aeiouAEIOUS]").
  • Tell the regular expression to ignore case: str_count(regex(name, ignore.case = TRUE), "[aeiou]"). We’ll talk about this next.
  • Use str_lower() to convert the names to lower case: str_count(to_lower(name), "[aeiou]"). We’ll come back to this function in Section 19.6.

This is pretty typical when working with strings — there are often multiple ways to reach your goal, either making your pattern more complicated or by doing some preprocessing on your string. If you get stuck trying one approach, it can often be useful to switch gears and tackle the problem from a different perspective.

19.4.4 매칭 치환

추출하기 시작하기 전에 포매팅에서의 비일관성이 있는 경우가 있다. 이 경우는 고치기가 더 쉽다.; str_* 과 이웃함수들로 복잡한 정규표현식으로 해결하는 것보다 데이터를 더 정규화하고 작업을 확인하기가 더 쉽다.

str_replace_all() 은 매칭된 것을 새로운 문자열로 치환할 수 있다. 가장 간단한 것은 패턴을 고정된 문자열로 치환하는 것이다:

x <- c("apple", "pear", "banana")
str_replace_all(x, "[aeiou]", "-")
#> [1] "-ppl-"  "p--r"   "b-n-n-"

str_replace_all() 를 사용하여 명명된 벡터를 제공하여 다중 치환을 수행할 수 있다. 이름은 매칭할 정규표현식을 제공하고 값은 치환을 제공한다.

x <- c("1 house", "1 person has 2 cars", "3 people")
str_replace_all(x, c("1" = "one", "2" = "two", "3" = "three"))
#> [1] "one house"               "one person has two cars"
#> [3] "three people"

str_remove_all()str_replace_all(x, pattern, "") 의 단축어이다 — 문자열에서 매칭된 패턴을 제거한다.

pattern as a function. Come back to that in Chapter ??.

x <- c("1 house", "1 person has 2 cars", "3 people")
str_replace_all(x, "[aeiou]+", str_to_upper)
#> [1] "1 hOUsE"             "1 pErsOn hAs 2 cArs" "3 pEOplE"

Use in mutate()

Using pipe inside mutate. Recommendation to make a function, and think about testing it — don’t need formal tests, but useful to build up a set of positive and negative test cases as you.

19.4.5 패턴 컨트롤

정규표현식에 대해 배우고 나서, 이제는, 작동하지 않는 곳에 작동이 될까봐 걱정될 것이다. fixed() 를 이용해서 정규표현식을 opt-out 할 수 있다:

str_view(c("", "a", "."), fixed("."))
  • '
  • "
  • \

고정 문자열과 정규표현식 모두 기본값으로 대소문자 구별함을 주목하라. ignore_case = TRUE 로 설정하여 opt out 할 수 있다.

str_view_all("x  X  xy", "X")
  • double_quote <- "\"" # or '"'
    single_quote <- '\'' # or "'"
str_view_all("x X xy", fixed("X", ignore_case = TRUE))
  • µ
  • 😄
str_view_all("x X xy", regex(".Y", ignore_case = TRUE))
  • a
  • ab
  • ae
  • bd
  • ea
  • eab

19.4.6 Exercises

  1. What name has the most vowels? What name has the highest proportion of vowels? (Hint: what is the denominator?)

  2. For each of the following challenges, try solving it by using both a single regular expression, and a combination of multiple str_detect() calls.

    1. Find all words that start or end with x.
    2. Find all words that start with a vowel and end with a consonant.
    3. Are there any words that contain at least one of each different vowel?
  3. Replace all forward slashes in a string with backslashes.

  4. Implement a simple version of str_to_lower() using str_replace_all().

  5. Switch the first and last letters in words. Which of those strings are still words?

19.5 문자열에서 데이터 추출하기

Common for multiple variables worth of data to be stored in a single string. In this section you’ll learn how to use various functions tidyr to extract them.

Waiting on: https://github.com/tidyverse/tidyups/pull/15

19.6 로캘 의존 동작

지금까지 모든 예제는 영어를 사용했었다. 다른 언어들이 영어와 다른 방식은 여기에서 세부사항까지 다루기에는 너무 다양하지만, 여러분의 로캘 (나라마다 달라지는 설정셋)에 기반하여 동작이 달라지는 함수들을 빠르게 스케치할 것이다. 로캘은 두 세 문자 소문자 언어 줄임말로 설정되는데, 선택적으로 _ 와 대문자 지역 식별자가 따라나온다. 예를 들어, “en” 은 영어, “en_GB” 는 영국영어, “en_US” 는 미국영어이다. 여러분 언어 코드를 모른다면, 위키피디아 가 목록을 가지고 있고, stringi::stri_locale_list() 로 지원하는지를 살펴볼 수 있다.

베이스 R 문자열 함수는 여러분의 로캘을 현재 로캘로 자동으로 사용한다. 문자열 조정 코드가 모국어로 텍스트 작업할 때 기대한 방식으로 작동하는 것을 의미하지만 다른 나라에서 살고 있는 사람과 공유할 때 다르게 작동할 수 있다. 이 문제를 피하기 위해 stringr 은 “en” 로캘을 기본값으로 하고, locale 인수를 설정하여 덮어쓰도록 한다. 또한, 어떤 함수가 다른 로캘에서 다르게 동작하는지 알아보는 것이 더 쉽게 된다.

로캘이 중요하게 되는 함수 세가지 종류가 있다:

  • 대소문자 변환: 대문자와 서문자가 있는 언어가 많지는 않다 (Latin, Greek, and Cyrillic, plus a handful of lessor known languages). 대소문자 변환 법칙이 이러한 알파벳을 사용하는 모든 언어에서 같지 않다. 예를 들어, 터키어는 i 가 두 개이다: 점이 있는 것과 없는 것, 대문자하는 법칙이 다르다:

    str_to_upper(c("i", "ı"))
    #> [1] "I" "I"
    str_to_upper(c("i", "ı"), locale = "tr")
    #> [1] "İ" "I"
  • 문자열 비교: str_equal() 은 대소문자를 선택적으로 무시하고 두 문자열이 같은지 비교한다:

    str_equal("i", "I", ignore_case = TRUE)
    #> [1] TRUE
    str_equal("i", "I", ignore_case = TRUE, locale = "tr")
    #> [1] FALSE
  • 문자열 정렬: str_sort()str_order() 는 벡터를 알파벳 순서로 정렬하지만, 알파벳은 모든 언어에서 같지 않다12. 예가 있다: 체코에서, “ch” 는 알파벳에서 h 뒤에 오는 digraph 이다.

    str_sort(c("a", "c", "ch", "h", "z"))
    #> [1] "a"  "c"  "ch" "h"  "z"
    str_sort(c("a", "c", "ch", "h", "z"), locale = "cs")
    #> [1] "a"  "c"  "h"  "ch" "z"

    덴마크어도 비슷한 문제가 있다. 일반적으로, diacritic 이 있는 글자들은 플레인 글자 뒤에 정렬된다. 하지만, 덴마크어에서 ø and å 는 알파벳 뒤에 오는 글자이다:

    str_sort(c("a", "å", "o", "ø", "z"))
    #> [1] "a" "å" "o" "ø" "z"
    str_sort(c("a", "å", "o", "ø", "z"), locale = "da")
    #> [1] "a" "o" "z" "ø" "å"

    TODO after dplyr 1.1.0: discuss arrange()

19.7 Letters

Functions that work with the letters inside of the string.

19.7.1 길이

str_length() 는 문자열에서 문자의 개수를 알려준다13:

str_length(c("a", "R for data science", NA))
#> [1]  1 18 NA

count() 과 함께 이를 사용하여 미국 아기이름의 길이의 분포를 찾고, filter() 로 가장 긴 이름을 찾을 수 있을 것이다14:

babynames %>%
  count(length = str_length(name), wt = n)
#> # A tibble: 14 × 2
#>   length        n
#>    <int>    <int>
#> 1      2   338150
#> 2      3  8589596
#> 3      4 48506739
#> 4      5 87011607
#> 5      6 90749404
#> 6      7 72120767
#> # … with 8 more rows

babynames %>% 
  filter(str_length(name) == 15) %>% 
  count(name, wt = n, sort = TRUE)
#> # A tibble: 34 × 2
#>   name                n
#>   <chr>           <int>
#> 1 Franciscojavier   123
#> 2 Christopherjohn   118
#> 3 Johnchristopher   118
#> 4 Christopherjame   108
#> 5 Christophermich    52
#> 6 Ryanchristopher    45
#> # … with 28 more rows

19.7.2 서브셋하기

str_sub(string, start, end) 을 사용하여 문자열의 부분을 추출할 수 있다. startend 인수들도 포함되기(inclusive) 때문에, 반환되는 문자열의 길이는 end - start + 1 이 된다:

x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
#> [1] "App" "Ban" "Pea"

음수 값을 사용하여 문자열 끝에서 부터 뒤로 셀 수 있다: -1 은 마지막 글자, -2 는 마지막에서 두번째 글자, 등이다.

str_sub(x, -3, -1)
#> [1] "ple" "ana" "ear"

문자열이 너무 짧을 때도 str_sub() 은 에러나지 않는다는 것을 주목하라: 가능한 최대의 것을 반환한다.

str_sub("a", 1, 5)
#> [1] "a"

str_sub()mutate() 와 같이 사용하여 각 이름의 처음과 마지막 글자를 찾을 수 있다:

babynames %>% 
  mutate(
    first = str_sub(name, 1, 1),
    last = str_sub(name, -1, -1)
  )
#> # A tibble: 1,924,665 × 7
#>    year sex   name          n   prop first last 
#>   <dbl> <chr> <chr>     <int>  <dbl> <chr> <chr>
#> 1  1880 F     Mary       7065 0.0724 M     y    
#> 2  1880 F     Anna       2604 0.0267 A     a    
#> 3  1880 F     Emma       2003 0.0205 E     a    
#> 4  1880 F     Elizabeth  1939 0.0199 E     h    
#> 5  1880 F     Minnie     1746 0.0179 M     e    
#> 6  1880 F     Margaret   1578 0.0162 M     t    
#> # … with 1,924,659 more rows

19.7.3 긴 문자열

문자열 길이를 신경쓰는 이유는, 플롯이나 테이블 라벨안에 집어넣으려고 노력할 때인 경우가 많다. stringr 은 문자열이 너무 긴 경우 편리한 두가지 도구를 제공한다:

  • str_trunc(x, 20) 은 너무 긴 문자열을 로 치환하여, 20 문자보다 긴 문자열이 없도록 보장한다.

  • str_wrap(x, 20) 은 각 줄이 최대 20 자가 되도록 새라인을 들여와서 문자열을 래핑한다 (하지만, 하이픈을 쓰지 않아서, 20 자 이상의 단어는 더 시간이 많이 걸릴 것이다).

x <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
str_trunc(x, 30)
#> [1] "Lorem ipsum dolor sit amet,..."
str_view(str_wrap(x, 30))
  • a
  • ab
  • abb

TODO: add example with a plot.

19.7.4 Exercises

  1. 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?
  2. Are there any major trends in the length of babynames over time? What about the popularity of first and last letters?

19.8 기타 함수들

stringr 이 아닌 다른 곳에서도 정규표현식을 사용할 수 있다.

  • matches(): as you can tell from it’s lack of str_ 접두어가 없는것에서 알 prefix, this isn’t a stringr fuction. It’s a “tidyselect” function, a fucntion that you can use anywhere in the tidyverse when selecting variables (예: dplyr::select(), rename_with(), across(), …).

  • str_locate(), str_match(), str_split(); useful for programming with strings.

  • apropos() searches all objects available from the global environment. This is useful if you can’t quite remember the name of the function.

    apropos("replace")
    #> [1] "%+replace%"       "replace"          "replace_na"       "setReplaceMethod"
    #> [5] "str_replace"      "str_replace_all"  "str_replace_na"   "theme_replace"
  • dir() lists all the files in a directory. The pattern argument takes a regular expression and only returns file names that match the pattern. For example, you can find all the R Markdown files in the current directory with:

    head(dir(pattern = "\\.Rmd$"))
    #> [1] "_import.Rmd"           "communicate-plots.Rmd" "communicate.Rmd"      
    #> [4] "data-import.Rmd"       "data-tidy.Rmd"         "data-transform.Rmd"

    (If you’re more comfortable with “globs” like *.Rmd, you can convert them to regular expressions with glob2rx()):