티스토리 뷰

#14 판다스 chapter 14. 문자열 처리하기

 

문자열 처리하기

- 문자열 다루기

- 파이썬과 문자열

문자열은 작은따옴표나 큰따옴표로 감싸서 만든다. 다음은 작은 따옴표로 grail, a scratch 라는 문자열 데이터를 만들어 변수 word,sent 에 저장한 것이다.

 

word = 'grail'
sent = 'a scratch'

 

- 인덱스로 문자열 추출하기

데이터프레임에서 인덱스를 이용하여 원하는 데이터를 추출했던 것을 기억할 것이다. 문자열도 인덱스를 사용하여 추출할 수 있다. 문자는 길이가 1인 문자열로 생각하면 된다.

 

-문자열 grail과 인덱스

 

인덱스

 

  • 0 = g = 음수 -5
  • 1 = r = 음수 -4
  • 2 = a = 음수 -3
  • 3 = i = 음수 -2
  • 4 = l = 음수 -1

-문자열 a scratch와 인덱스

 

  • a = 0 = 음수 -9
  • = 1 = 음수 -8
  • s = 2 = 음수 -7
  • c = 3 = 음수 -6
  • r = 4 = 음수 -5
  • a = 5 = 음수 -4
  • t = 6 = 음수 -3
  • c = 7 = 음수 -2
  • h = 8 = 음수 -1
  • 문자열 추출하기

1) 각 문자열의 첫 번째 문자는 아래와 같이 추출한다.

 

word = 'grail'
sent = 'a scratch'

print(word[0])

g

print(sent[0])

a

 

2) 인덱스 슬라이싱을 사용하면 여러 개의 문자를 한 번에 추출할 수 있다. 아래는 0~2번째 인덱스의 문자를 추출한 것이다. 이때 대괄호에 지정한 왼쪽 범위(0)는 포함하고 오른쪽범위(3)는 포함하지 않는다. 즉 인덱스가 0,1,2인 문자를 추출한다.

 

print(word[0:3])

gra

 

3) 음수를 사용해도 문자열을 추출할 수 있다. 만약 인덱스를 -1로 지정하여 문자열을 추출하면 마지막 문자가 추출된다.

 

print(sent[-1])

h

print(sent[-9:-8])

a

print(sent[0:-8])

a

 

-알아두면 좋아요!

 

-전체문자열을 추출할 때 음수를 사용하면 안된다.

 

문자열의 마지막 문자를 추출하려면 인덱스를 -1로 지정하면 된다. 그런데 전체 문자열을 추출하기 위해 인덱스 슬라이싱의 범위를 [0:-1]로 지정하면 마지막 문자는 추출되지 않는다. 그 이유는 바로 인덱스 슬라이싱의 오른쪽 범위로 지정한 값은 문자열 추출 범위에서 제외되기 때문이다. 그렇다면 전체 문자열을 추출하려면 어떻게 해야 할까? 이런 경우에는 len 메서드를 사용하여 문자열의 길이를 오른쪽 범위로 지정하면 된다.

 

print(sent[2:-1])

scratc

print(sent[-7:-1])

scratc

s_len = len(sent)
print(s_len)

9

print(sent[2:s_len])

scratch

 

- 전체 문자열 추출하기

만약 인덱스 슬라이싱을 할 때 문자열 범위를 왼쪽 범외나 오른쪽 범위 중 하나만 지정하면 어떻게 될까? 왼쪽 범위가 비어있으면 문자열의 첫번째 위치부터 문자열을 추출한다. 반대로 오른쪽 범위가 비어있으면 문자열의 마지막 위치까지 문자열을 추출한다.

 

  • 왼쪽이나 오른쪽 범위를 지정하지 않고 문자열 추출하기

1) 왼쪽 범위를 비우고 문자열을 추출하면 아래와 같다.

 

print(word[0:3])

gra

print(word[:3])

gra

 

2) 만약 오른쪽 범위를 비우면 문자열의 마지막 위치까지 문자열을 추출한다.

 

print(sent[2:len(sent)])

scratch

print(sent[2:])

scratch

 

3) 양쪽을 모두 비우면 전체 문자열을 추출한다.

 

print(sent[:])

a scratch

 

4) 만약 문자열을 일정한 간격으로 건너뛰며 추출해야 한다면 콜론(:)을 하나 더 추가하여 추출 간격을 지정하면 된다.

 

print(sent[::2])

asrth

 

문자열 메서드

