15 R输入输出

15.1 输入输出的简单方法

15.1.1 简单的输出

print()函数显示某个变量或表达式的值, 如

x <- 1.234
print(x)
## [1] 1.234
y <- c(1,3,5)
print(y[2:3])
## [1] 3 5

在命令行使用R时, 直接以变量名或表达式作为命令可以起到用print()函数显示的相同效果。

cat()函数把字符串、变量、表达式连接起来显示, 其中变量和表达式的类型一般是标量或向量,不能是矩阵、列表等复杂数据。 如

cat("x =", x, "\n")
## x = 1.234
cat("y =", y, "\n")
## y = 1 3 5

注意cat()显示中需要换行需要在自变量中包含字符串"\n", 即换行符。

cat()默认显示在命令行窗口, 为了写入指定文件中, 在cat()调用中用file=选项, 这时如果已有文件会把原有内容覆盖, 为了在已有文件时不覆盖原有内容而是在末尾添加, 在cat()中使用append=TRUE选项。 如:

cat("=== 结果文件 ===\n", file="res.txt")
cat("x =", x, "\n", file="res.txt", append=TRUE)

函数sink()可以用来把命令行窗口显示的运行结果转向保存到指定的文本文件中, 如果希望保存到文件的同时也在命令行窗口显示, 使用split=TRUE选项。如

sink("allres.txt", split=TRUE)

为了取消这样的输出文件记录, 使用不带自变量的sink()调用,如

sink()

在R命令行环境中定义的变量、函数会保存在工作空间中, 并在退出R会话时可以保存到硬盘文件中。 用save()命令要求把指定的若干个变量(直接用名字,不需要表示成字符串) 保存到用file=指定的文件中, 随后可以用load()命令恢复到工作空间中。 虽然允许保存多个变量到同一文件中, 但尽可能仅保存一个变量, 而且使用变量名作为文件名。 用save()保存的R特殊格式的文件是通用的, 不依赖于硬件和操作系统。 如

save(scores, file="scores.RData")
load("scores.RData")

保存多个变量,如x, zeta,命令如:

save(x, zeta, file="myvars20200315.RData")

save(list = c("x", "zeta"), file="myvars20200315.RData")

对于一个数据框, 可以用write.csv()readr::write_csv()将其保存为逗号分隔的文本文件, 这样的文件可以很容易地被其它软件识别访问, 如Microsoft Excel软件可以很容易地把这样的文件读成电子表格。 用如

da <- tibble("name"=c("李明", "刘颖", "张浩"),
    "age"=c(15, 17, 16))
write_csv(da, path="mydata.csv")

结果生成的mydata.csv文件内容如下:

name,age
李明,15
刘颖,17
张浩,16

但是,在Microsoft的中文版Windows操作系统中, 默认编码是GB编码, 用write_csv()生成的CSV文件总是使用UTF-8编码, 系统中的MS Office 软件不能自动识别这样编码的CSV文件, 可以改用write_csv_excel()函数; 基本R的write.csv()函数不存在这个问题。

15.1.2 简单的输入

scan()函数可以输入文本文件中的数值向量, 文件名用file=选项给出。 文件中数值之间以空格和换行分开。如

cat(1:12, "\n", file="d:/work/x.txt")
x <- scan("d:/work/x.txt")

程序中用全路径给出了输入文件位置, 注意路径中用了正斜杠/作为分隔符, 如果在MS Windows环境下使用\作为分隔符, 在R的字符串常量中\必须写成\\

如果scan()中忽略输入文件参数, 此函数将从命令行读入数据。 可以在一行用空格分开多个数值, 可以用多行输入直到空行结束输入。

这样的方法也可以用来读入矩阵。 设文件mat.txt包含如下矩阵内容:

3  4  2
5 12 10
7  8  6
1  9 11

可以先把文件内容读入到一个R向量中, 再利用matrix()函数转换成矩阵, 注意要使用byrow=TRUE选项, 而且只要指定ncol选项, 可以忽略nrow选项。如

M <- matrix(scan("mat.txt", quiet=TRUE), ncol=3, byrow=TRUE)
M

scan()中的quite=TRUE选项使得读入时不自动显示读入的数值项数。

上面读入数值矩阵的方法在数据量较大的情形也可以使用, 与之不同的是, read.table()readr::read_table()函数也可以读入这样的数据, 但是会保存成数据框而不是矩阵, 而且read.table()函数在读入大规模的矩阵时效率很低。

15.2 读取CSV文件

15.2.1 CSV格式

对于保存在文本文件中的电子表格数据, R可以用read.csv(), read.table(), read.delim(), read.fwf()等函数读入, 但是建议在readr包的支持下用read_csv(), read_table2(), read_delim(), read_fwf()等函数读入, 这些将读入的数据框保存为tibble类型, tibble是数据框的一个变种, 改善了数据框的一些不适当的设计。 readr的读入速度比基本R软件的read.csv()等函数的速度快得多, 速度可以相差十倍, 也不自动将字符型列转换成因子, 不自动修改变量名为合法变量名, 不设置行名。

对于中小规模的数据, CSV格式作为文件交换格式比较合适, 兼容性强, 各种数据管理软件与统计软件都可以很容易地读入和生成这样格式的文件, 但是特别大型的数据读入效率很低。

CSV格式的文件用逗号分隔开同一行的数据项, 一般第一行是各列的列名(变量名)。 对于数值型数据, 只要表示成数值常量形式即可。 对于字符型数据, 可以用双撇号包围起来, 也可以不用撇号包围。 但是, 如果数据项本身包含逗号, 就需要用双撇号包围。 例如,下面是一个名为testcsv.csv的文件内容, 其中演示了内容中有逗号、有双撇号的情况。

id,words
1,"PhD"
2,Master's degree 
3,"Bond,James"
4,"A ""special"" gift"

为读入上面的内容,只要用如下程序:

d <- read_csv("testcsv.csv")

读入的数据框显示如下:

# A tibble: 4 × 2
     id            words
  <int>            <chr>
1     1              PhD
2     2  Master's degree
3     3       Bond,James
4     4 A "special" gift

15.2.2 从字符串读入

read_csv()还可以从字符串读入一个数据框,如

d.small <- read_csv("name,x,y
John, 33, 95
Kim, 21, 64
Sandy, 49, 100
")
## Rows: 3 Columns: 3
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): name
## dbl (2): x, y
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
d.small
## # A tibble: 3 x 3
##   name      x     y
##   <chr> <dbl> <dbl>
## 1 John     33    95
## 2 Kim      21    64
## 3 Sandy    49   100

15.2.3 read_csv选项

read_csv()skip=选项跳过开头的若干行。 当数据不包含列名时, 只要指定col_names=FALSE, 变量将自动命名为X1, X2, ..., 也可以用col_names=指定各列的名字,如

