R) EDA - 그룹 연산

R) EDA - 그룹 연산

월별 매장별 매출을 집계하려면? 매장의 규모를 가늠하려면 어떻게 할까? 그룹 연산을 통해 초급/중급/고급으로 나누어서 살펴보도록 하자.

※ 본 내용은 monthly_store_transaction_data.csv 파일로 진행한다.
monthly_store_transaction_data.csv 다운받기 [클릭]

데이터를 준비하자. 대/중/소 카테고리는 cat_1/cat_2/cat_3 으로 되어있고 브랜드는 “brand” 접두사가 붙은 변수에 있다.
※ 정정합니다. prod_name이 제조사이고, brand_2가 제품명입니다.

1
2
3
4
5
6
7
8
9
10
11
df = read.csv("monthly_store_transaction_data.csv")
head(df, 2)
## month store quantity price cat_1
## 1 1 1 5 6 confectionery - cough lozenge
## 2 1 1 5 8 confectionery - cough lozenge
## cat_2 cat_3
## 1 confectionery - cough lozenge confectionery - cough lozenge
## 2 confectionery - cough lozenge confectionery - cough lozenge
## prod_name brand_1 brand_2
## 1 mondelez international halls halls honey-lemon
## 2 mondelez international halls halls menthollyptus

일단 매출 계산을 위해서 제품 판매 개수를 뜻하는 quantity 변수와 price 변수의 곱인 sales 변수를 만들어주자.

1
2
3
4
5
6
7
8
df[, "sales"] = df$quantity * df$price
head(df[, 9:11])
## brand_1 brand_2 sales
## 1 halls halls honey-lemon 30
## 2 halls halls menthollyptus 40
## 3 dove dove oxygen moisture 40
## 4 tic tac tic tac 12
## 5 sunfeast bounce sunfeast bounce elaichi delt 24

초급

우선 1번 매장의 총 매출을 계산해보자.

1
2
sum(df[df$store == 1, "sales"])
[1] 14654620

다른 매장도 계산해보자.

1
2
3
4
5
6
7
8
9
10
11
sum(df[df$store == 1, "sales"])
## [1] 14654620

sum(df[df$store == 2, "sales"])
## [1] 47255913

sum(df[df$store == 3, "sales"])
## [1] 22582111

sum(df[df$store == 4, "sales"])
## [1] 19316353

숫자만 바뀌는 것을 보고 반복문으로 바꿀 수 있다는 생각을 했다면 반복문을 어느 정도 이해하고 감을 잡았다는 뜻이다. 다음과 같이 작성해보자.

1
2
3
4
5
6
7
for(n_store in unique(df$store)){
print(sum(df[df$store == n_store, "sales"]))
}
## [1] 14654620
## [1] 47255913
## [1] 22582111
## [1] 19316353

그런데 각 숫자가 몇 번 매장인지 알기 어렵다는 것이 단점이다. 여기서 단순하게 “첫 번째가 1번 매장이겠지.” 라고 생각한다면 실무에서 지옥문이 열릴 수 있다.
다음과 같이 문자열을 이어 붙여주는 paste0() 함수를 활용해보자.

1
2
3
4
5
6
7
8
for(n_store in unique(df$store)){
print(paste0("Store: ", n_store, " - ",
sum(df[df$store == n_store, "sales"])))
}
## [1] "Store: 1 - 14654620"
## [1] "Store: 2 - 47255913"
## [1] "Store: 3 - 22582111"
## [1] "Store: 4 - 19316353"

초심자에게는 문자열과 변수를 여러개 이어붙이는 것이 생소할 수 있으니 코드를 지우거나 바꿔가면서 원리를 파악하도록 하자.

중급

앞에서는 단순 출력만 했다. 하지만 계산 결과를 다른 곳에서도 활용할 수 있어야 하지 않을까? 이제 슬슬 객체로 저장해보자.

1
2
3
4
5
6
sales = c()
for(n_store in unique(df$store)){
sales = c(sales, sum(df[df$store == n_store, "sales"]))
}
sales
[1] 14654620 47255913 22582111 19316353