지금까지는 인덱스 슬라이싱으로 문자열을 추출하는 방법을 알아보았다. 그런데 문자열이 너무 길어서 내가 원하는 문자가 몇 번째 인덱스에 있는지 파악하기 어렵거나 문자열에 포함된 소문자를 모두 대문자로 바꾸고 싶다면 어떻게 해야 할까? 이런 경우에는 문자열 메서드를 사용하면 된다.

 

  • 문자열 메서드
  • capitalize : 첫 문자를 대문자로 변환한다.
  • count : 문자열의 개수를 반환한다.
  • startswith : 문자열이 특정 문자로 시작하면 참이된다.
  • endswith : 문자열이 특정 문자로 끝나면 참이된다.
  • find : 찾을 문자열의 첫 번째 인덱스를 반환한다. 실패시 -1을 반환한다.
  • index : find 메서드와 같은 역할을 수행하지만 실패 시 valueError를 반환하다.
  • isalpha : 모든 문자가 알파벳이면 참이다.
  • isdecimal : 모든 문자가 숫자면 참이다.
  • isalnum : 모든 문자가 알파벳이거나 숫자면 참이다.
  • lower : 모든 문자를 소문자로 변환하다.
  • upper : 모든 문자를 대문자로 변환하다.
  • replace : 문자열의 문자를 다른 문자로 교체한다.
  • strip : 문자열의 맨 앞과 맨 뒤에 있는 빈 칸을 제거한다.
  • split : 구분자를 지정하여 문자열을 나누고, 나눈 값들의 리스트를 반환한다.
  • partition : split 메서드와 비슷한 역할을 수행하지만 구분자도 반환한다.
  • center : 지정한 너비로 문자열을 늘이고 문자열을 가운데 정렬한다.
  • zfill : 문자열의 빈 칸을 '0' 으로 채운다.

 

"black Knight".capitalize()

'Black knight'

"It' just a flesh wound!".count('u')

2

"Halt! Who goes there?".startswith('Halt')

True

"coconut".endswith('nut')

True

"It's just a flesh wound!".find('u')

6

"It's just a flesh wound!".index('scratch')

ValueError

"old woman".isalpha()

False

"37".isdecimal()

True

"I'm 37".isalnum()

False

"Black Knight".lower()

'black knight'

"Black Knight".upper()

'BLACK KNIGHT'

"flesh wound!".replace('flesh wound', 'scratch')

'scratch!'

" I'm not dead. ".strip()

"I'm not dead."

"NI! NI! NI! NI!".split(sep=' ')

['NI!', 'NI!', 'NI!', 'NI!']

"3,4".partition(',')

('3', ',', '4')

"nine".center(10)

'   nine   '

"9".zfill(5)

'00009'

 

- join, splitlines, replace 메서드 실습하기

 

1) join 메서드

 

join 메서드는 문자열을 연결하여 새로운 문자열을 반환하는 메서드이다. 다음은 분리된 좌표를 합친 것이다. 이때 join 메서드 앞에 문자 ('')를 지정하면 해당 문자를 단어 사이에 넣어 연결한다.

 

d1 = '40'
m1 = "46'"
s1 = '52.837"'
u1 = 'N'

d2 = '73'
m2 = "58'"
s2 = '26.302"'
u2 = 'W'

coords = ' '.join([d1,m1,s1,u1,d2,m2,s2,u2])
print(coords)

40 46' 52.837" N 73 58' 26.302" W

 

2) splitlines 메서드

 

이번엔 splitlines 메서드에 대해 알아보자. splitlines 메서드는 여러 행을 가진 문자열을 분리한 다음 리스트로 반환한다. multi_str에 저장된 문자열을 splitlines 메서드로 분리하면 다음과 같다.

 

multi_str = """Guard: What? Rideen on a hores?
King Arthur:Yes!
Guard: You're using coconuts!
King Arthur: What?
Guard: You've got ... coconut[s] and you're bangin' 'em together.
"""
print(multi_str)

Guard: What? Rideen on a hores?
King Arthur:Yes!
Guard: You're using coconuts!
King Arthur: What?
Guard: You've got ... coconut[s] and you're bangin' 'em together.

multi_str_split = multi_str.splitlines()
print(multi_str_split)

['Guard: What? Rideen on a hores?', 'King Arthur:Yes!', "Guard: You're using coconuts!", 'King Arthur: What?', "Guard: You've got ... coconut[s] and you're bangin' 'em together."]

 

3) 인덱스 슬라이싱을 응용하면 특정 문자열만 가져올 수도 있다. 다음은 Guard의 대사만 가져온 것이다.

 

