-
chapter-9 문자열 처리하기판다스 2021. 9. 23. 00:39In [1]:
word='grail' sent='a scratch'
인덱스로 문자열 추출하기¶
데이터프레임에서 인덱스를 이용하여 원하는 데이터를 추출했던 것을 기억하나요? 문자열도 인덱스를 사용하여 추출할 수 있습니다. 문자는 길이가 1인 문자열로 생각하면 됩니다. 다음은 문자열과 인덱스를 함께 나타낸 표입니다. 간단하게 살펴보고 바로 실습으로 넘어갈까요?
문자열 grail과 인덱스¶
- 인덱스 : 0 : 1 : 2 : 3 : 4
- 문자열 : g : r : a : i : l
- 음수인덱스: -5 : -4 : -3 : -2 : -1
문자열 a scratch와 인덱스¶
- 인덱스 : 0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8
- 문자열 : a : : s : c : r : a : t : c : h
- 음수인덱스: -9 : -8 : -7 : -6 : -5 : -4 : -3 : -2 : -1
1.¶
각 문자열의 첫 번째 문자는 오른쪽과 같은 방법으로 추출합니다.
In [3]:word='grail' sent='a scratch' print(word[0])
g
In [4]:print(sent[0])
a
4.¶
엔덱스 슬라이싱을 사용하면 여러 개의 문자를 한 번에 추출할 수 있습니다. 오른쪽은 인덱스 슬라이싱을 사용하여 0~2번째 인덱스의 문자를 추출한 것입니다. 이때 대괄호에 지정한 왼쪽 범위는 포함하고 오른쪽 범위는 포함하지 않습니다. 즉 , 인덱스가 0,1,2인 문자를 추출합니다.
In [5]:print(word[0:3])
gra
4.¶
음수를 사용해도 문자열을 추출할 수 있습니다. 만약 인덱스를 -1로 지정하여 문자열을 추출하면 마지막 문자가 추출됩니다. 오른쪽은 다양한 방법으로 음수 범위의 인덱스 슬라이싱을 한 것입니다.
전체 문자열을 추출할 때 음수를 사용하면 안 됩니다.¶
문자열의 마지막 문자를 추출하려면 인덱스를 -1로 지정하면 됩니다. 그런데 전체 문자열을 추출하기 위해 인덱스 슬라이싱의 번위를 [0:-1]로 지정하면 마지막 문자는 추출되지 않습니다. 왜 그럴까요? 바로 인덱스 슬라이싱의 오른쪽 범위로 지정한 값은 문자열 추출 범위에서 제외되기 때문입니다. 그러면 전체 문자열을 추출하려면 어떻게 해야 할까요? 이런 경우에는 len 메서드를 사용하여 문자열의 길이를 오른쪽 범위로 지정하면 됩니다.
전체 문자열 추출하기¶
만약 인덱스 슬라이싱을 할 때 문자열 범위를 왼쪽 범위나 오른쪽 범위 중 하나만 지정하면 어떻게 될까요? 왼쪽 범위가 비어 있으면 문자열의 첫 번째 위치부터문자열을 추출합니다. 반대로 오른쪽 범위가 비어 있으면 문자열의 마지막 위치까지 문자열을 추출합니다. 즉, word[0:3]과 word[:3]은 같은 범위의 문자열을 추출합니다. 이런 성질을 이용하여 전체 문자열을 추출해 보겠습니다.
1.¶
왼쪽 범위를 비우고 문자열을 추출하면 오른쪽과 같이 됩니다.
In [7]:print(word[0:3])
gra
In [8]:print(word[:3])
gra
2.¶
만약 오른쪽 범위를 비우면 문자열의 마지막 위치까지 문자열을 추출합니다.
In [9]:print(sent[2:len(sent)])
scratch
In [10]:print(sent[2:])
scratch
3.¶
양쪽을 모두 비우면 전체 문자열을 추출합니다.
In [11]:print(sent[:])
a scratch
4.¶
만약 문자열을 일정한 간격으로 건너뛰며 추출해야 한다면 콜론(:)을 하나 더 추가하여 추출 간격을 지정하면 됩니다. 오른쪽은 전체 문자열을 추출하되 다음 추출 문자는 자신을 포함하여 거리가 2인 인덱스의 문자를 추출한 것입니다.
In [12]:print(sent[::2])
asrth
09-2 문자열 메서드¶
지금까지 인덱스 슬라이싱으로 문자열을 추출하는 방법을 알아보았습니다. 그런데 문자열이 너무 길어서 내가 원하는 문자가 몇 번째 인덱스에 있는지 파악하기 어렵거나 문자열에 포함된 소문자를 모두 대문자로 바꾸고 싶다면 어떻게 해야 할까요? 이런 경우에는 문자열 메서드를 사용하면 됩니다. 자주 사용하는 문자열 메서드와 간단한 실습 코드를 다음 표에 정리했습니다. 문자열 메서드 표 아래에 있는 실습 코드를 정리한 표도 함께 읽어보세요.
문자열 메서드¶
- 메서드 : 설명
- capitalize : 첫 문자를 대문자로 변환합니다.
- count : 문자열의 개수를 반환합니다.
- startswith : 문자열이 특정 문자로 시작하면 참이 됩니다.
- endswith : 문자열이 특정 문자로 끝나면 참이 됩니다.
- find : 찾을 문자열의 첫 번째 인덱스를 반환합니다. 실패 시 -1을 반환합니다
- index : find 메서드와 같은 역할을 수행하지만 실패 시 ValueError를 반환합니다.
- isalpha : 모든 문자가 알파벳이면 참입니다.
- isdecimal : 모든 문자가 숫자면 참입니다.
- isalnum : 모든 문자를 소문자로 변환합니다.
- upper : 모든 문자가 알파벳이거나 숫자면 참입니다.
- lower : 모든 문자를 소문자로 변환 합니다.
- upper : 모든 문자를 대문자로 변환합니다.
- replace : 문자열의 문자를 다른 문자로 교체합니다.
- strip : 문자열의 맨 앞과 맨 뒤에 있는 빈 칸을 제거합니다.
- split : 구분자를 지정하여 문자열을 나누고, 나눈 값들의 리스트를 반환합니다.
- partiton : split 메서드와 비슷한 역활을 수행하지만 구분자도 반환합니다.
- center : 지정한 너비로 문자열을 늘이고 문자열을 가운데 정렬합니다.
- zfill : 문자열의 빈 칸을 '0'으로 채웁니다.
파이썬 문자열 메서드 --실습코드¶
- 실습 코드 : 실행결과
- "black knight".capitalize() : 'Black knight'
- "It's 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') : 7
- "It's just a flesh wound!".index('scratch') : ValueError
- "odl 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)
- "9".zfill(with=5)
In [15]: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"N73도58'26.302"w
2. splitlines 메서드¶
이번에는 splitlines 메서드에 대해 알아볼까요? splitlines 메서드는 여러 행을 가진 문자열을 분리한 다음 리스트로 반환합니다. multi_str에 저장된 문자열을 splitlines 메서드로 분리하면 다음과 같이 됩니다.
In [16]:multi_str="""Guard:what? Ridden on a horse? King Arthur:Yes! Guard: You're using coconuts! King Arthur:what? Guard:You've got ... coconyt[s] and you're bangin' 'em together. """ print(multi_str)
Guard:what? Ridden on a horse? King Arthur:Yes! Guard: You're using coconuts! King Arthur:what? Guard:You've got ... coconyt[s] and you're bangin' 'em together.
In [17]:multi_str_split=multi_str.splitlines() print(multi_str_split)
['Guard:what? Ridden on a horse?', 'King Arthur:Yes!', "Guard: You're using coconuts!", 'King Arthur:what?', "Guard:You've got ... coconyt[s] and you're bangin' 'em together."]
3.¶
인덱스 슬라이싱을 응용하면 특정 문자열만 가져올 수도 있습니다. 다음은 Guard의 대사만 가져온 것입니다.
In [18]:guard=multi_str_split[::2] print(guard)
['Guard:what? Ridden on a horse?', "Guard: You're using coconuts!", "Guard:You've got ... coconyt[s] and you're bangin' 'em together."]
4. replace 메서드¶
조금 더 나아가 볼까요? 만약 Guard의 대사에서 'Guard:'라는 문자열을 빼고 싶다면 어떻게 해야 할까요? 그럴 때는 문자열을 치환해 주는 replace 메서드를 사용하면 됩니다. 다음과 같이 "Guard:"를 ""로 치환하여 Guard: 를 없앴습니다.
In [19]:guard=multi_str.replace("Guard:","").splitlines()[::2] print(guard)
['what? Ridden on a horse?', " You're using coconuts!", "You've got ... coconyt[s] and you're bangin' 'em together."]
In [21]:var='flesh wound' s= "It's just a {}!" print(s.format(var))
It's just a flesh wound!
In [22]:print(s,format('scratch'))
It's just a {}! scratch
2.¶
플레이스 홀더는 여러 번 사용해도 됩니다. 하지만 여러 단어를 전달할 때는 어떻게 해야할까요? format 메서드에 여러 단어를 전달하려면 인덱스 개념을 응용하면 됩니다. 다음은 1개의 단어만 사용합니다. 따라서 인덱스를 0으로 지정한 플레이스 홀더를 사용했습니다.
In [23]:print("In 2005, Lu Chao of China recited{:,}digits of pi".format(67890))
In 2005, Lu Chao of China recited67,890digits of pi
3.¶
소수는 좀더 다양한 방법으로 포매팅할 수 있습니다. 다음은 플레이스 홀더에 0:4, 0:4%를 넣어 7/67890의 결괏값을 포매팅한 것입니다. {0:4}과{0:4%}에서 0은 format메서드에서 전달받을 값의 인데스를 의미하고. 4.는 소수점 이하의 숫자를 4개까지 출력하겠다는 것을 의미합니다. 이때 % 를 사용하면 7/67890의 결괏값을 백분율로 환산하여 출력합니다.
In [24]:print("I remember {0:.4}or{0:.4%} of what Lu Chao recitde".format(7/67890))
I remember 0.0001031or0.0103% of what Lu Chao recitde
4.¶
만약 사용자의 아이디가 5자리의 숫자로 표현되어야 한다면 42와 같은 2자리의 값은 00042로 출력해야겠죠. 다음은 format의 0번째 값을 5자리의 수로 표현하되 빈 칸을 0으로 채워 출력한 것입니다.
In [25]:print("My ID number is {0:05d}".format(42))
My ID number is 00042
In [27]:s='I only know %d digits of pi' %7 print(s)
I only know 7 digits of pi
2.¶
만약 삽입할 값이 문자열이라면 값을 삽입할 위치에 %s라고 입력해야 합니다 이때 %와 s사이에 소괄호를 사용하여 변수를 저장합니다.
In [28]:print('Some digits of %(cont)s: %(value).2f'%{'cont':'2','value':2.718})
Some digits of 2: 2.72
정규식 표현 -- 문법,특수 문자¶
정규식 표현에 사용되는 문법과 특수 문자를 다음과 같이 표로 정리했습니다. 정규식을 사용하려면 다음 표를 참고하여 찾고자 하는 문자열의 정규식 패턴을 만들어야 합니다. 여러분은 아직 정규식 패턴에 익숙하지 않기 떄문에 https://regex101.com 사이트에서 정규식 패턴을 실습해 보는 것을 추천합니다. 이 사이트는 정규식 패턴의 결과를 바로 볼 수 있어 실습에 많은 도움이 될 것입니다. 아래 표를 참고하여 실습 코드를 작성하고 결과를 확인해 보세요.
기본 정규식 문법¶
- 문법 : 실습 코드 : 설명
- . : .a : 문자(a) 앞에 문자 1개가 있는 패턴을 찾습니다.
- ^ : ^I like : 문자열의 처음부터 일치하는 패턴을 찾습니다.
- $ : on$ : 문자열의 끝 부분부터 일치하는 패턴을 찾습니다.
-
- : n\d* : n이후 숫자(\d)가 0 개이상인 패턴을 찾습니다.
-
- : n\d+ : n이후 숫자 (\d)가 1개 이상인 패턴을 찾습니다.
- ? : apple? : ?의 앞의 문자(e)가 있거나 없는 패턴을 찾습니다.
- {m} : n\d{2,4} : n 이후 숫자 (\d)가 2개({2})이상 , 4개({4})이하인 패턴을 찾습니다.
- \ : *, \?, + : *,?,+와 같은 특수 문자를 검색할 때 이스케이프 문자)를 사용합니다.
- [] : [cfh]all : c,f,h 중 1개를 포함하고 나머지 문자열이 all인 패턴을 찾습니다.
- l : apple | application : apple나 application중 하나만 있는 패턴을 찾습니다 (OR 연산).
- () : (\d+)-(\d+)-(\d+) : ()에 지정한 패턴을 찾을 때 사용합니다.
정규식 특수 문자¶
- 특수 문자 : 설명
- \d : 숫자 1개를 의미합니다([0-9]와 동일).
- \D : 숫자 이외의 문자 1개를 의미합니다.([^0-9]와 동일)
- \s : 공백이나 탭 1개를 의미합니다.
- \S : 공백 문자 이외의 문자 1개를 의미합니다.
- \w : 알파벳 1개를 의미합니다.
- \W : 알파벳 이외의 문자 1개를 의미합니다.(한글,중국어등)
정규식 표현 - 메서드¶
- 함수 : 실습코드 : 설명
- search : m= re.esarch('[0-9]{4}',test) / print(m.group()) : 첫 번째로 찾은 패턴의 양 끝 인덱슬를 반환합니다.
- match : m= re.esarch('[0-9]{4}',test) / print(m) : 문자열의 처음부터 검색하여 찾아낸 패턴의 양 끝 인덱스를 반환합니다.
- fullmatch : m = re.fullmatch('\d+\s\d+\s\d+\s\d+',test) / print(bool(m))
- split : m=re.split('\s',test) / print(m)
- findall : m=re.findall('[0-9]{4}',test) / print(m)
- finditer : m= re.finditer('[0-9]{4}',test) / for match in m:
- sub : print(re.sub('\s','-',test)) / '010-1234-5678-090' : 첫 번째 인자로 전달한 값(패턴)을 두 번째 인자로 전달한 값으로 교체합니다.
정규식으로 전화번호 패턴 찾기¶
전화번호와 같은 단순한 데이터도 복잡하고 다양한 정규식이 필요합니다. 처음에는 정규식이 복잡하고 어려워 보여 부담스러울 수 있습니다. 하지만 정규식은 원하는 패턴의 문자열을 가장 효율적으로 찾아주는 방법입니다. 따라서 데이터 분석이 필요한 분야에서는 정규식을 알아두면 좋습니다.
1.¶
다음과 같이 re 모듈과 테스트용 문자열을 준비합니다.
In [30]:import re tele_num='1234567890'
2.¶
match 메서드를 사용하여 길이가 10인 숫자를 확인해 보겠습니다. pattern 인자에는 10개의 숫자를 의미하는 10개의 \d를, string에는 테스트용 문자열인 tele_num을 전달했습니다. 만약 패턴을 찾으면 Match 오브젝트를 반환합니다. Match 오브젝트를 출력하면 span에는 찾은 패턴의 인덱스가, match에는 찾은 패턴의 문자열이 있는 것을 확인할 수 있습니다.
In [31]:m = re.match(pattern='\d\d\d\d\d\d\d\d\d\d', string=tele_num) print(type(m))
<class 're.Match'>
In [32]:print(m)
<re.Match object; span=(0, 10), match='1234567890'>
3.¶
이때 bool 메서드에 m을 전달하면 True나 False를 얻을 수 있습니다. 즉, match 메서드가 반환한 Match 오브젝트는 bool 메서드로 True,False를 판단할 수 있습니다.
In [33]:print(bool(m))
True
In [34]:if m: print('match') else: print('no match')
match
4.¶
Match 오브젝트에는 찾아낸 패턴의 정보를 확인할 수 있는 다양한 메서드가 있습니다. start와 end 메서드는 첫 번째와 마지막 인덱스를 반환합니다. span 메서드는 찾은 패턴의 첫 번째와 마지막 인덱스를 한 번에 반환합니다. group 메서드는 찾아낸 패턴을 반환합니다.
In [35]:print(m.start())
0
In [36]:print(m.end())
10
In [37]:print(m.span())
(0, 10)
In [38]:print(m.group())
1234567890
5.¶
그런데 전화번호를 입력하는 방법은 1234567890이 아니라 123-456-7890이나 123 456 7890과 같은 방법도 있습니다. 다음은 앞에서 사용한 패턴을 그대로 적용하여 123 456 7890을 검사한 것입니다. 그러면 패턴을 찾지 못해 Match 오브젝트가 아닌 None을 출력합니다.
In [39]:tele_num_spaces='123 456 7890' m = re.match(pattern='\d{10}', string=tele_num_spaces) print(m)
None
In [40]:if m: print('match') else: print('no match')
no match
6.¶
과정 5의 문제를 해결하려면 정규식을 다시 작성해야 합니다. 다음과 같이 빈칸을 의미하는 정규식 \s?를 넣어 패턴을 다시 만들겠습니다.
In [43]: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.¶
지역 코드는 소괄호를 감싸고 나머지 번호는 반각 기호로 구분한 전화번호의 정규식은 다음과 같이 작성합니다.
In [45]: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.¶
국가 코드까지 있는 전화번호의 정규식은 다음과 같이 작성합니다.
In [47]: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 메서드로 처리한 것입니다. 패턴을 컴파일한 다음 변수에 저장했기 때문에 정규식 메서드를 반복해서 사용할 수 있어 매우 편리합니다.
In [48]:p= re.compile('\d{10}') s='1234567890' m=p.match(s) print(m)
<re.Match object; span=(0, 10), match='1234567890'>
마무리하며¶
세상에는 문자열로 이루어진 데이터가 너무나도 많습니다. 그래서 데이터 과학자들은 문자열을 처리하는 방법을 매우 중요하게 생각합니다. 그래서 09장에서는 판다스와 전혀 상관없어 보이는 문자열 처리를 설명한 것입니다. 다음 장으로 넘어가기 전에 반드시 09장의 예제를 한 번씩 실습해 보기 바랍니다.
출처 : "do ot 데이터분석을 위한 판다스 입문"
'판다스' 카테고리의 다른 글
chapter-11 그룹 연산 (0) 2021.09.23 chapter-10 apply 메서드 활용 (0) 2021.09.23 chapter-8 판다스 자료형 (0) 2021.09.23 chapter-7 깔끔한 데이터 (0) 2021.09.23 chapter-6 누락값 처리하기 (0) 2021.09.23