R) 전처리 - 그룹핑-02

R) 전처리 - 그룹핑-02

A 공장에서 사용하는 인쇄 장비의 인쇄물 중 불량이 확인되었다. 보다 상세한 내용을 확인하기 위해 몇 번 카트리지에서 문제가 발생했는지 알기 위해 코드를 작성해보자.

본 포스팅은 R) 전처리 - 그룹핑-01보다 한 단계 난이도가 높으니 해당 포스팅을 먼저 보고 오시면 좋습니다.

데이터 준비

이전에는 정확하게 5의 배수로 떨어지도록 데이터가 준비되었지만, 이번에는 좀 더 현실적인 데이터를 준비해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set.seed(123)
df = data.frame(id = 1:10,
unit = sample(1:5, size = 10, replace = TRUE))
df
## id unit
## 1 1 3
## 2 2 3
## 3 3 2
## 4 4 2
## 5 5 3
## 6 6 5
## 7 7 4
## 8 8 1
## 9 9 2
## 10 10 3

문제 상황

A 공장에서 사용하는 인쇄 장비의 인쇄물에는 고유 ID가 적혀있다고 한다. 인쇄물의 품질 유지를 위해서는 인쇄 카트리지 제조사의 권장사항에 근거하여 5작업단위마다 카트리지를 교체해주어야 한다. 그런데 낮에 작업한 인쇄물에 불량을 발견하여 몇 번 카트리지에서 문제가 발생했는지 알아보고자 한다. 각 인쇄작업에 소요된 작업단위를 기반으로 몇 번 카트리지가 몇 번 인쇄물의 인쇄에 사용되었는지 알아보자.

인쇄물에 사용된 카트리지 번호는 특정 인쇄물에 사용되는 작업 단위가 5를 넘어가는 경우 두 개의 카트리지가 기록이 되어야 한다. 예를 들어 앞에서 만든 데이터 중 “ID”가 2인 경우 “unit”의 누적값이 6이기 때문에 첫 번째 카트리지와 두 번째 카트리지를 사용해야하기 때문에 “1/2”로 표기되어야 한다.

풀이

반복문 활용

작업단위가 5를 넘어가는지 확인하기 위한 객체인 “unit_check”와 카트리지 번호를 부여하기 위한 객체인 “cart_count”를 준비하여 반복문을 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
unit_check = 0
cart_count = 1
for(n in 1:nrow(df)){
unit_check = unit_check + df[n, "unit"]
if(unit_check >= 5){ # 1
if(unit_check > 5){ # 2
df[n, "cart"] = paste(cart_count, cart_count + 1, sep = "/") # 3
} else {
df[n, "cart"] = cart_count
}
cart_count = cart_count + 1 # 4
unit_check = unit_check - 5 # 5
} else {
df[n, "cart"] = cart_count
}
}
df
## id unit cart
## 1 1 3 1
## 2 2 3 1/2
## 3 3 2 2
## 4 4 2 2
## 5 5 3 3
## 6 6 5 3/4
## 7 7 4 4/5
## 8 8 1 5
## 9 9 2 5
## 10 10 3 6

1번 코드의 조건문에서는 누적 작업단위가 5 이상인 경우 카트리지 번호의 +1을 기록하고 그렇지 않은 경우 기존의 카트리지 번호가 입력되도록 하였다. 그런데 특정 인쇄물을 인쇄하면서 누적 작업단위가 5를 초과한다면 기존에 사용하던 카트리지와 새로운 카트리지 둘 다 필요하기 때문에 새로운 분기를 2번 코드의 조건문을 통해서 만들고 3번 코드를 사용하여 복수개의 카트리지 번호를 기록하도록 하였다. 그리고 1번 조건문에 속하는 4, 5번 코드는 카트리지 번호를 1 증가시키고 작업단위 만큼 다시 빼주는 기능을 수행한다.

벡터 연산 활용

최초 df 객체 기준으로 다시 시작해보자.

1
2
3
4
5
6
7
8
9
10
11
12
df
## id unit
## 1 1 3
## 2 2 3
## 3 3 2
## 4 4 2
## 5 5 3
## 6 6 5
## 7 7 4
## 8 8 1
## 9 9 2
## 10 10 3

누적합과 그 합의 몫과 나머지를 뜻하는 변수 “unit_cum”, “quotient”, “remainder” 를 만들며 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
df[, "unit_cum" ] = cumsum(df$unit)
df[, "quotient" ] = df$unit_cum %/% 5
df[, "remainder"] = df$unit_cum %% 5
df
## id unit unit_cum quotient remainder
## 1 1 3 3 0 3
## 2 2 3 6 1 1
## 3 3 2 8 1 3
## 4 4 2 10 2 0
## 5 5 3 13 2 3
## 6 6 5 18 3 3
## 7 7 4 22 4 2
## 8 8 1 23 4 3
## 9 9 2 25 5 0
## 10 10 3 28 5 3