guard = multi_str_split[::2]
print(guard)

['Guard: What? Rideen on a hores?', "Guard: You're using coconuts!", "Guard: You've got ... coconut[s] and you're bangin' 'em together."]

 

4) replace 메서드

 

만약 대사에서 'Guard:'라는 문자열을 빼고 싶으면 어떻게 해야 할까? 그럴 때는 문자열을 치환해 주는 replace 메서드를 사용하면 된다. 다음과 같이 "Guard:"를 ""로 치환하여 Guard: 를 없앴다.

 

guard = multi_str.replace("Guard:", "").splitlines()[::2]
print(guard)

[' What? Rideen on a hores?', " You're using coconuts!", " You've got ... coconut[s] and you're bangin' 'em together."]

 

문자열 포매팅

- 문자열 포매팅하기

문자열 포매팅은 문자열을 편리하게 출력할 수 있게 해주는 기능이다. 즉, 출력할 문자열의 형식을 지정하거나 변수를 조합하여 출력하는 방법을 말한다.

 

  • 문자열 포매팅하기

1) 다음과 같이 단어를 삽입할 위치를 {}로 지정하고 format 메서드에 원하는 단어를 전달하면 {}의 위치에 전달한 단어를 삽입해 출력한다. 이때 {}를 플레이스 홀더라고 한다.

 

var = 'flesh wound'
s = "It's just a {}"

print(s.format(var))

It's just a flesh wound

print(s.format('scratch'))

It's just a scratch

 

2) 플레이스 홀더는 여러 번 사용해도 된다. 하지만 단어를 전달할 때는 어떻게 해야 할까? format 메서드에 여러 단어를 전달하려면 인덱스 개념을 응용하면 된다. 다음은 1개의 단어만 사용한다. 따라서 인덱스를 0으로 지정한 플레이스 홀더를 사용했다.

 

s = """Black Knight: 'Tis but a {0}.
King Arthur: A {0}? your arm's off!
"""

print(s.format('scratch'))

Black Knight: 'Tis but a scratch.
King Arthur: A scratch? your arm's off!

 

3) 플레이스 홀드에는 변수를 지정해도 된다. 단 format 메서드에 전달하는 문자열도 변수에 담아 전달해야 한다.

 

s = 'Hayden Planetarium Coordinates: {lat}, {lon}'
print(s.format(lat='40.7815 N', lon='73.9733 W'))

Hayden Planetarium Coordinates: 40.7815 N, 73.9733 W

 

- 숫자 데이터 포매팅하기

이번에는 숫자 데이터 포매팅에 대해 알아보자. 숫자 데이터 포매팅을 사용하면 실수의 소수점을 어디까지 출력할지 등을 설정할 수 있다.

 

  • 숫자 데이터 포매팅하기

1) 숫자 데이터도 플레이스 홀더를 설정할 수 있다.

 

print('Some digits of pi: {}'.format(3.14159265359))

Some digits of pi: 3.14159265359

 

2) 플레이스 홀더에 : , 를 넣으면 쉼표를 넣어 숫자를 표현할 수도 있다.

 

print('In 2005, Lu Choa of China recited {:,} digits of pi'.format(67890))

In 2005, Lu Choa of China recited 67,890 digits of pi

 

3) 소수는 좀 더 다양한 방법으로 포매팅 할 수 있다. 다음은 플레이스 홀더에 0:.4, 0:.4% 를 넣어 7/67890 의 결괏값을 포매팅한 것이다. {0:.4}과 {0:.4%}에서 0은 format 메서드에서 전달받을 값의 인덱스를 의미하고 .4 는 소수점 이하의 숫자를 4개까지 출력하겠다는 것을 의미한다. 이때 % 를 사용하면 7/67890의 결괏값을 백분율로 환산하여 출력한다.

 

print("I Remeber {0:.4} or {0:.4%} of what Lu Chao recited".format(7/67890))

I Remeber 0.0001031 or 0.0103% of what Lu Chao recited

 

4) 만약 사용자의 아이디가 5자리의 숫자로 표현되어야 한다면 42와 같은 2자리의 값은 00042로 출력해야한다. 다음은 format의 0번째 값(42)을 5자리의 수로 표현하되 빈칸을 0으로 채워 출력한 것이다.

 

print("My ID number is {0:05d}".format(42))

My ID number is 00042

 

- % 연산자로 포매팅하기

지금까지 플레이스 홀더를 사용하여 여러 가지 포매팅을 실습했다. 이번에는 % 연산자로 포매팅을 실습해 보겠다.

 

  • % 연산자로 포매팅하기