d.small <- read_csv("John, 33, 95
Kim, 21, 64
Sandy, 49, 100
", col_names=c("name", "x", "y") )
## Rows: 3 Columns: 3
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): name
## dbl (2): x, y
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
d.small
## # A tibble: 3 x 3
##   name      x     y
##   <chr> <dbl> <dbl>
## 1 John     33    95
## 2 Kim      21    64
## 3 Sandy    49   100

15.2.4 编码设置

CSV文件是文本文件,是有编码问题的, 尤其是中文内容的文件。 readr包的默认编码是UTF-8编码。 例如,文件data/bp.csv以GBK编码(有时称为GB18030编码, 这是中文Windows所用的中文编码)保存了如下内容:

序号,收缩压
1,145
5,110
6, 未测
9,150
10, 拒绝
15,115

如果直接用read_csv()

d <- read_csv("data/bp.csv")

可能在读入时出错,或者访问时出错。 为了读入用GBK编码的中文CSV文件, 需要利用locale参数和locale()函数:

d <- read_csv("data/bp.csv", 
  locale=locale(encoding="GBK"))
## Rows: 6 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 收缩压
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
d
## # A tibble: 6 x 2
##    序号 收缩压
##   <dbl> <chr> 
## 1     1 145   
## 2     5 110   
## 3     6 未测  
## 4     9 150   
## 5    10 拒绝  
## 6    15 115

15.2.5 缺失值设置

read_csv()将空缺的值读入为缺失值, 将”NA”也读入为缺失值。 可以用na=选项改变这样的设置。 也可以将带有缺失值的列先按字符型原样读入, 然后再进行转换。

比如,上面的bp.csv文件中, 先将血压列按字符型读入, 再增加一列转换为数值型的列, 非数值转换为NA:

d <- read_csv("data/bp.csv", 
  locale=locale(encoding="GBK"))
## Rows: 6 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 收缩压
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
d[["收缩压数值"]] <- as.numeric(d[["收缩压"]])
## Warning: 强制改变过程中产生了NA
d
## # A tibble: 6 x 3
##    序号 收缩压 收缩压数值
##   <dbl> <chr>       <dbl>
## 1     1 145           145
## 2     5 110           110
## 3     6 未测           NA
## 4     9 150           150
## 5    10 拒绝           NA
## 6    15 115           115

15.2.6 各列类型设置

对每列的类型, readr用前1000行猜测合理的类型, 并在读取后显示猜测的每列类型。

但是有可能类型改变发生在1000行之后。 col_types选项可以指定每一列的类型, 如"col_double()", "col_integer()", "col_character()", "col_factor()", "col_date()", "col_datetime"等。 cols()函数可以用来规定各列类型, 并且有一个.default参数指定缺省类型。 对因子,需要在col_factor()中用lelvels=指定因子水平。

可以复制readr猜测的类型作为col_types的输入, 这样当数据变化时不会因为偶尔猜测错误而使得程序出错。如

d <- read_csv("data/bp.csv", locale=locale(encoding="GBK"),
    col_types=cols(
      `序号` = col_integer(),
      `收缩压` = col_character()
      ))
d
## # A tibble: 6 x 2
##    序号 收缩压
##   <int> <chr> 
## 1     1 145   
## 2     5 110   
## 3     6 未测  
## 4     9 150   
## 5    10 拒绝  
## 6    15 115

当猜测的文件类型有问题的时候, 可以先将所有列都读成字符型, 然后用type_convert()函数转换, 如:

d <- read_csv("filename.csv",
              col_types=cols(.default = col_character()))
d <- type_convert(d)

读入有错时,对特大文件可以先少读入一些行, 用nmax=可以指定最多读入多少行。 调试成功后再读入整个文件。

15.2.7 因子类型设置

设文件data/class.csv内容如下:

name,sex,age,height,weight
Alice,F,13,56.5,84
Becka,F,13,65.3,98
Gail,F,14,64.3,90
Karen,F,12,56.3,77
Kathy,F,12,59.8,84.5
Mary,F,15,66.5,112
Sandy,F,11,51.3,50.5
Sharon,F,15,62.5,112.5
Tammy,F,14,62.8,102.5
Alfred,M,14,69,112.5
Duke,M,14,63.5,102.5
Guido,M,15,67,133
James,M,12,57.3,83
Jeffrey,M,13,62.5,84
John,M,12,59,99.5
Philip,M,16,72,150
Robert,M,12,64.8,128
Thomas,M,11,57.5,85
William,M,15,66.5,112

最简单地用read_csv()读入上述CSV文件,程序如:

d.class <- read_csv("data/class.csv")
## Rows: 19 Columns: 5
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (2): name, sex
## dbl (3): age, height, weight
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.

从显示看出, 读入后显示了每列的类型。 对性别变量,没有自动转换成因子, 而是保存为字符型。 为了按自己的要求转换各列类型, 用了read_csv()coltypes=选项和cols()函数如下:

d.class <- read_csv(
  "data/class.csv", 
  col_types=cols(
  .default = col_double(),
  name=col_character(),
  sex=col_factor(levels=c("M", "F")) ))
str(d.class)
## spec_tbl_df [19 x 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ name  : chr [1:19] "Alice" "Becka" "Gail" "Karen" ...
##  $ sex   : Factor w/ 2 levels "M","F": 2 2 2 2 2 2 2 2 2 1 ...
##  $ age   : num [1:19] 13 13 14 12 12 15 11 15 14 14 ...
##  $ height: num [1:19] 56.5 65.3 64.3 56.3 59.8 66.5 51.3 62.5 62.8 69 ...
##  $ weight: num [1:19] 84 98 90 77 84.5 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   .default = col_double(),
##   ..   name = col_character(),
##   ..   sex = col_factor(levels = c("M", "F"), ordered = FALSE, include_na = FALSE),
##   ..   age = col_double(),
##   ..   height = col_double(),
##   ..   weight = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

其中str()函数可以显示数据框的行数(obs.)和变量数(variables), 以及每个变量(列)的类属等信息。

15.2.8 读入日期

设文件data/dates.csv中包含如下内容,并设其文件编码为GBK:

序号,出生日期,发病日期
1,1941/3/8,2007/1/1
2,1972/1/24,2007/1/1
3,1932/6/1,2007/1/1
4,1947/5/17,2007/1/1
5,1943/3/10,2007/1/1
6,1940/1/8,2007/1/1
7,1947/8/5,2007/1/1
8,2005/4/14,2007/1/1
9,1961/6/23,2007/1/2
10,1949/1/10,2007/1/2

可以先把日期当作字符串读入:

d.dates <- read_csv('data/dates.csv', 
  locale=locale(encoding="GBK"))
## Rows: 10 Columns: 3
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (2): 出生日期, 发病日期
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.

然后用lubridate::ymd()函数转换为R日期类型:

d.dates[["出生日期ct"]] <- lubridate::ymd(
  d.dates[["出生日期"]], tz='Etc/GMT-8')
d.dates[["发病日期ct"]] <- lubridate::ymd(
  d.dates[["发病日期"]], tz='Etc/GMT-8')

