• 数据处理包plyr和dplyr包的整理


    以下内容主要参照 Introducing dplyr 和 dplyr 包自带的简介 (Introduction to dplyr), 复制了原文对应代码, 并夹杂了个人理解和观点 (多附于括号内).

    0 初始化0.1 安装

    install.packages("dplyr")

    0.2 示范数据

    • library(Lahman): Lahman 包里的棒球比赛数据集 Batting
    • library(hflights): hflights 包里的飞机航班数据

    0.3 数据集类型

    将过长过大的数据集转换为显示更友好的 tbl_df 类型:

    hflights_df <- tbl_df(hflights)

    可以 hflights_df 感受一下不再被刷屏的感觉.

    1 基本操作

    把常用的数据操作行为归纳为以下五种:

    1.1 筛选: filter()

    按给定的逻辑判断筛选出符合要求的子数据集, 类似于 base::subset() 函数

    例如:

    filter(hflights_df, Month == 1, DayofMonth == 1)

    用R自带函数实现:

    hflights[hflightsMonth == 1 & hflightsDayofMonth == 1, ]

    除了代码简洁外, 还支持对同一对象的任意个条件组合, 如:

    filter(hflights_df, Month == 1 | Month == 2)

    注意: 表示 AND 时要使用 & 而避免 &&

    1.2 排列: arrange()

    按给定的列名依次对行进行排序.

    例如:

    arrange(hflights_df, DayofMonth, Month, Year)

    对列名加 desc() 进行倒序:

    arrange(hflights_df, desc(ArrDelay))

    这个函数和 plyr::arrange() 是一样的, 类似于 order()

    用R自带函数实现:

    hflights[order(hflightsDayofMonth,hflightsMonth, hflightsYear),]hflights[order(desc(hflightsArrDelay)), ]

    1.3 选择: select()

    用列名作参数来选择子数据集:

    select(hflights_df, Year, Month, DayOfWeek)

    还可以用 : 来连接列名, 没错, 就是把列名当作数字一样使用:

    select(hflights_df, Year:DayOfWeek)

    用 - 来排除列名:

    select(hflights_df, -(Year:DayOfWeek))

    同样类似于R自带的 subset() 函数 (但不用再写一长串的 c("colname1", "colname2") 或者 which(colname(data) == "colname3"), 甚至还要去查找列号)

    1.4 变形: mutate()

    对已有列进行数据运算并添加为新列:

    mutate(hflights_df,   gain = ArrDelay - DepDelay,   speed = Distance / AirTime * 60)

    作用与 plyr::mutate() 相同, 与 base::transform() 相似, 优势在于可以在同一语句中对刚增加的列进行操作:

    mutate(hflights_df,   gain = ArrDelay - DepDelay,   gain_per_hour = gain / (AirTime / 60))

    而同样操作用R自带函数 transform() 的话就会报错:

    transform(hflights,   gain = ArrDelay - DepDelay,   gain_per_hour = gain / (AirTime / 60))1.5 汇总: summarise()

    对数据框调用其它函数进行汇总操作, 返回一维的结果:

    summarise(hflights_df,   delay = mean(DepDelay, na.rm = TRUE))

    等同于 plyr::summarise(), 原文说该函数功能尚不是非常有用, 大概以后的更新会加强吧.

    2 分组动作 group_by()

    以上5个动词函数已经很方便了, 但是当它们跟分组操作这个概念结合起来时, 那才叫真正的强大! 当对数据集通过 group_by() 添加了分组信息后,mutate(), arrange() 和 summarise() 函数会自动对这些 tbl 类数据执行分组操作 (R语言泛型函数的优势).

    例 如: 对飞机航班数据按飞机编号 (TailNum) 进行分组, 计算该飞机航班的次数 (count = n()), 平均飞行距离 (dist = mean(Distance, na.rm = TRUE)) 和 延时 (delay = mean(ArrDelay, na.rm = TRUE))

    planes <- group_by(hflights_df, TailNum)delay <- summarise(planes,   count = n(),   dist = mean(Distance, na.rm = TRUE),   delay = mean(ArrDelay, na.rm = TRUE))delay <- filter(delay, count > 20, dist < 2000)

    用 ggplot2 包作个图观察一下, 发现飞机延时不延时跟飞行距离没太大相关性:

    ggplot(delay, aes(dist, delay)) +   geom_point(aes(size = count), alpha = 1/2) +   geom_smooth() +   scale_size_area()

    (图就不上了, 右键复制来的链接太凶残了, 看着像是现算的)

    更多例子见 vignette("introduction", package = "dplyr")

    另: 一些汇总时的小函数

    • n(): 计算个数
    • n_distinct(): 计算 x 中唯一值的个数. (原文为 count_distinct(x), 测试无用)
    • first(x), last(x) 和 nth(x, n): 返回对应秩的值, 类似于自带函数 x[1], x[length(x)], 和 x[n]

    注意: 分组计算得到的统计量要清楚样本已经发生了变化, 此时的中位数是不可靠的

    3 连接符 %>% 注意连接符为> 不是.

    包里还新引进了一个操作符, 使用时把数据名作为开头, 然后依次对此数据进行多步操作.

    比如:

    Batting %.%    group_by(playerID) %.%    summarise(total = sum(G)) %.%    arrange(desc(total)) %.%   head(5)

    这样可以按进行数据处理时的思路写代码, 一步步深入, 既易写又易读, 接近于从左到右的自然语言顺序, 对比一下用R自带函数实现的:

    head(arrange(summarise(group_by(Batting, playerID), total = sum(G)) , desc(total)), 5)

    或者像这篇文章所用的方法:

    totals <- aggregate(. ~ playerID, data=Batting[,c("playerID","R")], sum)ranks <- sort.list(-totals$R)totals[ranks[1:5],]

    文章里还表示: 用他的 MacBook Air 跑 %.% 那段代码用了 0.036 秒, 跑上面这段代码则用了 0.266 秒, 运算速度提升了近7倍. (当然这只是一例, 还有其它更大的数字.)

    更多请 ?"%.%", 至于这个新鲜的概念会不会和 ggplot2 里的 + 连接号一样, 发挥出种种奇妙的功能呢? 还是在实际使用中多体验感受吧.

    感想

    可以看到, 用 dplyr 所含函数实现的代码都要简洁易读得多, 说到底, R语言只是一个工具, 作为工具, 就是要拿来用的, 越称手越便利越简洁越好, 可是, 正如 Hadley Wickham 在2013年的访谈中提到的那样:

    如果你用了8小时进行数据清理和数据整理,而只用了2小时进行建模,那么很明显,你希望了解如何将数据清理和整理的时间尽可能缩短。

    反思之下, 本人也是将大把的时间花在了对数据的反复调整上, 或许是手生, 当然R语言在这方面也确实有一定不足, 大神又说了:

    数据分析有两个瓶颈,一是我们的目标是什么,二是我们如何用计算机去实现。我现有的很多作品,如 ggplot2,plyr 和 reshape2,更关注的是如何更简单地表达你的目标,而不是如何让计算机算得更快。

    这种内在的理念正是要将工具工具化, 把无谓的时间减少, 让精力用在真正需要考虑的地方. 正如 Vim 一样, 在投入一定的学习成本后, 继续用继续学, 不知不觉地就能心手如一, 想做什么, 就已经按下去了, 从而更多地思考要编辑什么, 而不必纠结于光标移动选择等细节. 这其中的巧妙之处在于: 实现过程要以人脑的思维运作方式为标准, 让工具来适应人, 以实现目的为导向, ggplot2 的图形图层语法也是如此. 不管是软件也好, 编程语言也好, 高效的方法都是相通的, 这也正是许多人努力的方向, 另外平素语出惊人的王垠最近也表达了类似观点.

    顺便肖凯老师在网易云课堂新开的R语言初级教程里提到了十大必学R包的说法, 并把 plyr 列为之一, 有趣的是居然还有人在问答平台上求详情, 好奇之下放狗一搜, 原来出处在此 (脱水版), 其中 ggplot2 和 reshape2 是平时都有在用的, 还有实用的 knitr 和 Slidify , 其它就没什么发言权了.

    深入学习

    暂时没有太多的相关资料, 如欲进一步学习, 可参阅:

      • dplyr 包自带的60页详细文档
      • 其余几个vignettes (网页) 或 vignette(package = "dplyr") , 包含了数据库相关, 混合编程, 运算性能比较, 以及新的 window-functions 等内容.
        简单看了下vignette("window-functions", package = "dplyr"), 提供了一系列函数, 扩展了原来只能返回一个数值的聚焦类函数(如sum(), mean())至返回等长度的值, 变成 cumsum()和 cummean(), 以及 n(), lead() 和 lag()等便捷功能.
      • plyr 包的相关文档: 主页
      • 还有 data.table 包也是很强大的哦, 空下来可以学一学

    常见的数据处理包 
    dplyr——package

    1.数据对象:tbl对象 
    使用dplyr包预处理时建议使用tbl_df()或tbl_cube()或tbl_sql()函数将原数据转换为tbl对象

    2.观测筛选 
    将指定条件的观测筛选出来:filter()函数 
    filter(.data,…) 
    .data为tbl对象 
    …为观测筛选条件,类似于subset()函数,但不同的是filter()函数不能筛选某些关心的变量变量

    library(dplyr)
    df <- data.frame(x = c("a","b","c","a","b","e","d","f"),y = c(1,2,3,4,5,6,7,8))
    dftbl <- tbl_df(df)
    filter(dftbl,x %in% c("a","b"))
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    3.变量选取 
    select()函数可以筛选指定的变量,而且选择变量时也可以重新命名变量。如果要剔除某些变量,只需要在变量前加上负号”-“。 
    select()函数,传递的参数: 
    starts_with(x,ignor.case = TRUE) # 选择以字符x开头的变量 
    ends_with(x,ignore.case = TRUE) # 选择以字符x结尾的变量 
    contains(x,ignore.case = TRUE) #选择所有包含x的变量 
    matches(x,ignore.case = TRUE) #选择匹配正则表达式的变量 
    num_range(“x”,1:5,width = 2) #选择从x01到x05的数值型变量 
    one_of(“x”,”y”,”z”) #选择包含在声明变量中的变量 
    everything() #选择所有变量,一般调整数据集中变量顺序时使用 
    示例:

    # 将dftbl数据集中的y变量放到x变量之前
    select(dftbl,everything())
    
    #筛选变量的同时,重新命名变量
    select(dftbl,x1 = 1,y1 = y)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    4.重命名变量 
    rename(tbl,newname = oldname,…)

     rename(dftbl,x1 = x,y1 = y)
    • 1
    • 1

    5.数据排序 
    数据预处理的过程中往往也需要按某些变量进行排序:arrange()函数实现语法: 
    arrange(.data,…) 
    arrange()函数默认以某个变量进行升序,如需降序则desc(var_name)

    arrange(dftbl,y) # y变量升序
    arrange(dftbl,desc(y)) #降序操作
    • 1
    • 2
    • 1
    • 2

    6.数据扩展 
    通过mutae()函数可以在原始数据集的基础上扩展新变量,并保留原始变量 
    mutate(.data,…)

    mutate(dftbl,z = y^2+y-10)
    • 1
    • 1

    同样可以进行数据扩展的函数transmute(),与mutate()函数不同的是,该函数扩展新变量的同时将删除所有原始变量,结果中只有扩展变量。

    transmute(dftbl,z =y^2)
    • 1
    • 1

    7.数据聚合 
    数据库操作中,往往需要进行聚合函数的应用,这里可以使用summarize()函数实现数据集聚合操作.个人理解的聚合函数就是对数据集中的变量进行统计计算。 
    语法如下: 
    summarize(.data,…) 
    可以用来聚合的函数有: 
    min(),max(),mean()…等统计量,以及IQR() #返回四分位极差 
    n() # 返回观测个数 
    n_distinct() #返回不同的观测个数 
    first() # 返回第一个观测 
    last() #返回最后一个观测 
    nth() #返回n个观测

    summarize(dftbl,max(y))
    summarize(dftbl,n())
    • 1
    • 2
    • 1
    • 2

    还可以用group_by()函数实现分组聚合 
    group_by()语法如下: 
    group_by(.data,add = FALSE)

    summarize(group_by(dftbl,x),sum(y))
    • 1
    • 1

    8.数据关联 
    数据框中经常需要将多个表进行连接操作,如左连接、右连接、内连接等,这里dplyr包也提供了数据集的连接操作。如下: 
    inner_join #内连接 
    left_join #左连接 
    right_join #右连接 
    full_join #全连 
    semi_join # 返回能够与y表匹配的x表所有记录 
    anti_join # 返回无法与y表匹配的x表的所有记录

    df2 <- data.frame(x = c("a","b","c"),z = c("A","B","C"))
    df2tbl <- tbl_df(df2)
    inner_join(x = dftbl,y = df2tbl,by = "x")
    semi_join(x = dftbl,y = df2tbl,by = "x")
    anti_join(x = dftbl,y = df2tbl,by = "x")
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    9.数据合并 
    R基础包cbind()和rbind()函数实现按列的方向进行数据合并和按行的方向进行合并。 
    dplyr包中也添加了类似功能的函数,分别是bind_cols()函数和bind_rows()函数

    mydf1 <- data.frame(x = c(1,2,3,4),y = c(10,20,30,40))
    mydf2 <- data.frame(x = c(5,6),y = c(50,60))
    mydf3 <- data.frame(z = c(100,200,300,400))
    bind_rows(mydf1,mydf2)  #行变长
    bind_cols(mydf1,mydf3)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    10.管道函数 
    即通过%>%将上一个函数的输出作为下一个函数的输入

    # 根据数据集dftbl和df2tbl,取出z变量对应的最大y值
     inner_join(x = dftbl,y = df2tbl,by = "x") %>% group_by(z) %>% summarise(max(y))
    • 1
    • 2
    • 1
    • 2

    11.连接数据库数据 
    如果需要获取MySQL数据库中的数据时,可以直接使用dplyr包中的src_mysql()函数。 
    src_mySQL 函数语法如下: 
    src_mysql(dbname,host = NULL,port = 0L,user = “root”,password = “”,…) 
    通过以上方式连接MySQL数据库后,使用tbl()函数获取数据集,tbl()函数语法如下: 
    tbl(src,from =”“) 
    src为src_mysql()函数对象 
    from为SQL语句

    src <- src_mysql("test",host = "localhost",user = "root",password = "snake")
    src
    • 1
    • 2
    • 1
    • 2

    plyr-package 
    可以非常方便的实现数据结构之间的转换 
    其中的函数名有一定的规律,跟输入输出的数据结构相关。

    1.函数介绍

    a*ply函数形式
    aaply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE)
    adply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE)
    alply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE)
    a_ply(.data = ,.margins = ,.fun = ,.progress = "none",.inform = FALSE) #输入结构:array,无输出结果
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    .data可以是数组也可以是矩阵; 
    .margins指定要分析的数组或矩阵的维度,即行维(margins = 1),列维(margins = 2) 
    .fun为行或列维指定需要处理的函数,可以是R自带的函数,如sum(),mean()等,也可以是自定义函数; 
    …为指定函数的其他参数;.progress指定以什么样的方式展示程序运行的进度,默认为不显示进度,还可以选择text(文本进度条)、tk(tk进度条)和win(windows系统自带的进度条) 
    .inform是否指定报错信息,默认不指定,因为设为TRUE,将会降低程序的执行效率,但该参数对bug的处理是有帮助的 
    示例;

    library(plyr)
    a <- array(data = 1:500000,dim = c(100000,5))
    # 对每一行求均值,不显示进度条
    test1 <- aaply(.data = a,.margins = 1,.fun = mean,.progress = "none")
    head(test1)
    
    # 对每一行求标准差,以文本的形式显示进度条
    test2 <- adply(.data = a,.margins = 1,.fun = sd,.progress = "text")
    head(test2)
    
    # 对每一列求和,以tk形式显示进度条
    a2 <- array(rnorm(100000),dim = c(100,1000))
    test3 <- alply(.data = a2,.margins = 2,.fun = sum,.progress = "tk")
    head(test3)
    
    # 对每一列求最大值,以windows自带进度条显示进度
    a3 <- array(rnorm(100000),dim = c(100,1000))
    test4 <- a_ply(.data = a3,.margins = 2,.fun = max,.progress = "win")
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    # d*ply函数格式 
    daply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE) 
    ddply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE) 
    dlply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE) 
    d_ply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE) 
    .data 指定为数据框结构; 
    .variables指定数据框中的分组变量,需要用点号.引起来; 
    .fun 基于分组变量,可对数据框中的其余变量指定某种函数,可以是R自带的函数,如sum(),mean()等,也可以自定义函数,类似于聚合分析; 
    .progress和.inform与a*plyr函数参数一致。 
    示例:

     # 构建自定义函数
    fun <- function(data) apply(data,2,mean)
    
    daply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun)
    
    ddply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun)
    
    dlply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    # l*ply函数格式

    laply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE)
    ldply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE)
    llply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE)
    l_ply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE)
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    .data可以指定为列表数据. 
    其余参数与a*ply()函数和d*ply()函数参数一致。 
    示例:

    x1 <- 1:100
    x2 <- seq(from = 100,to = 1000,by = 2)
    x3 <- runif(150,min = 10,max = 100)
    
    # 列表由向量构成
    l1 <- list(x1 = x1,x2 = x2,x3 = x3)
    
    laply(.data = l1,.fun = mean)
    ldply(.data = l1,.fun = summary)
    llply(.data = l1,.fun = quantile)
    l_ply(.data = l1,.fun = summary)
    
    # 构建数据框dll
     y11 <- rnorm(n = 100,mean = 10,sd = 5)
     y12 <- rt(n = 100,df = 3)
     y13 <- rf(n = 100,df1 = 2,df2 = 3)
     y14 <- factor(x = c("low","potential","high"),ordered = T)
     y15 <- sample(y14,size = 100,replace = TRUE)
     d11 <- data.frame(y1 = y11,y2 = y12,y3 = y13,y5 = y15)
     head(dll)
    
    # 构建数据框d21
     y21 <- 1:100
     y22 <- seq(from = 1,to = 2,length = 100)
     y23 <- rchisq(n = 100,df = 8)
     y24 <- factor(x = c("A","B","C","D"),order = T)
     y25 <- sample(y24,size = 100,replace = TRUE)
     d21 <- data.frame(y21 = y21,y22 = y22,y23 = y23,y25 = y25)
     head(d21)
    
     # 列表由数据框组成
     l2 <- list(first = d11,second = d21)
    
    library(psych)
    fun <- function(data) describeBy(data[,1:3],group = data[,4])
    llply(.data = l2,.fun = fun,.progress = "none")
    llply(.data = l2,.fun = fun,.progress = "text")
  • 相关阅读:
    NestingQuery
    Repeat
    GenericQuery
    StringOpr
    RHEL5.6 安装 virtualbox
    DNS的资料总结
    drop delete truncate 区别
    Linux Shell命令ulimit的用法
    OSI及TCP/IP的概念和区别
    shell:读取文件的每一行内容并输出
  • 原文地址:https://www.cnblogs.com/awishfullyway/p/6485250.html
Copyright © 2020-2023  润新知