무냐의 개발일지

[3/20] NLP 프로젝트1 _문자 전처리(split, strip 등) 본문

Data Scientist Bootcamp

[3/20] NLP 프로젝트1 _문자 전처리(split, strip 등)

무냐코드 2024. 3. 20. 12:11

 

1. 텍스트 데이터를 문자열로 저장하기

 

변수에 데이터를 할당하면 이 데이터들은 컴퓨터의 주기억장치인 메모리(RAM)에 저장됩니다.

 

1) 인코딩과 디코딩

컴퓨터 세상은 온통 숫자 0과 1 즉, 이진 데이터(binary data)로 표현되기 때문에 데이터도 마찬가지로 0과 1로 변환돼 다뤄지게 됩니다.

이진 데이터의 최소 단위는 비트(bit)이고, 비트가 8개 모이면 바이트(byte)가 되는데요. 메모리에는 바이트로 저장이 됩니다.

 

 

  • 인코딩 (encoding): 문자열을 바이트로 변환하는 과정
  • 디코딩 (decoding) : 바이트를 문자열로 변환하는 과정

유니코드 : 국제 표준 문자 (유니코드 테이블)

UTF-8 : 인코딩 방식

 

전 세계 문자를 모두 표시할 수 있는 표준 코드를 제정하였습니다. 이것이 바로 유니코드(Unicode)입니다. 유니코드는 글자와 코드가 1:1매핑되어 있는 ‘코드표'이다. 유니코드는 오직 한 가지 버전만 존재합니다. 그리고 UTF-8, UTF-16 등은 유니코드로 정의된 텍스트를 메모리에 인코딩하는 방식들을 말합니다.

 

  • ord() : 해당 문자에 대응하는 유니코드 숫자를 반환합니다.
  • chr() : 해당 유니코드 숫자에 대응하는 문자를 반환합니다.
 
 
 

2) 문자열 다루기 

| 이스케이프 문자
 