也可以用R本身的as.POSIXct函数转换:

d.dates[["出生日期ct"]] <- as.POSIXct(
  d.dates[["出生日期"]], format='%Y/%m/%d', tz='Etc/GMT-8')
d.dates[["发病日期ct"]] <- as.POSIXct(
  d.dates[["发病日期"]], format='%Y/%m/%d', tz='Etc/GMT-8')

这时保存的是POSIXct类型。 经过转换后的数据为:

knitr::kable(d.dates)
序号 出生日期 发病日期 出生日期ct 发病日期ct
1 1941/3/8 2007/1/1 1941-03-08 2007-01-01
2 1972/1/24 2007/1/1 1972-01-24 2007-01-01
3 1932/6/1 2007/1/1 1932-06-01 2007-01-01
4 1947/5/17 2007/1/1 1947-05-17 2007-01-01
5 1943/3/10 2007/1/1 1943-03-10 2007-01-01
6 1940/1/8 2007/1/1 1940-01-08 2007-01-01
7 1947/8/5 2007/1/1 1947-08-05 2007-01-01
8 2005/4/14 2007/1/1 2005-04-14 2007-01-01
9 1961/6/23 2007/1/2 1961-06-23 2007-01-02
10 1949/1/10 2007/1/2 1949-01-10 2007-01-02

也可以用R本身的as.Date函数转换:

d.dates[["出生日期ct"]] <- as.Date(
  d.dates[["出生日期"]], format='%Y/%m/%d')
d.dates[["发病日期ct"]] <- as.Date(
  d.dates[["发病日期"]], format='%Y/%m/%d')

这时保存的是Date类型。

上面将日期先读入为字符型再转换是比较保险的做法。 还可以直接在read_csv()函数中指定某列为col_date()

d.dates <- read_csv(
  'data/dates.csv', 
  locale=locale(encoding="GBK"),
  col_types=cols(
    `序号`=col_integer(),
    `出生日期`=col_date(format="%Y/%m/%d"),
    `发病日期`=col_date(format="%Y/%m/%d")
  ))
print(d.dates)
## # A tibble: 10 x 3
##     序号 出生日期   发病日期  
##    <int> <date>     <date>    
##  1     1 1941-03-08 2007-01-01
##  2     2 1972-01-24 2007-01-01
##  3     3 1932-06-01 2007-01-01
##  4     4 1947-05-17 2007-01-01
##  5     5 1943-03-10 2007-01-01
##  6     6 1940-01-08 2007-01-01
##  7     7 1947-08-05 2007-01-01
##  8     8 2005-04-14 2007-01-01
##  9     9 1961-06-23 2007-01-02
## 10    10 1949-01-10 2007-01-02

15.2.9 其它函数

除了read_csv()函数以外, R扩展包readr还提供了其它的从文本数据读入数据框的函数, 如read_table2(), read_tsv(), read_fwf()等。 这些函数读入的结果保存为tibble。 read_table2()读入用空格作为间隔的文本文件, 同一行的两个数据项之间可以用一个或多个空格分隔, 不需要空格个数相同, 也不需要上下对齐。 read_tsv()读入用制表符分隔的文件。 read_fwf()读入上下对齐的文本文件。

另外, read_lines()函数将文本文件各行读入为一个字符型向量。 read_file()将文件内容读入成一整个字符串, read_file_raw()可以不管文件编码将文件读入为一个二进制字符串。

对特别大的文本格式数据, data.table扩展包的fread()读入速度更快。

readr包的write_excel_csv()函数将tibble保存为csv文件, 总是使用UTF-8编码,结果可以被MS Office读取。

文本格式的文件都不适用于大型数据的读取与保存。 大型数据可以通过数据库接口访问, 可以用R的save()load()函数按照R的格式访问, 还有一些特殊的针对大数据集的R扩展包。

15.3 Excel表访问

15.3.1 借助于文本格式

为了把Microsoft Excel格式的数据读入到R中, 最容易的办法是在Excel软件中把数据表转存为CSV格式, 然后用read.csv()读取。

为了把R的数据框保存为Excel格式, 只要用write.csv()readr::write_excel_csv()把数据框保存成CSV格式, 然后在Excel中打开即可。 例如,下面的程序演示了write.csv()的使用:

d1 <- tibble("学号"=c("101", "103", "104"),
             "数学"=c(85, 60, 73), 
             "语文"=c(90, 78, 80))
write.csv(d1, file="tmp1.csv", row.names=FALSE)

保存在文件中的结果显示如下:

学号,数学,语文
101,85,90
103,60,78
104,73,80

15.3.2 使用剪贴板

为了把Excel软件中数据表的选中区域读入到R中, 可以借助于剪贴板。 在Excel中复制选中的区域,然后在R中用如

myDF <- read.delim("clipboard")

就可以把选中部分转换成一个R的数据框。 如果复制的区域不含列名, 应加上header=FALSE选项。

这种方法也可以从R中复制数据到在Excel中打开的电子表格中, 例如

write.table(iris, file="clipboard", sep = "\t", col.names = NA)

首先把指定的数据框(这里是iris)写入到了剪贴板, 然后在用Excel软件打开的工作簿中只要粘贴就可以。 上述程序中write.table()函数把指定的数据框写入到指定的文件中, 其中的col.names=NA选项是一个特殊的约定, 这时保存的文件中第一行是列名, 如果有行名的话,行名所在的列对应的列名是空白的(但是存在此项)。

如果从R中复制数据框到打开的Excel文件中时不带行名, 但是带有列名,可以写这样一个通用函数

write.clipboard <- function(df){ 
  write.table(df, file="clipboard", sep="\t", 
              row.names=FALSE)
}

15.3.3 利用readxl扩展包

readxl扩展包的readxl()函数利用独立的C和C++库函数读入.xls和.xlsx格式的Excel文件。一般格式为

read_excel(path, sheet = 1, col_names = TRUE, 
    col_types = NULL, na = "",  skip = 0)

结果返回读入的表格为一个数据框。 各个自变量为:

  • path: 要读入的Excel文件名,可以是全路径,路径格式要符合所用操作系统要求。

  • sheet: 要读入哪一个工作簿(sheet),可以是整数序号,也可以是工作簿名称的字符串。

  • col_names: 是否用第一行内容作为列名,缺省为是。

  • col_types: 可以在读入时人为指定各列的数据类型,缺省时从各列内容自动判断,有可能会不够准确。人为指定时,指定一个对应于各列的字符型向量,元素可取值为:

    • blank: 自动判断该列;
    • numeric: 数值型;
    • date: 日期;
    • text: 字符型。

writexl扩展包可以用来将数据框保存为Excel格式。 除了readxl和writexl扩展包, XLConnect, xlsx, tidyxl也可以进行与Excel文件或者Excel软件的交互。

15.4 文件访问

15.4.1 连接

输入输出可以针对命令行,针对文件,R支持扩展的文件类型, 称为“连接(connection)”。

