• 52.2.1 evalCpp() 转换单一计算表达式
  • 52.2.2 cppFunction() 转换简单的C++函数—Fibnacci例子
  • 52.2.3 sourceCpp() 转换C++程序—正负交替迭代例子
  • 52.2.4 sourceCpp() 转换C++源文件中的程序—正负交替迭代例子
  • 52.2.5 sourceCpp() 转换C++源程序文件—卷积例子
  • 52.2.6 随机数例子
  • 52.2.7 bootstrap例子
  • 52.2.8 在Rmd文件中使用C++源程序文件
  • 53 R与C++的类型转换
  • 53.1 wrap() 把C++变量返回到R中
  • 53.2 as() 函数把R变量转换为C++类型
  • 53.3 as() wrap() 的隐含调用
  • 54 Rcpp 属性
  • 54.1 Rcpp属性介绍
  • 54.2 在C++源程序中指定要导出的C++函数
  • 54.2.1 特殊注释 //[[Rcpp::export]]
  • 54.2.2 修改导出的函数名
  • 54.2.3 可导出的函数
  • 54.3 在R中编译链接C++代码
  • 54.3.1 sourceCpp() 函数中直接包含C++源程序字符串
  • 54.3.2 cppFunction() 函数中直接包含C++函数源程序字符串
  • 54.3.3 evalCpp() 函数中直接包含C++源程序表达式字符串
  • 54.3.4 depends 指定要链接的库
  • 54.4 Rcpp属性的其它功能
  • 54.4.1 自变量有缺省值的函数
  • 54.4.2 异常传递
  • 54.4.3 允许用户中断
  • 54.4.4 把R代码写在C++源文件中
  • 54.4.5 invisible 要求函数结果不自动显示
  • 54.4.6 在C++中调用R的随机数发生器
  • 55 Rcpp提供的C++数据类型
  • 55.1 RObject类
  • 55.2 IntegerVector类
  • 55.2.1 IntegerVector示例1:返回完全数
  • 55.2.2 IntegerVector示例2:输入整数向量
  • 55.3 NumericVector类
  • 55.3.1 示例1:计算元素 \(p\) 次方的和
  • 55.3.2 示例2: clone 函数
  • 55.3.3 示例3:向量子集
  • 55.4 NumericMatrix类
  • 55.4.1 示例1:计算矩阵各列模的最大值
  • 55.4.2 示例2:把输入矩阵制作副本计算元素平方根
  • 55.4.3 示例3:访问列子集
  • 55.5 Rcpp的其它向量类
  • 55.5.1 Rcpp的LogicalVector类
  • 55.5.2 Rcpp的CharacterVector类型
  • 55.6 Rcpp提供的其它数据类型
  • 55.6.1 Named类型
  • 55.6.2 List类型
  • 55.6.3 Rcpp的DataFrame类
  • 55.6.4 Rcpp的Function类
  • 55.6.5 Rcpp的Environment类
  • 56 Rcpp糖
  • 56.1 简单示例
  • 56.2 向量化的运算符
  • 56.2.1 向量化的四则运算
  • 56.2.2 向量化的赋值运算
  • 56.2.3 向量化的二元逻辑运算
  • 56.2.4 向量化的一元运算符
  • 56.3 用Rcpp访问数学函数
  • 56.4 用Rcpp访问统计分布类函数
  • 56.5 在Rcpp中产生随机数
  • 56.6 返回单一逻辑值的函数
  • 56.7 返回糖表达式的函数
  • 56.7.1 is_na
  • 56.7.2 seq_along
  • 56.7.3 seq_len
  • 56.7.4 pmin pmax
  • 56.7.5 ifelse
  • 56.7.6 sapply lapply
  • 56.7.7 sign
  • 56.7.8 diff
  • 56.8 R与Rcpp不同语法示例
  • 56.9 用RcppArmadillo执行矩阵运算
  • 56.9.1 生成多元正态分布随机数
  • 56.9.2 快速计算线性回归
  • 57 用Rcpp帮助制作R扩展包
  • 57.1 不用扩展包共享C++代码的方法
  • 57.2 生成扩展包
  • 57.2.1 利用已有基于Rcpp属性的源程序制作扩展包
  • 57.2.2 DESCRIPTION文件
  • 57.2.3 NAMESPACE文件
  • 57.3 重新编译
  • 57.4 建立C++用的接口界面
  • XI 其它
  • 58 R编程例子
  • 58.1 R语言
  • 58.1.1 用向量作逆变换
  • 58.1.2 斐波那契数列计算
  • 58.1.3 穷举所有排列
  • 58.1.4 可重复分组方式穷举
  • 58.1.5 升降连计数
  • 58.1.6 高斯八皇后问题
  • 58.1.7 最小能量路径
  • 58.2 概率
  • 58.2.1 智者千虑必有一失
  • 58.2.2 圆桌夫妇座位问题
  • 58.3 科学计算
  • 58.3.1 城市间最短路径
  • 58.3.2 Daubechies小波函数计算
  • 58.3.3 房间加热温度变化
  • 58.4 统计计算
  • 58.4.1 线性回归实例
  • 58.4.2 核回归与核密度估计
  • 58.4.3 二维随机模拟积分
  • 58.4.4 潜周期估计
  • 58.4.5 ARMA(1,1)模型估计
  • 58.4.6 VAR模型平稳性
  • 58.4.7 贮存可靠性评估
  • 58.5 数据处理
  • 58.5.1 小题分题型分数汇总
  • 58.5.2 类别编号重排
  • 58.6 文本处理
  • 58.6.1 用R语言下载处理《红楼梦》htm文件
  • 59 使用经验
  • 59.1 文件管理
  • 59.1.1 工作空间
  • 59.2 程序格式
  • A R Markdown文件格式
  • A.1 R Markdown文件
  • A.2 R Markdown文件的编译
  • A.2.1 编译的实际过程
  • A.3 在R Markdown文件中插入R代码
  • A.4 输出表格
  • A.5 利用R程序插图
  • A.6 冗余输出控制
  • A.7 代码段选项
  • A.7.1 代码和文本输出结果格式
  • A.7.2 图形选项
  • A.7.3 缓存(cache)选项
  • A.8 章节目录链接问题
  • A.9 其它编程语言引擎
  • A.10 交互内容
  • A.11 属性设置
  • A.11.1 YAML元数据
  • A.11.2 输出格式
  • A.11.3 输出格式设置
  • A.11.4 目录设置
  • A.11.5 章节自动编号
  • A.11.6 Word输出章节自动编号及模板功能
  • A.11.7 HTML特有输出格式设置
  • A.11.8 关于数学公式支持的设置
  • A.11.9 输出设置文件
  • A.12 LaTeX和PDF输出
  • A.12.1 TinyTex的安装使用
  • A.12.2 Rmd中Latex设置
  • A.13 生成期刊文章
  • A.14 附录:经验与问题
  • A.14.1 Word模板制作
  • A.14.2 数学公式设置补充
  • B 用bookdown制作图书
  • B.1 介绍
  • B.2 一本书的设置
  • B.3 章节结构
  • B.4 书的编译
  • B.5 交叉引用
  • B.6 数学公式和公式编号
  • B.7 定理类编号
  • B.8 文献引用
  • B.9 插图
  • B.10 表格
  • B.10.1 Markdown表格
  • B.10.2 kable() 函数制作表格
  • B.10.3 R中其它制作表格的包
  • B.11 数学公式的设置
  • B.12 使用经验
  • B.12.1 学位论文
  • B.12.2 LaTeX
  • B.12.3 算法
  • B.12.4 中文乱码
  • B.12.5 图片格式
  • B.12.6 其它经验
  • B.13 bookdown的一些使用问题
  • C 用R Markdown制作简易网站
  • C.1 介绍
  • C.2 简易网站制作
  • C.2.1 网站结构
  • C.2.2 编译
  • C.2.3 内容文件
  • C.2.4 网站设置
  • C.3 用blogdown制作网站
  • C.3.1 生成新网站的框架
  • C.3.2 网页内容文件及其设置
  • C.3.3 初学者的工作流程
  • C.3.4 网站设置文件
  • C.3.5 静态文件
  • D 制作幻灯片
  • D.1 介绍
  • D.2 Slidy幻灯片
  • D.2.1 文件格式
  • D.2.2 幻灯片编译
  • D.2.3 播放控制
  • D.2.4 生成单页HTML
  • D.2.5 数学公式处理与输出设置文件
  • D.2.6 其它选项
  • D.2.7 slidy幻灯片激光笔失效问题的修改
  • D.3 MS PowerPoint幻灯片
  • D.4 Bearmer幻灯片格式
  • D.5 R Presentation格式
  • References
  • 编著:李东风
  • 数据集经常需要选行子集、选列子集、排序、定义新变量、横向合并、长宽转换等操作, 而且经常会用若干个连续的操作分步处理, R的管道运算符 |> 和magrittr包的 %>% 特别适用于这种分步处理。

    dplyr包和tidyr包定义了一系列“动词”, 可以用比较自然的方式进行数据整理。 较复杂的分组操作还可以利用purrr包的 map 类函数。

    为了使用这些功能,可以载入 tidyverse 包, 则magrittr包,readr包,dplyr包和tidyr包都会被自动载入:

    下面的例子中用如下的一个班的学生数据作为例子, 保存在如下 data/class.csv 文件中:

    name,sex,age,height,weight
    Alice,F,13,56.5,84
    Becka,F,13,65.3,98
    Gail,F,14,64.3,90
    Karen,F,12,56.3,77
    Kathy,F,12,59.8,84.5
    Mary,F,15,66.5,112
    Sandy,F,11,51.3,50.5
    Sharon,F,15,62.5,112.5
    Tammy,F,14,62.8,102.5
    Alfred,M,14,69,112.5
    Duke,M,14,63.5,102.5
    Guido,M,15,67,133
    James,M,12,57.3,83
    Jeffrey,M,13,62.5,84
    John,M,12,59,99.5
    Philip,M,16,72,150
    Robert,M,12,64.8,128
    Thomas,M,11,57.5,85
    William,M,15,66.5,112

    读入为tibble:

    d.class <- read_csv(
      "data/class.csv", 
      col_types=cols(
      .default = col_double(),
      name=col_character(),
      sex=col_factor(levels=c("M", "F"))
    

    这个数据框有19个观测, 有如下5个变量:

  • height
  • weight
  • 另一个例子数据集是R的NHANES扩展包提供的NHANES, 这是一个规模更大的示例数据框, 可以看作是美国扣除住院病人以外的人群的一个随机样本, 有10000个观测,有76个变量, 主题是个人的健康与营养方面的信息。 仅作为教学使用而不足以作为严谨的科研用数据。 原始数据的情况详见http://www.cdc.gov/nchs/nhanes.htm。 载入NHANES数据框:

    ## [1] 10000    76
    ##  [1] "ID"               "SurveyYr"         "Gender"           "Age"             
    ##  [5] "AgeDecade"        "AgeMonths"        "Race1"            "Race3"           
    ##  [9] "Education"        "MaritalStatus"    "HHIncome"         "HHIncomeMid"     
    ## [13] "Poverty"          "HomeRooms"        "HomeOwn"          "Work"            
    ## [17] "Weight"           "Length"           "HeadCirc"         "Height"          
    ## [21] "BMI"              "BMICatUnder20yrs" "BMI_WHO"          "Pulse"           
    ## [25] "BPSysAve"         "BPDiaAve"         "BPSys1"           "BPDia1"          
    ## [29] "BPSys2"           "BPDia2"           "BPSys3"           "BPDia3"          
    ## [33] "Testosterone"     "DirectChol"       "TotChol"          "UrineVol1"       
    ## [37] "UrineFlow1"       "UrineVol2"        "UrineFlow2"       "Diabetes"        
    ## [41] "DiabetesAge"      "HealthGen"        "DaysPhysHlthBad"  "DaysMentHlthBad" 
    ## [45] "LittleInterest"   "Depressed"        "nPregnancies"     "nBabies"         
    ## [49] "Age1stBaby"       "SleepHrsNight"    "SleepTrouble"     "PhysActive"      
    ## [53] "PhysActiveDays"   "TVHrsDay"         "CompHrsDay"       "TVHrsDayChild"   
    ## [57] "CompHrsDayChild"  "Alcohol12PlusYr"  "AlcoholDay"       "AlcoholYear"     
    ## [61] "SmokeNow"         "Smoke100"         "Smoke100n"        "SmokeAge"        
    ## [65] "Marijuana"        "AgeFirstMarij"    "RegularMarij"     "AgeRegMarij"     
    ## [69] "HardDrugs"        "SexEver"          "SexAge"           "SexNumPartnLife" 
    ## [73] "SexNumPartYear"   "SameSex"          "SexOrientation"   "PregnantNow"

    变量ID是受试者编号, SurveyYr是调查年份, 同一受试者可能在多个调查年份中有数据。 变量中包括性别、年龄、种族、收入等人口学数据, 包括体重、身高、脉搏、血压等基本体检数据, 以及是否糖尿病、是否抑郁、是否怀孕、已生产子女数等更详细的健康数据, 运动习惯、饮酒、性生活等行为方面的数据。 这个教学用数据集最初的使用者是Cashmere高中的Michelle Dalrymple 和新西兰奥克兰大学的Chris Wild。

    23.2 查看数据框一般信息

    对一个数据框(包括tibble)d, 可以用dim(d)获得行、列数, 用names(d)获得各列的变量名, 用str(d)显示类型、大小、各列变量名以及类型、前几个值, 以及其他属性。 dplyr::glimpse(d)则可以显示数据框的大小、各列变量名、类型等,如:

    ## Rows: 19
    ## Columns: 5
    ## $ name   <chr> "Alice", "Becka", "Gail", "Karen", "Kathy", "Mary", "Sandy", "S…
    ## $ sex    <fct> F, F, F, F, F, F, F, F, F, M, M, M, M, M, M, M, M, M, M
    ## $ age    <dbl> 13, 13, 14, 12, 12, 15, 11, 15, 14, 14, 14, 15, 12, 13, 12, 16,…
    ## $ height <dbl> 56.5, 65.3, 64.3, 56.3, 59.8, 66.5, 51.3, 62.5, 62.8, 69.0, 63.…
    ## $ weight <dbl> 84.0, 98.0, 90.0, 77.0, 84.5, 112.0, 50.5, 112.5, 102.5, 112.5,…

    23.3filter()选择行子集

    数据框的任何行子集仍为数据框,即使只有一行而且都是数值也是如此。 行子集可以用行下标选取, 如d.class[8:12,]。 函数head()取出数据框的前面若干行, tail()取出数据框的最后若干行。

    dplyr包的filter()函数可以按条件选出符合条件的行组成的子集。 下例从d.class中选出年龄在13岁和13岁以下的女生:

    height weight

    23.4 按行序号选择行子集

    基本R的utils包的函数head(x, n)可以用来选择数据框x前面n行, tail(x, n)可以用来选择数据框x后面n行,如:

    height weight
  • slice_min(x, n=1)提取x值最小的n行;
  • slice_max(x, n=1)提取x值最大的n行;
  • slice_sample(n=5)随机无放回抽取n行, 也可以用sample_n(size = n)函数。
  • 这些函数也可以用prop=0.1取代n=5这样的写法, 输入一个要提取的行数的比例。

    23.5sample_n()对观测随机抽样

    dplyr包的sample_n(tbl, size)函数可以从数据集tbl中随机无放回抽取size行,如:

    height weight

    R的字符串函数(如paste())和正则表达式函数可以用来生成变量名子集, 然后在select中配合all_of()使用。 all_of()要求指定的所有变量名都是数据框中存在的; 如果指定的变量有些可能是不存在的, 想将确实存在的那些变量选取进来, 应使用any_of()

    select()有若干个配套函数可以按名字的模式选择变量列,

  • starts_with("se"): 选择名字以“se”`开头的变量列;
  • ends_with("ght"): 选择名字以“ght”`结尾的变量列;
  • contains("no"): 选择名字中含有子串“no”`的变量列;
  • matches("^[[:alpha:]]+[[:digit:]]+$"), 选择列名匹配某个正则表达式模式的变量列, 这里匹配前一部分是字母,后一部分是数字的变量名,如abc12
  • num_range("x", 1:3),选择x1, x2, x3
  • everything(): 代指所有选中的变量, 这可以用来将指定的变量次序提前, 其它变量排在后面。
  • 选择变量时, 可以用“新变量=老变量”的格式同时改名,

    gender 可以用pull(.data, !!sym(varname))这种格式; 如果varname是函数的自变量, 可以用pull(.data, {{ varname }})这种格式。 先选择仅有一个变量的子数据框再用pull(),如:

    ## [1] "Alice:Becka:Gail"

    基于基本R, 也可以用d.class[["name"]]这种格式取出一列为普通变量, 如果varname保存了变量名, 可以用d.class[[varname]]这种格式。

    不能用d.class[,"name"]这种方法, 对于tibble类型, 其结果仍是一个子数据框; 用d.class["name"]这种格式, 结果也是一个子数据框。

    23.10arrange()排序

    dplyr包的arrange()按照数据框的某一列或某几列排序, 返回排序后的结果,如

    height weight

    注意这样改名字不是对原始数据框修改而是返回改了名字后的新数据框。 也可以利用赋值运算符->写成:

    rename()这个函数可能出现在其它包中, 保险起见写成dplyr::rename()

    如果数据框中有大批变量名需要按某种规范统一修改, 可以考虑使用janitor::clean_names()

    23.12relocate()调整变量次序

    relocate(df, var1, var2)将指定的变量调整到最前面。

    height weight

    上面程序中的if_else()是dplyr包对基本R函数ifelse()的一个改进版本, 能够好处理缺失值和结果类属的问题。

    mutate()计算新变量时如果计算比较复杂, 也可以用多个语句组成复合语句,如:

    height weight 使得新定义的变量添加到原有变量的最前面。 可以用.after=变量名使得新添加的变量放在指定的变量后面。 可以用选项.keep="used"仅保留新变量以及计算新变量时用到的变量。

    dplyr定义了一些新的函数:

  • row_number()返回每个观测的行号。
  • min_rank(x),计算秩统计量,冲突时按最小值, 类似函数有dense_rank(x), percent_rank(x), cume_rank(x)等。 基本R的rank(x)函数配合ties.method选项实现类似功能。
  • dplyr::lag(x)x看成一个时间为1:n的时间序列, 然后在第\(i\)个时间点返回第\(i-1\)个时间点的值, 如dplyr::lag(c(2,3,5,7))返回NA, 2, 3, 5dplyr::lead(x)则在时间点\(i\)返回时间点\(i+1\)的值, 如dplyr::lead(c(2,3,5,7))返回3,5,7,NA。 注意stats::lag()函数有很大的差别, 它主要针对时间序列数据, 对一般向量反而不好用。
  • 23.14tranmute()生成新变量的数据框

    函数transmute()用法与mutate()类似, 但是仅保留新定义的变量, 不保留原来的所有变量。

    height_cm weight_kg

    可见结果中仅保留了新定义的变量。

    定义新变量也可以直接为数据框的新变量赋值:

    这样的做法与mutate()的区别是这样不会生成新数据框, 新变量是在原数据框中增加的。

    给数据框中某个变量赋值为NULL可以修改数据框, 从数据框中删去该变量。

    23.15 缺失值填补

    从统计角度出发考虑如何填补数据中的缺失值是比较复杂的。 这里仅给出从编程考虑的做法。

    有些非整洁的数据在某变量值持续多行保持不变时, 往往仅输入第一个值, 随后重复的值就输入为空白, 在读入为R数据框时自动变成缺失值。 对于这样的缺失值, 可以用tidyr::fill()函数, 可以指定需要填补的变量。 ## 6 2 3 14

    有时某个变量的缺失值实际上有含义,比如取值为0。 这时,可以用 dplyr::coalesce(x, default) 使 x 的缺失值用 default 取代。如:

    d.miss |>
      mutate(
        x = coalesce(n, 0)
    
    ## # A tibble: 6 × 4
    ##       A     B     n     x
    ##   <dbl> <dbl> <dbl> <dbl>
    ## 1     1     1    12    12
    ## 2    NA     2    10    10
    ## 3    NA     3    NA     0
    ## 4     2     1    15    15
    ## 5    NA     2    NA     0
    ## 6    NA     3    14    14

    有时,有可能缺失值被输入成了一个特殊数值, x的值仅取正数, 输入了-1就表示缺失值。 在读入CSV文件时, 如果所有缺失值都用-1表示, 可以在read_csv()中加选项na = -1。 如果不同列有不同的缺失值编码, 可以用na_if(x, na_value)x中等于na_value的值改为缺失值, 可以用在mutate()中, 如mutate(x = na_if(x, -1))

    在涉及到交叉分组的数据时, 有可能某些交叉分组没有出现在数据中, 这可能对某些分析程序造成影响。 可以用complete(A, B)这样的方法将数据框中A, B的交叉分类补全。 如果这样不能包含所有可取值, 也可以生成一个包含所有组合的小数据框, 用dplyr::full_join()作外连接。 dplyr::anti_join()可以用变量取值全集的数据框来比对, 发现没有出现的变量取值。

    在使用dplyr::count()对因子进行频数统计时, 数据中不出现的水平或水平组合默认在结果中也不出现, 但可以加选项.drop = FALSE使得这些水平或水平组合以频数0出现。 用dplyr::group_by()时, 也可以加.drop = FALSE使得没有观测的分组(但因子水平存在)也出现在结果中。 但更好的办法是在分组汇总完以后, 用complete(A, B)这样的办法补全汇总结果中的因子水平或水平组合。

    23.16 用管道连接多次操作

    管道运算符特别适用于对同一数据集进行多次操作。 例如,对d.class数据,先选出所有女生, 再去掉性别和age变量:

    height weight

    结果的数据框d有三个变量: group是大组,共分3个大组,每组4个观测; subgroup是子组,在每个大组内分为2个子组,每个子组2个观测。 共有\(3 \times 2 \times 2 = 12\)个观测(行)。

    23.18 宽表转换为长表

    “整洁数据”的要求是每行为一个观测, 每列为一个变量(属性), 如果有同一个体在不同时间的测量值应该放在不同的观测中, 如果同一个体在同一时间的不同属性应该放在同一观测中。 满足这些要求的数据框才容易作为作图、汇总、统计建模函数的输入使用。

    实际数据经常不满足上述的要求, 所以需要将数据框进行长宽格式的转换。 以典型的纵向数据为例, 每个受试者有多次随访的记录值, 如果每个受试者的所有随访记录值存放在一个观测中, 就称为宽表, 这不符合整洁数据要求; 如果每个受试者的随访记录值同一时间的多个属性用了多个观测保存, 称为长表, 也不符合整洁数据要求。

    有些数据将受试者的每个属性放在一行, 每一列代表一个受试者, 这也不符合整洁数据要求。

    tidyr的pivot_longer()可以将宽表转换成整洁数据, pivot_wider()可以将长表转换成整洁数据。 基本R的reshape()函数也可以进行长宽格式的转换。 reshape2包也提供了较丰富的长宽表转换功能。 建议优先使用tidyr包的功能。

    23.18.1 pivot_longer函数

    tidyr的pivot_longer()函数可以将横向的多次观测堆叠在一列中。 下面的数据:

    subject pivot_longer(cols = starts_with("FU"), names_to = "time", values_to = "response", names_prefix = "FU", names_transform = list(time = as.integer), values_drop_na = TRUE) |> knitr::kable()
    subject response

    其中的 cols = starts_with("FU") 也可以写成 cols = paste0("FU", 1:4)

    考虑nlmeU扩展包的armd.wide数据框。 这个数据框中有240个受试者的信息, 每行为一个受试者, 包括5个时间点:visual0, visual4, visual12, visual24, visual52。

    library(nlmeU)
    ## Attaching package: 'nlmeU'
    ## The following object is masked from 'package:stats':
    ##     sigma
    values_to = "visual" , names_prefix = "visual" , names_transform = list ( time = as.integer), values_drop_na = TRUE ) |> head ( 10 ) |> knitr :: kable ()
    subject lesion line0 treat.f miss.pat visual values_to = "visual" , names_prefix = "visual" , names_transform = list ( time = as.integer), values_drop_na = TRUE ) |> mutate ( timep = as.integer ( factor (time, levels= c ( 0 , 4 , 12 , 24 , 52 ))) - 1 ) |> head ( 10 ) |> knitr :: kable () subject lesion line0 treat.f miss.pat visual timep 关于正则表达式详见 49 。 比如,若变量名为 F1 , M1 , F2 , M2 , 则应将上面程序中的 names_sep = "_" 修改为 names_pattern = "(F|M)(1|2)" 。 其中的 "(F|M)(1|2)" 就是正则表达式, F|M 表示F或者M, 1|2 表示1或者2, 圆括号表示分组, 前后两组分别表示两个分类变量 gender response

    23.18.4 一行中有多个属性的多次观测的情形

    设有多个属性的多次测量用编号的列名保存在了同一观测中, 基本R软件中的anscombe数据集的一部分行:

    这可以看成是每个受试者的x, y两个变量的2次随访的值保存在了一个观测中。 用 names_pattern 指定切分变量名和随访号的模式, 在对应的 names_to 中用特殊的 ".value" 名字表示切分出来的那一部分实际是变量名, 这时不需要 values_to 选项。 程序如下:

    ## Rows: 3 Columns: 2
    ## ── Column specification ────────────────────────────────────────────────────────
    ## Delimiter: ","
    ## chr (1): succ/total
    ## dbl (1): testid
    ## ℹ Use `spec()` to retrieve the full column specification for this data.
    ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
    testid succ/total

    separate() 函数将变量名和性别值分开:

    23.20.2 extract函数

    函数 extract() 可以按照某种正则表达式表示的模式从指定列拆分出对应于正则表达式中捕获组的一列或多列内容。 例如,下面的数据中factors水平AA, AB, BA, BB实际是两个因子的组合, 将其拆分出来:

    design response

    23.20.3 separate_wider_delim函数

    tidyr::separate_wider_delim(x, delim, names) 将用分隔符分隔的内容拆分为多列, 新的列名用 names 给出。 相当于 separate() 的一个简化版本。

    separate_wider_position(x, width) 用指定宽度从字符串中提取多列, width 用一个有名向量格式指定, 输入的元素名作为提取的列名。

    23.20.4 separate_longer_delim函数

    tidyr::separate_longer_delim() 将某列中用分隔符分开的值拆分出来并堆叠在同一列中,如:

    类似地, separate_longer_position(x, width) 可以按指定宽度拆分字符串中的内容并堆叠到一列中。

    23.21 合并数据列

    tidyr::unite() 函数可以将同一行的两列或多列的内容合并成一列。 这是 separate() 的反向操作,

    testid ratio 第二个参数是合并后的变量名( ratio 变量), 其它参数是要合并的变量名, sep 指定分隔符。 实际上用 mutate() paste() 或者 sprintf() 也能完成合并。

    23.22 数据框纵向合并

    矩阵或数据框要纵向合并,使用 rbind 函数即可。 dplyr包的 bind_rows() 函数也可以对两个或多个数据框纵向合并。 要求变量集合是相同的,变量次序可以不同。

    比如,有如下两个分开男生、女生的数据框:

    合并行如下:

    filter (age <= 12 ) |> select (name, age, height, weight)

    用dplyr包的 inner_join() 函数将两个数据框按键值横向合并, 仅保留能匹配的观测。因为d1.class中丢失了Sandy的观测, 所以合并后的数据框中也没有Sandy的观测:

    ## Joining, by = "name"
    height weight 如果在 inner_join() left_join() 中使用 multiple = "all" , 则意味着允许左表的一个观测可以和右表的多个观测匹配。

    如果左右表的一对一连接不应该有遗漏, 如果有任何不匹配意味着数据有错, 可以在 inner_join() 中加选项 unmatched = "error" , 其默认值是 "drop"

    23.23.3 多对一左连接

    两个表的横向连接, 经常是多对一连接, 这用于从右表向左表中添加一些额外的变量。

    d.stu 中有学生学号、班级号、姓名、性别, d.cl 中有班级号、班主任名、年级, 可以通过班级号将两个表连接起来:

    sname

    R的 intersect() union() , setdiff() 本来是以向量作为集合进行集合操作。 dplyr包也提供了这些函数, 但是将两个tibble的各行作为元素进行集合操作。

    23.25 标准化

    设x是各列都为数值的列表(包括数据框和tibble)或数值型矩阵, 用 scale(x) 可以把每一列都标准化, 即每一列都减去该列的平均值,然后除以该列的样本标准差。 用 scale(x, center=TRUE, scale=FALSE) 仅中心化而不标准化。

    ##            height      weight
    ##  [1,] -1.13843504 -0.70371312
    ##  [2,]  0.57794313 -0.08897522
    ##  [3,]  0.38290015 -0.44025402
    ##  [4,] -1.17744363 -1.01108207
    ##  [5,] -0.49479323 -0.68175819
    ##  [6,]  0.81199469  0.52576268
    ##  [7,] -2.15265850 -2.17469309
    ##  [8,]  0.03182280  0.54771760
    ##  [9,]  0.09033569  0.10861910
    ## [10,]  1.29960213  0.54771760
    ## [11,]  0.22686577  0.10861910
    ## [12,]  0.90951618  1.44786952
    ## [13,] -0.98240066 -0.74762297
    ## [14,]  0.03182280 -0.70371312
    ## [15,] -0.65082761 -0.02311045
    ## [16,]  1.88473105  2.19433697
    ## [17,]  0.48042164  1.22832027
    ## [18,] -0.94339207 -0.65980327
    ## [19,]  0.81199469  0.52576268
    ## attr(,"scaled:center")
    ##    height    weight 
    ##  62.33684 100.02632 
    ## attr(,"scaled:scale")
    ##    height    weight 
    ##  5.127075 22.773933

    为了把 x 的每列变到 \([0,1]\) 内,可以用如下的方法:

    其中的 . %>% 管道操作中表示被传递处理的变量(一般是数据框)。 也可以写一个自定义的进行零一标准化的函数:

    ##          height    weight
    ##  [1,] 0.2512077 0.3366834
    ##  [2,] 0.6763285 0.4773869
    ##  [3,] 0.6280193 0.3969849
    ##  [4,] 0.2415459 0.2663317
    ##  [5,] 0.4106280 0.3417085
    ##  [6,] 0.7342995 0.6180905
    ##  [7,] 0.0000000 0.0000000
    ##  [8,] 0.5410628 0.6231156
    ##  [9,] 0.5555556 0.5226131
    ## [10,] 0.8550725 0.6231156
    ## [11,] 0.5893720 0.5226131
    ## [12,] 0.7584541 0.8291457
    ## [13,] 0.2898551 0.3266332
    ## [14,] 0.5410628 0.3366834
    ## [15,] 0.3719807 0.4924623
    ## [16,] 1.0000000 1.0000000
    ## [17,] 0.6521739 0.7788945
    ## [18,] 0.2995169 0.3467337
    ## [19,] 0.7342995 0.6180905
    ## attr(,"scaled:center")
    ## height weight 
    ##   51.3   50.5 
    ## attr(,"scaled:scale")
    ## height weight 
    ##   20.7   99.5

    函数 sweep() 可以执行对每列更一般的变换。

    23.26 读入多个文件

    设同类的数据放置在了多个文件中, 这时可以利用多种技术, 将多个文件读入并纵向合并, 需要时增加文件名代表的变量信息。

    作为举例, 我们将 d.class 分为男生和女生两个数据框, 分别保存到 data 子目录中:

    如果要读入这两个文件并合并为一个大数据框, 当然可以使用重复的写法:

    ## # A tibble: 4 × 5
    ##   name    age height weight sex  
    ##   <chr> <dbl>  <dbl>  <dbl> <chr>
    ## 1 Alice    13   56.5    84  F    
    ## 2 Becka    13   65.3    98  F    
    ## 3 Duke     14   63.5   102. M    
    ## 4 Guido    15   67     133  M

    如果有许多个文件要合并, 这样的硬编码方式就不够简洁, 也容易出错。 可以联合使用许多技巧。

    list.files() 函数可以指定某种文件名模式, 获取所有要读入的文件名,如:

    flis <- list.files(
      "data", pattern = "class-[[:alnum:]]+[.]csv")
    
    ## [1] "class-F.csv" "class-M.csv"

    其中的pattern选项输入了一个模式(正则表达式), 见第49章。

    可以用purrr::map()(见§25.2.1)将所有文件读入为一个数据框的列表, 用purrr::list_rbind()函数将作为列表元素的数据框纵向合并:

    ## # A tibble: 4 × 4
    ##   name    age height weight
    ##   <chr> <dbl>  <dbl>  <dbl>
    ## 1 Alice    13   56.5    84 
    ## 2 Becka    13   65.3    98 
    ## 3 Duke     14   63.5   102.
    ## 4 Guido    15   67     133

    这个程序的缺点是丢失了性别变量, 这个变量的值保存在文件名内。 可以用purrr::set_names()给各个文件名指定元素名, 这个函数的第二自变量可以直接输入元素名向量, 也可以指定一个函数, 从元素值产生元素名。 用map()读入了数据框列表后, 列表元素名会沿用文件名向量的元素名, 然后在list_rbind()中可以用names_to选项, 将数据框列表的元素名转换为合并后大数据框的一列:

    ## # A tibble: 4 × 5
    ##   filename    name    age height weight
    ##   <chr>       <chr> <dbl>  <dbl>  <dbl>
    ## 1 class-F.csv Alice    13   56.5    84 
    ## 2 class-F.csv Becka    13   65.3    98 
    ## 3 class-M.csv Duke     14   63.5   102.
    ## 4 class-M.csv Guido    15   67     133

    可以用适当的字符串处理函数提取文件名中的变量值,如:

    ## # A tibble: 4 × 5
    ##   name    age height weight sex  
    ##   <chr> <dbl>  <dbl>  <dbl> <chr>
    ## 1 Alice    13   56.5    84  F    
    ## 2 Becka    13   65.3    98  F    
    ## 3 Duke     14   63.5   102. M    
    ## 4 Guido    15   67     133  M

    这里用了str_sub取子串的方法提取变量值, 这适用于文件名中的变量值占据固定的位置的情形。 更复杂的文件名, 可以用正则表达式, 或者tidyr::separate_wider_delim()指定分隔符分割成多列。

    上面的程序先用“-”分割成两部分, 再用“.”将第二部分分割成两部分。

    也可以直接使用正则表达式,

    data.table包的rbindlist可以将保存为列表元素的多个数据框纵向合并, 运行效率高。

    23.27 使用data.table包

    data.table包不仅能高效地读写文本格式的数据, 也能高效地进行数据整理。 它可以利用CPU的多个核心并行处理, 并可以巧妙地使用内存, 避免冗余的复制。

    data.table在读入数据时不会自动将字符型数据转换为因子, 也不会生成数据框行名。 在显示data.table时, 过大的表会自动简化显示。

    ## Attaching package: 'data.table'
    ## The following objects are masked from 'package:dplyr':
    ##     between, first, last
    ## The following object is masked from 'package:purrr':
    ##     transpose

    用如下命令快速查看基本用法示例:

    23.27.1 查询

    data.table的 [ 运算符支持增强的查询功能, 可以指定行子集条件、列子集条件和分组条件, 一般格式为 DT[i, j, by]

    以nycflights13中的航班数据为例。

    ## Warning: package 'nycflights13' was built under R version 4.2.2

    23.27.2 行子集与排序

    仅提供行子集条件, 注意可以直接使用数据框变量名, 也不需要写成 DT[i,] 的格式:

    ##    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    ## 1: 2013     6   1        2           2359         3      341            350
    ## 2: 2013     6   1      538            545        -7      925            922
    ## 3: 2013     6   1      539            540        -1      832            840
    ## 4: 2013     6   1      553            600        -7      700            711
    ## 5: 2013     6   1      554            600        -6      851            908
    ## 6: 2013     6   1      557            600        -3      934            942
    ##    arr_delay carrier flight tailnum origin dest air_time distance hour minute
    ## 1:        -9      B6    739  N618JB    JFK  PSE      200     1617   23     59
    ## 2:         3      B6    725  N806JB    JFK  BQN      203     1576    5     45
    ## 3:        -8      AA    701  N5EAAA    JFK  MIA      140     1089    5     40
    ## 4:       -11      EV   5716  N835AS    JFK  IAD       42      228    6      0
    ## 5:       -17      UA   1159  N33132    JFK  LAX      330     2475    6      0
    ## 6:        -8      B6    715  N766JB    JFK  SJU      198     1598    6      0
    ##              time_hour
    ## 1: 2013-06-01 23:00:00
    ## 2: 2013-06-01 05:00:00
    ## 3: 2013-06-01 05:00:00
    ## 4: 2013-06-01 06:00:00
    ## 5: 2013-06-01 06:00:00
    ## 6: 2013-06-01 06:00:00

    按行号取行子集:

    ##    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    ## 1: 2013     1   1      517            515         2      830            819
    ## 2: 2013     1   1      533            529         4      850            830
    ##    arr_delay carrier flight tailnum origin dest air_time distance hour minute
    ## 1:        11      UA   1545  N14228    EWR  IAH      227     1400    5     15
    ## 2:        20      UA   1714  N24211    LGA  IAH      227     1416    5     29
    ##              time_hour
    ## 1: 2013-01-01 05:00:00
    ## 2: 2013-01-01 05:00:00

    返回按某些列排序的结果,如:

    [] 中的 order() 函数是data.table提供的改进版本, 可以用减号表示降序, 排序速度更快。

    23.27.3 列子集

    DT[,j] 可以将指定的列提取成一个R向量, 这与data.frame的做法相同, 但与tibble做法不同。

    ## [1]  11  20  33 -18 -25

    注意变量名不需要用撇号保护。

    选择多列, 或者选择一列但需要结果为data.table, 将这些列名写在 .() 内,如:

    ##    arr_delay dep_delay
    ## 1:        11         2
    ## 2:        20         4
    ## 3:        33         2
    ## 4:       -18        -1
    ## 5:       -25        -6

    取列子集时可以改变量名, 如 dt_flights[, .(dalaya = arr_delay, dalayd = dep_delay)]

    因为在 j 的位置直接使用变量名, 而不是用撇号保护起来的变量名, 所以如果将变量名保存在了某个R变量中(如 vars ), 希望访问这样的列子集, 就需要用 ..vars 的格式,

    ##    arr_delay dep_delay
    ## 1:        11         2
    ## 2:        20         4
    ## 3:        33         2
    ## 4:       -18        -1
    ## 5:       -25        -6

    23.27.4 计算汇总

    可以在 DT[i,j,by] j 位置计算新变量或者汇总统计量。 计算总延误小于0的个数:

    ## [1] 188401

    取行子集并计算统计量:

    ##       m_arr    m_dep
    ## 1: 17.59693 20.49973

    计算子集行数:

    ## [1] 9472

    其中 .N 是data.table提供的特殊语法。 这比先取子集再用 nrow() 计算的好处是不需要先生成子集, 而是直接计数。

    23.27.5 用by做分组汇总

    by = 指定一个或多个分组变量, 可以进行分组汇总。

    数据框中每个出发机场的计数:

    ##    origin      N
    ## 1:    EWR 120835
    ## 2:    LGA 104662
    ## 3:    JFK 111279

    注意 j by = 的参数都用了 .() 保护。 当 j by = 的参数仅有一个变量时, 也可以不用 .() 的格式,如:

    ##    origin      N
    ## 1:    EWR 120835
    ## 2:    LGA 104662
    ## 3:    JFK 111279

    by = 后面也可以使用保存了变量名的字符型变量,

    ##      origin dest     N
    ##   1:    EWR  IAH  3973
    ##   2:    LGA  IAH  2951
    ##   3:    JFK  MIA  3314
    ##   4:    JFK  BQN   599
    ##   5:    LGA  ATL 10263
    ##  ---                  
    ## 220:    LGA  TVC    77
    ## 221:    LGA  MYR     3
    ## 222:    EWR  TVC    24
    ## 223:    EWR  ANC     8
    ## 224:    EWR  LGA     1

    但是,这种语法设计有问题, 在上例中无法分辨 vars 是保存了变量名的变量, 还是数据框中的变量名, 直接使用变量名时用 by = .(dest) 这样的格式较好。

    取子集并分组汇总计数的例子:

    ##    origin     N
    ## 1:    JFK 13783
    ## 2:    LGA 15459
    ## 3:    EWR  3487

    计算该子集按出发机场分组后的平均延误:

    ##    origin       marr      mdep
    ## 1:    JFK  2.0812500 10.302155
    ## 2:    LGA -1.3317539  6.705769
    ## 3:    EWR  0.9776985 10.035419

    23.27.6 分组汇总并将结果排序

    by = 分组汇总时, 输出的各组次序是在原始数据中各组最先出现的次序。 为了使得汇总结果按组别取值排序, 将 by = 改为 keyby =

    ##    origin      N
    ## 1:    EWR 120835
    ## 2:    JFK 111279
    ## 3:    LGA 104662

    也可以先用 by 分组汇总, 再用 order() 排序, 这样还可以降序排列,

    ##    origin dest        marr      mdep
    ## 1:    EWR  MIA  0.06332703  9.204524
    ## 2:    EWR  LAX  0.91643454  5.861496
    ## 3:    EWR  DFW  1.48612539 11.250254
    ## 4:    JFK  TPA  5.20000000 10.918831
    ## 5:    JFK  STT -4.49501661  4.036545
    ## 6:    JFK  SJU -0.77603687  8.600917

    23.27.7 数据子集 .SD

    data.table的 [] 运算内用 .SD 表示对一个数据子集(Subset of Data)的处理。 用这个特殊语法, 可以在分组后统一对数据子集进行分析,

    ##    origin dest month  arr_delay dep_delay
    ## 1:    JFK  MIA     1  0.4789474 12.236842
    ## 2:    LGA  ORD     1  1.6497462  5.972152
    ## 3:    LGA  DFW     1  2.1135802  5.148780
    ## 4:    EWR  MIA     1  7.6559140 15.494624
    ## 5:    LGA  MIA     1 -4.8750000  2.823171
    ## 6:    JFK  SJU     1  1.5040650 11.766129

    在上例中, 用 SDcols = 指定对每个数据子集要分析的列, 用 lapply(.SD, f) 指定对每个数据子集的每一要分析的列应用 f 进行汇总。 这里的 f 用了R的无名函数形式。

    23.27.8 以引用方式增添、修改、删除列

    data.table使用特殊的 := 运算符, 可以直接以引用方式增添、修改、删除data.table列, 这样做的好处是对大型数据节约了存储量, 提高了效率。 参见该包的帮助文档中名为“Reference semantics”的vignette。

    23.27.9 使用关键列

    data.table不再使用行名, 但可以指定一个或多个关键列, 起到与行名类似的作用, 但功能更强, 访问效率更高。 关键列不需要唯一区分各行。 用 setkey() 或者 setkeyv() 给data.table指定关键列, 这会使得数据框按照这些列的升序排列。

    可以直接以关键列的值作为行下标, 如 dt_flights["JFK"] 可以取出满足 origin == "JFK" 条件的行, 又如 dt_flights[c("JFK", "LGA")] 取出origin等于JFK或LGA的行。 这样取行子集时, 因为用了二分法定位, 所以在大型数据上比 dt_flights[origin == "JFK"] 这样的线性访问算法的行子集计算效率高得多。 data.table对每个表仅允许设置一组关键列, 而不允许设置多组。

    可以指定多列作为关键列,

    取出origin为JFK,dest为MIA的子集:

    为了明确表示用了关键列帮助定位子集, 可以加上 on = 选项,如:

    这个语法主要用在辅助索引列功能, 但是在使用关键列时, 也可以使得程序意图更为明确。

    取出origin为JFK或LGA, dest为MIA的子集:

    取出dest为MIA,origin为任意值的子集:

    这里用了 unique(origin) 来获取origin的所有可取值。

    在行下标中使用关键列后, 仍可以用列下标取列子集或汇总计算, 用by分组。

    计算出发机场为JFK的航班每个月的延误最大值:

    可以用 mult = "first" 要求仅返回每个组的第一行, 用 mult = "last" 仅返回每个组的最后一行。

    可以用 nomatch = NULL 指定关键列的值对应的行不存在时, 就在结果中不包含对应的结果行。

    23.27.10 使用辅助索引列

    关键列仅允许有一组, 而且指定关键列会按关键列对数据框排序, 这都是使用关键列的障碍。

    data.table提供了辅助索引列(secondary indeces), 能够起到关键列的作用, 但是不需要对数据框重排, 还可以有多组。 增加辅助索引列, 仅增加一个按指定的列重排所用的行号序列。 还可以在取行子集时临时生成辅助索引列。

    类似于 setkey() setkeyv() , 可以用 setindex() setindexv() 设置关键列。

    注意这设置了两组辅助索引列。

    因为允许有多组辅助索引列, 所以在用辅助索引加速取行子集操作时, 需要用 on = 选项指定所用的辅助索引列,

    与使用关键列类似, 仍可以取列子集或者进行汇总, 用 by = sortby = 进行分组计算。

    如果 on = 指定的辅助索引列没有预先建立索引, 这会临时创建需要的辅助索引, 并在完成查询任务后自动删除。

    data.table设置了自动索引功能, 一旦根据某个变量值进行查询, 就自动设置该变量的辅助索引, 并且不会自动删除。

    23.27.11 横向合并

    一对一横向合并,如:

    ##      name  sex age height weight
    ## 1:  Karen    F  12   56.3   77.0
    ## 2:  Kathy    F  12   59.8   84.5
    ## 3:  Sandy <NA>  11   51.3   50.5
    ## 4:  James    M  12   57.3   83.0
    ## 5:   John    M  12   59.0   99.5
    ## 6: Robert    M  12   64.8  128.0
    ## 7: Thomas    M  11   57.5   85.0

    一对一横向合并用了行子集的语法, 将要合并的右表写在左表的行子集位置。 这作的是右外连接, 包括与右表匹配的每一个观测, 左表中不存在的观测用缺失值代替, 仅在左表中存在的观测被忽略。

    为了作内连接, 只要加选项 nomatch = NULL

    如果左表与右表用来对齐的列名不相同, 可以用如 a[b, on = c(avar = "bvar")] 的形式。 结果中这一列的列名用 avar , 取值则用 bvar 的值。

    可以用 a[!b, on=.(id)] 的格式表示选取所有不能与b匹配的观测, 即反向连接(anti-join)。

    这个语法还有许多功能, 用如下命令查看 [ 运算功能:

    23.27.12 长宽表转换

    melt() 函数将宽表转换为长表。 data.table可以对很大的表(比如,占用几个GB内存的表)进行高速转换。

    ##    subject time  y
    ## 1:       1    1  1
    ## 2:       3    1  5
    ## 3:       2    2  7
    ## 4:       3    2 10
    ## 5:       4    3  9
    ## 6:       2    4  4

    可以用 dcast() 将长表转换为宽表,如:

    ##    subject  1  2  3  4
    ## 1:       1  1 NA NA NA
    ## 2:       2 NA  7 NA  4
    ## 3:       3  5 10 NA NA
    ## 4:       4 NA NA  9 NA

    当有多个测量变量时, data.table也提供了相应的语法, 详见该包的帮助文档中题目为“Efficient reshaping using data.tables”的vignette。

    关于data.table的使用, 还可参见:

  • Quick R Tutorial
  •