산업 현장에서 다루는 데이터 분석하기
'''
Acknowledgements
This dataset is publicly available for anyone to use under the following terms.
von Birgelen, Alexander; Buratti, Davide; Mager, Jens; Niggemann, Oliver: Self-Organizing Maps for Anomaly Localization and Predictive Maintenance in Cyber-Physical Production Systems. In: 51st CIRP Conference on Manufacturing Systems (CIRP CMS 2018) CIRP-CMS, May 2018.
Paper available open access: https://authors.elsevier.com/sd/article/S221282711830307X
IMPROVE has received funding from the European Union's Horizon 2020 research and innovation programme under Grant Agreement No. 678867
'''
print('\n')
New vs worn cutting blade data
데이터분석과 시각화
이 데이터 셋은 OCME 사의 VEGA(포장장비) 센서 데이터 입니다.
VEGA 장비는 식품 및 음료 산업의 대규모 생산 라인에 배치됩니다.
이 기계는 병이나 캔을 정해진 크기로 묶어서 필름으로 포장 한 다음, 필름을 열 수축시켜 패키지로 결합합니다.
플라스틱 필름은 대형 스풀로 기계에 공급 된 다음 제품 포장지에 필름을 감쌀 수있는 길이로 자릅니다.
절단 블레이드를 올바르게 설정하고 유지 보수해야합니다.
또한, 블레이드가 금속 하우징 내에 봉입되어 있고 회전 속도가 빠르기 때문에 작동 중에 블레이드를 육안으로 검사 할 수 없습니다.
블레이드 성능 저하를 모니터링하면 기계 신뢰성이 향상되고 절단 실패로 인한 예기치 않은 장비 다운 시간이 줄어 듭니다.
우리는 새 블레이드와 완전히 마모 된 블레이드를 비교하는 두 가지 데이터를 알아보고자 합니다.
컬럼 설명
(데이터는 다음의 링크에서 다운받으실 수 있습니다)
Kaggle 회원가입이 필요합니다(무료)
-
Timestamp
- 초 단위의 타임스탬프
-
pCut Motor
- 회전 토크 발생을 위한 모터의 토크(돌림 힘)
-
pCut CTRL Position controller: Lag error
- 경로 생성기의 설정 점과 블레이드의 실제 현재 엔코더 위치 사이의 순간 위치 오차
-
pCut CTRL Position controller: Actual position
- 해당 순간 블레이드의 위치
-
pCut CTRL Position controller: Actual speed
- 해당 순간 블레이드의 속도
-
pSvolFilm CTRL Position controller: Actual position
- 해당 순간 언와인더(필름을 연속적으로 풀어주며 공급하는 장치) 의 위치
-
pSvolFilm CTRL Position controller: Actual speed
- 해당 순간 언와인더의 속도
-
pSvolFilm CTRL Position controller: Lag error
- 경로 생성기의 설정 점과 언와인더의 실제 현재 엔코더 위치 사이의 순간 위치 오차
# 파이썬의 데이터 분석 도구인 Pandas 를 불러옵니다.
# Pandas 를 쉽게 설명드리면, 파이썬으로 엑셀을 다룰 수 있는 도구라고 볼 수 있습니다.
# 이 도구를 앞으로는 pd라는 축약어로 사용하기로 합니다.
import pandas as pd
Load Dataset
모든 데이터 분석의 시작은 데이터를 읽어오는 것입니다.
파일의 경로를 지정하는 방법에 주의하셔야 합니다.
만일 read_csv를 실행할 때 (FileNotFoundError)라는 이름의 에러가 난다면 경로가 제대로 지정이 되지 않은 것입니다.
다음의 링크에서 경로를 지정하는 법을 다루고 있습니다.
탐색기에서 파일이 있는 경로까지 가서 경로주소를 복사한 다음, path 변수에 문자열로 넣어주세요.
원 표시 또는 역슬래시가 있다면 그것을 슬래시 ( / ) 로 바꿔주세요.
맨 마지막에도 슬래시를 붙여주세요.
예)
C:\Users\one\Desktop\Factory\blade\data 이라면,
C:/Users/one/Desktop/Factory/blade/data/ 로 바꿔서
path = 'C:/Users/one/Desktop/Factory/blade/data/' 이렇게 path 변수에 문자열로 넣어주세요
# 판다스의 read_csv로 NewBlade001.csv, WornBlade001.csv 파일을 읽어옵니다.
# 읽어온 데이터를 각각 new, old 이라는 이름의 변수에 할당합니다.
path = 'C:/Users/one/Desktop/Factory/Factory/Factory_all/blade/data/'
new = pd.read_csv(path+"NewBlade001.csv")
old = pd.read_csv(path+"WornBlade001.csv")
# 새로 불러온 변수 new의 첫 5 개 행을 확인합니다.
new.head()
Timestamp | pCut Motor: Torque | pCut CTRL Position controller: Lag error | pCut CTRL Position controller: Actual position | pCut CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Actual position | pSvolFilm CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Lag error | |
---|---|---|---|---|---|---|---|---|
0 | -0.188 | -0.112131 | -0.002490 | -884606 | 0.000000 | 11128 | 2.504289 | 0.261085 |
1 | -0.184 | -0.088931 | -0.003863 | -884606 | 17.166138 | 11128 | -2.504289 | 0.260083 |
2 | -0.180 | -0.115141 | 0.001630 | -884606 | -6.866455 | 11128 | 7.513016 | 0.259081 |
3 | -0.176 | -0.111815 | 0.003003 | -884606 | -13.732910 | 11128 | -2.504289 | 0.260083 |
4 | -0.172 | -0.130970 | 0.004376 | -884606 | -6.866455 | 11128 | 0.000000 | 0.261085 |
# 새로 불러온 변수 old의 첫 5 개 행을 확인합니다.
old.head()
Timestamp | pCut Motor: Torque | pCut CTRL Position controller: Lag error | pCut CTRL Position controller: Actual position | pCut CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Actual position | pSvolFilm CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Lag error | |
---|---|---|---|---|---|---|---|---|
0 | -0.188 | -0.001146 | 0.004242 | -838822 | 0.000000 | 35096 | 5.008578 | 0.008014 |
1 | -0.184 | 0.014685 | 0.001495 | -838822 | 6.866455 | 35096 | 0.000000 | 0.007012 |
2 | -0.180 | 0.023437 | -0.001251 | -838821 | 13.732910 | 35096 | 2.504289 | 0.008014 |
3 | -0.176 | 0.016672 | -0.001251 | -838821 | -6.866455 | 35096 | -2.504289 | 0.007012 |
4 | -0.172 | 0.002712 | 0.001495 | -838822 | -6.866455 | 35096 | -5.008727 | 0.008014 |
# .describe() 를 통해 우리는, 기초 통계량을 알 수 있습니다.
new.describe()
Timestamp | pCut Motor: Torque | pCut CTRL Position controller: Lag error | pCut CTRL Position controller: Actual position | pCut CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Actual position | pSvolFilm CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Lag error | |
---|---|---|---|---|---|---|---|---|
count | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 |
mean | 3.906000 | -0.077109 | -0.000306 | -881592.086426 | 878.606161 | 16150.827148 | 1371.617691 | 0.646027 |
std | 2.365404 | 0.372693 | 0.038205 | 2661.658773 | 1317.007966 | 3597.327988 | 552.624403 | 0.085916 |
min | -0.188000 | -2.292097 | -0.513631 | -884747.000000 | -954.437256 | 11128.000000 | -7.513016 | 0.259081 |
25% | 1.859000 | -0.304018 | -0.009814 | -884346.250000 | -172.519684 | 12420.500000 | 743.789139 | 0.612427 |
50% | 3.906000 | -0.165394 | -0.000182 | -881146.000000 | 449.752808 | 16047.000000 | 1389.908569 | 0.652235 |
75% | 5.953000 | 0.319277 | 0.009876 | -879821.500000 | 2059.078125 | 19591.000000 | 1968.411041 | 0.693293 |
max | 7.999999 | 0.606260 | 0.346109 | -877098.000000 | 3556.823730 | 22360.000000 | 2068.584717 | 0.869679 |
# 새 것과 헌 것의 기초 통계량만 잘 비교해도 수치상 다른 것들이 있죠?
old.describe()
Timestamp | pCut Motor: Torque | pCut CTRL Position controller: Lag error | pCut CTRL Position controller: Actual position | pCut CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Actual position | pSvolFilm CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Lag error | |
---|---|---|---|---|---|---|---|---|
count | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 | 2048.000000 |
mean | 3.906000 | -0.128408 | -0.000151 | -834777.470703 | 990.159236 | 41277.008301 | 1605.196273 | 0.649488 |
std | 2.365404 | 0.434817 | 0.041539 | 2815.127626 | 1318.092062 | 4097.408290 | 554.174297 | 0.116653 |
min | -0.188000 | -2.843960 | -0.452279 | -838822.000000 | -954.437256 | 35096.000000 | -7.513016 | 0.005009 |
25% | 1.859000 | -0.366514 | -0.012547 | -837605.250000 | -164.794922 | 37357.250000 | 1379.891235 | 0.628955 |
50% | 3.906000 | -0.191581 | -0.000255 | -834328.500000 | 911.521912 | 41240.000000 | 1402.430298 | 0.663509 |
75% | 5.953000 | 0.288852 | 0.012403 | -832540.750000 | 2059.936523 | 45164.750000 | 2005.976196 | 0.701098 |
max | 7.999999 | 0.611113 | 0.300557 | -830298.000000 | 3570.556641 | 48242.000000 | 2742.251953 | 0.935391 |
Combine Dataset
new 와 old 를 하나의 데이터프레임으로 묶기
# label 컬럼을 새로 만들어, new 는 0을, old 는 1 을 할당합니다.
# 섞였을 때 서로 구분하기 위함입니다.
new['label'] = 0
old['label'] = 1
# pd.concat() 안에 리스트 형태로, 묶고자 하는 데이터프레임들을 넣어주면 됩니다.
# 왼쪽(new) 의 아래에 오른쪽(old) 이 붙는다고 생각하시면 됩니다.
# 이어붙인 값을 = 을 통해 combined 라는 변수에 할당합니다.
combined = pd.concat([new, old])
# 잘 실행되었는지 확인하기 위해 describe() 를 사용합니다.
# 아래에 row 가 추가된 것이므로, count 행이 2048 에서 4096 으로 바뀐 것을 확인하면 됩니다.
combined.describe()
Timestamp | pCut Motor: Torque | pCut CTRL Position controller: Lag error | pCut CTRL Position controller: Actual position | pCut CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Actual position | pSvolFilm CTRL Position controller: Actual speed | pSvolFilm CTRL Position controller: Lag error | label | |
---|---|---|---|---|---|---|---|---|---|
count | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 |
mean | 3.906000 | -0.102758 | -0.000229 | -858184.778564 | 934.382699 | 28713.917725 | 1488.406982 | 0.647758 | 0.500000 |
std | 2.365115 | 0.405711 | 0.039902 | 23569.868730 | 1318.569755 | 13142.713531 | 565.526059 | 0.102446 | 0.500061 |
min | -0.188000 | -2.843960 | -0.513631 | -884747.000000 | -954.437256 | 11128.000000 | -7.513016 | 0.005009 | 0.000000 |
25% | 1.859000 | -0.332745 | -0.011033 | -881146.000000 | -168.228149 | 16048.500000 | 1372.378052 | 0.620462 | 0.000000 |
50% | 3.906000 | -0.173652 | -0.000189 | -857960.000000 | 638.580322 | 28728.000000 | 1397.421509 | 0.657892 | 0.500000 |
75% | 5.953000 | 0.306975 | 0.010926 | -834328.750000 | 2059.936523 | 41238.500000 | 1993.454224 | 0.697447 | 1.000000 |
max | 7.999999 | 0.611113 | 0.346109 | -830298.000000 | 3570.556641 | 48242.000000 | 2742.251953 | 0.935391 | 1.000000 |
# combined.columns 는 combined 이라는 변수에 담긴 데이터프레임 값의 컬럼들 입니다.
# 이 위치에 = 을 통해 리스트로 내가 새롭게 정하고자 하는 컬럼들을 집어넣을 수 있습니다.
# 기존 컬럼들의 이름이 너무 길어서... 줄여서 넣습니다.
combined.columns = ['Time', 'Torq', "Cut_Err", "Cut_Pos", "Cut_Spd",
"Film_Pos", "Film_Spd", "Film_Err", "label"]
# 기존에 있던 컬럼들의 상호 비교를 위해 모든 변수들과의 관계에 대한 산점도 행렬을 구합니다.
# from pandas.plotting import * 는 판다스 안의 산점도 행렬 함수를 불러오기 위함입니다.
# 산점도 행렬 함수 안에 들어가는 내용은 다음과 같습니다.
# combined -> 보려고 하는 모든 데이터가 있는 데이터프레임
# c -> color 의 약자로, 산점도 안에서 구분하고자 하는 정보, 지금은 combined['label'] 이고,
# 이는 새 것과 헌 것을 구분합니다.
# figsize -> 보려고 하는 이미지의 크기 정보
from pandas.plotting import *
result = scatter_matrix(combined,
c = combined['label'],
figsize=(25, 25))
파생변수 생성
# 데이터 분석자의 상식과 배경지식을 동원하여, 기존 특징들로부터 도출할 수 있는 새로운 변수입니다.
# 속도라는 물리량이 있고, 초당 250회 획득하는 변수이므로
# 해당 순간의 힘을 구할 수 있습니다.
# 미분의 개념을 적용합니다.
# 속도의 변화량을 시간변화량으로 나누어 힘을 구합니다.
# 엄밀하게는 비례상수인 m 이 들어가지만, 상수이기 때문에 무시합니다.
# 새로운 파생변수의 이름을 Force 가 아닌 Acc(가속도) 로 해도 무관합니다.
# .diff(1) 을 하여, 이전 row 와의 차이를 구하고
# 데이터 획득 주기인 0.004 초로 나눕니다.
# .loc[combined['label'] == 0] 은 new blade 안에서만 연산하기 위함입니다.
# .loc[combined['label'] == 1] 은 old blade 안에서만 연산하기 위함입니다.
# Blade 기준으로는 Cut_ 수식어를, 언와인더 기준으로는 Film_ 을 붙여줍니다.
combined['Cut_Force'] = combined["Cut_Spd"].loc[combined["label"] == 0].diff(1) / 0.004
combined['Cut_Force'] = combined["Cut_Spd"].loc[combined["label"] == 1].diff(1) / 0.004
combined['Film_Force'] = combined["Film_Spd"].loc[combined["label"] == 0].diff(1) / 0.004
combined['Film_Force'] = combined["Film_Spd"].loc[combined["label"] == 1].diff(1) / 0.004
# .fillna(0) 을 하는 이유는, 첫 번째 행(row) 의 경우 이전 값이 존재하지 않기 때문에
# NaN 이 있으므로, 이를 채워주기 위함입니다.
combined.fillna(0, inplace=True)
# 잘 연산되었는지 확인하기 위해 .head() 로 첫 5 번째 행들을 알아봅니다.
combined[["Cut_Force", "Film_Force", 'label']].head()
Cut_Force | Film_Force | label | |
---|---|---|---|
0 | 0.000000 | 0.000000 | 0 |
1 | 1716.613769 | -1252.144575 | 0 |
2 | 1716.613770 | 626.072288 | 0 |
3 | -5149.841309 | -1252.144575 | 0 |
4 | 0.000000 | -626.109481 | 0 |
정규화(Normalization)
# for 문으로 데이터프레임을 뽑아오면 컬럼명이 나오게 됩니다.
# 컬럼명을 키워드로 하여 해당 컬럼의 모든 열을 가져올 수 있습니다.
# 이 부분은 combined[i] 이고, 여기서 i 는 컬럼의 이름 입니다.
# 아래 작업을 모든 컬럼에 대해 반복합니다.
for i in combined:
# 한 컬럼에서 최소값과 최대값을 구하고
# 해당 컬럼의 모든 열 값에서 최소값을 뺍니다.
# 그리고 이를 최대값으로 나누게 되면, 모든 값들이 0 에서 1 사이의 값으로 정규화 됩니다.
minimum = combined[i].min()
maximum = combined[i].max()
# 최대값이 아닌, minimum 을 뺀 값으로 나누는 이유는
# 모든 열에서 minimum 을 뺀 시점에서 이미 최대값이 minimum 만큼 감소하기 때문입니다.
combined[i] = (combined[i] - minimum) / (maximum - minimum)
combined.describe()
Time | Torq | Cut_Err | Cut_Pos | Cut_Spd | Film_Pos | Film_Spd | Film_Err | label | Cut_Force | Film_Force | |
---|---|---|---|---|---|---|---|---|---|---|---|
count | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 | 4096.000000 |
mean | 0.500000 | 0.793385 | 0.597160 | 0.487837 | 0.417419 | 0.473835 | 0.544017 | 0.690843 | 0.500000 | 0.544433 | 0.465547 |
std | 0.288851 | 0.117425 | 0.046412 | 0.432880 | 0.291397 | 0.354117 | 0.205663 | 0.110112 | 0.500061 | 0.090624 | 0.134542 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.250000 | 0.726820 | 0.584593 | 0.066135 | 0.173748 | 0.132578 | 0.501821 | 0.661506 | 0.000000 | 0.493506 | 0.377048 |
50% | 0.500000 | 0.772866 | 0.597206 | 0.491965 | 0.352049 | 0.474215 | 0.510929 | 0.701737 | 0.500000 | 0.532467 | 0.475409 |
75% | 0.750000 | 0.911973 | 0.610135 | 0.925972 | 0.666161 | 0.811298 | 0.727687 | 0.744251 | 1.000000 | 0.597402 | 0.557376 |
max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
# 블레이드와 언와인더에 작용하는 해당 순간의 Force 정보를 만들었으니
# 이를 모든 변수들과 산점도 행렬을 통해 비교합니다.
# 여기에서 볼 사항은
# Time 과 다른 변수들간의 그래프
# 새롭게 만든 Force 와 다른 변수들간의 그래프 입니다.
from pandas.plotting import *
result = scatter_matrix(combined,
c = combined['label'],
figsize=(25, 25))
# 데이터 시각화 도구인 matplotlib를 불러와 이를 줄여 plt라고 사용합니다.
import matplotlib.pyplot as plt
# Time 과 다른 변수들간의 산점도를 lineplot 으로 확인하고자 합니다.
# 그래프가 y 축만 다르고 나머지가 동일하기 때문에
# 반복을 줄이기 위해 그래프를 그려주는 함수를 만들어줍니다.
def line_plot(y, log=False):
# y 값만 입력받는 함수입니다.
# .loc[combined['label'] == 0]
x = "Time"
x1 = combined[x].loc[combined['label'] == 0]
y1 = combined[y].loc[combined['label'] == 0]
x2 = combined[x].loc[combined['label'] == 1]
y2 = combined[y].loc[combined['label'] == 1]
plt.plot(x1, y1, label= y + "_New")
plt.plot(x2, y2, label= y + "_Old")
if log:
plt.yscale('log')
plt.legend(loc='lower left')
plt.show()
line_plot("Torq")
target_cols = ['Torq', "Cut_Err", "Cut_Pos", "Cut_Spd", "Cut_Force",
"Film_Pos", "Film_Spd", "Film_Err", "Film_Force"]
for target in target_cols:
line_plot(target)
# 함수의 입력값으로 log=True 를 넣어서 로그스케일로 확인합니다.
line_plot("Cut_Force", log=True)
# matplotlib의 subplots를 사용합니다. 이 함수는 여러 개의 시각화를 한 화면에 띄울 수 있도록 합니다.
# 이번에는 2x2으로 총 4개의 시각화를 한 화면에 띄웁니다.
figure, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
# 시각화의 전체 사이즈는 18x8로 설정합니다.
figure.set_size_inches(18, 15)
combined.plot.scatter(x = 'Film_Force', y = 'Film_Spd', c = 'label', colormap = 'viridis', ax=ax1)
combined.plot.scatter(x = 'Film_Force', y = 'Cut_Spd', c = 'label', colormap = 'viridis', ax=ax2)
combined.plot.scatter(x = 'Cut_Force', y = 'Film_Spd', c = 'label', colormap = 'viridis', ax=ax3)
combined.plot.scatter(x = 'Cut_Force', y = 'Cut_Spd', c = 'label', colormap = 'viridis', ax=ax4)
<matplotlib.axes._subplots.AxesSubplot at 0x2d8033110f0>