본문 바로가기

Data Handling/Data Preprocessing

훈련, 테스트 데이터 통합하기

오늘은 훈련 데이터와 테스트 데이터를 하나로 통합하는 코드를 분석해 볼텐데, 이번 포스팅에서는 데이터를 통합하기 위해 사용된 함수들을 위주로 정리해보겠습니다.

 

해당 코드는 캐글의 '산탄데르 제품 추천 경진대회'의 8등 팀(Alejo y Miro)의 소스코드 중 일부이며, '캐글 우승작으로 배우는 머신러닝 탐구생활' 도서를 참고했음을 밝힙니다.

그럼 시작해보겠습니다!!

 

 

순서

  1. 문제
  2. 뼈대 구상 및 코드
  3. 사용된 함수 정리

 


 

1. 문제

train set은 총 48개의 열로 구성되어 있습니다. 그 중 24개는 고객 관련 특성이고, 나머지 24개는 사용 중인 제품 관련 특성입니다.

test set은 고객 관련 특성 24개만 존재하고, 제품 관련 특성 24개에 대해서는 0으로 처리해서 train set과 통합하고자 합니다.

 

train set과 test set을 open() 함수를 통해 읽고, write() 함수를 통해 새로운 파일에 통합하는것이 목표입니다.

주의해야 할 사항으로는 고객 관련 특성 중 하나인 'nomprov'의 값에는 간혹 ','가 포함되어 있다는 점입니다. 

이게 왜 주의해야하냐구요? ',' 를 기준으로 열을 구분할때 문제가 생길수 있기 때문입니다.

 

 

 

2. 뼈대 구상 및 코드

새로운 파일에 train set과 test set을 불러와야하기 때문에 가장 먼저 기준이 되는 header를 잡아줄 것입니다.

두번째로는 header가 생긴 새로운 파일에 한 줄씩 써내려갈텐데요, 아까 말씀드린 'nomprov' 변수 값에 ',' 가 들어간 줄은 구분해주는 작업이 필요할 것입니다. 

 

2-1. header 가져오기

# 가져올 header를 가공하는 작업
head = open('파일 경로/train_ver2.csv').readline().strip('\n').split(',')
head = [h.strip('"') for h in head]

# 새로운 파일에 header를 작성하는 작업
open('파일 경로/new_file.csv','w').write("%s\n"%",".join(head))

코드의 직관적 해석 :

train 데이터의 첫번째 줄을 읽어오고, 그 줄에 포함된 줄바꿈(\n) 표시를 삭제해라. 그리고 콤마(,)를 기준으로 나눠라.