1) 만약 삽입할 값이 10진수라면 값을 삽입할 위치에 %d 라고 입력해야 한다. 그런다음 %d 연산자를 이용하여 삽입할 값(7)을 지정하여 출력한다.

 

s = 'I only know %d digits of pi' %7
print(s)

I only know 7 digits of pi

 

2) 만약 삽입할 값이 문자열이라면 값을 삽입할 위치에 %s 라고 입력해야 한다. 이때 % 와 s 사이에 소괄호를 사용하여 변수를 지정한다.

 

print('Some digits of %(cont)s: %(value).2f' % {'cont':'e', 'value':2.718}) 

Some digits of e: 2.72

 

- 알아두면 좋아요!

 

  • f-strings 포매팅 사용하기

f-strings 포매팅은 파이선 3.6 버전에서 새롭게 도입한 포매팅입니다. f-strings 포매팅의 사용 방법은 format 메서드의 사용 방법과 동일하다. 하지만 문자열 앞에 f를 붙인다는 점이 다르다. f-strings 포매팅은 읽기 쉬울 뿐만 아니라 문자열을 빠르게 처리한다는 장점이 있다.

 

var = 'flesh wound'
s = f"It's just a {var}!"
print(s)

It's just a flesh wound!

lat = '40.7815N'
lon = '73.9733W'
s = f'Hayden Planetarium Coordinates: {lat}, {lon}'
print(s)

Hayden Planetarium Coordinates: 40.7815N, 73.9733W

 

정규식으로 문자열 처리에 날개 달기

-정규식이란?

수만 개의 문자열 중에서 내가 원하는 패턴의 문자열만 추출하려면 어떻게 해야 할까? 예를 들어 I like apple, I like to make application 이라는 문자열에서 app을 포함하는 문자열만 추출하려면 어떻게 해야할까? find 메서드를 사용해도 되지만 이런 경우에는 정규식을 사용하면 편리하다.

 

  • 정규식 표현 -- 문법, 특수 문자
  • 기본 정규식 문법
. - .a 문자(a) 앞에 문자 1개가 있는 패턴을 찾는다
^ - ^I like 문자열의 처음부터 일치하는 패턴을 찾는다.
$ - 𝑜𝑛$  문자열의 끝 부분부터 일치하는 패턴을 찾는다.
n\d* - n 이후 숫자(\d)가 0개 이상인 패턴을 찾는다.
n\d+ - n 이후 숫자(\d)가 1개 이상인 패턴을 찾는다.
? - apple? ?의 앞의 문자(e)가 있거나 없는 패턴을 찾는다.
{m} - n\d{2} n 이후의 숫자(\d)가 2개인 패턴을 찾는다.
{m,n} - n\d{2,4} n 이후의 숫자가 2개 이상, 4개 이하인 패턴을 찾는다.
\ - *,?,+ *,?,+ 와 같은 특수 문자를 검색할 때 이스케이프 문자() 를 사용한다
[] - [cfh]all c,f,h 중 1개를 포함하고 나머지 문자열이 all인 패턴을 찾는다.
| - apple | applicationg apple 이나 application 중 하나만 있는 패턴을 찾는다.(OR연산)
() - (\d+)-(\d+)-(\d+) ()에 지정한 패턴을 찾을 때 사용한다.

 

 

  • 정규식 특수 문자
  • \d 숫자 1개를 의미한다
  • \D 숫자 이외의 문자 1개를 의미한다.
  • \s 공백이나 탭 1개를 의미한다.
  • \S 공백 문자 이외의 문자 1개를 의미한다.
  • \w 알파벳 1개를 의미한다.
  • \W 알파벳 이외의 문자 1개를 의미한다.(한글,중국어 등)
  • 정규식 메서드
  • serch : 첫 번째로 찾은 패턴의 양 끝 인덱스를 반환한다.
  • match : 문자열의 처음부터 검색하여 찾아낸 패턴의 양 끝 인덱스를 반환한다.
  • fullmatch : 전체 문자열이 일치하는지 검사한다.
  • split : 지정한 패턴으로 잘라낸 문자열 리스트로 반환한다.
  • findall : 지정한 패턴을 찾아 리스트로 반환한다.
  • finditer : findall 메서드와 기능이 동일하지만 iterator를 반환한다.
  • sub : 첫 번째 인자로 전달한 값을 두 번째 인자로 전달한 값으로 교체한다.
  • 정규식으로 전화번호 패턴 찾기

