输入值对于日期格式不够长_R的melt和dcast,兼论宽数据和长数据

处理数据常常会遇到两张数据格式,一种是长数据(long data),另一种是宽数据(wide data)。以常见的股票收盘价数据为例,表1是长数据常见形式,而表2是宽数据常见形式。而R语言作为处理数据的好手,提供了非常多工具来处理这两种数据,以及二者间的转换工具。

接下来首先介绍这两种数据,然后分析二者的优缺点,接着讨论R里面的转换工具melt和dcast函数,最后再对处理方法做一些讨论。

8e4b4a7e5f6644571b2074049bb35788.png

两种数据格式的基本介绍

观察表1的数据格式,可以看到长数据例子中,前两列是日期和股票代码,第三列是某日某只股票的收盘价。这类似于数据库里面的主键(primary key),通过前两列定义一个唯一标志符,第三列是标志符对应的值。当然,可以定义更多列作为标志符(例如加入一列数据字段factor,这里是收盘价),而每一个标志符也可以对应更多列数据(例如加入开盘价等),如表3所示。而如果有新的日期或股票数据进来,新增几行即可。

228c4df1e0d657f150c50560dd25d1a0.png

而宽数据则不一样。在表2中,第一列规定了每一行收盘价的日期,每一列规定了收盘价所属的股票。每一个(二维)宽数据对象只能对应一种数据,如果需要另一种数据,例如开盘价,则需要重新做一个这样的表格,第一行和第一列不变,中间的数据值进行调整即可。而如果有一只新上市的股票,则需要新增一列。

除了上述的不同,二者还有一个非常重要的区别就是对缺失值的处理方式。长数据往往不记录缺失值,而宽数据为了保持数据一致性则需要记录。例如金田实业(000003)这只股票在2002年6月14日退市,之后便没有收盘价。如果我们的数据从2000年开始记录所有股票的收盘价,对于长数据格式,可以不用再记录数据了,而宽数据则必须从退市之后记录空值(NULLNA)。

两种数据格式的优缺点

这两种数据形式是处理数据中最常遇到的,也有着各自的优缺点,基本上一者的优点就是另一者的缺点,因此接下来分类进行讨论。

维护性

这里的维护性主要指的是增减观测值,在实际中也就是数据每日更新、数据回退、新增数据种类等。

对于长数据而言,其主要也最重要的优点是增减观测值非常方便,在表3的例子中也就是增减日期date、股票stock、数据种类factor非常容易。这一点使得常见数据库储存数据都是长数据,因为数据库增加行比增加列容易非常多。例如金融研究领域常用的CSMAR数据库下载的数据,基本上都是长数据。当然前提是主键需要提前定义足够,如果一开始定义的表类型是表1那种的,那么要新增开盘价等数据就会非常麻烦。

而(二维)宽数据在这一点上就不是很方便了。仅仅考虑新增一行数据还比较简单,直接加入即可,而新增一列数据相对比较麻烦。比如新的股票上市,我们不仅要在上市当天填充新上市股票的收盘价,还必须要在之前所有日期填上空值。同时,如果要增加新的数据字段,则必须要放在新的表格里面。对于常见的数据库来说,这一缺点使得宽数据几乎无法使用,因为加一列和加一张表都是比较麻烦的,所以宽数据更多是以文件的形式储存,并且可以选择一只股票或一个交易日或一个数据字段一个文件的方式储存。

占用内存大小

为了分析这两种数据在内存中占用的大小,以A股的股票收盘价进行分析,时间区间为2005-01-01至2019-12-27共计3644个交易日,3872只股票。

dim(close_price)
# [1] 3644 3872

我们先看宽数据在内存里面的大小。如果数据对象是第一列为日期,之后每一列是股票收盘价的数据框(data frame),股票在某一交易日缺失数据则为空,那么在内存里面的大小为108.3MB。

close_price_df <- data.frame("date"=trading_date, close_price, check.names=FALSE)
format(object.size(close_price_df), units="MB")
# [1] "108.3 Mb"

而如果是列名为交易日的矩阵的话,则大小为108.1MB,没有太大的变化。

rownames(close_price) <- trading_date
format(object.size(close_price), units="MB")
# [1] "108.1 Mb"

而对于长数据而言,如果数据对象是如表1所示三列数据框,并且删除没有数据的观测值,则大小为194.7MB,大了接近一倍。

