데이터 병합 요약 그림

전처리 과정을 그림을통해 대략적으로 표현하였습니다...
serial_ng = pd.read_csv(r"C:\최종프로젝트\avi_serial_ng_data.csv")
사출설비 = pd.read_csv(r"C:\최종프로젝트\사출설비.csv")
serial_scan = pd.read_csv(r"C:\최종프로젝트\제품_serial_scan.csv")
저희가 받은 데이터는 플라스틱 사출성형 데이터로
① 불량 데이터 : 111만개
② 장비 데이터 : 355만개
③ scan 데이터 : 1349만개
로 이루어 져있습니다.
그리고 각 데이터는 다음과 같은 컬럼으로 이루어져 있습니다.
① 불량 데이터
| ID : 데이터베이스 내의 고유 레코드 식별자 UnloadTrayNo : 제품이 검사 장비에 들어올 때 담겨 있던 트레이 번호 LoadTrayNo : 검사 완료 후 제품이 배출될 때 담긴 트레이 번호 LoadTrayTime : 트레이가 장비에 로딩된 시점 ADAY : 데이터가 생성되거나 기록된 날짜 ATIM : 데이터가 기록된 시간 SerialNumber : 제품 개별마다 부여된 고유 번호(S/N) PcName : 해당 데이터를 수집하거나 검사 소프트웨어가 돌아가는 PC의 명칭 ☆ NG_TYPE : 불량의 종류를 나타내는 코드 ['Dent', 'Chip', 'Broken', 'Burr', 'Foreign material', 'Short shot', 'Dent Burr', 'Stain', 'Lift', 'Scratch', 'Barcode', 'GATE', 'Crack', 'Cav Shift', 'Shorted Yoke', 'Over Mold', 'Particle Burr'] NG_IMG_NO : 불량으로 판정된 부위의 이미지 번호나 저장 경로 UPDT : 데이터가 최종적으로 업데이트(Update)된 일시 SrcLoadTrayNo : 원천 트레이 정보(이전 공정에서의 트레이 번호) SrcUnloadTrayNo : 원천 트레이 정보 |
② 설비데이터
| ☆ MACHNO : 사출 성형기 장비 번호 (호기) JDAY : 작업 일자 또는 작업 지시 기준일 ADAY : 실제 데이터가 기록된 날짜 품번 : 생산 중인 제품의 고유 코드 ☆ LOTN : 해당 제품 군의 생산 로트 번호 ☆ Cycle Time : 한 개의 제품(또는 한 번의 사출)이 완성되기까지 걸리는 총 시간 ☆ 사출시간 : 가열된 플라스틱 수지를 금형 안으로 밀어 넣는 데 걸린 시간 ☆ 충진시간 : 금형 내부의 빈 공간이 수지로 완전히 채워지는 데 걸린 시간 ☆ 최소 쿠션 : 사출 후 스크류 끝에 남은 수지의 잔량 위치를 말하며, 제품의 밀도와 중량 일정성을 확인하는 핵심 지표 ☆ 충진피크압 : 수지를 채울 때 발생한 가장 높은 압력 ☆ 전 범위 피크압 : 전체 공정(충진~보압) 중 발생한 최대 압력 ☆ 보압 완료 위치 : 사출 후 수지가 식으면서 수축하는 것을 막기 위해 압력을 가해주는(보압) 공정이 끝났을 때, 사출 스크류의 최종 위치 ☆ 금형온도1 :형(틀)의 상측/하측 또는 가동측/고정측의 온도 ☆ 금형온도2 :형(틀)의 상측/하측 또는 가동측/고정측의 온도 |
③ scan 데이터
| ADAY : 스캔이 발생한 작업 일자 MAHNO : 제품을 스캔한 설비 번호 또는 공정 위치 ☆ SerialNumber : 제품의 고유 식별 번호 Status : 제품상태나 스캔 결과 (0 : 정상 , 1: 이상 으로 인코딩) ☆ Timestamp : 스캔이 일어난 정확한 시간 |
제품 스캔데이터와 불량 데이터 병합

