【tidyverse】part3:数据转化

1. 数据操作:dplyr包应用

dplyr包是为数据分析提供了一系列快捷有效的操作,其中有五个关键函数基本可以解决掉大多数在数据转化预处理时的困难:

  • 筛选函数:filter()
  • 排列函数:arrange()
  • 选取函数:select()
  • 添加变量函数:mutate()
  • 分组统计函数:summarize()

这五个核心函数构成了dplyr包的基础,虽然在plyr包中也会有相对应的函数,但是dplyr包是为data.frame数据集而设计的;

并且这五个关键函数都可以和group_by()来结合使用,因此总的来说是这六个函数为数据操作提供了十分重要的帮助;

同时,这几个函数的语法参数基本上都有共同之处:

  1. 第一个参数为数据集(data.frame)
  2. 之后便是一系列对数据集进行操作的命令
  3. 并且最后的结果并不会对原始数据产生任何影响,而是以一个新的数据集来展示

因此如果需要保存新的数据集,就需要额外赋值

本小节使用nycflights13::flights数据集

1.1 基本数据操作

筛选函数:filter

filter()函数之所以能对数据进行操作主要源于一系列的命令参数,这其中最重要的是通过比较运算符和逻辑运算符来对数据进行筛选:

  • 比较运算符:>, >=, <, <=, != (不等于),== (等于)
  • 逻辑运算符:|,&(且,或)

《【tidyverse】part3:数据转化》 逻辑运算符.png

)

除了能单独使用外,二者大多数都是搭配使用

#filter函数语法
filter(data, sequence_arguments,...)

#用法
flights %>% 
  filter(month == 11 | month == 12)

flights %>% 
  filter(arr_delay <= 120, dep_delay <= 120)

以上操作是针对非缺失值而言,filter的筛选命令生效是在条件(condition,有点类似于循环语句)为「TRUE」的情况下才能进行,往往并不包括「NA」值和「FALSE」条件
因此,如果筛选缺失值,不能直接使用比较运算符,只能通过R中的内置缺失值判断函数来进行「TRUE的操作」,比如:is.na(x)

df <- tibble(x = c(1, NA, 3))
filter(df, is.na(x) | x > 1)

查看更多参数可以使用?filter()查阅帮助文档

排列函数:arrange

排列函数与筛选函数也有着筛选的功能,但更多的是起到排序的作用

#arrange函数语法
arrange(data, sequence_argument,...)

#用法
arrange(flights, year, month, day)
  • arrange()函数根据给出的关键字(「year」、「month」、「day」)对数据集进行重新排列
  • 默认为升序排列

如果想对数据集进行降序排列,则可以使用desc(var)参数

arrange(flights, desc(arr_delay))

如果数据集中存在缺失值,缺失值无论是升序还是降序,永远都排列在最后面

选取函数:select

在一个数据集中有成千上百个变量是不常见的,当然在分析过程中除了要对数据进行预处理外,更重要的是缩小要关注的变量的范围,而select()函数就可以为这一过程提供十分快捷、有效的操作

#select函数语法
select(data, sequence_argument, everything())
  • select()的语法是相对简单的,在数据集之后的参数,除了可以输入要选取的变量之外,还可以搭配其他字符匹配函数(字符串处理:stringr包应用会有涉及)来进行操作:

    • starts_with('abc'):匹配开头为’abc’的值
    • ends_with('xyz'):匹配结尾为’xyz’的值
    • contains('ijk'):匹配包含’ijk’的值
    • matches('(.)\\1'):通过正则表达式来匹配
    • num_range('x', 1:3):选取x1~x3变量;类似于基础的R索引搭配paste()函数,用来选取命名规律的变量
    • one_of():可以将变量名放入一个向量中,再将这个向量放进函数中可以缩减代码长度
    • everything()参数表示将要选取的变量放到所有变量的最前面进行展示

同时还有一个与select()函数有着相似功能的函数,但是其并不履行着选取的功能,反而是起着重命名的作用——rename()函数:

#rename函数语法
rename(data, new_var_name = old_var_name,...)

当然select()函数也可以起到这功能,但是二者有着差别:

  • select()在修改变量名后会单独选取已修改命名的变量展示
  • rename()在修改变量名后会选取所有变量进行展示

添加变量函数:mutate

mutate()函数是一个能在数据集中根据已有的变量,来添加新变量的函数;在R的基础函数中transform()函数也起着相同的作用

  • 新加入的变量排在最后
  • 并且可以在添件新变量之前,使用预新建变量再来生成新变量
#mutate函数语法
mutate(data, new_var = old_var_argument,...)

#运用例子
flights_sml <- flights %>% 
  select(year:day, ends_with('delay'),
         distance, air_time) %>% 
  mutate(
    gain = arr_delay - dep_delay,
    speed  = distance / air_time *60
 )