벡터연산을 실시할 때 까다로운것이 한 인쇄물에서 두 개의 카트리지를 사용하는 경우이다. 이 처리를 위해서는 카트리지 번호 또는 그와 관련된 정보를 담고있는 최소 2개 이상의 신규 변수가 필요하다. 이를 위해 누적합의 몫이 증가했음을 알 수 있도록 몫(quotient)의 차분을 구하여 “diff”변수에 할당하였고, 누적합의 몫에 1을 더한 값을 “q2”변수에 할당하였다. 단, “q2”는 작업단위가 정확하게 5의 배수가 되는경우 값이 증가하지 않도록 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
df[, "diff"] = c(0, diff(df$quotient))
df[, "q2"] = ifelse(df$remainder == 0,
yes = df$quotient, no = df$quotient + 1)
df
## id unit unit_cum quotient remainder diff q2
## 1 1 3 3 0 3 0 1
## 2 2 3 6 1 1 1 2
## 3 3 2 8 1 3 0 2
## 4 4 2 10 2 0 1 2
## 5 5 3 13 2 3 0 3
## 6 6 5 18 3 3 1 4
## 7 7 4 22 4 2 1 5
## 8 8 1 23 4 3 0 5
## 9 9 2 25 5 0 1 5
## 10 10 3 28 5 3 0 6

여기서 계산하는 누적합의 몫은 카트리지 번호 계산에 사용되는 변수(quotient, q2)이며 “remainder”와 “diff”변수는 참조할 카트리지 번호(몫)와 복수개의 카트리지 번호 표기를 위해 참고하는 변수이다.

카트리지 번호 계산을 위해 “diff”와 “remainder” 변수를 활용한 ifelse() 함수를 작성하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df[, "cart"] = ifelse((df$diff == 1) & (df$remainder != 0), 
yes = paste(df$quotient, df$q2, sep = "/"), no = df$q2)
df
## id unit unit_cum quotient remainder diff q2 cart
## 1 1 3 3 0 3 0 1 1
## 2 2 3 6 1 1 1 2 1/2
## 3 3 2 8 1 3 0 2 2
## 4 4 2 10 2 0 1 2 2
## 5 5 3 13 2 3 0 3 3
## 6 6 5 18 3 3 1 4 3/4
## 7 7 4 22 4 2 1 5 4/5
## 8 8 1 23 4 3 0 5 5
## 9 9 2 25 5 0 1 5 5
## 10 10 3 28 5 3 0 6 6

벤치마킹

가끔 벡터연산이 직관적이지 않다고 하면서 반복문을 고집하는 사람이 있어 상기 두 사례를 벤치마킹 해보았다.

결과

row 개수에 따른 반복문과 벡터연산의 벤치마킹 결과는 다음과 같으며 코드는 본 포스팅 마지막에 있으니 참고바란다.

1
2
3
4
5
6
7
8
9
df_bench
## row time_for time_vec
## 1 100 0.01202106 0.003966093
## 2 500 0.04792905 0.003993988
## 3 1000 0.11185002 0.003994942
## 4 5000 0.49938011 0.031955004
## 5 10000 1.17499089 0.071937084
## 6 50000 16.15165305 0.311568975
## 7 100000 62.32074904 0.635099173

row 개수가 100개인 경우는 약 3배, 100000개는 약 100배의 속도 차이가 난다.

실행 코드

벤치마킹 실행 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ls_size = c(100, 500, 1000, 5000, 10000, 50000, 100000)

df_bench = data.frame()
for(size in ls_size){
set.seed(123)
df = data.frame(id = 1:size,
unit = sample(1:5, size = size, replace = TRUE))
df

unit_check = 0
cart_count = 1

time = Sys.time()
for(n in 1:nrow(df)){
unit_check = unit_check + df[n, "unit"]
if(unit_check >= 5){
if(unit_check > 5){
df[n, "cart"] = paste(cart_count, cart_count + 1, sep = "/")
} else {
df[n, "cart"] = cart_count
}
cart_count = cart_count + 1
unit_check = unit_check - 5
} else {
df[n, "cart"] = cart_count
}
}
time_end_for = Sys.time() - time

set.seed(123)
df = data.frame(id = 1:size,
unit = sample(1:5, size = size, replace = TRUE))
head(df)

time = Sys.time()
df[, "unit_cum"] = cumsum(df$unit)
df[, "quotient" ] = df$unit_cum %/% 5
df[, "remainder"] = df$unit_cum %% 5
df[, "diff"] = c(0, diff(df$quotient))
df[, "q2"] = ifelse(df$remainder == 0,
yes = df$quotient, no = df$quotient + 1)
df[, "cart"] = ifelse((df$diff == 1) & (df$remainder != 0),
yes = paste(df$quotient, df$q2, sep = "/"), no = df$q2)
time_end_vec = Sys.time() - time

df_bench_sub = data.frame(row = size,
time_for = time_end_for,
time_vec = time_end_vec)
df_bench = rbind(df_bench, df_bench_sub)
}
df_bench[, "time_for"] = as.numeric(df_bench$time_for)
df_bench[, "time_vec"] = as.numeric(df_bench$time_vec)
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×