Python은 다양한 분야에서 활용될 수 있는 프로그래밍 언어이다. 기존에 많이 사용하던 Java나 C와는 달리 문법 자체가 워낙 익히기 쉬운 구조라, 일반적인 프로그램 코딩은 물론이거나와, RasberryPi 코딩, 머신 러닝 등 폭 넓게 이용되는 언어다.
특히 기존의 프로그래밍언어로는 작업이 어려웠던 자료의 수집과 수집된 자료의 분석, 분석 결과의 저장 작업, 즉 통계를 위한 수집과 분석 작업에도 사용 빈도가 높아짐에 따라, 국내 기사 자격증 중 하나인 빅데이터 분석기사의 실기에서도 R과 함께 주요 프로그래밍 언어로 지정되어 있다.
필자 역시, 빅데이터 분석기사 취득을 위한 실기 시험 준비 뿐만 아니라 최근에 회사 업무에서 대용량의 데이터를 처리할 일이 많아지는 바람에 Python을 이용한 데이터 분석 과정에 대해 독학을 시작하게 되었다(물론, 요즘 일이 많아서 벌려놓은 독학 주제는 많은데 하나도 제대로 마무리를 못하고 있는게 문제긴 하지만...).
이번 포스팅에서는 Python을 사용한 데이터 분석의 가장 기본이 되는 정규 표현식 모듈(re)의 간략한 사용방법에 대해 알아보려 한다.
1. 정규표현식(Regex)이란?
정규 표현식은 문자열 데이터에서 특정 조건을 인식할 수 있도록 만드는 언어의 한 종류라고 할 수 있다. 예를 들어, 다음과 같이 사람 이름이 나열되어 있다고 가정해보자.
Diana
Alexander
Boris
Pavlov
Paul
Alisa
Aaron
James
...
여기서 A로 시작하는 이름만 별도로 추출하고 싶은데, 목록에 작성된 이름이 많으면 많을수록 수작업으로는 어려워진다. 설령 목록에 적혀있는 양이 적더라도, 수작업을 진행하면 인간의 실수(Human Error)가 당연히 발생하기도 하고 말이다. 이러한 상황에서, 정규표현식을 통해 A로 시작되는 이름을 추릴 수 있다.

위의 화면은 Regex Buddy라는 프로그램으로, 텍스트 내의 특정 문자열을 정규표현식을 통해 찾을 수 있도록 제작한 프로그램이다. 위와 같이, A.*(A로 시작하면서 여러 글자가 있는)을 정규표현식으로 사용함으로써, A로 시작하는 이름을 별도로 추출할 수 있다.
대부분의 OS나 프로그래밍 언어에서 제공하는 정규표현식은 비슷한 형태를 띈다. 위의 Regex Buddy의 경우 조금은 형태가 다르긴 하지만, 기본적인 Regex에 대해 알아놓으면 Python으로 정규표현식을 사용하여 특정 문자열을 검출하는 방식은 크게 어렵지 않다.
그럼, 본격적으로 Python의 re 모듈에 대해 알아보자.
2. Python re 모듈
re 모듈은 정규표현식의 REgex에서 앞 두 글자를 따서 만들어진 Python 기본 모듈이다. 기본 모듈이기 때문에, Python이 설치만 되어 있어도 사용에 큰 무리가 없다.

