텍스트 전처리(정규 표현식(Regular Expression)
1. 정규표현식 문법과 모듈 함수
파이썬에서는 정규 표현식 모듈 re를 진원한다. 이를 이용하면 특정 규치이 있는 텍스트 데이터를 빠르게 정제할 수 있다.
1) 정규 표현식 문법
정규 표현식을 위해 사용되는 문법 중 특수 문자들은 아래와 같다.
특수문자 | 설명 |
. | 한 개의 임의의 문자 ( 줄바꿈 문자인 \n은 제외 |
? | 앞의 문자가 존재할 수도 있고, 존재하지 않을 수도 있다. (문자가 0개 또는 1개) |
* | 앞의 문자가 무한개로 존재할 수도 있고, 존재하지 않을 수도 있다.(문자가 0개 이상) |
+ | 앞의 문자가 최소 한 개 이상 존재합니다.(문자가 1개 이상) |
^ | 뒤의 문자로 문자열이 시작됩니다. |
$ | 앞의 문자로 문자열이 끝납니다. |
{숫자} | 숫자만큼 반복합니다. |
{숫자1, 숫자2} | 숫자1 이상 숫자2 이하만큼 반복합니다. ?, *, +를 이것으로 대체할 수 있습니다. |
{숫자,} | 숫자 이상만큼 반복합니다. |
[] | 대괄호 안의 문자들 중 한 개의 문자와 매치합니다. [amk]라고 한다면 a 또는 m 또는 k 중 하나라도 존재하면 매치를 의미합니다. [a-z]와 같이 범위를 지정할 수도 있습니다. [a-zA-z]는 알파벳 전체를 의미하는 범위이며, 문자열에 알파벳이 존재하면 매치를 의미합니다. |
[^문자] | 해당 문자를 제외한 문자를 매치합니다. |
| | A|B와 같이 쓰이며 A 또는 B의 의미를 가집니다. |
정규 표현식 문법에는 역 슬래쉬(\)를 이용하여 자주 쓰이는 문자 규칙들이 있습니다.
문자 규칙 | 설명 |
\ | 역 슬래쉬 문자 자체를 의미합니다. |
\d | 모든 숫자를 의미합니다. [0-9]와 의미가 동일합니다. |
\D | 숫자를 제외한 모든 문자를 의미합니다. [^0-9]와 의미가 동일합니다. |
\s | 공백을 의미합니다. [\t\n\r\f\v]와 의미가 동일합니다. |
\S | 공백을 제외한 문자를 의미합니다. [^\t\n\r\f\v]와 의미가 동일합니다. |
\w | 문자 또는 숫자를 의미합니다. [a-zA-Z0-9]와 의미가 동일합니다. |
\W | 문자 또는 숫자가 아닌 문자를 의미합니다. [^a-zA-Z0-9]와 의미가 동일합니다. |
2) 정규 표현식 모듈 함수
정규표현식 모듈에서 지원하는 함수는 이와 같습니다.
모듈 함수 | 설명 |
re.compile() | 정규표현식을 컴파일하는 함수다. 다시 말해, 파이썬에게 전해주는 역할을 한다. 찾고자 하는 패턴이 빈번한 경우에는 미리 컴파일해놓고 사용하면 속도와 편의성면에서 유리합니다. |
re.search() | 문자열 전체에 대해서 정규표현식과 매치되는지를 검색합니다. |
re.match() | 문자열의 처음이 정규표현식과 매치되는지를 검색합니다. |
re.split() | 정규 표현식을 기준으로 문자열을 분리하여 리스트로 리턴합니다. |
re.findall() | 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열을 찾아서 리스트로 리턴합니다. 만약, 매치되는 문자열이 없다면 빈리스트가 리턴됩니다. |
re.finditer() | 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열에 대한 이터레이터 객체를 리턴합니다. |
re.sub() | 문자열에서 정규 표현식과 일치하는 부분에 대해서 다른 문자열로 대체합니다. |
2. 정규 표현식 실습
1) .기호
.은 한 개의 임의의 문자를 나타낸다. 예를 들어서 정규 표현식이 a.c라고 한다면 a와 c 사이에는 어떤 1개의 문자라도 올 수 있다. 즉 akc, azc, avc, a5c, a!c와 같은 형태는 모두 a.c의 정규표현식과 매치된다.
import re
r = re.compile('a.c')
print(r.search('kkk')) # 결과 출력 X
print(r.search('abc'))
None
<re.Match object; span=(0, 3), match='abc'>
2) ?기호
?는 ? 앞의 문자가 존재할 수 도 잇고, 존재하지 않을 수도 있는 경우를 나타냅니다. 예를 들어서 정규 표현식이 ab?c라고 합시다. 이경우 이 정규 표현식에서의 b는 있다고 취급할 수도 있고, 없다고 취급할 수도 있습니다. 즉, abc와 ac 모두 매치할 수 있습니다.
r = re.compile('ab?c')
print(r.search('abbc')) # 결과 출력 X
print(r.search('abc'))
print(r.search('ac'))
None
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 2), match='ac'>
두번째와 세번째 경우만 b가 있는경우 b가 없는 경우를 판단하여 매치 시켜준다.
3) *기호
*은 바로 앞의 문자가 0개 이상일 경우를 나타냅니다. 앞의 문자는 존재하지 않을 수도 있으며, 또는 여러 개일 수도 있습니다. 예를 들어서 정규표현식이 ab*c라고 한다면 ac, abc, abbc, abbbc등과 매치할 수 있으며 b의 갯수는 무수히 많아도 상관 없다.
import re
r = re.compile('ab*c')
print(r.search('a')) # 결과 출력 x
print(r.search('ac'))
print(r.search('abc'))
print(r.search('abbbbbc'))
None
<re.Match object; span=(0, 2), match='ac'>
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 7), match='abbbbbc'>
4) +기호
+는 *와 유사하다. 하지만 다른 점은 앞의 문자가 최소 1개 이상이어야 한다는 점이다.예시로 정규 표현식이 ab+c라고 한다면, ac는 매치되지 않는다. 하지만 abc,abbbc등과 매치할 수 있으며 b의 갯수는 무수히 많을 수 있다.
import re
r = re.compile('ab+c')
print(r.search('ac')) # 결과 출력x
print(r.search('abc'))
print(r.search('abbbbbbc'))
None
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 8), match='abbbbbbc'>
5) ^기호
^는 시작되는 글자를 지정한다. 정규표현식이 ^a라면 a로 시작되는 문자열만을 찾아낸다.
import re
r = re.compile('^a')
print(r.search('bbc'))
print(r.search('ab'))
None
<re.Match object; span=(0, 1), match='a'>
6) {숫자} 기호
문자에 해당 기호를 붙이면, 해당 문자를 숫자만큼 반복한 것이다. 예시로 정규표현식이 ab{2}c 라면 a와 c 사이에 b가 존재하면서 b가 2개인 문자열에 대해서 매치합니다.
import re
r = re.compile('ab{2}c')
print(r.search('ac'))# 결과 출력X
print(r.search('abc'))#결과 출력X
print(r.search('abbc'))
print(r.search('abbbbbbc'))
None
None
<re.Match object; span=(0, 4), match='abbc'>
None
7) {숫자1, 숫자2} 기호
문자에 해당 기호를 붙이면, 해당 문자를 숫자1 이상 숫자2 이하만큼 반복한다. 예시로 정규 표현식이 ab{2,8}c라면 a와 c 사이에 b가 존재하면서 b는 2개 이상 8개 이하인 문자열에 대해서 매치한다.
import re
r = re.compile('ab{2,8}c')
print(r.search('ac'))# 결과 출력x
print(r.search('abc'))# 결과 출력x
print(r.search('abbc')) # b2개
print(r.search('abbbbbbbbc')) # b8개
print(r.search('abbbbbbbbbc'))#결과 출력 x b9개
None
None
<re.Match object; span=(0, 4), match='abbc'>
<re.Match object; span=(0, 10), match='abbbbbbbbc'>
None
8) {숫자,} 기호
문자에 해당 기호를 붙이면 해당 문자를 숫자 이상 만큼 반복한다. 예시로 정규표현식이 a{2,}bc라면 뒤에 bc가 붙으면서 a의 갯수가 2개 이상인 경우인 문자열과 매치된다. 또한 만약 {0,}을 쓴다면 *와 동일한 의미가 되며, {1,}을 쓴다면 +와 동일한 의미다.
import re
r = re.compile('a{2,}bc')
print(r.search('bc')) # 결과 출력X
print(r.search('aa')) # 결과 출력x
print(r.search('aabc')) # a 2개
print(r.search('aaaaaaaabc')) # a 8개
None
None
<re.Match object; span=(0, 4), match='aabc'>
<re.Match object; span=(0, 10), match='aaaaaaaabc'>
9) [] 기호
[]안에 문자들을 넣으면 그 문자들 중 한 개의 문자와 매치라는 의미를 가진다. 예시로 정규 표현식이 [abc]라면 a 또는 b 또는 c가 들어가 있는 문자열과 매치된다. 범위를 지정하는 것도 가능하다. [a-zA-Z]는 알파벳 전부를 의미하며, [0-9]는 숫자 전부를 의미한다.
# 알파벳 소문자 abc에 대해서 정규표현식 만들기
import re
r = re.compile('[abc]') # [abc]는 [a-c]와 같음
print(r.search('zzz')) # 결과 출력 X
print(r.search('a'))
print(r.search('aaaaaaa'))
print(r.search('baac'))
None
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='b'>
# 알파벳 소문자에 대해서 범위 지정하여 정규표현식 만들기
import re
r = re.compile('[a-z]')
print(r.search('AAA'))# 결과 출력x
print(r.search('aBC'))
print(r.search('111'))#결과 출력x
None
<re.Match object; span=(0, 1), match='a'>
None
10) [^문자] 기호
[^문자]는 5)에서 말한 것과 같이 ^와는 다른 의미로 쓰이게 된다. ^기호 뒤에 문자들을 제외한 모든 문자를 매치하는 역할을 한다. 예시로 [^abc]라는 정규 표현식이라면 a 또는 b 또는 c가 들어간 문자열을 제외한 모든 문자열을 매치한다.
# [^문자]
import re
r = re.compile('[^abc]')
print(r.search('a')) # 결과출력 x
print(r.search('ab'))# 결과출력 x
print(r.search('b')) # 결과출력 x
print(r.search('d'))
print(r.search('1'))
None
None
None
<re.Match object; span=(0, 1), match='d'>
<re.Match object; span=(0, 1), match='1'>
3. 정규 표현식 모듈 함수 예제
1) re.match()dhk re.search()의 차이
search()가 정규 표현식 전체에 대해서 문자열이 매치하는지를 본다면, match()는 문자열의 첫 부분부터 정규표현식과 매치하는지를 확인한다. 문자열 중간에 찾을 패턴이 있다고 하더라도, match 함수는 문자열의 시작에서 패턴이 일치하지 않다면 찾지 않는다.
# re.match()와 re.search()의 차이
import re
r = re.compile('ab.')
print(r.search('kkkabc')) # 문자열의 중간에 있더라도 정규표현식을 만족함
print(r.match('kkkabc'))# 문자열의 첫부분에서 정규표현식을 만족하지 않아 결과 출력 x
print(r.match('abckkk')) # 문자열의 첫부분에서 정규표현식을 만족함
<re.Match object; span=(3, 6), match='abc'>
None
<re.Match object; span=(0, 3), match='abc'>
2) split() 함수는 이력된 정규 표현식을 기준으로 문자열들을 분리하여 리스트로 리턴한다. 토큰화에 유용하게 활용되기에 자연어 처리에 있어서 가장 많이 사용되는 정규 표현식 함수 중 하나이다.
import re
text = '사과 딸기 수박 메론 바나나'
re.split(' ', text) # 공백을 기준으로 문자열 분리
['사과', '딸기', '수박', '메론', '바나나']
import re
text = """사과
딸기
수박
메론
바나나"""
re.split('\n', text) # 줄바꿈을 기준으로 문자열 분리
import re
text = '사과+딸기+수박+메론+바나나'
re.split("\+",text) # +기호를 기준으로 문자열 분리
['사과', '딸기', '수박', '메론', '바나나']
3) re.findall()
findall() 함수는 정규 표현식과 매치되는 모든 문자열들을 리스트로 리턴한다. 단, 매치되는 문자열이 없다면 빈 리스트를 리턴한다.
import re
text = """이름 : 김철수
전화번호 : 010 - 1234 - 1234
나이 : 30
성별 : 남"""
print(re.findall('\d+', text)) # 숫자만 찾아내서 리스트로 리턴
print(re.findall("\d+", '문자열입니다.')) # 문자열입니다. 안에 숫자가 없기에 빈리스트로 리턴
4) re.sub()
sub() 함수는 정규 표현식 패턴과 일치하는 문자열을 찾아 다른 문자열로 대체한다.
import re
text = "Regular expression : A regular expression, regex or regexp[1] (sometimes called a rational expression)[2][3] is, in theoretical computer science and formal language theory, a sequence of characters that define a search pattern"
re.sub('[^a-zA-Z]',' ', text) # 문자열이 아닌것들은 공백처리
'Regular expression A regular expression regex or regexp sometimes called a rational expression is in theoretical computer science and formal language theory a sequence of characters that define a search pattern'
문자열이 아닌 것들을 전부다 공백으로 처리
5. 정규 표현식 텍스트 전처리 예제
import re
text = """100 John PROF
101 James STUD
102 Mac STUD"""
print(re.split('\s+', text)) # 최소 1개 이상의 공백인 패턴을 기준으로 split
print(re.findall('\d+', text)) # 숫자로 구성된 데이터뽑아옴
print(re.findall('[A-Z]', text)) # 정규표현식에 대문자라는 기준만 넣은 경우 문자열을 가져오는 것이 아닌 모든 대문자 각각을 가져온다.
print(re.findall('[A-Z]{4}', text)) # 대문자가 연속으로 4번 등장하는 경우
print(re.findall('[A-Z][a-z]+', text)) # 대문자와 소문자가 섞여있는 상황에서 이름에 대한 행의 값을 가져오려면 처음에 대문자가 등장하고 그후에 소문자가 여러번 등장하는경우
['100', 'John', 'PROF', '101', 'James', 'STUD', '102', 'Mac', 'STUD']
['100', '101', '102']
['J', 'P', 'R', 'O', 'F', 'J', 'S', 'T', 'U', 'D', 'M', 'S', 'T', 'U', 'D']
['PROF', 'STUD', 'STUD']
['John', 'James', 'Mac']
6. 정규 표현식을 이용한 토큰화
NLTK에서는 정규 표현식을 사용해서 단어 토큰화를 수행하는 RegexpTokenizer를 지원한다. RegxpTokenizer()에서 괄호 안에 원하는 정규 표현식을 넣어서 토큰화를 수행한다.
import nltk
from nltk.tokenize import RegexpTokenizer
#문자 또는 숫자가 1개 이상인 경우를 인식하는 코드이다. 문장에서 구두점을 제외하고 단어들만을 가지고 토큰화를 수행한다.
tokenizer=RegexpTokenizer("[\w]+")
print(tokenizer.tokenize("Don't be fooled by the dark sounding name, Mr. Jone's Orphanage is as cheery as cheery goes for a pastry shop"))
['Don', 't', 'be', 'fooled', 'by', 'the', 'dark', 'sounding', 'name', 'Mr', 'Jone', 's', 'Orphanage', 'is', 'as', 'cheery', 'as', 'cheery', 'goes', 'for', 'a', 'pastry', 'shop']
import nltk
from nltk.tokenize import RegexpTokenizer
# gaps = True는 정규 표현식을 토큰으로 나누기 위한 기준으로 사용한다는 것이다. gaps = True라는 부분을 기재하지 않는다면, 토큰화의 결과는 공백만 나오게된다.
tokenizer = RegexpTokenizer("[\s]+", gaps = True)
print(tokenizer.tokenize("Don't be fooled by the dark sounding name, Mr. Jone's Orphanage is as cheery as cheery goes for a pastry shop"))
["Don't", 'be', 'fooled', 'by', 'the', 'dark', 'sounding', 'name,', 'Mr.', "Jone's", 'Orphanage', 'is', 'as', 'cheery', 'as', 'cheery', 'goes', 'for', 'a', 'pastry', 'shop']
참고문헌 : https://wikidocs.net/21703