728x90
반응형

이전 포스트까지 정규 표현식이 무엇인지, Python에서 정규 표현식을 어떻게 탐색하는지에 대해 알아보았다. 이번 포스트에서는 가장 중요한 "메타 문자로 정규 표현식 패턴 만들기"에 대해 알아보도록 하겠다.

 

 

 

1. 메타 문자로 정규 표현식 패턴 만들기

  • 메타 문자로 정규 표현식 만들기는 크게 3가지 방법으로 돌아간다.
  1. 패턴 문자열 생성: 찾고자 하는 패턴 문자열(문자 클래스)을 생성한다.
  2. 패턴 문자의 반복 지정: 패턴 문자열이 몇 번 반복되는지 지정하기
  3. 패턴 문자의 위치 지정: 패턴 문자열로 시작하거나 패턴 문자열로 끝나는지 지정한다.
  • 위 3개 과정은 다 함께 진행될 수도 있고, 하나만 진행될 수도 있다. 이 외에도 문자의 패턴들을 그룹으로 만들거나 |(or)를 이용하여, 원하는 패턴이 존재하는 경우를 선택하게 할 수도 있다.
  • 각 부분에 해당하는 메타 문자는 이전 포스트인 <Python-기초:3.0. 정규 표현식(1) - 소개: 정규 표현식과 메타 문자>를 보면 깔끔하게 표로 정리해놨으니, 이를 참고하기 바란다.

 

 

 

 

2. 패턴 문자열 생성 하기

2.1. 문자 클래스 [ ]

  • 문자 클래스(character class)라고 하며, [ ] 안에는 1개 이상의 문자를 넣을 수 있고, 그 문자 중 하나라도 일치한다면 일치하는 패턴으로 판단한다.
# [abc]라는 정규 표현식 패턴 생성
>>> pattern = "[abc]"
>>> regex_pattern = re.compile(pattern)

>>> Text_cat = "cat"
>>> print(regex_pattern.findall(Text_cat))
<re.Match object; span=(0, 1), match='c'>


>>> print(regex_pattern.findall(Text_cat))
['c', 'a']
  • "[abc]"는 3개의 문자가 들어 있는 것으로 보이지만, [ ]는 문자 하나로 인식한다.
  • cat은 "[abc]" 내부의 c와 일치하므로, match 되는 값인 'c'를 반환하였다.
  • findall() 메서드로 탐색 시, 일치하는 패턴인 ["c", "a"]를 하나하나 반환하였다.

 

 

2.2. 문자 클래스 [ ]와 범위 특수 문자 "-"

  • [ ] 안에 들어간 특수문자는 메타 문자가 아닌 원본 문자의 의미로 사용된다.
  • 그러나, "-"와 "^"는 별개의 의미를 갖는다.
  • "-"는 문자 클래스 내에서 범위를 의미한다.
>>> pattern = "[0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • "[0-9]"는 0에서 9까지의 숫자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['h', 'a', 'v', 'e', 'a', 't', 's']
  • "[a-z]"는 a부터 z까지의 영문자, 즉 소문자를 의미한다.
>>> pattern = "[A-Z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['I', 'C']
  • [A-Z]는 A부터 Z까지의 영문자, 즉 대문자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern, re.IGNORECASE)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
  • re.compile() 안에 re.IGNORECASE(re.I)를 넣으면 대소문자 구분 없이 사용할 수 있다.
>>> pattern = "[a-zA-Z0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0']
  • "[a-z]", "[A-Z]", "[0-9]"는 한 문자 클래스 안에서 동시에 사용될 수 있다.
>>> pattern = "[가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['나', '는', '미', '국', '의', '의', '스', '팸', '통', '조', '림', '개', '를', '가', '지', '고', '있', '다']
  • "[가-힣]"을 쓰면 한글에 대해서도 정규 표현식을 사용할 수 있다.
>>> pattern = "[가\-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다. - 힣힣"
>>> print(regex_pattern.findall(Text_data))
['가', '-', '힣', '힣']
  • "\-"처럼 특수 문자 앞에 역슬래시를 사용하면 본래 문자를 그대로 쓸 수 있다.

 

 

2.3. 문자 클래스 [ ]와 반대 특수 문자 "^"

  • 문자 클래스 [ ] 안에 "^"가 들어가면, 그 내용이 반대가 된다.
>>> pattern = "[^가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', 'H', 'o', 'r', 'm', 'e', 'l', ' ', 'F', 'o', 'o', 'd', 's', ' ', ' ', ' ', '1', '0', ' ', ' ', '.']
  • 한글을 제외한 모든 문자열이 반환된 것을 볼 수 있다.
  • 공백(" ")도 하나의 문자이며, 이는 문자 클래스 안에 띄어쓰기로 표현할 수 있다.
>>> pattern = "[^가-힣 ]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0', '.']
>>> pattern = "[\^a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['o', 'r', 'm', 'e', 'l', 'o', 'o', 'd', 's']
  • "-"와 마찬가지로 앞에 역슬래시를 붙여 "\^"로 만들면 문자열 자체로 사용할 수 있다.

 

 

2.4. 별도 표기법을 가진 문자 클래스

  • 앞서 학습한 문자 클래스 중 "[a-z]", "[A-Z]", "[0-9]", "[ ]"는 꽤나 빈번하게 사용되는 문자 클래스기 때문에 이를 별도의 표기법으로도 제공한다.
  • 대표적인 문자 클래스는 6가지로 다음과 같다.
문자 클래스 기능 유사한 기능을 하는 문자 클래스
\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_]
>>> pattern = "\W"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.']
  • 위에서 소개한 별도 표기법을 가진 문자 클래스는 [ ] 안에 담을 수도, 따로 사용할 수도 있다.
>>> pattern = "\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']


>>> pattern = "[\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • 한 문자 클래스에 대해선 별 의미가 없으나, 두 문자 클래스를 동시에 사용할 땐 크게 다르다.
>>> pattern = "\s\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 1']
  • "\s\d"는 공백 뒤에 숫자가 오는 패턴이므로 '통조림 10개를'에서 ' 1'에 매치된다.
>>> pattern = "[\s\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', '1', '0', ' ', ' ']
  • "[\s\d]"는 공백과 숫자를 함께 넣은 문자 클래스 "[ 0-9]"와 같으므로, 다른 결과를 반환한다.

 

 

2.5. 모든 문자를 의미하는 Dot "."

  • "."은 모든 문자열 한자리를 의미한다.
  • 단 "." 은 줄 바꿈 문자 "\n"는 제외한다.
>>> pattern = "\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10']
  • 숫자 뒤에 문자가 하나 오는 경우인 10을 가지고 왔다.
>>> pattern = ".\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 10']
  • 공백도 Dot에 해당하므로 ' 10'을 가지고 왔다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개를']
  • 추천하는 방법은 아니지만 Dot를 여러 개 붙여서 문자열의 길이도 지정할 수 있다.
  • Dot(".")은 기본적으로 줄 바꿈 문자인 "\n"을 제외하지만, re.DOTALL(re.S) 파라미터를 추가함으로써 줄 바꿈 문자도 포함할 수 있다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern, re.DOTALL)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개\n 20개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개\n', '20개를']

 

 

 

 

3. 패턴 문자열의 반복 지정

  • 앞서 패턴 문자열을 만드는 법을 배워봤다. 문자 클래스(Character class)는 기본적으로 한 자리이므로, 사용자가 원하는 패턴을 찾아내려면 패턴 문자열의 반복 수를 지정해주면 된다.
  • 반복 수를 지정하는 방법은 다음과 같다.
메타 문자 의미
+ 앞의 패턴 문자열이 1번 이상 반복되어야 한다.
* 앞의 패턴 문자열이 0번 이상 반복되어야 한다.
? 앞의 패턴 문자열은 존재할 수도 있고, 없을 수도 있다(0 이상 1 이하).
{n, m} 앞의 패턴 문자열은 n번 이상 m번 이하 반복되어야 한다.
{n, } 앞의 패턴 문자열은 n번 이상 반복되어야 한다.
{n} 앞의 패턴 문자열을 n번 반복되어야 한다.
  • 앞서 학습한 패턴 문자열 작성 방식과 위의 반복 수를 이용해서 문장 내 원하는 패턴을 찾아보도록 사자.
  • 다음과 같은 문장이 있다고 가정해보자.
>>> Sentence = "마동석씨의 핸드폰 번호는 010-1234-5678, 이메일 주소는 Email_ID.1234@naver.com이다."
  • 여기서 핸드폰 번호를 추출해보자.
  • 핸드폰 번호는 01[016789]-[0-9]{4}-[0-9]{4}의 패턴을 갖는다.
>>> pattern = "01[016789]-[0-9]{4}-[0-9]{4}"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['010-1234-5678']
  • 아주 쉽게 원하는 패턴을 찾아내었다.
  • 이번에는 이메일 주소를 추출해보자.
  • 이메일 주소의 패턴은 [a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]? 이다.
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID.1234@naver.com']
  • 이메일 주소의 패턴에는 "?"가 들어갔는데, 이메일 주소의 도메인은 아래와 같이 존재하는 경우도 있기 때문이다.
>>> Sentence = "나문희씨의 이메일 주소는 Email_ID@company.co.kr이다."

>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID@company.co.k']
  • 정규 표현식의 첫 포스트에서 제시한 아래 예제에서 휴대전화 번호, 집 전화번호, 이메일 주소 들을 따로 추출해보자.
data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(data))

  • re 모듈은 기본적으로 하나의 텍스트 데이터에 대해 동작하므로, for문을 이용해서 하나하나씩 비교하고, 해당하는 list에 담아보도록 하자.
# Data가 담길 기본 list를 생성한다.
cellphone_list = []
homephone_list = []
email_list = []


# 각 카테고리별 패턴
cellPhone_pattern = re.compile("01[016789][\-.][0-9]{4}[\-.][0-9]{4}")
homePhone_pattern = re.compile("0[2-6][1-5]?[\-.][0-9]{3,4}[\-.][0-9]{4}")
email_pattern = re.compile("[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*")


for target in data:
    
    cellPhone_Object = cellPhone_pattern.match(target)
    homePhone_Object = homePhone_pattern.match(target)
    email_Object = email_pattern.match(target)
    
    if cellPhone_Object != None:
        
        cellphone_list.append(cellPhone_Object.group())
        
    if homePhone_Object != None:
        
        homephone_list.append(homePhone_Object.group())
        
    if email_Object != None:
        
        email_list.append(email_Object.group())