re 모듈을 사용하여 우리가 원하는 글자를 찾으려면, 먼저 우리가 찾으려는 문자열의 공통적인 특징을 알아내야 한다. 그 다음으로 공통적인 특징으로부터 만들어낼 수 있는 정규표현식을 문법에 맞게 작성해야하며, 마지막으로 정규표현식 패턴에 맞는 문자를 찾을 수 있도록 python 코드를 작성하면 된다.
그럼, 앞서 들었던 예를 다시 확인해보자. 이름 목록에서 A로 시작되는 이름만 모두 추출해야한다고 하면, 필자가 필요로하는 정규표현식의 형태는 다음과 같아진다.
* 정규표현식: ^A.+ ---> A로 시작하고 뒤에 문자가 하나 이상 나와야 한다는 의미다. 자세한 내용은 뒤에서...
이름 목록에서 정규표현식 패턴에 맞는 내용만 정확하게 찾으려면, 우선 정규표현식을 패턴으로 등록하는 과정이 필요하다. 이 때 re 모듈의 compile이라는 함수를 사용한다.
(1) re.compile()
일반적인 의미의 컴파일은 사람의 언어를 기계어(0,1)로 변환하는 것을 말한다. Python의 re 모듈도 이와 크게 다르지 않은데, 사람이 - 나름대로 - 인식할 수 있는 정규표현식을 컴퓨터가 알아들을 언어로 바꿔주는 과정에 사용하는 함수라고 보면 된다. Python에서 정규표현식으로 패턴을 등록할 경우, 함수는 다음과 같이 사용한다.
pattern = re.compile (r"<정규표현식>", 옵션)
re.compile 함수는 함수 자체의 역할 때문에라도 반드시 정규표현식이 인자로 들어가야 한다. 두 번째 인자는 옵션인데 필요에 따라 사용하는 부분이 커서 조금 뒤에 별도로 설명하려 한다. 어쨌든 re.compile을 통해 필자가 뽑아내려는 이름에 필요한 패턴을 정의하면 다음과 같이 된다.


cmd 창에서 Python을 인터렉티브 모드로 실행하여 Pattern을 정의한 뒤, pattern의 타입을 확인해보면, re.Pattern이라는 클래스로 정의된 것이 나온다. 일반적인 경우라면 문자열(str)로 나타날 것이라 생각하겠지만, 정규표현식의 역할 특성 상, 문자열 대신 re.Pattern이라는 객체로 정의가 된다.
자... 그럼 필자가 원하는 정규표현식으로의 Pattern 정의는 완료가 되었다. 그럼 저 이름 목록과 일치하는지는 어떻게 확인하면 될까? 이는 re.Pattern 의 match 또는 search 함수를 사용하면 된다. 이 때문에 pattern 변수가 re.Pattern 객체의 형태를 띄는 것이다.
(2) re.Pattern.match, re.Pattern.search()
필자가 정의한 변수 pattern은 Python 클래스의 한 형태다. 그렇기 때문에 클래스 내부에서 사용하는 함수(메서드)도 당연히 존재한다. 이 함수들 중 match()와 search()라는 녀석들이 pattern에 일치하는 문자열을 찾는데 사용이 된다. 두 함수는 사용 방법이 동일하기 때문에 match() 함수만 별도로 설명을 진행한다.
match() 함수는 Pattern과 일치하는 문자열을 인자로 사용한다. 따라서, 필자가 정의한 pattern에 맞는 문자가 있는지 확인하려면 다음과 인자에 검증할 문자열이나 문자열 변수를 입력해주면 된다.
match("검증할 문자열 또는 문자열 변수")

잘 보면 결과로 반환된 값 중 span이라는 것이 보이는데, 이는 Alex라는 문자열의 첫 번째(0, A를 의미한다)부터 4번째(x를 의미한다)까지 모두 정규표현식의 패턴에 일치한다는 의미이다. 이번에는 조금 긴 문자열을 넣어보자.

Breta loves Alex라는 문구를 검증 문구로 사용하였는데, pattern에 매치되는 내용이 없는 것으로 떨어진다. 이는 문자열 자체에 Alex가 포함되어 있긴 하지만, 문자열이 A로 시작되지도 않았기 때문이다. 따라서, Alex와 Breta의 자리를 바꾸어 다시 패턴 검증을 하면 다음과 같이 결과가 나타나게 된다.

search() 함수는 match() 함수와 유사한 기능을 가지지만 조금 다른 양상을 띈다. match() 함수의 경우, 문자열의 시작부분에 매칭되는 내용이 없다면 아무 값도 반환하지 않지만, search() 함수의 경우 문자열을 검증하는 과정에서, 전체 내용을 스캔한 뒤, 일치하는 문자가 있다면 그 내용을 반환하게 된다. 따라서 한 줄의 문자열로 이루어진 경우, search와 match는 큰 차이점을 보이지 않는다. 이 내용은 뒤에서도 잠깐 시연할 예정이다.

