本文共 4114 字,大约阅读时间需要 13 分钟。
dplyr是R语言里面处理数据数据非常好使的包,但是最近使用它解决一些问题时遇到了瓶颈,并且搜到的教程都特别基础,所以我打算从源码的角度去找解决方案。
为了理解dplyr::mutate
这个函数,我们需要借助一个实例,分别思考mutate(mtcars)
, mutate(mtcars, gear+carb)
和mutate(mtcars, new=gear+carb)
会在运行的时候的处理流。
在R语言中直接输入函数的名字,就能看到"mutate"的源码
> dplyr::mutatefunction (.data, ...) { UseMethod("mutate")}
这告诉我们,"mutate"其实是一个泛型函数( generic function),因为它使用UseMethod()
调用和数据结构对应的"mutate". 我们可以用”methods“去找给定泛型函数的所有实现方法
PS: 如果你输入的不是数据框或者tbl_df
,那么泛型函数就会报错, 因为找不到对应的
> methods(mutate)[1] mutate.data.frame* mutate.default* mutate.tbl_df*
由于"mtcars"是数据框,那么UseMethod
就会选择mutate.data.frame
作为实际使用的函数。但是你不能通过直接在命令里输这些mutate.data.frame
来查看它的源代码,这是因为*
标注的是不可见函数(nonvisible function),就是不在默认命名空间中的而函数,,你需要用getAnywhere()
找到这些函数,然后使用命名空间限定符来访问。
> getAnywhere(mutate.data.frame)A single object matching ‘mutate.data.frame’ was foundIt was found in the following places registered S3 method for mutate from namespace dplyr namespace:dplyrwith valuefunction (.data, ...) { as.data.frame(mutate(tbl_df(.data), ...))}
这说明对于data.frame类的数据,会先用tbl_df
更改数据结构,重新调用mutate函数。那么对于tbl_df
类,泛型函数就会调用mutate.tbl_df*
> getAnywhere(mutate.tbl_df)A single object matching ‘mutate.tbl_df’ was foundIt was found in the following places registered S3 method for mutate from namespace dplyr namespace:dplyrwith valuefunction (.data, ...) { dots <- named_quos(...) mutate_impl(.data, dots)}
第一个函数named_quos
有两个作用,第一返回quosure类,第二保证quosure类都是由名字的,所以mutate(mtcars, gear+carb)
的新增列的名字就是gear+carb.
第二个函数调用了mutate_impl
,以.data和dots作为输入,这个函数也是不可见的,所以也要用getAnywhere
> getAnywhere(mutate_impl)A single object matching ‘mutate_impl’ was foundIt was found in the following places namespace:dplyrwith valuefunction (df, dots) { .Call(`_dplyr_mutate_impl`, df, dots)}
.Call函数是C/C++代码的交互界面,负责调用_dplyr_mutate_impl
模块,传入的就是数据框和dots对象。
R语言部分的代码到此就结束了,因为后续就是调用C/C++代码编译后的函数。
这部分的代码在GitHub上托管,。虽然我几乎没用C/C++写代码,但还能勉强看代码接着之前_dplyr_mutate_impl
,对应代码如下
// [[Rcpp::export]]SEXP mutate_impl(DataFrame df, QuosureList dots) { if (dots.size() == 0) return df; check_valid_colnames(df); if (is(df)) { return mutate_grouped (df, dots); } else if (is (df)) { return mutate_grouped (df, dots); } else { return mutate_not_grouped(df, dots); }}
SEXP类由Rcpp包提供,让R包能够方便的使用.Call
和C/C++代码交互,这样子就不需要写专门的代码将R语言的数据结构转换成C++数据结构。
然后判断quosure列表的长度,数据框是否存在无效的列名,是否需要分组计算。当长度为0时返回原来的数据框。后面就是具体运算的代码,读起来真的是费劲,但是对于我而言,只需要了解 QuosureList dots 最后是如何被使用的就行。
dots只是存放表达的中间态,随后会经由循环传给NamedQuosure类,后续这些指令传给call_proxy
,而这个类来自于"#include <dplyr/Result/CallProxy.h>"。不能再继续了,因为此恨绵绵无绝期,继续就是Rcpp这个无底洞,放弃吧。
总结一句:理解dplyr包的关键在于,你得知道dplyr包本身不参与的数据处理,它只是生成SQL语言转述给后端的数据库,让数据库完成数据处理部分。换句话说,它对SQL语句的简洁封装,实现前后端分离。
通过读源代码的方式,我理解到掌握mutate
的核心其实学会dplyr编程,学会将你需要执行的表达式传递给dots,你就能自由的使用mutate
甚至是其他所有dplyr系列。
mutate
拓展函数: mutate_if
,mutate_all
,mutate_at
就是学习mutate
的最好案例
这三个函数虽然看起来不同,但是殊途同归,最后都是mutate(.tbl, !(!(!funs)))
,funs
虽然由看似不同的manip_if
, manip_at
, manip_all
构建,但是本质上都是manip_apply_sym
的变体而已。接下来用mutate_all(mtcars, funs(mean)
的执行过程来辅助理解。
首先以.tbl=mtcars
,.funs=funs(mean)
设置函数内局部变量,
然后调用manip_all
,源代码如下
manip_all <- function (.tbl, .funs, .quo, .env, ...) { syms <- syms(tbl_nongroup_vars(.tbl))# 这一步得到目标处理列列名的符号列表 funs <- as_fun_list(.funs, .quo, .env, ...)# 这里的.funs会是funs(./2.54)运算后的fun_list, 而.quo则是funs(./2.54本身,是一个quosure类#.env则表示当前所处环境,最后将所有提供的函数合并成一个fun_list。 manip_apply_syms(funs, syms, .tbl)}
syms以列表形式存储待处理的列名的符号(不是字符串),funs以fun_calls数据结构存放要执行的函数。最后这些参数连同数据框本身传给manip_apply_syms
,返回一个列表,这个列表记录着需要执行的运算。
syms <- syms(tbl_nongroup_vars(mtcars))funlist <- dplyr:::as_fun_list(funs(mean), quo(mean), caller_env())dplyr:::manip_apply_syms(funlist,syms, mtcars)# 运行结果$mpgexpr: ^mean(mpg) env: global$cyl expr: ^mean(cyl) env: global...$carb expr: ^mean(carb) env: global
最后就会出现一个非常神奇的表达形式!(!(!funs))
, 我目前还不知道有什么用。可能是rlange !!!
,用于将列表的内容解压
,所以下面两个表达是等价的
# 表达一mutate_all(mtcars,funs(mean)# 表达二syms <- syms(tbl_nongroup_vars(mtcars))funlist <- dplyr:::as_fun_list(funs(mean), quo(mean), caller_env())rs <- dplyr:::manip_apply_syms(funlist,syms, mtcars)mutate(mtcars,!!!rs)
转载地址:http://zbkul.baihongyu.com/