sales 객체에 저장했고 이를 출력해보았다. 이번에도 역시나 어떤 숫자가 어떤 매장과 연결되는지 알기 어렵다. 1차원 벡터에 새롭게 연산되는 숫자를 뒤에 하나씩 추가하는 방법 말고 데이터프레임에 정보를 저장해보자.

1
2
3
4
5
6
7
8
9
10
11
12
df_sales = data.frame()
for(n_store in unique(df$store)){
df_sales_sub = data.frame(store = n_store,
sales = sum(df[df$store == n_store, "sales"]))
df_sales = rbind(df_sales, df_sales_sub)
}
df_sales
## store sales
## 1 1 14654620
## 2 2 47255913
## 3 3 22582111
## 4 4 19316353

비어있는 데이터프레임 df_sales를 생성하고 연산 정보를 별도의 객체 df_sales_sub객체에 저장하여 rbind() 함수로 데이터프레임을 차례대로 이어붙여주는 코드이다. 이 결과를 보고 충분히 만족하는 사람도 있겠지만, 데이터가 매우 커질 경우 반복문은 연산시간이 기하급수적으로 늘어난다. 이제 aggregate() 함수를 활용하여 연산해보자.

먼저 매장별 매출 집계이다.

1
2
3
4
5
6
aggregate(data = df, sales ~ store, FUN = "sum")
## store sales
## 1 1 14654620
## 2 2 47255913
## 3 3 22582111
## 4 4 19316353

물결표시(~) 앞에 적은 변수가 FUN 인자에 선언한 함수의 영향을 받는다. 즉 sales 변수의 합계를 계산한다. 하지만 물결표시 뒤에도 변수가 선언되어있다. 즉 store 변수별 sales 변수의 합계가 되겠다.

연산 기준 추가는 + 부호를 활용한다. 다음은 월별 매장별 매출 합계를 산출하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
aggregate(data = df, sales ~ month + store,  FUN = "sum")
## month store sales
## 1 1 1 3793930
## 2 2 1 5487956
## 3 3 1 5372734
## 4 1 2 13355219
## 5 2 2 22237719
## 6 3 2 11662975
## 7 1 3 8546597
## 8 2 3 6551780
## 9 3 3 7483734
## 10 1 4 5080903
## 11 2 4 5497964
## 12 3 4 8737486

그냥 심심풀이로 ggplot을 그려보았다. 향후 ggplot2 관련 포스팅에서 설명할 예정이며 포스팅 게시 이후 여기에 링크가 추가될 예정이다.

1
2
3
4
5
6
7
8
9
10
library("ggplot2")
ggplot(data = df_store_monthly_sales,
aes(x = store,
y = sales,
group = month,
fill = as.character(month))) +
geom_col(position = "dodge") +
scale_y_continuous(labels = scales::comma_format()) +
theme_bw() +
theme(legend.position = "none")

역시나 dplyr가 빠질 수 없다. 매장별 매출 합계는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
library("dplyr")
df %>%
group_by(store) %>%
summarise(sales = sum(sales))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 4 x 2
## store sales
## <int> <int>
## 1 1 14654620
## 2 2 47255913
## 3 3 22582111
## 4 4 19316353

월별 매장별 매출 합계는 다음과 같다. 첫 번째 묶음 연산기준이 되는 변수를 가장 처음에 기술하고 다음 변수는 쉼표 이후에 기술한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
df %>% 
group_by(month, store) %>%
summarise(sales = sum(sales))
`summarise()` regrouping output by 'month' (override with `.groups` argument)
## # A tibble: 12 x 3
## # Groups: month [3]
## month store sales
## <int> <int> <int>
## 1 1 1 3793930
## 2 1 2 13355219
## 3 1 3 8546597
## 4 1 4 5080903
## 5 2 1 5487956
## 6 2 2 22237719
## 7 2 3 6551780
## 8 2 4 5497964
## 9 3 1 5372734
## 10 3 2 11662975
## 11 3 3 7483734
## 12 3 4 8737486

고급

이번엔 매장의 규모를 가늠할 수 있는 새로운 지표를 만들어보자. 새 지표의 기준은 다음과 같다고 가정한다.

대 카테고리 개수 $\times$ 10 + 중 카테고리 개수 $\times$ 5 + 소 카테고리 개수 $\times$ 1