函数file()生成到一个普通文件的连接, 函数url()生成一个到指定的URL的连接, 函数gzfile, bzfile, xzfile, unz支持对压缩过的文件的访问(不是压缩包,只对一个文件压缩)。 这些函数大概的用法如下:

file("path", open="", blocking=T,
     encoding = getOption("encoding"), 
     raw = FALSE)

url(description, open = "", blocking = TRUE,
    encoding = getOption("encoding"))

textConnection(description, open="r", 
    local = FALSE,
    encoding = c("", "bytes", "UTF-8"))

gzfile(description, open = "", 
       encoding = getOption("encoding"),
       compression = 6)

bzfile(description, open = "", 
       encoding = getOption("encoding"),
       compression = 9)

xzfile(description, open = "", 
       encoding = getOption("encoding"),
       compression = 6)

unz(description, filename, open = "",
    encoding = getOption("encoding"))

生成连接的函数不自动打开连接。 给定一个未打开的连接, 读取函数从中读取时会自动打开连接, 函数结束时自动关闭连接。 用open()函数打开连接,返回一个句柄; 生成连接时可以用open参数要求打开连接。 要多次从一个连接读取时就应该先打开连接, 读取完毕用close函数关闭。

函数textConnection()打开一个字符串用于读写。

在生成连接与打开连接的函数中用open参数指定打开方式, 取值为:

  • r—文本型只读;
  • w—文本型只写;
  • a—文本型末尾添加;
  • rb—二进制只读;
  • wb—二进制只写;
  • ab—二进制末尾添加;
  • r+r+b—允许读和写;
  • w+w+b—允许读和写,但刚打开时清空文件;
  • a+a+b—末尾添加并允许读。

15.4.2 文本文件访问

函数readLines()readr::read_lines()scan()可以从一个文本型连接读取。

给定一个打开的连接或文件名, 用readLines函数可以把文件各行读入为字符型向量的各个元素, 不包含文件中用来分开各行的换行标志。 可以指定要读的行数。 如

ll <- readLines("data/class.csv")
print(head(ll, 3))
## [1] "name,sex,age,height,weight" "Alice,F,13,56.5,84"        
## [3] "Becka,F,13,65.3,98"

readr包的read_lines()如:

ll <- readr::read_lines("data/class.csv")
print(head(ll, 3))
## [1] "name,sex,age,height,weight" "Alice,F,13,56.5,84"        
## [3] "Becka,F,13,65.3,98"

writeLines函数可以把一个字符型向量各元素作为不同行写入一个文本型连接。如

vnames <- strsplit(ll, ",")[[1]]
writeLines(vnames, "class-names.txt")

其中的第二参数应该是一个打开的文本型写入连接, 但是可以直接给出一个要写入的文件名。 用readr包的write_lines():

readr::write_lines(vnames, "class-names.txt")

用scan函数读入用空格和空行分隔的字符串向量:

vnames <- scan(
  "class-names.txt", what=character(),
  quiet=TRUE)
vnames
## [1] "name"   "sex"    "age"    "height" "weight"

15.4.3 文本文件分批读写

readLines()readr::read_lines()writeLines()readr::write_lines()支持分批读写。 这需要预先打开要读取和写入的文件, 所有内容都处理一遍以后关闭读取和写入的文件。

使用file()函数打开文件用于读写, 使用close()函数关闭打开的文件。 打开文件时可以用encoding=指定编码。

下面的程序每次将GB18030编码的文件cancer.csv读入至多10行, 转为UTF-8写入tmp.csv中, 使用readLines()writeLines():

fin <- file("data/cancer.csv", "rt")
fout <- file("tmp.csv", "wt", encoding="UTF-8")
repeat{
  lines <- readLines(fin, n=10)
  cat("Read", length(lines), "lines.", "\n")
  if(length(lines)==0) break
  writeLines(lines, fout)
}
close(fout)
close(fin)

试验发现readr::read_lines()不支持分批读取。

15.4.4 二进制文件访问

函数save用来保存R变量到文件, 函数load用来从文件中读取保存的R变量。

save(x, y, file="saved20210811.RData")

save(list=c("x", "y"), file="saved20210811.RData")

读入如

load("saved20210811.RData")

对较大的数据,常常每个保存文件仅保存一个变量, 并以变量名为文件名,如:

save(data01, file="data01.RData")

函数readBinwriteBin对R变量进行二进制文件存取。

如果要访问其它软件系统的二进制文件, 请参考R手册中的“R Data Import/Export Manual”。

15.4.5 字符型连接

函数textConnection打开一个字符串用于读取或写入, 是很好用的一个R功能。 可以把一个小文件存放在一个长字符串中, 然后用textConnection读取,如

fstr <-
"name,score
王芳,78
孙莉,85
张聪,80
"
d <- read.csv(textConnection(fstr), header=TRUE)
print(d)
##   name score
## 1 王芳    78
## 2 孙莉    85
## 3 张聪    80

读取用的textConnection的参数是一个字符型变量。

在整理输出结果时,经常可以向一个字符型变量连接写入, 最后再输出整个字符串值。 例如:

tc <- textConnection("sres", open="w")
cat("Trial of text connection.\n", file=tc)
cat(1:10, "\n", file=tc, append=TRUE)
close(tc)
print(sres)

注意写入用的textConnection 的第一个参数是保存了将要写入的字符型变量名的字符串, 而不是变量名本身, 第二个参数表明是写入操作, 使用完毕需要用close关闭。

15.5 中文编码问题

读写文本格式的数据, 或者用readLines()readr::read_lines()读写文本文件, 可能会遇到中文编码不匹配的问题。 这里总结一些常用解决方法, 所用的操作系统为中文Windows10, 在RStudio中运行,R版本为4.1.0。 常见的中文编码有GBK(或GB18030, GB), UTF-8, UTF-8有BOM标志等。

可以用iconvlist()查看R支持的编码名称。

假设有如下的含有中文的文件:

序号,收缩压
1,145
5,110
6, 未测
9,150
10, 拒绝
15,115

这个文件是在中文版MS Office的Excel软件中输入后, 用Office的“文件——另存为——.csv格式”生成的, 结果的编码是GBK编码, 或GB18030编码。 文件下载: data/bp.csv

我们用工具软件将其转换成UTF-8无BOM格式,下载链接: data/bp-utf8nobom.csv

转为UTF-8有BOM格式,下载链接: data/bp-utf8bom.csv

15.5.1 用基本R的读取函数读取

与所用操作系统默认编码相同的文本文件, R基本软件的read.csv()read.table()readLines()函数都可以正常读取, 所以bp.csv文件可以正常读取,如

read.csv("data/bp.csv")
##   序号 收缩压
## 1    1    145
## 2    5    110
## 3    6   未测
## 4    9    150
## 5   10   拒绝
## 6   15    115
readLines("data/bp.csv")
## [1] "序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"

但是另外两个以UTF-8编码的文件则不能正确读入:

read.csv("data/bp-utf8nobom.csv")
## Error in make.names(col.names, unique = TRUE) : invalid multibyte string 2
readLines("data/bp-utf8bom.csv")
## [1] "锘垮簭鍙\xb7,鏀剁缉鍘\x8b" "1,145"                    
## [3] "5,110"                     "6, 鏈祴"                 
## [5] "9,150"                     "10, 鎷掔粷"               
## [7] "15,115"

读取UTF-8编码无BOM的文件时, 在read.csv()read.table()等函数中加fileEncoding="UTF-8"选项可以纠正编码问题:

read.csv("data/bp-utf8nobom.csv", fileEncoding="UTF-8")
##   序号 收缩压
## 1    1    145
## 2    5    110
## 3    6   未测
## 4    9    150
## 5   10   拒绝
## 6   15    115

读取UTF-8编码无BOM或者有BOM的文件时, 在readLines()函数中加encoding="UTF-8"选项可以纠正编码问题:

readLines("data/bp-utf8nobom.csv", encoding="UTF-8")
## [1] "序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"
readLines("data/bp-utf8bom.csv", encoding="UTF-8")
## [1] "<U+FEFF>序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"

但是,UTF-8有BOM标志的文本文件不能被read.csv()识别:

read.csv("data/bp-utf8bom.csv", fileEncoding="UTF-8")
## invalid input found on input connection "bp-utf8bom.csv"
##   incomplete final line found by readTableHeader on "bp-utf8bom.csv"

15.5.2 用readr包读取

readr包的read_csv()read_table2()read_lines()函数默认从UTF-8编码的文件中读取, 无BOM或者有BOM都可以。 如:

read_csv("data/bp-utf8nobom.csv")
## Rows: 6 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 收缩压
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 6 x 2
##    序号 收缩压
##   <dbl> <chr> 
## 1     1 145   
## 2     5 110   
## 3     6 未测  
## 4     9 150   
## 5    10 拒绝  
## 6    15 115
read_csv("data/bp-utf8bom.csv")
## Rows: 6 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 收缩压
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 6 x 2
##    序号 收缩压
##   <dbl> <chr> 
## 1     1 145   
## 2     5 110   
## 3     6 未测  
## 4     9 150   
## 5    10 拒绝  
## 6    15 115
read_lines("data/bp-utf8nobom.csv")
## [1] "序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"
read_lines("data/bp-utf8bom.csv")
## [1] "序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"

但是,对GBK编码的文件,不能直接读取:

read_csv("data/bp.csv")
## Parsed with column specification:
## cols(
##   `<d0><f2><U+00BA><c5>` = col_double(),
##   `<ca><d5><cb><f5><U+0479>` = col_character()
## )
## Error in nchar(x[is_na], type = "width") : 
##   invalid multibyte string, element 1
read_lines("data/bp.csv")
## [1] "<d0><f2><U+00BA><c5>,<ca><d5><cb><f5><U+0479>" "1,145"                          
## [3] "5,110"                           "6, δ<U+00B2><e2>"               
## [5] "9,150"                           "10, <U+00BE><U+073E><f8>"       
## [7] "15,115"
## [1] "\xd0\xf2�\xc5,\xca\xd5\xcb\xf5ѹ" "1,145"                          
## [3] "5,110"                           "6, δ�\xe2"                      
## [5] "9,150"                           "10, �ܾ\xf8"                      
## [7] "15,115"          

为了读取GBK(或GB18030)编码的文件, 在read_csv()read_lines()函数中加入 locale=locale(encoding="GBK")选项, GBK也可以写成GB18030:

read_csv("data/bp.csv", locale=locale(encoding="GBK"))
## Rows: 6 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 收缩压
## dbl (1): 序号
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 6 x 2
##    序号 收缩压
##   <dbl> <chr> 
## 1     1 145   
## 2     5 110   
## 3     6 未测  
## 4     9 150   
## 5    10 拒绝  
## 6    15 115
read_lines("data/bp.csv", locale=locale(encoding="GBK"))
## [1] "序号,收缩压" "1,145"       "5,110"       "6, 未测"     "9,150"      
## [6] "10, 拒绝"    "15,115"

15.5.3 输出文件的编码

write.csv()writeLines()生成的含有中文的文件的编码默认为操作系统的默认中文编码, 这里是GB18030。

readr的write_csv()write_lines()函数生成的含有中文的文件的编码默认UTF-8无BOM。 如

write_csv(tibble("姓名"=c("张三", "李四")), "tmp.csv")

结果生成的文件编码为UTF-8无BOM, 这样的文件可以被R的readr::read_csv()正确读取, 但是不能被MS Excel软件正确读取。

write_lines()输出的文件也是编码为UTF-8无BOM。

write_excel_csv()可以生成带有UTF-8有BOM的CSV文件, 这样的文件可以被MS Office正确识别:

write_excel_csv(tibble("姓名"=c("张三", "李四")), "tmp2.csv")

15.6 目录和文件管理

目录和文件管理函数:

  • getwd()—返回当前工作目录。
  • setwd(path)—设置当前工作目录。
  • list.files()dir()—查看目录中内容。 list.files(pattern=’.*[.]r$’)可以列出所有以“.r”结尾的文件。
  • file.path()—把目录和文件名组合得到文件路径。
  • file.info(filenames)—显示文件的详细信息。
  • file.exists()—查看文件是否存在。
  • file.access()—考察文件的访问权限。
  • create.dir()—新建目录。
  • file.create()—生成文件。
  • file.remove()unlink()—删除文件。unlink()可以删除目录。
  • file.rename()—为文件改名。
  • file.append()—把两个文件相连。
  • file.copy()—复制文件。
  • basename()dirname()— 从一个全路径文件名获取文件名和目录。

15.7 SQL数据库访问

15.7.1 介绍

对于大型的数据, 或者保存在多个表中的复杂数据, 经常会保存在一个数据库中。 数据库可以存在于专用的数据库服务器硬件上, 也可以是本机中的一个系统程序, 或者R直接管理的一个文件。

比较通用的数据库是关系数据库, 这样的数据库已经有很标准的设计理念和管理方法, 从用户使用的角度来看, 都可以使用一种专用的SQL语言来访问和管理。

R通过扩展包可以访问许多种常用的关系数据库系统, 这些扩展包大多按照DBI扩展包规定的接口规范为用户提供了方便的访问功能。

15.7.2 SQLite数据库访问

SQLite是一个开源的、轻量级的数据库软件, 其数据库可以保存在本机的一个文件中, R的RSQLite扩展包直接提供了SQLite数据库功能。 如果自己的研究数据规模很大,比如有几个GB, 不利于整体读入到计算机内存当中, 而每次使用时只需要其中的一个子集, 就可以采用保存数据到SQLite数据库文件的方法。

学会了在R中使用SQLite数据库, 其它的数据库也可以类似地使用。

15.7.2.1 NHANES数据

