R语言基础:S3和S4 class

用R进行数据处理时,经常出现S4 class的数据类型,或在软件包的帮助文件中描述为S4 class,那么什么是S3和S4 class呢?

参考资源:R语言S3类的理解与构建 - ywliao - 博客园

                  R语言基础教程——第7章:面向对象编程(S4类)

一些程序员认为S3类不具有面向对象编程固有的安全性。例如,你可以任意修改S3类,哪怕是不合法的修改。相比而言,S4类更加安全。

想要更好理解S3和S4 class,最好先明白什么是“面向对象”,尝试了一下,然后就放弃了。。。。。。。

明面意思,class就是类的意思,但是“类”这个概念比较抽象,既包含类型type(S3类、S4类)又包含类名name(“类”的命名)。首先通过S3 class引入“类”的概念

S3 class

S3类数据创建简单,list命令即可创建,且可进行编辑

##创建person,包含name,age,gender等三种属性
> person<-list(name="xiao ming", age=16, gender="M")
> person
$name
[1] "xiao ming"
$age
[1] 16
$gender
[1] "M"

##此时person的类名仅是“list”,也就是说仅属于“list”类
> class(person)
[1] "list"

通过append命令添加类名

##append命令添加类名“people”
> class(person) <- append(class(person), "people")
> person
$name
[1] "xiao ming"
$age
[1] 16
$gender
[1] "M"
attr(,"class")
[1] "list"   "people"

##此时person同时属于“list”和“people”类
> class(person)
[1] "list"   "people"

##数据类型是S3 class
> otype(person)
[1] "S3"

S3类属性访问(数据访问):S3和S4的一大区别就是S3通过$访问不同属性,而S4通过@访问

> person$weight
[1] "75kg"
> person$height
[1] 180

S4 class

S4类的创建

setClass(Class, representation, prototype, contains=character(),
          validity, access, where, version, sealed, package,
          S3methods = FALSE, slots)

Class: 定义类名
slots: 定义属性和属性类型
prototype: 定义属性的默认值
contains=character(): 定义父类,继承关系
validity: 定义属性的类型检查
where: 定义存储空间
sealed: 如果设置TRUE,则同名类不能被再次定义
package: 定义所属的包

创建一个S4对象实例(ps个人理解:对象等同于“类”,实例等同“变量”,就是将类与变量联系起来)

##pryr包作为辅助工具
install.packages("pryr")
library(pryr)

##定义一个S4对象,这个Person类中同时包含name和age两种属性
> setClass("Person",slots=list(name="character",age="numeric"))

##实例化为一个Person对象
##new,Generate an Object from a Class
> father<-new("Person",name="F",age=44)
> father
An object of class "Person"
Slot "name":
[1] "F"
Slot "age":
[1] 44

##类名
> class(father)
[1] "Person"
attr(,"package")
[1] ".GlobalEnv"
##S4类
> otype(father)
[1] "S4"

访问对象的属性

> setClass("Person",slots=list(name="character",age="numeric"))
> a<-new("Person",name="a")
##访问S4对象的属性
> a@name
[1] "a"
> slot(a, "name")
[1] "a"
> # 错误的属性访问
> a$name
> a[1]
> a[1]

创建一个有继承关系的S4对象

##创建一个S4对象Person,包含name和age两种属性
> setClass("Person",slots=list(name="character",age="numeric"))
##创建Person的子类Son,这个子类中同时包含father,mother,name和age四种属性
> setClass("Son",slots=list(father="Person",mother="Person"),contains="Person")

##实例化Person对象(father,mother和son)
> father<-new("Person",name="F",age=44)
> father
An object of class "Person"
Slot "name":
[1] "F"
Slot "age":
[1] 44

> mother<-new("Person",name="M",age=39)
> mother
An object of class "Person"
Slot "name":
[1] "M"
Slot "age":
[1] 39

##实例化一个Son对象,son中同时包含father,mother,name,age四种属性
> son<-new("Son",name="S",age=16,father=father,mother=mother)
> son
An object of class "Son"
Slot "father":
An object of class "Person"
Slot "name":
[1] "F"
Slot "age":
[1] 44