>>> print(f"휴대전화 번호: {cellphone_list}")
>>> print(f"휴대전화 번호: {homephone_list}")
>>> print(f"휴대전화 번호: {email_list}")
휴대전화 번호: ['010.1234.5678', '010-1234-5678']
휴대전화 번호: ['02-1234-5678', '031-123-4567', '042.987.6543']
휴대전화 번호: ['emailID01@naver.com', 'emailID02@gmail.com', 'Email.03@daum.net', 'EM_example.01@gmail.com', 'email_1234@company.co.kr']
  • 앞서 학습한 패턴 문자열 생성과 패턴 문자열의 반복 횟수만 지정해주니 아주 간단하게 원하는 패턴만 추출하는 데 성공하였다.

 

 

 

 

4. 패턴 문자열의 위치 지정

  • 패턴 문자열이 맨 뒤에 있는지, 맨 앞에 있는지만을 지정할 수 있다.
  • '^': 패턴 문자열 앞에 위치하며, 텍스트의 맨 왼쪽이 해당 패턴 문자열로 시작해야 한다.
  • '$': 패턴 문자열 뒤에 위치하며, 텍스트의 맨 오른쪽이 해당 패턴 문자열로 끝나야 한다.
  • 아래와 같은 이름 데이터가 있다고 가정해보자.
>>> name = "김영수, 김나연, 최영호, 김영식, 박순자, 최정훈, 박성훈, 김상훈, 최현정, 박성민"

>>> name_pattern = re.compile("^김..")
>>> print(name_pattern.findall(name))
['김영수']


>>> name_pattern = re.compile("^최..")
>>> name_pattern.findall(name)
[]


>>> name_pattern = re.compile("박..$")
>>> name_pattern.findall(name)
['박성민']


>>> name_pattern = re.compile("김..$")
>>> name_pattern.findall(name)
[]
  • '^', '$' 패턴은 문자열의 맨 처음과 맨 끝만 중요하게 판단한다.
  • 성이 김으로 시작하는 사람을 찾고 싶다면, 물론 간단한 패턴 문자열 생성으로 해결할 수야 있다.
>>> name_pattern = re.compile("[ ]?김..")
>>> name_pattern.findall(name)
['김영수', ' 김나연', ' 김영식', ' 김상훈']
  • 그러나 간단한 몇 개의 조작만 한다면, '^', '$' 메타 문자만을 이용해서도 찾을 수 있다.
  • 먼저 각 이름의 구분자인 ', '를 줄 바꿈 문자인 '\n'으로 바꿔주자.
>>> name_LF = re.sub(", ", "\n", name)
>>> name_LF
'김영수\n김나연\n최영호\n김영식\n박순자\n최정훈\n박성훈\n김상훈\n최현정\n박성민'
  • re.sub(pattern, repl, string): string에서 pattern에 해당하는 문자열을 repl 문자열로 치환한다.
  • re.compile에 추가 파라미터 re.MULTILINE(re.M)를 하나 넣어줘 보자.
>>> name_pattern = re.compile("^김..", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['김영수', '김나연', '김영식', '김상훈']
  • re.MULTILINE은 문자열 전체의 처음이나 끝이 아닌 각 라인의 처음이나 끝으로 설정해주는 파라미터이다.
  • 즉, '^'와 '$'를 각 줄에 적용할 수 있다는 것이다.
  • 이름이 "훈"으로 끝나는 사람도 쉽게 찾을 수 있다.
>>> name_pattern = re.compile("..훈$", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['최정훈', '박성훈', '김상훈']
  • 이 외에도 "\A", "\Z"가 존재하는데, 각각 "^", "$"와 동일한 기능을 갖는다. 유일한 차이라면, re.MULTILINE 파라미터가 있어도 전체에 대해서 작동한다는 것이다.

 

 

 

 

5. 기타 메타 문자

  • 앞서 소개한 메타 문자 중 "|"와 "()" 이 두 가지를 아직 다루지 않았는데, "|"만 다루고, "()"는 다음 포스팅에서 다루도록 하겠다.
  • "()"는 Grouping이라는 것으로, 꽤나 복잡한 녀석이라 다음 포스트에서 자세히 설명해보도록 하겠다. 이번 포스트에선 "|"까지만 다루겠다.

 

5.1. "|": 또는(Or)

  • "A|B" 패턴 문자열이 있다면, A에 해당하는 패턴 문자열이나 B에 해당하는 패턴 문자열에 대해 가지고 온다.
>>> Text_data = "Banana, Apple, WaterMelon, Melon, StrawBerry"

>>> pattern = "Apple|Banana"
>>> regex_pattern = re.compile(pattern)
>>> print(regex_pattern.findall(Text_data))
['Banana', 'Apple']

 

 

 

지금까지 기본적인 정규 표현식 문법 작성 방법에 대해 알아보았다. 지금까지 다룬 내용만으로도 정규 표현식을 사용하는 것은 크게 어렵지 않으나, 혹시나 정규 표현식에 대해 보다 자세히 학습해보고 싶은 사람이 있을 수도 있으므로, 다음 포스트에서는 정규 표현식에 대한 보다 어려운 부분도 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

이전 포스트에서 정규 표현식이 무엇인지, 메타 문자가 무엇인지에 대해 가볍게 알아보았다. 앞서 학습한 메타 문자를 예제를 기반으로 제대로 다뤄보려면, 자신이 만든 정규 표현식 패턴이 제대로 만들어졌는지를 확인할 필요가 있다.

이번 포스트에서는 Python에서 정규 표현식을 어떻게 지원하는지 re 모듈을 통해 알아보도록 하겠다. 이번 포스트에서 예제로 정규 표현식으로 만든 패턴을 일부 사용하긴 할 것이나, 이는 다음 포스트에서 자세히 다룰 것이므로 이번에는 참고만 하도록 하자.

 

 

 

 

1. Python과 re 모듈

정규 표현식으로 만든 패턴 문자열이 제대로 작동하는지 알기 위해선 Python에서 기본적으로 제공하는 re 모듈에 대해 알 필요가 있다. 

  • re 모듈은 Python에서 정규 표현식을 지원하기 위해 제공하는 기본 라이브러리이다.
  • re 모듈은 컴파일러 객체를 선언하고, 다음과 같은 메서드를 통해 작동한다.
# re 모듈을 가지고 온다.
import re
pattern = "cat"

regex_pattern = re.compile(pattern)
  • re 모듈의 컴파일 객체는 다음과 같은 메서드를 통해 작동한다.
  • 컴파일 객체는 regex_pattern이 아닌 다른 변수명에 담아도 상관없다.
Method/Attribute 기능
match() 문자열의 왼쪽(시작)부터 컴파일된 정규 표현식 패턴 객체가 일치하는지 판단한다.
search() 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 찾는다.
findall() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 리스트로 반환한다.
finditer() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 이터레이터(Iterator)로 반환한다.

 

 

 

 

1.1. match()

  • match 함수는 왼쪽(시작)부터 정규 표현식 패턴이 일치하는 곳까지를 반환해준다.
  • 왼쪽부터라는 것이 무슨 뜻인지 아리송한 감이 있는데, 이게 정확히 어떻게 돌아가는지 알아보도록 하자.
  • 다음과 같은 정규 표현식 패턴과 Text_data가 있다고 가정해보자.
>>> pattern = "cats"

>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I love cats and I wish I had cats."
  • Text_data에는 pattern인 cats가 들어 있으므로, 문제없이 찾을 수 있을 것 같다.
  • 이를 match 메서드를 이용해서 탐색해보자.
>>> m = regex_pattern.match(Text_data)
>>> print(m)
None
  • 분명히 "cats"이라는 단어가 Text_data 안에 있음에도 탐색되지 않았다.
  • 이는 match() 메서드가 왼쪽에서부터 탐색하기 때문으로, 주어진 "cats"라는 pattern은 Text_data의 문장 중간에 존재하지, 앞에서부터는 존재하지 않기 때문이다.
  • "cats"가 나오도록 패턴을 조금 수정해보자.
    • 새로운 패턴은 다음 포스트에서 자세히 다룰 것이므로, 일단 따라가 보자!
>>> pattern = ".*cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 33), match='I love cats and I wish I had cats'>
  • pattern으로 '.*cats'를 사용하였는데, '.*'는 cats 앞에 문자가 0개 이상 존재한다는 의미다.
  • 즉, 0개 이상 문자가 앞에 존재하는 cats와 일치하는 문장을 탐색한 것이다.
  • match 메서드는 _sre.SRE_Match 객체를 리턴한다.
  • _sre.SRE_Match 객체는 Match 된 문자열의 위치, Match 된 문자열을 반환한다.
  • 이를 통해, Match 메서드 시작부터 패턴이 일치하는지 판단한다는 것을 알 수 있다.

 

 

  • 이번에는 pattern을 아주 조금만 수정해서 match() 메서드의 성질을 보다 자세히 보자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 12), match='I love cats '>
  • 앞서 사용한 pattern 뒤에 스페이스(space)를 추가하자, "I love cats "까지만 출력되었다.
  • 즉, match 메서드는 전체 문장이 얼마나 길든 간에 처음부터 pattern과 일치되는 곳까지만 반환하는 것을 알 수 있다.

 

 

1.1.1. match 객체가 제공하는 메서드

  • 앞서 match는 _sre.SRE_Match 객체를 반환한다고 하였는데, 이는 다음과 같은 메서드를 이용해 내부 정보에 접근할 수 있다.
Method / Attribute 기능
group() 매치된 문자열 출력
start() 매치된 문자열의 시작 지점 출력
end() 매치된 문자열의 끝 지점 출력
span() 매치된 문자열의 시작 지점과 끝 지점을 튜플로 출력
  • 예제를 통해 보도록 하자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)

>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
I love cats 
0
12
(0, 12)

 

 

 

 

1.2. search()

  • search 메서드는 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 반환한다.
  • 앞서 만든 예제에 그대로 적용해보도록 하자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>
  • search 메서드도 match 메서드와 동일하게 _sre.SRE_Match 객체를 반환한다.
  • search 메서드는 match 메서드와 달리 pattern이 처음부터 일치할 필요는 없으며, Text_data 내 가장 먼저 일치하는 문자열의 위치와 문자열을 반환한다.
  • 만약 존재하지 않는 문자열을 검색하면, match와 마찬가지로 None을 반환한다.
>>> pattern = "dogs"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
None
  • search() 메서드는 match() 메서드와 동일한 _sre.SRE_Match 객체를 반환하므로, match와 동일한 방법을 통해, 원하는 값을 출력할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
cats
7
11
(7, 11)

 

 

 

 

1.3. findall()

  • findall() 메서드는 가장 설명이 깔끔하면서, 유용한 기능을 제공하는 메서드다.
  • 말 그대로, 문장 안에 원하는 텍스트가 존재하면 그 텍스트를 리스트(List)로 모두 가지고 오는 기능으로, 이를 이용해서 해당 문장 안에 패턴에 일치하는 문자열이 몇 개 존재하는지도 쉽게 확인할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.findall(Text_data)