문자열에 작은따옴표(')나 큰따옴표("), 줄 바꿈 등의 특수문자를 포함하고 싶은데, 그 문자가 특수문자가 아닌 것처럼 처리하고 싶을 때 이스케이프 문자를 사용할 수 있습니다. 이스케이프 문자는 \[특정 문자] 형태로, 직접 입력할 수 없는 일부 문자를 문자열에 포함시킬 수 있는 특수 문자인데요. 이 이스케이프 문자를 사용해서 한번 시도해 봅시다.

| 원시 문자열

 이스케이프 문자를 무시하고 싶을 때는 어떻게 해야 할까요? 이때 사용하는 것이 원시 문자열(raw string). 문자열을 시작하는 따옴표 앞에 r을 붙이면 이스케이프 문자가 적용되지 않은 있는 그대로의 원시 문자열을 나타낼 수 있습니다.

#- 예제 코드2
print('Please don\'t touch it')
print(r'Please don\'t touch it')

#결과
Please don't touch it
Please don\'t touch it

 

| startswith, endswith

 

EmployeeID = ['OB94382', 'OW34723', 'OB32308', 'OB83461', 
                                  'OB74830', 'OW37402', 'OW11235', 'OB82345'] 
Production_Employee = [P for P in EmployeeID if P.startswith('OB')]   # 'OB'로 시작하는 직원 ID를 다 찾아봅니다
Production_Employee

 

| 공백 문자 처리 : trimming

 

  • 스페이스(space) : 한 칸 띄어쓰기
  • 탭(tab) \t : 네 칸 띄어쓰기. 때에 따라 두 칸 띄어쓰기로 표기되기도 합니다.
  • 줄 바꿈(new line) : 줄 바꿈
  • 라인 피드 (line feed, 개행) \n : 줄 바꿈을 엄밀히 말하면 라인 피드라고 합니다.
  • 캐리지 리턴 (carriage return, 복귀) \r : 커서를 맨 앞으로 이동시키는 것, 즉 커서를 원위치로 복귀(return)한다는 뜻입니다. 커서를 맨 앞으로 보내고,\r 뒤에 문자가 있으면 그 문자를 출력합니다.

 

| 공백문자 제거

strip()

txt = "      Strip white spaces.      "
print('[{}]'.format(txt))
print('--------------------------')

#- 양쪽 공백 제거 : strip()
print('[{}]'.format(txt.strip()))
print('--------------------------')

#- 왼쪽 공백 제거 : lstrip()
print('[{}]'.format(txt.lstrip()))
print('--------------------------')

#- 오른쪽 공백 제거 : rstrip()
print('[{}]'.format(txt.rstrip()))

 

 

| 대소문자

upper() : 모든 문자를 대문자로 변환합니다.
lower() : 모든 문자를 소문자로 변환합니다.
capitalize() : 첫 글자만 대문자로 변환합니다.

 

| isX

문자열의 구성에 따라 불린(boolean)의 값을 반환(return)해줍니다.

 

  • isupper() : 문자열이 모두 대문자로만 되어 있으면 True, 그렇지 않으면 False를 반환
  • islower() : 문자열이 모두 소문자로만 되어 있으면 True, 그렇지 않으면 False를 반환
  • istitle(): 문자열의 첫 글자만 대문자로 되어 있으면 True, 그렇지 않으면 False를 반환
  • isalpha(): 문자열이 모두 알파벳 문자로만 되어 있으면 True, 그렇지 않으면 False를 반환
  • isalnum(): 문자열이 모두 알파벳 문자 숫자로만 되어 있으면 True, 그렇지 않으면 False를 반환
  • isdecimal(): 문자열이 모두 숫자로만 되어 있으면 True, 그렇지 않으면 False를 반환

| join() , split()

#- join()
stages = ['fundamentals', 'exploration', 'goingdeeper']
",".join(stages)

#결과
'fundamentals,exploration,goingdeeper'
#- split()
'fundamentals,exploration,goingdeeper'.split(',')

#결과
['fundamentals', 'exploration', 'goingdeeper']

 

| replace()

 

sent = 'I can do it!'
sent.replace('I', 'You')

 

  • 가변 객체(mutable object)
    • 객체를 생성한 후 객체의 값을 수정할 수 있습니다.
    • 변수는 값이 수정된 같은 객체를 가리키게 됩니다.
    • e.g. list, set, dict
  • 불변 객체(immutable object)
    • 객체를 생성한 후 객체의 값을 수정할 수 없습니다.
    • 변수는 해당 값을 가진 다른 객체를 가리키게 됩니다.
    • e.g. int, float, complex, bool, string, tuple, frozen set

 

3) 정규 표현식 (regular expression, regex)

 

* 구현 순서

#특정 문자열을 바꾸는 2가지 방법(replace, 정규표현식)
import re
sent = 'I can do it!'
pattern = re.sub("I", "You", sent)
pattern

sent = 'I can do it!'
rep = sent.replace("I", "You")
rep
import re

#1)찾으려는 문자열의 패턴을 정의한다 : compile
#1단계 :  "the"라는 패턴을 컴파일한 후 패턴 객체를 리턴합니다. 
pattern = re.compile("the")    

#2) 정의된 패턴과 매칭되는 경우를 찾아서 다양한 처리를 한다
# 2단계 : 컴파일된 패턴 객체를 활용하여 다른 텍스트에서 검색을 수행합니다.
pattern.findall('of the people, for the people, by the people')

#결과
['the', 'the', 'the']

 

정규 표현식을 활용하기 위해 꼭 명시적으로 re.compile()을 호출해야 하는 것은 아닙니다. 아래 코드는 위에서 2단계로 수행했던 내역을 명시적 컴파일 과정 없이 한 줄로 동일하게 처리하고 있습니다.

 

re.findall('the', 'of the people, for the people, by the people')

결과는 동일하게 나온다. 이렇게 쓰는게 그래도 간결해서 좋아보인다.

 

 

| 메서드

  • search() : 일치하는 패턴 찾기 (일치 패턴이 있으면 MatchObject를 반환합니다)
  • match() : search()와 비슷하지만, 처음부터 패턴이 검색 대상과 일치해야 합니다.
  • findall() : 일치하는 모든 패턴 찾기 (모든 일치 패턴을 리스트에 담아서 반환합니다)
  • split() : 패턴으로 나누기
  • sub() : 일치하는 패턴으로 대체하기

 