(3) re.Pattern.match(), re.Pattern.search() 의 두 번째 인자에 대해서...
** re.I (re.IGNORECASE)
필자가 다음과 같이 작성된 이름 목록을 가지고 있고, 이 이름 목록에서 앞서 보았던 것과 마찬가지로 A로 시작하는 이름에 대해 별도로 추출을 진행하려고 한다.

for 문을 사용하여 각 이름마다 패턴 검증을 진행할 것이기 때문에, 코드는 다음과 같이 작성될 것이다.

결과는 다음과 같이 나온다.

실제로 Alex, Aaron, Alisa 3 명이 A로 시작되는 이름을 가지고 있음을 알 수 있다.
이제, 필자는 인간의 실수를 유발해보려한다. name_list에 이름을 작성하는 직원이 실수로 소문자 a로 시작하는 이름을 명단에 넣었다고 가정해보자.

중간에 abstein이라는 이름이 추가되었는데, 다른 이름과 달리 소문자로 시작한다. 따라서 이 이름의 경우, 패턴 검증에 발견되지 않게 된다.

이러한 문제는 어떻게 해결하면 좋을까? 정규표현식에서 ^[Aa] 라는 문장으로 A 또는 a로 시작하는 이름을 다 매칭하도록 하는 방법도 있지만, match()와 search() 함수의 두 번째 인자를 활용하면 복잡한 정규표현식 없이도 문제를 해결할 수 있다.
re 모듈을 dir() 함수로 확인해보면 IGNORECASE라는 녀석이 보인다. 이 녀석은 패턴 내 정규표현식에 알파벳이 들어간 경우, 해당 알파벳의 대소문자 구분을 없애주는 역할을 한다고 보면 된다.
다시 pattern 변수를 다음과 같이 정의하고 동일한 코드를 실행하면, 이전과 달리 abstein도 추출되어 나옴을 확인할 수 있다.

** re.M (re.MULTILINE)
re.M은 조금 특수한 상황에서 사용하는 옵션이다. Python의 경우, 한 줄의 문자열을 변수에 담을 수도 있지만, 여러 줄의 문자열의 변수에 담는 것도 가능하다. 예를 들어, 필자가 앞에서 작성했던 name_list는 다음과 같이 name_text 변수처럼 여러 줄로 정의할 수도 있다.

그런데, 이렇게 여러 줄로 된 변수의 경우, 개행 문자(\n)의 존재로 인해 re.Pattern.search()나 re.Pattern.match() 함수로는 패턴 검증이 불가능하다. 너무 당연한 소리지만 문자열의 시작이 D 이기 때문에 절대 패턴검증에 걸릴 수가 없다.

여러 줄의 문자열로 정의된 변수의 경우, split("\n")으로 변수를 쪼개고 for 문으로 넣어 검증하기에는 비효율적인 면이 있다. 따라서 re 모듈에서는 MULTILINE이라는 녀석을 통해 여러 줄로 이루어진 문자열에 대해 패턴 검증을 할 수 있는 기능을 지원한다.

앞서 match와 search의 차이점에 대해서 설명했듯이, match() 함수의 경우, 첫 줄에 필자가 정의한 패턴과 일치되는 내용이 나타나지 않아 빈 값을 반환함에 반해, search() 함수는 변수에 정의된 여러 줄의 문자열을 스캔한 뒤, 패턴이 일치하는 내용이 있는 경우, 그 부분에 대한 정보를 반환한다. 따라서 re.M 옵션의 경우, re.Pattern.match()보다 re.Pattern.search() 함수와 사용되는 빈도가 극히 높을 수 밖에 없다.
(3) re.match(), re.fullmatch(), re.search()
마지막으로 re 모듈의 하위에 직접 정의된 match(), fullmatch(), search() 함수에 대해 알아보자. 세 함수는 정규표현식 패턴이 정의되어 있지 않은 상황에서, 시험 용도나 간단한 패턴 검증 진행 시 사용하는 함수라고 보면 된다. 인자는 두 가지가 필수로 들어가는데, 하나는 정규표현식 패턴, 다른 하나는 검증 문자열이다.
re.match(정규표현식, 검증문자열)
re.fullmatch(정규표현식, 검증문자열)
re.search(정규표현식, 검증문자열)
match와 search의 경우, 앞서 re.Pattern에서도 언급한 내용과 동일하게, 문자열의 시작지점에서 일치되는 것을 찾을 것이냐, 아니면 전체 내용을 스캔한 뒤 일치하는 내용을 찾을 것이냐의 차이만 존재한다. fullmatch는 이들 둘과 상당히 다른 점을 보이는데, 반드시 정규표현식과 동일한 모양을 띄어야만 그 결과값을 반환한다는 차이점이 있다.
필자가 text 변수에 "One black dog chases a little white dog"라는 문장을 정의하고, 여기에 각 함수를 패턴 One black dog로 검증한다고 해보자.