>>> print(m)
['cats', 'cats']
  • match, search와 달리 리스트를 바로 반환하므로, group, start, end, span과 같은 함수를 사용하지 않는다.

 

 

 

 

1.4. finditer()

  • finditer()는 findall()의 진화 버전이라고 할 수 있는 메서드로, Python 초보자라면 그 기능을 알기 쉽지 않다.
  • finditer()와 findall()의 차이는 결과를 이터레이터(Iterator)로 출력할 것인지 리스트(List)로 출력할 것인지로, List로 출력하면, 사용하기는 단순하나 단지 패턴에 일치하는 단어들을 List에 담아 출력하므로, 그 기능이 한정적이다.
  • 그러나 finditer()는 이터레이터를 통해 값을 반환하므로, 더 많은 정보를 담을 수 있고, 엄청나게 큰 Text_data가 대상일 때, 메모리 측면에서도 훨씬 유리하다.
  • 이터레이터에 대해서는 나중에 자세히 다룰 것이므로, 간단하게 내가 원할 때, 순서대로 값을 반환할 수 있는 녀석이라고 생각하면 된다.
  • 이터레이터는 모든 데이터를 메모리에 올려놓지 않기 때문에 findall()보다 용량을 덜 차지한다는 장점이 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)
>>> print(m)
<callable_iterator object at 0x000001EB3D932358>
  • 출력된 결과가 앞서 봤던 match(), search(), findall()과 다르게 전혀 알아볼 수 없는 녀석이 나왔다.
  • Python의 이터레이터는 next() 또는 for와 같은 함수를 이용해야 값을 출력할 수 있다.
# 첫 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>

# 두 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(29, 33), match='cats'>

# 세 번째 next
>>> next(m)

  • next()로 이터레이터 안에 있는 값을 꺼내자 _sre.SRE_Match 객체가 반환되는 것을 볼 수 있다.
  • 이터레이터 안에는 2개의 객체만 존재하므로, 마지막 객체 반환 시, StopIteration을 출력하는 것을 볼 수 있다.
  • 이번에는 for문으로 이터레이터 안의 객체를 꺼내보자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m: 
>>>    print(i)
    
<_sre.SRE_Match object; span=(7, 11), match='cats'>
<_sre.SRE_Match object; span=(29, 33), match='cats'>
  • finditer() 메서드는 findall() 메서드와 달리 _sre.SRE_Match 객체를 반환하므로, 문장 내 패턴에 일치하는 대상들의 위치 정보도 쉽게 반환할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m:
>>>    print(i.span())

(7, 11)
(29, 33)

 

 

 

 

이번 포스트에서는 Python에서 기본적으로 제공하는 re 모듈에 대해 한 번 다뤄봤다. 텍스트 데이터를 다룰 때, re 모듈은 매우 유용하게 사용되므로 꼭 숙지하도록 하자.

다음 포스트에서는 메타 문자를 이용하여, 예제 데이터에 대한 정규 표현식 패턴을 만들어보고, re 모듈로 이를 검증해보도록 하자.

728x90
반응형
728x90
반응형

이전 포스트까지 문자열(String)을 다루는 기본적인 방법에 대해 알아보았다. 텍스트 데이터의 양이 적고, 패턴이 매우 단순하다면 앞서 다룬 방법만으로도 충분하겠지만, 이를 넘어서는 텍스트 데이터를 다룰 땐 앞서 다룬 방법만으로는 데이터를 다루기가 보다 곤란하다. 이때 등장하는 것이 문자열이 갖는 특정 규칙을 이용해 패턴을 표현하는 정규 표현식이다.

 

 

 

 

1. 정규 표현식(Regular Expression)이란?

위키피디아에선 정규 표현식을 다음과 같이 설명한다 <출처: 위키백과, 정규 표현식>.


정규 표현식은 특정 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어로, 정규 표현식은 많은 텍스트 편집기와 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원하고 있으며, 특히 펄과 Tcl은 언어 자체에 강력한 정규 표현식을 구현하고 있다.


이를 이해하기 쉽게 아~~~주 간단하게 표현하면 다음과 같다.

"대상 문자열이 가지고 있는 특정 패턴을 정규 표현식이라는 이름의 패턴 문자열을 이용해서 찾아내는 방법"

당신이 "연락처"라는 데이터를 수집했다고 가정해보자, 응답자들은 단순히 연락처를 적으라고 하였으므로, 휴대전화 번호, 집전화번호, 이메일 주소와 같은 다양한 종류의 연락처를 입력하였고, 당신의 보스는 당신에게 휴대전화 번호와 집전화번호, 이메일 주소를 각각 분리해서 가지고 오라고 지시를 하였다. 애초에 이를 따로 수집했으면 이런 귀찮은 일도 안 생기겠지만, 이미 저질러진 일을 어쩌겠는가!라는 상황에서 유용하게 사용할 수 있는 것이 정규 표현식이다.

정규표현식에 대한 이해를 돕기 위해 수집한 데이터의 예시를 눈으로 보도록 하자.

data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]

위 예제 데이터를 보면, 휴대전화 번호와 집전화번호, 이메일 주소마다 고유한 패턴을 가지고 있는 것을 볼 수 있다.

  • 다음 패턴은 위 데이터만 대상으로 놓고 분류한 것이다.
  • 휴대전화 번호는 010 특수 기호 '.', '-' 숫자 4자리 '.', '-' 숫자 4자리로 구성되어 있다.
  • 집전화번호는 0 뒤에 1~9의 숫자가 1개에서 2개가 오고 그 뒤에 특수 기호 '.', '-' 숫자 3 ~ 4 자리, '.', '-' 숫자 4자리로 구성되어 있다.
  • 이메일 주소는 ID와 Domain으로 나눠지며, 그 사이에는 '@'가 존재한다.
  • ID는 영문 대, 소문자와 숫자, 특수문자 '.', '_"로 이루어져 있으며, 맨 앞에는 특수문자가 들어갈 수 없다.
  • Domain은 소문자로만 이루어져 있으며, 특수문자 '.'를 기준으로 소문자가 1개에서 2개의 그룹으로 연결된다.

정규 표현식은 위 패턴을 하나의 문법으로 만들어 그 문법에 해당하는 대상을 찾거나 바꿀 수 있는 도구다. 즉, 휴대전화 번호만 찾고 싶다면 휴대전화 번호의 기본적인 패턴을 문법으로 만들고 그 문법에 해당하는 텍스트 데이터를 조회하여, 휴대전화 번호만 가지고 오게 할 수 있다.

 

※ 팁

한 가지 팁이라면, 데이터만 보고 정규 표현식을 사용하는 것보다. 해당 데이터의 생성 방식을 먼저 찾아보고 정규 표현식을 사용하는 것이 일반화 측면에서 더 좋다. 다음과 같은 정보는 잠깐만 인터넷 검색을 해보면 알 수 있는 정보이다.

  • 휴대전화 번호의 통신망 식별번호
    • 010, 011, 016, 017, 018, 019
  • 지역번호
    • 02, 031, 032, 033, 041, 042, 043, 044, 051, 052, 053, 054, 055, 061, 062, 063, 064

 

정규 표현식 문법은 각종 특수문자가 섞여 있고, 문법 작성 방식이 가독성과 거리가 꽤 멀기 때문에 지저분해 보인다는 단점이 있어, 많은 데이터 분석 입문자들이 기피하는 경향이 있다. 그러나, 텍스트 데이터를 다룰 때, 정규 표현식을 다룰 수 있고 없고의 차이는 매우 크므로, 조금 고통스럽더라도 함께 공부해보도록 하자(막상 공부해보면 보기보다 할만하다!).

 

 

 

 

2. 정규 표현식 문법과 메타 문자

  • Python으로 정규 표현식을 다루는 법을 알기 전에 먼저 정규 표현식 문법부터 알아보도록 하자.
  • 정규 표현식은 언어마다 문법이 조금씩 다르므로, 사용하려는 프로그램 언어에 맞는 정규 표현식 문법을 공부하는 것이 좋다.
  • 정규 표현식 사용 방법은 일반적으로 다음과 같은 순으로 진행된다.
  1. 정규 표현식 문법 작성
  2. 작성된 문법을 이용한 조작(탐색, 대체 등)

 

 

2.1. 메타 문자(Meta characters)

  • 평상시, 큰 의미 없이 사용했던 특수 문자들이 정규 표현식에서는 특정한 기능을 갖는데, 이를 메타 문자라고 한다.
  • 정규 표현식 문법은 이 메타 문자와 찾고자 하는 Text 데이터로 구성된다.
  • 이 특수 문자를 문자 그대로 사용하려면 앞에 '\'(역 슬래시)를 넣어줘야 한다.
  • 역 슬래시는 한국인이 사용하는 일반적인 키보드에서 ''로 표기되며, ''는 보이기에만 이렇지 실제로는 역 슬래시로 작용하므로, 이를 그냥 사용해도 문제없다.
  • 메타 문자는 크게 '문자의 패턴', '문자의 반복', '문자열의 위치', '기타 기능'으로 구성된다.
  • 대표적인 메타 문자는 다음과 같다.
문자의 패턴 문자의 반복
[] 문자 클래스 + 앞 문자 패턴이 1개 이상
. 임의의 한 문자 * 앞 문자 패턴이 0개 이상
\d 숫자 ? 앞 문자가 없거나 하나 있음
\D 숫자가 아닌 것 {n, m} 앞 문자 패턴이 n개 이상 m개 이하
\w 문자, 숫자, _ {n, } 앞 문자 패턴이 n개 이상
\W 문자, 숫자, _가 아닌 것 {n} 앞 문자 패턴이 n개
\s 공백 문자    
\S 공백 문자가 아닌 것    

 

 

문자열의 위치 기타 기능
^ 문자열의 시작 | 또는(or)
$ 문자열의 끝 () 문자열 패턴의 그룹화

 

 

이번 포스트에서는 정규 표현식이 무엇인지 알아보고, 정규 표현식에 사용되는 메타 문자가 무엇인지 알아보았다. 다음 포스트에서는 Python에서 정규 표현식의 기능을 어떻게 제공하는지 파악해보도록 하자.

728x90
반응형
728x90
반응형

 지금까지 문자열(string) type을 생성하고 이스케이프 문자, 포맷팅에 대해 학습해보았다. 지금까지 학습한 내용은 문자열을 생성하는 것에 관련 있는 것이지, 문자열을 다루는 내용과는 거리가 멀다고 할 수 있다.

 이번 포스트에서는 문자열을 직접 가지고 노는 문자열 전처리 코드에 대해 학습을 해보겠다.

 

 

