큰 데이터를 다루다보면 그룹별 합을 해야하는 경우가 많습니다. 엑셀의 SUMIF 기능이라고 생각하면 간단한데요 R에서 더욱 편리하게 적용할 수 있습니다.
연습 데이터를 다음과 같이 만들었습니다
> v1 <- c("A", "A", "A", "A", "B", "B", "B", "B")
> v2 <- c(2020, 2019, 2020, 2019, 2019, 2020, 2019, 2019)
> v3 <- c(100, 500, 400, 300, 250, 80, 400, 200)
> v123 <- data.frame(v1, v2, v3)
> v123
v1 v2 v3
1 A 2020 100
2 A 2019 500
3 A 2020 400
4 A 2019 300
5 B 2019 250
6 B 2020 80
7 B 2019 400
8 B 2019 200
이 데이터에서 A와 B의 합을 다음과 같이 구할 수 있습니다.
by=list(v123$v1) 를 이용했기 때문에 Group.1 에 A와 B의 값이 들어가 있는것을 볼 수 있습니다.
> aggregate(v123$v3, by=list(v123$v1), FUN=sum)
Group.1 x
1 A 1300
2 B 930
그럼 A, B에다가 년도 정보까지 같이 그룹핑을 하고 싶으면 간단하게 by에 추가를 하면 됩니다.
다음과 같이 추가한 그룹의 갯수만큼 결과를 확인 할 수 있습니다.
> aggregate(v123$v3, by=list(v123$v1, v123$v2), FUN=sum)
Group.1 Group.2 x
1 A 2019 800
2 B 2019 850
3 A 2020 500
4 B 2020 80
여기까지 따라서 연습해보시면 다른 function도 사용해보고싶다는 생각을 하실것 같습니다. 그럼 FUN값을 변경해서 합산(sum) 뿐아니라 max, min, median, mean, sum, length 등 다양하게 응용을 해보시길 바랍니다.
이 문제가 자주 발생하며 가장 쉬운 방법 apply()은 mutate명령 내 에서 함수 를 사용하는 것 입니다.
library(tidyverse) df=data.frame( x1=c(1,0,0,NA,0,1,1,NA,0,1), x2=c(1,1,NA,1,1,0,NA,NA,0,1), x3=c(0,1,0,1,1,0,NA,NA,0,1), x4=c(1,0,NA,1,0,0,NA,0,0,1), x5=c(1,1,NA,1,1,1,NA,1,0,1)) df %>% mutate(sum = select(., x1:x5) %>% apply(1, sum, na.rm=TRUE))여기서 표준 dplyr트릭 (예 : starts_with()또는 contains())을 사용하여 열을 선택하려는 모든 것을 사용할 수 있습니다 . 단일 mutate명령 내에서 모든 작업을 수행함으로써이 조치는 dplyr처리 단계 스트림 내의 어느 곳에서나 발생할 수 있습니다 . 마지막으로, apply()함수를 사용하면 목적에 맞게 구축 된 요약 기능을 포함하여 필요한 요약을 유연하게 사용할 수 있습니다.
또는 tidyverse가 아닌 함수를 사용하는 아이디어가 매력적이지 않다면 열을 모아서 요약 한 다음 결과를 다시 원래 데이터 프레임에 조인 할 수 있습니다.
df <- df %>% mutate( id = 1:n() ) # Need some ID column for this to work df <- df %>% group_by(id) %>% gather('Key', 'value', starts_with('x')) %>% summarise( Key.Sum = sum(value) ) %>% left_join( df, . )여기서는 starts_with()함수를 사용하여 열을 선택하고 합계를 계산했으며 NA값으로 원하는 모든 작업을 수행 할 수 있습니다 . 이 접근 방식의 단점은 매우 유연하지만 dplyr데이터 정리 단계 의 흐름에 적합하지 않다는 것 입니다.
<요약>
그룹별 적용
- apply() : 2차원 데이터를 행, 열 방향으로 연산 # 적용방향 = 1:같은 행별, 2:같은 열별, c(1,2): 원소별
원소별 적용
- sapply() : 벡터에 함수를 반복 적용(벡터로 출력) # 데이터 색인 시 벡터가 편리하므로, 주로 sapply()를 사용
- lapply() : 벡터에 함수를 반복 적용(리스트로 출력)
- mapply() : 벡터에 함수를 반복 적용(리스트로 출력) # sapply()와 유사, 다수의 인자를 받는 함수를 적용하기 위해 사용
그룹별 연산
- tapply() : 그룹별 연산
* 작성 방법
apply(iris[,-5], 2, mean)
sapply(iris[,-5], mean)
lapply(iris[,-5], mean)
mapply(mean, iris[,-5])
tapply(iris$Petal.Length, iris$Species, mean)
Apply 계열 함수
apply 계열 함수란 데이터에 임의의 함수를 적용하여 결과를 얻기 위한 함수입니다.
적용 함수라고 불리고, 벡터 연산을 위해 보통 for문을 사용하는데 보다 간단하고 빠르게 벡터 연산을 수행하기 위해 사용합니다.
1. 그룹별 적용
apply() : 2차원 데이터를 행, 열 방향으로 연산
apply(데이터 셋, 적용방향, 적용(그룹)함수)
# 적용방향 = 1:같은 행별, 2:같은 열별, c(1,2): 원소별
apply() 함수는 2차원데이터(배열, 행렬, 데이터 프레임)에 적용 가능합니다.
apply() 함수는 함수가 적용되는 과정에서 데이터를 벡터 형식으로 묶어서 함수에 전달합니다. 그러므로, 벡터를 받을 수 있는 함수에만 적용 가능하겠죠!
또, apply() 함수는 그룹 연산을 위한 목적의 함수이므로 그룹 함수를 적용하는것을 권합니다.
참고: '행별'의 의미는 R에서는 서로 같은 행끼리를 의미하고, Python에서는 서로 다른 행 끼리를 의미합니다.
> m1 <- matrix(1:9, ncol = 3)
> m1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> apply(m1,1,sum) # 행별(서로 같은 행끼리) sum 연산
[1] 12 15 18
> apply(m1,2,sum) # 열별(서로 같은 열끼리) sum 연산
[1] 6 15 24
> apply(m1,c(1,2),sum) # 원소별 sum 연산 (apply()함수에서 1:1 함수의 적용은 권하지 않음)
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
...
> apply(iris[,-5], 2, mean) # 컬럼별 평균 계산
Sepal.Length Sepal.Width Petal.Length Petal.Width
5.843333 3.057333 3.758000 1.199333
#Q. 두 번째 글자만 추출하는 사용자 정의함수 생성 및 적용
> m2 <- matrix(c('a1', 'a2', 'a3','a4'), nrow = 2)
> m2
[,1] [,2]
[1,] "a1" "a3"
[2,] "a2" "a4"
# 1) 사용자 정의 함수 생성
> library(stringr) # str_sub() 함수 사용을 위함
> f2 <- function(x){
+ return(str_sub(x,2,2))
+ }
# 2) 함수 적용
> f2(m2) # 사용자 정의 함수를 바로 호출한 경우
[1] "1" "2" "3" "4"
> apply(m2,1,f2) # apply()로 함수를 적용한 경우
[,1] [,2]
[1,] "1" "2"
[2,] "3" "4"
#Q. 연습문제
> disease <- read.csv("전염병발병현황.csv", stringsAsFactors = F)
> str(disease)
'data.frame':12 obs. of 6 variables:
$ 월별 : chr "1월" "2월" "3월" "4월" ...
$ 콜레라 : int 6 5 1 1 1 3 14 15 3 1 ...
$ 장티푸스: int 175 165 200 200 194 227 179 163 145 107 ...
$ 이질 : int 550 253 263 507 343 272 224 347 105 142 ...
$ 대장균 : int 7 9 13 13 27 92 201 114 43 39 ...
$ A형간염 : int 351 535 1003 856 959 928 630 505 364 230 ...
> disease
월별 콜레라 장티푸스 이질 대장균 A형간염
1 1월 6 175 550 7 351
2 2월 5 165 253 9 535
...
11 11월 3 97 377 27 190
12 12월 4 121 679 21 167
# 1) 월별 발병수의 총합
> apply(disease[,-1],1,sum)
[1] 1089 967 1480 1577 1524 1522 1248 1144 660 519 694 992
# 2) 전염병별 발병수의 총합
> apply(disease[,-1],2,sum)
콜레라 장티푸스 이질 대장균 A형간염
57 1973 4062 606 6718
행, 열의 합 또는 평균 계산 함수
apply() 함수에서 사용할 수 없는 NA처리, na.rm 기능을 적용하기 위해 사용합니다.
> rowSums(m1, na.rm = T) # 행별(서로 같은 행끼리) 합
> rowMeans(m1, na.rm = T) # 행별(서로 같은 행끼리) 평균
> colSums(m1, na.rm = T) # 열별(서로 같은 열끼리) 합
> colMeans(m1, na.rm = T) # 열별(서로 같은 열끼리) 평균
2. 원소별 적용
sapply() : 벡터에 함수를 반복 적용(벡터로 출력)
lapply() : 벡터에 함수를 반복 적용(리스트로 출력)
mapply() : 벡터에 함수를 반복 적용(리스트로 출력)
sapply(벡터, 함수, 함수의 인자) # 데이터 색인 시 벡터가 편리하므로, 주로 sapply()를 사용
lapply(벡터, 함수, 함수의 인자)
mapply(함수, 함수의 인자) # sapply()함수와 유사하지만, 다수의 인자를 받는 함수를 적용하기 위해 사용
# emp에서 SAL이 3000 이상이면 SAL의 10%를, 미만이면 8%를 출력
> bonus <- function(s) {
+ if(s >= 3000){
+ return(s * 1.1)
+ } else {
+ return(s * 1.08)
+ }
+ }
# bonus(emp$SAL) 함수 수행 시 if문은 여러 값(벡터)을 연산할 수 없으므로, 단 하나의 리턴값만 연산이 수행됩니다.
그렇기 때문에 for문이 필요하겠죠!
# 혹여나 결과가 제대로 나오는 것 처럼 보이더라도 자세히 확인해보면 첫 번째 값의 진리값 기준으로 계산이 수행됩니다.
# 이럴 때! apply 계열 함수 사용 시 for문을 사용하지 않더라도 간단하고, 성능적으로도 빠르게 벡터 연산을 가능하게 해줍니다.
> sapply(emp$SAL, bonus) # 벡터로 출력
[1] 864 1728 1350 3213 1350 3078 2646 3300 5500 1620 1188 1026 3300 1404
> lapply(emp$SAL, bonus) # 리스트로 출력
[[1]]
[1] 864
[[2]]
[1] 1728
...
[[13]]
[1] 3300
[[14]]
[1] 1404
> unlist(lapply(emp$SAL, bonus)) # lapply()함수의 결과 리스트를 다시 벡터로 변환하기 위한 방법 (unlist 함수)
[1] 864 1728 1350 3213 1350 3078 2646 3300 5500 1620 1188 1026 3300 1404
> mapply(mean, iris[,-5])
Sepal.Length Sepal.Width Petal.Length Petal.Width
5.843333 3.057333 3.758000 1.199333
#. apply(), sapply(), lapply() 비교
> m1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> f1 <- function(x) {
+ return(x+3)
+ }
> apply(m1, 2, f1) # 데이터 m1에 그룹별(행,열) 함수의 적용
[,1] [,2] [,3]
[1,] 4 7 10
[2,] 5 8 11
[3,] 6 9 12
> sapply(m1, f1) # 데이터 m1에 각 원소별 함수의 적용(벡터로 출력) - matrix로 형태 변환이 필요
[1] 4 5 6 7 8 9 10 11 12
> lapply(m1, f1) # 데이터 m1에 각 원소별 함수의 적용(리스트로 출력)
[[1]]
[1] 4
[[2]]
[1] 5
...
[[8]]
[1] 11
[[9]]
[1] 12
#. apply 계열 함수들의 함수의 추가인자 전달 방식
> m1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
> m1[2,2] <- NA
> m1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 NA 8
[3,] 3 6 9
> apply(m1, 1, sum) # NA가 포함된 행의 결과는 NA로 리턴
[1] 12 NA 18
> rowSums(m1, na.rm = T)
[1] 12 10 18
> help(apply) # apply(X, MARGIN, FUN, ...) : ... 에는 함수의 인자를 작성할 수 있습니다.
> apply(m1,1,sum,na.rm = T) # sum 이라는 함수가 na.rm 옵션을 가지고 있으므로 apply() 함수에서 사용 가능
[1] 12 10 18
# 어떤 함수더라도 apply 함수를 만나면 해당 함수의 옵션을 사용할 수 있습니다.
#Q. 입력한 문자열에서 입력한 개수만큼 처음부터 추출
# f("aabcx",3) = aab
> f <- function(x,n){
+ return(substr(x,1,n))
+ }
> f("aabcx",3)
[1] "aab"
# 이름의 앞 3글자만 추출
> f(emp$ENAME,3) # 참고. 파이썬에서는 함수 안에 벡터를 작성해도 벡터연산이 불가함
[1] "SMI" "ALL" "WAR" "JON" "MAR" "BLA" "CLA" "SCO" "KIN" "TUR" "ADA" "JAM" "FOR" "MIL"
> apply(emp$ENAME, c(1,2), f, 3) # apply()함수에는 벡터 데이터가 올 수 없습니다.
Error in apply(emp$ENAME, c(1, 2), f, 3) :
dim(X)는 반드시 양의 값을 가지는 길이를 가져야 합니다
> sapply(emp$ENAME, f, 3) # sapply()함수는 벡터 데이터 연산 가능,
함수에 인자 전달이 필요하다면 함수 뒤에 기입해주어야 합니다.
SMITH ALLEN WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER
"SMI" "ALL" "WAR" "JON" "MAR" "BLA" "CLA" "SCO" "KIN" "TUR" "ADA" "JAM" "FOR" "MIL"
3. 그룹별 연산
tapply() : 그룹별 연산
tapply(vector, index, function, ...)
tapply(연산대상(벡터), 그룹 지표, 함수, 함수인자)
# 앞에는 계산하고자하는 대상을, 뒤에는 그룹핑할 수 있는 그룹의 지표(논리값 or 숫자 or 문자열)
> tapply(c(1,2,3,4),c(1,1,2,2),sum)
1 2
3 7
> tapply(emp$SAL, emp$DEPTNO, mean, na.rm = T) # 같은 부서(10,20,30) 내의 급여 평균
10 20 30
2916.667 2175.000 1566.667
> tapply(iris$Petal.Length, iris$Species, mean) # iris의 같은 종류 별 Petal Length의 평균
setosa versicolor virginica
1.462 4.260 5.552
*. rnorm : 정규분포를 따르는 분포 중, 임의의 수를 출력하고자 할 때 사용
rnorm(개수, 평균, 표준편차)
평균 : 어떤 값들의 집합의 적절한 특징을 나타내거나 요약하는 것을 의미
정규분포 : 수집된 자료의 분포를 근사하는 데에 자주 사용되며, 이것은 중심극한정리에 의하여 독립적인 확률변수들의
평균은 정규분포에 가까워지는 성질이 있기 때문(연속 확률 분포의 하나)
표준편차 : 자료의 산포도(데이터가 얼마나 퍼져있나를 나타내는 용어)를 나타내는 수치로, 분산의 양의 제곱근으로 정의
(출처: 위키백과, 네이버 지식백과)
# 평균이 0이고 표준편차가 1인 난수 10개 생성
> rnorm(10,0,1)
[1] 0.52293395 0.16371174 -1.36251762 -0.47018805 0.70748778 0.06825669 1.37206336 -0.98367573 1.83710522
[10] -0.46185635
> mapply(rnorm, 10,0,1) # mapply() 함수에서 인자로 동시에 여러 값 입력 가능
[,1]
[1,] -0.22139742
[2,] 1.06827309
[3,] -0.07316873
[4,] -1.64685767
[5,] -0.67088724
[6,] -0.27782831
[7,] -0.14553122
[8,] -0.27055841
[9,] 0.37373581
[10,] -0.44668074
> mapply(rnorm, c(2,4,6),c(0,10,100),c(1,1,1)) # for문을 사용하지 않아도 apply() 함수를 통해 함수 반복 수행 가능
[[1]]
[1] -1.648982 0.399832
[[2]]
[1] 9.922510 9.748816 9.582573 10.479338
[[3]]
[1] 100.35910 101.10890 99.51336 99.57302 100.46906 100.34759
Q.prac
# apply 관련 연습문제(fruits.csv 파일 사용)
> fruits <- read.csv("fruits.csv", stringsAsFactors = F)
> head(fruits)
year name qty price
1 2000 apple 6 6000
2 2000 banana 2 1000
3 2000 peach 7 3500
4 2000 berry 9 900
5 2001 apple 10 10000
6 2001 banana 7 3500
> str(fruits)
'data.frame': 12 obs. of 4 variables:
$ year : int 2000 2000 2000 2000 2001 2001 2001 2001 2002 2002 ...
$ name : chr "apple" "banana" "peach" "berry" ...
$ qty : int 6 2 7 9 10 7 3 15 13 10 ...
$ price: int 6000 1000 3500 900 10000 3500 1500 1500 13000 5000 ...
# Q1) qty가 10 이상일 때 수량의 합과 미만일 때 수량의 합을 각각 계산
> sum(fruits[fruits$qty >= 10,"qty"]) # qty 10 이상
[1] 59
> sum(fruits[fruits$qty < 10,"qty"]) # qty 10 미만
[1] 39
> tapply(fruits$qty, fruits$qty>=10, sum) # tapply의 첫 번째 인자는 반드시 벡터(data frame은 사용 불가)
FALSE TRUE
39 59
# Q2) 과일 이름별 price의 평균 출력
> tapply(fruits$price, fruits$name, mean)
apple banana berry peach
9666.667 3166.667 1166.667 2500.000
# Q3) year컬럼에서 년도를 두 자리 표현식으로 변경
> f3 <- function(x,y,z){
+ return(substr(x,y,z))
+ }
> f3(fruits$year,3,4) # 함수를 바로 사용한 방법
[1] "00" "00" "00" "00" "01" "01" "01" "01" "02" "02" "02" "02"
> sapply(fruits$year, f3, 3, 4) # sapply()로 사용자 정의 함수를 사용한 방법
[1] "00" "00" "00" "00" "01" "01" "01" "01" "02" "02" "02" "02"
> sapply(fruits$year, substr, 3, 4) # sapply()로 내장 함수를 사용한 방법 - (대상, 함수, 인자...)
[1] "00" "00" "00" "00" "01" "01" "01" "01" "02" "02" "02" "02"
> mapply(substr, fruits$year, 3, 4) # mapply()로 내장 함수를 사용한 방법 - (함수, 대상, 인자...)
[1] "00" "00" "00" "00" "01" "01" "01" "01" "02" "02" "02" "02"
Q.deep
# 심화문제
# 1. card 파일을 읽고
> card <- read.csv("card_history.csv", stringsAsFactors = F)
> str(card)
'data.frame':30 obs. of 7 variables:
$ NUM : int 1 2 3 4 5 6 7 8 9 10 ...
$ 식료품 : chr "19,400" "22,200" "24,600" "22,300" ...
$ 의복 : chr "143,000" "120,400" "88,500" "124,800" ...
$ 외식비 : chr "8,600" "7,000" "7,500" "7,700" ...
$ 책값 : chr "29,000" "26,000" "22,000" "78,000" ...
$ 온라인소액결제: chr "5,600" "3,300" "7,500" "3,900" ...
$ 의료비 : chr "19,200" "13,000" "16,600" "28,100" ...
> card # 천 단위 구분기호 제거 후 숫자형으로 변경 필요
NUM 식료품 의복 외식비 책값 온라인소액결제 의료비
1 1 19,400 143,000 8,600 29,000 5,600 19,200
2 2 22,200 120,400 7,000 26,000 3,300 13,000
...
29 29 31,300 93,800 6,600 30,000 5,400 29,200
30 30 24,600 163,100 6,900 30,000 4,800 10,000
# 1) 각 항목별 총 합
> library(stringr) # str_replace_all 함수 사용을 위함
> str_replace_all(card,',','') # 문자열 함수는 대부분 데이터 프레임 확장 불가 -> 적용함수 사용(원소별 치환)
Warning message:
In stri_replace_all_regex(string, pattern, fix_replacement(replacement), :
argument is not an atomic vector; coercing
> d1 <- sapply(card, str_replace_all,',','') # 문자열 함수는 원소별 적용함수를 사용해야합니다.
> card <- as.data.frame(d1)
> card
NUM 식료품 의복 외식비 책값 온라인소액결제 의료비
1 1 19400 143000 8600 29000 5600 19200
2 2 22200 120400 7000 26000 3300 13000
...
29 29 31300 93800 6600 30000 5400 29200
30 30 24600 163100 6900 30000 4800 10000
> as.numeric(card) # 그러나! 형 변환 함수는 2차원 이상의 데이터 구조 적용 불가
Error: (list) object cannot be coerced to type 'double'
# 해결방법. 사용자 정의 함수를 생성하여 천단위 구분기호 제거 후 숫자형 변경
> fcard <- function(x) {
+ return(as.numeric(str_replace_all(x,',','')))
+ }
> card <- as.data.frame(sapply(card,fcard))
> apply(card[,-1], 2, sum)
식료품 의복 외식비 책값 온라인소액결제 의료비
795500 3947700 281400 945000 186300 633600
################################################################
# 2. movie.csv 파일을 읽고
> movie <- read.csv("movie_ex1.csv", stringsAsFactors = F, encoding = 'utf-8')
> head(movie)
년 월 일 지역.시도 지역.시군구 지역.읍면동 성별 연령대 이용_비율...
1 2018 2 1 강원도 강릉시 임당동 여 50대 0.00016
2 2018 2 1 강원도 강릉시 임당동 남 30대 0.00165
3 2018 2 1 강원도 강릉시 임당동 남 50대 0.00049
4 2018 2 1 강원도 속초시 조양동 남 40대 0.00148
5 2018 2 1 강원도 속초시 조양동 남 50대 0.00008
6 2018 2 1 강원도 속초시 조양동 여 40대 0.00091
> str(movie)
'data.frame':66870 obs. of 9 variables:
$ 년 : int 2018 2018 2018 2018 2018 2018 2018 2018 2018 2018 ...
$ 월 : int 2 2 2 2 2 2 2 2 2 2 ...
$ 일 : int 1 1 1 1 1 1 1 1 1 1 ...
$ 지역.시도 : chr "강원도" "강원도" "강원도" "강원도" ...
$ 지역.시군구 : chr "강릉시" "강릉시" "강릉시" "속초시" ...
$ 지역.읍면동 : chr "임당동" "임당동" "임당동" "조양동" ...
$ 성별 : chr "여" "남" "남" "남" ...
$ 연령대 : chr "50대" "30대" "50대" "40대" ...
$ 이용_비율...: num 0.00016 0.00165 0.00049 0.00148 0.00008 0.00091 0.00082 0.00165 0.00041 0.00016 ...
# 1) 요일별 이용률 평균
> library(stringr)
# 참고.
# 년월일을 합치기 위한 방법
> paste(movie$년,movie$월, movie$일, sep = '')
> str_c(movie$년, movie$월, movie$일)
# 날짜 파싱 과정
> as.Date("201911", '%Y%m%d') # 날짜로 인식 불가, 월과 일의 구분이 애매함
> as.Date("2019/1/1", '%Y/%m/%d') # 구분기호를 넣어주면 월과 일이 두 자리 표현식(01)이 아니더라도 가능
# My)
> movie_day <- as.Date(str_c(movie$년, '-', movie$월, '-', movie$일), '%Y-%m-%d')
> library(lubridate)
> as.character(wday(movie_day, label=T))
[1] "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목"
...
[985] "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목" "목"
# sol 1) paste 함수 사용
> v1 <- (paste(movie$년, '/', movie$월, '/', movie$일, sep = ''))
> movie$day1 <- as.character(as.Date(v1, '%Y/%m/%d'),'%A') # as.Date는 날짜 포맷 변경이 아닌 파싱용 함수입니다.
# R의 날짜 default 포맷이 0000-00-00 이므로 0000/00/00 형식으로 파싱해도 적용이 안되는 것이죠.
> tapply(movie$이용_비율..., movie$day1, mean)
금요일 목요일 수요일 월요일 일요일 토요일 화요일
0.0017283872 0.0015061331 0.0013655414 0.0009007030 0.0017006749 0.0021701041 0.0009055736
# sol 2) str_c 함수 사용
> v2 <- str_c(movie$년, sprintf('%02d', movie$월), sprintf('%02d', movie$일))
> movie$day2 <- as.character(as.Date(v2, '%Y%m%d'),'%A')
> per <- tapply(movie$이용_비율..., movie$day2, mean)
> per[c(4,7,3,2,1,6,5)] # 요일 정렬은 색인을 활용하여 할 수 있습니다. (요일 정렬순서를 색인값에 전달)
월요일 화요일 수요일 목요일 금요일 토요일 일요일
0.0009007030 0.0009055736 0.0013655414 0.0015061331 0.0017283872 0.0021701041 0.0017006749
################################################################
# 3. delivery_02 파일을 읽고
> delivery <- read.csv("delivery_02.csv", stringsAsFactors = F, encoding = 'utf-8')
> head(delivery)
일자 시간대 업종 시도 시군구 읍면동 통화건수
1 20180201 0 음식점-족발/보쌈전문 서울특별시 강남구 논현동 5
2 20180201 0 음식점-족발/보쌈전문 서울특별시 강남구 역삼동 5
...
5 20180201 0 음식점-족발/보쌈전문 서울특별시 동작구 신대방동 5
6 20180201 0 음식점-족발/보쌈전문 서울특별시 노원구 상계동 5
# 1) 각 읍면동별 통화건수의 총 합을 구하되,
# 각 동은 숫자를 포함하고 있는 경우 숫자를 제외한 동까지 표현하도록 함
# (ex 을지로6가 => 을지로)
> strsplit('을지로6가','[0-9]')[[1]][1] # 코드 구현 전, 단계별로 확인해보면서 진행하는 것이 중요합니다.
[1] "을지로"
> strsplit('을지로','[0-9]')[[1]][1]
[1] "을지로"
> strsplit(delivery$읍면동,'[0-9]')[[1]][1] # 하지만, 문자열 함수는 벡터 연산 불가
[1] "논현동"
> str_split(delivery$읍면동,'[0-9]')[[1]][1]
[1] "논현동"
> f2 <- function(x) { # 적용함수 사용
+ return(strsplit(x,'[0-9]')[[1]][1])
+ }
> delivery$town <- sapply(delivery$읍면동,f2)
> tapply(delivery$통화건, delivery$town, sum)
가락동 가산동 가양동 갈월동 갈현동 개봉동 개포동 거여동 경운동 공덕동 공릉동 관수동 광장동 교남동
10710 11172 20655 370 385 22711 2729 410 761 1286 4959 690 1678 2084
...
합정동 행당동 현석동 홍은동 홍익동 홍제동 화곡동 화양동 회현동 효창동 후암동 휘경동 흑석동 흥인동
8966 690 10 2710 290 16116 30048 1391 480 560 12148 430 1183 590
Q.prac2
# 1. emp 데이터를 사용하여 각 부서별 최대 연봉값을 구하여라
> emp <- read.csv("emp.csv", stringsAsFactors = F)
> head(emp)
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
1 7369 SMITH CLERK 7902 1980-12-17 0:00 800 NA 20
2 7499 ALLEN SALESMAN 7698 1981-02-20 0:00 1600 300 30
3 7521 WARD SALESMAN 7698 1982-02-22 0:00 1250 500 30
4 7566 JONES MANAGER 7839 1981-04-02 0:00 2975 NA 20
5 7654 MARTIN SALESMAN 7698 1981-09-28 0:00 1250 1400 30
6 7698 BLAKE MANAGER 7839 1981-05-01 0:00 2850 NA 30
> tapply(emp$SAL, emp$DEPTNO, max)
10 20 30
5000 3000 2850
##########################################################
# 2. 위의 데이터를 활용하여 부서별 최대값을 갖는 직원의 이름, 부서, 연봉, 최대연봉을 함께 출력하여라.
(marge 를 사용하지 않고 apply() 함수를 사용하여 풀어보기)
> emp[emp$SAL == 5000 & emp$DEPTNO == 10, c("ENAME", "DEPTNO", "SAL")] # 이런 형태로 만들것임
ENAME DEPTNO SAL
9 KING 10 5000
> df1 <- as.data.frame(tapply(emp$SAL, emp$DEPTNO, max)) # data frame 형태로 변환
> df1 <- cbind(row.names(df1), df1) # 행 이름을 첫 번째 컬럼에 넣어주기 위해 cbind 사용
> colnames(df1) <- c('DEPTNO', 'SAL')
> df1
DEPTNO SAL
10 10 5000
20 20 3000
30 30 2850
# apply()함수를 사용하면 (DEPTNO, SAL) 행 별로 전달 가능
# apply()함수는 데이터를 하나로 묶어서 vector 형식으로 전달됨. 그 중 일부를 뽑기 위해 벡터의 숫자 색인 사용
> f_max <- function(x){
+ emp[emp$SAL == x[2] & emp$DEPTNO == x[1], c("ENAME", "DEPTNO", "SAL")]
+ }
> apply(df1, 1, f_max)
$`10`
ENAME DEPTNO SAL
9 KING 10 5000
$`20`
ENAME DEPTNO SAL
8 SCOTT 20 3000
13 FORD 20 3000
$`30`
ENAME DEPTNO SAL
6 BLAKE 30 2850
##########################################################
# 3. data2 데이터를 읽고
> train <- read.csv("data2.csv", stringsAsFactors = F, encoding = 'utf-8')
> head(train)
노선번호 시간 승차 하차
1 line_1 506 88,136 35,394
2 line_1 607 114,628 195,028
3 line_1 708 259,282 483,162
4 line_1 809 384,892 1,165,703
5 line_1 910 315,797 791,704
6 line_1 1011 340,972 585,759
> str(train)
'data.frame':80 obs. of 4 variables:
$ 노선번호: chr "line_1" "line_1" "line_1" "line_1" ...
$ 시간 : int 506 607 708 809 910 1011 1112 1213 1314 1415 ...
$ 승차 : chr " 88,136 " " 114,628 " " 259,282 " " 384,892 " ...
$ 하차 : chr " 35,394 " " 195,028 " " 483,162 " " 1,165,703 " ...
# 1) 다음과 같이 노선별 승하차의 총 합을 표현
# line_1 line_2 line_3 line_4
# XXXXX XXXXX XXXXX XXXXX
> f_train <- function(x) {
+ return(as.numeric(str_replace_all(x,',','')))
+ }
> library(stringr)
> train[,3:4] <- as.data.frame(sapply(train[,3:4],f_train))
> str(train)
'data.frame':80 obs. of 4 variables:
$ 노선번호: chr "line_1" "line_1" "line_1" "line_1" ...
$ 시간 : int 506 607 708 809 910 1011 1112 1213 1314 1415 ...
$ 승차 : num 88136 114628 259282 384892 315797 ...
$ 하차 : num 35394 195028 483162 1165703 791704 ...
> tapply(train$승차+train$하차, train$노선번호, sum)
line_1 line_2 line_3 line_4
19097780 95377046 33039606 39873125
# 2) 오전 오후의 승하차의 총 합을 표현
# 오전 오후
# XXXX XXXXX
> str_replace_all('506','[0-9][0-9]$','') # 뒤의 두 숫자 삭제
[1] "5"
> str_replace_all('1506','[0-9][0-9]$','') # 뒤의 두 숫자 삭제
[1] "15"
> train$time <- as.numeric(str_replace_all(train$시간,'[0-9][0-9]$',''))
# 논리값 사용 후 컬럼이름 지정(데이터가 큰 경우 작업을 나눠서 하는 것이 유리)
> v_time <- tapply(train$승차+train$하차, train$time >= 12, sum)
> names(v_time) <- c('오전','오후')
> v_time
오전 오후
61837486 125550071
# 오전 오후 바로 출력(데이터가 적은 경우 사용)
> tapply(train$승차+train$하차,
+ ifelse(train$time >= 12, '오후','오전'),
+ sum)
오전 오후
61837486 125550071
# my answer
> f_train2 <- function(x) {
+ return(as.numeric(str_sub(gettextf('%04d', x),1,2)))
+ }
> train$시간 <- sapply(train[,2], f_train2)
> train$time <- ifelse(train$시간>=0 & train$시간<=12, "오전", "오후")
> tapply(train$승차+train$하차, train$time, sum)
오전 오후
70394130 116993427
Q.deep2
# 심화 문제
# 1. student.csv 파일을 불러와서
> student <- read.csv("student.csv", stringsAsFactors = F, encoding = 'utf-8')
> head(student)
> str(student)
# 1) 남,여별 키의 평균
> student$gender <- ifelse(substr(student$JUMIN,7,7)=='1', '남', '여')
> tapply(student$HEIGHT, student$gender, mean)
남 여
176.3333 165.7500
# 2) 3,4학년 학생의 이름, 학번(studno), 키와 몸무게 출력
> student[student$GRADE %in% c(3,4),c("NAME", "STUDNO", "HEIGHT", "WEIGHT", "GRADE")]
NAME STUDNO HEIGHT WEIGHT GRADE
1 이진욱 9411 180 72 4
2 서재수 9412 172 64 4
...
9 구유미 9514 160 58 3
10 임세현 9515 171 54 3
# 3) id 컬럼에 숫자가 포함되어 있는 경우 숫자의 삭제처리
> library(stringr)
> student$ID <- str_replace_all(student$ID,'[0-9]','')
##########################################################
# 2. subway2.csv 파일을 읽고
> subway <- read.csv("subway2.csv", stringsAsFactors = F, skip = 1)
> head(subway)
> str(subway)
# 1) 시간대별 승하차 총합
> toNum <- function(x) {
+ as.numeric(str_replace_all(x, ',', ''))
+ }
> library(stringr)
> subway[,-c(1,2)] <- sapply(subway[,-c(1,2)],toNum)
> apply(subway[,-c(1,2)],2,sum)
X05.06 X06.07 X07.08 ... X22.23 X23.24 X24.01
1338165 3953180 10184565 ... 8782129 4997008 1204504
# 2) 역별 출근시간대(05~10시)의 승하차 총합
> subway$전체[2]
[1] ""
> is.na(subway$전체[2])
[1] FALSE
> is.null(subway$전체[2])
[1] FALSE
> f_fillna <- function(x){
+ v1 <- c()
+ for(i in 1:length(x)){
+ if(x[i] == ""){
+ v1 <- c(v1,x[i-1])
+ } else{
+ v1 <- c(v1,x[i])
+ }
+ }
+ return(v1)
+ }
> subway$전체 <- f_fillna(subway$전체)
> head(subway)
전체 구분 X05.06 X06.07 X07.08 ... X22.23 X23.24 X24.01
1 서울역(1) 승차 17465 18434 50313 ... 88902 49049 4558
2 서울역(1) 하차 7829 48553 110250 ... 50266 32182 13943
3 시 청(1) 승차 2993 4473 7633 ... 69327 24653 1691
4 시 청(1) 하차 4142 19730 67995... 8552 5349 1603
> tapply(subway$X05.06 + subway$X06.07 + subway$X07.08 + subway$X08.09 + subway$X09.10,
+ subway$전체,
+ sum)
강 남 강 변 건 대 경복궁 고속터미 교대(2) 교대(3) 구 의 구로공단 구파발 ...
1303760 792423 592564 376575 558865 656049 184478 434690 1071360 228266 ...
Q.prac3
# employment.csv 파일을 읽고
> employ <- read.csv("employment.csv", stringsAsFactors = F, na.strings = '-')
> employ
고용형태 X2007 X2007.1 X2007.2 X2007.3 X2007.4 X2007.5
1 고용형태 총근로일수 (일) 총근로시간 (시간) 정상근로시간 (시간) 초과근로시간 (시간) 월급여액 (천원)
2 전체근로자 22.3 188.8 176.5 12.3 1,847 1,732
3 전체근로자(특수형태포함) <NA> <NA> <NA> <NA> 1,858
4 정규근로자 22.7 193.2 179.3 13.9 2,027 1,893
5 비정규근로자 20.8 171.4 165.3 6.1 1,145 1,108
6 비정규근로자(특수형태포함) <NA> <NA> <NA> <NA> 1,313
...
## 컬럼명과 첫 번째 행의 각 세부항목을 결합하여 새로운 컬럼 이름 설정
> v1 <- substr(colnames(employ),2,5)[-1]
> v1
[1] "2007" "2007" "2007" "2007" "2007" "2007" "2007" "2007" "2007" "2008" "2008" "2008" "2008" "2008" "2008" "2008" "2008" "2008" ... "2012" "2012" "2012" "2012" "2013" "2013" "2013" "2013" "2013" "2013" "2013" "2013" "2013"
> employ[1,-1]
X2007 X2007.1 X2007.2 X2007.3 X2007.4 X2007.5 X2007.6 ...
1 총근로일수 (일) 총근로시간 (시간) 정상근로시간 (시간) 초과근로시간 (시간) 월급여액 (천원) 정액급여 (천원) 초과급여 (천원) ...
> strsplit(employ[1,-1], ' ')[[1]][1] # split 함수(문자 함수) 벡터 연산 불가 -> 적용 함수 필요
> f_split <- function(x) {
+ strsplit(x, ' ')[[1]][1]
+ }
> v2 <- sapply(employ[1,-1], f_split)
> v2
X2007 X2007.1 X2007.2 X2007.3 X2007.4 X2007.5 X2007.6 X2007.7 ...
"총근로일수" "총근로시간" "정상근로시간" "초과근로시간" "월급여액" "정액급여" "초과급여" "연간특별급여" ...
...
> colnames(employ)[-1] <- str_c(v1, v2,sep = '_')
> employ <- employ[-1,]
> head(employ)
고용형태 2007_총근로일수 2007_총근로시간 2007_정상근로시간 2007_초과근로시간 2007_월급여액 2007_정액급여 ...
2 전체근로자 22.3 188.8 176.5 12.3 1,847 1,732 114 ...
3 전체근로자(특수형태포함) <NA> <NA> <NA> <NA> 1,858 1,748 ...
4 정규근로자 22.7 193.2 179.3 13.9 2,027 1,893 134 ...
...
## 모든 컬럼(첫 번째 제외)을 숫자로 변경
> f_numeric <- function(x){
+ as.numeric(str_replace_all(x,',',''))
+ }
> employ[,-1] <- sapply(employ[,-1], f_numeric) # 첫 번째 컬럼을 제외
# 1) 각 년도별 월급여액 평균
> data1 <- employ[,str_detect(colnames(employ),'월급여액')]
> data1
2007_월급여액 2008_월급여액 2009_월급여액 2010_월급여액 2011_월급여액 2012_월급여액 2013_월급여액
2 1847 1945 1960 2023 2102 2216 2288
3 1858 1952 1964 2025 2103 2215 2310
4 2027 2151 2199 2285 2385 2502 2566
...
> apply(data1, 2, mean)
2007_월급여액 2008_월급여액 2009_월급여액 2010_월급여액 2011_월급여액 2012_월급여액 2013_월급여액
1416.667 1458.500 1461.250 1493.250 1568.167 1634.917 1760.667
# 2) 각 년도별 총 근로일수 평균
> data2 <- employ[,str_detect(colnames(employ),'총근로일수')]
> data2
2007_총근로일수 2008_총근로일수 2009_총근로일수 2010_총근로일수 2011_총근로일수 2012_총근로일수 2013_총근로일수
2 22.3 21.7 22.5 22.2 21.6 20.9 20.3
3 NA NA NA NA NA NA NA
4 22.7 22.2 23.1 22.9 22.5 21.7 21.0
5 20.8 19.9 20.4 20.0 19.1 18.4 17.9
> apply(data2, 2, mean, na.rm = T) # 근로일 수가 점차 줄고 있는 것을 확인 가능
2007_총근로일수 2008_총근로일수 2009_총근로일수 2010_총근로일수 2011_총근로일수 2012_총근로일수 2013_총근로일수
21.36667 20.40000 21.06667 20.55556 20.15556 19.30000 18.92222
참고: KIC 캠퍼스 머신러닝기반의 빅데이터분석 양성과정