Slot "mother":
An object of class "Person"
Slot "name":
[1] "M"
Slot "age":
[1] 39

Slot "name":
[1] "S"
Slot "age":
[1] 16
##son属于Son类
> class(son)
[1] "Son"
attr(,"package")
[1] ".GlobalEnv"

查看对象属性

> # 查看son对象的name属性
> son@name
[1] "S"

> # 查看son对象的age属性
> son@age
[1] 16

> # 查看son对象的father属性
> son@father
An object of class "Person"
Slot "name":
[1] "F"
Slot "age":
[1] 44

> ##可进一步查看son@father中的name和age属性
> son@father@name
[1] "F"

##查看son的属性类型,S4类
> otype(son)
[1] "S4"

##检查son@name属性类型
> otype(son@name)
[1] "base"

##检查son@father属性类型,father为S4类
> otype(son@father)
[1] "S4"

##用isS4(),检查S4对象的类型
> isS4(son)
[1] TRUE
> isS4(son@name)
[1] FALSE
> isS4(son@mother)
[1] TRUE

son的数据结构

S4对象的默认值

##创建S4对象
> setClass("Person",slots=list(name="character",age="numeric"))
##属性age为空
> a<-new("Person",name="a")
> a
An object of class "Person"
Slot "name":
[1] "a"

Slot "age":
numeric(0)   ##无age属性

##设置属性age的默认值20
> setClass("Person",slots=list(name="character",age="numeric"),prototype = list(age = 20))
##属性age为空
> b<-new("Person",name="b")
> b
An object of class "Person"
Slot "name":
[1] "b"

Slot "age":
[1] 20      ##默认值20

S4对象的类型检查,检查是否符合类的设定

> setClass("Person",slots=list(name="character",age="numeric"))
##传入错误的age类型
> bad<-new("Person",name="bad",age="abc")
Error in validObject(.Object) : 
  invalid class “Person” object: invalid object for slot "age" in class "Person": got class "character", should be or extend class "numeric"
##设置age的非负检查
> setValidity("Person",function(object) {
+        if (object@age <= 0) stop("Age is negative.")
+    })
Class "Person" [in ".GlobalEnv"]

Slots:
                          
Name:       name       age
Class: character   numeric
##传入小于0的年龄
> bad2<-new("Person",name="bad",age=-1)
 Error in validityMethod(object) : Age is negative. 

从一个已经实例化的对象中创建新对象

S4对象,还支持从一个已经实例化的对象中创建新对象,创建时可以覆盖旧对象的值

> setClass("Person",slots=list(name="character",age="numeric"))
##创建一个对象实例n1
> n1<-new("Person",name="n1",age=19);n1
An object of class "Person"
Slot "name":
[1] "n1"
Slot "age":
[1] 19

##从实例n1中,创建实例n2,并修改name的属性值
> n2<-initialize(n1,name="n2")
> n2
An object of class "Person"
Slot "name":
[1] "n2"
Slot "age":
[1] 19

##从实例n1中,创建实例n3,并修改age的属性值
> n3<-initialize(n1,age=99)
> n3
An object of class "Person"
Slot "name":
[1] "n1"
Slot "age":
[1] 99

S4的泛型函数

S4的泛型函数实现有别于S3的实现,S4分离了方法的定义和实现,如在其他语言中我们常说的接口和实现分离。通过setGeneric()来定义接口,通过setMethod()来定义现实类。这样可以让S4对象系统,更符合面向对象的特征。

通过S4对象系统,把原来的函数定义和调用2步,为成了4步进行:

(1)定义数据对象类型

(2)定义接口函数

(3)定义实现函数

(4)把数据对象以参数传入到接口函数,执行实现函数

通过S4对象系统,是一个结构化的,完整的面向对象实现。

##普通函数的定义和调用
work<-function(x) cat(x, "is working")
work('Conan')

##定义Person对象
setClass("Person",slots=list(name="character",age="numeric"))
##定义泛型函数work,即接口
setGeneric("work",function(object) standardGeneric("work"))
##定义work的现实,并指定参数类型为Person对象
setMethod("work", signature(object = "Person"), function(object) cat(object@name , "is working") )

##创建一个Person对象a
a<-new("Person",name="Conan",age=16)
##把对象a传入work函数
work(a)
Conan is working