문자열 전처리

1. 문자열 연산

 문자열 연산은 우리가 일반적으로 아는 더하기, 빼기, 곱하기와 같은 기능으로, 앞서 학습했던 List에서의 더하기, 곱하기처럼 문자A + 문자B는 문자A문자B가 되며, 문자 A * 3은 문자A문자A문자A가 된다.

# 1. 문자열을 더해보자
>>> A = "Python"
>>> B = "is"
>>> C = "easy"
>>> D = "and"
>>> E = "useful"
>>> F = A + " " + B + " " + C + " " + D + " " + E
>>> print(F)
Python is easy and useful


# 2. 문자열을 곱해보자
>>> A * 2
'PythonPython'


# 3. 문자열을 더하고 곱해보자
>>> G = A + " "
>>> print(G * 3)
Python Python Python 
  • 문자열 더하기는 꽤 유용하게 사용할 수 있는 기능으로, 포맷팅과 동일하게 사용할 수도 있다.
# 문자열 더하기를 포맷팅처럼 사용해보자
>>> AB = A + " " + B + " "
>>> print(AB + C)
>>> print(AB + E)
Python is easy
Python is useful

 

위와 같은 산술 연산 기호를 사용하는 것은 아니지만 문자열 빼기도 가능하다

# 생성된 문자열 F에서 and를 제거해보자
>>> print(F)
Python is easy and useful

>>> F.replace("and ", "")
'Python is easy useful'


# 문자열 F에서 and를 or로 바꿔보자
>>> F.replace("and", "or")
'Python is easy or useful'
  • 문자열 빼기가 문자열 F에서 문자열 D를 제거하는 것이라면, replace 함수를 응용해서 사용할 수 있다.
  • replace(): 문자열.replace(대상 문자, 바꿀 문자) 함수를 사용하면, 대상 문자를 내가 원하는 다른 문자로 바꿀 수 있다.

 

 

 

2. 문자열 인덱싱(슬라이싱)

 문자열 인덱싱은 문자에서 내가 원하는 구간의 문자만 선택하는 방법이다.

# 위에서 생성했던 문자열 F를 가지고 indexing을 해보자
>>> print(F)
'Python is easy and useful'


# 1. 먼저 문자 전체의 길이를 확인해보자
>>> len(F)
25


# 2. 문자열 F에서 첫 문자부터 5번째 문자까지 가지고 와보자
>>> F[0:5]
'Pytho'

# 2.1. 문자열 F를 뒤에서 6번째부터 뒤 모든 문자를 가지고 와보자
>>> F[-6:len(F)]
'useful'

>>> F[-6:]
'useful'
  • len(x): len 함수는 들어간 문자의 길이를 반환한다. x에 list나 DataFrame 등이 들어가는 경우, list는 원소의 수, DataFrame은 Row의 수를 반환한다.
  • 인덱싱에서 [n1:n2] n1에 아무것도 넣지 않는 경우 처음부터인 0을 의미한다. n2에 아무것도 넣지 않는 경우 마지막 위치를 의미한다.
  • 인덱싱한 값을 다른 변수에 담으면, 슬라이싱(부분만 가져오기)이 된다.
# 슬라이싱 기능을 활용하면 원하는 위치만 제거 혹은 다른 문자 끼워넣기가 가능하다.
>>> print(F)
'Python is easy and useful'

# 1. "easy and "를 제거해보자
>>> F[:10] + F[-6:]
'Python is useful'

# 2. useful을 useless로 바꿔보자
>>> F[:10] + F[-6:-3] + "less"
'Python is useless'

 

 위 인덱싱에선 내가 찾고자 하는 문자의 위치를 직접 확인해야 했으나, 문자 위치를 반환하는 함수인 find(), index() 함수를 사용하면, 위치를 수월하게 찾을 수 있다.

# 문자열 F에서 and의 위치를 찾아보자
>>> print(F)
'Python is easy and useful'

>>> F.find("and")   
15
>>> F[15:]
'and useful'    # 단어의 시작 위치가 출력된다.

# index() 함수는 find() 함수와 동일한 기능을 한다.
>>> target_start = F.index("and")
>>> print(target_start)
15

# len() 함수를 섞어서 사용하면 이 문제를 해결할 수 있다.
>>> F[target_start:target_start+len("and")]     # 문자 끝 위치 = 문자 시작 위치 + 문자 길이
'and'
  • 문자열.find(탐색 문자), 문자열.index(탐색 문자) 함수를 사용하면, 해당 문자가 처음 등장한 문자의 위치를 알 수 있다.
  • len() 함수와 섞어서 사용한다면 문자의 끝 위치도 알 수 있다.
  • 그러나 만약, 해당하는 문자가 여러개라면 가장 왼쪽에서 등장한 문자의 위치만 반환한다는 단점이 있다(이는 추후 학습할 정규표현식 함수를 사용하면 수월하게 해결할 수 있다).
  • index() 함수와 find() 함수의 차이는 대상 단어가 없을 때로, find() 함수 사용 시 대상 단어가 없으면 -1이 반환되고, index() 함수는 에러가 발생한다.

 

 

 

3. list와 str

 str type과 list type을 오가게 하는 함수가 있으며, 이를 단순히 말하면 아래와 같다.

  • "구분자".join(대상 문자열): 합치기
  • "대상 문자열".split("구분자"): 쪼개기
>>> target = "Python is fun and useful"

# 1. 문자열을 list로 쪼개보자
>>> target_list = target.split(" ")
>>> print(target_list)
['Python', 'is', 'fun', 'and', 'useful']

# 2. list를 문자열로 합쳐보자
# _(Under bar)로 합쳐보자
>>> target_str = "_".join(target_list)
>>> print(target_str)
Python_is_fun_and_useful

# 3. 생성된 단어를 한 글자 단위로 잘라보자
# join() 함수를 문자열을 대상으로 사용하면, 문자열에서 단어 하나하나 사이에 특정 단어가 들어간다.
>>> put_separator = "|".join(target_str)
>>> print(put_separator.split("|"))
['P', 'y', 't', 'h', 'o', 'n', '_', 'i', 's', '_', 'f', 'u', 'n', '_', 'a', 'n', 'd', '_', 'u', 's', 'e', 'f', 'u', 'l']
  • join() 함수를 문자열을 대상으로 사용 시, 문자열의 단어 하나하나 사이에 join 단어가 들어간다.
  • |는 거의 사용하지 않는 특수 문자 중 하나로 일반적으로 Rawdata에 존재하지 않는다.
  • join() 함수와 split() 함수를 섞어서 사용하면 한 단어 단위로 자를 수 있다.

 

 

 

4. 공백 제거하기

# Target
>>> target = " Python is easy and useful "

# 1. 앞의 공백을 제거하자
>>> target.lstrip()
'Python is easy and useful '

# 2. 뒤의 공백을 제거하자
>>> target.rstrip()
' Python is easy and useful'

# 3. 양쪽 공백을 제거하자
>>> target.strip()
'Python is easy and useful'

# 4. 모든 공백을 제거하자
>>> target.replace(" ", "")
'Pythoniseasyanduseful'
  • 문자열.lstrip(): 왼쪽 공백 제거
  • 문자열.rstrip(): 오른쪽 공백 제거
  • 문자열.strip(): 양쪽 공백 제거
  • 문자열.replace(" ", ""): 모든 공백 제거

 

 

 

5. 대소문자 바꾸기

# Target
>>> target = "Python is easy and useful"

# 1. 소문자를 대문자로 바꿔보자
>>> upper_T = target.upper()
>>> print(upper_T)
PYTHON IS EASY AND USEFUL

# 2. 대문자를 소문자로 바꿔보자
>>> upper_T.lower()
'Python is easy and useful'
  • 문자열.upper(): 소문자를 대문자로 만든다
  • 문자열.lower(): 대문자를 소문자로 만든다

 

 

 

6. 문자열 개수 세기

# target
>>> target = "Python is fun and useful"

# 1. 문자열 전체 길이를 확인해보자
>>> len(target)
25

# 2. 문자열 안에 특정 문자가 몇 개 있는지 확인해보자
>>> target.count(" ")
4

>>> target.count("Python")
1

 

 

 

 지금까지 문자열을 다루는 기본적인 함수에 대해 알아보았다. 위 함수들을 보면, 내가 원하는 단어를 탐색하고 조작하는 부분이 실전에서 필요한 것에 비해 부족하다는 것이 느껴지는데, 이를 보다 체계적으로 할 수 있는 방법이 바로 정규표현식이다.

 다음 포스트에서는 파이썬 정규표현식에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

  예를 들어 당신이 어떠한 반복되는 문자열을 만드려고, 하고 그 중 일부분만 바꿔야 한다면 어떻게 하겠는가?

# 예를 들어 뽀로로, 크롱의 인사와 짱구, 짱아의 인사 문자열을 생성하려면 어떻게 해야할까?
>>> str0 = "뽀로로: \"크롱 반가워!\"\n크롱:\"오! 뽀로로 좋은 아침이야!\""
>>> str1 = "짱구: \"짱아 반가워!\"\n짱아:\"오! 짱구 좋은 아침이야!\""
>>> print(str0)
>>> print("\n")
>>> print(str1)
뽀로로: "크롱 반가워!"
크롱:"오! 뽀로로 좋은 아침이야!"

짱구: "짱아 반가워!"
짱아:"오! 짱구 좋은 아침이야!"

 위 예시처럼 일일이 그 문자열을 만들어 새로 지정해 줄 것인가? 이는 매우 비 경제적인 방법인데, 이번 포스트에선 문자열의 일부를 손쉽게 바꿀 수 있는 방법인 문자열 포맷팅에 대해 학습해보겠다.

 

 

문자열 포맷팅(String Formatting)

 지난 포스트인 이스케이프 문자 예제에서 뽀로로, 크롱과 같은 문자열 내 특정 문자가 반복되어 출력되는데, 이 부분만 자동으로 바꿀 수 있다면, 보다 적은 코드만 써도 되지 않을까? 라는 생각이 들지 않는가?

 문자열 포맷팅은 코드를 더 경제적으로 쓸 수 있는 방법 중 하나로, 출력되는 문자열에서 일부분만 쉽게 바꿀 수 있는 방법 중 하나다.

 문자열 포맷팅은 크게 3가지 방법이 있다.

  1. % formating
  2. {} formating
  3. f-string

 

 

1. % formating

