前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「R」数据操作(一)

「R」数据操作(一)

作者头像
王诗翔呀
发布2020-07-02 15:51:51
1.9K0
发布2020-07-02 15:51:51
举报
文章被收录于专栏:优雅R优雅R

本文内容:

  • 基础函数操作数据框
  • sqldf包使用SQL查询数据框
  • data.table包操作数据
  • dplyr管道操作处理数据
  • rlist包处理嵌套数据结构

使用内置函数操作数据框

数据框的本质是一个由向量构成的列表,由于列长度相同,所以可以当做矩阵进行访问和操作。比如选择满足特定条件的行,使用[]符号,第一个参数提供一个逻辑向量,第二个参数留空。

本文大部分的代码都是基于一组产品的虚拟数据。我们先将数据载入,然后学习怎么用不同的方法操作数据。

代码语言:javascript
复制
if(!require(readr)) install.packages("readr")
#> 载入需要的程辑包:readr

product_info = read_csv("../../R/dataset/product-info.csv")
#> Parsed with column specification:
#> cols(
#>   id = col_character(),
#>   name = col_character(),
#>   type = col_character(),
#>   class = col_character(),
#>   released = col_character()
#> )
product_info
#> # A tibble: 6 x 5
#>   id    name      type  class   released
#>   <chr> <chr>     <chr> <chr>   <chr>   
#> 1 T01   SupCar    toy   vehicle yes     
#> 2 T02   SupPlane  toy   vehicle no      
#> 3 M01   JeepX     model vehicle yes     
#> 4 M02   AircraftX model vehicle yes     
#> 5 M03   Runner    model people  yes     
#> 6 M04   Dancer    model people  no

当数据以数据框的形式载入内存后,我们可以使用下面的代码查看每一列的类型:

代码语言:javascript
复制
sapply(product_info, class)
#>          id        name        type       class    released 
#> "character" "character" "character" "character" "character"

注意read_csv函数载入的数据框与内置函数read.csv函数是不同的,主要体现在不会将字符串转换为因子变量,当然前者的速度要快得多。

接下来我们正式学习用R内置的函数操作数据框进行分析和统计的一些方法。

内置函数操作数据框

选取typetoy的行:

代码语言:javascript
复制
product_info[product_info$type == "toy", ]
#> # A tibble: 2 x 5
#>   id    name     type  class   released
#>   <chr> <chr>    <chr> <chr>   <chr>   
#> 1 T01   SupCar   toy   vehicle yes     
#> 2 T02   SupPlane toy   vehicle no

或选取releasedno的行:

代码语言:javascript
复制
product_info[product_info$released == "no", ]
#> # A tibble: 2 x 5
#>   id    name     type  class   released
#>   <chr> <chr>    <chr> <chr>   <chr>   
#> 1 T02   SupPlane toy   vehicle no      
#> 2 M04   Dancer   model people  no

对列进行筛选需要将第1个参数留空,给第2个参数提供字符向量。

代码语言:javascript
复制
product_info[, c("id", "name", "type")]
#> # A tibble: 6 x 3
#>   id    name      type 
#>   <chr> <chr>     <chr>
#> 1 T01   SupCar    toy  
#> 2 T02   SupPlane  toy  
#> 3 M01   JeepX     model
#> 4 M02   AircraftX model
#> 5 M03   Runner    model
#> 6 M04   Dancer    model

行列筛选也是可以的,我们只要组合前面的两种情况即可。

代码语言:javascript
复制
product_info[product_info$type == "toy", c("name", "class", "released")]
#> # A tibble: 2 x 3
#>   name     class   released
#>   <chr>    <chr>   <chr>   
#> 1 SupCar   vehicle yes     
#> 2 SupPlane vehicle no

内置函数subset()可以简化取子集操作的过程:

代码语言:javascript
复制
subset(product_info,
       subset = type == "model" & released == "yes",
       select = name:class)
#> # A tibble: 3 x 3
#>   name      type  class  
#>   <chr>     <chr> <chr>  
#> 1 JeepX     model vehicle
#> 2 AircraftX model vehicle
#> 3 Runner    model people

使用with()函数在数据框的语义中计算表达式,即可以直接使用数据框的列名,而不必重复指定数据框:

代码语言:javascript
复制
with(product_info, name[released == "no"])
#> [1] "SupPlane" "Dancer"

除了构建子集,表达式还可以用来统计每列各个可能值出现的频数。

代码语言:javascript
复制
with(product_info, table(type[released == "yes"]))
#> 
#> model   toy 
#>     3     1

除了产品信息表,还有一张产品属性的统计表:

代码语言:javascript
复制
product_stats = read_csv("../../R/dataset/product-stats.csv")
#> Parsed with column specification:
#> cols(
#>   id = col_character(),
#>   material = col_character(),
#>   size = col_integer(),
#>   weight = col_double()
#> )
product_stats
#> # A tibble: 6 x 4
#>   id    material  size weight
#>   <chr> <chr>    <int>  <dbl>
#> 1 T01   Metal      120   10  
#> 2 T02   Metal      350   45  
#> 3 M01   Plastics    50   NA  
#> 4 M02   Plastics    85    3  
#> 5 M03   Wood        15   NA  
#> 6 M04   Wood        16    0.6

如果现在要获取尺寸最大的前3个产品的名字该怎么办?

一种方法是将product_stats按尺寸降序排列,选择前3个记录的id,然后用id值筛选product_info的行:

代码语言:javascript
复制
top3_id = unlist(product_stats[order(product_stats$size, decreasing = TRUE), "id"])[1:3]

product_info[product_info$id %in% top3_id, ]
#> # A tibble: 3 x 5
#>   id    name      type  class   released
#>   <chr> <chr>     <chr> <chr>   <chr>   
#> 1 T01   SupCar    toy   vehicle yes     
#> 2 T02   SupPlane  toy   vehicle no      
#> 3 M02   AircraftX model vehicle yes

我们用比较冗长的方式完成了任务。但仔细在想想,两个数据框是通过id连接到一起的,我们可以把它们合并到一起,然后执行提取操作:

代码语言:javascript
复制
product_table = merge(product_info, product_stats, by = "id")
product_table
#>    id      name  type   class released material size weight
#> 1 M01     JeepX model vehicle      yes Plastics   50     NA
#> 2 M02 AircraftX model vehicle      yes Plastics   85    3.0
#> 3 M03    Runner model  people      yes     Wood   15     NA
#> 4 M04    Dancer model  people       no     Wood   16    0.6
#> 5 T01    SupCar   toy vehicle      yes    Metal  120   10.0
#> 6 T02  SupPlane   toy vehicle       no    Metal  350   45.0

现在通过合并的数据框,我们可以根据任意一列排序数据框,而不需要处理其他的表格数据:

代码语言:javascript
复制
product_table[order(product_table$size), ]
#>    id      name  type   class released material size weight
#> 3 M03    Runner model  people      yes     Wood   15     NA
#> 4 M04    Dancer model  people       no     Wood   16    0.6
#> 1 M01     JeepX model vehicle      yes Plastics   50     NA
#> 2 M02 AircraftX model vehicle      yes Plastics   85    3.0
#> 5 T01    SupCar   toy vehicle      yes    Metal  120   10.0
#> 6 T02  SupPlane   toy vehicle       no    Metal  350   45.0

前面的问题我们也可以利用合并的数据框加以解决:

代码语言:javascript
复制
product_table[order(product_table$size, decreasing = TRUE), "name"][1:3]
#> [1] "SupPlane"  "SupCar"    "AircraftX"

有时候我们需要生成新数据框来对原始数据基础上进行调整和处理,从而避免破坏原始数据。transform()函数可以帮助我们完成这类任务,例如:

代码语言:javascript
复制
transform(product_table,
          released = ifelse(released == "yes", TRUE, FALSE),
          density = weight / size)
#>    id      name  type   class released material size weight density
#> 1 M01     JeepX model vehicle     TRUE Plastics   50     NA      NA
#> 2 M02 AircraftX model vehicle     TRUE Plastics   85    3.0  0.0353
#> 3 M03    Runner model  people     TRUE     Wood   15     NA      NA
#> 4 M04    Dancer model  people    FALSE     Wood   16    0.6  0.0375
#> 5 T01    SupCar   toy vehicle     TRUE    Metal  120   10.0  0.0833
#> 6 T02  SupPlane   toy vehicle    FALSE    Metal  350   45.0  0.1286