#使用预新建变量再生成其他新变量
flights_sml <- flights %>% 
  mutate(
    gain = arr_delay - dep_delay,
    hours = air_time /60,
    gain_per_hour = gain / hours
 )

如果仅想保留新建的变量,则将mutate()函数替换为transmute()函数即可;
虽然添加变量函数便于理解,但是可能会涉及到其他运算来进行更复杂的变量生成:

  • 加减乘除运算
  • 整除(x%/%y)和求余(x%%y)
  • 取对数
  • 偏移量(lag:滞后差分和lead前移)
  • 累积运算(R提供了cum*,dplyr包提供了cummean)
  • 逻辑比较运算
  • 秩运算

分组与输出统计量函数:group_by和summarize

summarize()函数如果是单独使用时效果并不是特别好,但是如果搭配group_by()函数,进行分组统计输出,就能起到意想不到的效果(如分组求和等);这就有些类似于R基础函数aggregate()

#分组与输出统计量函数语法
group_by(data, by_vars, ...)
summarize(data, new_col = func(var, na.rm =))

#运用案例1
by_day <- group_by(flights, year,month, day) %>% 
  summarize(delay = mean(dep_delay, na.rm = T))
by_day

#运用案例2
delays <- flights %>% 
  group_by(dest) %>% 
  summarize(
    count = n(),
    dist = mean(distance, na.rm = T),
    delay = mean(arr_delay, na.rm = T)
  ) %>% 
  filter(count > 20, dest != 'HNL')

  • 当中的n()其实是一个起着类似于R基础的table()频数统计功能的函数,但是只能用在summarize()mutate()filter()中;如果想单独实现频数统计功能,则使用count()函数

