앞선 포스팅에서 분석 결과 파일을 불러와서 pandas 라이브러리를 활용해 데이터 테이블만 추출하는 과정을 살펴봤습니다.
이번에는 포토샵의 batch-automation처럼 이러한 과정을 폴더 내의 모든 파일들에 일괄 적용해 보도록 하겠습니다.
아래와 같이 4개의 파일이 모였습니다. 근데 사실 평소에 이렇게 몇개 안되는 파일만 처리한다면 굳이 이렇게 에너지를 들여 자동화할 필요가 없겠네요. 저는 한 20~30개씩 처리하게 되니까 하나씩 엑셀로 열어서 복붙하기가 귀찮기도 하고, 또 작업이 너무 졸려서 실수할것 같다는 생각이 들어서 이런 식으로 작업 방법을 바꾸게 되었습니다.

먼저 이렇게 쥬피터 노트북 파일과 같은 폴더에 분석 데이터 파일을 넣습니다. 그리고 폴더 안에 있는 txt 파일들, 즉 처리하고자 하는 파일들의 목록을 만들어 보겠습니다.
# 필요한 라이브러리 호출
import os
import pandas as pd
import numpy as np
import chardet
# 파일명 및 헤더 지정을 위한 리스트 셋업
ld = []
nd = []
for name in os.listdir():
if ".txt" in name:
ld.append(name)
nd.append(name[:name.index(".txt")])
print(ld)
print(nd)
['Sample_4.txt', 'Sample_2.txt', 'Sample_3.txt', 'Sample_1.txt']
['Sample_4', 'Sample_2', 'Sample_3', 'Sample_1']
ld는 확장자까지 포함한 파일명, nd는 확장자를 뗀 파일명의 목록입니다. ld는 파일 불러오기 할 때 쓸거고, nd는 샘플 이름을 붙일때 활용할 목적으로 만들었습니다. 다들 그렇게 하시겠지만 분석할 때 파일명은 시료 이름 또는 중요한 실험조건 같은 정보들을 담고 있으니까요.
반복문을 활용하여 ld에 담긴 파일들에 앞선 포스팅에서 했던 작업 내용을 그대로 적용한 뒤, 필요한 부분만 추출해서 하나의 데이터 프레임으로 취합하도록 하겠습니다.
for i in range(len(ld)):
# 인코딩 형식 확인
with open(ld[i], "rb") as f_encode:
codec = chardet.detect(f_encode.read())["encoding"]
# 데이터 테이블 상단의 샘플정보 텍스트의 마지막 행 인덱싱 (skiprows를 지정하는데 사용)
with open(ld[i], "r", encoding = codec) as f_idx:
df_idx = pd.DataFrame(f_idx)
idx = df_idx[df_idx[0].str.contains("Curve Value")].index
# 폴더 내의 모든 측정 파일 (.txt)을 열고 이를 데이터프레임으로 변환하여 각각의 변수에 할당
with open(ld[i], "r", encoding = codec) as globals()["tma{}".format(i + 1)]:
globals()["tma_df{}".format(i + 1)]\
= pd.read_table(globals()["tma{}".format(i + 1)],\
header = 0,\
on_bad_lines = "skip",\
sep = "\s+",\
skiprows = int(idx[0]) + 1,\
names = ["index", "time", "ts{}".format(i + 1), "tr", nd[i]])
# with open 구문은 끝날때 파일이 자동으로 닫히므로 close() 함수가 필요없음
# 측정 단위가 들어간 행은 굳이 필요가 없으므로 제거 (텍스트가 깨져나오는 것은 인코딩 때문이며, 어차피 지울 행이므로 신경 안써도 됨)
globals()["tma_df{}".format(i + 1)].drop([0], axis = 0, inplace = True)
# 데이터 아래쪽의 텍스트를 제거
current_df = globals()["tma_df{}".format(i + 1)]
textrows_start = np.int64(current_df.index[(current_df["index"] == "Results:")]).item()
textrows_end = np.int64(current_df.index[(current_df["ts{}".format(i + 1)] == "STARe")]).item()
current_df.drop([i for i in range(textrows_start, textrows_end + 1)], axis = 0, inplace = True)
- 파일을 바이너리 형태로 먼저 읽어서 chardet으로 인코딩을 확인하는 부분은 범용성을 위한 옵션입니다. 이렇게 해놓으면 윈도우에선 열리는데 우분투에선 안열린다던가 하는 문제를 신경쓰지 않아도 됩니다. 동료들에게 공유해줄 때도 유용합니다. 상세 내용은 아래 포스팅을 참고해 주세요.
- ld와 nd의 구성요소 갯수와 정렬 순서는 같습니다. 따라서 len(ld) = len(nd), nd[i] = ld[i] 입니다.
- inplace = True 매개변수는 변경사항을 원본 데이터프레임에 덮어쓰겠다는 의미입니다.
- 제가 사용한 장비에서, 측정 온도 간격 및 설정 온도 구간은 동일하지만 측정 온도의 절대값은 매번 다릅니다 (많은 장비들이 그럴 겁니다). 그래서 각 샘플의 측정 온도에 대해 column name을 ts{“샘플번호”}와 같이 붙이도록 해놓았습니다.
globals() 함수는 일정한 규칙으로 파일명을 자동 생성할 때 유용합니다. globals()를 이용하여 각각의 파일에서 추출된 데이터프레임 (tma_df{“번호”})이 하나씩 생성되었습니다.