| 특수문자

  • [ ] : 문자
  • - : 범위
  • . : 하나의 문자
  • ? : 0회 또는 1회 반복
  • * : 0회 이상 반복
  • + : 1회 이상 반복
  • {m, n} : m ~ n
  • \d : 숫자, [0-9]와 동일
  • \D : 비 숫자, [^0-9]와 동일
  • \w : 알파벳 문자 + 숫자 + _, [a-zA-Z0-9_]와 동일
  • \W : 비 알파벳 문자 + 비숫자, [^a-zA-Z0-9_]와 동일
  • \s : 공백 문자, [ \t\n\r\f\v]와 동일
  • \S : 비 공백 문자, [^ \t\n\r\f\v]와 동일
  • \b : 단어 경계
  • \B : 비 단어 경계
  • \t : 가로 탭(tab)
  • \v : 세로 탭(vertical tab)
  • \f : 폼 피드
  • \n : 라인 피드(개행문자)
  • \r : 캐리지 리턴(원시 문자열)

 

ex) 예를 들어 '2020'이나 '2021'처럼 특정 연도를 찾을 때는 re.compile("2020")처럼 쓰면 되겠지만 1000년 이후의 연도를 모두 찾고 싶다면 어떻게 될까요?

숫자는 \d에 해당하기 때문에 \d\d\d\d를 사용하면 숫자 4개가 연달아 있는 부분을 찾을 수 있겠네요.

그런데 1000년 이후이니까 맨 앞자리가 1이나 2이겠네요.

그럼 1~2를 뜻하는 [1-2]를 사용하고 뒤에 숫자 3개가 연달아 있도록 [1-2]\d\d\d 처럼 쓸 수 있어요.

 \d\d\d \d{3}으로 바꿔서 숫자가 세 번 등장하는 것을 표현할 수 있습니다.

\d{3}-이 두 번 반복되는 것이 보이네요. (\d{3}-){2}로 바꾸면 될 것 같네요!

 

#- 연도(숫자)
text = """
The first season of America Premiere League  was played in 1993. 
The second season was played in 1995 in South Africa. 
Last season was played in 2019 and won by Chennai Super Kings (CSK).
CSK won the title in 2000 and 2002 as well.
Mumbai Indians (MI) has also won the title 3 times in 2013, 2015 and 2017.
"""
pattern = re.compile("[1-2]\d\d\d")
pattern.findall(text)

#결과
['1993', '1995', '2019', '2000', '2002', '2013', '2015', '2017']

#- 전화번호(숫자, 기호)
phonenumber = re.compile(r'\d{3}-\d{3}-\d{4}')
phone = phonenumber.search('This is my phone number 010-111-1111')
if phone:
  print(phone.group())
print('------')
phone = phonenumber.match ('This is my phone number 010-111-1111')
if phone:
  print(phone.group())
  
#결과
010-111-1111
------

 

#- 이메일(알파벳, 숫자, 기호)
text = "My e-mail adress is doingharu@aiffel.com, and tomorrow@aiffel.com"
pattern = re.compile("[0-9a-zA-Z]+@[0-9a-z]+\.[0-9a-z]+")
pattern.findall(text)

#[0-9a-zA-Z]+를 보면 숫자 0~9, 알파벳 대소문자([0-9a-zA-Z])가 여러 번(+) 등장한다는 이야기네요.
# \. 을 기준으로 [0-9a-z]+은 숫자 0~9, 알파벳 소문자가 여러 번 나타난다는 의미네요.

 

 

2. 파일과 디렉터리

1) 파일

 

#write
f = open("hello.txt","w") 
#- open(파일명, 파일모드)
#- 파일을 열고 파일 객체를 반환합니다. 
for i in range(10):
    f.write("안녕")
    #- write() 메서드로 '안녕'을 10번 씁니다.
f.close()
#- 작업이 끝나면 close() 메서드로 닫아줍니다. *필수!

print("완료!")

#read
with open("hello.txt", "r") as f:
  print(f.read())
  
#결과
안녕안녕안녕안녕안녕안녕안녕안녕안녕안녕

 

  • f.read() : 파일을 읽는다.
  • f.readline() : 파일을 한 줄씩 읽는다.
  • f.readlines() : 파일 안의 모든 줄을 읽어 그 값을 리스트로 반환한다.
  • f.write(str) : 파일에 쓴다. 문자열 타입을 인자로 받는다.
  • f.writelines(str) : 파일에 인자를 한 줄씩 쓴다.
  • f.close() : 파일을 닫는다.
  • f.seek(offset) : 해당 파일의 위치(offset)를 찾아 파일의 커서를 옮긴다. 파일의 처음 위치는 0이다.
  • f.tell(): 현재 커서의 위치를 반환한다.

 