나눠진 단어마다 큰따옴표(")를 삭제해라.

가공된 단어들을 다시 하나의 문자열로 합치는데 콤마(,)로 구분해줘라. 그리고 그걸 'new_file'에 써라.

 

 

 

2-2. 새로운 파일에 한 줄씩 써내려가기

# train set을 new_file에 한줄씩 옮기는 작업
for line in open('파일 경로/train_ver2.csv'):
    fields = line.strip('\n').split(',')
    fields = [field.strip() for field in fields]
    open('파일 경로/new_file.csv','w').write("%s%s\n"%(",".join(fields),suffix))

코드의 직관적 해석 :

train data에서 한 줄씩 줄바꿈(\n) 표시를 삭제하고 콤마(,)를 기준으로 나눠라.

나눠진 단어 하나하나마다 양 끝의 공백이 있으면 제거해라.

가공된 단어들을 콤마(,)로 구분된 하나의 문자열로 합친것과 suffix를 'new_file'에 작성해라.

 

 

 

전체 코드

# train과 test 데이터를 하나로 합치는 코드
def clean_data(fi, fo, header, suffix):
    
    # fi : 읽어오는 데이터 경로
    # fo : 통합된 데이터 경로
    # header : header를 사용할지 말지에 대한 boolean
    # suffix : test set의 부족한 24개의 열에 대해 공백으로 채우기 위한 매개변수
    
    # 1. header를 새로운 파일에 옮기는 과정
    head = fi.readline().strip('\n').split(',') # 파일의 첫 열을 읽고, \n을 제거하고 ,를 중심으로 나눔
    head = [h.strip('"') for h in head] # 나눠진 음절마다 "를 삭제함
    
    # 'nomprov' 변수 위치를 저장 (nomprov 값에 ,를 포함하는 경우가 있어서)
    for i, h in enumerate(head):
        if h == "nomprov":
            ip = i
            
    # header가 True이면 저장할 파일에 header를 작성한다
    if header:
        fo.write("%s\n"%",".join(head)) # 나눠져있는 head를 하나의 문자열로 join해라(ex. 'a','b','c'-> 'a,b,c'), 그리고 새로운 파일에 써라
        
    # 2. 기존 파일을 새로운 파일에 한줄씩 가져오는 과정
    n = len(head)
    for line in fi: # 한 줄씩 가져온다
        fields = line.strip('\n').split(',') # \n을 삭제하고 ,를 기준으로 나눈다
        
        if len(fields) > n: # 나눠진게 열의 수보다 많으면(nomprov값에 ,가 들어있었으면) 다시 합쳐준다
            prov = fields[ip] + fields[ip+1]
            del fields[ip]
            fields[ip] = prov
        
        assert len(fields) == n # 나눠진 값과 열의 수가 동일한지 확인
        fields = [field.strip() for field in fields] # 나눠진 값마다 양 끝의 공백을 제거
        fo.write("%s%s\n"%(",".join(fields),suffix)) # ,와 함계 하나의 문자열 join + suffix 넣고 줄바꿈(\n)
 
 
 # 데이터 합치기
 with open('파일 경로/new_file.csv','w') as f: # 새로운 파일 생성하고 작성할것이다
    clean_data(open('파일 경로/train_ver2.csv'),f,True,"")
    
    comma24 = "".join(["," for i in range(24)]) # 24개의 ,를 생성하고 하나의 문자열로 합치기
    clean_data(open('파일 경로/test_ver2.csv'),f,False,comma24)

 

 

 

 

3. 사용된 함수 정리

3-1. open(), write()

open() 은 파일을 열어주는 내장함수 입니다.

지금까지 pandas의 read_csv() 함수를 사용했었는데 뭐가 다르냐구요?

pandas.read_csv() 함수는 파일 전체를 가져오는데 반해, open() 함수는 한 줄씩 처리하는 기능 차이라고 할 수 있습니다.

따라서 특정 데이터의 header만을 필요로 하는 경우에는 굳이 파일 전체를 가져올 필요 없이 open() 함수로 처리하는것이 메모리 소모 측면에서 효율적이라고 할 수 있습니다.

 

open() 함수를 사용할 때에는 파일 경로만 지정해줘도 되는데, 해당 파일의 사용 목적에 따라서 변경해줘야할 값들이 있습니다. 

대표적인 세 가지만 소개해드리자면,

- 'r' [default] : 읽기전용

- 'w' : 쓰기전용 (기존 파일이 있다면 지우고 새로 만듬)

- 'a' : 쓰기전용 (기존 파일에 이어서 쓰는 경우)

이정도가 있습니다. 

 

이렇게 open() 함수를 통해서 파일을 열고, read(), readline(), readlines() 와 같은 함수를 통해 한 줄씩 읽거나, write() 함수를 통해 수정 및 작성하는 형태로 사용됩니다.

 

마지막으로 가져온 파일이 더이상 필요하지 않을 때에는 close() 함수로 닫아주어야 하는데요 (메모리 낭비 때문),

with 구문이 이것을 대체해주기도 합니다. 

예시를 통해 확인하고 open() 함수와 write() 함수는 마무리하겠습니다.

다음 두 코드는 같은 기능을 합니다.

# with 구문을 사용한 코드
with open('파일경로/train.csv','w') as f:
    f.write('hwis doc')
    
# close() 함수를 사용한 코드
f = open('파일경로/train.csv','w')
f.write('hwis doc')
f.close()

# f.read() : 내용 전체를 문자열로 리턴
# f.readline() : 한 줄 리턴
# f.readlines() : 리스트에 한 줄씩 넣어져서 리턴

# f.write() : 쓰기

 

 

3-2. strip(), split(), join()

- strip()

전체 코드에서 보면 알 수 있듯, 데이터를 읽어오고 나누고 가공하다보면 단어 앞뒤로 공백이나 줄바꿈(\n) 표시가 남아있게 됩니다. 이를 해결해주는 좋은 함수가 바로 strip() 입니다. 따로 지정해주는것 없이 strip()만 사용하게 되면 앞뒤의 공백을 없애주고, 예시 코드에서처럼 strip('"')을 하게되면 큰따옴표(")를 삭제해줍니다.

 

- split()

특정 기호 혹은 문자를 중심으로 나누는 것을 도와주는 함수입니다. 가장 많이 쓰이는 형태로는 split(',') 인데요, 

아무래도 csv파일을 많이 다루다보니 구분자가 콤마(,)로 되어있는 경우가 많기 때문입니다.

 

- join()

split() 함수와 반대되는 개념이라고 볼 수 있습니다. apb = ['a', 'b', 'c', 'd'] 로 나눠져 있는 알파벳이 있다고 했을때, 하나의 문자열로 나타낼때 join() 함수를 사용합니다. 한 줄씩 파일에 작성(write)할때 정말 '한 줄' 단위가 필요하기 때문에 join() 함수의 사용이 필요한데, 각 문자들을 합쳐줄때 구분자를 지정할 수 있습니다. 예를들어 ",".join(apb) 를 한다면 apb = ['a, b, c, d'] 가 되겠네요. 그리고 이렇게 만들어진 apb를 write() 함수에 넣어주면 '한 줄'이 작성되겠네요 !!