# 1. % formating
>>> print("%s: %s, 반가워!" %("뽀로로", "크롱"))
>>> print("%s: %s, 좋은 아침이야!" %("크롱", "뽀로로"))
뽀로로: 크롱, 반가워!
크롱: 뽀로로, 좋은 아침이야!
  • 위 예시를 보면, %s라는 문자와 %()라는 처음 보는 문자가 들어간 것을 볼 수 있다.
  • %s %d % ("str", 2) 이 기본적인 방식이다. 여기서 %s, %d와 같은 문자를 문자열 포맷 코드라고 한다.
  • 문자열 포맷 코드는 다음과 같다.
코드 설명 코드 설명
%s 문자열(string) %d 정수(integer)
%c 문자 1개(character) %f 부동소수(floating-point)
  • 참고로 %s의 경우 문자형이나, 숫자, 소수 모두 문자형으로 변형이 가능하므로, %s를 넣는 경우, 모든 형태의 값을 넣을 수 있다.
  • 이 밖에도 8진수(%o), 16진수(%x)가 더 있긴 하지만, %s, %d, %f만 사용할 수 있어도 충분하다.
  • 참고로 %를 문자로 그냥 출력하고 싶다면 %%를 입력해야한다.
# 위 코드를 좀 더 쉽게 써보자
>>> name1 = "뽀로로"
>>> name2 = "크롱"
>>> print("%s: %s, 반가워! \n%s: %s, 좋은 아침이야!" %(name1, name2, name2, name1))
뽀로로: 크롱, 반가워! 
크롱: 뽀로로, 좋은 아침이야!

>>> name3 = "짱구"
>>> name4 = "짱아"
>>> print("%s: %s, 반가워! \n%s: %s, 좋은 아침이야!" %(name3, name4, name4, name3))
짱구: 짱아, 반가워! 
짱아: 짱구, 좋은 아침이야!
  • 위 코드 같이 반복 출력하고자 하는 Data("뽀로로", "크롱")를 변수(name1, name2)에 담아서 사용하면 보다 쉽게 쓸 수 있다.
  • 그러나 위 방법의 경우, 문자를 넣어주고 싶은 위치랑 %()안의 변수 위치가 동일해야하므로, Data가 문자열에서 들어갈 위치를 헷깔릴 수 있다는 단점이 있다.

 

 

 

2. {} formating

# {} formating을 써보자
print("{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!".format(name1="뽀로로", name2="크롱"))
뽀로로: 크롱, 반가워!
크롱: 오! 뽀로로 좋은 아침이야!
  • {} format은 문자열 내에 원하는 변수를 지정하여 사용할 수 있다.
  • % formating과 달리 변수의 위치를 구체적으로 알 수 있다.
  • 이를 def 코드를 사용해서 함수로 만든다면, 보다 효율적으로 사용할 수 있다.
# def 코드를 사용해서 만들어보자
>>> def hello(name1, name2):
>>>     print("{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!".format(name1=name1, name2=name2))
    
>>> hello("짱구", "짱아")

짱구: 짱아, 반가워!
짱아: 오! 짱구 좋은 아침이야!
  • 그러나, 이 방법은 .format(변수이름=변수)를 넣어줘야하기 때문에 코드가 길어진다는 단점이 있다.

 

 

 

3. f-string

% formating과 {} formating을 비교해보면, 각각 코드를 만드는 데에 있어 장점과 단점이 나뉘는 것을 알 수 있다.

  • % formating은 문자열 포맷팅 밖에서 지정한 변수를 가져갈 수 있다.
  • {}.formating은 문자열 내 포맷팅 된 변수들의 위치를 알 수 있다.
  • 위 장점을 적절히 섞는다면 보다 코드를 수월하게 짤 수 있지 않을까? > f-string을 통해 이를 해결할 수 있다.
# f-string 포맷팅을 사용해보자
>>> name1 = "뽀로로"
>>> name2 = "크롱"
>>> print(f"{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!")
뽀로로: 크롱, 반가워!
크롱: 오! 뽀로로 좋은 아침이야!
  • f-string은 위 % formating이나 {} formating보다 코드가 단순하다.
  • 문자열 밖에서 문자열에 들어갈 가변적인 변수를 지정하고, 문자열 앞에 f를 붙이면 된다.
  • 문자열 내에선 {} formating과 같은 방식으로 문자열을 적으면 된다.
  • 위 코드를 def 코드를 이용해서 함수로 만들어보자.
# 함수화 해보자
>>> def hello(name1, name2):
>>>     print(f"{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!")
    
>>> hello("짱구", "짱아")


짱구: 짱아, 반가워!
짱아: 오! 짱구 좋은 아침이야!
  • 위의 {} formating을 사용해서 만든 함수보다 코드가 단순하다는 것을 알 수 있다.

 

 

 

 지금까지 문자열의 일부만 바꾸는 방법인 문자열 포맷팅에 대해 알아보았다. 이 중 f-string이 가장 코드가 단순하고, 직관적이므로, 가능하면 f-string을 쓰길 바란다.

 다음 포스트에서는 문자열을 가지고 노는 방법인 문자열 전처리에 대해 학습해보겠다.

728x90
반응형
728x90
반응형

 지난 포스트에서는 Python의 기본적인 자료형에 대해 공부해보았다. "Python-기초: 1.0. 자료형(1) - scalar"에서 Data type으로 int, float, None, Boolean은 나왔으나, 정작 문자열은 나오지 않았다.

 이번 포스트에서는 data type 중 문자열 Type인 string에 대해 심도 깊게 다뤄보도록 하겠다. 

 

 

문자열(String)

  • Python에서 문자열은 ''(Quotation)나 ""(Double Quotation) 안에 문자를 넣어서 만든다.
  • Python의 기본 인코딩은 utf-8이므로, R과 달리 한글에 대한 텍스트 마이닝이 깔끔하게 잘 수행된다.
  • 문자열 데이터를 처리하여, 그 안에 숨어 있는 정보를 찾아내는 전반적인 과정을 텍스트 마이닝(Text Mining)이라 한다.

※ 텍스트마이닝(Text Mining)

  • 현장에서 접하는 대다수의 데이터는 숫자로 이루어진 연속형 데이터보다 Log Data 같은 문자, 사진 같은 이미지, 동영상, 음악과 같은 비정형 데이터가 대부분이다.
  • 대부분의 전처리(Data Handling)는 이러한 비정형 데이터를 분석에 용이한 숫자 데이터로 변환해주는 과정이다.
  • 문자형 Data를 연속형 데이터로 변환하는 경우, 크게 2가지 방법이 있다.
    1. 범주화: 문자에 해당하는 숫자를 배정 - 리소스를 덜 먹음
    2. 벡터화: One-Hot Vector, Word2Vec 등의 방법 사용 - 일반적으로 기계학습에서 사용

 

 

 

문자열 Data를 만들어보자

# 문자열 Data를 만들어보자.
# 1. "" or ''를 사용해서 만든다.
>>> str1 = "Python is very useful"
>>> print(str1)
Python is very useful

>>> str2 = 'Python is a lot of fun'
>>> print(str2)
Python is a lot of fun

>>> str3 = """Python is easy and fun"""
>>> print(str3)
Python is easy and fun