我们以NHANES扩展包的NHANES数据框为例演示在R中访问关系数据库的方法。 在数据库中一个数据框叫做一个表(table)。 NHANES表来自美国国家健康统计中心的调查数据, 该调查项目从1960年代开始对美国非住院非军人住户进行健康与营养方面的抽样调查, 从1999年开始大约5000各个年龄的受调查者每年在自己家接受调查员面访, 完成调查中的健康检查部分, 健康检查是在流动检查中心进行的。 抽样调查有复杂的抽样设计, 并不是简单随机抽样。 R扩展包中NHANES数据框经过了重抽样使其尽可能与目标总体的各种比例一致, 但数据应仅用作教学演示目的。 NHANES中有10000个观测, 是在2009-2010和2011-2012两次得到的, 有75个测试变量。 部分变量为:

  • SurveyYr:用来区分两次考察。
  • ID:受试者编码。
  • Gender: 性别,male 或 female。
  • Age: 年龄,80岁以上录入为80。
  • Race1: 种族,可取Mexican, Hispanic, White, Black, 或 Other。
  • Education:20岁及以上受试者的受教育级别,可取8thGrade, 9-11thGrade, HighSchool, SomeCollege, 或 CollegeGrad。
  • MaritalStatus:婚姻状态,可取Married, Widowed, Divorced, Separated, NeverMarried, 或 LivePartner(与伴侣生活)。
  • Weight: 体重(千克)。
  • Height: 身高(厘米),对2岁以上。

15.7.2.2 初始化新SQLite数据库

用使用RSQLite,先载入RSQLite扩展包, 然后指定一个SQLite数据库文件(不需要是已经存在的), 用dbConnect()打开该数据库建立连接(如果没有就新建):

library(RSQLite)
f_sqlite <- "_tmp/db2020.SQLITE"
con <- dbConnect(drv=SQLite(), dbname=f_sqlite)

可以用dbWriteTable()函数将NHANES数据框写入到打开的数据库中:

data(NHANES, package="NHANES")
dbWriteTable(conn=con, name="nh", value=NHANES)

写入后,数据库中的表名为nh。

15.7.2.3 查看数据库中的表

假设连接con还保持打开状态, 可以用dbListTables()查看数据库中有哪些表:

dbListTables(con)
## [1] "nh"

可以用dbListFields()查看某个表有哪些列, 数据库中称为域(fields):

dbListFields(con, "nh")
##  [1] "ID"               "SurveyYr"         "Gender"          
##  [4] "Age"              "AgeDecade"        "AgeMonths" 
##  ………………

15.7.2.4 读入数据库中的表

为了从数据库读取整个数据表(数据框), 建立连接后用dbReadTable()函数读取,如:

d1 <- dbReadTable(
  conn=con, name="nh")
d1 %>%
  count(SurveyYr)
## # A tibble: 2 x 2
##   SurveyYr     n
##   <chr>    <int>
## 1 2009_10   5000
## 2 2011_12   5000

两次调查各有5000个观测。

15.7.2.5 用SQL命令访问数据

还可以用dbGetQuery()执行SQL查询并以数据框格式返回查询结果。 比如, 仅返回SurveyYr和ID两列, 且仅选择男性:

d2 <- dbGetQuery(
  conn=con, 
  statement=paste(
    "SELECT SurveyYr, ID",
    "  FROM nh",
    "  WHERE Gender='male'"))
d2 %>%
  count(SurveyYr)
## # A tibble: 2 x 2
##   SurveyYr     n
##   <chr>    <int>
## 1 2009_10   2475 
## 2 2011_12   2505

15.7.2.6 分批读入数据库中的表

对于特别大的表, 可能超出了计算机的内存, 无法整体读入到R当中。 如果只需要一个子集, 可以用上面的dbGetQuery()执行SQL命令在数据库中提取子集并仅将需要的子集读入到R中。 如果需要读入的部分还是超过了内存, 或者全部读入会使得处理速度很慢, 可以分批读入,分批处理。

先用dbSendQuery()发送SQL查询命令:

qry <- dbSendQuery(
  conn=con, 
  statement=paste(
    "SELECT Weight ",
    "  FROM nh",
    "  WHERE Gender='male'"))

然后, 用dbHasCompleted()检查是否已经将结果取完, 如果没有取完就用dbFetch()取出一定行数, 分段处理:

s <- 0
n <- 0
while(!dbHasCompleted(qry)){
  chunk = dbFetch(qry, n=1000)
  n <- n + sum(!is.na(chunk[["Weight"]]))
  s <- s + sum(chunk[["Weight"]], na.rm=TRUE)
}
cat("Average Weight = ", s/n, "\n")

需要释放查询对应的资源:

dbClearResult(qry)

这段程序只是举例说明如何分段读入、分段处理, 如果只是要计算nh表中男性的Weight变量平均值, 可以读入整个nh表或者nh表中男性的Weight变量的所有值然后计算, 或者用SQL命令计算。

15.7.2.7 关闭数据库连接

在数据库使用完毕以后, 要记得关闭数据库:

dbDisconnect(con)

15.7.2.8 其它数据库操作

  • 删除表:dbRemoveTable(conn, name)
  • 检查某个表是否存在:dbExistsTable(conn, name)
  • 向一个表中插入保存在数据框中的一些行:dbAppendTable(conn, name, value)
  • 执行SQL命令并返回受到影响的行数:dbExecute(con, statement)

15.7.3 SQL命令简介

SQL是关系数据库查询和管理的专用语言, 关系数据库都支持SQL语言, 但彼此之间可能有一些技术性的差别。

用d.class数据框演示,有19个学生的姓名、性别、年龄、身高、体重。

str(d.class)
## spec_tbl_df [19 x 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ name  : chr [1:19] "Alice" "Becka" "Gail" "Karen" ...
##  $ sex   : Factor w/ 2 levels "M","F": 2 2 2 2 2 2 2 2 2 1 ...
##  $ age   : num [1:19] 13 13 14 12 12 15 11 15 14 14 ...
##  $ height: num [1:19] 56.5 65.3 64.3 56.3 59.8 66.5 51.3 62.5 62.8 69 ...
##  $ weight: num [1:19] 84 98 90 77 84.5 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   .default = col_double(),
##   ..   name = col_character(),
##   ..   sex = col_factor(levels = c("M", "F"), ordered = FALSE, include_na = FALSE),
##   ..   age = col_double(),
##   ..   height = col_double(),
##   ..   weight = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

下面建立一个内存中的SQLite数据库, 将d.class保存在数据库中:

library(RSQLite)
con <- dbConnect(drv=SQLite(), dbname=":memory:")
dbWriteTable(con, name="class", value=d.class)

15.7.3.1 取行子集

取出所有行的命令:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT *",
    "  FROM class"
  ))
dim(dtmp)
## [1] 19  5

为了取出满足条件的某些行, 在SQL命令中加上WHERE子句:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT *",
    "  FROM class",
    "  WHERE sex='F' AND age <= 12"  ))