long_price <- melt(close_price_df, "date", variable.name="stock", value.name="value", na.rm=TRUE)
format(object.size(long_price), units="MB")
# [1] "194.7 Mb"

而实际上,有数据的观测值仅占全数据的60%,如果是不删除没有数据的观测值的话,大小为269.6 MB。

sum(!is.na(close_price)) / (3644*3872)
# [1] 0.6014419

long_price <- melt(close_price_df, "date", variable.name="stock", value.name="value")
format(object.size(long_price), units="MB")
# [1] "269.6 Mb"

这个结果是可以预期的,因为长数据需要两列来定义主键,相较宽数据有更多“无效”数据,并且主键那些列往往是字符类型数据,会占用不少内存。

因此在分析较大的数据时,宽数据能够有较好的性能表现,同时储存在硬盘里也会更小。

分析的方便性

在进行数据分析式,结合R的向量化运算,大部分时候宽数据分析方便性几乎是“碾压”长数据的。例如这个数据,我们要计算每只股票的日收益率,只需要一行代码就可以解决

stock_rtn <- close_price[2: nrow(close_price), ] / close_price[1: (nrow(close_price) - 1), ]

而长数据往往需要利用循环,一只股票一只股票的计算,当然也可以用data.table包的BY方法,总体来说比较麻烦。

大部分时候,宽数据都会较长数据更好进行分析。但在进行回归、人工智能模型拟合的时候,还是需要转换成长数据,因为此时需要X和Y的一一对应关系。

总结

宽数据的强一致性带来了分析的便利和性能的提升,但与此同时也导致了维护的不方便,因此在实际中需要进行取舍。

如果使用数据库来存储数据,基本上只能使用长数据格式,而宽数据往往使用文件的形式存储。从读取速度来看,读文件一般是比读数据库快的,并且如果结合data.table包的fread函数,速度会再上一个台阶。

R里面的长宽数据的转换工具

既然这两种数据格式如此常见并且各有优势,因此R里面提供了非常多的工具来进行二者的转换。最出名的莫过于reshape2包的meltdcast函数,而data.table包针对他们的data.table对象同样也提供了相同的函数,速度更快并且更节省内存。接下来分别介绍这两个函数。

melt函数

melt函数是将宽数据转换为长数据的函数,即表2转换成表1,下面对主要参数进行说明,

  • id.vars:选择用来做主键的列,对于表2来说就是date列,当然也可以有多列
  • measure.vars:观测值的列,默认是非id.vars那些列,表2中就是除第一列的其他列
  • variable.name:measure.vars转换成一列后的列名,这里就是“stock”
  • value.name:观测值转换成一列后的列名,这里是“value”
  • na.rm:是否删除NA数据,例如000003退市后没有收盘价了,但宽数据依旧保留了这一列,转换成长数据后是否要删除

我们以mtcars数据来作为例子

car <- data.frame("car"=rownames(mtcars), mtcars)
car_long <- melt(car, id.vars="car", variable.name="measure", value.name="value")

dcast函数

dcast函数是将长数据转换为宽数据,即表1转换为表2,下面对主要参数进行说明:

  • formula:类似于回归的公式,例如date ~ stock就是以date作为第一列,stock作为其他列的列名,当然公式左右两边都可以有多个字段
  • fun.aggregate:将具有相同主键的数据依据某个函数进行计算,例如针对表三的公式为stock ~ factor,那么相同日期的数据数据就会被fun.aggregate参数的函数进行计算。值得注意的是,如果出现“Aggregation function missing: defaulting to length”的警告信息,那么说明主键存在重复数据。
  • value.var:选择哪些列的数据转换

接着上面的例子,把长数据再转换成宽数据

car_wide <- dcast(car_long, car~measure)

一点讨论

从上面可以看出,reshare包提供的这两个函数非常的直观好用,并且速度非常快。如果我们自己要做的话,长数据转宽数据得使用循环和merge的方式,宽数据转长数据得循环和rbind的方式,效率非常低下,因为之前有自己实现过,在这点上深有体会。

实际情况中,往往从数据库首先拿到的是长数据,我们更多会转换成宽数据。但有些时候不一定非得先转换再进行分析,利用aggregateby函数以及data.table对象的BY方法等,同样可以非常快速地对数据进行分析,选用哪种方法完全基于实际情况等判断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值