두 데이터가 시리얼데이터라는 공통된 컬럼을 가지고 있어서 시리얼데이터를 축으로 병합하기로 하였습니다.
먼저 불량데이터의 시간의 경우 다른 데이터랑 상관없는 독립된 컬럼이기에 헤깔리지 않기위해 드롭해줍니다.
# ng 데이터의 ADAY, ATIM 컬럼 드롭
serial_ng = serial_ng.drop(columns=['ADAY', 'ATIM'])
그리고 그림과 같이 scan데이터를 위주로 불량데이터를 결합해주고 스캔에 없는 불량데이터는 merge의 right 메소드를 통해 삭제해줍니다. 이렇게 통째로 삭제하는 이유는 우리는 제품의 특성이 있고 그에 해당하는 불량품이 뭔지만 알면 되기 때문입니다.
예를들어 그림의 보라색부분은 제품데이터는 없고 그냥 불량번호만 있기 때문에 삭제해주는 것입니다.
# ng 데이터 scan 데이터 병합
merged_df = pd.merge(serial_ng,
serial_scan,
on='SerialNumber',
how='right')
merged_df['Status2'] = merged_df['ID'].notna().astype(int)
기본 scan데이터에 있는 Status컬럼은 OK는 정상, NG는 불량으로 나타납니다. 이를 0과 1로 인코딩해줍니다.
# Status 컬럼의 'OK'를 0으로, 'NG'를 1로 매핑
merged_df['Status'] = merged_df['Status'].replace({'OK': 0, 'NG': 1})
merged_df['Status'] = merged_df['Status'].astype(int)
여기서 ng데이터에 있는 불량판정 비율과 scan데이터에 있는 불량판정 비율을 비교해줍니다.
ng데이터로 판정한 불량개수 : 78만개
scan데이터에 있는 불량개수 : 219개
ng, scan데이터 둘다 포함되는 불량 : 3개
이를 보면 ng데이터의 불량과 scan데이터가 연관성없이 독립적으로 적용된것을 볼 수 있습니다.
그렇기에 ng는 이미지를 통한 불량데이터이고 scan데이터는 다른 측정을 통해 불량을 확인을 한것으로 추측해볼 수 있습니다.
위에 2개 데이터를 합친 데이터와 장비 데이터 결합