前面数据中我们看到有一些缺失值(用NA表示),很多时候我们不希望数据出现任何缺失值,因此需要某种办法处理它们。为了演示处理的方法,我们再载入一张包含缺失值的表,包含每件产品的质量、耐久性、防水性的测试结果。

代码语言:javascript
复制
product_tests = read_csv("../../R/dataset/product-tests.csv")
#> Parsed with column specification:
#> cols(
#>   id = col_character(),
#>   quality = col_integer(),
#>   durability = col_integer(),
#>   waterproof = col_character()
#> )
product_tests
#> # A tibble: 6 x 4
#>   id    quality durability waterproof
#>   <chr>   <int>      <int> <chr>     
#> 1 T01        NA         10 no        
#> 2 T02        10          9 no        
#> 3 M01         6          4 yes       
#> 4 M02         6          5 yes       
#> 5 M03         5         NA yes       
#> 6 M04         6          6 yes

na.omit()可以删除所有包含缺失值的行:

代码语言:javascript
复制
na.omit(product_tests)
#> # A tibble: 4 x 4
#>   id    quality durability waterproof
#>   <chr>   <int>      <int> <chr>     
#> 1 T02        10          9 no        
#> 2 M01         6          4 yes       
#> 3 M02         6          5 yes       
#> 4 M04         6          6 yes

另外,函数complete.cases()可以返回逻辑向量,表明某行是否完整。

代码语言:javascript
复制
complete.cases(product_tests)
#> [1] FALSE  TRUE  TRUE  TRUE FALSE  TRUE

利用该函数可以筛选数据框,比如获得不含缺失值的id值:

代码语言:javascript
复制
product_tests[complete.cases(product_tests), "id"]
#> # A tibble: 4 x 1
#>   id   
#>   <chr>
#> 1 T02  
#> 2 M01  
#> 3 M02  
#> 4 M04

前面给出的3个表格有共同的id列,可惜R里面内置函数只能一次合并2个数据框:

代码语言:javascript
复制
product_full = merge(product_table, product_tests, by = "id")
product_full
#>    id      name  type   class released material size weight quality
#> 1 M01     JeepX model vehicle      yes Plastics   50     NA       6
#> 2 M02 AircraftX model vehicle      yes Plastics   85    3.0       6
#> 3 M03    Runner model  people      yes     Wood   15     NA       5
#> 4 M04    Dancer model  people       no     Wood   16    0.6       6
#> 5 T01    SupCar   toy vehicle      yes    Metal  120   10.0      NA
#> 6 T02  SupPlane   toy vehicle       no    Metal  350   45.0      10
#>   durability waterproof
#> 1          4        yes
#> 2          5        yes
#> 3         NA        yes
#> 4          6        yes
#> 5         10         no
#> 6          9         no

对完全合并好的表格,我们利用tapply()函数(apply家族成员)可以进行统计,该函数专门用于处理表格数据,使用某些方法根据某列队另一列的数据进行统计。

例如根据type列计算quality列的均值:

代码语言:javascript
复制
mean_quality1 = tapply(product_full$quality,
                       list(product_full$type),
                       mean, na.rm=TRUE)
mean_quality1
#> model   toy 
#>  5.75 10.00

返回的结果看起来是个数值向量,我们使用str()看看:

代码语言:javascript
复制
str(mean_quality1)
#>  num [1:2(1d)] 5.75 10
#>  - attr(*, "dimnames")=List of 1
#>   ..$ : chr [1:2] "model" "toy"

实际上,这是个一维数组

代码语言:javascript
复制
is.array(mean_quality1)
#> [1] TRUE

tapply()返回的是一个数组,而不是简单的数值向量,因此可以方便地计算多组操作。

例如计算每一对typeclass组合的quality均值:

代码语言:javascript
复制
mean_quality2 = tapply(product_full$quality,
                       list(product_full$type, product_full$class),
                       mean, na.rm = TRUE)
mean_quality2
#>       people vehicle
#> model    5.5       6
#> toy       NA      10

对于二维数组,我们可以使用两个参数来获取其中的值:

