R 프로그래밍
작성 완료
- 벡터(vector)
- 행렬(matrix)
- 리스트(list)
- 데이터 프레임(data frame)
- 인자와 데이터 요약(factors and summaries)
- 입출력(input and output)
- 문자열 작업
참고: R 프로그래밍 - 허명회 지음
- R 프로그래밍에 대해 복습할 겸 간단히 정리
x <- 1
is.vector(x)
- x는 길이가 1인 벡터 = 스칼라
print(pi)
print(pi, 16)
- π는 숫자가 예약되어 있는 스칼라
M <- matrix(1:12, nrow = 4, ncol = 3, byrow = T)
M
- M은 행렬 ---> 벡터의 2차원 배열
f <- c(1, 1, 2, 3, 5, 8, 13, 21)
f[5]
f[1:5]
f[-c(1,4)] ## -기호는 제외한다는 의미를 가짐
f[c(1,2,4)]
seq(0, 10, 2.5)
- seq(a, b, length = n)는 a부터 b까지 길이가 n인 일정 간격 수열을 생성
seq(0, 10, length = 11)
- rep(x, times = k)는 x의 각 요소가 k의 각 요소씩 반복된 벡터
rep(NA, 5)
rep(c(1, 2), 3)
rep(c(1, 2, 3), c(3, 2, 1))
- rep(x, each = k)는 x의 각 요소가 각각 k번 반복된 벡터
rep(c(1, 2), each = 3)
x <- 1:10
x[x %% 2 == 1]
subset(x, x %% 2 == 0)
x <- 1:10
x1 <- ifelse(x %% 2 == 0, x, 2*x)
cbind(x, x1)
era <- c(5, 4 ,3, 4 ,5, 6)
era
- paste()함수는 2개의 문자열 벡터를 sep인수로 붙인다
names(era) <- paste("y", 2001:2006, sep = "-")
era
n <- 100
p <- 3
x <- rnorm(n*p)
A <- matrix(x, nrow = n, ncol = p)
A[1:6, ]
- [행, 열] 순으로 인덱싱
A[1:6, 1:2]
- 필터링을 통해 인덱싱도 가능
A[A[,3] > 0 & A[,2] > 0, ]
- t(A) 함수는 행렬 $\bf A$의 전치행렬을 구해줌
B <- matrix(1:9, 3, 3)
B
t(B)
- solve()함수는 역행렬을 구해줌
C <- matrix(c(16 ,4, 1, 4, 4, 1, 1, 1, 1), 3, 3)
C
solve(C)
- %*%는 행렬곱 ---> 잘 알아두자
solve(C) %*% C
- 참고로 $AA^{-1} = I$
- $I$는 단위행렬
- apply(A, 1 or 2, f) ---> 행렬A의 행(1) or 열(2)에 함수 f를 적용
- c(1, 2)는 행과 열에 적용 ---> 각 원소 : 파이썬의 applymap 함수를 생각하자
M <- matrix(1:12, 3, 4)
M
- apply에서 옵션을 1(행)로 하니 전치된 행렬을 반환함
- 그래서 원래의 행렬 크기와 같게 하려면 t() 함수를 통해 전치시켜야 함
max_ = apply(M, 2, max)
apply(M, 1, "-", max_)
max_ = apply(M, 2, max)
t(apply(M, 1, "-", max_))
- apply에서 옵션을 2(열)로 하니 문제 없어보임
max_ = apply(M, 1, max)
apply(M, 2, "-", max_)
- 옵션을 c(1, 2)로 하니 각 원소에서 -1을 수행함
apply(M, 1:2, "-", 1)
- 행렬의 각 열에 대해 최소값 0과 최대값 1이 되도록 변환
n <- 5
p <- 4
M <- matrix(rnorm(n*p), n, p)
M
min_ <- apply(M, 2, min)
max_ <- apply(M, 2, max)
M.1 <- t(apply(M, 1, "-", min_))
M.2 <- t(apply(M.1, 1, "/", max_-min_))
M.2
n <- 10
x <- matrix(round(rnorm(n*4, 50, 10)), n, 4) ## 평균이 50, 표준편차가 10인 정규분포에서 난수 40개 추출
rownames(x) <- paste("S", 1:n, sep = "")
colnames(x) <- c("math", "engl", "science", "arts")
x
members <- list(leaders = c("gang", "iu"), assisstants = "kang")
members
- class(X) 함수는 X의 type을 알려줌
class(members)
- names()함수로 요소의 라벨을 확인 + 변경 가능
print(names(members))
names(members)[2] <- "workers"
print(names(members))
- [[ ]] 사용
members[[1]]
- [ ]은 sublist
- [[ ]]은 벡터이고 [ ]은 리스트임
members[1]
- 요소 이름 사용
members[["leaders"]]
- $ 기호 사용 ---> 개인적으로 제일 편함
members$leaders
- 리스트 내 자료 값 변경도 가능
members$leaders[1] <- "park"
members
salaries <- list(leaders = c(250, 200), assistant = 100, members = c(300, 200, 180, 120 ,100))
salaries
- lapply() 사용
lapply(salaries, mean)
- sapply() 사용
- 벡터로 출력이 불가능할 때는 lapply() 처럼 list로 출력함
sapply(salaries, mean)
unlist(salaries) ## list에서 vector로 변환되었다
course.id <- c(1, 2, 3, 4 ,5, 6 ,7, 8, 9 ,10)
mid <- c(8, 22, 25, 25 ,21, 12, 12, 29, 40, 25)
final <- c(11, 24, 31, 13 ,34 ,26, NA ,36, 34 ,38)
exams <- data.frame(course.id, mid, final)
exams
is.list(exams)
colnames(exams)
names(exams)
length(exams)
- [row, column]으로 인덱싱
exams[exams$mid > 20, ]
exams[exams$mid > 20, 'final'] ## exams[exams$mid > 20, 3] 과 동일함
- 데이터 프레임의 각 열은 수치 벡터이므로 연산 가능
mean(exams[, "mid"])
mean(exams$final)
- 값이 NA로 나옴 ---> 7번 학생이 기말시험을 결시함
- 그럼 어떻게 평균을 구하지? ---> na.rm = T 옵션을 통해 NA를 제외한 데이터만 사용할 수 있음
mean(exams$final, na.rm = T)
- 만약 시험을 결시한 경우 0점 처리 한다면? ---> is.na() 함수를 통해 NA인 경우 TRUE를 아닌 경우 FALSE를 생성하여 처리 가능
is.na(exams$final)
exams$final[is.na(exams$final)] <- 0 ## is.na가 True인 경우만 0으로 변경
exams
mean(exams$final)
- exams 변수는 중간 기말 성적이 기록되어 있음
- 새로운 변수 book은 과제와 프로젝트 점수가 student_name의 순서로 정렬되어 있음
- 이 둘을 합쳐보자
student_name <- c('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')
homework <- c(22, 34, 31, 24, 37, 36, 37, 28, 37, 34)
project <- c(NA, 7, NA, NA, 17 ,10, 8, NA ,4, NA)
course.id <- c(8, 10, 5, 1, 4, 2, 3 ,9 ,7 ,6)
book <- data.frame(student_name, homework, project, course.id)
book
- NA는 자료의 수치 연산을 불가능하게 하므로 앞서 했던것처럼 is.na()를 통해 0점 처리하자
book$project[is.na(book$project)] <- 0
book
- 2개의 데이터 프레임을 통합하고자 할 때 확인할 것은 각 데이터 프레임에서 개체들의 정렬순서
1. 2개의 프레임에서 순서가 같은 경우 열 묶음(cbind)를 한다 ---> cbind(exams, book)
- 하지만 course.id의 순서가 다르므로 불가능
2. 2개의 프레임에서 그 순서가 다른 경우 공통 개체 식별자(key)를 찾아 정의 ---> merge() 함수가 key 변수를 찾아줌
class_record <- merge(exams, book, by = 'course.id')
class_record
- 위와 같이 key 변수의 이름(위에서는 course.id)이 두 데이터 프레임에서 동일한 경우 by를 사용 ---> 그렇지 않은 경우에는 by.x와 by.y를 사용
- 학생들의 총 점수 합계를 구하여 데이터 프레임에 새 변수로 추가해보자
class_record$total <- apply(class_record[ ,c(2, 3, 5, 6)], 1, sum)
class_record
set.seed(1) ## 시드 넘버 설정
alpha <- sample(c("A", "B", "C"), 25, replace = T) ## 'A', 'B', 'C' 중에서 무작위로 25개를 중복을 허용하여 뽑는다
f <- factor(alpha) ## alpha를 범주형 변수로 바꿈
alpha
f
- 문자열인 alpha와 인자 벡터인 f가 같아 보이지만 다름
- factor는 Levels이 존재하여 데이터 분류에 용이함
- str() 함수는 데이터를 요약하여 보여줌
z <- sample(1:5, 25, replace = T)
g <- factor(z)
str(data.frame(f = f, g = g))
- table()은 빈도표를 만듦
- addmargins()는 빈도표의 주변 합을 테이블에 추가함
table(f)
table(f, g) ## f와 g의 인덱스를 보고 만듦 ex) f[x] = 'A'이고 g[x] = 3이라면 ('A', 3)위치의 값을 +1하는 식으로 만듦
addmargins(table(f))
addmargins(table(f, g))
- tapply(x, f, fun)는 x를 f의 수준 별로 쪼개서 fun을 적용함
- 파이썬에 존재하는 gruopby 함수와 agg 함수를 생각하면 쉽다
set.seed(2)
x <- round(rnorm(25, 50, 10))
data.frame(x = x, f = f)
tapply(x, f, mean)
tapply(x, f, function(t){max(t)-min(t)})
- function()은 사용자 정의 함수임
- 한 줄일 경우 { }생략 가능
- 쪼갬의 대상이 벡터가 아니라 데이터 프레임인 경우에는 split()과 sapply()를 함께 이용
split(data.frame(x = x, z = z), f)
s <- split(data.frame(x = x, z = z), f)
class(s)
sapply(s, apply, 2, mean)
- aggregate(x, list(f, g), fun)은 x를 f와 g의 조합으로 쪼개서 fun을 적용함
aggregate(data.frame(x = x, z = z)$x, list(f, g), sum)
- tapply()는 쪼갤 기준이 하나
- aggregate()는 쪼갤 기준이 여러개(list)
- cut(x, breaks)는 수치형 벡터 x를 breaks로 쪼개서 factor 변수로 만듦(구간화)
- 만약 기준 변수가 factor가 아니라면 factor로 만듦
set.seed(21)
x <- runif(100, 0, 10)
y <- 5 + 0.5*(x-5) + rnorm(100) ## x와 y는 선형관계
chr_ <- c('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10')
x_cut <- cut(x, chr_)
class(x_cut)
chr_
cbind(x, x_cut, y)
- $(x, y)$의 산점도에 $x$의 구간별 평균을 막대로 넣어 구간별 평균의 이동을 산출해보자
y_local <- aggregate(y, list(x_cut), mean) ## x_cut은 1~10까지 존재 ---> 1부터 10까지 x_cut별로 따로 모아 그에 해당하는 y 데이터의 평균을 구함
y_local
plot(x, y, ylim = c(0, 10), main = "x vs y" )
segments(0:9, y_local$x, 1:10, y_local$x, lwd = 2) ## segments 함수는 x좌표와 y좌표를 입력받아 line을 그려줌
abline(v = 1:9, lty = "dotted") ## abline 함수는 line을 그려줌, v는 수직선의 위치
- 참고: abline 함수
- 참고: segments 함수
- read.table\read.csv('파일 위치') ---> 텍스트 파일 or csv 파일을 읽어 내부 저장소에 dataframe으로 만듦
- getwd() ---> 현재의 작업 디렉토리를 알려줌
- setwd('위치') ---> 작업 디렉토리를 입력한 위치로 변경
- dir() ---> 현재 작업 디렉토리에 있는 파일들의 리스트를 보여줌
- write.table\write.csv()은 특정 데이터 프레임을 작업 디렉토리에 텍스트 파일 or csv 파일로 입력함
- 파일을 읽을 때 stringAdFactors = F 옵션을 적용하면 문자열 변수가 자동으로 factor 형이 되는 것을 막아줌
- sink()함수를 통해 결과값을 텍스트 파일로 저장할 수 있음 ---> sink function
scan 함수
- scan() ---> 비정형의 문자열 데이터를 읽을 때 유용
- what 은 읽어 들일 데이터 값 형식 numeric, logical, character
- 책 따라 하는데 scan함수가 제대로 작동되지 않아 찾아보니 quote = "" 로 하지 않아서라고 함 ---> 참고: https://pythonq.com/so/r/29317
- quote = "" 옵션을 적용하지 않으면 Read 1 item을 반환 ---> 빈칸을 기준으로 문자열을 쪼개지 않았다는 뜻 ---> 그런데 sep = "\n" 으로 하면 Read 20 item임
- 원인을 알았는데 내가 yesterday노래 가사 텍스트 파일을 만들 때 맨 앞에 " 기호를 실수로 추가했었음 ---> " 기호를 없애니 잘 동작함
lyrics <- scan("yesterday.txt", what = "character") ## quote = "" 옵션을 적용 안해도 잘 동작함
str(lyrics)
head(lyrics, 10)
- 빈칸을 구분자로 인식하여 문자열을 쪼개어 읽어옴 + 줄 단위로 읽고 싶다면 sep = "\n"
lyrics_2 <- scan("yesterday.txt", what = "character", sep = "\n")
str(lyrics_2)
head(lyrics_2, 5)
- print함수와 비슷하나 여러개의 출력이 가능하고 출력이 공백 없이 이어짐
print("a")
print("b")
cat("a")
cat("b")
- grep(pattern, x)는 x에서 pattern이 있는 곳을 알려줌
grep("Yesterday", lyrics)
grep("yesterday", lyrics)
- "Yesterday"는 1 ,63 ,105번째 요소에 있고 "yesterday"는 22, 40 ,62, 84, 104, 126번째 요소에 있음을 알려줌
- 이번에는 "?"를 찾아보자
- grep("?", lyrics)를 하면 될 것 같지만 아님
grep("?", lyrics)
- "?"는 정규표현식 기호로 사용되게 원래의 물음표 기호를 사용하고자 하면 "\\"을 앞에 넣어줘야함 ---> 나중에 정규표현식 공부하자
grep("\\?", lyrics)
- nchar(x)는 문자열 x의 길이를 알려줌(빈칸 포함)
nchar("yesterday")
- 참고 : length(x)의 결과는 아래와 같음
length("yesterday")
- 문자열 벡터에도 적용 가능함
nchar(lyrics)
- 다수의 문자열을 붙여 하나의 문자열을 만듦
- 파이썬의 join 함수를 생각하면 된다
paste("a", "b", "c")
paste("a", "b", "c", sep = "-")
- paste 함수는 디폴트로 빈칸 하나를 기준으로 문자열을 붙인다
- paste0 함수를 사용하면 빈칸 없음을 기준으로 문자열을 붙일 수 있다
paste0("a", "b", "c")
substr("abcdefg", 2, 4)
strsplit("a-b-c-d-e", "-")
class(strsplit("a-b-c-d-e", "-")) ## 쪼갠 문자열들의 타입은 list이다
unlist(strsplit("a-b-c-d-e", "-"))
strsplit(c("a-b-c-d-e", "qq-bb"), "-") ## 문자열 벡터도 가능
unlist(gregexpr("-", "2021-12-22"))
- grep 함수와의 차이점은 아래와 같음
unlist(grep("-", "2021-12-22"))
- grep 함수에서는 "2021-12-22"를 하나 취급한다
- 마치 length("2021-12-22")는 $1$인것처럼
- 포함여부만 확인 가능하고 문자열의 어느 위치에 존재하는지는 확인 불가능함
unlist(gregexpr("-", c("2021-12-22", "12-22")))
## 첫 번째 문자열에선 5, 8위치에 "-"이 존재하고
## 두 번째 문자열에선 3 위치에 "-" 존재한다
unlist(grep("-", c("2021-12-22", "12-22")))
## 첫 번째 문자열에 "-"이 존재하고
## 두 번째 문자열에 "-"이 존재한다
## 문자열 어느 위치에 "-"이 존재하는지는 알 수 없다
- regexpr 함수(regular expression)도 있는데 이 함수는 처음 위치만 알려준다
unlist(regexpr("-", "2021-12-22"))
## 8번째 인덱스에도 "-"이 존재하지만 처음 "-"이 등장한 위치만 알려준다
gsub("-", ".", "2021-12-22")