前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >37. R 茶话会(七:高效的处理数据框的列)

37. R 茶话会(七:高效的处理数据框的列)

作者头像
北野茶缸子
发布2021-12-17 09:18:01
1.5K0
发布2021-12-17 09:18:01
举报

前言

这个笔记的起因是在学习DataExplorer 包的时候,发现:

这我乍一看,牛批啊。这语法还挺长见识的。

转念思考了一下🤔,其实目的也就是将数据框中的指定列转换为因子。换句话说,就是如何可以批量的对数据框的指定行或者列进行某种操作。(这里更多强调的是对原始数据框的直接操作,如果是统计计算直接找summarise 和它的小伙伴们,其他的玩意儿也各有不同,掉头左转:

34. R 数据整理(六:根据分类新增列的种种方法 1.0)

其实按照我的思路,还是惯用的循环了,对数据框的列名判断一下,如果所取的列在数据框中,就修改一下其格式,重新赋值:

代码语言:javascript
复制
data(cancer, package = "survival")

colon3 <- colon

tmp <- sapply(vars, function(x) {
  if (x %in% colnames(colon3)) colon3[, x] <<- as.factor(colon3[,x]) 
})
str(colon3)

现在来总结一下,具体参见:vignette("colwise"):

统计数据处理

基本处理

这里示例数据如下:

代码语言:javascript
复制
test2 <- data.frame(id = sample(LETTERS[1:10], 30, replace = T),
                    a = sample(1:10, 30, replace = T),
                    b = sample(1:100, 30, replace = T),
                    c = sample(1:1000, 30, replace = T))
str(test2)
'data.frame': 30 obs. of  4 variables:
 $ id: chr  "I" "C" "B" "H" ...
 $ a : int  4 4 10 4 7 1 9 5 3 6 ...
 $ b : int  56 100 94 11 45 30 66 1 36 81 ...
 $ c : int  610 158 715 181 862 91 945 204 63 218 ...

如果需要批量计算统计数据,需要借助summarise 函数。

比较粗暴的就是,一行一行的手动写。

批量有两种操作:

  • summarise_at

(也可以先select 再summarise_all) :

代码语言:javascript
复制
> test2 %>% summarise_at(
+   c("a", "b", "c"),
+   list(avg = ~mean(.), std = ~sd(.)), na.rm=TRUE)
     a_avg b_avg c_avg  a_std    b_std    c_std
1 5.066667  51.2 425.8 2.8519 30.93865 315.7111

这时候有不聪明的小朋友要问了,如果取反呢?比如我的数据里,只有一个分类数据,对其取反取数更加容易。

我这里举两个例子:

代码语言:javascript
复制
> test2 %>% select(-any_of("id")) %>% summarise_all(
+   list(avg = ~mean(.), std = ~sd(.)), na.rm=TRUE)
     a_avg b_avg c_avg  a_std    b_std    c_std
1 5.066667  51.2 425.8 2.8519 30.93865 315.7111

test2 %>% summarise_at(
  setdiff(colnames(test2), "id"),
  list(avg = ~mean(.), std = ~sd(.)), na.rm=TRUE)

本质都是取反。

  • across
代码语言:javascript
复制
test2 %>% summarise(across(-any_of("id"), mean))     

across 必须要在mutate 或summarise 这类函数内部,对数据框的列进行类似apply 的批量操作。其第一个参数需要是列名。也可以:

代码语言:javascript
复制
test2 %>% summarise(across(-where(is.character), mean))  

其中where 类似base 中的which,相当于接受逻辑值,以返回对应位置。

和summarise_all 一样,其本质也可以接受list 传递函数:

代码语言:javascript
复制
test2 %>% summarise(
  across(-where(is.character), list(avg = ~ mean(.), std = ~ sd(.))))    

分组处理

非常的简单,加个group_by 即可:

代码语言:javascript
复制
> test2 %>% group_by(id) %>% summarise_at(
+   setdiff(colnames(test2), "id"),
+   list(avg = ~ mean(.), std = ~ sd(.)), na.rm=TRUE)
# A tibble: 10 × 7
   id    a_avg b_avg c_avg  a_std b_std c_std
   <chr> <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl>
 1 A      3     28   655   NA     NA     NA  
 2 B      8.5   69.5 788.   2.12  34.6  104. 
 3 C      4     86.5 406.   0.816 15.3  372. 
 4 D      5     54.5 497.   3.16  21.0  388. 
 5 E      2.67  35.3 402.   1.53  32.6  380. 
 6 F      7.33  48.5 434.   1.86  36.8  289. 
 7 G      4      4    69   NA     NA     NA  
 8 H      7.5   36.8 440.   2.38  33.9  359. 
 9 I      2.5   72   560    2.12  22.6   70.7
10 J      1.67  37.7  56.7  1.15   8.62  37.9

如果你问NA 值怎么办~ 我会说,加个什么,加个什么,加个filter(n() > 1) 试试吧~

其他处理

  • 自定义分组后名称

across 还提供了参数,可以自定义返回的名称:

代码语言:javascript
复制
> test2 %>% summarise(
+   across(-where(is.character), list(avg = ~ mean(.), std = ~ sd(.)), 
+          .names = "test2_{.col}_{.fn}"))       
  test2_a_avg test2_a_std test2_b_avg test2_b_std test2_c_avg test2_c_std
1    5.066667      2.8519        51.2    30.93865       425.8    315.7111

默认的是:

代码语言:javascript
复制
> test2 %>% summarise(
+   across(-where(is.character), list(avg = ~ mean(.), std = ~ sd(.))))       
     a_avg  a_std b_avg    b_std c_avg    c_std
1 5.066667 2.8519  51.2 30.93865 425.8 315.7111

如果不用list 传递的话,因为没有名字,所以需要自定义名称了:

代码语言:javascript
复制
> test3 %>% summarise(
+   across(c("a","b"), ~ mean(.), .names = "{.col}_{.fn}"), 
+   across(c("a", "b"), ~ sd(.), .names = "{.col}_std"), 
+   )       
# A tibble: 1 × 4
    a_1   b_1 a_std b_std
  <dbl> <dbl> <dbl> <dbl>
1  5.07  51.2  2.85  30.9
  • 重排顺序

比如:

代码语言:javascript
复制
> test2 %>% summarise(
+   across(-where(is.character), 
+          list(avg = ~ mean(.),
+               std = ~ sd(.)),
+          .names = "test2_{.col}_{.fn}",
+          na.rm = T
+          )
+ ) %>% relocate(ends_with("avg"))
  test2_a_avg test2_b_avg test2_c_avg test2_a_std test2_b_std test2_c_std
1    5.066667        51.2       425.8      2.8519    30.93865    315.7111

这样就可以让结果以avg 结尾的先输出。

和select 这些一样,他们也有一些挑列的专属函数:

代码语言:javascript
复制
select(test, starts_with("Petal")) #选中..开头的列
select(test, ends_with("Width")) #选中..结尾的列
select(test, contains("etal")) #选中包含..的列
select(test, matches(".t.")) #选中符合某正则表达的列
select(test, everything()) #选中所有列,可以使指定的列先提前
select(test, last_col()) #选中最后一列
select(test, last_col(offset = 1)) #选中倒数第二列。offset 表示忽略n个。忽略最后一个即表示选择倒数第二个。
  • 陷阱

如果我们先进行了某步运算,比如统计数目,再across 循环某种运算,则有NA 风险:

代码语言:javascript
复制
df <- data.frame(x = c(1, 2, 3), y = c(1, 4, 9))

df %>% 
  summarise(n = n(), across(where(is.numeric), sd))
#>    n x        y
#> 1 NA 1 4.041452

因为across 也对 n = 3 这一数据计算了sd。

有两种策略:

代码语言:javascript
复制
df %>% 
  summarise(across(where(is.numeric), sd), n = n())
#>   x        y n
#> 1 1 4.041452 3

df %>% 
  summarise(n = n(), across(where(is.numeric) & !n, sd))
#>   n x        y
#> 1 3 1 4.041452

ps:发现这个 ! 取反的方法只对across 的.cols 参数生效。

代码语言:javascript
复制
> test2 %>% group_by(id) %>% summarise_at(
+   colnames(test2,) & !id, 
+   list(avg = ~ mean(.), std = ~ sd(.)), na.rm=TRUE)
Error in !id : 参数种类不对
  • 自定义统计函数
代码语言:javascript
复制
> rescale01 <- function(x) {
+   rng <- range(x, na.rm = TRUE)
+   (x - rng[1]) / (rng[2] - rng[1])
+ }
> df <- tibble(x = 1:4, y = rnorm(4))
> df %>% summarise(across(where(is.numeric), rescale01))
# A tibble: 4 × 2
      x     y
  <dbl> <dbl>
1 0     0.127
2 0.333 1    
3 0.667 0.756
4 1     0  

直接对数据框处理

这里就回到开始的问题了,如果是希望对数据框本身进行处理,而非统计学运算呢?

也很简单,无非是summarise 换成了mutate。

比如:

代码语言:javascript
复制
df <- tibble(x = 1:3, y = 3:5, z = 5:7)
mult <- list(x = 1, y = 10, z = 100)

df %>% mutate(across(all_of(names(mult)), ~ .x * mult[[cur_column()]]))
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1    30   500
#> 2     2    40   600
#> 3     3    50   700

总结

  • 为什么选择across?

因为它好用啊。

  1. 批量处理
  2. 组合一般的运算
  3. 逻辑判断方便获得指定列(通过& )
  4. 无缝结合tidyverse 中的其他函数

image.png

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 北野茶缸子 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 统计数据处理
    • 基本处理
      • 分组处理
        • 其他处理
        • 直接对数据框处理
        • 总结
        相关产品与服务
        批量计算
        批量计算(BatchCompute,Batch)是为有大数据计算业务的企业、科研单位等提供高性价比且易用的计算服务。批量计算 Batch 可以根据用户提供的批处理规模,智能地管理作业和调动其所需的最佳资源。有了 Batch 的帮助,您可以将精力集中在如何分析和处理数据结果上。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档