3.1 不同类型数据的输入与输出

加载数据是数据分析的开始,判断数据存储类型并选择合适的工具加载数据可以使我们后续的数据分析避免许多不必要的问题。数据存储方式有多种:分隔符分隔的文本、Excel、pdf以及各种数据库。在商业数据的分析中,我们接触更多的是数据存储类型包括:csv、text、excel和pdf等。所以本书将重点介绍这几类文本的输入和输出。

3.1.1 数据的输入

分隔符分隔的文本

分隔符分隔的文本以text和csv文件为典型代表。输入此类数据有几种方式:键盘输入、data.table()以及readr包。使用键盘输入数据是数据输入最原始的方式,在数据分析中几乎不会用到,适合在R语言的学习过程中使用;data.table()属于R原生函数(read.函数均为原生函数),没有做效率和稳定性方面的考量,不推荐使用;Hadley Wickham 和 RStudio团队开发了许多功能十分强大的R包来替代R原生函数,在读取文本文件时,我们建议首选readr包。所以接下来我们对前两种方式进行简要介绍,重点关注readr包的使用。

键盘输入数据有两种方式:在代码中嵌入和调用R内置的文本编辑器。

在代码中嵌入数据即在创建容器(向量、数据框、列表等)的同时,将数据输入。这种方法在上一章节中已经提过,在此不再赘述。

调用R内置的文本编辑器,需要首先创建一个空的容器,之后使用edit()函数调用可视化的编辑器输入数据。注意,在使用edit函数调用编辑器输入数据时,要对变量进行赋值操作:data<-edit(data)。因为edit()函数是在对象的副本上操作的,副本会随着编辑器的关闭而消失。另外可以使用fix()函数直接编辑变量,无需进行赋值操作。例如:
editdata<-vector("integer",5)
editdata<-edit(editdata)
fix(editdata)

你还可以使用read.table()读取带分隔符的文本文件。这个函数可以读取表格格式的文件并将其保存为一个数据框。此函数的基本语法如下:

Mytable  <- read.table( file, header = LogicalValue, sep = "delimiter", row.names = "name" )

其中,file是一个带分隔符的ASCII文本文件,header是一个表明文件首行是否包含了变量名的逻辑值(TRUE或FALSE),sep用来指定分隔数据的分隔符,rows.names是一个可选参数,用以指定一个或多个表示行标识符的变量。关于此函数的其它用法,可以通过帮助文档查阅。

但是,使用read.table()时,有一些需要注意的事项。首先row.names会会将制定的列作为行名,这一列会失去原有的标签。其次,一些特殊的变量名称会被R自动转换为R默认的格式,比如read table会转换成read.table。在默认情况下,read.table()会把字符变量转换为因子,我们可以使用stringAsFactors参数进行指定,也可以使用colClasses参数为每一行指定数据类型。

文件的读取方式会决定数据的质量,高质量的读取可以是我们免除一部分后续数据处理的操作。readr包中有许多功能强大的函数读取不同格式的文本文件,可以高效迅速的读取数据。最常用的是read_csv()函数,因为csv是数据存储最常见的形式之一。read_csv()的常用代码示例如下所示:

read_csv(file, col_names = TRUE, col_types = NULL,
  na = c("", "NA"), skip = 0,comment = "")

第一个参数是文件存储的路径;col_names参数是一个表明文件首行是否包含了变量名的逻辑值,另外你也可以向它提供一个字符串作为列名;col_types参数可以为每一列指定数据类型;参数na设定了使用哪个或者哪些值来表示文件中的缺失值;skip可以设置一个参数以跳过数据的前n行。这些参数足够导入csv数据的大部分需求,如果有其它更加精细的设定,可以查看函数的参考文档。最后一个参数comment则可以批量剔除数据,例如 comment = “#”则会丢弃所有以#开头的行。

read_csv()还有一个重要功能是它在运行时会打印列的数据类型说明,这使得我们数据在加载完成之后就能对数据有一个直观的了解。为了方便学习,readr中提供了不同类型的数据集样例,我们可以使用readr_example()函数获取数据集名称,使用readr_example(filename)获取数据集路径。之后本书将以此作为演示数据集:

readr_example()
readr_example("challenge.csv")
read_csv("D:/R-3.5.1/library/readr/extdata/challenge.csv",col_names = TRUE,na = c(" ","NA"))

readr包中还有一些其它的函数以读取不同类型的文件:如read_tsv()读取制表符分隔数据;read_lines()函数从文件中逐行读取数据(适合复杂的数据处理);read_fwf()按照宽度设定读取的范围。这些函数在参数的设定上与read_csv()非常相似,所以掌握了read_csv()函数的使用,则完全可以举一反三。

Excel数据

在R中导入Excel文件的常用包有两个:readxl和xlsx。本书更加推荐readxl包,因为使用xlsx包依赖较多,而且read.xlsx()函数并不是十分稳定,重复读取文件容易电脑内存过载。所以本书重点介绍redxl包的使用,对于xlsx包只做了简要说明。

首先需要声明的是并不建议在R中直接导入Excel数据。我们可以通过在Excel中导出csv文件,然后在R中导入csv文件。在R通过xlsx包直接导入Excel数据是一个复杂的过程。使用xlsx包要先安装rJava和xlsxjar包,然后还需要配置Java环境。

目前xlsx包支持的Excel版本有Excel 97/2000/XP/2003/2007。代码示例如下:

Myexcel <- read.xlsx(file,sheetIndex,header)

其中file是Excel文件存储的路径,sheetIndex是表的索引的数值,header是逻辑值,指是否将第一行识别为别名。此外,还可以使用read.xlsx2 ()函数来读取大型表格,它的工作过程调用了java,所以效率上会有所提升。

readxl包是tidyverse包中的一部分,是导入Excel表格数据的一个R包,由Hadley Wickham开发的,底层调用的是c++程序处理数据。所以在稳定性和效率上,readxl都非常强大。read_excel()的常用代码示例如下所示:

Myexcel <- read_excel(path, sheet = NULL, range = NULL, col_names = TRUE,
  col_types = NULL, na = "", skip = 0, n_max = Inf)

其中range参数指定的是导入数据的区域,默认是全部导入。由于其它参数与read_csv()几乎相同,所以不再赘述。

readxl包中含有一些例子,我们可以使用readxl_example()函数将它们展示出来,然后使用readxl_example(“filename”“)得到该文件的路径,我们可以使用这些例子进行函数的学习。代码示例如下:

library(readxl)
## Warning: 程辑包'readxl'是用R版本4.1.1 来建造的
Excelroot <- readxl_example("datasets.xls")
Excelroot
## [1] "C:/Users/wujun/Documents/R/win-library/4.1/readxl/extdata/datasets.xls"
Myexcel <- read_excel(Excelroot)
Myexcel
## # A tibble: 150 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl> <chr>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # ... with 140 more rows

pdf数据

本书针对商业数据分析的案例编写,在商业案例中,pdf文件也是很重要的一种形式。对于加载pdf数据,我们推荐使用readtext包。readtext是专门设计用来读取文档数据的包,它可以读取多种类型的文本数据(csv、html、txt、pdf、doc等),这里读取pdf格式的文本是通过pdftools进行转换的。我们主要通过readtext()函数进行pdf文件的读取,readtext()函数的常用代码示例如下所示:

DATA_DIR <- system.file("extdata/", package = "readtext")
Mypdf <- readtext(paste0(DATA_DIR, "pdf/UDHR/UDHR_chinese.pdf"), 
                 docvarsfrom = "filenames", 
                 docvarnames = c("document", "language"))

readtext包同样提供了用于学习的数据集样例,DATA_DIR可以获取样例的路径,但是这里的文件名(UDHR_chinese)需要手动获取。由于文本型文件的名称都是诸如.pdf等类型,docvarsfrom 参数可以选择命名相同的pdf参数作为处理对象。第三个参数则是对数据集的命名,如果不指定,则会默认使用(docvar1, docvar2, …)作为名称。

3.1.2 数据的输出

数据的输出也是数据分析过程的一部分,在这里我们同样推荐使用readr包,在输出的时候选择.csv的格式。这种格式相比于Excel,更见适合进行数据的后续分析。我们在这里用到了write_csv()函数,此函数的常用格式为:
x <- c(T,T,F)  
y <- c(F,T,F) 
x&&y 
## [1] FALSE
x&y 
## [1] FALSE  TRUE FALSE