각각의 df에서 샘플온도와 길이값, 두개의 column만 뽑아서 이걸 하나의 데이터프레임으로 합쳐주도록 하겠습니다. 그래야 export해서 엑셀에서 작업하기에도 편리하니까요.
# 병합을 위한 첫번째 데이터프레임 선언 (샘플온도와 측정값, 두 개의 열만 선택)
m0 = pd.concat([tma_df1["ts1"], tma_df1[nd[0]]], axis = 1)
# 두번쨰~마지막 데이터프레임의 데이터 열을 찾아 병합
for j in range(1, len(ld)):
globals()["m{}".format(j)]\
= pd.concat([globals()["m{}".format(j - 1)],\
globals()["tma_df{}".format(j + 1)]["ts{}".format(j + 1)],\
globals()["tma_df{}".format(j + 1)][nd[j]]], axis = 1)
샘플이 4개니까 m0, m1, m2, m3 이렇게 4개가 생성되고,
m0: 첫번째 샘플
m1: m0 + 두번째 샘플
m2: m1 + 세번째 샘플
m3: m2 + 네번째 샘플
이런 식이 됩니다. 따라서 m3를 출력해보면 아래와 같은 데이터 프레임을 확인할 수 있습니다.

ld, nd가 내림차순으로 정렬되어 있으니 샘플 순서도 그에 따라서 합쳐졌네요. 원하는 데이터가 잘 추출되었는지 간단하게 그래프를 그려서 한번 확인해 보겠습니다.
# 데이터를 문자열에서 부동소수점 형태로 바꾸기
m3_float = m3.astype(np.float64)
# X, y 열의 인덱스를 구분
ts_idx, val_idx = [], []
for i in range(len(m3_float.columns)):
if i % 2 == 0:
ts_idx.append(i)
else:
val_idx.append(i)
# 4개의 데이터 plot
fig, ax = plt.subplots(1, 4, figsize = (24, 4))
for i in range(len(ts_idx)):
tss = m3_float.columns[ts_idx[i]]
vals = m3_float.columns[val_idx[i]]
ax[i].plot(m3_float[tss], m3_float[vals])
ax[i].set_xlabel("Temperature")
ax[i].set_ylabel("Length (um)")

전형적인 형태가 나오는 걸 보니 데이터 추출이 정상적으로 이루어졌군요. 이제 합쳐진 데이터프레임을 엑셀에서 열 수 있도록 csv 형식으로 export 하겠습니다. 파일명에 작성한 날의 날짜를 쓰는건 제 개인적인 취향일 뿐 중요한 건 아닙니다.
import datetime
# 최종 출력파일에 현재날짜를 표기하기 위한 변수 정의
now = datetime.datetime.now()
# 병합된 데이터프레임을 csv 파일로 내보냄 : 파일명에 오늘 날짜 (YYMMDD 형식)를 표기하도록 함
globals()["m{}".format(len(ld) - 1)].to_csv("{}_tma_merged.csv".format(str(now.year)[2:4] + now.strftime("%m") + now.strftime("%d")))
변환된 파일을 엑셀에서 열어본 모습입니다. 번거로운 과정이었지만, 서두에서도 말씀드렸듯 파일 갯수가 늘어날수록 이렇게 한번 시간을 들여서 자동화를 해놓으면 반복적인 작업에 소요되는 시간을 현저히 단축시킬 수 있습니다. 분석장비의 사용 빈도, 평소에 처리해야 하는 샘플의 갯수 등을 잘 따져보시고, 필요성을 저울질 해 보시기 바랍니다. 다음 포스팅에서는 단순 데이터 취합 뿐 아니라 계산값을 추출해서 추가하는 부분까지 다루어 보겠습니다.