dtmp
##    name sex age height weight
## 1 Karen   F  12   56.3   77.0
## 2 Kathy   F  12   59.8   84.5
## 3 Sandy   F  11   51.3   50.5

15.7.3.2 取行列子集

可以在SELECT子句中指定要取出的列,列名之间用逗号分隔, 如:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT name, age, height, weight",
    "  FROM class",
    "  WHERE sex='F' AND age <= 12"))
dtmp
##    name age height weight
## 1 Karen  12   56.3   77.0
## 2 Kathy  12   59.8   84.5
## 3 Sandy  11   51.3   50.5

在WHERE子句中可以使用如下的比较和逻辑运算:

  • = ^= > < >= <= IN
  • EQ NE GT LT GE LE IN
  • IS NULL表示“是空值”,与缺失值类似
  • BETWEEN a AND b, 表示属于闭区间\([a,b]\)
  • AND逻辑与,OR逻辑或,NOT逻辑非。

15.7.3.3 取某列的所有不同取值

在列名前加DISTINCT修饰,可以取出该列的所有不重复的值:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT DISTINCT sex",
    "  FROM class"))
dtmp
##   sex
## 1   F
## 2   M

多个变量的不重复的组合:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT DISTINCT sex, age",
    "  FROM class",
    "  WHERE age >= 15"))
dtmp
##   sex age
## 1   F  15
## 2   M  15
## 3   M  16

15.7.3.4 查询结果排序

ORDER BY子句排序:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT name, height",
    "  FROM class",
    "  WHERE name IN ('Alice', 'Becka', 'Gail')",
    "  ORDER BY height DESC"))
dtmp
##    name height
## 1 Becka   65.3
## 2  Gail   64.3
## 3 Alice   56.5

在变量名后面加后缀DESC表示降序。

15.7.3.5 查询时计算新变量

SELECT中用“表达式 AS 变量名”的格式计算新变量。 例如,class表中的身高以英寸为单位, 体重以磅为单位,分别转换为厘米和千克单位:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT name, round(height*2.54) AS hcm,",
    "    round(weight*0.4535924) as wkg",
    "  FROM class"))
head(dtmp, 3)
##    name hcm wkg
## 1 Alice 144  38
## 2 Becka 166  44
## 3  Gail 163  41

15.7.3.6 分组汇总

GROUP BY子句对观测分组, 用SUM, AVG等统计函数计算汇总统计量。

统计函数有:

  • COUNT(*)表示行数;
  • SUM(x)求和;
  • AVG(x)求平均;
  • MAX(x), MIN(x)求最大值和最小值。

例如:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT COUNT(*) AS n, AVG(height) AS avgh",
    "  FROM class"))
dtmp
##    n     avgh
## 1 19 62.33684

分组计算如:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT sex, COUNT(*) AS n, AVG(height) AS avgh",
    "  FROM class",
    "  GROUP BY sex"))
dtmp
##   sex  n     avgh
## 1   F  9 60.58889
## 2   M 10 63.91000

在用了GROUP BY以后, 对统计结果的筛选条件要写在HAVING子句中而不是用WHERE子句,如:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT age, COUNT(*) AS n",
    "  FROM class",
    "  GROUP BY age",
    "  HAVING COUNT(*)=1"))
dtmp
##   age n
## 1  16 1

15.7.3.7 将查询结果保存为新表

可以将查询结果保存在数据库中,而不是返回到R中。 这时, 需要用dbExecute()函数, 返回值是受到影响的行数:

for(tab in c("class1", "class2")){
  if(dbExistsTable(con, tab)){
    dbRemoveTable(con, tab)
  }
}

dbExecute(
  conn=con,
  statement=paste(
    "CREATE TABLE class1 AS",
    "  SELECT name, sex",
    "    FROM class"
  ))
## [1] 0

dbExecute(
  conn=con,
  statement=paste(
    "CREATE TABLE class2 AS",
    "  SELECT name, age",
    "    FROM class"
  ))
## [1] 0

dbListTables(con)
## [1] "class"  "class1" "class2"

在执行比较复杂的查询时, 可以用这种方法生成一些中间结果, 最后要删除这些作为中间结果的表。

15.7.3.8 从多个表查询

使用数据库的好处除了可以管理大型数据, 还有许多其它好处, 比如可以保证数据被修改时不会因断电、网络故障等出错, 可以并发读取或修改, 可以备份、恢复, 等等。

关系数据库经常需要将有关的信息保存在多张表中, 而不是使用一张大表, 这与数据库的设计理念有关, 可以减少数据冗余, 增强数据的一致性。 但是, 在使用这些信息时, 就需要从多张表进行查询, 称为表的连接查询。

最常见的连接查询是所谓内连接(INNER JOIN), 按照某一个或者某几个关键列将数据行对齐进行查询。 最容易理解的一对一的查询, 比如, 设学生的姓名、性别保存在class1表中, 姓名、年龄保存在class2表中, 没有重名, 则姓名可以作为关键列。 如果要查询女生年龄大于大于15的人, 就需要使用两张表:

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT a.name, sex, age",
    "  FROM class1 AS a, class2 AS b",
    "  WHERE a.name = b.name AND sex='F' AND age >= 15"))
dtmp
##     name sex age
## 1   Mary   F  15
## 2 Sharon   F  15

上面的程序中WHERE子句中的a.name = b.name就是内连接。 在使用多张表时, 在FROM子句的多张表之间用逗号分隔, 一般在表名后用AS关键字引入一个临时的别名, 对两张表中共同的变量名如name需要用a.nameb.name区分。

内连接也支持一对多的连接。 比如, 下面的表将F映射到, M映射到, 可以按sex连接:

dclass.sexm <- data.frame(
  sex = c("F", "F"), 
  sexc = c("女", "男"))
if(dbExistsTable(con, "sexm")){
  dbRemoveTable(con, "sexm")
}
dbWriteTable(con, name="sexm", value=dclass.sexm)

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT a.name, a.sex, sexc",
    "  FROM class1 AS a, sexm AS b",
    "  WHERE a.sex = b.sex AND name IN ('Alice', 'Alfred')"))
dtmp
##    name sex sexc
## 1 Alice   F   女
## 2 Alice   F   男

在内连接时如果关键列的值匹配后形成多对多的连接, 将会做两两搭配组合。

内连接仅保留关键列能匹配的行。 如果希望保留不能匹配的行, 就要使用外连接, 分为:

  • 左外连接,保留左表的所有行,右表仅保留匹配的行;
  • 右外连接,保留右表的所有行,左表仅保留匹配的行;
  • 全外连接,保留所有匹配和不匹配的行。

左外连接的程序示例:

d1 <- data.frame(
  id = c("a", "b"),
  x = c(11, 12))
d2 <- data.frame(
  id = c("a", "c"),
  y = c(21, 22))
