附录D 练习答案

练习1-1

如果你遇到困难,请咨询你的系统管理员,或者在R-help邮件列表上提问。

练习1-2

使用冒号操作符来创建一个向量,然后使用sd函数:

sd(0:100)

练习1-3

输入demo(plotmath)并按下回车键,或者点击图形看看它能提供什么。

练习2-1

  • 简单的除法就可以得到倒数,然后使用atan计算反正切(arc):
  1. `atan( 1 / 1:1000 )
  • 使用<-来给变量赋值:
  1. x <- - 1:1000
  2. y <- atan(1/x)
  3. z <- 1 /tan(y)

练习2-2

对于比较两个应该包含相同数字的向量,通常all.equal就是你所需要的:

  1. x == z
  2. identical(x, z)
  3. all.equal(x, z)
  4. all.equal(x, z, tolerance = 0)

练习2-3

包含在以下三个向量的精确值可能不同:

  1. true_and_missing <- c(NA, TRUE, NA)
  2. false_and_missing <- c(FALSE, FALSE, NA)
  3. mixed <- c(TRUE, FALSE, NA)
  4. any(true_and_missing)
  5. any(false_and_missing)
  6. any(mixed)
  7. all(true_and_missing)
  8. all(false_and_missing)
  9. all(mixed)

练习3-1

  1. class(Inf)
  2. class(NA)
  3. class(NaN)
  4. class("")

把class代替为typeofmodestorage.mode重复以上例子。

练习3-2

  1. pets <- factor(sample(
  2. c("dog", "cat", "hamster", "goldfish"),
  3. 1000,
  4. replace = TRUE
  5. ))
  6. head(pets)
  7. summary(pets)

建议转换为因子,但这并非强制。

练习3-3

  1. carrot <- 1
  2. potato <- 2
  3. swede <- 3
  4. ls(pattern = "a")

你定义的蔬菜可能会有所不同。

练习4-1

创建序列有几种方法,其中包括冒号运算符。此解决方案使用seq_lenseq_along

  1. n <- seq_len(20)
  2. triangular <- n * (n + 1) / 2
  3. names(triangular) <- letters[seq_along(n)]
  4. triangular[c("a", "e", "i", "o")]

练习4-2

同样地,创建从-11序列为0再到11的序列有许多种不同的方法。abs能计算一个数的绝对值:

  1. diag(abs(seq.int(-11, 11)))

练习4-3

威尔金森(Wilkinson)矩阵有一个有趣的属性,它们的大部分特征值几乎相等。21x21矩阵是最经常使用的:

  1. identity_20_by_21 <- diag(rep.int(1, 20), 20, 21)
  2. below_the_diagonal <- rbind(0, identity_20_by_21)
  3. identity_21_by_20 <- diag(rep.int(1, 20), 21, 20)
  4. above_the_diagonal <- cbind(0, identity_21_by_20)
  5. on_the_diagonal <- diag(abs(seq.int(-10, 10)))
  6. wilkinson_21 <- below_the_diagonal + above_the_diagonal + on_the_diagonal
  7. eigen(wilkinson_21)$values

练习5-1

为简单起见,这里我已经手动指定哪些数字是平方。你能想出一个方法来自动确定一个数是否为平方数吗?

  1. list(
  2. "0 to 9" = c(0, 1, 4, 9),
  3. "10 to 19" = 16,
  4. "20 to 29" = 25,
  5. "30 to 39" = 36,
  6. "40 to 49" = 49,
  7. "50 to 59" = NULL,
  8. "60 to 69" = 64,
  9. "70 to 79" = NULL,
  10. "80 to 89" = 81,
  11. "90 to 99" = NULL
  12. )

我们还可以自动计算平方数。以下的cut函数将创建不同的范围组:0〜9、10〜19,依此类推,然后返回一个向量,指出每个平方数在哪一组。然后split函数把平方数向量分开成一个列表,其中每个元素包含所对应组里的值:

  1. x <- 0:99
  2. sqrt_x <- sqrt(x)
  3. is_square_number <- sqrt_x == floor(sqrt_x)
  4. square_numbers <- x[is_square_number]
  5. groups <- cut(
  6. square_numbers,
  7. seq.int(min(x), max(x), 10),
  8. include.lowest = TRUE,
  9. right = FALSE
  10. )
  11. split(square_numbers, groups)

练习5-2

有许多种不同的方法可以获得iris数据集的数值子集。实验一下吧!

  1. iris_numeric <- iris[, 1:4]
  2. colMeans(iris_numeric)

练习5-3

这个解决方案是许多对数据框取索引和求子集的方法之一:

  1. beaver1$id <- 1
  2. beaver2$id <- 2
  3. both_beavers <- rbind(beaver1, beaver2)
  4. subset(both_beavers, as.logical(activ))

练习6-1

我们可以使用new.env创建一个环境。之后的语法与列表一样:

  1. multiples_of_pi <- new.env()
  2. multiples_of_pi[["two_pi"]] <- 2 * pi
  3. multiples_of_pi$three_pi <- 3 * pi
  4. assign("four_pi", 4 * pi, multiples_of_pi)
  5. ls(multiples_of_pi)
  6. ## [1] "four_pi" "three_pi" "two_pi"

练习6-2

这比你想象的要更容易。我们只需要在除以二时使用模运算符来获得余数,然后一切就能自动工作,也包括了非无限值:

  1. is_even <- function(x) (x %% 2) == 0
  2. is_even(c(-5:5, Inf, -Inf, NA, NaN))
  3. ## [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE
  4. ## [12] NA NA NA NA

练习6-3

同样,该函数只需要一行即可。formalsbody函数能完成最难的那部分工作,而我们只需要返回其结果列表:

  1. args_and_body <- function(fn)
  2. {
  3. list(arguments = formals(fn), body = body(fn))
  4. }
  5. args_and_body(var)
  6. ## $arguments
  7. ## $arguments$x
  8. ##
  9. ##
  10. ## $arguments$y
  11. ## NULL
  12. ##
  13. ## $arguments$na.rm
  14. ## [1] FALSE
  15. ##
  16. ## $arguments$use
  17. ##
  18. ##
  19. ##
  20. ## $body
  21. ## {
  22. ## if (missing(use))
  23. ## use <- if (na.rm)
  24. ## "na.or.complete"
  25. ## else "everything"
  26. ## na.method <- pmatch(use, c("all.obs", "complete.obs",
  27. ## "pairwise.complete.obs", "everything", "na.or.complete"))
  28. ## if (is.na(na.method))
  29. ## stop("invalid 'use' argument")
  30. ## if (is.data.frame(x))
  31. ## x <- as.matrix(x)
  32. ## else stopifnot(is.atomic(x))
  33. ## if (is.data.frame(y))
  34. ## y <- as.matrix(y)
  35. ## else stopifnot(is.atomic(y))
  36. ## .Call(C_cov, x, y, na.method, FALSE)
  37. ## }
  38. args_and_body(alarm)
  39. ## $arguments
  40. ## NULL
  41. ##
  42. ## $body
  43. ## {
  44. ## cat("\a")
  45. ## flush.console()
  46. ## }

练习7-1

  1. formatC(pi, digits = 16)
  2. ## [1] "3.141592653589793"

如果你要把formatC替换成formatprettyNum这也适用。

练习7-2

调用strsplit并使用正则表达式来匹配空格和标点符号。记得能匹配一个字符:

  1. # 使用一个可选的逗号,必须的空格,可以的连字符以及一个可选的空格分开strsplit(x, ",? -? ?")
  2. ## [[1]]
  3. ## [1] "Swan" "swam" "over" "the" "pond" "Swim" "swan" "swim!"
  4. ##
  5. ## [[2]]
  6. ## [1] "Swan" "swam" "back" "again" "Well" "swum" "swan!"
  7. # 或者把最后的连字符的空格括起来
  8. strsplit(x, ",? (- )?")
  9. ## [[1]]
  10. ## [1] "Swan" "swam" "over" "the" "pond" "Swim" "swan" "swim!"
  11. ##
  12. ## [[2]]
  13. ## [1] "Swan" "swam" "back" "again" "Well" "swum" "swan!"

练习7-3

默认情况下,cut所创建的间隔只包括上限而没有下限。为了获得最小值(3)的计数,我们需要在它下面(例如,在2)加入一个额外的断点。尝试使用plyr包中的count替换掉table

  1. scores <- three_d6(1000)
  2. bonuses <- cut(
  3. scores,
  4. c(2, 3, 5, 8, 12, 15, 17, 18),
  5. labels = -3:3
  6. )
  7. table(bonuses)
  8. ## bonuses
  9. ## -3 -2 -1 0 1 2 3
  10. ## 4 39 186 486 233 47 5

练习8-1

使用if区分其条件。运算符%in%能使条件的检查更容易:

  1. score <- two_d6(1)
  2. if(score %in% c(2, 3, 12))
  3. {
  4. game_status <- FALSE
  5. point <- NA
  6. } else if(score %in% c(7, 11))
  7. {
  8. game_status <- TRUE
  9. point <- NA
  10. } else
  11. {
  12. game_status <- NA
  13. point <- score
  14. }

练习8-2

因为我们不明确代码所要执行的次数,所以使用repeat循环是最合适的:

  1. if(is.na(game_status))
  2. {
  3. repeat({
  4. score <- two_d6(1)
  5. if(score == 7)
  6. {
  7. game_status <- FALSE
  8. break
  9. } else
  10. if(score == point)
  11. {
  12. game_status <- TRUE
  13. break
  14. }
  15. })
  16. }

练习8-3

因为我们知道需要执行的循环(从最短单词的长度到最长单词的长度)的次数,for循环是最合适的:

  1. nchar_sea_shells <- nchar(sea_shells)
  2. for(i in min(nchar_sea_shells):max(nchar_sea_shells))
  3. {
  4. message("These words have ", i, " letters:")   
  5. print(toString(unique(sea_shells[nchar_sea_shells == i])))
  6. }
  7. ## These words have 2 letters:
  8. ## [1] "by, So, if, on"
  9. ## These words have 3 letters:
  10. ## [1] "She, sea, the, The, she, are, I'm"
  11. ## These words have 4 letters:
  12. ## [1] "sure"
  13. ## These words have 5 letters:
  14. ## [1] "sells"
  15. ## These words have 6 letters:
  16. ## [1] "shells, surely"
  17. ## These words have 7 letters:
  18. ## [1] ""
  19. ## These words have 8 letters:
  20. ## [1] "seashore"
  21. ## These words have 9 letters:
  22. ## [1] "seashells"

练习9-1

由于输入的是一个列表,输出的始终相同而且长度已知,所以vapply是最好的选择:

  1. vapply(wayans, length, integer(1))
  2. ## Dwayne Kim Keenen Ivory Damon Kim Shawn
  3. ## 0 5 4 0 3
  4. ## Marlon Nadia Elvira Diedre Vonnie
  5. ## 2 0 2 5 0

练习9-2

1.我们可以通过使用strheadclass以及阅读帮助页面?state.x77大致感受一下此数据集:

  1. ## num [1:50, 1:8] 3615 365 2212 2110 21198 ...
  2. ## - attr(*, "dimnames")=List of 2
  3. ## ..$ : chr [1:50] "Alabama" "Alaska" "Arizona" "Arkansas" ...
  4. ## ..$ : chr [1:8] "Population" "Income" "Illiteracy" "Life Exp" ...
  5.  
  6. ## Population Income Illiteracy Life Exp Murder HS Grad Frost
  7. ## Alabama 3615 3624 2.1 69.05 15.1 41.3 20
  8. ## Alaska 365 6315 1.5 69.31 11.3 66.7 152
  9. ## Arizona 2212 4530 1.8 70.55 7.8 58.1 15
  10. ## Arkansas 2110 3378 1.9 70.66 10.1 39.9 65
  11. ## California 21198 5114 1.1 71.71 10.3 62.6 20
  12. ## Colorado 2541 4884 0.7 72.06 6.8 63.9 166
  13. ## Area
  14. ## Alabama 50708
  15. ## Alaska 566432
  16. ## Arizona 113417
  17. ## Arkansas 51945
  18. ## California 156361
  19. ## Colorado 103766
  20.  
  21.  
  22. ## [1] "matrix"

2.现在我们知道此数据是一个矩阵,因此apply函数最合适。我们要循环每列,因此我们把2作为维度值传递给它:

  1. ## Population Income Illiteracy Life Exp Murder HS Grad
  2. ## 4246.420 4435.800 1.170 70.879 7.378 53.108
  3. ## Frost Area
  4. ## 104.460 70735.880
  5. ## Population Income Illiteracy Life Exp Murder HS Grad
  6. ## 4.464e+03 6.145e+02 6.095e-01 1.342e+00 3.692e+00 8.077e+00
  7. ## Frost Area
  8. ## 5.198e+01 8.533e+04

练习9-3

tapply是R基本包中的最佳选择。而plyr包中的ddply也能很好地工作:

  1. with(commute_data, tapply(time, mode, quantile, prob = 0.75))
  2. ## bike bus car train
  3. ## 63.55 55.62 42.33 36.29
  4. ddply(commute_data, .(mode), summarize, time_p75 = quantile(time, 0.75))
  5. ## mode time_p75
  6. ## 1 bike 63.55
  7. ## 2 bus 55.62
  8. ## 3 car 42.33
  9. ## 4 train 36.29

练习10-1

在R的图形用户界面中,单击Package→“Install package(s)… ”,选择一个镜像,然后选择Hmisc。如果下载或安装失败,则请确保你有互联网接入和写入库目录的权限。你也可以尝试访问CRAN的网站来手动下载。问题中最常见原因是权限受到限制,在这种情况下请与你的系统管理员联系。

练习10-2

  1. install.packages(“lubridate”)

你可能希望指定一个库进行安装。

练习10-3

installed.packages函数能在你的机器上获取软件包的详细信息及其位置(以下的结果可能跟你机器上的位置不同):

  1. pkgs <- installed.packages()
  2. table(pkgs[, "LibPath"])
  3. ##
  4. ## C:/Program Files/R/R-devel/library D:/R/library
  5. ## 29 169

练习11-1

此示例使用strptime解析和使用strtime来进行格式化,除此之外还有其他很多方法:

  1. in_string <- c("1940-07-07", "1940-10-09", "1942-06-18", "1943-02-25")
  2. (parsed <- strptime(in_string, "%Y-%m-%d"))
  3. ## [1] "1940-07-07" "1940-10-09" "1942-06-18" "1943-02-25"
  4. (out_string <- strftime(parsed, "%a %d %b %y"))
  5. ## [1] "Sun 07 Jul 40" "Wed 09 Oct 40" "Thu 18 Jun 42" "Thu 25 Feb 43"

练习11-2

?Sys.timezone帮助页面建议了使用以下代码导入时区文件:

  1. tzfile <- file.path(R.home("share"), "zoneinfo", "zone.tab")
  2. tzones <- read.delim(
  3. tzfile,
  4. row.names = NULL,
  5. header = FALSE,
  6. col.names = c("country", "coords", "name", "comments"),
  7. as.is = TRUE,
  8. fill = TRUE,
  9. comment.char = "#"
  10. )

找到你所在时区的最佳方法取决于你在哪里。从View(tzones)开始,如有需要,使用subset来缩小搜索范围。

练习11-3

你可以通过访问一个基础R包中的POSIXlt日期组件解决此问题,但是使用lubridatemonthday函数会更加清楚:

  1. zodiac_sign <- function(x)
  2. {
  3. month_x <- month(x, label = TRUE)
  4. day_x <- day(x)
  5. switch(
  6. month_x,
  7. Jan = if(day_x < 20) "Capricorn" else "Aquarius",
  8. Feb = if(day_x < 19) "Aquarius" else "Pisces",
  9. Mar = if(day_x < 21) "Pisces" else "Aries",
  10. Apr = if(day_x < 20) "Aries" else "Taurus",
  11. May = if(day_x < 21) "Taurus" else "Gemini",
  12. Jun = if(day_x < 21) "Gemini" else "Cancer",
  13. Jul = if(day_x < 23) "Cancer" else "Leo",
  14. Aug = if(day_x < 23) "Leo" else "Virgo",
  15. Sep = if(day_x < 23) "Virgo" else "Libra",
  16. Oct = if(day_x < 23) "Libra" else "Scorpio",
  17. Nov = if(day_x < 22) "Scorpio" else "Sagittarius",
  18. Dec = if(day_x < 22) "Sagittarius" else "Capricorn"
  19. )
  20. }
  21. #可如下使用
  22. nicolaus_copernicus_birth_date <- as.Date("1473-02-19")
  23. zodiac_sign(nicolaus_copernicus_birth_date)
  24. ## [1] "Pisces"

请注意,switch语句的使用意味着这个函数的实现并非向量化。你可以通过大量使用ifelse语句来实现矢量化,或采取更容易地调用Vectorize(zodiac_sign)的方法。

练习12-1

使用system.file找到该文件并使用read.csv导入数据:

  1. hafu_file <- system.file("extdata", "hafu.csv", package = "learningr")
  2. hafu_data <- read.csv(hafu_file)

练习12-2

使用xlsx包中的read.xlsx2函数导入数据。该数据在第一个(也是唯一的)工作表,而且最好指定每列的数据的类型:

  1. library(xlsx)
  2. gonorrhoea_file <- system.file(
  3. "extdata",
  4. "multi-drug-resistant gonorrhoea infection.xls",
  5. package = "learningr"
  6. )
  7. gonorrhoea_data <- read.xlsx2(
  8. gonorrhoea_file,
  9. sheetIndex = 1,
  10. colClasses = c("integer", "character", "character", "numeric")
  11. )

练习12-3

使用RSQLite包的方法有好几种。一次一个步骤,我们可以像这样连接数据库:

  1. library(RSQLite)
  2. driver <- dbDriver("SQLite")
  3. db_file <- system.file("extdata", "crabtag.sqlite", package = "learningr")
  4. conn <- dbConnect(driver, db_file)
  5. query <- "SELECT * FROM Daylog"
  6. head(daylog <- dbGetQuery(conn, query))
  7. ## Tag ID Mission Day Date Max Temp Min Temp Max Depth Min Depth
  8. ## 1 A03401 0 08/08/2008 27.734 25.203 0.06 -0.07
  9. ## 2 A03401 1 09/08/2008 25.203 23.859 0.06 -0.07
  10. ## 3 A03401 2 10/08/2008 24.016 23.500 -0.07 -0.10
  11. ## 4 A03401 3 11/08/2008 26.453 23.281 -0.04 -0.10
  12. ## 5 A03401 4 12/08/2008 27.047 23.609 -0.10 -0.26
  13. ## 6 A03401 5 13/08/2008 24.625 23.438 -0.04 -0.13
  14. ## Batt Volts
  15. ## 1 3.06
  16. ## 2 3.09
  17. ## 3 3.09
  18. ## 4 3.09
  19. ## 5 3.09
  20. ## 6 3.09
  21. dbDisconnect(conn)
  22. ## [1] TRUE
  23. dbUnloadDriver(driver)
  24. ## [1] TRUE

或者放肆一些,使用在第十二章中定义的函数:

  1. head(daylog <- query_crab_tag_db("SELECT * FROM Daylog"))

或者,我们可以使用dbReadTable函数再进一步简化:

  1. get_table_from_crab_tag_db <- function(tbl)
  2. {
  3. driver <- dbDriver("SQLite")
  4. db_file <- system.file("extdata", "crabtag.sqlite", package = "learningr")
  5. conn <- dbConnect(driver, db_file)
  6. on.exit(
  7. {
  8. dbDisconnect(conn)
  9. dbUnloadDriver(driver)
  10. }
  11. )
  12. dbReadTable(conn, tbl)
  13. }
  14. head(daylog <- get_table_from_crab_tag_db("Daylog"))
  15. ## Tag.ID Mission.Day Date Max.Temp Min.Temp Max.Depth Min.Depth
  16. ## 1 A03401 0 08/08/2008 27.734 25.203 0.06 -0.07
  17. ## 2 A03401 1 09/08/2008 25.203 23.859 0.06 -0.07
  18. ## 3 A03401 2 10/08/2008 24.016 23.500 -0.07 -0.10
  19. ## 4 A03401 3 11/08/2008 26.453 23.281 -0.04 -0.10
  20. ## 5 A03401 4 12/08/2008 27.047 23.609 -0.10 -0.26
  21. ## 6 A03401 5 13/08/2008 24.625 23.438 -0.04 -0.13
  22. ## Batt.Volts
  23. ## 1 3.06
  24. ## 2 3.09
  25. ## 3 3.09
  26. ## 4 3.09
  27. ## 5 3.09
  28. ## 6 3.09

练习13-1

1.为了检测问号,使用str_detect

  1. library(stringr)
  2. data(hafu, package = "learningr")
  3. hafu$FathersNationalityIsUncertain <- str_detect(hafu$Father, fixed("?"))
  4. hafu$MothersNationalityIsUncertain <- str_detect(hafu$Mother, fixed("?"))

2.要替换这些问号,使用str_replace函数:

  1. hafu$Father <- str_replace(hafu$Father, fixed("?"), "")
  2. hafu$Mother <- str_replace(hafu$Mother, fixed("?"), "")

练习13-2

请确保你已经安装了reshape2包!关键是把测量变量命名为“Father”和“Mother”:

  1. hafu_long <- melt(hafu, measure.vars = c("Father", "Mother"))

练习13-3

我们可以使用基础R包中的函数来完成,例如table函数能为我们计数,sorthead的组合可以使我们能够找到相同的值。把useNA = "always"传递给table意味着NA将始终包含在counts向量中:

  1. top10 <- function(x)
  2. {
  3. counts <- table(x, useNA = "always")
  4. head(sort(counts, decreasing = TRUE), 10)
  5. }
  6. top10(hafu$Mother)
  7. ## x
  8. ## Japanese <NA> English American French German Russian
  9. ## 120 50 29 23 20 12 10
  10. ## Fantasy Italian Brazilian
  11. ## 8 4 3

plyr包提供了另一种替代的解决方案,它能把答案作为一个数据框返回,这对进一步操纵结果可能更为有用:

  1. top10_v2 <- function(x)
  2. {
  3. counts <- count(x)
  4. head(arrange(counts, desc(freq)), 10)
  5. }
  6. top10_v2(hafu$Mother)
  7. ## x freq
  8. ## 1 Japanese 120
  9. ## 2 <NA> 50
  10. ## 3 English 29
  11. ## 4 American 23
  12. ## 5 French 20
  13. ## 6 German 12
  14. ## 7 Russian 10
  15. ## 8 Fantasy 8
  16. ## 9 Italian 4
  17. ## 10 Brazilian 3

练习14-1

1.cor计算相关性,并且默认为Pearson相关性。对其他类型使用method参数实验一下:

  1. with(obama_vs_mccain, cor(Unemployment, Obama))
  2. ## [1] 0.2897

2.以base/lattice/ggplot2的顺序,我们有:

  1. with(obama_vs_mccain, plot(Unemployment, Obama))
  2. xyplot(Obama ~ Unemployment, obama_vs_mccain)
  3. ggplot(obama_vs_mccain, aes(Unemployment, Obama)) +  geom_point()

练习14-2

1.直方图:

  1. plot_numbers <- 1:2
  2. layout(matrix(plot_numbers, ncol = 2, byrow = TRUE))
  3. for(drug_use in c(FALSE, TRUE))
  4. {
  5. group_data <- subset(alpe_d_huez2, DrugUse == drug_use)
  6. with(group_data, hist(NumericTime))
  7. }
  8. histogram(~ NumericTime | DrugUse, alpe_d_huez2)
  9. ggplot(alpe_d_huez2, aes(NumericTime)) +
  10. geom_histogram(binwidth = 2) +
  11. facet_wrap(~ DrugUse)

2.盒形图:

  1. boxplot(NumericTime ~ DrugUse, alpe_d_huez2)
  2. bwplot(DrugUse ~ NumericTime, alpe_d_huez2)
  3. ggplot(alpe_d_huez2, aes(DrugUse, NumericTime, group = DrugUse)) +
  4. geom_boxplot()

练习14-3

为简单起见,我们只使用ggplot2给出此答案。由于这是一个数据探索,所以没有“正确”的答案:如果图中显示了一些你感到有趣的事情,那么这是值得画出的。当你有几个“干扰因素”(在本例中,我们有年龄/种族/性别)存在时,对于不同的绘图会有很多不同的可能性。一般情况下,处理多变量有两种策略:先作一个总体概述再添加变量;或者一开始就把所有包含在内,然后再删除那些看起来不那么有趣的变量。我们将使用第二种策略。

要看到每个变量的影响,最简单的方法的是把所有的东西都放到面板上:

  1. ggplot(gonorrhoea, aes(Age.Group, Rate)) +
  2. geom_bar(stat = "identity") +
  3. facet_wrap(~ Year + Ethnicity + Gender)

每个面板上的条形高度是不同的,特别是种族,但是我们很难在50个面板中看出是怎么回事。我们可以把每年的数据都画在相同的面板来进行简化。我们使用group来声明哪些值属于同一栏,使用fill为每个条形指定不同的填充颜色,还可使用position = "dodge"把条形彼此分开,而不是堆叠在一起:

  1. ggplot(gonorrhoea, aes(Age.Group, Rate, group = Year, fill = Year)) +
  2. geom_bar(stat = "identity", position = "dodge") +
  3. facet_wrap(~ Ethnicity + Gender)

面板数量减少是好事,但我还是觉得很难从那些条形中获取很多信息。由于大部分年龄组宽度的(五年)相同,我们可以稍用点手段:画一条以年龄为x轴的线图。虽然因为年龄组更宽一些,该图在每个面板的右侧看上去有点不对劲,但它已经可以提供某些信息了:

  1. ggplot(gonorrhoea, aes(Age.Group, Rate, group = Year, color = Year)) +
  2. geom_line() +
  3. facet_wrap(~ Ethnicity + Gender)

该线紧靠在一起,所以看上去似乎并没有显示出太大的时间趋势(虽然时间段正好是五年)。由于有两种切面的方式,我们可以通过使用facet_grip而不是facet_wrap稍微改善一下绘图:

  1. ggplot(gonorrhoea, aes(Age.Group, Rate, group = Year, color = Year)) +
  2. geom_line() +
  3. facet_grid(Ethnicity ~ Gender)

这清楚地表明种族对淋病感染率的影响:曲线要比在“非西班牙裔黑人”和“美洲印第安人和阿拉斯加土著”的群体高得多。由于这些群体占据了大部分,所以很难看到性别的影响。通过给每一行分配一个不同的y轴,它可以中和种族的影响,并更加清楚地看到男性和女性之间的区别:

  1. ggplot(gonorrhoea, aes(Age.Group, Rate, group = Year, color = Year)) +
  2. geom_line() +
  3. facet_grid(Ethnicity ~ Gender, scales = "free_y")

在这里你可以看到女性的感染率高于男性(在相同年龄组和民族中),并有一个更加持续的高峰期:从15到24岁;而男性为20到24岁。

练习15-1

1.在这种情况下,无论是错别字的数目x和错别字的平均数目lambda都是3

  1. dpois(3, 3)
  2. ## [1] 0.224

2.位数q为12(个月),size为1,因为我们只需要怀孕一次,每个月的probability0.25

  1. pnbinom(12 1 0.25 )
  2. ## [1] 0.9762

3.为了更加直截了当,我们只把probability设定为0.9

  1. Qbirthday(0.9)
  2. ## [1] 41

练习15-2

我们要么可以取数据集的一个子集(使用通常的索引方法或subset函数),要么把子集的详细信息传递给lmsubset参数:

  1. model1 <- lm(
  2. Rate ~ Year + Age.Group + Ethnicity + Gender,
  3. gonorrhoea,
  4. subset = Age.Group %in% c("15 to 19" ,"20 to 24" ,"25 to 29" ,"30 to 34")
  5. )
  6. summary(model1)
  7. ##
  8. ## Call:
  9. ## lm(formula = Rate ~ Year + Age.Group + Ethnicity + Gender, data = gonorrhoea,
  10. ## subset = Age.Group %in% c("15 to 19", "20 to 24", "25 to 29",
  11. ## "30 to 34"))
  12. ##
  13. ## Residuals:
  14. ## Min 1Q Median 3Q Max
  15. ## -774.0 -127.7 -10.3 106.2 857.7
  16. ##
  17. ## Coefficients:
  18. ## Estimate Std. Error t value Pr(>|t|)
  19. ## (Intercept) 9491.13 25191.00 0.38 0.7068
  20. ## Year -4.55 12.54 -0.36 0.7173
  21. ## Age.Group20 to 24 131.12 50.16 2.61 0.0097
  22. ## Age.Group25 to 29 -124.60 50.16 -2.48 0.0138
  23. ## Age.Group30 to 34 -259.83 50.16 -5.18 5.6e-07
  24. ## EthnicityAsians & Pacific Islanders -212.76 56.08 -3.79 0.0002
  25. ## EthnicityHispanics -124.06 56.08 -2.21 0.0281
  26. ## EthnicityNon-Hispanic Blacks 1014.35 56.08 18.09 < 2e-16
  27. ## EthnicityNon-Hispanic Whites -174.72 56.08 -3.12 0.0021
  28. ## GenderMale -83.85 35.47 -2.36 0.0191
  29. ##
  30. ## (Intercept)
  31. ## Year
  32. ## Age.Group20 to 24 **
  33. ## Age.Group25 to 29 *
  34. ## Age.Group30 to 34 ***
  35. ## EthnicityAsians & Pacific Islanders ***
  36. ## EthnicityHispanics *
  37. ## EthnicityNon-Hispanic Blacks ***
  38. ## EthnicityNon-Hispanic Whites **
  39. ## GenderMale *
  40. ## ---
  41. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  42. ##
  43. ## Residual standard error: 251 on 190 degrees of freedom
  44. ## Multiple R-squared: 0.798, Adjusted R-squared: 0.789
  45. ## F-statistic: 83.7 on 9 and 190 DF, p-value: <2e-16

Year不显著,因此我们将其删除:

  1. model2 <- update(model1, ~ . - Year)
  2. summary(model2)
  3. ##
  4. ## Call:
  5. ## lm(formula = Rate ~ Age.Group + Ethnicity + Gender, data = gonorrhoea,
  6. ## subset = Age.Group %in% c("15 to 19", "20 to 24", "25 to 29",
  7. ## "30 to 34"))
  8. ##
  9. ## Residuals:
  10. ## Min 1Q Median 3Q Max
  11. ## -774.0 -129.3 -6.7 104.3 866.8
  12. ##
  13. ## Coefficients:
  14. ## Estimate Std. Error t value Pr(>|t|)
  15. ## (Intercept) 358.2 53.1 6.75 1.7e-10
  16. ## Age.Group20 to 24 131.1 50.0 2.62 0.00949
  17. ## Age.Group25 to 29 -124.6 50.0 -2.49 0.01363
  18. ## Age.Group30 to 34 -259.8 50.0 -5.19 5.3e-07
  19. ## EthnicityAsians & Pacific Islanders -212.8 55.9 -3.80 0.00019
  20. ## EthnicityHispanics -124.1 55.9 -2.22 0.02777
  21. ## EthnicityNon-Hispanic Blacks 1014.3 55.9 18.13 < 2e-16
  22. ## EthnicityNon-Hispanic Whites -174.7 55.9 -3.12 0.00207
  23. ## GenderMale -83.8 35.4 -2.37 0.01881
  24. ##
  25. ## (Intercept) ***
  26. ## Age.Group20 to 24 **
  27. ## Age.Group25 to 29 *
  28. ## Age.Group30 to 34 ***
  29. ## EthnicityAsians & Pacific Islanders ***
  30. ## EthnicityHispanics *
  31. ## EthnicityNon-Hispanic Blacks ***
  32. ## EthnicityNon-Hispanic Whites **
  33. ## GenderMale *
  34. ## ---
  35. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  36. ##
  37. ## Residual standard error: 250 on 191 degrees of freedom
  38. ## Multiple R-squared: 0.798, Adjusted R-squared: 0.79
  39. ## F-statistic: 94.5 on 8 and 191 DF, p-value: <2e-16

这一次,Gender就变得显著了,因此我们也可以停止了。(儿童和老人的感染率在性别上都很接近,因为性对于它们来说并非传播的主要原因,但是对于成年女性来说,她们的感染率要高于男性。)

大多数可能的交互项都不是非常有趣。通过增加一个种族和性别的交互,我们可以得到了一个更好的拟合,虽然不是所有的交互项都是显著的(输出非常详细,所以未显示)。如果你仍然对此分析感兴趣,尝试为黑色/非黑人创建一个单独的民族指示器,然后使用它作为与性别的交互项:

  1. ##
  2. ## Call:
  3. ## lm(formula = Rate ~ Age.Group + Ethnicity + Gender + Ethnicity:Gender,
  4. ## data = gonorrhoea, subset = Age.Group %in% c("15 to 19",
  5. ## "20 to 24", "25 to 29", "30 to 34"))
  6. ##
  7. ## Residuals:
  8. ## Min 1Q Median 3Q Max
  9. ## -806.0 -119.4 -9.4 105.3 834.8
  10. ##
  11. ## Coefficients:
  12. ## Estimate Std. Error t value
  13. ## (Intercept) 405.1 63.9 6.34
  14. ## Age.Group20 to 24 131.1 50.1 2.62
  15. ## Age.Group25 to 29 -124.6 50.1 -2.49
  16. ## Age.Group30 to 34 -259.8 50.1 -5.19
  17. ## EthnicityAsians & Pacific Islanders -296.9 79.2 -3.75
  18. ## EthnicityHispanics -197.2 79.2 -2.49
  19. ## EthnicityNon-Hispanic Blacks 999.5 79.2 12.62
  20. ## EthnicityNon-Hispanic Whites -237.0 79.2 -2.99
  21. ## GenderMale -177.6 79.2 -2.24
  22. ## EthnicityAsians & Pacific Islanders:GenderMale 168.2 112.0 1.50
  23. ## EthnicityHispanics:GenderMale 146.2 112.0 1.30
  24. ## EthnicityNon-Hispanic Blacks:GenderMale 29.7 112.0 0.27
  25. ## EthnicityNon-Hispanic Whites:GenderMale 124.6 112.0 1.11
  26. ## Pr(>|t|)
  27. ## (Intercept) 1.7e-09 ***
  28. ## Age.Group20 to 24 0.00960 **
  29. ## Age.Group25 to 29 0.01377 *
  30. ## Age.Group30 to 34 5.6e-07 ***
  31. ## EthnicityAsians & Pacific Islanders 0.00024 ***
  32. ## EthnicityHispanics 0.01370 *
  33. ## EthnicityNon-Hispanic Blacks < 2e-16 ***
  34. ## EthnicityNon-Hispanic Whites 0.00315 **
  35. ## GenderMale 0.02615 *
  36. ## EthnicityAsians & Pacific Islanders:GenderMale 0.13487
  37. ## EthnicityHispanics:GenderMale 0.19361
  38. ## EthnicityNon-Hispanic Blacks:GenderMale 0.79092
  39. ## EthnicityNon-Hispanic Whites:GenderMale 0.26754
  40. ## ---
  41. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  42. ##
  43. ## Residual standard error: 251 on 187 degrees of freedom
  44. ## Multiple R-squared: 0.802, Adjusted R-squared: 0.789
  45. ## F-statistic: 63.2 on 12 and 187 DF, p-value: <2e-16

练习15-3

首先解决安装问题。像通常一样获取软件包并安装:

  1. install.packages("betareg")

我们需要重新调整响应变量为01之间(而不是0100),因为这是β分布的范围:

  1. ovm <- within(obama_vs_mccain, Obama <- Obama / 100)

现在我们准备运行模型。它与运行一元线性回归相同,但我们称之为betareg而不是lm。我随机地关注种族群体Black和宗教Protestant

  1. library(betareg)
  2. beta_model1 <- betareg(
  3. Obama ~ Turnout + Population + Unemployment + Urbanization + Black + Protestant,
  4. ovm,
  5. subset = State != "District of Columbia"
  6. )
  7. summary(beta_model1)
  8. ## Call:
  9. ## betareg(formula = Obama ~ Turnout + Population + Unemployment + Urbanization +
  10. ## Black + Protestant, data = ovm, subset = State != "District of Columbia")
  11. ##
  12. ## Standardized weighted residuals 2:
  13. ## Min 1Q Median 3Q Max
  14. ## -2.8338 -0.4574 -0.0623 0.7706 2.0443
  15. ##
  16. ## Coefficients (mean model with logit link):
  17. ## Estimate Std. Error z value Pr(>|z|)
  18. ## (Intercept) -5.518e-01 4.991e-01 -1.106 0.26894
  19. ## Turnout 1.693e-02 5.910e-03 2.864 0.00418 **
  20. ## Population 1.656e-09 5.341e-09 0.310 0.75659
  21. ## Unemployment 5.736e-02 2.315e-02 2.478 0.01322 *
  22. ## Urbanization -1.818e-05 1.538e-04 -0.118 0.90594
  23. ## Black 8.177e-03 4.275e-03 1.913 0.05575 .
  24. ## Protestant -1.781e-02 3.169e-03 -5.620 1.91e-08 ***
  25. ##
  26. ## Phi coefficients (precision model with identity link):
  27. ## Estimate Std. Error z value Pr(>|z|)
  28. ## (phi) 109.46 23.23 4.711 2.46e-06 ***
  29. ## ---
  30. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  31. ##
  32. ## Type of estimator: ML (maximum likelihood)
  33. ## Log-likelihood: 72.11 on 8 Df
  34. ## Pseudo R-squared: 0.7234
  35. ## Number of iterations: 36 (BFGS) + 3 (Fisher scoring)

Urbanization是不显著的,所以我们将其删除,使用与lm相同的技术:

  1. beta_model2 <- update(beta_model1, ~ . - Urbanization)
  2. summary(beta_model2)
  3. ## Call:
  4. ## betareg(formula = Obama ~ Turnout + Population + Unemployment + Black + Protestant, data = ovm,
  5. ## subset = State != "District of Columbia")
  6. ##
  7. ## Standardized weighted residuals 2:
  8. ## Min 1Q Median 3Q Max
  9. ## -2.8307 -0.4568 -0.0528 0.7736 2.0072
  10. ##
  11. ## Coefficients (mean model with logit link):
  12. ## Estimate Std. Error z value Pr(>|z|)
  13. ## (Intercept) -5.688e-01 4.766e-01 -1.193 0.23268
  14. ## Turnout 1.702e-02 5.861e-03 2.904 0.00369 **
  15. ## Population 1.725e-09 5.309e-09 0.325 0.74521
  16. ## Unemployment 5.697e-02 2.293e-02 2.484 0.01299 *
  17. ## Black 7.931e-03 3.727e-03 2.128 0.03335 *
  18. ## Protestant -1.758e-02 2.479e-03 -7.091 1.33e-12 ***
  19. ##
  20. ## Phi coefficients (precision model with identity link):
  21. ## Estimate Std. Error z value Pr(>|z|)
  22. ## (phi) 109.43 23.23 4.711 2.46e-06 ***
  23. ## ---
  24. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  25. ##
  26. ## Type of estimator: ML (maximum likelihood)
  27. ## Log-likelihood: 72.1 on 7 Df
  28. ## Pseudo R-squared: 0.7232
  29. ## Number of iterations: 31 (BFGS) + 3 (Fisher scoring)

同样,也要把Population去掉:

  1. beta_model3 <- update(beta_model2, ~ . - Population)
  2. summary(beta_model3)
  3. ## Call:
  4. ## betareg(formula = Obama ~ Turnout + Unemployment + Black + Protestant, data = ovm, subset = State !=
  5. ## "District of Columbia")
  6. ##
  7. ## Standardized weighted residuals 2:
  8. ## Min 1Q Median 3Q Max
  9. ## -2.8277 -0.4580 0.0432 0.7420 1.9352
  10. ##
  11. ## Coefficients (mean model with logit link):
  12. ## Estimate Std. Error z value Pr(>|z|)
  13. ## (Intercept) -0.555770 0.475674 -1.168 0.24265
  14. ## Turnout 0.016859 0.005850 2.882 0.00395 **
  15. ## Unemployment 0.059644 0.021445 2.781 0.00542 **
  16. ## Black 0.008202 0.003637 2.255 0.02412 *
  17. ## Protestant -0.017789 0.002399 -7.415 1.21e-13 ***
  18. ##
  19. ## Phi coefficients (precision model with identity link):
  20. ## Estimate Std. Error z value Pr(>|z|)
  21. ## (phi) 109.16 23.17 4.711 2.46e-06 ***
  22. ## ---
  23. ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  24. ##
  25. ## Type of estimator: ML (maximum likelihood)
  26. ## Log-likelihood: 72.05 on 6 Df
  27. ## Pseudo R-squared: 0.7226
  28. ## Number of iterations: 27 (BFGS) + 2 (Fisher scoring)

绘图可以使用与lm完全相同的代码来完成:

  1. plot_numbers <- 1:6
  2. layout(matrix(plot_numbers, ncol = 2, byrow = TRUE))
  3. plot(beta_model3, plot_numbers)

练习16-1

对于检查和处理用户输入,并没有须特别遵循的规则。在一般情况下,如果输入的形式是错误的,最好是设法将其转换成正确的,并在此过程中警告用户。因此,对于非数字输入,我们可以尝试将它们转换为数值,并抛出一个警告。

对于非正值,我们可以尝试替换一个范围内的值,或假装该值是缺失的,但这样会使答案出错。在这种情况下,最好是抛出一个错误:

  1. harmonic_mean <- function(x, na.rm = FALSE)
  2. {
  3. if(!is.numeric(x))
  4. {
  5. warning("Coercing 'x' to be numeric.")
  6. x <- as.numeric(x)
  7. }
  8. if(any(!is.na(x) & x <= 0))
  9. {
  10. stop("'x' contains non-positive values")
  11. }
  12. 1 / mean(1 / x, na.rm = na.rm)
  13. }

练习16-2

这里是RUnit的版本。为了测试缺失值,我们需要考虑的情况有: na.rmTRUEna.rmFALSE。为了测试须抛出警告的情况,我们略施小计,将warn选项改为2,意即将警告看作错误。为了测试非正值,使用边界数值零。

  1. test.harmonic_mean.1_2_4.returns_12_over_7 <- function()
  2. {
  3. expected <- 12 / 7
  4. actual <- harmonic_mean(c(1, 2, 4))
  5. checkEqualsNumeric(expected, actual)
  6. }
  7. test.harmonic_mean.no_inputs.throws_error <- function()
  8. {
  9. checkException(harmonic_mean())
  10. }
  11. test.harmonic_mean.some_missing.returns_na <- function()
  12. {
  13. expected <- NA_real_
  14. actual <- harmonic_mean(c(1, 2, 4, NA))
  15. checkEqualsNumeric(expected, actual)
  16. }
  17. test.harmonic_mean.some_missing_with_nas_removed.returns_12_over_7 <- function()
  18. {
  19. expected <- 12 / 7
  20. actual <- harmonic_mean(c(1, 2, 4, NA), na.rm = TRUE)
  21. checkEqualsNumeric(expected, actual)
  22. }
  23. test.harmonic_mean.non_numeric_input.throws_warning <- function()
  24. {
  25. old_ops <- options(warn = 2)
  26. on.exit(options(old_ops))
  27. checkException(harmonic_mean("1"))
  28. }
  29. test.harmonic_mean.zero_inputs.throws_error <- function()
  30. {
  31. checkException(harmonic_mean(0))
  32. }

翻译成testthat的版本很简单。使用expect_warning函数,可使警告更容易测试:

  1. expect_equal(12 /7, harmonic_mean(c(1, 2, 4)))
  2. expect_error(harmonic_mean())
  3. expect_equal(NA_real_, harmonic_mean(c(1, 2, 4, NA)))
  4. expect_equal(12 /7, harmonic_mean(c(1, 2, 4, NA), na.rm = TRUE))
  5. expect_warning(harmonic_mean("1"))
  6. expect_error(harmonic_mean(0))

练习16-3

下面是更新的harmonic_mean函数:

  1. harmonic_mean <- function(x, na.rm = FALSE)
  2. {
  3. if(!is.numeric(x))
  4. {
  5. warning("Coercing 'x' to be numeric.")
  6. x <- as.numeric(x)
  7. }
  8. if(any(!is.na(x) & x <= 0))
  9. {
  10. stop("'x' contains non-positive values")
  11. }
  12. result <- 1 / mean(1 / x, na.rm = na.rm)
  13. class(result) <- "harmonic"
  14. result
  15. }

为了生成一个S3的方法,名称的格式必须符合function.class的形式,在本例中即为print.harmonic。其他内容可以与其他print函数生成,但在这里,我们使用较低级别的cat函数:

  1. print.harmonic <- function(x, ...)
  2. {
  3. cat("The harmonic mean is", x, "\n", ...)
  4. }

练习17-1

创建函数和数据框都很简单:

  1. sum_of_squares <- function(n)
  2. {
  3. n * (n + 1) * (2 * n + 1) / 6
  4. }
  5. x <- 1:10
  6. squares_data <- data.frame(x = x, y = sum_of_squares(x))

在你调用package.skeleton时,需要考虑在哪个目录下创建软件包:

  1. package.skeleton("squares", c("sum_of_squares", "squares_data"))

练习17-2

此函数的文档应该置于R/sum_of_squares.R,或类似的:

  1. #' Sum of Squares
  2. #'
  3. #' Calculates the sum of squares of the first \code{n} natural numbers.
  4. #'
  5. #' @param n A positive integer.
  6. #' @return An integer equal to the sum of the squares of the first \code{n}
  7. #' natural numbers.
  8. #' @author Richie Cotton.
  9. #' @seealso \code{\link[base]{sum}}
  10. #' @examples
  11. #' sum_of_squares(1:20)
  12. #' @keywords misc
  13. #' @export

该软件包的文档位于R/squares-package.R中:

  1. #' squares: Sums of squares.
  2. #'
  3. #' A test package that contains data on the sum of squares of natural
  4. #' numbers, and a function to calculate more values.
  5. #'
  6. #' @author Richie Cotton
  7. #' @docType package
  8. #' @name squares
  9. #' @aliases squares squares-package
  10. #' @keywords package
  11. NULL

数据文档可以被放入同一个文件,或是在R/squares-data.R中:

  1. #' Sum of squares dataset
  2. #'
  3. #' The sum of squares of natural numbers.
  4. #' \itemize{
  5. #' \item{x}{Natural numbers.}
  6. #' \item{y}{The sum of squares from 1 to \code{x}.}
  7. #' }
  8. #'
  9. #' @docType data
  10. #' @keywords datasets
  11. #' @name squares_data
  12. #' @usage data(squares_data)
  13. #' @format A data frame with 10 rows and 2 variables.
  14. NULL

练习17-3

代码很容易,困难的部分在于修复任何问题:

  1. check("squares")
  2. build("squares")