우선 각 매장별 대 카테고리 개수 부터 구해보자.

1
2
3
4
5
6
aggregate(data = df, cat_1 ~ store, FUN = function(x){length(unique(x))})
## store cat_1
## 1 1 75
## 2 2 53
## 3 3 72
## 4 4 56

FUN 인자에 람다함수(일회성 함수)를 사용하였지만 더 쓸 예정이라 별도의 사용자 정의 함수로 만들어버리자.

1
2
3
cat_counter = function(x){
length(unique(x))
}

슬슬 양산을 시작해보자.

1
2
3
4
5
6
7
8
9
cat_1 = aggregate(data = df, cat_1 ~ store, FUN = "cat_counter")
cat_2 = aggregate(data = df, cat_2 ~ store, FUN = "cat_counter")
cat_3 = aggregate(data = df, cat_3 ~ store, FUN = "cat_counter")
cat_3
## store cat_3
## 1 1 195
## 2 2 113
## 3 3 182
## 4 4 120

앞에서 제시되었던 공식대로 연산하고 그 결과를 확인해보자.

1
2
3
4
5
6
7
8
9
idx_cal = (cat_1$cat_1 * 10) + (cat_2$cat_2 * 5) + cat_3$cat_3
df_store_index = data.frame(store = cat_1$store,
index = idx_cal)
df_store_index
## store index
## 1 1 1690
## 2 2 1043
## 3 3 1607
## 4 4 1135

아무튼 계산했다. 1번이 1690으로 가장 큰 값을 가진다. 그런데 좀 더 간결하게 할 수 없을까? 다음과 같이 aggregate() 함수의 변칙적인 사용법을 아는 사람은 많지 않다. 여기서 마침표는 수식(formula)에서 명시되지 않은 모든 변수를 뜻한다. 즉, store 변수만 명시되었으니 나머지 cat_1 , cat_2 , cat_3 변수가 여기에 지정된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat_123 = aggregate(data = df[, c(2, 5:7)], . ~ store, 
FUN = "cat_counter")
cat_123
## store cat_1 cat_2 cat_3
## 1 1 75 149 195
## 2 2 53 80 113
## 3 3 72 141 182
## 4 4 56 91 120
cat_123[, "index"] = (cat_123$cat_1 * 10) + (cat_123$cat_2 * 5) + cat_123$cat_3
cat_123
## store cat_1 cat_2 cat_3 index
## 1 1 75 149 195 1690
## 2 2 53 80 113 1043
## 3 3 72 141 182 1607
## 4 4 56 91 120 1135

이걸 또 dplyr 패키지 버전으로 바꿔보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
df %>% 
group_by(store) %>%
summarise(cat_1 = n_distinct(cat_1),
cat_2 = n_distinct(cat_2),
cat_3 = n_distinct(cat_3)) -> df_dplyr
df_dplyr
## # A tibble: 4 x 4
## store cat_1 cat_2 cat_3
## <int> <int> <int> <int>
## 1 1 75 149 195
## 2 2 53 80 113
## 3 3 72 141 182
## 4 4 56 91 120

갑자기 쉬워진 것 같지만 기분탓이다. 여기서는 n_distinct() 함수를 사용하는 것이 핵심이다. 이는 length(unique())와 같이 length()unique() 함수의 중첩 대신 사용할 수 있다.

최종 연산은 다음과 같다.

1
2
3
4
5
6
7
8
9
df_dplyr[, "index"] = (df_dplyr$cat_1 * 10) + (df_dplyr$cat_2 * 5) + df_dplyr$cat_3
df_dplyr
# A tibble: 4 x 5
## store cat_1 cat_2 cat_3 index
## <int> <int> <int> <int> <dbl>
## 1 1 75 149 195 1690
## 2 2 53 80 113 1043
## 3 3 72 141 182 1607
## 4 4 56 91 120 1135

물론 인덱스 연산도 별도의 가중합 함수를 만들면 그 형태가 변하긴 하겠다. 그리고 가중합은 별도의 패키지에서 제공하긴 하는데 굳이 간단한 연산은 패키지에 의존하지 않고 직접 함수를 만들어서 사용하는 것이 코드 유지보수 및 관리에 좋다.

Your browser is out-of-date!

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

×