R) 전처리 - 문자열 분리

R) 전처리 - 문자열 분리

데이터프레임의 특정 변수에 있는 텍스트를 분리하는 다양한 방법을 초급/중급/고급 으로 나누어서 알아본다.

본 내용은 Apple 앱스토어에 등록된 앱 1000개 정보가 담긴 데이터 세트 “store_apple_app_1k.csv”를 활용한다.
store_apple_app_1k.csv 다운받기 [클릭]

1
2
3
4
5
6
7
8
9
10
11
df = read.csv("store_apple_app_1k.csv", encoding = "UTF-8")
head(df, 2)
## obs id track_name size_bytes currency price rating_count_tot
## 1 1 281656475 PAC-MAN Premium 100788224 USD 3.99 21292
## 2 2 281796108 Evernote - stay organized 158578688 USD 0.00 161065
## rating_count_ver user_rating user_rating_ver ver cont_rating prime_genre
## 1 26 4 4.5 6.3.5 4+ Games
## 2 26 4 3.5 8.2.2 4+ Productivity
## sup_devices.num ipadSc_urls.num lang.num vpp_lic
## 1 38 5 10 1
## 2 37 5 23 1

초급

실습에 사용할 문자열은 다음과 같다.

1
2
3
4
5
df[1:4, "track_name"]
## [1] "PAC-MAN Premium"
## [2] "Evernote - stay organized"
## [3] "WeatherBug - Local Weather, Radar, Maps, Alerts"
## [4] "eBay: Best App to Buy, Sell, Save! Online Shopping"

문자열을 분리하는 기본함수 strsplit()를 사용한 다섯 개의 예제를 보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
strsplit(df[1:4, "track_name"], split = " ") # 1
## [[1]]
## [1] "PAC-MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "-" "stay" "organized"
##
## [[3]]
## [1] "WeatherBug" "-" "Local" "Weather," "Radar," "Maps,"
## [7] "Alerts"
##
## [[4]]
## [1] "eBay:" "Best" "App" "to" "Buy," "Sell," "Save!" "Online"
## [9] "Shopping"

strsplit(df[1:4, "track_name"], split = ",") # 2
## [[1]]
## [1] "PAC-MAN Premium"
##
## [[2]]
## [1] "Evernote - stay organized"
##
## [[3]]
## [1] "WeatherBug - Local Weather" " Radar" " Maps"
## [4] " Alerts"
##
## [[4]]
## [1] "eBay: Best App to Buy" " Sell" " Save! Online Shopping"

strsplit(df[1:4, "track_name"], split = "[[:punct:]]") # 3
## [[1]]
## [1] "PAC" "MAN Premium"
##
## [[2]]
## [1] "Evernote " " stay organized"
##
## [[3]]
## [1] "WeatherBug " " Local Weather" " Radar" " Maps" " Alerts"
##
## [[4]]
## [1] "eBay" " Best App to Buy" " Sell" " Save"
## [5] " Online Shopping"

strsplit(df[1:4, "track_name"], split = "\\s") # 4
## [[1]]
## [1] "PAC-MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "-" "stay" "organized"
##
## [[3]]
## [1] "WeatherBug" "-" "Local" "Weather," "Radar," "Maps,"
## [7] "Alerts"
##
## [[4]]
## [1] "eBay:" "Best" "App" "to" "Buy," "Sell," "Save!" "Online"
## [9] "Shopping"

strsplit(df[1:4, "track_name"], split = "[[:blank:]]|[[:punct:]]") # 5
## [[1]]
## [1] "PAC" "MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "" "" "stay" "organized"
##
## [[3]]
## [1] "WeatherBug" "" "" "Local" "Weather" ""
## [7] "Radar" "" "Maps" "" "Alerts"
##
## [[4]]
## [1] "eBay" "" "Best" "App" "to" "Buy" "" "Sell"
## [9] "" "Save" "" "Online" "Shopping"

strsplit() 함수의 결과가 list로 반환되기 때문에 다음과 같이 unlist() 함수를 사용하여 1차원 벡터로 만들 수 있으나 기존의 구조가 풀어지기 때문에 상황에 맞게 써야 한다.

1
2
3
4
5
unlist(strsplit(df[1:4, "track_name"], split = " "))
## [1] "PAC-MAN" "Premium" "Evernote" "-" "stay" "organized"
## [7] "WeatherBug" "-" "Local" "Weather," "Radar," "Maps,"
## [13] "Alerts" "eBay:" "Best" "App" "to" "Buy,"
## [19] "Sell," "Save!" "Online" "Shopping"

각 코드 strsplit() 함수의 split 인자에 사용된 내용을 정리하면 다음과 같다.
- 1번: 한 칸 띄어쓰기
- 2번: 쉼표
- 3번: 특수문자(. , : ; ? ! \ | / ` = * + - ^ _ ~ “ ‘ [ ] { } ( ) < > @ # $)
- 4번: 공백
- 5번: 띄어쓰기, 탭 또는 특수문자

이렇게 strsplit() 함수는 결과가 list로 반환되기 때문에 분리한 문자열을 별도의 변수에 할당하는 것이 쉽지 않다. 다음 내용에서 이 단점을 극복하는 방법을 기술한다.

중급

이제 패키지의 힘을 빌려보자. 먼저 stringr 패키지의 str_split() 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
library("stringr")
str_split(df[1:4, "track_name"], pattern = " ")
## [[1]]
## [1] "PAC-MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "-" "stay" "organized"
##
## [[3]]
## [1] "WeatherBug" "-" "Local" "Weather," "Radar," "Maps,"
## [7] "Alerts"
##
## [[4]]
## [1] "eBay:" "Best" "App" "to" "Buy," "Sell," "Save!" "Online"
## [9] "Shopping"

stringr 패키지는 stringi C언어의 ICU 라이브러리를 기반으로 하는 패키지로 만들어졌다. 그래서 기본 함수 strsplit() 함수를 사용하는 것 보다 연산 속도가 빠르다. 하지만 연산속도가 빠르다고 해서 분리한 문자열을 새로운 변수로 할당하는 방법이 쉬워지는 것은 아니다. 다음으로 splitstackshape 패키지를 보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
library("splitstackshape")
cSplit(indt = df[1:4, 2:3], splitCols = "track_name", sep = " ") # 1
## id track_name_1 track_name_2 track_name_3 track_name_4 track_name_5 track_name_6
## 1: 281656475 PAC-MAN Premium <NA> <NA> <NA> <NA>
## 2: 281796108 Evernote - stay organized <NA> <NA>
## 3: 281940292 WeatherBug - Local Weather, Radar, Maps,
## 4: 282614216 eBay: Best App to Buy, Sell,
## track_name_7 track_name_8 track_name_9
## 1: <NA> <NA> <NA>
## 2: <NA> <NA> <NA>
## 3: Alerts <NA> <NA>
## 4: Save! Online Shopping

cSplit(indt = df[1:4, 2:3], splitCols = "track_name", sep = " ", direction = "long") # 2
## id track_name
## 1: 281656475 PAC-MAN
## 2: 281656475 Premium
## ...
## 21: 282614216 Online
## 22: 282614216 Shopping
## id track_name

cSplit(indt = df[1:4, 2:3], splitCols = "track_name", sep = " ", drop = FALSE) # 3
## id track_name track_name_1 track_name_2
## ## 1: 281656475 PAC-MAN Premium PAC-MAN Premium
## 2: 281796108 Evernote - stay organized Evernote -
## 3: 281940292 WeatherBug - Local Weather, Radar, Maps, Alerts WeatherBug -
## 4: 282614216 eBay: Best App to Buy, Sell, Save! Online Shopping eBay: Best
## track_name_3 track_name_4 track_name_5 track_name_6 track_name_7 track_name_8 track_name_9
## 1: <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 2: stay organized <NA> <NA> <NA> <NA> <NA>
## 3: Local Weather, Radar, Maps, Alerts <NA> <NA>
## 4: App to Buy, Sell, Save! Online Shopping

1번 코드의 결과를 보면 cSplit() 함수는 sep 인자에 할당된 규칙을 기반으로 문자열을 분리한다. 분리된 각 문자열은 자동으로 splitCols 인자에 할당된 변수명을 접두사로한 신규 변수에 하나씩 할당된다. 이 때 sep 인자에 할당된 규칙을 만족하는 문자가 없다면 결측인 <NA> 로 처리된다. 그리고 <NA> 는 factor형 변수의 결측이니 참고하도록 하자.

2번 코드를 보면 direction 인자에 “long”을 할당하였는데 기본값은 “wide” 이다. 문자열을 분리하다 보면 sep 에 할당한 규칙을 만족하는 문자가 너무 많을 경우 변수가 매우 많아지는데 이를 방지하기 위하여 column 개수가 많은 wide form 대신 row 개수가 많은 long form을 사용하는 것이 대안이 될 수 있다. 그리고 결과 예시는 너무 길어 중략하였다.

3번 코드를 보면 drop 인자에 FALSE를 할당하였는데 cSplit() 함수는 기본적으로 splitCols 인자에 지정되는 변수를 결과에서 제외한다. 그래서 이를 방지하고자 한다면 3번 코드를 참고하도록 하자.

앞에서 사용한 cSplit() 함수는 결과 값이 데이터테이블로 나오기 때문에 필요시 결과를 as.data.frame() 함수로 데이터프레임으로 변환 후 활용용하도록 한다.

1
2
3
string_sep = cSplit(indt = df[1:4, 2:3], splitCols = "track_name", sep = " ")
class(string_sep)
## [1] "data.table" "data.frame"

고급

이번 실습을 위해서는 다음과 같이 split_result 객체를 준비하자.

1
2
3
4
5
6
7
split_result = strsplit(df[1:2, "track_name"], split = " ")
split_result
## [[1]]
## [1] "PAC-MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "-" "stay" "organized"

텍스트를 분리하더라도 원하는 부분의 텍스트만 뽑고자 한다면 다음의 예제를 참고하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
lapply(X = split_result, FUN = "[", 1) # 1
## [[1]]
## [1] "PAC-MAN"
##
## [[2]]
## [1] "Evernote"

lapply(X = split_result, FUN = "[", i = 1) # 2
## [[1]]
## [1] "PAC-MAN"
##
## [[2]]
## [1] "Evernote"

lapply(X = split_result, FUN = "[", i = 1:2) # 3
## [[1]]
## [1] "PAC-MAN" "Premium"
##
## [[2]]
## [1] "Evernote" "-"

lapply(X = split_result, FUN = "[", i = c(1, 4)) # 4
## [[1]]
## [1] "PAC-MAN" NA
##
## [[2]]
## [1] "Evernote" "organized"

1번 코드는 list를 다루는 함수를 할당하는 FUN 인자에 대괄호를 사용한 것을 볼 수 있다. 이는 사실 인덱싱을 사용할 때 그 대괄호를 의미한다. 단순한 문법상의 특수문자 같지만 사실 이도 함수이며 1차원 객체의 경우 숫자를 입력하여 특정 위치의 원소를 추출할 수 있는데 이는 사실 i 라는 인자를 생략한 것이다. 그래서 해당 인자를 명시한 코드가 2번이 되겠다. 아무튼 1번 코드는 문자열을 분리하고 해당 문자열 뭉치에서 첫 번째 문자열을 반환한다. 이 역시 list 형식이라 별도의 변수에 할당을 위해서는 unlist() 함수를 활용해야 함을 잊지 말자.

3번 코드는 분리된 문자열 뭉치 중 첫 번째와 두 번째 문자열을 뽑는 코드이고 4번 코드는 연속이 아니라 특정 위치의 문자열을 가져온다. 여기서 지정한 위치에 문자열이 존재하지 않을 경우 결측치를 반환한다.

3번과 4번 코드의 경우 특정 변수에 그 결과를 할당하기 까다로운데 이 때는 다음과 같이 사용자 정의 함수나 일회성 함수를 활용하자.

1
2
3
4
5
6
7
8
9
10
11
lapply(X = split_result, 
FUN = function(x){paste(x[c(1, 2)], collapse = "@")})
## [[1]]
## [1] "PAC-MAN@Premium"
##
## [[2]]
## [1] "Evernote@-"

unlist(lapply(X = split_result,
FUN = function(x){paste(x[c(1, 2)], collapse = "@")}))
## [1] "PAC-MAN@Premium" "Evernote@-"
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×