2) 디렉터리 (참고)

 

* 최상위 폴더 (루트 디렉터리 Root directory)

  • Window 운영 체제 : C:\
  • Linux 계열 운영 체제 : /     (맥)
#print working directory (현재 있는 디렉토리)
pwd  

#change directory (띄어쓰기만 넣으면 루트 디렉터리로 간다)
cd / 

#list (디렉토리 목록 확인)
ls  

#copy (파일, 디렉토리 복사. 디렉토리 복사 시에는 -r 옵션)
cp -r testdir testdir_cp

#make directory (디렉토리 생성. -p 옵션 주면, 하위 디렉토리까지 한번에 생성)
mkdir 

#remove (파일, 디렉토리 삭제, 디렉토리 삭제 시에는 -r 옵션, -f옵션은 삭제여부 묻지 않고 바로 삭제)
rm -rf testdir/

 

 

| 파이썬이 지원하는 디렉터리 표준 라이브러리

  • sys
  • os
  • glob

 

3) 모듈과 패키지

  • 모듈(module) : 파이썬으로 만든 코드가 들어간 파일 .py
  • 패키지(package) : 기능적으로 동일하거나 동일한 결과를 만드는 모듈들의 집합 또는 폴더. 종종 라이브러리라고도 불림
  • 라이브러리(library) : 모듈과 패키지의 집합. 패키지보다 포괄적인 개념이나 패키지와 혼용되어 사용되기도 함.
  • PIP(Package Installer for Python) : 패키지 관리자로 파이썬을 설치하면 기본으로 설치됨
  • PyPA(Python Packaging Authority) : 파이선 패키지를 관리하고 유지하는 그룹
  • PyPI(The Python Package Index) : 파이썬 패키지들의 저장소

 

  • sys.path : 현재 폴더와 파이썬 모듈들이 저장되는 위치를 리스트 형태로 반환
  • sys.path.append() : 자신이 만든 모듈의 경로를 append 함수를 이용해서 추가함. 그 후 추가한 디렉터리에 있는 파이썬 모듈을 불러와 사용할 수 있다.
  • os.chdir() : 디렉터리 위치 변경
  • os.getcwd() : 현재 자신의 디렉터리 위치를 반환
  • os.mkdir() : 디렉터리 생성
  • os.rmdir() : 디렉터리 삭제 (단, 디렉터리가 비어 있을 경우)
  • glob.glob() : 해당 경로 안의 디렉터리나 파일들을 리스트 형태로 반환
  • os.path.join() : 경로(path)를 병합하여 새 경로 생성
  • os.listdir() : 디렉터리 안의 파일 및 서브 디렉터리를 리스트 형태로 반환
  • os.path.exists() : 파일 혹은 디렉터리의 경로 존재 여부 확인
  • os.path.isfile() : 파일 경로의 존재 여부 확인
  • os.path.isdir() : 디렉터리 경로의 존재 여부 확인
  • os.path.getsize() : 파일의 크기 확인

 


3. 여러가지 파일 포맷 다루기

 

 

1) CSV 파일

 

billboardchart = {
  			1 : ["Tho Box","Roddy Ricch","2019-12-19"],
                 2 : ["Don't Start Now", "Dua Lipa", "2019-11-01"],
                 3 : ["Life Is Good", "Future Featuring Drake", "2020-02-10"],
                 4 : ["Blinding", "The Weeknd", "2019-11-29"],
                 5 : ["Circles", "Post Malone","2019-08-30"]}

with open("billboardchart.csv","w") as f:
    for i in billboardchart.values():
        data = ",".join(i)
        f.write(data+"\n")

import csv

header = ["title", "singer", "released date"]

with open("billboardchart.csv","r") as inputfile:
    with open("billboardchart_out.csv","w", newline='\n') as outputfile:
        fi = csv.reader(inputfile, delimiter=',')
        fo = csv.writer(outputfile, delimiter=',')
        fo.writerow(header)
        for row in fi:
            fo.writerow(row)

 

 

