您的位置:首页 > 其它

R语言S4类应用的一个简单例子

2017-09-29 00:06 459 查看
原文见此处。

做了翻译的尝试:

最近我与来自WLOG Solutions的朋友讨论了银行现金流管理引擎在R中的实现问题。实现代码漂亮地展示了S4类的使用,所以我想这应该是S4类的一个很好的例子。

定义问题

每一家商业银行都需要为其顾客提供现金获取入口,例如旗下数以百计的现金获取点(ATM自助取款机)或者分行点。银行管理者面临的这个问题需要解决三个冲突的目标:(1)他们必须确保在现金获取点有足够的现金以保证流动性;(2)他们需要最小化冻结资金的数量,因为它们对银行的经营不起作用;(3)他们需要最小化从金库到现金获取点的运输成本。这是一个复杂的优化问题,尤其是,它涉及到了,需要通过使用历史数据预测每天在现金获取点的现金余额。

当要设计一个求解方案来为多个现金获取点提供预测值时,萌发了使用面向对象方法的一个典型应用例子。对于每一个现金获取点,我们有可供利用的具有相同结构的历史数据,并能基于此获得余额预测值。然而,在不同的现金获取点,顾客对现金有不同的使用特征,这将需要应用不同的统计预测模型。例如,在ATM现金获取点,顾客可以只取现,但在分行点,顾客也可以存款。因此,我们使用S4类来建模。

实现方案

定义一个具有CashPoints 属性(现金获取点属性,其属性值是许多的不同的现金获取点)的Bank类。再定义一个CashPoint 类,为虚类(不能被实例化),再接着定义的ATM 类和Branch类将继承自CashPoint类。 下图展示了类之间的结构。每一个CashPoint类的实例将有“历史余额(balances)”属性(该实例还有关于现金获取点的id属性,为字符类型,“历史余额”属性为数据框类型),还有一个能提供预测值的givePrediction方法。该givePrediction方法基于不同的ATM 类和Branch类有不同的实现。



下文给出了实现的代码。首先使用setGeneric() 函数来创建一个泛函接口givePrediction(),泛函givePrediction()将会基于不同的对象类型来调度合适的方法(多态)。接着我们使用setClass() 函数创建了S4类型的Bank类、ATM类、Branch类,并为这些类各自创建givePrediction方法。在我们的例子中,在ATM类中使用线性回归模型,在Branch类中简单地使用均值来进行余额预测。注意到我们也为CashPoint类定义了givePrediction方法,如果没有被其子类的合适方法(givePrediction方法)覆盖的话,CashPoint类的givePrediction方法将会被调用(stop(“no givePrediction method for this class”),无法为CashPoint 类创建实例,因为其是一个虚类)。

通过new一个Bank类实例并调用givePrediction() 函数,代码得以运行。Bank 类构造器从bank_model.txt文档读取银行关于现金获取点的结构数据,bank_model.txt文档包括两列,一列是现金获取点的ID,另一列是现金获取点的类型(ATM或Branch分行点)。接着调用setClass() 函数创建CashPoint类。每一个CashPoint类实例都将通过读取branch_balances_data.txt文档的方式被初始化,branch_balances_data.txt文档有三个列:BranchId(现金获取点的ID),Date(日期), Balance(余额)。初始化CashPoint类实例将通过BranchId列来subset合适的子集。实例化的CashPoint对象的balances字段存放了与该CashPoint实例对象对应的历史数据(即Date(日期), Balance(余额))。Bank 类被实例化之后,givePrediction()将被调度并作用在Bank 类实例。在此过程,根据程序运行时现金获取点的类型(ATM或Branch分行点), 将通过S4类系统自动调用ATM or Branch类的 givePrediction()方法。

代码贴上:

#定义givePrediction泛函接口
setGeneric("givePrediction", function(object) {
standardGeneric("givePrediction")
})

#定义Bank类,具有cashPoints属性,为list数据类型
setClass("Bank", representation(cashPoints = "list"))

