14 문자열
14.1 소개
지금까지 자세한 내용은 많이 배우지 않고 많은 문자열을 사용했습니다. 이제 문자열을 자세히 살펴보고, 문자열이 어떻게 작동하는지 배우고, 자유롭게 사용할 수 있는 강력한 문자열 조작 도구를 마스터할 때입니다.
문자열과 문자형 벡터를 만드는 세부 사항부터 시작하겠습니다. 그런 다음 데이터에서 문자열을 만드는 방법과 반대로 데이터에서 문자열을 추출하는 방법을 알아볼 것입니다. 그 다음 개별 문자로 작업하는 도구에 대해 논의할 것입니다. 이 장은 개별 문자로 작업하는 함수와 다른 언어로 작업할 때 영어에 대한 기대가 잘못될 수 있는 부분에 대한 간단한 논의로 마무리됩니다.
다음 장에서도 문자열 작업을 계속할 것이며, 거기서는 정규 표현식의 힘에 대해 더 배우게 될 것입니다.
14.1.1 선수 지식
이 장에서는 핵심 tidyverse의 일부인 stringr 패키지의 함수를 사용할 것입니다. 또한 조작할 재미있는 문자열을 제공하는 babynames 데이터도 사용할 것입니다.
모든 stringr 함수는 str_로 시작하기 때문에 stringr 함수를 사용하고 있는지 금방 알 수 있습니다. 이것은 RStudio를 사용하는 경우 특히 유용한데, str_을 입력하면 자동 완성이 트리거되어 사용 가능한 함수에 대한 기억을 되살릴 수 있기 때문입니다.
14.2 문자열 만들기
책의 앞부분에서 문자열을 스치듯 만들었지만 세부 사항은 논의하지 않았습니다. 먼저 작은따옴표(') 또는 큰따옴표(")를 사용하여 문자열을 만들 수 있습니다. 두 가지의 동작에는 차이가 없으므로 일관성을 위해 tidyverse 스타일 가이드에서는 문자열에 여러 개의 "가 포함되어 있지 않는 한 "를 사용할 것을 권장합니다.
string1 <- "This is a string"
string2 <- 'If I want to include a "quote" inside a string, I use single quotes'따옴표를 닫는 것을 잊어버리면 연속 프롬프트인 +가 표시됩니다:
> "This is a string without a closing quote
+
+
+ HELP I'M STUCK IN A STRING
이런 일이 발생하고 어떤 따옴표를 닫아야 할지 모르겠다면 Escape 키를 눌러 취소하고 다시 시도하세요.
14.2.1 이스케이프(Escapes)
문자열에 리터럴 작은따옴표나 큰따옴표를 포함하려면 \를 사용하여 “이스케이프”할 수 있습니다:
double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"따라서 문자열에 리터럴 백슬래시를 포함하려면 이스케이프해야 합니다: "\\":
backslash <- "\\"문자열의 인쇄된 표현은 이스케이프를 보여주기 때문에 문자열 자체와 동일하지 않다는 점에 유의하세요(즉, 문자열을 인쇄할 때 출력을 복사하여 붙여넣으면 해당 문자열을 다시 만들 수 있습니다). 문자열의 원시 내용을 보려면 str_view()1를 사용하세요:
14.2.2 원시 문자열(Raw strings)
여러 따옴표나 백슬래시로 문자열을 만드는 것은 금방 헷갈립니다. 문제를 설명하기 위해 double_quote 및 single_quote 변수를 정의한 코드 블록의 내용을 포함하는 문자열을 만들어 보겠습니다:
tricky <- "double_quote <- \"\\\"\" # or '\"'
single_quote <- '\\'' # or \"'\""
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"백슬래시가 정말 많네요! (이것을 때때로 기우는 이쑤시개 증후군이라고 합니다.) 이스케이프를 제거하려면 대신 원시 문자열2을 사용할 수 있습니다:
tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"원시 문자열은 일반적으로 r"("로 시작하고 )"로 끝납니다. 그러나 문자열에 )"가 포함되어 있는 경우 대신 r"[]" 또는 r"{}"를 사용할 수 있으며, 그래도 충분하지 않은 경우 대시를 원하는 만큼 삽입하여 열고 닫는 쌍을 고유하게 만들 수 있습니다. 예: r"--()--", r"---()---" 등. 원시 문자열은 모든 텍스트를 처리할 수 있을 만큼 유연합니다.
14.2.3 기타 특수 문자
", ', \ 외에도 유용하게 사용할 수 있는 몇 가지 다른 특수 문자가 있습니다. 가장 일반적인 것은 \n(새 줄)과 \t(탭)입니다. 또한 \u 또는 \U로 시작하는 유니코드 이스케이프가 포함된 문자열을 볼 수도 있습니다. 이것은 모든 시스템에서 작동하는 영어가 아닌 문자를 쓰는 방법입니다. ?Quotes에서 다른 특수 문자의 전체 목록을 볼 수 있습니다.
str_view()는 탭을 쉽게 찾을 수 있도록 중괄호를 사용합니다3. 텍스트 작업의 어려움 중 하나는 텍스트에 공백이 포함될 수 있는 방법이 다양하다는 것인데, 이러한 배경 지식은 뭔가 이상한 일이 일어나고 있음을 인식하는 데 도움이 됩니다.
14.2.4 연습문제
-
다음 값을 포함하는 문자열을 만드세요:
He said "That's amazing!"\a\b\c\d\\\\\
-
R 세션에서 문자열을 만들고 인쇄하세요. 특수 문자 “0a0”은 어떻게 됩니까?
str_view()는 어떻게 표시합니까? 이 특수 문자가 무엇인지 구글링해 볼 수 있습니까?x <- "This\u00a0is\u00a0tricky"
14.3 데이터에서 많은 문자열 만들기
이제 “손으로” 문자열 한두 개를 만드는 기본 사항을 배웠으므로 다른 문자열에서 문자열을 만드는 세부 사항으로 들어갑니다. 이것은 작성한 텍스트를 데이터 프레임의 문자열과 결합하려는 일반적인 문제를 해결하는 데 도움이 됩니다. 예를 들어 “Hello”와 name 변수를 결합하여 인사를 만들 수 있습니다. str_c() 및 str_glue()를 사용하여 이를 수행하는 방법과 mutate()와 함께 사용하는 방법을 보여줄 것입니다. 그러면 자연스럽게 summarize()와 함께 어떤 stringr 함수를 사용할 수 있는지에 대한 질문이 제기되므로 문자열 요약 함수인 str_flatten()에 대한 논의로 이 섹션을 마무리하겠습니다.
14.3.1 str_c()
str_c()는 임의의 수의 벡터를 인수로 받아 문자 벡터를 반환합니다:
str_c()는 기본 paste0()과 매우 유사하지만 재활용 및 결측값 전파에 대한 일반적인 tidyverse 규칙을 준수하여 mutate()와 함께 사용되도록 설계되었습니다:
결측값을 다른 방식으로 표시하려면 coalesce()를 사용하여 대체하세요. 원하는 것에 따라 str_c() 내부 또는 외부에서 사용할 수 있습니다:
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 Flora Hi Flora! Hi Flora!
#> 2 David Hi David! Hi David!
#> 3 Terra Hi Terra! Hi Terra!
#> 4 <NA> Hi you! Hi!
14.3.2 str_glue()
str_c()로 많은 고정 문자열과 가변 문자열을 섞는다면 "를 많이 입력하게 되어 코드의 전체 목표를 보기가 어렵습니다. glue 패키지에서 str_glue()4를 통해 대안적인 접근 방식을 제공합니다. 특별한 기능이 있는 단일 문자열을 제공합니다: {} 내부의 모든 것은 따옴표 밖에 있는 것처럼 평가됩니다:
보시다시피 str_glue()는 현재 결측값을 문자열 "NA"로 변환하므로 불행히도 str_c()와 일관성이 없습니다.
문자열에 일반 { 또는 }를 포함해야 하는 경우 어떻게 되는지 궁금할 수도 있습니다. 어떻게든 이스케이프해야 한다고 추측했다면 올바른 방향입니다. 비결은 glue가 약간 다른 이스케이프 기술을 사용한다는 것입니다: \와 같은 특수 문자를 접두사로 붙이는 대신 특수 문자를 두 배로 늘립니다:
14.3.3 str_flatten()
str_c()와 str_glue()는 출력이 입력과 길이가 같기 때문에 mutate()와 잘 작동합니다. summarize()와 잘 작동하는 함수, 즉 항상 단일 문자열을 반환하는 함수를 원한다면 어떻게 해야 할까요? 그것이 str_flatten()5의 역할입니다: 문자 벡터를 받아 벡터의 각 요소를 단일 문자열로 결합합니다:
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", "mandarin"
)
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, mandarin14.3.4 연습문제
14.4 문자열에서 데이터 추출
여러 변수가 하나의 문자열에 함께 섞여 있는 것은 매우 일반적입니다. 이 섹션에서는 이를 추출하기 위해 네 가지 tidyr 함수를 사용하는 방법을 배웁니다:
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)
자세히 보면 여기에 공통 패턴이 있음을 알 수 있습니다: separate_, 그 다음 longer 또는 wider, 그 다음 _, 그 다음 delim 또는 position으로. 이는 이 네 가지 함수가 두 가지 더 단순한 기본 요소로 구성되어 있기 때문입니다:
-
pivot_longer()및pivot_wider()와 마찬가지로_longer함수는 새 행을 생성하여 입력 데이터 프레임을 길게 만들고_wider함수는 새 열을 생성하여 입력 데이터 프레임을 넓게 만듭니다. -
delim은", "또는" "와 같은 구분 기호로 문자열을 분할합니다.position은c(3, 5, 2)와 같이 지정된 너비로 분할합니다.
Chapter 15 에서 이 제품군의 마지막 멤버인 separate_wider_regex()로 돌아올 것입니다. 이것은 wider 함수 중 가장 유연하지만 사용하기 전에 정규 표현식에 대해 알고 있어야 합니다.
다음 두 섹션에서는 이러한 분리 함수 뒤에 있는 기본 아이디어를 제공할 것입니다. 먼저 행으로 분리(조금 더 간단함)하고 그 다음 열로 분리합니다. wider 함수가 문제를 진단하기 위해 제공하는 도구에 대해 논의하며 마무리하겠습니다.
14.4.1 행으로 분리
문자열을 행으로 분리하는 것은 구성 요소의 수가 행마다 다를 때 가장 유용한 경향이 있습니다. 가장 일반적인 경우는 구분 기호를 기반으로 분할하기 위해 separate_longer_delim()이 필요한 경우입니다:
df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |>
separate_longer_delim(x, delim = ",")
#> # A tibble: 6 × 1
#> x
#> <chr>
#> 1 a
#> 2 b
#> 3 c
#> 4 d
#> 5 e
#> 6 f야생에서 separate_longer_position()을 보는 것은 더 드물지만, 일부 오래된 데이터셋은 각 문자가 값을 기록하는 데 사용되는 매우 간결한 형식을 사용합니다:
df2 <- tibble(x = c("1211", "131", "21"))
df2 |>
separate_longer_position(x, width = 1)
#> # A tibble: 9 × 1
#> x
#> <chr>
#> 1 1
#> 2 2
#> 3 1
#> 4 1
#> 5 1
#> 6 3
#> # ℹ 3 more rows14.4.2 열로 분리
문자열을 열로 분리하는 것은 각 문자열에 고정된 수의 구성 요소가 있고 이를 열로 펼치고 싶을 때 가장 유용한 경향이 있습니다. 열 이름을 지정해야 하기 때문에 longer 등가물보다 약간 더 복잡합니다. 예를 들어 다음 데이터셋에서 x는 .로 구분된 코드, 에디션 번호, 연도로 구성됩니다. separate_wider_delim()을 사용하려면 두 인수에서 구분 기호와 이름을 제공합니다:
df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", "edition", "year")
)
#> # A tibble: 3 × 3
#> code edition year
#> <chr> <chr> <chr>
#> 1 a10 1 2022
#> 2 b10 2 2011
#> 3 e15 1 2015특정 조각이 유용하지 않은 경우 NA 이름을 사용하여 결과에서 생략할 수 있습니다:
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", NA, "year")
)
#> # A tibble: 3 × 2
#> code year
#> <chr> <chr>
#> 1 a10 2022
#> 2 b10 2011
#> 3 e15 2015separate_wider_position()은 일반적으로 각 열의 너비를 지정하기 때문에 약간 다르게 작동합니다. 따라서 이름은 새 열의 이름을 제공하고 값은 차지하는 문자 수인 명명된 정수 벡터를 제공합니다. 이름을 지정하지 않아 출력에서 값을 생략할 수 있습니다:
df4 <- tibble(x = c("202215TX", "202122LA", "202325CA"))
df4 |>
separate_wider_position(
x,
widths = c(year = 4, age = 2, state = 2)
)
#> # A tibble: 3 × 3
#> year age state
#> <chr> <chr> <chr>
#> 1 2022 15 TX
#> 2 2021 22 LA
#> 3 2023 25 CA14.4.3 넓히기 문제 진단
separate_wider_delim()6은 고정되고 알려진 열 집합을 필요로 합니다. 일부 행에 예상된 수의 조각이 없으면 어떻게 될까요? 조각이 너무 적거나 너무 많은 두 가지 가능한 문제가 있으므로 separate_wider_delim()은 도움이 되는 두 가지 인수 too_few와 too_many를 제공합니다. 다음 샘플 데이터셋으로 too_few 케이스를 먼저 살펴보겠습니다:
df <- tibble(a = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))
df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z")
)
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `a`.
#> ! 2 values were too short.
#> ℹ Use `too_few = "debug"` to diagnose the problem.
#> ℹ Use `too_few = "align_start"/"align_end"` to silence this message.오류가 발생하지만 오류는 진행 방법에 대한 몇 가지 제안을 제공합니다. 문제를 디버깅하는 것부터 시작하겠습니다:
debug <- df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z"),
too_few = "debug"
)
#> Warning: Debug mode activated: adding variables `a_ok`, `a_pieces`, and
#> `a_remainder`.
debug
#> # A tibble: 5 × 7
#> x y z a a_ok a_pieces a_remainder
#> <chr> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1 1 1 1-1-1 TRUE 3 ""
#> 2 1 1 2 1-1-2 TRUE 3 ""
#> 3 1 3 <NA> 1-3 FALSE 2 ""
#> 4 1 3 2 1-3-2 TRUE 3 ""
#> 5 1 <NA> <NA> 1 FALSE 1 ""디버그 모드를 사용하면 출력에 a_ok, a_pieces, a_remainder라는 세 개의 추가 열이 추가됩니다(다른 이름의 변수를 분리하면 다른 접두사가 붙습니다). 여기서 a_ok를 사용하면 실패한 입력을 빠르게 찾을 수 있습니다:
debug |> filter(!a_ok)
#> # A tibble: 2 × 7
#> x y z a a_ok a_pieces a_remainder
#> <chr> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1 3 <NA> 1-3 FALSE 2 ""
#> 2 1 <NA> <NA> 1 FALSE 1 ""a_pieces는 예상되는 3(names의 길이)과 비교하여 몇 개의 조각이 발견되었는지 알려줍니다. a_remainder는 조각이 너무 적을 때는 유용하지 않지만 잠시 후에 다시 보게 될 것입니다.
이 디버깅 정보를 보면 구분 기호 전략에 문제가 있거나 분리하기 전에 전처리를 더 해야 한다는 것을 알 수 있습니다. 이 경우 업스트림에서 문제를 수정하고 too_few = "debug"를 제거하여 새로운 문제가 오류가 되도록 하세요.
다른 경우에는 누락된 조각을 NA로 채우고 계속 진행하고 싶을 수 있습니다. too_few = "align_start"와 too_few = "align_end"는 NA가 어디로 가야 하는지 제어할 수 있게 해줍니다:
df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z"),
too_few = "align_start"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 <NA>
#> 4 1 3 2
#> 5 1 <NA> <NA>조각이 너무 많은 경우에도 동일한 원칙이 적용됩니다:
df <- tibble(a = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))
df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z")
)
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `a`.
#> ! 2 values were too long.
#> ℹ Use `too_many = "debug"` to diagnose the problem.
#> ℹ Use `too_many = "drop"/"merge"` to silence this message.하지만 이제 결과를 디버깅할 때 a_remainder의 목적을 볼 수 있습니다:
debug <- df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z"),
too_many = "debug"
)
#> Warning: Debug mode activated: adding variables `a_ok`, `a_pieces`, and
#> `a_remainder`.
debug |> filter(!a_ok)
#> # A tibble: 2 × 7
#> x y z a a_ok a_pieces a_remainder
#> <chr> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1 3 5 1-3-5-6 FALSE 4 -6
#> 2 1 3 5 1-3-5-7-9 FALSE 5 -7-9너무 많은 조각을 처리하기 위한 약간 다른 옵션 세트가 있습니다: 추가 조각을 조용히 “삭제(drop)”하거나 모두 최종 열로 “병합(merge)”할 수 있습니다:
df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z"),
too_many = "drop"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 5
#> 4 1 3 2
#> 5 1 3 5
df |>
separate_wider_delim(
a,
delim = "-",
names = c("x", "y", "z"),
too_many = "merge"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 5-6
#> 4 1 3 2
#> 5 1 3 5-7-914.5 문자(Letters)
이 섹션에서는 문자열 내의 개별 문자로 작업할 수 있는 함수를 소개합니다. 문자열의 길이를 찾고, 하위 문자열을 추출하고, 플롯과 테이블에서 긴 문자열을 처리하는 방법을 배웁니다.
14.5.1 길이
str_length()는 문자열의 문자 수를 알려줍니다:
str_length(c("a", "R for data science", NA))
#> [1] 1 18 NA이것을 count()와 함께 사용하여 미국 아기 이름 길이의 분포를 찾은 다음 filter()와 함께 사용하여 가장 긴 이름(우연히 15자임)을 볼 수 있습니다7:
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
#> # ℹ 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
#> # ℹ 28 more rows14.5.2 부분집합
str_sub(string, start, end)를 사용하여 문자열의 일부를 추출할 수 있습니다. 여기서 start와 end는 부분 문자열이 시작하고 끝나야 하는 위치입니다. start 및 end 인수는 포함적이므로 반환된 문자열의 길이는 end - start + 1이 됩니다:
음수 값을 사용하여 문자열 끝에서부터 거꾸로 셀 수 있습니다: -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
#> # ℹ 1,924,659 more rows14.5.3 연습문제
- 아기 이름 길이의 분포를 계산할 때
wt = n을 사용한 이유는 무엇입니까? -
str_length()와str_sub()를 사용하여 각 아기 이름에서 중간 글자를 추출하세요. 문자열의 문자 수가 짝수이면 어떻게 하시겠습니까? - 시간이 지남에 따라 아기 이름 길이에 주요 추세가 있습니까? 첫 글자와 마지막 글자의 인기는 어떻습니까?
14.6 영어가 아닌 텍스트
지금까지 우리는 영어 텍스트에 초점을 맞췄는데, 두 가지 이유로 작업하기가 특히 쉽습니다. 첫째, 영어 알파벳은 비교적 간단합니다. 26개의 글자만 있습니다. 둘째(그리고 아마도 더 중요한 것은), 우리가 오늘날 사용하는 컴퓨팅 인프라는 주로 영어 사용자에 의해 설계되었습니다. 불행히도 영어가 아닌 언어를 완전히 다룰 지면이 없습니다. 그럼에도 불구하고 인코딩, 문자 변형, 로케일 의존 함수 등 여러분이 직면할 수 있는 가장 큰 과제 중 일부에 대해 주의를 환기시키고 싶었습니다.
14.6.1 인코딩(Encoding)
영어가 아닌 텍스트로 작업할 때 첫 번째 과제는 종종 인코딩입니다. 무슨 일이 일어나고 있는지 이해하려면 컴퓨터가 문자열을 표현하는 방법을 알아야 합니다. R에서는 charToRaw()를 사용하여 문자열의 기본 표현을 얻을 수 있습니다:
charToRaw("Hadley")
#> [1] 48 61 64 6c 65 79이 6개의 16진수 각각은 하나의 글자를 나타냅니다. 48은 H, 61은 a 등입니다. 16진수에서 문자로의 매핑을 인코딩이라고 하며, 이 경우 인코딩은 ASCII라고 합니다. ASCII는 미국 정보 교환 표준 코드(American Standard Code for Information Interchange)이기 때문에 영어 문자를 표현하는 데 훌륭합니다.
영어 이외의 언어에서는 상황이 그렇게 쉽지 않습니다. 컴퓨팅 초기에는 영어가 아닌 문자를 인코딩하기 위한 많은 경쟁 표준이 있었습니다. 예를 들어 유럽에는 두 가지 다른 인코딩이 있었습니다. 라틴1(일명 ISO-8859-1)은 서유럽 언어에 사용되었고 라틴2(일명 ISO-8859-2)는 중부 유럽 언어에 사용되었습니다. 라틴1에서 바이트 b1은 “±”이지만 라틴2에서는 “ą”입니다! 다행히 오늘날에는 거의 모든 곳에서 지원되는 하나의 표준이 있습니다: UTF-8. UTF-8은 오늘날 인간이 사용하는 거의 모든 문자와 이모티콘과 같은 많은 추가 기호를 인코딩할 수 있습니다.
readr은 모든 곳에서 UTF-8을 사용합니다. 이것은 좋은 기본값이지만 UTF-8을 사용하지 않는 오래된 시스템에서 생성된 데이터에 대해서는 실패할 것입니다. 이런 일이 발생하면 문자열을 인쇄할 때 이상하게 보일 것입니다. 때로는 한두 개의 문자가 엉망이 될 수 있고, 때로는 완전한 횡설수설을 얻을 수 있습니다. 예를 들어 비정상적인 인코딩을 가진 두 개의 인라인 CSV가 있습니다8:
이를 올바르게 읽으려면 locale 인수를 통해 인코딩을 지정합니다:
올바른 인코딩을 어떻게 찾습니까? 운이 좋다면 데이터 문서 어딘가에 포함되어 있을 것입니다. 불행히도 그런 경우는 드물기 때문에 readr은 파악하는 데 도움이 되는 guess_encoding()을 제공합니다. 완벽하지는 않고 텍스트가 많을 때(여기처럼 텍스트가 적을 때와 달리) 더 잘 작동하지만 시작하기에 합리적인 곳입니다. 올바른 것을 찾기 전에 몇 가지 다른 인코딩을 시도할 것을 예상하세요.
인코딩은 풍부하고 복잡한 주제입니다. 여기서는 겉만 핥았습니다. 더 배우고 싶다면 http://kunststube.net/encoding/의 자세한 설명을 읽어보는 것을 추천합니다.
14.6.2 문자 변형
악센트가 있는 언어로 작업하는 것은 악센트가 있는 문자가 단일 개별 문자(예: ü)로 인코딩되거나 악센트가 없는 문자(예: u)와 분음 부호(예: ¨)를 결합하여 두 문자로 인코딩될 수 있으므로 문자의 위치를 결정할 때(예: str_length() 및 str_sub() 사용) 상당한 어려움을 줍니다. 예를 들어 이 코드는 똑같이 보이는 ü를 표현하는 두 가지 방법을 보여줍니다:
그러나 두 문자열은 길이가 다르고 첫 번째 문자가 다릅니다:
str_length(u)
#> [1] 1 2
str_sub(u, 1, 1)
#> [1] "ü" "u"마지막으로 ==로 이 문자열들을 비교하면 다르다고 해석되는 반면 stringr의 편리한 str_equal() 함수는 둘 다 모양이 같다는 것을 인식합니다:
u[[1]] == u[[2]]
#> [1] FALSE
str_equal(u[[1]], u[[2]])
#> [1] TRUE14.6.3 로케일 의존 함수
마지막으로 동작이 로케일(locale) 에 따라 달라지는 소수의 stringr 함수가 있습니다. 로케일은 언어와 유사하지만 언어 내의 지역적 변형을 처리하기 위한 선택적 지역 지정자를 포함합니다. 로케일은 소문자 언어 약어로 지정되며 선택적으로 _와 대문자 지역 식별자가 뒤따릅니다. 예를 들어 “en”은 영어, “en_GB”는 영국 영어, “en_US”는 미국 영어입니다. 언어 코드를 아직 모른다면 위키백과에 좋은 목록이 있으며 stringi::stri_locale_list()를 보면 stringr에서 지원되는 코드를 볼 수 있습니다.
기본 R 문자열 함수는 운영 체제에서 설정한 로케일을 자동으로 사용합니다. 즉, 기본 R 문자열 함수는 해당 언어에 대해 예상하는 작업을 수행하지만 다른 국가에 사는 사람과 코드를 공유하면 코드가 다르게 작동할 수 있습니다. 이 문제를 피하기 위해 stringr은 “en” 로케일을 사용하여 영어 규칙을 기본값으로 사용하며 이를 재정의하려면 locale 인수를 지정해야 합니다. 다행히 로케일이 정말 중요한 함수 세트는 대소문자 변경과 정렬 두 가지뿐입니다.
대소문자 변경 규칙은 언어마다 다릅니다. 예를 들어 터키어에는 점이 있는 i와 점이 없는 i 두 가지가 있습니다. 두 개의 별도 문자이므로 대문자로 다르게 표기됩니다:
str_to_upper(c("i", "ı"))
#> [1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
#> [1] "İ" "I"문자열 정렬은 알파벳 순서에 따라 달라지며 알파벳 순서는 모든 언어에서 동일하지 않습니다9! 예를 들어 체코어에서 “ch”는 알파벳에서 h 뒤에 오는 복합 문자입니다.
이것은 dplyr::arrange()로 문자열을 정렬할 때도 나타나며, 이것이 locale 인수가 있는 이유입니다.
14.7 요약
이 장에서는 문자열 생성, 결합, 추출 방법과 영어가 아닌 문자열에서 직면할 수 있는 몇 가지 문제에 대해 stringr 패키지의 강력한 기능 중 일부를 배웠습니다. 이제 문자열 작업을 위한 가장 중요하고 강력한 도구 중 하나인 정규 표현식을 배울 때입니다. 정규 표현식은 문자열 내의 패턴을 설명하기 위한 매우 간결하지만 매우 표현력이 풍부한 언어이며 다음 장의 주제입니다.
또는 기본 R 함수
writeLines()를 사용하세요.↩︎R 4.0.0 이상에서 사용 가능합니다.↩︎
str_view()는 또한 색상을 사용하여 탭, 공백, 일치 항목 등을 강조합니다. 색상은 현재 책에 표시되지 않지만 대화식으로 코드를 실행할 때 알 수 있습니다.↩︎stringr을 사용하지 않는 경우
glue::glue()를 사용하여 직접 액세스할 수도 있습니다.↩︎동일한 원칙이
separate_wider_position()및separate_wider_regex()에도 적용됩니다.↩︎이 항목들을 보면 babynames 데이터가 공백이나 하이픈을 삭제하고 15자 이후를 자른다고 추측할 수 있습니다.↩︎
여기서는 특수
\x를 사용하여 이진 데이터를 문자열로 직접 인코딩하고 있습니다.↩︎중국어와 같이 알파벳이 없는 언어의 정렬은 훨씬 더 복잡합니다.↩︎