knitr::kable(d1)
id x
a 11
b 12
knitr::kable(d2)
id y
a 21
c 22
dbWriteTable(con, name="table1", value=d1)
dbWriteTable(con, name="table2", value=d2)

dtmp <- dbGetQuery(
  conn=con,
  statement=paste(
    "SELECT a.id, x, y",
    "  FROM table1 AS a LEFT JOIN table2 AS b",
    "  ON A.id=b.id"))
dtmp
## id  x  y
## 1  a 11 21
## 2  b 12 NA

右外连接用RIGHT JOIN关键字, 全外连接用FULL OUTER JOIN关键字。 但SQLite目前不支持右外连接和全外连接, 其它的数据库一般是可以支持的。

15.7.4 访问Oracle数据库

Oracle是最著名的数据库服务器软件。 要访问的数据库, 可以是安装在本机上的, 也可以是安装在网络上某个服务器中的。 如果是远程访问, 需要在本机安装Oracle的客户端软件。

假设已经在本机安装了Oracle服务器软件, 并设置orcl为本机安装的Oracle数据库软件或客户端软件定义的本地或远程Oracle数据库的标识, test和oracle是此数据库的用户名和密码, testtab是此数据库中的一个表。

为了在R中访问Oracle数据库服务器中的数据库, 在R中需要安装ROracle包。 这是一个源代码扩展包, 需要用户自己编译安装。 在MS Windows环境下, 需要安装R软件和RTools软件包(在CRAN网站的Windows版本软件下载栏目中)。 在MS Windows命令行窗口,用如下命令编译R的ROracle扩展包:

set OCI_LIB32=D:\oracle\product\10.2.0\db_1\bin
set OCI_INC=D:\oracle\product\10.2.0\db_1\oci\include
set PATH=D:\oracle\product\10.2.0\db_1\bin;C:\Rtools\bin;C:\Rtools\gcc-4.6.3\bin;"%PATH%"
C:\R\R-3.2.0\bin\i386\rcmd INSTALL ROracle_1.2-1.tar.gz

其中的前三个set命令设置了Oracle数据库程序或客户端程序链接库、头文件和可执行程序的位置, 第三个set命令还设置了RTools编译器的路径。 这些路径需要根据实际情况修改。 这里的设置是在本机运行的Oracle 10g服务器软件的情况。 最后一个命令编译ROracle扩展包,相应的rcmd程序路径需要改成自己的安装路径。

如果服务器在远程服务器上, 设远程服务器的数据库标识名为ORCL, 本机需要安装客户端Oracle instant client软件, 此客户端软件需要与服务器同版本号, 如instantclient-basic-win32-10.2.0.5.zip, 这个软件不需要安装, 只需要解压到一个目录如 C:\instantclient_10_2中。 在本机(以MS Windows操作系统为例)中, 双击系统,选择高级–环境变量, 增加如下三个环境变量:

NLS_LANG = SIMPLIFIED CHINESE_CHINA.ZHS16GBK
ORACLE_HOME = C:\instantclient_10_2
TNS_ADMIN = C:\instantclient_10_2

并在环境变量PATH的值的末尾增加Oracle客户端软件所在的目录 verb|C:\instantclient_10_2, 并与前面内容用分号分开。

然后,在client所在的目录 C:\instantclient_10_2 中增加如下内容的tnsnames.ora`文件

  orcl = 
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.102 )
     (PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = orcl)
    )
  )

其中HOST的值是安装Oracle服务器的服务器的IP地址, orcl是一个服务器实例名, 能够在服务器端的tnsnames.ora文件中查到, 等号前面的orcl是对数据库给出的客户端别名, 这里就干脆用了和服务器端的数据库标识名相同的名字orcl。

不论是在本机的数据框服务器还是在本机安装设置好客户端后, 在R中用如下的程序可以读入数据库中的表:

libraryROracle)
drv <- dbDriver("Oracle")

conn <- dbConnect(drv, username="test", 
                  password="oracle",  dbname="orcl")

rs <- dbSendQuery(conn, "select * from testtab")
d <- fetch(rs)

可以用dbGetTable()取出一个表并存入R数据框中。 用dbSendQuery()发出一个SQL命令, 用fetch()可以一次性取回或者分批取回, 在表行数很多时这种方法更适用。

15.7.5 MySQL数据库访问

MySQL是高效、免费的数据库服务器软件, 在很多行业尤其是互联网行业占有很大的市场。 为了在R中访问MySQL数据库, 只要安装RMySQL扩展包(有二进制版本)。 现在性能更好的一个连接MySQL的扩展包是RMariaDB。

假设服务器地址在 192.168.1.111, 可访问的数据库名为 world, 用户为 test, 密码为 mysql。 设world库中有表country。

在R中要访问MySQL数据框,首先要建立与数据库服务器的连接:

library(RMySQL)
con <- dbConnect(RMySQL::MySQL(), 
    dbname="world",
    username="test", password="mysql",
    host="192.168.1.111")

下列代码列出world库中的所有表, 然后列出其中的country表的所有变量:

dbListTables(con)
dbListFields(con, "country")

下列代码取出country表并存入R数据框d.country中:

d.country <- dbReadTable(con, "country")

下列代码把R中的示例数据框USArrests写入MySQL库world的表arrests中:

data(USArrests)
dbWriteTable(con, "arrests", USArrests, 
    overwrite=TRUE)

当然,这需要用户对该库有写权限。

可以用dbGetQuery()执行一个SQL查询并返回结果,如

dbGetQuery(con, "select count(*) from arrests")

当表很大时,可以用dbSendQuery()发送一个SQL命令, 返回一个查询结果指针对象, 用dbFetch()从指针对象位置读取指定行数, 用dbHasCompleted()判断是否已读取结束。如

res <- dbSendQuery(con, "SELECT * FROM country")
while(!dbHasCompleted(res)){
  chunk <- dbFetch(res, n = 5)
  print(chunk[,1:2])
}
dbClearResult(res)

数据库使用完毕时, 需要关闭用dbConnect()打开的连接:

dbDisconnect(con)

15.7.6 利用RODBC访问Access数据库

扩展包RODBC在MS Windows操作系统中可以访问Excel、Access、dBase、FoxPro等微机数据库软件的数据库, 也可以在安装了需要的ODBC客户端后访问Oracle等数据库。 用odbc包访问远程数据库速度更快, 而且遵从DBI的接口规范, RODBC不使用DBI接口规范。

假设有Access数据库在文件c:/Friends/birthdays.mdb中, 内有两个表Men和Women, 每个表包含域Year, Month, Day, First Name, Last Name, Death。 域名应尽量避免用空格。

下面的程序把女性记录的表读入为R数据框:

library(RODBC)
con <- odbcConnectAccess("c:/Friends/birthdays.mdb")
women <- sqlFetch(con, sqtable="Women")
close(con)

RODBC还有许多与数据库访问有关的函数, 比如,sqlQuery()函数可以向打开的数据库提交任意符合标准的SQL查询, sqlSave()可以将R数据框保存到数据库中。