其他操作符
R语言中,还有一些其它的操作符:

  • %in%:是match()函数等价方式,返回一个布尔值的向量。

  • %>%:用于dplyr包中,把数据集传递给下一个函数使用。这是一个非常重要的符号,在后续章节中我们会经常使用,你会感受到它的便捷。

3.1.3 函数

函数是一组组合在一起以执行特定任务的语句,输入参数,依据语句的逻辑返回一个值。事实上,在R语言中,我们所使用的一切都是对象,我们所做的的一切操作都是函数。或者说,函数也是一个对象,我们使用的“+”、“-”等运算符本质上来说也是函数。

R语言具有大量内置函数,还有数千个第三方包,在我们进行一些常用的操作时,这些函数已经够用了。但是在数据分析中需要重复某些逻辑或操作时,或者在进行高度定制化的操作时,创建函数是一个极佳的选择。R语言解释器能够将控制传递给函数,以及函数完成动作所需的参数。该函数依次执行其任务并将控制返回到解释器以及可以存储在其他对象中的任何结果。在函数中,参数都是局部变量,这在进行复杂任务时,具有很大优势。

  1. 创建函数

每一个R函数都包括三个部分:函数名,程序主体以及参数集合,在编写自定义R函数时,需要将三个部分各自储存在一个R对象中。这里需要使用function函数,例如:

Myfunction<-function(x,y){
  x+y
}

这里,My_function是函数名;(x,y)指定了函数的参数;“x+y”是程序主体。该函数没有调用return函数,函数计算的最后一个表达式的值作为返回值,My_function函数返回x和y的和。调用My_function可以看到函数的具体内容。

Myfunction<-function(x,y){
  x+y
}
Myfunction
## function(x,y){
##   x+y
## }
Myfunction(2,3)
## [1] 5
调用函数时,如果不命名参数,则R按照位置匹配参数。因此,在上述代码中2对应形式参数x,3对应形式参数y。如果要改变传递参数的顺序,则可以传入命名参数:
My_function<-function(x=2,y=3){
  x+y
}
My_function(y=2,x=3)
## [1] 5
可以看到,我们为函数的参数设置了默认值,当调用函数时,如果没有为参数传递相应的值,那么函数将自动使用默认值作为函数代码块执行的当前值:
My_function()
## [1] 5
函数中会自动返回值,当我们需要函数运行过程的其它数据时,可以使用return函数,清晰地指定要返回的值,return函数除了指定函数的返回值之外,还能使函数退出,不再继续执行后面的代码:
  1. 函数的动态类型
R中,函数不是强类型的,它非常灵活。函数最初设计是用于标量运算,但是在R中,它可以自动拓张到向量运算甚至其它类型额数据,例如:
My_function<-function(x,y){
  x+y
}
My_function(c(1,2),3)
## [1] 4 5
My_function(as.Date("2019-3-10"),1)
## [1] "2019-03-11"
  1. 参数(…)

在数据分析的过程中,有时我们需要输入的参数数量会发生改变,这时使用(x,y)的传参方式无法满足需求。但是我们可以注意到R语言中一些函数可以接受任意数量的输入,这些函数是如何实现的呢?

R提供了一个特殊的运算符(…),该运算符允许函数具有任意多个的参数,并且不需要在函数定义中指定。这个运算符可以将它捕获的参数传递给另一个函数。当函数作为另一个函数的变量时,这种方式可以将所有参数捕获并传递给其他函数。下面我们我们对str_c函数进行了重新包装:
commas <- function(...) stringr::str_c(..., collapse = ", ") 
commas(1:10) 
## [1] "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"
rule <- function(..., pad = "-") {   
title <- paste0(...)   
width <- getOption("width") - nchar(title) - 10
cat(title, " ", stringr::str_dup(pad, width), "\n", sep = "") 
} 
rule(c("Important","output"))
## Importantoutput -----------------------------------------------------------------------------------------------------------------------------

上述代码块中,commas会将参数用“,”分隔开。rule函数中,getOption(“width”)获取一个常数值,跟输出的列数有关。cat函数则将输出打印到控制台,我们可以看到输出占满了控制台的整行,这就是getOption(“width”)的作用。