17.1 S3
클래스
S3 클래스는 R 프로그래밍 언어에서 가장 널리 사용되는 클래스입니다.
R에 미리 정의 된 대부분의 클래스는이 유형입니다. 이것이 간단하고 구현하기 쉽다는 사실이 그 이유입니다.
17.1.1 S3
클래스를 정의하고 S3
개체를 생성하는 방법
S3 클래스에는 사전 정의된 공식적인 정의가 없습니다.
기본적으로 클래스 속성이 일부 클래스 이름으로 설정된 리스트는 S3
객체입니다. 리스트의 구성 요소는 개체의 멤버 변수가 됩니다.
다음은 student
라는 의 S3
개체를 생성하는 간단한 예입니다.
# 원하는 구성요소를 갖는 리스트를 생성합니다.
<- list(name = "John", age = 21, GPA = 3.5)
s
# 클래스 이름을 지정합니다.
class(s) <- "student"
# 이게 다입니다. 이제 "student" 클래스인 개체 s가 생성되었습니다.
s
## $name
## [1] "John"
##
## $age
## [1] 21
##
## $GPA
## [1] 3.5
##
## attr(,"class")
## [1] "student"
이것은 공식적인 클래스 정의가 있고 개체에 속성과 메서드가 적절하게 정의되어 있는 C ++, Python 등을 사용해 본 프로그래머들에게 어색해 보일 수 있습니다.
R의 S3
시스템은 매우 임기응변적입니다. 같은 클래스의 객체가 여러분의 의지에 따라 완전히 다르게 보이는 객체의 클래스로 변환시킬 수 있습니다. 모든 것이 당신에게 달려 있습니다.
17.1.2 생성자를 사용하여 개체를 만드는 방법
클래스와 같은 이름을 갖는 함수(필수 아님)를 사용하여 개체를 만드는 것이 좋습니다.
이것은 개체 생성에 약간의 일관성(uniformity)을 가져와 유사하게 보이게 합니다.
멤버 속성에 대한 무결성 검사(integrity test)를 추가할 수도 있습니다. 여기에 예가 있습니다. 이 예제에서는 개체의 클래스 속성을 설정하기 위해 attr()
함수를 사용하고 있음을 주목하기 바랍니다.
다음은 student
클래스의 개체를 만들 수 있는 student()
함수를 정의하는 것을 보여주고 있습니다.
# "student" 클래스 생성 함수 정의 : student(n, a, g)
<- function(n, a, g) {
student # (데이터 입력 오류 방지를 위한) 무결성 테스트 조건을 추가핳 수 있습니다.
if(g > 4 || g < 0) stop("GPA must be between 0 and 4")
<- list(name = n, age = a, GPA = g)
value
# class() 함수 또는 attr() 함수를 이용하여 클래스가 설정될 수 있습니다.
attr(value, "class") <- "student"
value }
student
클래스의 개체를 만들 수 있는 student()
함수가 만들어 졌습니다.
다음은 이 student()
함수를 사용하여 student
클래스의 s3.s1
개체를 만드는 예를 보여 주고 있습니다.
# student() 함수를 이용하여 student 클래스의 s3.s1 개체를 생성합니다.
<- student("Paul", 26, 3.7)
s3.s1
# s 개체의 요소들과 클래스를 확인합니다.
s3.s1
## $name
## [1] "Paul"
##
## $age
## [1] 26
##
## $GPA
## [1] 3.7
##
## attr(,"class")
## [1] "student"
class(s3.s1)
## [1] "student"
# 새로운 s3.s1 개체를 생성합니다. 이때, 3번째 요소 값이 student() 함수의 무결성 검사에 걸립니다
<- student("Paul", 26, 5) s3.s1
## Error in student("Paul", 26, 5): GPA must be between 0 and 4
# student() 함수를 이용하여 개체를 만들 때 무결성 검사를 통과해야 개체가 생성됩니다.
<- student("Paul", 26, 2.5)
s3.s2
# 만일 무결성 검사와 상관없이 요소의 값을 수정할 수 있습니다.(꼭 그렇게 해야 한다면)
$GPA <- 5 s3.s2
17.1.3 메소드와 일반 함수
위의 예에서는 단순히 개체의 이름 s
를 입력하면 내부 요소들이 출력되었습니다.
대화형 모드에서 개체 이름만 입력하면 print()
함수가 작동되어 출력됩니다.
s3.s1
## $name
## [1] "Paul"
##
## $age
## [1] 26
##
## $GPA
## [1] 3.7
##
## attr(,"class")
## [1] "student"
또한 벡터, 행렬, 데이터 프레임, factor 등과 함께 print()
를 사용할 수 있으며 이들이 속한 클래스에 따라 그 결과는 다르게 인쇄됩니다.
print()
함수는 이처럼 다양한 모양의 개체를 인쇄하는 방법을 어떻게 알까요?
대답은 print()
함수가 일반 함수(generic function)라는 것입니다. 실제로 여러 메서드 모음이 있습니다. 이러한 모든 방법은 method(print)
로 확인할 수 있습니다.
head(methods(print), 10) # 앞의 10개만 확인합니다.
## [1] "print,ANY-method" "print,diagonalMatrix-method"
## [3] "print,sparseMatrix-method" "print.aareg"
## [5] "print.abs.error.pred" "print.acf"
## [7] "print.AES" "print.agnes"
## [9] "print.all_vars" "print.anova"
위 목록에서 print.data.frame
과 print.factor
등과 같은 메소드를 볼 수 있습니다.
데이터 프레임에서 print()
함수를 호출하면 print.data.frame()
함수로 전달됩니다.
factor 로 동일한 작업을 수행했다면 호출은 print.factor()
함수로 전달됩니다. 여기서 메소드 이름이 generic_name.class_name()
형식임을 알 수 있습니다. 이것이 R이 클래스에 따라 어떤 메소드를 호출할지 알아낼 수있는 방법입니다.
"student"
클래스의 개체를 인쇄하면 print.student()
형식의 메소드를 찾지만 이 형식의 메소드는 없습니다.
그렇다면 "student"
클래스의 개체는 어떤 메소드를 호출했을까요?
print.default()
함수를 호출한 것입니다. 이것은 일치하는 메소드를 찾을 수 없을 때 호출되는 대체 메소드입니다. 일반 함수에는 기본 매소드를 가지고 있습니다.
print()
함수와 같은 많은 일반 함수가 있습니다. 이러한 일반 함수들은 method(class = "default")
를 이용하여 모두 나열할 수 있습니다.
head(methods(class="default"), 10) # 앞의 10개만 확인하겠습니다.
## [1] "add1.default" "aggregate.default" "AIC.default"
## [4] "all.equal.default" "ansari.test.default" "anyDuplicated.default"
## [7] "aperm.default" "ar.burg.default" "ar.yw.default"
## [10] "arrange.default"
17.1.4 자신의 메소드를 작성하는 방법
이제 student
클래스를 출력할 수 있는 print.student()
메소들 만들어 보겠습니다.
<- function(obj) {
print.student cat(obj$name, "\n")
cat(obj$age, "years old\n")
cat("GPA:", obj$GPA, "\n")
}
이제 이 print.student()
메서드는 "student"
클래스의 객체를 print()
할 때마다 호출됩니다.
S3
시스템에서 메서드는 개체나 클래스에 속하지 않고 일반 함수에 속합니다. 이것은 개체의 클래스가 설정되어 있는 한 작동합니다.
# 이제 s 개체를 출력해 보겠습니다. 이때 앞에서 정의한 print.student() 메소드가 호출되어 출력합니다.
s3.s1
## Paul
## 26 years old
## GPA: 3.7
# s 개체의 student 클래스 속성을 없앨 수도 있습니다.
unclass(s3.s1)
## $name
## [1] "Paul"
##
## $age
## [1] 26
##
## $GPA
## [1] 3.7
class(s3.s1) # student 클래스가 아닌 list 클래스로 출력이 됩니다.
## [1] "student"
17.1.5 자신 만의 일반 함수 작성
print()
함수 또는 plot()
함수 등과 같은 고유한 일반 함수를 만들 수도 있습니다. 먼저 이러한 기능이 어떻게 구현되는지 살펴 보겠습니다.
print
## function (x, ...)
## UseMethod("print")
## <bytecode: 0x0000000016999388>
## <environment: namespace:base>
plot
## function (x, y, ...)
## UseMethod("plot")
## <bytecode: 0x000000002375d9d8>
## <environment: namespace:base>
UseMethod()
함수에 전달된 일반 함수의 이름으로 단일 호출이 있음을 알 수 있습니다. 이것은 모든 배경이 되는 세부 정보를 처리 하게 될 디스패처(dispatcher) 함수입니다. 이것을 이용한다면 일반 함수를 구현하는 것은 간단합니다.
예를 들어 grade
라는 새로운 일반 함수를 만들어 보겠습니다.
<- function(obj) {
grade UseMethod("grade")
}
일반 함수는 메소드 없이는 쓸모가 없습니다. 따라서 기본 메소드를 구현해 보겠습니다.
<- function(obj) {
grade.default cat("This is a generic function.\n")
}
이 기본 메소드로 s
개체를 출력해 보겠습니다.
grade(s3.s1)
## This is a generic function.
grade()
함수를 이용하여 s
개체를 출력해 보니, grade.default()
메소드가 작동됨을 알 수 있습니다.
이제 "student"
클래스를 위한 grade.student()
메소드를 만들어 보겠습니다.
<- function(obj) {
grade.student cat("Your grade is", obj$GPA, "\n")
}
이제 이 student
클래스의 s
개체를 출력해 보겠습니다. 아까와 달리 grade()
함수의 인수인 s
개체가 student
클래스이기 때문에 grade.student()
메소드가 적용되어 출력됨을 알 수 있습니다.
grade(s3.s1)
## Your grade is 3.7
이런 식으로 우리는 grade()
이라는 일반 함수를 구현하고 이를 우리가 만든 클래스를 위한 메소드로 구현해 보았습니다.