library(learnr)
# library(gradethis)
library(testwhat)
library(nycflights13)
library(tidyverse)
library(qfwr)
# options(tibble.print_max = 6, tibble.print_min = 4)
knitr::opts_chunk$set(echo = FALSE)
tutorial_options(
  # exercise.cap = NULL,
  # exercise.eval = FALSE,
  exercise.timelimit = 60, #<<
  # exercise.lines = NULL,
  exercise.blanks = FALSE, #<<
  exercise.checker = testwhat::testwhat_learnr, #<<
  # exercise.error.check.code = NULL,
  # exercise.completion = TRUE,
  # exercise.diagnostics = TRUE,
  # exercise.startover = TRUE,
  exercise.reveal_solution = FALSE #<<
)

r emo::ji("book")基础概念题

以下选择题主要用来测试同学们对本讲所讲基础概念和核心函数的理解和掌握情况—— 这是熟练运用各种函数完成数据处理任务的基本功 r emo::ji("muscle")

quiz(
  question("1 构成 `dplyr` 包数据处理语言基本操作的函数包括:",
           answer("`filter()`", correct = TRUE),
           answer("`arrange()`", correct = TRUE),
           answer("`mutate()`", correct = TRUE),
           answer("`subset()`"),
           answer("`select()`", correct = TRUE),
           answer("`summarise()`", correct = TRUE),
           answer("`group_by()`", correct = TRUE),
           correct = random_praise_cn(), # "正确!",
           incorrect = "错误!参见 _R数据科学_ 的3.1.3小节。",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  question("2 以下哪些命令是对数据表的行(样本)进行操作的?",
           answer("`filter()`", correct = TRUE),
           answer("`arrange()`", correct = TRUE),
           answer("`mutate()`"),
           answer("`transmute()`"),
           answer("`select()`"),
           answer("`slice()`", correct = TRUE),
           correct = random_praise_cn(), # "Great!",
           incorrect = random_encouragement_cn(), # "错误,请重试!",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  question("3 以下哪些命令与数据表的列(变量)操作有关?",
           answer("`filter()`"),
           answer("`arrange()`"),
           answer("`mutate()`", correct = TRUE),
           answer("`transmute()`", correct = TRUE),
           answer("`select()`", correct = TRUE),
           answer("`rename()`", correct = TRUE),
           answer("`relocate()`", correct = TRUE),
           correct = random_praise_cn(), # "Excellent!",
           incorrect = random_encouragement_cn(), # "错误,请重试!",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  question("4 以下关于 `dplyr` 包中函数说法正确的有:",
           answer("`summarise()` 中比较少使用<i>N</i>元素进<i>N</i>元素出的
                  向量化的函数,如 `log()`。", correct = TRUE),
           answer("`mutate()` 总是将新变量添加在数据集的最前面。"),
           answer('`transmute()` 仅保留新变量和分组变量,其效果和设定参数 
                  `.keep = "none"` 的 `mutate()` 函数相同。', correct = TRUE),
           answer("`select()` 可在选择变量的同时对变量进行重命名。", 
                  correct = TRUE),
           answer("`group_by()` 生成分组数据集,可能会对后续的数据操作产生影响,
                  必要时可用 `ungroup()` 取消分组设定。", 
                  correct = TRUE),
           correct = random_praise_cn(), # "漂亮!",
           incorrect = random_encouragement_cn(), # "错误,请重试!",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  question("5 以下关于多表操作说法正确的有:",
           answer("数据处理实践中最常用的合并式连接命令为 `left_join()`。", 
                  correct = TRUE),
           answer("`semi_join()` 和 `anti_join()` 完成的是样本筛选式连接。",
                  correct = TRUE),
           answer("`*_join()` 族命令的 `on` 参数用来设定连接所用的键变量,
                  支持灵活多样的设定。", 
                  message = "到底是`on`还是`by`呢?这是个问题!&#x1F914;"),
           answer("`bind_cols()` 基于行序对多个数据集进行横向合并,
                  无需指定键变量。", correct = TRUE),
           answer("`bind_rows()` 基于变量名对多个数据集进行纵向合并。", 
                  correct = TRUE),
           correct = random_praise_cn(), # "答对了!给你点赞!",
           incorrect = random_encouragement_cn(), # "错误,请重试!",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  question("6 以下关于 `dplyr` 包优点概括正确的有:",
           answer("`dplyr` 以一组简洁清晰的动词函数来处理常见的数据操作任务。", 
                  correct = TRUE),
           answer("`dplyr` 核心函数的第一个参数为数据集,返回值也为数据集,
                  能很好支持管道操作 `%>%`。", correct = TRUE),
           answer("`dplyr` 能通过接口包(如 `dbplyr` 和 `dtplyr`)以相同的函数
                  接口操作其他常见的数据库(如 `SQLite`)和 R 数据结构(如 
                  `data.table`)。", correct = TRUE),
           answer("`dplyr` 通过 `C++` 实现很多关键的底层代码,从而实现对内存中
                  数据的高效运算。", correct = TRUE),
           correct = random_praise_cn(), # "Bingo!",
           incorrect = random_encouragement_cn(), # "错误,请重试!",
           allow_retry = TRUE,
           random_answer_order = TRUE
  ),
  caption = "不定项选择题"
)

r emo::ji("woman_technologist")编程练习

接下来需要同学们循序渐进完成6个简单的编程练习。

EX01

练习1. 了解数据集

我已经用library()命令帮同学们载入代码练习所需用到dplyr包、 ggplot2包以及nycflights13数据包。

首先,请同学们在下面代码窗口键入相关命令来大致了解相关的数据集和learnr代码 输入窗口的基本操作。

Hint:

# 写点代码并运行
help(package = "nycflights13") # 打开数据包帮助文档
?flights  # 打开flights(航班)数据集的帮助文档
flights  # 打印flights数据集
glimpse(flights)  # 概览flights数据集
View(flights)  # 在数据浏览窗口中打开flights数据集

接下来我们分几个小练习,逐一了解下基本的数据操作函数。

EX02

练习2. 筛选样本

找出flights数据集中出发延误至少1小时,但在飞行过程中追回至少30分钟的航班。

____(____, dep_delay >= 60, ____)
1. 代码窗口中的注释为代码的模板;
2. 你可以在代码窗口中键入`flights`,然后点击`Run Code`按钮,了解应使用的变量名;
3. 完成代码后,记得点击`Submit Answer`检查答案正确与否;
& ...
4. 点击`Next Hint>>`查看参考答案。
# 参考答案
filter(flights, dep_delay >= 60, arr_delay - dep_delay <= -30)
nc_msg <- "`dplyr`包中筛选样本的函数是`filter()`,你选对了吗?"
arg1ns_msg <- "你忘记设定`filter()`函数的`.data`参数了?"
arg2ns_msg <- "你忘记设定`filter()`函数的筛选条件了?"
inc_msg1 <- "`filter()`函数的`.data`参数设置有误。"
inc_msg2 <- "你确定你设定的筛选条件完全符合题意吗?仔细检查下。另参见`Hints`。"
ex() %>% check_function(., "filter", not_called_msg = nc_msg) %>% {
  check_arg(., ".data", arg_not_specified_msg = arg1ns_msg, append = FALSE) %>%
    check_equal(eval = FALSE, incorrect_msg = inc_msg1, append = FALSE)

  check_arg(., "...", arg_not_specified_msg = arg2ns_msg, append = FALSE)

  check_result(.) %>%
    check_equal(incorrect_msg = inc_msg2, append = FALSE)
}
success_msg(random_praise_cn(code = TRUE))

EX03

练习3. 选择变量

flights数据集中选出变量yearmonthdaydep_timedep_delay等5个变量,将前三个变量分别更名为nianyueri,并存为新数据集flights_sml[此题比较简单就不给你们Hints啦。]

flights 
flights_sml <- ...

flights_sml
# 参考答案
flights_sml <- select(flights, 
                      nian = year, yue = month, ri = day,
                      dep_time, dep_delay)
nc_msg  <- "尽管有其他实现方式,但最适合解决这个问题的函数就是用`dplyr`包中`select()`函数来选取(并重命名)变量,你选对了吗?"
udf_msg <- "你忘记将`select()`函数返回结果存为新数据集`flights_sml`了?"
inc_msg <- "除了题目要求你们选择的5个变量之外,你还_额外_多选了几个变量?"
cm_msg  <- "你可能忘记选取并按要求_重命名_变量year、month或day了?"
cm_msg2 <- "你可能忘记_选取_变量dep_time或dep_delay了?"

ex() %>% {
  check_function(., "select", not_called_msg = nc_msg)
  check_object(., "flights_sml", undefined_msg = udf_msg) %>% 
    check_correct(., 
      check_equal(., incorrect_msg = inc_msg, append = FALSE, 
                  eq_fun = function(x, y) setequal(names(x), names(y))), 
      {
        check_column(., "nian", col_missing_msg = cm_msg, append = FALSE)
        check_column(., "yue", col_missing_msg = cm_msg, append = FALSE)
        check_column(., "ri", col_missing_msg = cm_msg, append = FALSE)
        check_column(., "dep_time", col_missing_msg = cm_msg2, append = FALSE)
        check_column(., "dep_delay", col_missing_msg = cm_msg2, append = FALSE)
      }
    )
}
success_msg(random_praise_cn(code = TRUE))

EX04

练习4. 变量修改

flights数据集中现有的变量dep_timesched_dep_timearr_timesched_arr_time等的取值并不规范,请将它们“原地”修改为从午夜开始的分钟数,并将修改后的数据存为flights_new

flights
flights_new <- ...

flights_new
1. 不难看出,这些变量的后两位数字为分钟,之前的一至两位数字为小时,
   即1401表示14:01,想想有什么好办法可将它们拆解开。
1. help("Arithmetic"),看看有无合适的运算符?
2. 请先尝试下,然后再点击`Next Hint>>`查看参考答案。
! 你自行尝试探索解决问题了吗?
! 你确定、一定以及肯定想看参考答案吗?
! ……
! 那好吧,点击`Next Hint>>`查看参考答案 ……
# 参考答案
flights_new <- mutate(
  flights,
  dep_time = dep_time %/% 100 * 60 + dep_time %% 100,
  sched_dep_time = sched_dep_time %/% 100 * 60 + sched_dep_time %% 100,
  arr_time = arr_time %/% 100 * 60 + arr_time %% 100,
  sched_arr_time = sched_arr_time %/% 100 * 60 + sched_arr_time %% 100
)
nc_msg  <- "你用什么函数呢?`dplyr`包中生成新变量/修改变量的大杀器就是`mutate()`!"
udf_msg <- "你忘记将`mutate()`函数返回结果存为新数据集`flights_sml`了?"
inc_msg <- "仔细检查下,肯定有个变量的计算公式写错了!"

ex() %>% {
  check_function(., "mutate", not_called_msg = nc_msg)
  check_object(., "flights_new", undefined_msg = udf_msg) %>% {
    check_column(., "dep_time") %>%
      check_equal(incorrect_msg = inc_msg, append = FALSE)
    check_column(., "sched_dep_time") %>%
      check_equal(incorrect_msg = inc_msg, append = FALSE)
    check_column(., "arr_time") %>%
      check_equal(incorrect_msg = inc_msg, append = FALSE)
    check_column(., "sched_arr_time") %>%
      check_equal(incorrect_msg = inc_msg, append = FALSE)
  }
}

success_msg(paste0(random_praise_cn(code = TRUE), 
                   "几乎相同的代码重复了四次,我错了两次,你呢?"))

EX05

练习5. 分组汇总

根据flights数据集分组汇总计算每日航班出发延误时间的中位数,存为新数据集mdelay_by_day(不使用管道操作符%>%)。

by_day <- ___(flights, ___, ___, ___)
mdelay_by_day <- ___(by_day, mdelay = ___)
mdelay_by_day
1. 代码窗口中已给出代码模板。
2. 此题比较简单,就不给参考答案。
1. 若实在搞不定,我也只能告诉你计算中位数的函数是
   `median(x, na.rm = FALSE, ...)`
# 参考答案
by_day <- group_by(flights, year, month, day)
mdelay_by_day <- summarize(by_day, mdelay = median(dep_delay, na.rm = TRUE))
udf_msg1g <- "找不到数据集by_day——请使用by_day变量保存分组后的数据集。"
inc_msg1g <- "group_by()函数的.data参数设置有误。"
inc_msg2g <- "group_by()函数中分组变量设定有误。"
inc_msg3g <- "group_by()函数的结果有误,请仔细检查。"
ex() %>% {
  check_object(., "by_day", undefined_msg = udf_msg1g)
  check_function(., "group_by") %>% {
    check_arg(., ".data") %>% 
      check_equal(eval = FALSE, incorrect_msg = inc_msg1g, append = FALSE)
    check_arg(., "...") %>% 
      check_equal(eval = FALSE, incorrect_msg = inc_msg2g, append = FALSE)
    # check_result(.) %>% 
    #   check_equal(incorrect_msg = inc_msg3g, append = FALSE)
  }
}

udf_msg1s <- "找不到数据集mdelay_by_day——你忘记将生成的汇总数据保存为该数据集了?"
nc_msg1s  <- "汇总数据就用`summarise()`!"
nc_msg2s  <- "你在`summarise()`中应该调用`median()`函数来计算中位数。"
inc_msg1s <- "`summarise()`函数的`.data`参数设置有误。"
inc_msg2s <- "`median()`函数的`x`参数设置有误。"
inc_msg3s <- "`median()`函数的`na.rm`参数未设置或设置有误。"

ex() %>% {
  check_object(., "mdelay_by_day", undefined_msg = udf_msg1s)
  check_function(., "summarise", not_called_msg = nc_msg1s) %>% 
    check_arg(., ".data") %>% 
    check_equal(eval = FALSE, incorrect_msg = inc_msg1s, append = FALSE)
  check_function(., "median", not_called_msg = nc_msg2s) %>% {
    check_arg(., "x") %>% 
      check_equal(eval = FALSE, incorrect_msg = inc_msg2s, append = FALSE)
    check_arg(., "na.rm") %>% 
      check_equal(incorrect_msg = inc_msg3s, append = FALSE)
  }
}
success_msg(random_praise_cn(code = TRUE))

EX06

练习6. 综合运用

图示2013年6月13日航班平均到达延误时间的空间分布情况,如下图:

flights %>%
  filter(year == 2013, month == 6, day == 13) %>% 
  group_by(dest) %>%
  summarise(mean_delay = mean(arr_delay, na.rm = TRUE)) %>% 
  left_join(airports, by = c("dest" = "faa")) %>% 
  ggplot(aes(lon, lat)) + 
  borders("state") + 
  geom_point(aes(size = mean_delay), shape = 1) + 
  coord_quickmap()
flights %>%
  ___ %>%       # 筛选数据
  ___ %>%       # 分组
  ___ %>%       # 计算汇总统计量
  ___(airports, by = ___) %>%   # 合并式连接
  ggplot(aes(lon, lat)) +
  borders("state") +
  geom_point(aes(size = mean_delay), shape = 1) +
  coord_quickmap()
1. 代码窗口中已给出使用 %>% 的代码模板,制图部分的代码已全部给出
2. 此题步骤相对多些,但并不难(我还是不准备给你显示参考答案,:))
3. 你可采取逐步运行的方法(输入部分代码,按`Run Code`看结果)
4. 你会用到的四个函数为:group_by()、filter()、left_join()、summarise()
# 参考答案
flights %>%
  filter(year == 2013, month == 6, day == 13) %>% 
  group_by(dest) %>%
  summarise(mean_delay = mean(arr_delay, na.rm = TRUE)) %>% 
  left_join(airports, by = c("dest" = "faa")) %>% 
  ggplot(aes(lon, lat)) + 
  borders("state") + 
  geom_point(aes(size = mean_delay), shape = 1) + 
  coord_quickmap()
inc_msg = "样本筛选错误——你设定的筛选条件正确吗?记得是6月13日哦。"
ex() %>% check_function(., "filter") %>% {
  check_arg(., ".data") %>% check_equal(eval = FALSE)
  check_result(.) %>% 
    check_equal(incorrect_msg = inc_msg, append = FALSE)
}

ex() %>% check_function(., "group_by") %>% {
  check_arg(., "...") %>% 
    check_equal(eval = FALSE, append = FALSE, 
                incorrect_msg = "要按目的地`dest`进行分组。")
}

ex() %>% check_function(., "summarise") %>% 
  check_result(.) %>% 
  check_equal(incorrect_msg = "平均延误时间的计算结果有误,请仔细检查下。", 
              append = FALSE)

ex() %>% check_function(., "left_join") %>% {
  check_arg(., "y") %>% 
    check_equal(eval = FALSE, append = FALSE, 
                incorrect_msg = "要合并的是机场数据集`airports`。")
  check_arg(., "by") %>% 
    check_equal(eval = FALSE, incorrect_msg = 'Hint: `by = c("dest" = "faa")`')
}

nc_msg <- "本题作图部分就不需要折腾修改啦,直接保留代码模板中给出的代码即可。"
ex() %>% {
  check_function(., "ggplot", not_called_msg = nc_msg)
  check_function(., "aes", not_called_msg = nc_msg)
  check_function(., "borders", not_called_msg = nc_msg)
  check_function(., "geom_point", not_called_msg = nc_msg)
  check_function(., "coord_quickmap", not_called_msg = nc_msg)
}

success_msg(paste0(random_praise_cn(code = TRUE),
                   "恭喜!你已经会用管道操作符渐进式解决问题!"))


r emo::ji("raising_hand_woman")提交练习答案

qfwr:::submission_ui(cid = "f7b7af3cdfc6496ea9af436ed77bd59a")
qfwr:::submission_server()


yyzeng/qfwr documentation built on Nov. 29, 2021, 6:23 p.m.