마찬가지로 white dog에 대해서도 검증을 하면 search 외에는 반환값이 나타나지 않음을 알 수 있다.

조금 더 복잡한 정규표현식으로 One black dog를 검증하면 다음과 같이 fullmatch() 함수를 제외하고 결과가 반환되는 것을 확인할 수 있다.

3. 정규표현식 패턴으로 추출한 결과를 하나의 그룹으로 묶을 수 없을까?
필자가 re.Pattern.match() 또는 re.Pattern.search()로 추출한 결과들은 일반적인 문자열로 나타나지 않는다. 그 결과를 다시 보면 알겠지만 모두 re.Match 객체로 반환된다.

따라서, 이 결과들을 문자열 클래스의 함수(메서드)인 split 이나 strip 등으로 갈무리하여 별도로 추출하는 작업은, 가능하긴 하지만 매우 비효율적이다.

따라서 re.compile 함수 내의 정규표현식 인자 자리에는, 인자들이 하나의 그룹으로 묶일 수 있는 기능을 제공하는 문법이 제공된다. 바로 (?P<그룹명>정규표현식) 이다. 정규표현식에 일치하는 문자는 모두 그룹명으로 지정된 그룹에 포함이 되는데, 그룹에 포함된 추출 결과는 re.Pattern.match() 또는 re.Pattern.search()로 검색된 re.Match 클래스의 함수(메서드)인 group()에 의해 출력할 수 있다.

개개인의 개별 정보가 CSV(Comma Seperated Values)로 작성되어 있는 경우, re.Match로 나타나는 이들 그룹 내 결과는 dictionary 형태로 별도 추출이 가능하기도 하다. 아래의 예제를 보자.

위의 스크린샷은 개개인의 이름, 전화번호, 이메일 주소가 저장된 CSV 파일을 Python으로 불러들여 읽은 결과다. 여기서 이름, 전화번호, 이메일은 나열 순서가 동일하기 때문에, 정규표현식을 잘만 이용한다면 이들 정보를 손쉽게 분류하여 보기 쉬운 Dictionary 형태로 변환할 수 있다.
필자는 이름과 전화번호, 이메일을 구분하기 위한 정규표현식 및 그룹 지정을 아래와 같이 작성하려 한다.
pattern = re.compile(r"(?P<name>\S+),(?P<cellphone>\S+),(?P<email>\S+)")
위의 결과로 나타난 re.Match 클래스를 group() 외에도 groupdict()라는 함수로도 추출이 가능한데, groupdict()의 경우, 그룹명과 값을 Dictionary 형태로 변환하여 출력하는 기능을 가진다.

이번 포스팅에서는 Python의 re 모듈을 사용하여 정규표현식 패턴과 일치하는 문자열을 검색하는 방법에 대해 알아보았다. 다음 포스팅에서는 정규 표현식에서 사용하는 문법 종류에 대해 알아보려 한다.
Fin.
'Python > Python DataAnalysis' 카테고리의 다른 글
[Python Data Analysis] 6. DataFrame 파일 입출력 (0) | 2021.11.08 |
---|---|
[Python Data Analysis] 5. DataFrame, Index 관련 매서드 (0) | 2021.11.03 |
[Python Data Analysis] 4. DataFrame 객체 (0) | 2021.11.02 |
[Python Data Analysis] 3. Pandas 모듈 (0) | 2021.09.20 |
[Python Data Analysis] 2. Python 정규표현식(REGEX) (0) | 2021.09.12 |
댓글