代码语言:javascript
复制
typeof(mean_quality2)
#> [1] "double"
class(mean_quality2)
#> [1] "matrix"

mean_quality2["model", "vehicle"]
#> [1] 6

同理我们可以根据多列分组,使用with()可以避免反复输入product_full

代码语言:javascript
复制
mean_quality3 = with(product_full,
                     tapply(quality, list(type, material, released),
                            mean, na.rm = TRUE))

mean_quality3
#> , , no
#> 
#>       Metal Plastics Wood
#> model    NA       NA    6
#> toy      10       NA   NA
#> 
#> , , yes
#> 
#>       Metal Plastics Wood
#> model    NA        6    5
#> toy     NaN       NA   NA

使用3个参数可以获取单元格中的值:

代码语言:javascript
复制
mean_quality3["model", "Wood", "yes"]
#> [1] 5

reshape2重塑数据框

前面我们学习了如何筛选、排序、合并和汇总数据框,有时候我们需要做些更复杂的操作。

例如下面数据包含两种产品不同日期的质量和耐久性的测试结果:

代码语言:javascript
复制
toy_tests = read_csv("../../R/dataset/product-toy-tests.csv")
#> Parsed with column specification:
#> cols(
#>   id = col_character(),
#>   date = col_integer(),
#>   sample = col_integer(),
#>   quality = col_integer(),
#>   durability = col_integer()
#> )
toy_tests
#> # A tibble: 8 x 5
#>   id        date sample quality durability
#>   <chr>    <int>  <int>   <int>      <int>
#> 1 T01   20160201    100       9          9
#> 2 T01   20160302    150      10          9
#> 3 T01   20160405    180       9         10
#> 4 T01   20160502    140       9          9
#> 5 T02   20160201     70       7          9
#> 6 T02   20160303     75       8          8
#> 7 T02   20160403     90       9          8
#> 8 T02   20160502     85      10          9

如果需要同时比较两种产品的质量和耐久性,这种格式就比较麻烦,如果是下面的格式就好了:

代码语言:javascript
复制
date    T01 T02
20160201    9   9
2016    10  9

reshape2包就是用来搞定这种任务的,如果没有安装,运行下面代码:

代码语言:javascript
复制
install.packages("reshape2")

安装成功后,我们就可以使用dcast()来转换数据,用于比较:

代码语言:javascript
复制
library(reshape2)

toy_quality = dcast(toy_tests, date ~ id, value.var = "quality")
toy_quality
#>       date T01 T02
#> 1 20160201   9   7
#> 2 20160302  10  NA
#> 3 20160303  NA   8
#> 4 20160403  NA   9
#> 5 20160405   9  NA
#> 6 20160502   9  10

上述代码重塑了toy_testsdate列被共享,id值被单独分割为列,每个dateid对应的值是quality

可以看到数据中存在缺失值,有一种叫末次观测值结转法(LOCF)可以填补缺失值,当非缺失值后面紧跟一个缺失值时,就用该缺失值填补后面的缺失值,直到所有缺失值都被填满。zoo包提供了LOCF的一个实现,使用下面代码安装:

代码语言:javascript
复制
install.packages("zoo")

下面用一组简单的向量演示:

代码语言:javascript
复制
library(zoo)
#> 
#> 载入程辑包:'zoo'
#> The following objects are masked from 'package:base':
#> 
#>     as.Date, as.Date.numeric
na.locf(c(1, 2, NA, NA, 3, 1, NA, 2, NA))
#> [1] 1 2 2 2 3 1 1 2 2

同样的方法我们可以应用于现在处理的数据:

代码语言:javascript
复制
na.locf(toy_quality$T01)
#> [1]  9 10 10 10  9  9

如果需要填补的数据很多,包含上千个产品,更好的做法是使用lapply进行自动分配:

代码语言:javascript
复制
toy_quality[-1] = lapply(toy_quality[-1], na.locf )
toy_quality
#>       date T01 T02
#> 1 20160201   9   7
#> 2 20160302  10   7
#> 3 20160303  10   8
#> 4 20160403  10   9
#> 5 20160405   9   9
#> 6 20160502   9  10
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 优雅R 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用内置函数操作数据框
    • 内置函数操作数据框
      • reshape2重塑数据框
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档