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")