전화번호와 같은 단순한 데이터도 복잡하고 다양한 정규식이 필요하다. 처음에는 복잡하고 어려워 보여 부담스러울 수 있다. 하지만 정규식은 원하는 패턴의 문자열을 가장 효율적으로 찾아주는 방법이다.

 

1) 다음과 같이 re 모듈과 테스트용 문자열을 준비하자.

 

import re

tele_num = '1234567890'

 

2) match 메서드를 사용하여 길이가 10인 숫자를 확인해 보자. patter 인자에는 10개의 숫자를 의미하는 10개의 \d를 string 에는 테스트용 문자열은 tele_num을 전달했다. 만약 패턴을 찾으면 Match 오브젝트를 반환한다. Match 오브젝트로 출력하면 span에는 찾은 인덱스가, match 에는 찾은 문자열이 있는 것을 확인할 수 있다.

 

m = re.match(pattern='\d\d\d\d\d\d\d\d\d\d', string=tele_num)
print(type(m))

<class 're.Match'>

print(m)

<re.Match object; span=(0, 10), match='1234567890'>

 

3) 이때 bool 메서드에 m을 전달하면 True나 False를 얻을 수 있다. 즉, match 메서드가 반환한 Match 오브젝트는 bool 메서드로 True, False를 판단할 수 있다.

 

print(bool(m))

True

if m:
    print('match')
else:
    print('no match')
    
match

 

4) Match 오브젝트에는 찾아낸 패턴의 정보를 확인할 수 있는 다양한 메서드가 있다. start와 end 메서드는 첫 번째와 마지막 인덱스를 반환한다. span 메서드는 찾은 패턴의 첫 번째와 마지막 인덱스를 한 번에 반환한다. group 메서드는 찾아낸 패턴을 반환한다.

 

print(m.start())

0

print(m.end())

10

print(m.span())

(0, 10)

print(m.group())

1234567890

 

5) 그런데 전화번호를 입력하는 방법은 1234567890이 아니라 123-456-7890 이나 123 456 7890과 같은 방법도 있다. 다음은 앞에서 사용한 패턴을 그대로 적용하여 123 456 7890 을 검사한 것이다. 그러면 패턴을 찾지 못해 Match 오브젝트가 아닌 None을 출력한다.

 

tele_num_spaces = '123 456 7890'
m = re.match(pattern='\d{10}', string=tele_num_spaces)
print(m)

None

if m:
    print('match')
else:
    print('no match')
    
no match

 

6) 과정 5의 문제를 해결하려면 정규식을 다시 작성해야 한다. 다음과 같이 빈 칸을 의미하는 정규식 \s?를 넣어 패턴을 다시 만들자.

 

p = '\d{3}\s?\d{3}\s?\d{4}'
m = re.match(pattern=p, string=tele_num_spaces)
print(m)

<re.Match object; span=(0, 12), match='123 456 7890'>

 

7) 지역 코드는 소괄호로 감싸고 나머지 번호는 반각 기호로 구분한 전화번호의 정규식은 다음과 같이 작성한다.

 

tele_num_space_paren_dash = '(123) 456-7890'
p = '\(?\d{3}\)?\s?\d{3}\s?-?\d{4}'
m = re.match(pattern=p, string=tele_num_space_paren_dash)
print(m)

<re.Match object; span=(0, 14), match='(123) 456-7890'>

 

8) 국가 코드까지 있는 전화번호의 정규식은 다음과 같이 작성한다.

 

cnty_tele_num_space_paren_dash = '+1 (123) 456-7890'
p = '\+?1\s?\(?\d{3}\)?\s?\d{3}\s?-?\d{4}'
m = re.match(pattern=p, string=cnty_tele_num_space_paren_dash)
print(m)

<re.Match object; span=(0, 17), match='+1 (123) 456-7890'>

 

 - 알아두면 좋아요!

 

  • compile 메서드로 정규식 메서드 사용하기

패턴을 반복해서 사용하려면 compile 메서드로 패턴을 컴파일한 다음 변수에 저장하여 사용하면 된다. 다음은 앞에서 실습한 내용 중 하나를 compile 메서드로 처리한 것이다. 패턴을 컴파일한 다음 변수에 저장했기 때문에 정규식 메서드를 반복해서 사용할 수 있어 매우 편리하다.

 

p = re.compile('\d{10}')
s = '1234567890'
m = p.match(s)
print(m)

<re.Match object; span=(0, 10), match='1234567890'>

 

 

출처 : 데이터 분석을 위한 판다스 입문

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
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
글 보관함