* 마크업 언어

인터넷 웹상에서 문서 즉, 내용을 교환할 때 이러한 마크업 언어를 이용합니다.

 

2) XML (Extensible Markup Language의 약자로, 다목적 마크업 언어)

API에서 데이터를 요청하고 저장할 때 XML이나 JSON 형식을 이용해 데이터를 교환

 

부모 - 자식 관계

   <Table>
         <Row>
            <Cell>
               <Data ss:Type="String">Notice</Data>
            </Cell>
         </Row>

 

ElementTree

파이썬 표준 라이브러리인 ElementTree는 XML 관련 기능을 다음과 같이 제공합니다.

  • Element() : 태그 생성
  • SubElement() : 자식 태그 생성
  • tag : 태그 이름
  • text : 텍스트 내용 생성
  • attrib : 속성 생성

dump()

생성된 XML 요소 구조를 시스템(sys.stdout)에 사용합니다. 출력 형식은 일반 XML 파일로 기록됩니다.

  • write() : XML 파일로 저장
  • 리스트(list)와 유사한 메서드를 제공
    • append(), insert(), remove(), pop()

 

import xml.etree.ElementTree as ET

person = ET.Element("Person")
name = ET.Element("name")
name.text = "이펠"
person.append(name)

age = ET.Element("age")
age.text = "28"
person.append(age)

ET.SubElement(person, 'place').text = '강남'

ET.dump(person)

 

#결과

<Person>
<name>이펠</name>
<age>28</age>
<place>강남</place>
</Person>
#속성값 변경 : attrib 메서드
#name 태그명 변경 : tag 메서드

person.attrib["id"] = "0x0001"
name.tag = "firstname"
ET.dump(person)
<Person id="0x0001">
<firstname>이펠</firstname>
<age>28</age>
<place>강남</place>
</Person>

 

#lastname 태그를 firstname 태그 다음에 삽입하고, 속성에 date 넣기
lastname = ET.Element('lastname', date='2020-03-20')
lastname.text = '아'
person.insert(1,lastname)
ET.dump(person)
<Person id="0x0001">
<firstname>이펠</firstname>
<lastname date='2020-03-20'>아</lastname>
<age>28</age>
<place>강남</place>
</Person>

- 인덱스번호는 firstname 다음이니까 1 

# XML 파일 저장
ET.ElementTree(person).write('person.xml')


!ls 
#이걸로 확인

 

 

XML 파싱하기

파싱(parsing, 구문 분석)이란 어떤 문자열을 의미 있는 토큰(token)으로 분해해, 문법적 의미와 구조를 반영한 파스 트리(parse tree)를 만드는 과정

 

- 파이썬에서는 그 방법으로 크게 2가지를 제공하는데요. 위에서 살펴 본 ElementTree가 첫 번째이고, 다른 하나가 바로 BeautifulSoup 라이브러리를 사용하는 것입니다. 

 

 

3) JSON (JavaScript Object Notation의 약자로, 웹 언어인 JavaScript의 데이터 객체 표현 방식)

- 웹 브라우저와 다른 애플리케이션 사이에서 HTTP 요청으로 데이터를 보낼 때 널리 사용하는 표준 파일 포맷 중 하나로, XML과 더불어 웹 API나 config 데이터를 전송할 때 많이 쓰입니다.

 

- 이런 모양으로 생겼다. Dictionary랑 매우 유사한 구조를 가졌다.

person = {
      "first name" : "Yuna",
      "last name" : "Jung",
      "age" : 33,
      "nationality" : "South Korea",
      "education" : [{"degree":"B.S degree", "university":"Daehan university", "major": "mechanical engineering", "graduated year":2010}]
       } 

 

#json파일 저장
import json

person = {
      "first name" : "Yuna",
      "last name" : "Jung",
      "age" : 33,
      "nationality" : "South Korea",
      "education" : [{"degree":"B.S degree", "university":"Daehan university", "major": "mechanical engineering", "graduated year":2010}]
       } 

with open("person.json", "w") as f:
    json.dump(person , f)


#json파일 읽기
with open("person.json", "r", encoding="utf-8") as f:
    contents = json.load(f)
    print(contents["first name"])
    print(contents["education"])