기존 합친 데이터에서 사출설비 데이터를 합치기 위해 두가지 조건을 가정했습니다.
첫째는 기계번호의 일치입니다.
당연히 같은 기계에서 나오는 데이터끼리 매칭시켜야하기때문에 이를 확인해야 합니다.
두번째로 기계 데이터의 시계열과 제품스캔데이터가 ±6초 이내여야 한다는 조건을 가정했습니다.
사출성형기가 한번 작동할때 총 4개의 cavity가 존재하는데 이는 한번에 4개의 제품을 생산할 수 있음을 의미합니다.
그런데 스캔데이터는 각 4개의 제품이 서로다른시간에 찍히니 두 데이터의 시계열이 정확히 일치하지 않습니다.
그리고 도메인 기반으로 성형기가 작동하는 단위인 1shot당 12초의 간격이 있다는 것을 알 수 있었습니다.
그러므로 사출성형기의 shot데이터 시간의 12초 범위안에 들면 제품 시계열 데이터가 해당 shot으로 인지할 수 있도록 코드를 작성했습니다.
# 1. ADAY와 ATIM을 합쳐서 'Timestamp_사출' 컬럼 생성
사출설비['Timestamp_사출'] = pd.to_datetime(
사출설비['ADAY'].astype(str) + ' ' + 사출설비['ATIM'].astype(str),
errors='coerce' # 잘못된 형식의 데이터는 NaT(결측치) 처리
)
# 사출설비 시간기준 정렬
사출설비_sort = 사출설비.copy()
사출설비_sort.drop(columns=['ADAY', 'ATIM'], inplace=True)
사출설비_sort.sort_values(by='Timestamp_사출', inplace=True)
사출설비_sort
사출설비에서 제품이 나오고 scan이 되기까지 대략 288초가 소요된다는 것을 관계자에게 물어보아 알아냈습니다.
# 288초를 더한 timestamp_later 컬럼 생성
사출설비_sort['Timestamp_later'] = 사출설비_sort['Timestamp_사출'] + pd.Timedelta(seconds=288)
사출설비_sort
# 데이터 병합전 데이터 형태 통합
merged_df['MACHNO'] = merged_df['MACHNO'].astype(str)
사출설비_sort['MACHNO'] = 사출설비_sort['MACHNO'].astype(str)
merged_df['Timestamp'] = pd.to_datetime(merged_df['Timestamp'])
사출설비_sort['Timestamp_later'] = pd.to_datetime(사출설비_sort['Timestamp_later'])
# 2. merge_asof 실행 (by='MACHNO' 조건 추가)
# left_on/right_on으로 시간을 맞추고, by로 설비 번호를 정확히 일치시킵니다.
final_df = pd.merge_asof(
merged_df, # 기준 데이터 (148만 행)
사출설비_sort, # 합칠 데이터 (33만 행)
left_on='Timestamp', # 왼쪽 시간 컬럼
right_on='Timestamp_later', # 오른쪽 시간 컬럼
by='MACHNO', # 설비 번호가 정확히 일치하는 것끼리만 병합 🌟
direction='nearest', # 가장 가까운 시간
tolerance=pd.Timedelta('6second') # 플러스마이너스 6초 제한
)
# 3. 결과 확인
print(f"최종 데이터 행 수: {len(final_df):,}")
print(f"설비&시간 매칭 성공 수: {final_df['Timestamp_later'].notna().sum():,}")
print(f"매칭 실패(Null) 수: {final_df['Timestamp_later'].isna().sum():,}")
최종 데이터 행 수: 13,548,850
설비&시간 매칭 성공 수: 12,647,778
매칭 실패(Null) 수: 901,072
결과를 보면 꽤 많은 비율이 성공적으로 결합된 것을 볼 수 있습니다.
매칭이 안된것들에 대해서는 일단 null값으로 비워두기로 하였습니다.
해당 과정으로 데이터를 병합할 경우 문제점

가로축은 설비데이터의 시간축입니다.
1번과 2번을 보면 2개의 shot간격이 매우 짧아 겹치는 부분이 있는걸 볼 수 있습니다.
이럴경우 저 짝대기가 한개의 제품데이터라고 했을때 분류가 잘못되는 경우가 있을 수 있습니다.
3번의 경우에는 제품개별 시계열이 설비 데이터의 12초 구간을 벗어나 매칭되지 않는 경우 입니다.
이로 몇개의 데이터가 소실될 수 있으나 대부분의 데이터가 매칭되는 것을 봐서 크게 문제는 없을 것 같습니다.
느낀점
학습용 데이터가 아닌 실무용 데이터를 직접 병합해보면서 엄청 많은 데이터를 다루기 위해서는 데이터 분석역량이 무조건 필요하다고 느꼈습니다. 엑셀에서도 데이터 수가 짤릴 뿐더러 파이썬파일로도 버벅이는 정도의 데이터량이기 때문에 데이터 분석역량이 없었다면 해당 자료는 건드리지 못했을것 같습니다.
그리고 제조업에 가게된다면 통계 및 도메인 지식이 필수라고 느꼈습니다. 규격서를 꼼꼼히 보고 검토한 덕분에 여러 힌트를 얻을 수 있었고 제조업 불량 검출 과정을 어느정도 이해하고 있었기에 힌트를 얻은 점들도 있었습니다.