查看S4对象的函数:当我们使用S4对象进行面向对象封装后,我们还需要能查看到S4对象的定义和函数定义。

# 检查work的类型
 ftype(work)
# 直接查看work函数
work
# 查看work函数的现实定义
 showMethods(work)
# 查看Person对象的work函数现实
getMethod("work", "Person")
selectMethod("work", "Person")
# 检查Person对象有没有work函数
 existsMethod("work", "Person")
 hasMethod("work", "Person")

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个比较典型的 MapReduce 问题。我们需要利用 MapReduce 框架对每个学生的成绩进行处理,最终得到每个学生的平均绩点 GPA。 首先,我们需要定义一个自定义的数据型来存储学生信息。对于每个学生,我们需要记录他的学号、课程号、成绩分数以及该课程的学分。因此,我们可以定义一个 Student: ```java public class Student implements Writable { private String studentId; // 学号 private String courseId; // 课程号 private int score; // 成绩分数 private int credit; // 学分 // 省略构造函数、getter 和 setter 方法 @Override public void write(DataOutput out) throws IOException { out.writeUTF(studentId); out.writeUTF(courseId); out.writeInt(score); out.writeInt(credit); } @Override public void readFields(DataInput in) throws IOException { studentId = in.readUTF(); courseId = in.readUTF(); score = in.readInt(); credit = in.readInt(); } // 计算绩点 GPA public double calculateGPA() { double gpa = 0.0; if (score >= 90) { gpa = 4.0; } else if (score >= 85) { gpa = 3.7; } else if (score >= 82) { gpa = 3.3; } else if (score >= 78) { gpa = 3.0; } else if (score >= 75) { gpa = 2.7; } else if (score >= 72) { gpa = 2.3; } else if (score >= 68) { gpa = 2.0; } else if (score >= 64) { gpa = 1.5; } else if (score >= 60) { gpa = 1.0; } return gpa; } } ``` 接下来,我们需要实现 Map 和 Reduce 函数。在 Map 函数中,我们将输入的每一行解析成一个 Student 对象,并以学生学号为 key,以该学生的成绩信息为 value 进行输出。在 Reduce 函数中,我们将计算每个学生的平均绩点 GPA,并以学生学号为 key,以学生的平均绩点 GPA 为 value 进行输出。 ```java public class ScoreAverage { public static class ScoreMap extends Mapper<Object, Text, Text, Student> { private Text studentId = new Text(); private Student student = new Student(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] fields = value.toString().split(":"); studentId.set(fields[0]); student.setStudentId(fields[0]); student.setCourseId(fields[1]); student.setScore(Integer.parseInt(fields[2])); // 根据课程号设置学分 if (fields[1].startsWith("C1")) { student.setCredit(4); } else if (fields[1].startsWith("C2")) { student.setCredit(3); } else if (fields[1].startsWith("C3")) { student.setCredit(2); } else { student.setCredit(1); } context.write(studentId, student); } } public static class ScoreReduce extends Reducer<Text, Student, Text, DoubleWritable> { private DoubleWritable result = new DoubleWritable(); public void reduce(Text key, Iterable<Student> values, Context context) throws IOException, InterruptedException { int totalCredit = 0; double totalGPA = 0.0; // 遍历该学生的所有成绩信息,计算绩点 GPA for (Student student : values) { totalCredit += student.getCredit(); totalGPA += student.calculateGPA() * student.getCredit(); } // 计算平均绩点 GPA double averageGPA = totalGPA / totalCredit; result.set(averageGPA); context.write(key, result); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "ScoreAverage"); job.setJarByClass(ScoreAverage.class); job.setMapperClass(ScoreMap.class); job.setReducerClass(ScoreReduce.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Student.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(DoubleWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } ``` 最后,我们需要将上述代码打包成一个 jar 文件,并在 Hadoop 上运行该程序,得到每个学生的平均绩点 GPA。 运行命令: ``` $HADOOP_HOME/bin/hadoop jar ScoreAverage.jar ScoreAverage input output ``` 其中,ScoreAverage 是打包后的 jar 文件名,input 是输入文件路径,output 是输出文件路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值