>>> str4 = '''So let's do Python'''
>>> print(str4)
So let's do Python
  • 문자열 Data는 ""나 ''를 사용해서 만들며, "나 ' 기호를 3번 연속 사용해서 만들 수도 있다.
  • 보통 """나 '''로 만드는 경우는 긴 문장이나 주석을 넣을 때 사용한다.
# 문자열 안에 문자열 생성 기호인 ''나 ""를 넣고 싶은 경우엔 어떻게 할까?
>>> print("Let's do our best today")
Let's do our best today

>>> print('Pororo said "Hello" to Crong')
Pororo said "Hello" to Crong

>>> Frozen = """
    Anna:
    "Elsa?  Do you want to build a snowman?
    "Come on let's go and play.
    I never see you anymore. Come out the door.
    It's like you've gone away.
    We used to be best buddies
    And now we're not. I wish you would tell me why.
    Do you want to build a snowman?
    It doesn't have to be a snowman."

    Elsa:
    "Go away, Anna."

    Anna:
    "...Okay bye."
    """
    
>>> print(Frozen)

Anna:
"Elsa?  Do you want to build a snowman?
"Come on let's go and play.
I never see you anymore. Come out the door.
It's like you've gone away.
We used to be best buddies
And now we're not. I wish you would tell me why.
Do you want to build a snowman?
It doesn't have to be a snowman."

Elsa:
"Go away, Anna."

Anna:
"...Okay bye."
  • 짧은 문장 안에 '나 "가 들어가야한다면 그에 상응하는 문자로 묶어주면 된다.
  • 그러나 이 두 가지가 모두 들어가야한다면, """ 문장 """으로 묶어주면 된다.
  • 또는 escape 문자인 \을 사용하면 된다.

 

 

 

이스케이프 문자(Escape Sequence)

파이썬을 포함한 각종 언어에서는 잘 사용하지 않는 특수 문자나 특수문자 + 문자 조합에 특정 기능을 넣어 놓는다. 그러나, 가끔 이 이스케이프 문자가 실제 문자 Data 대상인 경우도 있기 때문에 이스케이프 문자가 아님을 알려주는 방법을 숙지할 필요가 있다.

이스케이프 문자 이름 설명
\b 벡스페이스 뒤로 한 칸 삭제
\t 문자열 사이에 탭 간격 생성
\n 라인피드 줄 바꿈
\f 폼피드 현재 커서를 다음 줄로 이동
\r 캐리지 현재 커서를 가장 앞으로 이동
\\ 역슬래시 \를 이스케이프 문자 앞에 사용하는 경우 그대로 출력
  • 위 표에서 많이 사용되는 것은 탭, 라인피드, 이스케이프 문자 앞에 \ 사용하기이므로, 나머지 이스케이프 문자에 대해선 크게 신경 쓰지 말자.
  • 키보드에서 \는 존재하지 않는데, ₩ 키가 동일한 기능을 한다.
# 이스케이프 문자를 사용해보자
>>> str5 = "뽀로로: \"크롱 반가워!\"\n크롱:\"오! 뽀로로 좋은 아침이야!\""
>>> print(str5)
뽀로로: "크롱 반가워!"
크롱:"오! 뽀로로 좋은 아침이야!"

# 이스케이프 문자를 그대로 출력시켜보자
>>> str5 = "뽀로로: \"크롱 반가워!\"\\n크롱:\"오! 뽀로로 좋은 아침이야!\""
뽀로로: "크롱 반가워!"\n크롱:"오! 뽀로로 좋은 아침이야!"

 

  • 위 예제에서 역 슬래시 사용 후 "나 ' 같은 특정 기능이 들어간 특수 문자를 넣으면 그대로 출력되는 것을 알 수 있다.
  • 이스케이프 문자에서 \n나 \t는 Data를 line 단위로 가져오기, tap 단위로 구분 지어 가져오기(구분자)를 하는 경우에 문자 안에 해당 문자가 들어 있어, Row를 잘못 인식할 수도 있다(실제 Row의 수보다 더 많은 Row를 생성).
  • 텍스트 데이터를 다룰 때, 이스케이프 문자나 ' or "로 인해 의도치 않은 결과가 출력 될 수 있다.

 

 

 

 이번 포스트에서는 가볍게 문자열을 만드는 방법과 이스케이프 문자에 대해 알아보았다. 이스케이프 문자는 반드시 숙지하길 바라며, 최소한 \n이 내려쓰기, \t는 탭이라는 것은 꼭! 외워놓기 바란다.

 Data를 가지고 오는 방법 중 txt data를 line별로 가지고 오거나, 탭을 구분자로 가져오는 경우가 많은데, 대상 Data 안에 해당 이스케이프 문자가 들어 있다면(예를 들어 URL Log Data), 데이터를 잘못 불러오는 문제가 발생할 수 있다.

 다음 포스트에선 자동으로 문자열의 일부를 바꾸는 방법인 포맷팅(Formating)에 대해 학습해보겠다. 

 

728x90
반응형
728x90
반응형

 자, 드디어 마지막 기초 자료형인 DataFrame이다. DataFrame은 pandas의 대표적인 Type이며, R을 공부해 본 사람이라면, 상당히 친숙하게 느껴지는 단어일 것이다.

 pandas의 DataFrame은 R의 Dataframe을 Python에서도 사용해보기 위해 만들어졌으며, R에서 할 수 있는 대부분의 기능을 판다스에서도 구현할 수 있다.

 이번 포스트에서는 데이터 분석가들의 필수 Type인 DataFrame이 어떻게 생겼는지와 아주 대략적인 대표 기능만 살펴보고 바로 넘어가도록 하자.

 

 

 먼저 DataFrame을 만들어보자.

  • DataFrame을 만드는 방법은 크게 2가지가 있다.
    1.  길이가 동일한 list들을 컬럼 하나하나에 배정하는 방법
    2. M*N 행렬 형태의 Data(Array, Tupple)를 DataFrame에 넣는 방법
# pandas 모듈을 가지고 오자
>>> import pandas as pd
>>> import numpy as np


# 1. 길이가 동일한 list들을 DataFrame에 넣어보자
# DataFrame에 들어갈 길이가 같은 list들을 만들자
>>> name = ["민철", "기훈", "재성", "현택", "윤기"]
>>> math = [40, 60, 80, 75, 65]
>>> english = [75, 80, 65, 80, 70]
>>> science = [85, 70, 75, 80, 60]

# list들을 이용해서 DataFrame을 만드는 경우는 다음과 같다
>>> DF = pd.DataFrame({"name":name, "math":math, "english":english, "science":science})
>>> DF

name	math	english	science
0	민철	40	75	85
1	기훈	60	80	70
2	재성	80	65	75
3	현택	75	80	80
4	윤기	65	70	60



# 2. array를 이용해서 DataFrame을 만들어보자.
# 10, 100 사이의 임의의 값으로 만들어진 행렬을 생성하자
>>> row_number = 50
>>> score_mat = np.random.randint(10, 100, size=(row_number, 4))

# ID를 만들어보자
>>> ID_list = []
>>> for i in range(row_number):
    
	    ID = "A" + str(i)
    	ID_list.append(ID)
    
# shape을 맞춰서 ID_array와 score_mat을 병합시켜보자
>>> ID_array = np.array(ID_list)
>>> ID_array = ID_array.reshape((50, 1))

>>> data_array = np.hstack((ID_array, score_mat))

# array를 DataFrame을 만들어보자
>>> DF2 = pd.DataFrame(data_array, columns=["ID", "math", "English", "science", "Korean"])
# 생성한 DataFrame의 상위 10개만 출력해보자
>>> DF2.head(10)


ID	math	English	science	Korean
0	A0	35	38	62	51
1	A1	52	29	40	93
2	A2	28	16	99	71
3	A3	93	42	61	48
4	A4	23	60	39	48
5	A5	93	96	16	55
6	A6	13	69	88	90
7	A7	31	18	80	30
8	A8	59	12	66	93
9	A9	54	70	57	38
  • DataFrame을 처음 보면 마치 엑셀에서 우리가 일반적으로 만들던 표랑 굉장히 유사하다는 것을 알 수 있다.
  • DataFrame에는 각 열(Column)별로 동일한 데이터 타입을 넣을 수 있다.
  • pd.DataFrame() 함수를 통해 DataFrame을 만들 수 있다.
  • np.random.randint(시작 값, 끝 값, 형태) 함수는 시작 값, 끝 값 사이에서 랜덤한 값이 담긴 array를 생성한다.
  • np.hstack((array1, array2)) 함수는 두 array를 열을 기준으로 병합한다.
  • DataFrame.head(숫자) 함수는 내가 숫자만큼 DataFrame을 출력한다.

 

 

DataFrame의 컬럼별 dtype을 확인해보자

# DataFrame의 data type을 확인해보자
>>> DF2.dtypes
ID         object
math       object
English    object
science    object
Korean     object
dtype: object


# math, English, science, Korean 컬럼을 정수 type으로 바꿔보자
>>> DF2["math"] = DF2["math"].astype("int64")
>>> DF2["English"] = DF2["English"].astype("int64")
>>> DF2["science"] = DF2["science"].astype("int64")
>>> DF2["Korean"] = DF2["Korean"].astype("int64")
>>> DF2.dtypes
ID         object
math        int64
English     int64
science     int64
Korean      int64
dtype: object
  • DataFrame.dtypes 함수를 통해 DataFrame의 각 컬럼들의 Type을 확인할 수 있다.
  • DataFrame["컬럼"].astype("바꿀 dtype") 함수를 통해 DataFrame의 해당 컬럼 dtype을 바꿀 수 있다.

 

 

DataFrame을 Slicing 해보자.

# 10번 row부터 20번 row까지 출력해보자
>>> DF2[10:20]
	ID	math	English	science	Korean
10	A10	78	24	99	79
11	A11	41	87	83	10
12	A12	61	71	31	78
13	A13	74	80	32	99
14	A14	20	19	95	38
15	A15	24	67	22	24
16	A16	39	53	41	82
17	A17	34	57	52	67
18	A18	34	60	27	73
19	A19	27	35	91	81




# 수학 80점 이상인 Row만 출력해보자
>>> DF2[DF2["math"] >= 80]
	ID	math	English	science	Korean
2	A2	90	70	70	72
4	A4	90	27	64	42
8	A8	99	21	71	92
9	A9	89	61	11	30
28	A28	81	29	27	86
32	A32	84	19	89	31
44	A44	97	73	36	78
45	A45	80	95	54	12
48	A48	86	19	99	83
  • pandas의 dataFrame은 Numpy의 array와 동일한 방법으로 Slicing 할 수 있으며, 내가 원하는 조건에 대한 Row도 쉽게 가져올 수 있다.

 

 

 이번 포스트에서는 DataFrame에 대해 아주 간략하게 훑어만 봤는데, 설명을 보다 보면 설명이 지나치게 부족하지 않는가? 하는 생각이 들 것이다. 

 pandas의 DataFrame 역시 Numpy의 array와 마찬가지로 그 영역이 매우 크기 때문에 따로 카테고리를 만들어서 세세하게 설명하고자 한다.

 이번 포스트에서는 맛보기로 DataFrame이 어떻게 생겼는지만 인식하는 수준에서 끝내고, 추후 Python-pandas 카테고리의 포스트에서 pandas의 각 기능들을 세세하게 따져보도록 하겠다.

 
728x90
반응형
728x90
반응형

 이번 포스트에서는 Dictionary에 대해 살펴보도록 하겠다.

 

 

Dictionary

개요

  • Dictionary는 말 그대로 사전이라고 할 수 있으며, 사전은 기본적으로 "단어: 설명"의 구성으로 돼있다.
  • Python의 Dictionary 역시 key : value 관계로 이루어져 있으며, key를 이용해 해당하는 value를 받을 수 있다.
  • Dictionary의 안에는 scalar, list, array, DataFrame 등 다양한 Type의 Data 담을 수 있다.
  • Dictionary의 key는 순서가 따로 없으며, 내가 원하는 key에 해당하는 value를 얻고자 할 때, 순서가 없으므로 순차적으로 탐색하지 않고, 바로 value를 가지고 오므로 속도가 매우 빠르다
  • Dictionary의 안에 또 Dictionary를 넣을 수 있으며, 이 구조는 마치 Tree 구조와 같다.

 

 

 위 이야기만 보면 잘 와닿지 않으므로, 실제로 Dictionary를 만들어보자.

# Dictionary를 만들어보자.
>>> dict_a = {}
>>> type(dict_a)
dict

>>> dict_b = dict()
>>> type(dict_b)
dict
  • Dictionary는 위와 같이 dict()라는 함수를 쓰거나 {}를 이용해서 만들 수 있다.

 

 

 이번엔 Dictionary에 Data를 넣어보자

# Data를 넣어보자
>>> dict_c = {"아침":"만두", "점심":"햄버거", "저녁":"된장찌개"}
>>> dict_c
{'아침': '만두', '점심': '햄버거', '저녁': '된장찌개'}
  • Dictionary에는 key:value로 Data를 넣는다.
  • key에는 문자형("", ''로 묶인!), scalar(정수, 소수, None, Boolean)가 들어갈 수 있다.
  • value엔 무엇이든지 다 들어갈 수 있다.

 

 

 조금 더 복잡하게 Data를 넣어보자

>>> dict_c = {"아침":{14:"만두", 15:"피자", 16:"곰탕"},
              "점심":{14:"햄버거", 15:"떡볶이", 16:"라면"},
          	  "저녁":{14:"된장찌개", 15:"김치찌개", 16:"삼겹살"}}
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'}}
  • dictionary 안에는 dictionary를 포함한 다양한 type의 data를 넣을 수 있다.

 

 

 야식이라는 key를 추가해보자

# "야식"이라는 새로운 Dictionary를 추가해보자
>>> dict_c["야식"] = {14:"김밥"}
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'},
 '야식': {14: '김밥'}}
  • dict["key] = value를 사용하면 새로운 key와 value를 추가할 수 있다.

 

 

 이번에는 dict_c의 key가 무엇이 있는지 가져와보자

# Dictionary의 key들을 가지고 와보자
>>> dict_c.keys()
dict_keys(['아침', '점심', '저녁', '야식'])


# Dictionary 안의 "아침"dictionary의 key들을 가지고 와보자
>>> dict_c["아침"].keys()
dict_keys([14, 15, 16])


# key "아침"의 16일 식단이 무엇인지 알아보자
>>> dict_c["아침"][16]
'곰탕'


# key "야식"에 16일 식단을 추가해보자
>>> dict_c["야식"][16] = "짜파게티"
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'},
 '야식': {14: '김밥', 16: '짜파게티'}}
 
 
# 알고보니 15일 저녁에 라볶이를 먹었다. 수정해보자
>>> dict_c["저녁"][15] = "라볶이"
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '라볶이', 16: '삼겹살'},
 '야식': {14: '김밥', 16: '짜파게티'}}
  • Dictionary의 key나 value는 쉽게 추가, 수정할 수 있으며, 내가 원하는 것을 가져올 수 있다.

 

 

때로는 Dictionary를 이용해서 다양한 Data들을 담아놓는 거대한 선반 역할을 할 수도 있다.

# 기존에 배웠던 Type들을 모두 넣어보자
>>> import tensorflow as tf
>>> import numpy as np

>>> dict_d = {"scalar_1":int(32.9),
    	      "list_1":["A1", "A2", "A3", "A4"],
        	  "array_1":np.array([[1,3,5,7],[2,4,6,8]]),
          	  "tensor_1":tf.zeros(shape=(2, 3))}

>>> dict_d
{'scalar_1': 32,
 'list_1': ['A1', 'A2', 'A3', 'A4'],
 'array_1': array([[1, 3, 5, 7],
        [2, 4, 6, 8]]),
 'tensor_1': <tf.Tensor: shape=(2, 3), dtype=float32, numpy=
 array([[0., 0., 0.],
        [0., 0., 0.]], dtype=float32)>}
        
        
>>> dict_d["tensor_1"]
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0., 0., 0.],
       [0., 0., 0.]], dtype=float32)>
  • Dictionary에는 다양한 종류의 Data를 넣어놓고 내가 필요한 Data를 꺼내 쓰는 곧간처럼 쓸 수 있다.
  • 말 그대로 Dictionary는 사전이므로, Tokenize 된 단어와 벡터들을 연결해서 word:Vector 사전으로 쓸 수도 있다.
  • Dictionary의 형태는 또 다른 자주 쓰이는 Type인 json과 동일하다. 그러므로, json을 사용하고자 하는 경우 Dictionary를 잘 다룰 수 있으면 매우 편하다.

 

 

Json

  • json은 데이터 분석을 위해서 다룰 줄 알아야 하는 Type 중 하나이지만, dictionary와 거의 유사하므로 따로 설명하지는 않곗다.
  • json은 Dictionary처럼 Data가 key:value로 이어진 형태이며, 따옴표(')와 큰따옴표(")로 발생하는 문제만 잘 해결한다면 Dictionary를 그대로 json으로 변환해서 사용해도 된다.
  • json은 json 모듈을 이용해 Python의 객체(Dictionary)를 Json 문자열로 변환시키면 된다.
  • 판다스의 DataFrame은 key(column: 열)과 value(row: 행)으로 서로 연결된 형태이므로, 사실 Dictionary와 굉장히 유사한 형태이다. 그러므로 Pandas의 DataFrame도 json 형태로 쉽게 만들 수 있다.
  • 추후 json module에 대해 설명하며 json에 대해 더 자세히 다루도록 하겠다.

 

 

 

 이번 포스트에서는 Dictionary에 대해 알아보았는데, 이번 포스트는 지금까지 포스트 중 가장 성의가 없지 않나?라는 생각이 들 정도로 글의 양이 적은 거 같다.

 다만, 위 함수들만 있어도 Dictionary를 사용하는데 큰 지장은 없으므로, 굳이 사족을 달지는 않겠다.

 다음 포스트에서는 데이터 분석가에게 익숙한 데이터 타입 중 하나인 DataFrame을 간략히 설명하고 기초 자료형에 대한 설명을 마치도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 Numpy의 array에 대해 간략하게 훑어보았다. 이번 포스트에선 머신러닝에서 대표적으로 사용되는 모듈인 tensorflow의 고유 Type 중 하나인 Tensor에 대해 학습해보겠다.

 

 

tensor

개요

  • Tensorflow는 Google에서 개발한 머신러닝의 대표적인 모듈로 tensor는 이 tensorflow의 대표적인 Type이지만, 정작 Tensorflow를 이용해서 머신러닝을 할 때에는 사용할 일이 많지 않은 type이기도 하다.
  • "Tensorflow를 활용해서 기계학습을 진행하는데 tensor를 쓰지 않는다니??"라는 생각이 들 수 있는데, tensorflow로 기계학습 진행 시, 일반적으로 keras를 이용해서 model을 생성하게 되고, 그 모델에 들어갈 데이터셋의 핸들링 과정에서 tensor로 핸들링하는 것보다 Numpy의 array를 이용해서 핸들링할 일이 더 많기 때문이다.
  • 애초에 Tensorflow의 tensor와 Numpy의 array의 관련 함수의 상당수가 거의 유사하다 보니, 여기저기 많이 쓰여 익숙하게 쓰는 Numpy의 array가 더 편하기 때문이기도 하다.

 

 

Tensor란?

  • Tensor란 N 차원의 배열(array)을 의미한다.
  • 일반적으로 1차원 배열을 벡터(Vector), 2차원 배열을 행렬(Matrix), 3차원 이상의 배열을 다차원 배열이라고 하며, 이들 모두를 통틀어서 Tensor라고 한다.
  • Tensor는 N 차원 배열이므로, 배열을 다루는 Numpy의 array와 태생적으로 기능이 유사할 수 밖에 없다.

 

 

Rank

Rank 이름 내용
0 스칼라 원소 1개, Python-기초: 1.0. 자료형(1) - scalar 참조
1 벡터 1차원 array, Vt = [0.1, 0.2, 0.3]
2 행렬 2차원 array, Mt = [[1, 3, 5] , [2, 4, 6]]
3 3 차원 텐서 3차원 array - Channel 추가
n n 차원 텐서 n차원 array - 축이 n개인 array
  • 뭔가 대충 설명한 느낌이 강한데, 보다 구체적으로 이해하려면 머릿속에 그림을 그려봐야한다.
  • Scalar > Vector > Matrix > 3-Tensor 이런 식으로 하나하나 층을 쌓아보자

  • Scalar를 하나의 원소로 보고, 그 원소가 일렬로 쭉 쌓으면 Vector, 그 Vector를 아래로 쭉 쌓으면 Matrix, Matrix를 N개의 층을 쌓으면 N-Tensor가 되는 것이다.
  • 보통 3-Tensor는 그림을 Tensor화(수치화) 했을 때, Red, Green, Blue에 대한 3개의 Layer를 쌓는다. 이를 Channer이라 한다.

 

 

Tensor를 가지고 놀아보자

# Tensor를 생성해보자.
# 1. 0으로 채워진 Tensor를 만들어보자
>>> tf.zeros(shape=(3, 10))
<tf.Tensor: shape=(3, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
       
       
# 2. 1로 채워진 Tensor를 만들어보자
>>> tf.ones(shape=(3,4), dtype = "int16")
<tf.Tensor: shape=(3, 4), dtype=int16, numpy=
array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]], dtype=int16)>
       
       
# 3. 내가 원하는 상수로 채워진 Tensor를 만들어보자 
>>> tf.fill(dims=(3, 6), value=3)
<tf.Tensor: shape=(3, 6), dtype=int32, numpy=
array([[3, 3, 3, 3, 3, 3],
       [3, 3, 3, 3, 3, 3],
       [3, 3, 3, 3, 3, 3]])>
       
     
# 4. array를 만들듯이 Tensor를 만들어보자
>>> tf.constant([[1,3,5,7],[2,4,6,8]])
<tf.Tensor: shape=(2, 4), dtype=int32, numpy=
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])>
       

# 5. array를 이용해서 Tensor를 만들어보자
>>> array1 = np.array([[1, 3, 5, 7], [2, 4, 6, 8]])
>>> tf.constant(array1)
<tf.Tensor: shape=(2, 4), dtype=int32, numpy=
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])>


# 6. 패턴을 갖는 Tensor를 만들어보자
>>> tf.range(1, 20, 3)
<tf.Tensor: shape=(7,), dtype=int32, numpy=array([ 1,  4,  7, 10, 13, 16, 19])>
  • 별 설명 없이 여러 tensor를 만들어보았는데, 이를 보면 앞서 봤던 numpy의 array와 굉장히 유사하다는 생각이 들지 않는가?
  • 애초에 tf.constant() 함수에 array를 담으면 tensor가 되고 모든 형태의 array를 포함하는 것이 tensor이므로, 굳이 tensor를 공부하지 않아도 Numpy의 array만 잘 다루면 tensor를 무리 없이 쓸 수 있다.

 

# Tensor의 다양한 속성들을 뽑아보자
>>> TS1 = tf.constant([[1, 3, 5, 7, 9, 11], [2, 4, 6, 8, 10, 12], [2, 3, 5, 7, 11, 13]],
						dtype="float64")
>>> TS1
<tf.Tensor: shape=(3, 6), dtype=float64, numpy=
array([[ 1.,  3.,  5.,  7.,  9., 11.],
       [ 2.,  4.,  6.,  8., 10., 12.],
       [ 2.,  3.,  5.,  7., 11., 13.]])>
   
   
# 1. shape을 뽑아보자 
>>> TS1.shape
TensorShape([3, 6])


# 2. shape을 바꿔보자
>>> tf.reshape(TS1, shape=(2, 9))
<tf.Tensor: shape=(2, 9), dtype=float64, numpy=
array([[ 1.,  3.,  5.,  7.,  9., 11.,  2.,  4.,  6.],
       [ 8., 10., 12.,  2.,  3.,  5.,  7., 11., 13.]])>


# 3. dtype을 뽑아보자
>>> TS1.dtype
tf.float64


# 4. dtype을 바꿔보자
>>> tf.cast(TS1, dtype="int64")
<tf.Tensor: shape=(3, 6), dtype=int64, numpy=
array([[ 1,  3,  5,  7,  9, 11],
       [ 2,  4,  6,  8, 10, 12],
       [ 2,  3,  5,  7, 11, 13]], dtype=int64)>
  • 위 예제를 보면 굳이 tensor로 변환을 하지 않고, Numpy로 데이터 핸들링을 하는 것이 더 편하겠다는 생각이 들지 않는가?
  • tensor를 통해 사용할 수 있는 함수가 이외에도 수없이 많으나, 이에 시간을 할애하기보다는 scipy, pandas, matplotlib 등 다양한 모듈에서 폭넓게 사용할 수 있는 Numpy를 공부하는 것이 보다 효율적으로 판단되므로, 본 블로그에서는 tensor의 관련 함수는 여기까지만 정리하도록 하고 Numpy를 더 자세히 학습하도록 하겠다.

 

 

dtype

  • 위 예제를 보면 dtype="int64"와 같이 우리가 기존에 알고 있는 int 뒤에 숫자가 붙어 있는 것을 알 수 있다.
  • dtype은 float32, float64, int8, int16, int32, int64, unit8, string, bool 등이 있으며 뒤에 있는 숫자는 bit를 의미한다.
  • 종종 앞에 있는 dtype인 float은 일치하지만 bit가 일치하지 않아 오류가 발생하기도 하니, 혹여 dtype error가 뜨는 경우, 이를 확인해보도록 하자.

 

 

 

 지금까지 가볍게 tensor에 대해 학습해보았다. tensor는 Numpy의 array와 기능이 거의 일치하며, tensorflow로 학습을 할 때도 Numpy를 주로 사용하니 더 파고들진 않겠다.

 그러나 위에서 설명한 Tensor Rank 그림은 상당히 중요한 그림이므로, 꼭 숙지하도록 하자.

728x90
반응형
728x90
반응형

 지금까지 scalar, list Type에 대해서 알아보았다. 이제 남은 대표적인 Type은 array, tensor, dictionary, DataFrame이 있는데, 이들은 앞서 다뤘던 두 Type에 비해 훨씬 심도 깊은 학습이 필요하므로, 특징만 간략히 설명하고 넘어가겠다.

 

 

array

Numpy의 array 

  • Python의 단점을 이야기할 때, 흔히들 느린 속도를 꼽는데, 이 이미지를 한 번에 종식시킬 수 있는 것이 바로 Numpy라는 모듈이며, 그 Numpy 모듈의 기본 Type이 바로 array다.
  • Numpy는 C언어, Fortran을 기반으로 만들어졌기 때문에 연산 속도가 매우 빠르며, 쉬운 것이 장점인 파이썬으로 구현되어 있기에 C언어를 공부하지 않고도 복잡한 수학 연산 문제를 아주 쉽고 빠르게 접근할 수 있다.
  • 특히 데이터 분석가의 친구인 판다스의 단점인 느린 속도를 해결할 수 있기 때문에 데이터 분석을 하고자 한다면, Numpy를 아주 잘 다룰 수 있어야 한다.
  • Numpy의 array는 Scipy, tensorflow, sklearn, Matplotlib, Pandas 등 빅데이터 분석에서 필수로 사용되는 모듈을 활용하는 데에 있어 기초가 되어준다.
  • 보다 상세한 내용은 추후 Numpy에 대해 자세히 학습할 때 이야기해보도록 하자.
# array를 사용하기 위해선 numpy 모듈을 가지고 와야한다.
>>> import numpy as np

# array는 list를 만들고 np.array() 함수에 넣어서 생성하기도 한다.
>>> a = [1,2,3,4,5]
>>> a_array = np.array(a)
>>> a_array
array([1, 2, 3, 4, 5])
  • numpy 모듈을 불러오는 import numpy as np는 "numpy를 수입(import) 해오겠다 np 처럼(as)" 이라고 곧이곧대로 해석해도 된다. 
    1. 이게 파이썬의 대표적인 장점 중 하나로 꼽히는 파이썬 코드가 우리가 실제로 사용하는 언어와 굉장히 가깝다는 것을 보여주는 대표적인 예시중 하나이다.
  • np는 numpy의 줄임말이며, Python은 "."을 기준으로 하여, 모듈로부터 하위 함수로 한 단계 한단계 내려가는 형태를 가지고 있다.
    • 즉, np.array()는 numpy 모듈의 array() 함수라는 뜻이다.
# array에 담겨있는 data type(dtype)은 상당히 중요하다.
>>> a_array.dtype
dtype('int32')


# 소수(float)로 구성된 array를 만들어보자
>>> b = np.arange(1, 10, 2, dtype = "float")
>>> b
array([1., 3., 5., 7., 9.])

>>> b.dtype
dtype('float64')
  • array.dtype은 array의 data type을 알 수 있다.
  • np.arange()은 앞서 list의 range와 비슷한 기능을 하며, array를 바로 생성한다.
  • array 내 data를 정수로 입력했다 할지라도 dtype을 "float"으로 지정하면 소수로 생성된다.

 

 

array 연산과 Broadcast

# Numpy의 array 연산은 R의 Vector 연산과 굉장히 유사하다.
>>> c = np.array([1, 3, 5, 7, 9])
>>> d = np.array([2, 4, 6, 8, 10])
>>> e = np.array([10, 20])


# 자리수가 동일한 array(vector)끼리는 동일한 위치의 원소끼리 연산이 이루어진다.
>>> print(c+d) 
[ 3  7 11 15 19]
>>> print(c*d)
[ 2 12 30 56 90]
>>> print(c/d)
[0.5        0.75       0.83333333 0.875      0.9]


# 자리수가 동일하지 않은 array(vector)간의 연산은 이루어지지 않는다.
>>> print(c + e)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-3b9c09c64b20> in <module>
----> 1 print(c + e)

ValueError: operands could not be broadcast together with shapes (5,) (2,) 
  • Numpy의 array 연산을 보면 R의 Vector 연산과 꽤 유사한 것을 알 수 있다.
  • Numpy의 array는 Tensorflow의 기본 Type인 Tensor와 거의 동일한 개념으로 봐도 문제없다.
    1. Tensor란 다차원 배열(array)을 아우르는 말이며, 이 안에는 1차원 배열인 Vector와 2차원 배열인 Metrics를 포함하는 것이다.
    2. 선형 대수학의 관점에서 접근할 때, array에 보다 익숙해지기 위해 1차원 array는 단순하게 vector로 부르도록 하겠다.
  • shape(모양)이 동일하지 않은 array끼리 연산 시, ValueError가 발생한다.
# BroadCast
>>> c
array([1, 3, 5, 7, 9])

>>> c * 2
array([ 2,  6, 10, 14, 18])

>>> c + 2
array([ 3,  5,  7,  9, 11])

>>> c / 2
array([0.5, 1.5, 2.5, 3.5, 4.5])
  • 1차원 array에 scalar 값을 연산 시, array의 모든 원소에 scalar값이 연산된다.
# array를 여러개 쌓으면 행렬이 된다.
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * 2
array([[ 2,  6, 10, 14],
       [ 4,  8, 12, 16]])
 
 
# 행렬에 대한 Broadcast
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * np.array([10, 20, 30, 40])
array([[ 10,  60, 150, 280],
       [ 20,  80, 180, 320]])
  • m*n행렬에 대해 스칼라 값을 연산하거나  n*1 벡터를 연산하면 array의 Broadcast와 같은 방식으로 연산이 이루어진다.
#  행렬 곱
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * np.array([[10, 10, 10, 10], [20, 20, 20, 20]])
array([[ 10,  30,  50,  70],
       [ 40,  80, 120, 160]])
       

>>> g = np.array([[10,10],[20,20],[30,30],[40,40]])
>>> np.dot(f,g)
array([[500, 500],
       [600, 600]])
  • 형태(shape)가 동일한 행렬끼리 *+-/와 같은 연산 수행 시, 동일한 위치에 있는 원소끼리 곱해진다(이는 우리가 일반적으로 아는 행렬곱이 아니다.).
  • m*n행렬, n*o행렬과 같이 행렬곱이 가능한 대상을 np.dot(mat1, mat2)을 하는 경우 우리가 아는 행렬곱이 연산된다.

 

 

배열의 형태

# array의 shape
>>> vt1 = np.array([1,2,3,4])
>>> vt2 = np.arange(1, 5, 0.1)
>>> mat1 = np.array([[1,3,5,7],[2,4,6,8]])
>>> mat2 = np.array([[1,10],[2,20],[3,30],[4,40]])

>>> print(vt1.shape)
(4,)
>>> print(vt2.shape)
(40,)
>>> print(mat1.shape)
(2, 4)
>>> print(mat2.shape)
(4, 2)


# 형태 변환(reshape)
>>> vt1.reshape((4,1))
array([[1],
       [2],
       [3],
       [4]])
>>> mat2.reshape((2,4))
array([[ 1, 10,  2, 20],
       [ 3, 30,  4, 40]])
       
       
# 평활(Flatten)
>>> mat1
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])
>>> mat1.flatten()
array([1, 3, 5, 7, 2, 4, 6, 8])

>>> mat2
array([[ 1, 10],
       [ 2, 20],
       [ 3, 30],
       [ 4, 40]])
>>> mat2.flatten()
array([ 1, 10,  2, 20,  3, 30,  4, 40])
  • 앞서 이야기했던 array의 shape은 Numpy를 다루는 데 있어 필수 사항이며, 나아가 딥러닝을 다루는데 주로 사용되는 모듈인 tensorflow를 사용할 때도 매우 중요하다.
  • 상당수의 연산 오류는 shape이나 dtype이 일치하지 않아 발생한다.
  • reshape() 함수를 이용해서 array의 형태를 바꿀 수 있다.
  • 우리가 일반적으로 2차원으로 아는 행렬은 flatten() 함수를 통해 수월하게 벡터화되며, 이는 텐서플로우 학습 시, 상당히 중요한 내용이다.
    1. 행렬이 1차원 배열로 변환 가능하다는 것은 우리가 아는 2차원으로 알고 있는 행렬은 사실 상 1차원임을 의미한다. 이에 대해선 추후 Numpy를 설명하면서 보다 자세히 짚고 넘어가도록 하겠다.

 

 

 Numpy의 핵심 Type인 array는 이를 공부하는 데만 해도 상당한 시간을 투자해야 하기 때문에 이번 포스트에서는 가장 기본적인 array의 성격들만 알아보았다.

 앞서 list를 학습할 때, 발생했던 상당 부분의 list의 한계점으로 여겨졌던 부분들은 array를 통해 대부분 해결 가능하다.

 또 다른 Type인 tensorflow의 tensor는 numpy의 array와 굉장히 유사하며, 함수에 약간의 차이가 있긴하나 거의 동일한 기능들이 존재한다. 또한, tensorflow 학습 시, keras를 위주로 쓰게 되며, 굳이 tensor로 연산하기보다 numpy의 array로 연산하는 것을 추천한다. 

  1. tensorflow에서 tensor를 다루는 함수는 numpy의 array를 다루는 함수와 기능이 거의 일치한다. 굳이 tensor를 다루는 함수를 공부하지 않아도 tensorflow를 다루는데 큰 지장이 없다.
  2. tensorflow에 대해 추후 학습하겠으나, tensorflow 2.0 version에 들어오며, keras를 주로 활용하여 딥러닝을 실시하게 될 것이다.
  3. 상세한 내용은 python-Numpy 카테고리에서 포스팅하도록 하겠다.
728x90
반응형

+ Recent posts