小结

  • dplyr包的基础函数是:
    • 筛选函数:filter()
    • 排列函数:arrange()
    • 选取函数:select()
    • 添加变量函数:mutate()
    • 分组与统计量输出函数:“group_bysummarize()`
  • 所有函数搭配管道操作符来使用会更加快捷高效且简洁
  • 所有函数输出的结果都不会保存在内存中,如果需要存储结果,需要另外赋值
  • 所有函数的参数都十分简单,第一个重要的参数是数据集,之后才是对数据集进行的一系列操作参数

1.2 联结

有时一个数据分析很难只会涉及到一个数据集,还有可能涉及到其他的数据集,因此需要额外的将一个数据集和另一个相关的数据集进行「联结」,从而结合创造出一个新的、包含自己需要的变量的数据集

《【tidyverse】part3:数据转化》 flights数据集.png

因此这就需要使用到有关「联结」,而dplyr包就提供了类似于SQL数据库的「JOIN语句」:

  • 添加联结
  • 筛选联结
  • 集合操作符

而联结在SQL中又是通过「键-值」来实现联结的

「键」又有两种:

  • 主键:一个表中的唯一识别码,类似身份证
  • 外键:可以简单理解中对别的表的主键的联结

一个变量(列)既可以是主键,也可以是外键,因此正确识别并找出一个数据集的「键」,才能为联结地开展提供便利;通过「键」能将不同数据集的多个观察值通过键联结起来(回想分散与聚合函数使用到的key和value)

添加联结

添加联结主要有以下两种类型:

  • 内联结(键能一一对应)inner_join()
  • 外联结:
    • 左联结:left_join()
    • 右联结:right_join()
    • 全联结:full_join()

通过图片来理解外联结或许会更直白一点
《【tidyverse】part3:数据转化》 外联结图示.png

Venn图

《【tidyverse】part3:数据转化》 联结逻辑Venn图.png

#join函数语法
*_join(x, y, by = 'var')

R基础的函数中有提供merge()函数,用法与join类似

以上情况都是基于「键」为唯一时才能起到真正的作用,如果键不唯一时,可能会出现一键对多值的情况发生

筛选联结
添加联结是在保留原有的数据集的情况下,再加入其他数据集的变量,通过一个共有变量来联结两个数据集;而筛选联结则进一步加深了保留的操作,有保留和删除两种

  • semi_join(x,y)保留所有x中与y匹配的观察值
  • anti_join(x,y)除去所有x中与y匹配的观察值

集合操作符

  • intersect(x,y):返回x和y都有存在的观察值
  • union(x,y):返回x和y的非重复值
  • setdiff(x,y):返回在x中存在,但是在y中不存在的观察值

小结

  • 联结可以理解为:以一个共同的变量来联结不同的数据集
  • 联结的操作与SQL语句类似,如果有了解过JOIN语句在本小节便能更快的理解dplyr包提供的*_join()函数
  • 联结最主要的核心思想就是「键-值」

2. 字符串处理:stringr包应用

2.1 字符串基础

字符串创建

  • 使用「’ ‘」或「” “」来表示字符串
  • 可以使用一个向量来囊括所有带引号的字符串
  • 使用「\」来表示转意符
  • 「\n」表示换行符,「\t」制表符

字符串函数

  • str_length()用来计算字符串长度
  • str_c()用来联结多个字符串,类似于paste()函数
  • str_replace_na()可以将所有NA值在打印时,现实NA

字符串筛选

使用str_sub()函数可以筛选出向量中的特定字符串

#str_sub函数语法
str_sub(string, start = 1L, end = -1L)

#运用案例
x <- c("Apple", "Banana", "Pear") 
str_sub(x, 1, 3) #如果是负数,则是倒数

字符样式转换

对于字符大小写转换,stringr包提供了三个基本的函数:

  • str_to_upper():转换为大写
  • str_to_lower():转换为小写
  • str_to_title():首字母大写

这三个函数中都有一个locale参数,可以用来指定文本的语言类型,默认为英语

2.2 正则表达式

stringr包提供了两个匹配的函数:

  • str_view()
  • str_view_all()

两者语法一致

str_view(string, pattern, match = NA)
str_view_all(string, pattern, match = NA)
  • 第一个参数为字符串向量
  • 第二个参数为要匹配的条件

在匹配过程中,往往可能需要用到其他符号:

  • 「.」表示通配符
  • 「\」表示转意符,表达正则表达式时,只使用一个转意符会造成错误,因此需要再添加一个
str_view(c('abc','a.c','bdf'), 'a\.c') #报错
str_view(c('abc','a.c','bdf'), 'a\\.c') #正确显示

2.3 字符匹配

2.3.1 锚键匹配

锚键时用来匹配开头和结尾为某个字符的正则表达式:

  • ^用来匹配开头字符
  • $用来匹配结尾字符
x <- c("apple", "banana", "pear") 
str_view(x, "^a")
str_view(x, 'a$')

2.3.2 字符类型与其他选项匹配

对于包含数字的文本以及包含分隔线的字符,以下有4个方式可以进行匹配:

  • \d匹配任何数字
  • \s匹配所有空白
  • [abc]匹配a或b或c
  • [^abc]表示匹配除了a或b或c以外的字符

在实际匹配中,可以使用逻辑运算符来表示其他选项,这样就会匹配每一种可能的情况:

str_view(c('grey','gray'), 'gr(e|a)y')

2.3.3 重复匹配

有时在一个字符串种会出现重复出现的字符,因此有三种方式可以来进行重复出现情况的匹配:

  • ?表示0或1次
  • +表示1次或多次
  • *表示0次或多次
x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
str_view(x, "CC?")
str_view(x, "CC+")
str_view(x, "CC*")

初次之外,还可以使用花括号的形式来表达次数:

  • {n}表示精确出现的次数
  • {n,}表示大于等于n次
  • {,m}表示小于等于m次
  • {n,m}表示处于[n,m]区间的次数

简单可以理解为花括号为一种区间的表达形式,且这种区间时闭区间

str_view(x, "C{2}")
str_view(x, "C{2,}")
str_view(x, "C{,3}")
str_view(x, "C{2,3}")

并且默认的匹配往往都是将所有字符都匹配出来,但是如果仅仅是想快捷的找出存在的字符串,而不需要匹配那么多字符,可以在花括号后面加上一个「?」

2.4 stringr工具

stringr包除了提供基础的正则表达式检索之外,还提供了其他额外的实用功能:

  • 明确匹配字符串
  • 查找匹配位置
  • 提取匹配内容
  • 以新值来替代匹配
  • 分隔字符串匹配

明确匹配字符串

stringr包提供了返回逻辑值的字符串匹配函数str_detect(),用来快速检索匹配字符串中是否包含字符并返回逻辑值结果,有点类似于R基础的is.*()函数族用法

#str_detect函数语法
str_detect(string, pattern)

#运用案例
x <- c("apple", "banana", "pear") 
str_detect(x, "e")

str_count()str_detect()的进阶版本:除了返回逻辑值外,还能计算包含字符的字符串频数

提取匹配内容

str_extract()函数提供提取匹配内容的功能,通过匹配相应的字符,返回符合匹配条件的观测值集合

color_match <- str_c(colors, collapse = "|")
has_color <- str_subset(sentences, color_match)
matches <- str_extract(has_color, color_match)
head(matches)

这仅仅是进行单项匹配,如果还希望进行全匹配,可以使用str_extract_all()

替代匹配

stringr包提供了两个参数来批量替代掉文本中的字符:

  • str_replace()
  • str_replace_all()

二者语法一致

#函数语法
str_replace(string, pattern, replacement)
  • 第二个参数为匹配的字符
  • 第三个参数为用来替换匹配字符的字符

分割字符串

类似于R基础函数中的split()函数,stringr包也提供了一个用来分割字符串的函数str_split()

#str_split函数语法
str_split(string, pattern, n = Inf, simplify = FALSE)
  • 第二个参数为指定的分隔符
  • n参数为输出分隔后结果的数量,默认为Inf
  • simplify参数是将输出后的结果转化为更轻量的矩阵

除了使用分隔符来分割字符外,还可以使用字符、线、句子、单词等用来分割字符,但这就需要用到boundary()函数来指定

x <- "This is a sentence. This is another sentence." str_view_all(x, boundary("word"))
str_split(x, boundary("word"))[[1]]

小结

  • stringr包提供了许多字符处理的函数,命名上基本都是一致以str_开头,当输入命令后Rstudio会自动跳出提示
  • 对字符串处理最重要的就是熟悉、掌握正则表达式的检索用法,之后才能对匹配的文本或字符使用str_sub()进行筛选并用str_extract()进行提取

3. 因子处理:forcats包应用

forcats包是专门用来处理分类变量的包,但是没有包含在tidyverse的核心包中,因此需要自己额外安装并加载

if(!require(forcats))(install.packages('forcats'))
library(forcats)

一般而言,如果不指定levels,那么因子变量的值(英文)排序会根据开头的字母表来排序

R基础函数中提供了查看一个因子类型变量的水平以及标签的函数:

  • labels():查看标签
  • levels():查看水平(排序)

处理因子变量面临的最主要的两个问题是:

  • 因子水平的顺序排布
  • 改变因子水平值

调整因子顺序

改变因子水平的第一步是对因子进行可视化,这样可以更为直观的发现因子水平的分布,之后可以使用fct_reorder()函数对因子重新进行水平的调整

relig <- gss_cat
fct_reorder(relig$relig, relig$tvhours)
  • 第一个参数为要调整的因子
  • 第二个参数为用来调整因子水平的数值型向量

该函数在进行可视化展示时可以省去排序调整这一步骤,能方便快捷进行分析

同时forcats包提供了能将特定水平值提前到最前面的函数:
fct_relevel()

  • 第一个参数为要调整的因子
  • 第二个参数为因子水平的值

对于需要统计各因子频数的情况,可以搭配fct_infreq()fct_rev()两个函数来使用:

gss_cat %>% 
  mutate(relig = relig %>% 
           fct_infreq() %>% 
           fct_rev()) %>% 
  ggplot(aes(relig)) + 
  geom_bar() +
  coord_flip() #由于各值标签过长,因此转为横向条形图

调整因子水平

forcats包提供了用于调整因子水平的函数:fct_recode(),可以方便快捷的调整因子水平,同时可以将一个新的水平标签直接赋值给旧的水平标签

gss_cat %>% mutate(partyid = fct_recode(partyid, 
    "Republican, strong" = "Strong republican", 
    "Republican, weak" = "Not str republican", 
    "Independent, near rep" = "Ind,near rep", 
    "Independent, near dem" = "Ind,near dem", 
    "Democrat, weak" = "Not str democrat", 
    "Democrat, strong" = "Strong democrat", 
    "Other" = "No answer", 
    "Other" = "Don't know", 
    "Other" = "Other party" 
    )) %>% 
    count(partyid)

如果并不想指定新的因子水平,而是想直接将几个因子水平合并为新的水平,则可以使用fct_collapse()函数:

gss_cat %>% 
  mutate(
    partyid = fct_collapse(partyid,
    other = c("No answer", "Don't know", "Other party"),
    rep = c("Strong republican", "Not str republican"),
    ind = c("Ind,near rep", "Independent", "Ind,near dem"),
    dem = c("Not str democrat", "Strong democrat")
    )) %>% 
  count(partyid)

同时fct_lump()函数也提供类似于折叠合并的功能,但是并没有修改水平标签名的功能,并且还需要自己通过参数n=#来手动设置分组的数目

gss_cat %>% 
    mutate(relig = fct_lump(relig, n = 10)) %>% 
    count(relig, sort = TRUE) %>% 
    print(n = Inf)

小结

  • 处理因子变量主要围绕调整「因子顺序」和「因子水平」两个方面来开展
  • 在处理因子顺序上使用:
    • fct_reorder()
    • fct_relevel()
    • fct_infreq()fct_rev()
  • 在处理因子水平标签上使用:
    • fct_recode()
    • fct_collapse()
    • fct_lump()
  • forcats包的函数与R基础的因子函数不同,可能需要多花一些时间才能掌握

4. 时间序列处理:lubridate包应用(暂放)

小结

    原文作者:100gle
    原文地址: https://www.jianshu.com/p/ff1ba855c720
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