#定义Bank类的初始化方法initialize,读取bank_model.txt文档并为cashPoints属性赋值,注意new()函数的应用,例如,new("ATM","CashPoint_1"),意味着new一个ATM实例,第二个参数"CashPoint_1"为下文定义的CashPoint类的initialize方法的第二个参数的取值
setMethod("initialize", "Bank", function(.Object){
BankModel <- read.table(file = "bank_model.txt",
sep = ";", header = TRUE, stringsAsFactors = FALSE)
.Object@cashPoints <- apply(BankModel, 1, function(cp) {
new(cp[2], cp[1])
})
names(.Object@cashPoints) <- apply(BankModel, 1,
paste, collapse = "_")
return(.Object)
})

#定义Bank类的givePrediction方法,sapply函数的第二个参数值"givePrediction"将根据第一个参数cashPoints取值,调度合适的givePrediction方法(即多态)
setMethod("givePrediction", "Bank", function(object){
return(sapply(object@cashPoints, "givePrediction"))
})

#定义CashPoint类,虚类,具有id和balances属性,数据类型分别为字符和数据框
setClass("CashPoint", representation(id = "character",
balances = "data.frame", "VIRTUAL"))

#定义CashPoint类的initialize方法,应注意第二个参数cashPointId是如何取值的
setMethod("initialize", "CashPoint", function(.Object, cashPointId){
.Object@id <-cashPointId
balances <- read.table(file = "branch_balances_data.txt",
sep = ";", header = TRUE)
.Object@balances <- subset(balances,
balances$BranchId == .Object@id)
.Object@balances$Date <- as.Date(.Object@balances$Date)
return(.Object)
})

#定义CashPoint类的givePrediction方法
setMethod("givePrediction", "CashPoint", function(object){
stop("no givePrediction method for this class")
})

#定义Branch类,继承自CashPoint类,从而也具有id和balances属性,数据类型分别为字符和数据框;也具有givePrediction方法
setClass("Branch", contains = "CashPoint")

#改写Branch类继承自CashPoint类的givePrediction方法,求均值以预测余额
setMethod("givePrediction", "Branch", function(object){
return(mean(object@balances$Balance))
})

#定义ATM类,继承自CashPoint类,从而也具有id和balances属性,数据类型分别为字符和数据框;也具有givePrediction方法
setClass("ATM", contains = "CashPoint")

#改写ATM类继承自CashPoint类的givePrediction方法,用到了线性回归模型以预测余额
setMethod("givePrediction", "ATM", function(object) {
LM <- lm(Balance ~ as.numeric(Date), data = object@balances)
prediction <- predict(LM,
data.frame(Date = 1 + max(object@balances$Date)))
return(unname(prediction))
})

#测试
givePrediction(new("Bank"))


bank_model.txt

CashPointId;CashPointType

CashPoint_1;ATM

CashPoint_2;Branch

CashPoint_3;ATM

CashPoint_4;Branch

CashPoint_5;ATM

branch_balances_data.txt

BranchId;Date;Balance

CashPoint_1;2012-12-01;423000

CashPoint_1;2012-12-02;312000

CashPoint_1;2012-12-03;220000

CashPoint_1;2012-12-04;123000

CashPoint_2;2012-12-01;223000

CashPoint_2;2012-12-02;212000

CashPoint_2;2012-12-03;320000

CashPoint_2;2012-12-04;223000

CashPoint_3;2012-12-01;323000

CashPoint_3;2012-12-02;312000

CashPoint_3;2012-12-03;270000

CashPoint_3;2012-12-04;223000

CashPoint_4;2012-12-01;323000

CashPoint_4;2012-12-02;412000

CashPoint_4;2012-12-03;320000

CashPoint_4;2012-12-04;373000

CashPoint_5;2012-12-01;223000

CashPoint_5;2012-12-02;192000

CashPoint_5;2012-12-03;150000

CashPoint_5;2012-12-04;133000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: