• R语言做散点图

    plot(
    x = mtcars$wt,
    y = mtcars$mpg,
    main = "mpg vs wt",
    xlab = "wt (重量)",
    ylab = "mpg (每加仑英里数)",
    pch = 19, # 点的形状(19=实心圆)
    col = "steelblue"
    )
    abline(lm(mtcars$mpg ~ mtcars$wt), col = "red", lwd = 2) # 线性回归拟合线
    # install.packages("ggplot2")
    library(ggplot2)
    ggplot(mtcars, aes(x = wt, y = mpg)) +
    geom_point() +
    geom_smooth(method = "lm", se = FALSE, color = "red")
  • R语言数据转换

    https://wp.me/p80aHo-2px

    Table of Contents

    引言

    可视化是生成洞见的重要工具,但你很少能拿到正好符合绘图需求的数据形式。通常你需要创建一些新的变量或汇总,以便用数据回答你的问题;或者你只是想重命名变量、重新排列观测值,让数据更易于处理。接下来,你将学习如何完成这些操作(以及更多内容)。通过 dplyr 包介绍数据转换,并使用一个关于 2013 年从纽约市起飞航班的新数据集。

    本章的目标是为你概述用于转换数据框的所有关键工具。我们将从对数据框的行进行操作的函数开始,然后介绍对列进行操作的函数,接着回过头来进一步讨论管道(pipe),这是一个用于组合各种动词的重要工具。随后我们将介绍如何按分组进行操作。最后,本章将以一个案例研究收尾,展示这些函数的实际应用。

    先决条件

    在本章中,我们将重点介绍 dplyr 包。我们将使用 nycflights13 包中的数据来说明关键思想,并使用 ggplot2 帮助我们理解这些数据。

    # install.packages("nycflights13")
    library(nycflights13)
    # install.packages("dplyr")
    library(dplyr)

    请仔细留意在加载 包时打印的冲突信息,如果有的话。它会告诉你 dplyr 覆盖了 base R 中的一些函数。如果在加载 dplyr 之后你仍想使用这些函数的 base 版本,就需要使用它们的完整名称:stats::filter() 和 stats::lag()。到目前为止,我们大多忽略了函数来自哪个包,因为通常这并不重要。然而,了解函数所属的包可以帮助你查找帮助文档以及发现相关函数,因此当我们需要明确函数来源时,会使用与 R 相同的语法:packagename::functionname()。

    nycflights13

    为了探索 dplyr 的基本动词,我们将使用 nycflights13::flights。该数据集包含了 2013 年从纽约市出发的全部 336,776 趟航班。数据来源于美国运输统计局(US Bureau of Transportation Statistics),并在 ?flights 中有文档说明。

    > flights
    # A tibble: 336,776 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 336,766 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    flights 是一个 tibble(tibble,一种数据框的特殊类型)。tidyverse 使用 tibble 来避免一些常见的“坑”。tibbles 和数据框之间最重要的区别在于它们的打印方式:tibble 是为大型数据集设计的,所以它们只显示前几行,以及能够在一屏中放下的列。要查看全部内容,有几种选项。如果你在使用 RStudio,那么最方便的可能是 View(flights),它会打开一个交互式、可滚动且可筛选的视图。否则你也可以使用 print(flights, width = Inf) 来显示所有列,或者使用 glimpse():

    > glimpse(flights)
    Rows: 336,776
    Columns: 19
    $ year <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2
    $ month <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
    $ day <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
    $ dep_time <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
    $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
    $ dep_delay <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1
    $ arr_time <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
    $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
    $ arr_delay <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1
    $ carrier <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
    $ flight <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
    $ tailnum <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394
    $ origin <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
    $ dest <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
    $ air_time <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1
    $ distance <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
    $ hour <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6
    $ minute <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0
    $ time_hour <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0

    在这两种视图中,变量名后面会跟着一些缩写,用来告诉你每个变量的类型:<int> 表示 integer(整数),<dbl> 表示 double(也就是实数),<chr> 表示 character(也就是字符串),而 <dttm> 表示 date-time(日期时间)。这些信息很重要,因为你能对某一列执行的操作在很大程度上取决于它的“类型”。

    dplyr 基础知识

    你即将学习 dplyr 的主要动词(函数)。它们将使你能够解决绝大多数数据处理方面的挑战。但在讨论它们各自的差异之前,先说明它们的共同点:

    • 第一个参数始终是一个数据框(data frame)。
    • 后续参数通常会用变量名(不加引号)来说明要对哪些列进行操作。
    • 输出总是一个新的数据框。
    • 因为每个动词只擅长做一件事,所以要解决复杂问题通常需要把多个动词组合起来,而我们将用管道 |> 来完成。先简要说明:管道会把左边的内容传递给右边的函数,因此 x>f(y) 等价于 f(x,y),而 x>f(y)>g(z) 等价于 g(f(x,y),z)。最容易读出管道的方式是“then”(然后)。即使你还没有学到具体细节,你也依然能对下面的代码有一个大致的理解:

    dplyr 的动词根据它们作用的对象被分为四组:行(rows)、列(columns)、分组(groups)或表(tables)。接下来,你将学习针对行、列和分组最重要的动词。让我们开始吧!

    作用于数据集“行”的最重要动词是 filter() 和 arrange():filter() 会改变哪些行被保留(但不改变它们的顺序),而 arrange() 会改变行的顺序(但不改变哪些行被保留)。这两个函数都只会影响行,列保持不变。我们还会讨论 distinct(),它用于查找具有唯一值的行。与 arrange() 和 filter() 不同,distinct() 除了可以可选地修改列之外,还能实现其功能。

    filter()

    filter() 允许你根据各列的取值来保留行。第一个参数是数据框(data frame)。第二个及后续参数是必须为真(成立)才能保留该行的条件。比如,我们可以找到所有延误超过 120 分钟(两个小时)的航班:

    > flights |>
    + filter(dep_delay > 120)
    # A tibble: 9,723 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 848 1835 853 1001 1950
    2 2013 1 1 957 733 144 1056 853
    3 2013 1 1 1114 900 134 1447 1222
    4 2013 1 1 1540 1338 122 2020 1825
    5 2013 1 1 1815 1325 290 2120 1542
    6 2013 1 1 1842 1422 260 1958 1535
    7 2013 1 1 1856 1645 131 2212 2005
    8 2013 1 1 1934 1725 129 2126 1855
    9 2013 1 1 1938 1703 155 2109 1823
    10 2013 1 1 1942 1705 157 2124 1830
    # ℹ 9,713 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    除了 >(大于),你还可以使用 >=(大于等于)、<(小于)、<=(小于等于)、==(等于)和 !=(不等于)。你也可以用 & 或 , 来组合条件,表示“and”(同时满足两个条件);或者用 | 来表示“or”(满足任一条件):

    > flights |>
    + filter(month == 1 & day == 1)
    # A tibble: 842 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 832 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    当你把 | 和 == 结合使用时,有一个很有用的快捷方式:%in%。它会保留那些变量等于右侧任意一个取值的行:

    > flights |>
    + filter(month %in% c(1, 2))
    # A tibble: 51,955 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 51,945 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    当你运行 filter() 时,dplyr 会执行过滤操作,生成一个新的数据框,然后把它打印出来。它不会修改现有的航班数据集,因为 dplyr 的函数从不修改它们的输入。要保存结果,你需要使用赋值运算符 <-

    jan1 <- flights |>
    filter(month == 1 & day == 1)

    常见错误

    当你刚开始学习 R 时,最容易犯的错误是:在测试是否相等时用 =,而不是 ==。一旦发生这种情况,filter() 会提示你:

    > flights |>
    + filter(month = 1)
    Error in `filter()`:
    ! We detected a named input.
    This usually means that you've used `=` instead of `==`.
    ℹ Did you mean `month == 1`?
    Run `rlang::last_trace()` to see where the error occurred.

    另一个错误是你会像在英语里那样去写“or”(或)语句:

    flights |>
    filter(month == 1 | 2)

    这“能用”,就指它不会报错,但它并没有做到你想要的事情:| 会先检查条件 month == 1,然后再检查条件 2,而 2 并不是一个合理的条件来检查。

    arrange()

    arrange() 会根据列的取值来改变行的顺序。它接受一个数据框,以及一组列名(或更复杂的表达式)用于排序。若你提供不止一个列名,那么后续每增加一个列,都将用于在前一个列的取值相同的情况下打破“并列”。例如,下面的代码会按出发时间进行排序,而这个出发时间由四个列共同表示。我们会先得到最早的年份;然后在同一年内部,再得到最早的月份;依此类推。

    > flights |>
    + arrange(year, month, day, dep_time)
    # A tibble: 336,776 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute
    <int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
    1 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15
    2 2013 1 1 533 529 4 850 830 20 UA 1714 N24211 LGA IAH 227 1416 5 29
    3 2013 1 1 542 540 2 923 850 33 AA 1141 N619AA JFK MIA 160 1089 5 40
    4 2013 1 1 544 545 -1 1004 1022 -18 B6 725 N804JB JFK BQN 183 1576 5 45
    5 2013 1 1 554 600 -6 812 837 -25 DL 461 N668DN LGA ATL 116 762 6 0
    6 2013 1 1 554 558 -4 740 728 12 UA 1696 N39463 EWR ORD 150 719 5 58
    7 2013 1 1 555 600 -5 913 854 19 B6 507 N516JB EWR FLL 158 1065 6 0
    8 2013 1 1 557 600 -3 709 723 -14 EV 5708 N829AS LGA IAD 53 229 6 0
    9 2013 1 1 557 600 -3 838 846 -8 B6 79 N593JB JFK MCO 140 944 6 0
    10 2013 1 1 558 600 -2 753 745 8 AA 301 N3ALAA LGA ORD 138 733 6 0
    # ℹ 336,766 more rows
    # ℹ 1 more variable: time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    你可以在 arrange() 里对某个列使用 desc(),根据该列的取值将数据框重新排序为降序(从大到小)。例如,下面这段代码会把航班按延误程度从最多到最少进行排序:

    > flights |>
    + arrange(desc(dep_delay))
    # A tibble: 336,776 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute
    <int> <int> <int> <int> <int> <dbl> <int> <int> <dbl> <chr> <int> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
    1 2013 1 9 641 900 1301 1242 1530 1272 HA 51 N384HA JFK HNL 640 4983 9 0
    2 2013 6 15 1432 1935 1137 1607 2120 1127 MQ 3535 N504MQ JFK CMH 74 483 19 35
    3 2013 1 10 1121 1635 1126 1239 1810 1109 MQ 3695 N517MQ EWR ORD 111 719 16 35
    4 2013 9 20 1139 1845 1014 1457 2210 1007 AA 177 N338AA JFK SFO 354 2586 18 45
    5 2013 7 22 845 1600 1005 1044 1815 989 MQ 3075 N665MQ JFK CVG 96 589 16 0
    6 2013 4 10 1100 1900 960 1342 2211 931 DL 2391 N959DL JFK TPA 139 1005 19 0
    7 2013 3 17 2321 810 911 135 1020 915 DL 2119 N927DA LGA MSP 167 1020 8 10
    8 2013 6 27 959 1900 899 1236 2226 850 DL 2007 N3762Y JFK PDX 313 2454 19 0
    9 2013 7 22 2257 759 898 121 1026 895 DL 2047 N6716C LGA ATL 109 762 7 59
    10 2013 12 5 756 1700 896 1058 2020 878 AA 172 N5DMAA EWR MIA 149 1085 17 0
    # ℹ 336,766 more rows
    # ℹ 1 more variable: time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    请注意,行数并没有变化——我们只是对数据进行重新排序,并没有进行筛选。

    distinct()

    distinct() 用于查找数据集中所有不重复的行,因此从技术上讲,它主要是基于“行”来操作。大多数时候,你会希望得到某些变量的不同组合,因此你也可以(可选地)提供列名:

    > # 移除重复行
    > flights |>
    + distinct()
    # A tibble: 336,776 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 336,766 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows
    > # 移除重复行,保留 origin 和 dest 列
    > flights |>
    + distinct(origin, dest)
    # A tibble: 224 × 2
    origin dest
    <chr> <chr>
    1 EWR IAH
    2 LGA IAH
    3 JFK MIA
    4 JFK BQN
    5 LGA ATL
    6 EWR ORD
    7 EWR FLL
    8 LGA IAD
    9 JFK MCO
    10 LGA ORD
    # ℹ 214 more rows
    # ℹ Use `print(n = ...)` to see more rows

    如果你在筛选不重复的行时还想保留其他列,也可以使用 .keep_all = TRUE 选项。

    > # 移除重复行,保留 origin 和 dest 列,并保留其他列
    > flights |>
    + distinct(origin, dest, .keep_all = TRUE)
    # A tibble: 224 × 19
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 214 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    这些航班之所以都出现在 1 月 1 日并非巧合:distinct() 会在数据集中找到某个“唯一行”的第一次出现,并丢弃其余重复项。

    如果你想找的是出现次数,那么最好把 distinct() 换成 count()。通过设置 sort = TRUE,你还可以按“出现次数”从高到低对结果进行排序。

    > # 计算每个 origin 和 dest 组合的航班数量,并按数量降序排序
    > flights |>
    + count(origin, dest, sort = TRUE)
    # A tibble: 224 × 3
    origin dest n
    <chr> <chr> <int>
    1 JFK LAX 11262
    2 LGA ATL 10263
    3 LGA ORD 8857
    4 JFK SFO 8204
    5 LGA CLT 6168
    6 EWR ORD 6100
    7 JFK BOS 5898
    8 LGA MIA 5781
    9 JFK MCO 5464
    10 EWR BOS 5327
    # ℹ 214 more rows
    # ℹ Use `print(n = ...)` to see more rows

    练习

    • 在每个条件的单个流水线(pipeline)中,找到所有满足该条件的航班:
      • 到达延误达到两小时或以上
      • 飞往休斯敦(IAH 或 HOU)
      • 由联合航空(United)、美国航空(American)或达美航空(Delta)运营
      • 在夏季出发(7 月、8 月和 9 月)
      • 到达晚于两小时以上,但出发并没有晚点
      • 至少延误一小时,但在飞行过程中弥补了超过 30 分钟
    library(nycflights13)
    library(dplyr)
    # 1) 到达延误达到两小时或以上
    flights |>
    filter(arr_delay >= 120)
    # 2) 飞往休斯敦(IAH 或 HOU)
    flights |>
    filter(dest %in% c("IAH", "HOU"))
    # 3) 由联合航空(United)、美国航空(American)或达美航空(Delta)运营
    flights |>
    filter(carrier %in% c("UA", "AA", "DL"))
    # 4) 在夏季出发(7 月、8 月和 9 月)
    flights |>
    filter(month %in% c(7, 8, 9))
    # 5) 到达晚于两小时以上,但出发并没有晚点
    flights |>
    filter(arr_delay > 120, dep_delay <= 0)
    # 6) 至少延误一小时,但在飞行过程中弥补了超过 30 分钟
    # (即:dep_delay >= 60 且 arrived_delay 比 dep_delay 少超过 30)
    flights |>
    filter(
    dep_delay >= 60,
    dep_delay - arr_delay > 30
    )
    • 对航班进行排序,以找出出发延误最长的航班。再找出在早晨最早出发的航班。
    flights |>
    arrange(desc(dep_delay), dep_time) |>
    slice(1) # 选择第一行,即 dep_delay 最大的航班
    • 对航班进行排序以找出最快的航班。(提示:试着在函数里加入一个数学计算。)
    flights |>
    mutate(speed = distance / (air_time / 60)) |> # 计算速度,单位为英里/小时
    arrange(desc(speed)) |> # 按照速度降序排序
    slice(1) # 选择第一行,即速度最快的航班
    • 哪些航班飞行距离最远?哪些航班飞行距离最短?
    # 飞行距离最远的一班(含并列)
    flights |>
    filter(distance == max(distance, na.rm = TRUE))
    # 飞行距离最短的一班(含并列)
    flights |>
    filter(distance == min(distance, na.rm = TRUE))
    • 如果你同时使用 filter() 和 arrange(),它们使用的先后顺序会有影响吗?为什么?想一想结果会怎样变化,以及这两个函数需要做多少工作。

    一般建议把 filter() 尽量放前面(越早筛掉越好),让 arrange()/group_by() 等“昂贵”的操作尽可能在更少的行上进行。

    有四个重要的动词会影响列而不改变行:mutate() 创建从现有列派生出来的新列,select() 改变哪些列会出现,rename() 改变列名,relocate() 改变列的顺序位置。

    mutate()

    mutate() 的作用是添加新的列,这些列是根据现有的列计算得来的。你将学习一大组函数,用来处理不同类型的变量并对它们进行操作。就目前而言,我们先专注于基础代数:它让我们可以计算收益,也就是延误航班在空中补回了多少时间,以及速度(英里每小时):

    flights |>
    mutate(
    gain = dep_delay - arr_delay,
    speed = distance / air_time * 60
    )

    默认情况下,mutate() 会把新列添加到数据集的右侧,这使得你很难看清这里发生了什么。我们可以使用 .before 参数,而是把这些变量添加到左侧:

    > flights |>
    + mutate(
    + gain = dep_delay - arr_delay,
    + speed = distance / air_time * 60,
    + .before = 1
    + )
    # A tibble: 336,776 × 21
    gain speed year month day dep_time sched_dep_time dep_delay arr_time
    <dbl> <dbl> <int> <int> <int> <int> <int> <dbl> <int>
    1 -9 370. 2013 1 1 517 515 2 830
    2 -16 374. 2013 1 1 533 529 4 850
    3 -31 408. 2013 1 1 542 540 2 923
    4 17 517. 2013 1 1 544 545 -1 1004
    5 19 394. 2013 1 1 554 600 -6 812
    6 -16 288. 2013 1 1 554 558 -4 740
    7 -24 404. 2013 1 1 555 600 -5 913
    8 11 259. 2013 1 1 557 600 -3 709
    9 5 405. 2013 1 1 557 600 -3 838
    10 -10 319. 2013 1 1 558 600 -2 753
    # ℹ 336,766 more rows
    # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
    # flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
    # distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    . 表示 .before 是传给该函数的一个参数,而不是我们正在创建的第三个新变量的名字。你也可以使用 .after 来把内容加在某个变量之后;在 .before 和 .after 中,你都可以用变量名来代替位置参数。比如,我们可以把新变量加在 day 之后:

    flights |>
    mutate(
    gain = dep_delay - arr_delay,
    speed = distance / air_time * 60,
    .after = day
    )

    另外,你也可以使用 .keep 参数来控制保留哪些变量。尤其有用的参数是 "used":它指定我们只保留在 mutate() 步骤中所涉及或创建的列。例如,下面的输出将只包含变量 dep_delayarr_delayair_timegainhours 和 gain_per_hour

    flights |>
    mutate(
    gain = dep_delay - arr_delay,
    hours = air_time / 60,
    gain_per_hour = gain / hours,
    .keep = "used"
    )

    请注意,由于我们并没有把上面这次计算的结果重新赋值回 flights,因此新的变量 gain、hours 和 gain_per_hour 只会被打印出来,但不会被存储到数据框中。并且,如果我们希望让它们能够在未来使用时继续出现在数据框里,我们就需要认真考虑:是否要把结果赋值回 flights,用更多变量覆盖原始数据框;还是将结果保存到一个新的对象中。很多时候,正确做法是创建一个新的对象,并用具有信息量的名称来表明其内容,例如 delay_gain。当然,你也可能有充分理由选择覆盖掉 flights。

    select()

    拿到包含数百甚至上千个变量的数据集并不罕见。在这种情况下,最先面临的挑战往往只是:关注你真正感兴趣的变量。select() 允许你基于变量名所进行的操作,快速缩小到一个有用的子集:

    按名称选择列:

    flights |>
    select(year, month, day)

    选择从 year 到 day(包含 day)之间的所有列:

    flights |>
    select(year:day)

    选择所有列,但不包括从 year 到 day(含)之间的列:

    flights |>
    select(!year:day)

    从历史上看,这个操作使用的是 - 而不是 !,所以你很可能在实际中会看到这种写法。两个运算符的作用相同,但在行为上有一些微妙差异。我们建议使用 !,因为它读作 “not”,并且与 & 和 | 组合得很自然。

    选择所有字符列:

    flights |>
    select(where(is.character))

    在 select() 内部,你可以使用一些辅助函数:

    • starts_with("abc"):匹配以 “abc” 开头的名称。
    • ends_with("xyz"):匹配以 “xyz” 结尾的名称。
    • contains("ijk"):匹配包含 “ijk” 的名称。
    • num_range("x", 1:3):匹配 x1、x2 和 x3。

    更多细节请查看 ?select。一旦你了解正则表达式,你也就能使用 matches() 来选择与某种模式相匹配的变量。

    你可以在选择 select() 变量时使用 = 来重命名:新的名称出现在 = 的左侧,旧的变量出现在右侧:

    flights |>
    select(tail_num = tailnum)

    rename()

    如果你想保留所有现有变量,只是想重命名其中一些,那么就可以使用 rename() 而不是 select()

    flights |>
    rename(tail_num = tailnum)

    如果你有一堆命名不一致的列,而且手动逐一修正会很痛苦,可以看看 janitor::clean_names(),它提供了一些有用的自动清理功能。

    relocate()

    使用 relocate() 来移动变量。你可能想把相关的变量收集到一起,或者把重要的变量移到前面。默认情况下,relocate() 会把变量移动到最前面:

    flights |>
    relocate(time_hour, air_time)

    You can also specify where to put them using the .before and .after arguments, just like in mutate():

    flights |>
    relocate(year:dep_time, .after = time_hour)
    flights |>
    relocate(starts_with("arr"), .before = dep_time)

    练习

    • 从 flights 中选择 dep_time、dep_delay、arr_time 和 arr_delay。
    flights |>
    select(dep_time, dep_delay, arr_time, arr_delay)
    • 如果在一次 select() 调用中多次指定同一个变量名,会发生什么?

    在 R 的 dplyr::select() 里同一个变量名在同一次 select() 调用中出现多次,通常会导致以下效果之一(取决于版本与具体写法):

    最常见情况:只保留一次
    select() 会对结果进行去重(不会让同一列在输出里重复出现多份)。
    也可能:报错或忽略重复
    如果你的写法属于某些“选择语法”(比如和负选择、重命名、范围选择混在一起),有时会触发提示/报错,或以“后面覆盖/忽略”的方式处理。

    • 运行下面这段代码的结果是否让你感到意外?默认情况下,select 的这些辅助函数是如何处理大写和小写的?你又该如何改变这种默认行为?
    flights |> select(contains("TIME"))

    在 dplyr 的 select() 辅助函数(如 contains())里,默认是不区分大小写(也就是 TIME 会匹配到 time)。

    • 将 air_time 重命名为 air_time_min 以表明其计量单位,并把它移动到数据框的开头。
    flights |>
    rename(air_time_min = air_time) |> # 将 air_time 列重命名为 air_time_min
    relocate(air_time_min, .before = 1)
    • 为什么下面这段代码不起作用?这个报错信息是什么意思?
    > flights |>
    + select(tailnum) |>
    + arrange(arr_delay)
    Error in `arrange()`:
    In argument: `..1 = arr_delay`.
    Caused by error:
    ! object 'arr_delay' not found
    Run `rlang::last_trace()` to see where the error occurred.
    flights |>
    arrange(arr_delay) |> # 先排序再选择
    select(tailnum) # 选择后只有一列

    管道

    我们已经向你展示了上面管道的一些简单示例,但它真正的强大之处在于当你开始把多个动词组合在一起时就体现出来了。比如,假设你想找出飞往休斯顿 IAH 机场的最快航班:你需要把 filter()mutate()select() 和 arrange() 组合在一起:

    flights |>
    filter(dest == "IAH") |>
    mutate(speed = distance / air_time * 60) |>
    select(year:day, dep_time, carrier, flight, speed) |>
    arrange(desc(speed))

    尽管这个管道有四个步骤,但因为每行开头都是动词,所以很容易快速浏览:先从 flights 数据开始,然后 filter,接着 mutate,然后 select,最后 arrange。

    如果我们没有管道(pipe)会怎样呢?我们就需要把每次函数调用嵌套到前一次调用之中:

    arrange(
    select(
    mutate(
    filter(
    flights,
    dest == "IAH"
    ),
    speed = distance / air_time * 60
    ),
    year:day, dep_time, carrier, flight, speed
    ),
    desc(speed)
    )

    或者,我们也可以使用一大堆中间对象:

    flights1 <- filter(flights, dest == "IAH")
    flights2 <- mutate(flights1, speed = distance / air_time * 60)
    flights3 <- select(flights2, year:day, dep_time, carrier, flight, speed)
    arrange(flights3, desc(speed))

    尽管这两种写法各有其适用场景,但管道(pipe)通常会产出更容易编写、也更容易阅读的数据分析代码。

    要在你的代码中加入管道,我们建议使用内置的键盘快捷键 Ctrl/Cmd + Shift + M。你需要在 RStudio 的选项里做一个小调整,以便在代码中使用 |> 代替 %>%,如图 3.1 所示;关于 %>%,我们稍后再讲。

    到目前为止,你已经学习了那些在“行”和“列”上工作的函数。当你加入对“分组(groups)”的处理能力时,dplyr 会变得更加强大。在这一节中,我们将重点介绍最重要的函数:group_by()summarize() 以及 slice 系列的函数。

    group_by()

    使用 group_by() 将你的数据集划分为对分析而言有意义的组:

    > flights |>
    + group_by(month)
    # A tibble: 336,776 × 19
    # Groups: month [12]
    year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    <int> <int> <int> <int> <int> <dbl> <int> <int>
    1 2013 1 1 517 515 2 830 819
    2 2013 1 1 533 529 4 850 830
    3 2013 1 1 542 540 2 923 850
    4 2013 1 1 544 545 -1 1004 1022
    5 2013 1 1 554 600 -6 812 837
    6 2013 1 1 554 558 -4 740 728
    7 2013 1 1 555 600 -5 913 854
    8 2013 1 1 557 600 -3 709 723
    9 2013 1 1 557 600 -3 838 846
    10 2013 1 1 558 600 -2 753 745
    # ℹ 336,766 more rows
    # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
    # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    group_by() 不会改变数据,但如果你仔细看输出,会发现输出表明它是“按月分组”的(Groups: month [12])。这意味着后续的操作将改为“按月”进行。group_by() 会为数据框添加这种分组特性(称为 class),从而改变对数据应用的后续动词的行为。

    summarize()

    最重要的分组操作是汇总。如果只用来计算一个汇总统计量,它会把数据框减少为:每个组只有一行。在 dplyr 中,这个操作由 summarize()执行,正如下面的示例所示,它按月计算平均离港延误时间:

    > flights |>
    + group_by(month) |>
    + summarize(
    + avg_delay = mean(dep_delay)
    + )
    # A tibble: 12 × 2
    month avg_delay
    <int> <dbl>
    1 1 NA
    2 2 NA
    3 3 NA
    4 4 NA
    5 5 NA
    6 6 NA
    7 7 NA
    8 8 NA
    9 9 NA
    10 10 NA
    11 11 NA
    12 12 NA

    哎呀!出错了,我们所有的结果都是 NA(读作 “N-A”),这是 R 中表示缺失值的符号。这是因为有些观测到的航班在延误(delay)列中缺少数据,所以当我们在计算均值时把这些值也包含进去,就得到了 NA 的结果。我们会通过将参数 na.rm 设置为 TRUE,来告诉 mean() 函数忽略所有缺失值:

    > flights |>
    + group_by(month) |>
    + summarize(
    + avg_delay = mean(dep_delay, na.rm = TRUE)
    + )
    # A tibble: 12 × 2
    month avg_delay
    <int> <dbl>
    1 1 10.0
    2 2 10.8
    3 3 13.2
    4 4 13.9
    5 5 13.0
    6 6 20.8
    7 7 21.7
    8 8 12.6
    9 9 6.72
    10 10 6.24
    11 11 5.44
    12 12 16.6

    你可以在一次对 summarize() 的调用中创建任意数量的汇总结果。接下来的章节会学到各种有用的汇总方式,但其中一个非常有用的汇总是 n():它会返回每个组中的行数:

    > flights |>
    + group_by(month) |>
    + summarize(
    + avg_delay = mean(dep_delay, na.rm = TRUE),
    + n = n()
    + )
    # A tibble: 12 × 3
    month avg_delay n
    <int> <dbl> <int>
    1 1 10.0 27004
    2 2 10.8 24951
    3 3 13.2 28834
    4 4 13.9 28330
    5 5 13.0 28796
    6 6 20.8 28243
    7 7 21.7 29425
    8 8 12.6 29327
    9 9 6.72 27574
    10 10 6.24 28889
    11 11 5.44 27268
    12 12 16.6 28135

    在数据科学中,均值和计数往往能让你走得出奇地远!

    切片(slice)函数.

    有五个方便的函数,允许你在每个组内提取特定的行:

    • df |> slice_head(n = 1) 从每个组中取出第一行。
    • df |> slice_tail(n = 1) 从每个组中取出最后一行。
    • df |> slice_min(x, n = 1) 取出在列 x 上数值最小的那一行。
    • df |> slice_max(x, n = 1) 取出在列 x 上数值最大的那一行。
    • df |> slice_sample(n = 1) 随机取出一行。

    你可以改变 n 来选择多于一行;或者不使用 n = ...,而是用 prop = 0.1 来选择(例如)每个组中 10% 的行。比如,下面这段代码会找出:到达每个目的地时延误最严重的航班:

    flights |>
    group_by(dest) |> # 按目的地 dest 对数据分组
    slice_max(arr_delay, n = 1) |> # 在每个目的地组内,取 arr_delay 最大的那一行(延误时间最长)
    relocate(dest) # 将列 dest 移动到数据框的前面(放置位置更靠前,方便查看)
    # A tibble: 108 × 19
    # Groups: dest [105]
    dest year month day dep_time sched_dep_time dep_delay arr_time
    <chr> <int> <int> <int> <int> <int> <dbl> <int>
    1 ABQ 2013 7 22 2145 2007 98 132
    2 ACK 2013 7 23 1139 800 219 1250
    3 ALB 2013 1 25 123 2000 323 229
    4 ANC 2013 8 17 1740 1625 75 2042
    5 ATL 2013 7 22 2257 759 898 121
    6 AUS 2013 7 10 2056 1505 351 2347
    7 AVL 2013 8 13 1156 832 204 1417
    8 BDL 2013 2 21 1728 1316 252 1839
    9 BGR 2013 12 1 1504 1056 248 1628
    10 BHM 2013 4 10 25 1900 325 136
    # ℹ 98 more rows
    # ℹ 11 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
    # flight <int>, tailnum <chr>, origin <chr>, air_time <dbl>, distance <dbl>,
    # hour <dbl>, minute <dbl>, time_hour <dttm>
    # ℹ Use `print(n = ...)` to see more rows

    注意,这里有 105 个目的地,但我们得到的是 108 行。怎么回事?slice_min() 和 slice_max() 会保留并列的值,因此当 n = 1 时,它会把所有具有最高值的行都返回。如果你想做到每个组恰好只有一行,可以把 with_ties = FALSE 设置上。

    这和用 summarize() 计算最大延误是类似的,但你得到的是完整的对应行(如果有并列,则是对应的多行),而不是单个汇总统计量。

    按多个变量进行分组

    你可以使用多个变量来创建分组。例如,我们可以为每个日期创建一个组。

    daily <- flights |>
    group_by(year, month, day)
    daily
    #> # A tibble: 336,776 × 19
    #> # Groups: year, month, day [365]
    #> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    #> <int> <int> <int> <int> <int> <dbl> <int> <int>
    #> 1 2013 1 1 517 515 2 830 819
    #> 2 2013 1 1 533 529 4 850 830
    #> 3 2013 1 1 542 540 2 923 850
    #> 4 2013 1 1 544 545 -1 1004 1022
    #> 5 2013 1 1 554 600 -6 812 837
    #> 6 2013 1 1 554 558 -4 740 728
    #> # ℹ 336,770 more rows
    #> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …

    当你对一个按多个变量分组的 tibble 进行汇总时,每次汇总都会“剥离”掉最后一层分组。回过头看,这并不是让这个函数工作的理想方式,但在不破坏现有代码的前提下很难进行更改。为了让你清楚地知道发生了什么,dplyr 会显示一条消息,告诉你可以如何修改这种行为:

    daily_flights <- daily |>
    summarize(n = n()) # 每个分组里数一共有多少行
    #> `summarise()` has regrouped the output.
    #> ℹ Summaries were computed grouped by year, month, and day.
    #> ℹ Output is grouped by year and month.
    #> ℹ Use `summarise(.groups = "drop_last")` to silence this message.
    #> ℹ Use `summarise(.by = c(year, month, day))` for per-operation grouping
    #> (`?dplyr::dplyr_by`) instead.

    如果你对这种行为感到满意,你也可以明确地要求它,从而抑制该消息:

    daily_flights <- daily |>
    summarize(
    n = n(),
    .groups = "drop_last" # 默认情况,排除最后一层分组
    )

    或者,你可以通过设置不同的值来改变默认行为,例如使用“drop”来删除所有分组,或使用“keep”来保留相同的分组。

    取消分组

    你可能还想在不使用 summarize() 的情况下,从数据框中移除分组。你可以使用 ungroup() 来实现。

    daily |>
    ungroup()
    #> # A tibble: 336,776 × 19
    #> year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
    #> <int> <int> <int> <int> <int> <dbl> <int> <int>
    #> 1 2013 1 1 517 515 2 830 819
    #> 2 2013 1 1 533 529 4 850 830
    #> 3 2013 1 1 542 540 2 923 850
    #> 4 2013 1 1 544 545 -1 1004 1022
    #> 5 2013 1 1 554 600 -6 812 837
    #> 6 2013 1 1 554 558 -4 740 728
    #> # ℹ 336,770 more rows
    #> # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>, …

    现在我们来看看:当你对一个未分组的数据框进行汇总时,会发生什么。

    daily |>
    ungroup() |>
    summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE),
    flights = n() # 分组里有多少行
    )
    #> # A tibble: 1 × 2
    #> avg_delay flights
    #> <dbl> <int>
    #> 1 12.6 336776

    你会得到一行结果,因为 dplyr 会把未分组的数据框中的所有行都视为属于同一个分组。

    .by

    dplyr 1.1.0 引入了一种新的、实验性的、用于“按操作(per-operation)分组”的语法,也就是 .by 参数。group_by() 和 ungroup() 不会被移除,但现在你也可以使用 .by 参数在单个操作内部进行分组:

    > flights |>
    + summarize(
    + delay = mean(dep_delay, na.rm = TRUE),
    + n = n(),
    + .by = month
    + )
    # A tibble: 12 × 3
    month delay n
    <int> <dbl> <int>
    1 1 10.0 27004
    2 10 6.24 28889
    3 11 5.44 27268
    4 12 16.6 28135
    5 2 10.8 24951
    6 3 13.2 28834
    7 4 13.9 28330
    8 5 13.0 28796
    9 6 20.8 28243
    10 7 21.7 29425
    11 8 12.6 29327
    12 9 6.72 27574

    或者,如果你想按多个变量进行分组:

    > flights |>
    + summarize(
    + delay = mean(dep_delay, na.rm = TRUE),
    + n = n(),
    + .by = c(origin, dest)
    + )
    # A tibble: 224 × 4
    origin dest delay n
    <chr> <chr> <dbl> <int>
    1 EWR IAH 11.8 3973
    2 LGA IAH 9.06 2951
    3 JFK MIA 9.34 3314
    4 JFK BQN 6.67 599
    5 LGA ATL 11.4 10263
    6 EWR ORD 14.6 6100
    7 EWR FLL 13.5 3793
    8 LGA IAD 16.7 1803
    9 JFK MCO 10.6 5464
    10 LGA ORD 10.7 8857
    # ℹ 214 more rows
    # ℹ Use `print(n = ...)` to see more rows

    .by 适用于所有动词(verbs),并且它的优点是:当你需要抑制分组提示消息时,不必使用 .groups 参数;等你做完之后也不必再调用 ungroup()。

    案例研究:汇总值与样本量

    每当你进行任何汇总时,最好始终包含计数(n())。这样,你就能确保自己不是基于极少量的数据得出结论。我们将用 Lahman 包中的一些棒球数据来演示这一点。具体来说,我们会比较球员击出安打(H)的比例与他们尝试把球打进场内的次数(AB):

    # install.packages("Lahman")
    library(Lahman)
    # 从 Lahman::Batting 数据集中,按球员 ID 分组
    batters <- Lahman::Batting |>
    group_by(playerID) |>
    summarize(
    # 计算每个球员的表现:安打总数 / 试图击球总数
    performance = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
    # 统计每个球员的击球尝试次数(样本量)
    n = sum(AB, na.rm = TRUE)
    )
    # 查看结果
    batters
    #> # A tibble: 24,011 × 3
    #> playerID performance n
    #> <chr> <dbl> <int>
    #> 1 aardsda01 0 4
    #> 2 aaronha01 0.305 12364
    #> 3 aaronto01 0.229 944
    #> 4 aasedo01 0 5
    #> 5 abadan01 0.0952 21
    #> 6 abadfe01 0.111 9
    #> # ℹ 24,005 more rows

    当我们将击球手的水平(用打击率 performance 衡量)与击球机会数量(用打数 n 衡量)作图时,你会看到两种模式:

    打数较少的球员,其 performance 的波动更大。这种图的形状非常典型:只要你把均值(或其他汇总统计量)与组大小作图,就会看到随着样本量增加,波动会减小4。

    skill(performance)与击球机会(n)之间存在正相关,因为球队会把最多的击球机会留给最优秀的击球手。

    batters |>
    filter(n > 100) |> # 只保留打数(AB)超过 100 次的球员,排除样本过小、表现波动过大的噪声数据
    ggplot(aes(x = n, y = performance)) + # 创建散点图:横轴为打数(n),纵轴为打击率(performance)
    geom_point(alpha = 1 / 10) + # 绘制透明度为 0.1 的散点,避免大量重叠点导致图像过密("overplotting")
    geom_smooth(se = FALSE) # 添加平滑趋势线(默认用 loess 局部回归),不显示置信区间(se = FALSE)

    注意 ggplot2 与 dplyr 结合使用的便捷模式:只需记住,对数据集进行处理时用 |>,而向图形中添加图层时则切换为 +

    这也对排序(排名)有重要影响。如果你简单地按 desc(performance) 排序,那么打击率最高的人显然往往是那些打数极少却恰好击出安打的球员——他们未必是最优秀的击球手。

    batters |>
    arrange(desc(performance)) # 按打击率(performance)从高到低排序,最高者排在最前面
    #> # A tibble: 24,011 × 3
    #> playerID performance n
    #> <chr> <dbl> <int>
    #> 1 abramge01 1 1 # 注意:这些“100% 打击率”球员仅打过 1–2 次球(n 极小),结果高度不稳定,属统计噪声
    #> 2 alberan01 1 1
    #> 3 averysk01 1 1
    #> 4 banisje01 1 1
    #> 5 barbesa01 1 2
    #> 6 bartocl01 1 1
    #> # ℹ 24,005 more rows # 共 24,011 名球员(按 playerID 去重汇总后)

    总结

    在本章中,你学习了 dplyr 提供的用于处理数据框(data frames)的工具。按用途大致可以分为三类:操作行的工具(例如 filter() 和 arrange())、操作列的工具(例如 select() 和 mutate()),以及操作分组的工具(例如 group_by() 和 summarize())。

  • R语言工作流程:基础

    https://wp.me/p80aHo-2oS

    Table of Contents

    你现在已经有了一些运行 R 代码的经验。我们并没有给出太多细节,但你显然已经掌握了基础!当你开始使用 R 进行编程时,感到沮丧是很正常的,因为它对标点符号非常严格,哪怕一个字符放错位置都会引发报错。不过,虽然你可能会感到些许挫败,但可以放心,这种经历是典型且暂时的:每个人都会经历,而克服它的唯一办法就是不断尝试。

    在我们继续之前,先确保你已经打下了运行 R 代码的坚实基础,并且了解一些最有用的 RStudio 功能。

    代码基础

    让我们回顾一些此前为了尽快让你开始绘图而省略的基础内容。你可以使用 R 来进行基本的数学计算:

    1 / 200 * 30
    (59 + 73 + 2) / 3
    sin(pi / 2)

    你可以使用赋值运算符 <- 来创建新对象:

    x <- 3 * 4
    x

    请注意,x 的值不会被打印出来,它只是被存储了。如果你想查看该值,可以在控制台中输入 x。

    你可以使用 c() 将多个元素组合成一个向量

    primes <- c(2, 3, 5, 7, 11, 13)

    并且,对向量进行的基本算术运算会作用于该向量的每一个元素:

    primes <- c(2, 3, 5, 7, 11, 13)
    primes * 2
    primes - 1

    所有用于创建对象的 R 语句(即赋值语句)都具有相同的形式:

    对象名称 <-

    阅读这段代码时,可以在脑海中将其理解为“对象名获得值”。

    你会进行大量赋值操作,而输入 <- 会有点麻烦。你可以使用 RStudio 的键盘快捷键来节省时间:Alt + -(减号)。注意,RStudio 会自动在 <- 两侧添加空格,这是良好的代码格式习惯。代码即使在状态良好时也可能难以阅读,所以给你的眼睛一点缓解,记得使用空格。

    注释

    在一行中,# 之后的任何文本都会被 R 忽略。这使你可以编写注释,这些文本会被 R 忽略,但供其他人阅读。我们有时会在示例中加入注释来解释代码中发生了什么。

    注释可以帮助简要说明接下来的代码在做什么。

    # create vector of primes
    primes <- c(2, 3, 5, 7, 11, 13)
    # multiply primes by 2
    primes * 2

    对于像这样的简短代码片段,不一定需要为每一行代码都添加注释。但随着你编写的代码变得更加复杂,注释可以为你(以及你的协作者)节省大量理解代码所做工作的时间。

    使用注释来解释代码的“为什么”,而不是“做了什么”或“如何做”。通过仔细阅读代码,总是可以弄清楚代码在做什么以及如何实现,即使过程可能有些繁琐。如果你在注释中描述了每一个步骤,然后又修改了代码,就需要记得同时更新注释,否则将来回看代码时会造成困惑。

    理解为什么要这样做要困难得多,甚至可能无法从代码本身看出来。例如,geom_smooth() 有一个名为 span 的参数,用于控制曲线的平滑程度,值越大曲线越平滑。假设你决定将 span 的值从默认的 0.75 改为 0.9:未来的读者很容易理解发生了什么,但如果你没有在注释中说明你的思考过程,就没有人会知道你为什么要修改默认值。

    对于数据分析代码,使用注释来解释你的整体思路,并在遇到重要见解时将其记录下来。这些信息无法从代码本身重新获取。

    变量名中有什么含义?

    对象名称必须以字母开头,并且只能包含字母、数字、_ 和 .。你应该让对象名称具有描述性,因此需要为多个单词采用一种命名约定。我们推荐使用 snake_case,即用下划线 _ 分隔小写单词。

    i_use_snake_case
    otherPeopleUseCamelCase

    进行一次赋值:

    this_is_a_really_long_name <- 2.5

    要查看这个对象,可以试试 RStudio 的自动补全功能:输入“this”,按 TAB,继续输入字符直到形成唯一前缀,然后按回车。

    假设你犯了一个错误,this_is_a_really_long_name 的值应该是 3.5,而不是 2.5。你可以使用另一个键盘快捷键来帮助修正。例如,可以按 ↑ 调出你刚刚输入的命令并进行编辑。或者,输入“this”,然后按 Cmd/Ctrl + ↑ 来列出所有以这些字母开头的历史命令。使用方向键进行选择,然后按回车重新输入该命令。把 2.5 改为 3.5 并重新运行。

    再进行一次赋值:

    r_rocks <- 2^3

    让我们试着查看它:

    r_rock
    R_rocks

    这说明了你与 R 之间的一种隐含约定:R 会为你完成繁琐的计算,但作为交换,你必须在指令上做到完全精确。否则,你很可能会收到一条错误提示,说明找不到你要的对象。拼写错误很重要;R 无法读懂你的想法,也不会在你输入 r_rock 时自动理解为 r_rocks。大小写同样重要;类似地,当你输入 R_rocks 时,R 也不会自动理解为 r_rocks。

    调用函数

    R 拥有大量内置函数,调用方式如下:

    函数名(参数1 =1, 参数2 =2, …)

    让我们试试使用 seq(),它可以生成规则的数值序列,同时也顺便学习一些 RStudio 的实用功能。输入 se 并按 TAB,会弹出一个列表显示可能的补全项。通过继续输入(如输入 q)来明确选择 seq(),或者使用 ↑/↓ 方向键进行选择。注意弹出的浮动提示,它会提醒你该函数的参数和用途。如果你需要更多帮助,可以按 F1,在右下角的帮助面板中查看完整说明。

    当你选定所需函数后,再按一次 TAB。RStudio 会自动为你补全匹配的开括号 ( 和 闭括号 )。输入第一个参数的名称 from,并将其设为 1。然后输入第二个参数的名称 to,并将其设为 10。最后按回车。

    seq(from = 1, to = 10)

    在函数调用中,我们常常省略前几个参数的名称,因此可以将其改写如下:

    seq(1, 10)

    输入以下代码,并注意 RStudio 也会为成对的引号提供类似的辅助:

    x <- "hello world"

    引号和括号必须始终成对出现。RStudio 会尽力帮助你,但仍然有可能出错并导致不匹配。如果发生这种情况,R 会显示续行符“+”:

    符号 + 表示 R 正在等待更多输入;它认为你还没有完成。通常这意味着你遗漏了一个 ” 或一个 )。要么补上缺失的成对符号,要么按 ESCAPE 取消该表达式并重新尝试。

    请注意,右上角窗格中的环境(Environment)选项卡会显示你创建的所有对象:

    练习

    • 为什么这段代码无法运行?
    my_variable <- 10
    my_varıable

    仔细观察!(这看起来也许毫无意义,但训练你的大脑去注意哪怕最细微的差别,在编程时会带来回报。)

    • 对以下每条 R 命令进行修改,使其能够正确运行:
    libary(todyverse)
    ggplot(dTA = mpg) +
    geom_point(maping = aes(x = displ y = hwy)) +
    geom_smooth(method = "lm)

    正确的版本

    library(ggplot2)
    ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
    geom_point() +
    geom_smooth(method = "lm")
    • 按下 Option + Shift + K / Alt + Shift + K。会发生什么?如何通过菜单进入相同的位置?
    • 运行以下代码。两张图中哪一张被保存为 mpg-plot.png?为什么?
    my_bar_plot <- ggplot(mpg, aes(x = class)) +
    geom_bar()
    my_scatter_plot <- ggplot(mpg, aes(x = cty, y = hwy)) +
    geom_point()
    ggsave(filename = "mpg-plot.png", plot = my_bar_plot)

    保存的是 my_bar_plot(柱状图)

    原因是你在 ggsave() 里明确指定了 plot = my_bar_plot,所以保存的就是这个对象,而不是最近绘制的图(my_scatter_plot)。

    总结

    现在你已经对 R 代码的工作方式有了更多了解,也掌握了一些在将来回看代码时帮助理解它的技巧。接下来,我们将继续你的数据科学之旅,向你介绍 dplyr——tidyverse 中用于数据转换的包,无论是选择重要变量、筛选感兴趣的行,还是计算汇总统计量。

  • R语言数据可视化

    https://wp.me/p80aHo-2mf

    Table of Contents

    引言

    鳍更长的企鹅比鳍更短的企鹅更重还是更轻?你可能已经有了答案,但试着让你的回答更精确一些。鳍长与体重之间的关系是什么样的?是正相关还是负相关?是线性的还是非线性的?这种关系是否会因企鹅的物种不同而变化?又是否会因企鹅所生活的岛屿不同而变化?让我们创建一些可视化图表来回答这些问题。

    你可以使用palmerpenguins中的penguins数据框(也称为palmerpenguins::penguins)来检验你对这些问题的回答。数据框是一种矩形结构的数据集合,由变量(列)和观测(行)组成。penguins数据集包含了由Kristen Gorman博士和南极帕尔默站LTER项目收集并发布的344条观测数据。

    为了让讨论更清晰,我们先定义一些术语:

    变量是你可以测量的数量、特征或属性。

    数值是变量在被测量时所呈现的状态。变量的数值可能会随着每次测量而变化。

    观测是在相似条件下进行的一组测量(通常是在同一时间、针对同一对象完成所有测量)。一次观测会包含多个数值,每个数值对应一个不同的变量。我们有时也将观测称为数据点。

    表格数据是一组数值的集合,每个数值都与一个变量和一个观测相关联。当每个数值都位于独立的“单元格”中、每个变量位于各自的列中、每个观测位于各自的行中时,这种表格数据就是整洁的(tidy)。

    在此语境中,变量指的是所有企鹅的某一属性,而观测指的是单只企鹅的所有属性。

    在控制台中输入数据框的名称,R会打印其内容的预览。请注意,在该预览的顶部显示为tibble。在tidyverse中,我们使用一种称为tibble的特殊数据框,你很快就会进一步了解它。

    > penguins
    # A tibble: 344 × 8
    species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
    <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
    1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
    2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
    3 Adelie Torgersen 40.3 18 195 3250 female 2007
    4 Adelie Torgersen NA NA NA NA NA 2007
    5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
    6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
    7 Adelie Torgersen 38.9 17.8 181 3625 female 2007
    8 Adelie Torgersen 39.2 19.6 195 4675 male 2007
    9 Adelie Torgersen 34.1 18.1 193 3475 NA 2007
    10 Adelie Torgersen 42 20.2 190 4250 NA 2007
    # ℹ 334 more rows
    # ℹ Use `print(n = ...)` to see more rows

    该数据框包含8列。若想以另一种方式查看数据,从而看到所有变量以及每个变量的前几条观测,可以使用glimpse()。或者,如果你使用的是RStudio,可以运行View(penguins)来打开一个交互式数据查看器。

    该数据框包含8列。若想以另一种方式查看数据,从而看到所有变量以及每个变量的前几条观测,可以运行View(penguins)来打开一个交互式数据查看器。

    penguins中的变量包括:

    species:企鹅的物种(Adelie、Chinstrap或Gentoo)。

    flipper_length_mm:企鹅鳍的长度(单位:毫米)。

    body_mass_g:企鹅的体重(单位:克)。

    要了解更多关于penguins的数据,可以运行 ?penguins 打开其帮助页面。

    最终目标是重现如下可视化图表,用于展示这些企鹅的鳍长与体重之间的关系,并将企鹅的物种纳入考虑。

    创建一个ggplot
    让我们一步一步地重现这个图。

    使用ggplot2时,你从函数ggplot()开始绘图,先定义一个绘图对象,然后在其上添加各个图层。ggplot()的第一个参数是用于绘图的数据集,因此ggplot(data = penguins)会创建一个空的图形,该图已准备好展示penguins数据,但由于我们尚未指定如何进行可视化,目前它仍然是空的。这个图并不十分有趣,但你可以把它看作是一块空白画布,接下来你将在其上绘制图形的各个图层。

    # install.packages("ggplot2")
    library(ggplot2)
    ggplot(data = penguins)

    接下来,我们需要告诉ggplot()如何将数据中的信息以可视化形式呈现。ggplot()函数的mapping参数用于定义数据集中变量如何映射到图形的视觉属性(美学属性)。mapping参数始终在aes()函数中进行定义,而aes()中的x和y参数用于指定映射到x轴和y轴的变量。现在,我们只将鳍长映射到x美学属性,将体重映射到y美学属性。ggplot2会在data参数中查找这些被映射的变量,在这里即为penguins数据。

    下面的图展示了添加这些映射后的结果。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    )

    我们的空白画布现在有了更多结构——可以清楚地看到鳍长将显示在何处(x轴),体重将显示在何处(y轴)。但企鹅本身尚未出现在图中。这是因为我们还没有在代码中说明如何将数据框中的观测值呈现在图上。

    为此,我们需要定义一个几何对象(geom):也就是图中用来表示数据的几何元素。这些几何对象在ggplot2中通过以geom_开头的函数提供。人们通常会根据图中使用的geom类型来描述图表。例如,柱状图使用柱形几何对象(geom_bar()),折线图使用线形几何对象(geom_line()),箱线图使用箱线几何对象(geom_boxplot()),散点图使用点几何对象(geom_point())等等。

    geom_point()函数会为图添加一个点图层,从而创建一个散点图。ggplot2提供了许多geom函数,每个函数都会向图中添加不同类型的图层。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point()
    #> Warning: Removed 2 rows containing missing values or values outside the scale range
    #> (`geom_point()`).

    现在我们得到了一个看起来像“散点图”的图形。它还没有完全达到我们的“最终目标”图,但通过这个图,我们已经可以开始回答推动本次探索的问题:“鳍长与体重之间的关系是什么样的?”这种关系似乎是正相关的(鳍越长,体重也越大),相当线性(点大致分布在一条直线附近而不是曲线附近),并且强度适中(围绕这条线的离散程度不大)。通常来说,鳍更长的企鹅体重也更大。

    在为该图添加更多图层之前,让我们先停下来回顾一下我们收到的警告信息:

    Removed 2 rows containing missing values (geom_point()).

    我们之所以会看到这条消息,是因为数据集中有两只企鹅缺少体重和/或鳍长的数值,而在缺少这两个值的情况下,ggplot2无法在图中表示它们。与R一样,ggplot2遵循这样一种理念:缺失值不应被悄然忽略。这类警告可能是你在处理真实数据时最常见的警告之一——缺失值是一个非常普遍的问题,在后续的图中,我们将抑制此警告,以避免它在我们生成的每一个图旁边都被打印出来。

    散点图对于展示两个数值变量之间的关系非常有用,但对于任何看起来存在的变量关系,都应保持一定的怀疑,并思考是否存在其他变量能够解释或改变这种表面关系的性质。例如,鳍长与体重之间的关系是否会因物种不同而有所差异?让我们将物种信息纳入图中,看看这是否能为这些变量之间的关系带来更多见解。我们将通过使用不同颜色的点来表示不同的物种。

    要实现这一点,我们需要修改美学映射还是几何对象呢?如果你猜的是“在美学映射中,也就是在aes()里面”,那你已经开始掌握使用ggplot2创建数据可视化的要领了!如果没有,也不用担心。你会绘制更多的ggplot图,并在实践中不断检验和提升你的直觉。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g, color = species)
    ) +
    geom_point()

    当将一个分类变量映射到某个美学属性时,ggplot2会自动为该变量的每一个唯一水平(这里是三个物种)分配一个独特的美学取值(此处为不同颜色),这一过程称为缩放(scaling)。ggplot2还会添加一个图例,用于说明这些取值分别对应哪些水平。

    现在让我们再添加一个图层:一条平滑曲线,用于展示体重与鳍长之间的关系。在继续之前,请回顾上面的代码,并思考我们如何将其添加到现有的图中。

    由于这是一个用于表示数据的新几何对象,我们将添加一个新的geom作为点几何对象之上的一层:geom_smooth()。并且我们将指定使用线性模型绘制最佳拟合直线,即设置method = “lm”。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g, color = species)
    ) +
    geom_point() +
    geom_smooth(method = "lm")

    我们已经成功添加了线条,但这个图看起来并不像前面的完成图,后者只有一条表示整个数据集的线,而不是为每个企鹅物种分别绘制多条线。

    当美学映射在ggplot()中(即全局层面)定义时,它们会被传递到图中的每一个后续geom图层中。不过,ggplot2中的每个geom函数也可以接受一个mapping参数,从而允许在局部层面定义美学映射,并将其与从全局继承的映射叠加。由于我们希望点根据物种进行着色,但不希望线条按物种分开绘制,因此应只在geom_point()中指定color = species。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point(mapping = aes(color = species)) +
    geom_smooth(method = "lm")

    好了!我们现在得到的图已经非常接近我们的最终目标了,虽然还不够完美。我们仍然需要为每种企鹅使用不同的形状,并改进标签。

    通常来说,仅使用颜色来表示图中的信息并不是一个好主意,因为由于色盲或其他色觉差异,人们对颜色的感知可能不同。因此,除了颜色之外,我们还可以将物种映射到形状这一美学属性上。

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point(mapping = aes(color = species, shape = species)) +
    geom_smooth(method = "lm")

    请注意,图例也会自动更新,以反映点形状的不同。

    最后,我们可以通过在新图层中使用labs()函数来改进图的标签。labs()中的一些参数可能不言自明:title用于添加标题,subtitle用于添加副标题。其他参数与美学映射相对应,x表示x轴标签,y表示y轴标签,而color和shape则定义图例的标签。此外,我们还可以使用ggthemes包中的scale_color_colorblind()函数来优化配色,使其对色盲更友好。

    library(ggthemes)
    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point(aes(color = species, shape = species)) +
    geom_smooth(method = "lm") +
    labs(
    title = "Body mass and flipper length",
    subtitle = "Dimensions for Adelie, Chinstrap, and Gentoo Penguins",
    x = "Flipper length (mm)", y = "Body mass (g)",
    color = "Species", shape = "Species"
    ) +
    scale_color_colorblind()

    我们终于有了一个与我们的“终极目标”完美匹配的图表!

    练习

    • penguins 有多少行?有多少列?
    > dim(penguins)
    [1] 344 8
    • penguins 数据框中的 bill_depth_mm 变量描述的是什么?请查看 ?penguins 的帮助文档来找出答案。

    bill_depth_mm 表示企鹅喙的深度,单位是毫米。

    • 绘制 bill_depth_mm 与 bill_length_mm 的散点图。也就是说,绘制一个以 bill_depth_mm 为纵轴、bill_length_mm 为横轴的散点图。描述这两个变量之间的关系。
    ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
    geom_point()
    • 如果你绘制 species 与 bill_depth_mm 的散点图,会发生什么?
    ggplot(penguins, aes(x = species, y = bill_depth_mm)) +
    geom_plot()
    • 为什么下面的代码会报错?你会如何修复它?
    ggplot(data = penguins) +
    geom_point()

    这段代码会报错是因为在 ggplot() 中没有指定美学映射(aes),而 geom_point() 需要明确的 x 和 y 变量才能绘制散点图。

    • geom_point() 中的 na.rm 参数有什么作用?该参数的默认值是什么?创建一个散点图,在其中成功地将该参数设置为 TRUE。

    na.rm 参数用于控制是否在绘图时自动移除包含缺失值(NA)的观测。如果设置为 TRUE,会忽略这些缺失值而不报错或警告;如果为 FALSE,则可能会出现警告信息。该参数的默认值是 FALSE。

    ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
    geom_point(na.rm = TRUE)
    • 将以下说明添加到你在上一个练习中制作的图中:“Data come from the palmerpenguins package.” 提示:查看 labs() 的文档。
    ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
    geom_point(na.rm = TRUE) +
    labs(caption = "Data come from the palmerpenguins package.")
    • 重现以下可视化。bill_depth_mm 应该映射到哪个美学属性?它应该在全局层级映射,还是在 geom 层级映射?
    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g, color = bill_depth_mm)) +
    geom_point() +
    geom_smooth(se = TRUE)
    • 在脑海中运行这段代码并预测输出结果会是什么样子。然后在 R 中运行代码,检查你的预测。
    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g, color = island)
    ) +
    geom_point() +
    geom_smooth(se = FALSE)
    • 这两个图看起来会不同吗?为什么/为什么不?
    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point() +
    geom_smooth()
    ggplot() +
    geom_point(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_smooth(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    )

    这两段代码会生成相同的图:一个以 flipper_length_mm 为 x 轴、body_mass_g 为 y 轴的散点图,并叠加一条带置信区间的平滑曲线。

    区别在于映射和数据的指定位置:
    第一段在 ggplot() 中全局定义了 data 和 aes,因此后续所有几何对象都会继承。
    第二段在每个 geom 中分别定义 data 和 aes,但由于设置相同,最终效果一致。

    随着我们从这些入门部分继续前进,我们将过渡到更简洁地表达 ggplot2 代码的方式。到目前为止,我们一直非常明确,这在你学习时是有帮助的:

    ggplot(
    data = penguins,
    mapping = aes(x = flipper_length_mm, y = body_mass_g)
    ) +
    geom_point()

    通常,一个函数的前一两个参数非常重要,你应该把它们牢记于心。ggplot() 的前两个参数是 data 和 mapping,其余部分,我们将不再显式写出这些参数名。这样可以减少输入量,并通过减少多余文本,使你更容易看出不同图之间的差异。

    将前一个图用更简洁的方式重写如下:

    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
    geom_point()

    可视化分布

    如何对一个变量的分布进行可视化取决于该变量的类型:分类变量或数值变量。

    分类变量

    如果一个变量只能取一小部分可能值中的一个,那么它就是分类变量。要查看分类变量的分布,可以使用条形图。条形的高度表示每个 x 值对应的观测数量。

    ggplot(penguins, aes(x = species)) +
    geom_bar()

    对于具有无序水平的分类变量(例如上面的企鹅物种)的条形图,通常更倾向于根据频数对条形进行重新排序。为此,需要将该变量转换为因子(R 处理分类数据的方式),然后对该因子的水平进行重新排序。

    library(forcats)
    ggplot(penguins, aes(x = fct_infreq(species))) +
    geom_bar()

    数值变量

    如果一个变量可以取广泛范围的数值,并且对这些数值进行加减或求平均是有意义的,那么它就是数值型变量(或定量变量)。数值型变量可以是连续的,也可以是离散的。

    连续变量分布的一种常用可视化方式是直方图。

    ggplot(penguins, aes(x = body_mass_g)) +
    geom_histogram(binwidth = 200)

    直方图将 x 轴划分为等宽的区间(bins),并用柱子的高度表示每个区间中的观测数量。在上面的图中,最高的柱子表示有 39 个观测的 body_mass_g 值位于 3500 到 3700 克之间,这两个数值分别是该柱子的左右边界。

    你可以使用 binwidth 参数来设置直方图中区间的宽度,该宽度以 x 变量的单位来衡量。在使用直方图时,你应该始终尝试多种不同的 binwidth,因为不同的区间宽度可能会揭示不同的模式。在下面的图中,binwidth 为 20 时过于狭窄,导致柱子过多,使得难以判断分布的形状。同样,binwidth 为 2,000 时又过大,导致所有数据只被分成三根柱子,也难以判断分布的形状。而 binwidth 为 200 则提供了一个较为合理的平衡。

    数值变量分布的另一种可视化方式是密度图。密度图是直方图的平滑版本,是一种实用的替代方案,特别适用于来自潜在平滑分布的连续数据。我们不会详细介绍 geom_density() 是如何估计密度的(你可以在函数文档中了解更多),但可以通过一个类比来解释密度曲线是如何绘制的。想象一个由木块组成的直方图,然后再想象把一根煮熟的意大利面条从上面放下。面条覆盖在这些木块上的形状,可以看作是密度曲线的形状。它比直方图展示的细节更少,但可以更容易快速看出分布的形状,特别是在峰值和偏度方面。

    ggplot(penguins, aes(x = body_mass_g)) +
    geom_density()

    练习

    • 绘制一个企鹅物种的条形图,并将 species 映射到 y 美学属性。这个图有什么不同?
    ggplot(penguins, aes(y = body_mass_g)) +
    geom_histogram(binwidth = 200)

    将 species 映射到 y 美学属性后,条形图会变为水平条形图,而不是默认的垂直条形图。本质上显示的信息没有变化,仍然是各个企鹅物种的计数,但方向发生了改变,这通常可以让类别名称更容易阅读。

    • 以下两个图有什么不同?在改变柱子的颜色时,哪种美学属性(color 还是 fill)更有用?
    ggplot(penguins, aes(x = species)) +
    geom_bar(color = "red")
    ggplot(penguins, aes(x = species)) +
    geom_bar(fill = "red")

    color = “red” 只会改变柱子的边框颜色为红色,柱子内部仍是默认填充(通常是灰色)。
    fill = “red” 会将柱子的内部填充为红色,而边框颜色保持默认。

    • geom_histogram() 中的 bins 参数有什么作用?

    bins 参数用于指定直方图中分箱(柱子)的数量。它会将数据范围划分为指定数量的区间,每个区间对应一根柱子。调整 bins 的值会影响图形的细致程度:值越大,柱子越多、越细;值越小,柱子越少、越宽。

    • 在加载 ggplot2包后,使用 diamonds 数据集中的 carat 变量绘制直方图。尝试不同的 binwidth。哪个 binwidth 能揭示最有趣的模式?
    ggplot(diamonds,aes(x=carat)) +
    geom_histogram(binwidth = 0.1)

    可视化关系

    为了可视化变量之间的关系,我们需要将至少两个变量映射到图形的美学属性上。接下来,你将学习用于可视化两个或多个变量关系的常用图形,以及用于创建这些图形的几何对象(geoms)。

    一个数值变量和一个分类变量

    为了可视化一个数值变量与一个分类变量之间的关系,我们可以使用并排的箱线图。箱线图是一种用于表示位置度量(百分位数)的可视化简写形式,用来描述数据分布。同时,它也有助于识别潜在的异常值。如图 1.1 所示,每个箱线图由以下部分组成:

    一个盒子表示数据中间一半范围,这个距离称为四分位距(IQR),从分布的第25百分位延伸到第75百分位。盒子中间有一条线表示中位数,即分布的第50百分位。这三条线可以让你了解分布的离散程度,以及分布是围绕中位数对称还是向一侧偏斜。

    用可视化的点表示那些距离盒子边缘超过1.5倍IQR的观测值。这些离群点较为异常,因此会被单独绘制。

    一条线(或称“须”)从盒子的两端延伸,直到分布中最远的非离群点。

    让我们使用 geom_boxplot() 来查看不同物种的体重分布:

    ggplot(penguins, aes(x = species, y = body_mass_g)) +
    geom_boxplot()

    或者,我们可以使用 geom_density() 绘制密度图。

    ggplot(penguins, aes(x = body_mass_g, color = species)) +
    geom_density(linewidth = 0.75)

    我们还通过使用 linewidth 参数自定义了线条的粗细,以便它们在背景中更加突出。

    此外,我们可以将物种映射到颜色和填充美学属性上,并使用 alpha 美学属性为填充的密度曲线添加透明度。该属性的取值范围在 0(完全透明)到 1(完全不透明)之间。在下面的图中,它被设置为 0.5。

    ggplot(penguins, aes(x = body_mass_g, color = species, fill = species)) +
    geom_density(alpha = 0.5)

    请注意我们在这里使用的术语:

    如果我们希望某个美学属性所表示的视觉特征根据变量的取值而变化,就将变量映射到该美学属性上。
    否则,我们是为该美学属性设置一个固定值。

    两个分类变量

    我们可以使用堆叠条形图来可视化两个分类变量之间的关系。例如,下面的两个堆叠条形图都展示了岛屿与物种之间的关系,具体来说,是可视化每个岛屿内物种的分布情况。

    第一个图显示了每个岛屿上各种企鹅物种的频数。该频数图表明每个岛屿上的阿德利企鹅数量是相同的,但我们无法很好地了解各个岛屿内部的百分比分布情况。

    第二个图是通过在几何对象中设置 position = “fill” 创建的相对频率图,对于比较不同岛屿上的物种分布更为有用,因为它不受各岛屿企鹅数量不均的影响。通过该图我们可以看到,Gentoo 企鹅全部生活在 Biscoe 岛,并且约占该岛企鹅数量的 75%;Chinstrap 企鹅全部生活在 Dream 岛,并且约占该岛企鹅数量的 50%;而 Adelie 企鹅生活在三个岛屿上,并且在 Torgersen 岛上占全部企鹅数量。

    在创建这些条形图时,我们将用于分隔成不同条形的变量映射到 x 美学属性,将用于改变条形内部颜色的变量映射到 fill 美学属性。不幸的是,ggplot2 默认将 y 轴标记为 “count”,但我们可以通过添加一个 labs() 图层并将 y 轴标签指定为 “proportion” 来覆盖这一设置。

    ggplot(penguins, aes(x = island, fill = species)) +
    geom_bar(position = "fill") +
    labs(y = "proportion")

    两个数值变量

    到目前为止,你已经学习了如何使用散点图(通过 geom_point() 创建)和光滑曲线(通过 geom_smooth() 创建)来可视化两个数值变量之间的关系。散点图可能是用于展示两个数值变量关系的最常用图形。

    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
    geom_point()

    三个或更多变量

    正如我们所看到的,我们可以通过将变量映射到其他美学属性上,在图形中加入更多变量。例如,在下面的散点图中,点的颜色代表物种,点的形状代表岛屿。

    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
    geom_point(aes(color = species, shape = island))

    然而,在图形中添加过多的美学映射会使其显得杂乱且难以理解。另一种对分类变量特别有用的方法是将图形拆分为分面,即每个子图分别展示数据的一个子集。

    要按单个变量对图形进行分面,可以使用 facet_wrap()。facet_wrap() 的第一个参数是一个公式,你可以通过在变量名前加上 ~ 来创建。传递给 facet_wrap() 的变量应当是分类变量。

    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
    geom_point(aes(color = species, shape = species)) +
    facet_wrap(~island)

    练习

    • ggplot2 包中附带的 mpg 数据框包含了美国环境保护署针对 38 款汽车模型收集的 234 条观测数据。mpg 中哪些变量是分类变量?哪些变量是数值变量?(提示:输入 ?mpg 查看该数据集的文档。)当你运行 mpg 时,如何查看这些信息?
    str(mpg)
    • 使用 mpg 数据框绘制 hwy 与 displ 的散点图。接下来,将第三个数值变量分别映射到颜色、大小、同时映射到颜色和大小,以及形状。对于分类变量和数值变量,这些美学属性的表现有何不同?
    ggplot(mpg, aes(x = displ, y = hwy)) +
    geom_point(aes(color = cty, size = cty, shape = class))
    • 在 hwy 与 displ 的散点图中,如果将第三个变量映射到 linewidth,会发生什么?

    如果在 hwy 与 displ 的散点图中将第三个变量映射到 linewidth,基本不会产生预期效果。

    原因是 linewidth 主要用于线状几何对象(例如 geom_line()、geom_path()),而不是用于点(geom_point())。对于点图,控制大小的美学属性是 size,而不是 linewidth。

    因此:

    在 geom_point() 中使用 linewidth 通常会被忽略或不起作用
    有时可能会出现警告,提示该美学属性未被使用
    如果想根据变量改变点的大小,应使用 size 而不是 linewidth

    • 如果将同一个变量映射到多个美学属性,会发生什么?

    如果将同一个变量映射到多个美学属性(例如同时映射到颜色和大小),这些属性会同时根据该变量发生变化,从而增强数据模式的表达。

    具体来说:

    对于数值变量:颜色会呈现渐变,大小也会连续变化,两者共同反映数值大小规律
    对于分类变量:不同类别会对应不同的颜色和大小组合,更容易区分各组
    不过需要注意:

    映射过多美学属性可能会让图形显得杂乱,降低可读性
    有些组合(如形状 + 大量类别)可能不易区分

    • 绘制 bill_depth_mm 与 bill_length_mm 的散点图,并根据 species 为点着色。按物种着色揭示了这两个变量之间的关系的什么信息?那么按物种分面又会如何?
    # 按物种着色
    ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = species)) +
    geom_point()
    # 按物种分面
    ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
    geom_point() +
    facet_wrap(~ species)

    按物种着色可以在同一坐标系中比较不同物种的分布情况,显示各物种之间的差异和可能的分组趋势,但可能会有重叠。

    按物种分面则将每个物种分开展示,使每一类内部的关系更清晰,更容易观察各自的模式,但不如着色方式直观对比不同物种之间的相对位置。

    • 为什么下面的代码会产生两个独立的图例?如何修复以将这两个图例合并?
    ggplot(
    data = penguins,
    mapping = aes(
    x = bill_length_mm, y = bill_depth_mm,
    color = species, shape = species
    )
    ) +
    geom_point() +
    labs(color = "Species") # 注意这里大写了,会被判别为另一个变量,删掉这句即可。
    • 创建以下两个堆叠柱状图。第一个可以回答什么问题?第二个可以回答什么问题?
    ggplot(penguins, aes(x = island, fill = species)) +
    geom_bar(position = "fill")
    ggplot(penguins, aes(x = species, fill = island)) +
    geom_bar(position = "fill")

    第一个图(x = island, fill = species)可以回答:在每个岛上,不同物种的相对比例是多少?

    第二个图(x = species, fill = island)可以回答:对于每个物种,它们分布在不同岛上的相对比例是多少?

    保存你的图形

    一旦你绘制好了图形,可能希望将其从 R 中导出并保存为图像,以便在其他地方使用。这正是 ggsave() 的作用,它会将最近创建的图形保存到磁盘:

    ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
    geom_point()
    ggsave(filename = "penguin-plot.png")

    这会将你的图形保存到你的工作目录中,这个概念你将在后面进一步学习。

    如果你没有指定宽度和高度,它们将取自当前绘图设备的尺寸。为了实现可复现的代码,建议你明确指定这些参数。你可以在文档中了解更多关于 ggsave() 的内容。

    不过,一般来说,我们建议你使用 Quarto 来整合最终报告。Quarto 是一个可复现的写作系统,允许你将代码与文字交织在一起,并自动将图形包含在报告中。

    练习

    • 运行以下代码行。两个图中哪个被保存为 mpg-plot.png?为什么?
    ggplot(mpg, aes(x = class)) +
    geom_bar()
    ggplot(mpg, aes(x = cty, y = hwy)) +
    geom_point()
    ggsave("mpg-plot.png") # 只保存当前图片
    • 在上面的代码中,你需要做哪些修改才能将图形保存为 PDF 而不是 PNG?你可以如何查找 ggsave() 支持哪些图像文件类型?

    常见问题

    当你开始运行 R 代码时,很可能会遇到问题。别担心——这对每个人来说都会发生。我们都已经写了多年的 R 代码,但每天仍然会写出第一次运行就出错的代码!

    首先,仔细对比你正在运行的代码和书中的代码。R 对细节非常敏感,一个放错位置的字符就可能带来完全不同的结果。确保每个 ( 都有对应的 ),每个 ” 都成对出现。有时你运行代码却没有任何反应,这时请查看控制台左侧:如果显示的是 +,说明 R 认为你还没有输入完整的表达式,它正在等待你继续输入。在这种情况下,通常可以按下 ESCAPE 键中止当前命令,然后从头重新开始。

    在创建 ggplot2 图形时,一个常见的问题是把 + 放在了错误的位置:它必须位于行尾,而不是行首。换句话说,确保你没有不小心写出像下面这样的代码:

    ggplot(data = mpg)
    + geom_point(mapping = aes(x = displ, y = hwy))

    如果你仍然卡住了,可以尝试查看帮助。你可以在控制台运行 ?function_name 来获取任何 R 函数的帮助,或者在 RStudio 中选中函数名并按下 F1。即使帮助看起来不太有用也没关系——可以直接跳到示例部分,寻找与你想要实现的内容相匹配的代码。

    如果这仍然没有帮助,请仔细阅读错误信息。有时候答案就隐藏在其中!不过当你刚接触 R 时,即使答案就在错误信息里,你也可能还不知道如何理解它。另一个很有用的工具是 Google:尝试搜索错误信息,很可能已经有人遇到过相同的问题,并在网上获得了解答。

    总结

    在本章中,你已经学习了使用 ggplot2 进行数据可视化的基础知识。我们从 ggplot2 的基本理念开始:可视化是将数据中的变量映射到位置、颜色、大小和形状等美学属性上。随后,你学习了如何通过逐层添加来增加图形的复杂性并提升其表现效果。你还了解了如何通过使用额外的美学映射以及使用分面(faceting)将图形拆分为多个小图,从而可视化单个变量的分布,以及两个或多个变量之间的关系等常见图形。

    在后续内容中,我们会反复使用可视化,并在需要时介绍新的技术。

    在掌握了可视化的基础之后,下面我们将稍微转换一下重点,为你提供一些实用的工作流程建议。

  • 科学论文写作要点(2019-10-15更新)

    发表的论文是写给读者的,不是写给自己的,因此,要保证读者有兴趣读你的文章。描述自己的发现,然后用实验数据支持你的观点,逻辑地下结论。写给别人,要求文字和数据都能清晰的表达!写作顺序不要根据实验的时间顺序,而要遵从逻辑顺序。

    编辑喜欢的文章:

    1. 同行认同的强有力证据支持的文章;

    2. 对领域有更深的理解,解决重大问题或开发新方向;

    3. 有更大的实际应用价值,提供应用可行方案。

    如何挑选杂志:

    1. 通过读相关文章,找到喜欢发表类似领域文章的杂志;

    2. 在杂志网站寻找该领域文章的发表的标准。
     

    编辑的最爱:

    接下来,编者会介绍生命科学和物理学中,他们喜爱的例子。

    一个nature的例子

    当你被细菌感染,你会肿胀、发红、疼痛,这些每个人都知道。生理学家告诉我们,它有一个发展顺序:细菌引起炎症、肿胀并释放一些东西,然后激活神经元引起疼痛。

    这篇文章发现了顺序上的不同。它发现细菌首先激活神经元,而免疫系统和炎症是可有可无的。神经元上的细菌直接引起疼痛,反过来神经元释放一些分子调节免疫和炎症。这与以往的认知相反。就这篇文章的影响而言,很难找到切入点,因为它的确影响到基础科学、应用科学和临床。很明显,它可以改变人们在医院谈论细菌如何引起炎症和疼痛的话题。

    作为一个基础科学杂志的编辑,我对科学意义和潜在的疯狂想法感到兴奋。这篇文章,细菌直接影响“伤害感受器”神经元系统,“伤害感受器”系统随后调节免疫系统,但是,还有其他感觉神经元,其他感受神经元也能从细菌那里直接获得信息,然后调节免疫系统吗?

    该文打开了神经元系统和免疫系统互作的潘多拉魔盒。但是上述一切,需要非常多的结果,是涉及很多人的重要研究,它明显不是在Nature发表的唯一途径。所以,如果在这篇特别的文章中吸取一个教训,作为一个研究人员,在整理一篇论文之前,它是一个项目,可能你应该将自己放在裁判的位置问自己所有该问的问题,在所有问题都得到解决后(复选框打勾),你可能在审查中得到很好的体验。

    又一个nature的例子 (生命科学)

    它是一个基本的细胞过程,叫做泛素化。泛素是附着在其他细胞蛋白上的小蛋白。细胞中的任何蛋白实际上都可以被泛素化。一旦蛋白被泛素化,它们被标记,或者降解或者改变活性或稳定性。实质上,泛素,像其他转录后修饰器一样,例如磷酸化,是一个化学组,绑定在蛋白上,影响他们的活性。

    泛素化的一个工作背景(案例)是标记受损线粒体上的蛋白。本例中,一种E3连接酶叫做parkin,将泛素绑定到线粒体表面的蛋白上,但是为了让parkin正常行使功能,它必须被一个叫做PINK1的蛋白给磷酸化。我们知道PINK1对parkin的磷酸化是该反应所必须的,但这还不够。这篇论文发现泛素也能被磷酸化,这是这篇文章最重要的信息。但是,真正在编辑水平上感到兴奋的是,就像我前面解释的,细胞内的任何蛋白都可以被泛素化。所以尽管在这篇文章中,他们已经展示了parkin泛素被PINK磷酸化的背景(案例),这可能也与各种其他蛋白有关。任何被泛素化的细胞内蛋白也可以被进一步被这样的磷酸化影响,这本身非常重要,因为泛素是一个转录后修饰器。

    这篇论文展示了转录后修饰器本身的转录后修饰,新增加了复杂的一层,细胞内调控的新层。同时,PINK和parkin是重要的蛋白,因为它们与理解帕金森症有关。在一些熟悉的疾病形式中,这些蛋白中的每一个都是单独突变的。最终,PINK和parkin也是细胞质量控制过程中的调解员。这篇文章的优点是什么?是新奇的概念,一个泛素被磷酸化的新现象,也在各种角度引起广泛的兴趣。

    令研究团体非常兴奋的确认该工作的是,几个星期之内,三个不同的独立研究组发现了相同的结果,三篇文章基本上一起发表。实际上,我们在新闻和观点文章中报道了这三篇文章。给一个建议,这个建议不仅适用于细胞生物学家,也适用于想在Natrue或其他杂志上展示自己工作的科研工作者,用Covering letter去展示你们的优势和解释这篇论文的重要性,以及为什么这篇论文适合该杂志。

    再一个nature的例子(生命科学)

    到今年为止,我处理过的优秀的稿件之一,叫做“世界珊瑚礁鱼类的恢复潜力”。珊瑚礁是世界上生物多样性最丰富的海洋生态系统之一,因此非常重要。但是,他们也是世界上最受威胁的生态系统之一。这源自至少3种不同的威胁,一个是气候变化中的温度效应,引起珊瑚漂白。另一个气候变化引起的原因是二氧化碳的增加,它引海洋酸化,使珊瑚结合更多的钙。最后一个威胁是捕鱼和过度捕捞。这篇文章着眼于开发珊瑚礁使鱼类生物量减少。

    本文的标题导致需要找出未开发珊瑚礁的鱼类生物量,事实证明,它是每公顷一吨。要得到这个数字,作者对未开发的珊瑚礁和已开发的不同时期的珊瑚礁以及已经被保护了不同时间的珊瑚礁进行了观察。还做了时空替换。然后,他们回看这些巨大的数据集,他们有全球800个珊瑚礁。他们回看降级情况,发现全球80%的珊瑚礁比未开发的降低50%甚至更低。因此,这篇优秀论文发表的关键之一是巨大的数据集。

    这里,我们查看了800左右的珊瑚礁,我们搜集了大约10年的数据。那不是说在Natrure发表的文章必须用大数据,但它是写Natrue文章的组分之一。通常有至少3到4个不同组分合并到一起组合成一篇Nature文章,这些组分包括数据集的大小,新概念,提出以前我们不知道的数字,令人瞩目的技术,令人印象深刻的大量统计分析。

    然后,你会得到启示。本文对海洋保区和可替代海洋保护区的政策有启示。所以,3到4个不同的方面聚在一起带来了一篇Natrue论文。最终发表在Nature上的论文会发生什么?不是单一的因素让他们能够发表在Natrue,需要有一系列属性使它成为Natrue论文。显然,你看到,发表的文章与几个月前接收到的原始稿有明显提升。所以请牢记,不要被收到的评审意见吓倒,同行评议是提供建设性意见的强有力工具,用来提高我们的文章水平,这就是为什么我们最终能发表伟大的作品。

    一个Nature Plants的例子

    有一篇文章对我印象深刻,题目为:“通过在空生态位增加植物资源评估入侵性的发展”,发表在《自然植物》杂志。它是一篇好文章,因为它很容易吸引读者全览此文。它具有优质引言、清晰的实验设计和明确的结论。在这方面,它绝对是一篇好文章。该文章观察加利福尼亚的一种叫做黄星蓟的杂草。黄星蓟自从1900年代引入开始一直生长到现在,而它原本生长在地中海气候中。它在快速的增长,一些人认为其是加利福尼亚最具入侵力的植物。

    这里,我们关注的问题是为什么这个植物能在加利福尼亚如此成功,在地中海,它是一个表现完美的植物,不接管任何地方,它谦虚的坐着,与周围的环境和睦相处。研究人员没有把它与当地杂草做对比,去得到它的优势特征,这些是过去研究外来入侵植物所做的。他们关注的是黄星蓟到底有什么不同,怎样在地中海生长。它们从加利福尼亚各地取样,也从地中海采集了植物样本,从这些植物中取出种子,播种在温室受控环境下,观察是否它是相同的植物,或者是否有不同的特性。

    长话短说,星蓟有能力超越加利福尼亚的本土植物,事实似乎并非如此。它更像是在生态位上生长良好的植物,而这个生态位是当地植物让出来的,出于某种原因,让出生态位的植物已经死了,星蓟利用它们不再占用的生态位。在某些方面,这是很小的研究。只包含1个植物和加利福尼亚的一个地点。但是,我认为它会刺激其他人和其他研究者,去关注他们自己关注的植物是否也正在发生类似的情况。它有点像换一种方式观察外来入侵者。

    我们趋向于认为外来入侵植物不好,它们破坏自然生态系统,赶走已经存在的植物。而本例中,似乎不是这样发生的,因此,我认为它是一个以不同视角看待事物的好案例。它为我们提供给另一个看待外来入侵的方式。

    一个Nature的例子(物理)

    一篇处理洛伦兹对称性的文章在一月份发表。他们使用了新、老两种方式测试洛伦兹对称性。换句话说,他们使用被困的离子,它将量子信息技术用于基础物理测试。在物理学上,洛伦兹对称非常重要。它基本上意味着物理定律在不同的参考框架。什么是参考框架?例如,如果你正匀速驾驶一辆汽车,火车以不同的速度行驶,这些可以是不同的参考框架。问题是,在所有参考系统内,物理定律都是一样的吗?以不同的速度移动或改变参考系统。

    所以,你如何测定洛伦兹对称性是否真实?经典的测试是迈克尔逊 – 莫利式测试。他们使用一束光,射入干涉仪,光有两条途径,如果两条途径的光线速度不同,它应该是可视的,你最后可以看到的干涉模式。作者做了什么?基本相同,但他们使用电子波包,不是一束光,在对称测试中它具备一些优势。因此,我认为这是一篇非常重要的论文,因为使用被困的离子和波包的想法去测试洛伦兹对称性,具有原创性和新颖性。

    那些被困的离子已被大量用在量子信息,但是将他们用于精密测试,比如本文,是非常原创的想法。第二,使用这个技术,他们得到了非常精密的结果。所以,他们能够确定洛伦兹对称式1018电子的一部分,这非常重要,因为它超越了一个里程碑。10-17,是弱电力和万有引力间的比率,正如期望那样,洛伦兹平衡违反了它。因此,精密度高于这种里程碑。所以,我认为这是一个非常有趣的结果,它也将为其他研究者改善这个结果提供途径,只是在精度方面。但也得到了量子信息和精度测试方面的灵感。

    又一个Nature的例子(物理)

    十二月,我们发表了一篇珍宝级的论文,叫做《蜘蛛感官系统启发的超灵敏机械裂纹传感器》。

    当这篇文章第一次摆在我的桌子上时,非常与众不同,我不得不阅读两遍,三次才完全理解作者所作的工作,和可能的重要性。他们写了很长的引言,关于蜘蛛的仿生学。我最终了解到,这篇论文非常棒,它用仿生学设备概念,开拓了一些列可能的应用范围。

    这篇论文,作者用很薄的铂金膜做了一个高灵敏度的机械传感器。它只有20纳米厚。它能在薄膜内通过小心的弯曲产生微小的裂缝。

    这篇文章成为Nature论文有几个原因。一个是它证明了一个新奇且吸引人的设备概念。第二,它在一个火热的领域取得了明显进展。第三,论文论述了一系列实际应用的范围。例如,他们证明了传感器非常灵敏,可以检测瓢虫震动翅膀。它能检测得到嘈杂环境中人们演讲的不同内容。他们能在皮肤上检测人的心跳。这非常刺激,其他研究者可以提出设想,关注具体设备的实际应用。

    小节一下:

    1. 发表可以让同行检验你的发现,也引领该领域的进步。

    2. 专业的编辑会寻找有数据支持且在该领域或相关领域做出巨大贡献的新发现。

    3. 尽早地构思文章非常有益。它会让你的文章更加清晰、结构严谨,能更好的为读者传递思想。
     

    进一步工作

    1. 做一个敏锐的读者
       –  当阅读一个论文时,大多数科学家会批判的评估该文是否有可靠的数据支持,是否有新的结论:
             – 在领域内是否有巨大进步;
             – 对领域有广泛的影响。
    2. 创建自己的科学写作工具箱
       – 搜集你认为有益的文章案例,为未来写作积攒灵感:
             – 科学评论;
             – 图表;
             – 结构;
             – 短语;
             – 术语。
    3. 在你写论文时问自己:
       – 你应该在文章中回答一些问题,确保读者能够理解你的工作。在大多数文章中你都能找到答案,你需要考虑的是,在自己的研究中回答相同的问题:
             – 我的工作有什么用?与领域内科学家做的工作有什么相关性。
             – 目标和假设如何从我之前的工作中得出?
             – 让读者信服我的结论,应该做什么?怎么呈现和解释数据?
             – 在领域里,我的发现怎样改变当前的理念。
     

    有效写作的ABC

    什么是写作的ABC?

    我们都知道写作很难。通常情况下,只是把文字罗列出来很简单,难就难在,不仅要让同行或者对该领域有了解的人明白,更要让普通读者也清楚你在做什么。这里有三条规则:A是准确,B是简练,C是清晰。

    准确

    怎么理解准确?准确就是确保你的读者可以明白你写的是什么,没有模糊的描述。模糊的描述容易引起很多问题,比如用词不当引起误解。

    “16.9倍的基因组覆盖,主要通过454的成对合成配对和未配对,剩下的覆盖率由Sanger测序获得。”如果你看到这句话,会觉得很难理解。

    我们改为“16.9倍的基因组覆盖,26%被Sanger引物测序,剩下的74%来自454测序平台的成对和非成对合成测序(附表1和附表注释)”就清晰多了。当然,还需要解释这些百分比是如何计算出来的。

    简练

    简练很难做到,相对简单的是用一大段文字进行描述。

    保持文字简练时一定要注意,不要使用华丽的辞藻和过多描述,这样会让文章失去重心。保持简练很重要,但同时必须准确。

    有一个例子,原文为“基于这些结果,我们假设接种疫苗的对照个体与用化合物X处理的相比可以呈现相同的细胞因子谱。评估该假设,我们对比了接种疫苗的对照个体和接受治疗的患者。我们发现了高频率的…”。句子冗长,有很多冗余和重复。事实上,也很难读懂,可能需要再读一遍才能知道文章描述了什么。

    修订版就好多了。“基于这些结果,我们假设接种疫苗的对照个体与用X复合物处理的患者都呈现出相似的细胞因子谱。相比之下,我们发现较高频率的…”。这非常精简,移除了重复和冗余,用一个句子取代两个句子。把两个句子合并成一个可能在大多数写作中都是黄金法则。

    清晰

    我经常要求作者提交手稿时描述的更加清晰。常见的是好几件事被聚集在一个自然段,或者一件事被拆分到不同段落或部分。这很难让读者跟上你的思路。

    “而黑猩猩广泛分布于赤道非洲,矮黑猩猩,有相对较小也相对较远的栖息地,那也意味着他们是最后描述的猿,居住在刚果共和国刚果河以南(图1a),是稀有的类人猿。”长句中两件事,使得它很难读懂,很不清晰。

    所以,修正的版本为“而在赤道非洲广泛分布黑猩猩,矮黑猩猩则在刚果共和国刚果河以南居住(图1a)。由于相对较远和较小的栖息地,矮黑猩猩是最后描述的类人猿物种,也是圈养的最稀有的猿类。”我们将一个长句分解成两个句子,每个句子只说一件事。这样,句子清晰了,更容易阅读和理解。

    写作风格中的常见问题

    最常见的三个问题是歧义、不必要的复杂性和被动的声音。

    歧义

    经常能够看到的是模糊的比较,比如说一些东西比另一些东西重要,却并不说明另一些东西是什么。

    it的指代不明确也很常见。

    另外,人们趋向于使用拟人的写作手法。一些人喜欢写“癌细胞很有个性,决定抗击肿瘤治疗,在人体内逃跑和转移。”这确实是用简单的方式去解释,但是并不准确。事实上,拟人手法应该尽量避免,因为它不是科学语言。

    不必要的复杂性和被动的声音

    另一个重要问题是简短和清晰。用短的单词永远比长的要好,比如在讨论或其他一些地方用use代替utilize。这主要是由于很多人的英语非母语,你要考虑句子长度等问题。

    更加华丽的描绘常常难以理解。所以,另一种简短和清晰的方式是你将它说出来。当你自然表述的时候,你会使用更短的单词并将你的信息表达的更清晰。然而,一定要小心,不要用口语词汇来写作。不要使用缩写词,如can’t、won’t、don’t等,诸如这些。你不会在科技论文中看到这些,所以也要避免使用它们。

    在你的写作中要注意时态。在描述你已经做过的事情时,倾向于使用被动。但是,你也应该考虑使用积极的声音。例如,”某某技术曾被用来鉴定…“,你可以换成主动的声音,”我们使用某某技术去发现了…“。主动比被动更短、更清晰、更容易理解。用主动表示我们做了什么,用被动表示别人做了什么,读者更加容易区分,这在科学写作上非常重要。

    构建段落

    内容的主要单元或主要的文字,或者介绍、结果、讨论部分都由段落构成。理想情况下,每个段落解释或描述一个主题。当看一段话的时候,开头和结尾最能抓住读者的内心。因此,写好开头和结尾尤为关键。

    一般的,写一段话的方式有两种。第一种是从上到下的方法,在段落的第一句强调你的主题,然后解释它的意思,用证据证明它,等等。从上到下的方法在科学文献中最常见,因为易于阅读。读者可以通过第一句话获得整段的主题。

    构建段落的第二种方法是自下而上法,在讨论部分非常常用。例如,你具体的描述都做了什么,然后再最后给出结论。在讨论某种特定的方法或具体的结果得到某个结论时,最好使用自下而上法。

    高频问题

    1. 收到有问题的的文章,编辑会怎么做?
     

    文章的第一次评估,语言不是问题。只要编者能理解研究者所要表达的即可。之后,如果文章语言接近发表水平,简单的完善;如果语言不足以发表,编辑会要求让母语是英语的人帮忙修改。首次体检,不用太担心英语水平。
     

    2. 对非英语母语的人写文章,有什么建议吗?
     

    我的母语也不是英语,根据我自己的经验,写第一篇论文很难。必须要做的是,阅读大量文章,观察短语、结构等,看看摘要如何组织,尽可能列出常用短语,以备自己写文章的时候使用。

    小结

    好的文章需要发现新的东西,并用数据证明它。时刻认识到写文章为了交流,而不是写给自己。
     

    文章不是简单的罗列,要针对目标读者而写。保持准确、简洁和清晰。
     

    – 每个段落一个主题或一个概念。

    – 正确使用主动时态。

    – 用简单的词汇,但需要专业和精确。
     

    设立个人目标
     

    花时间审视自己的文章。检查文章是否达到准确、简洁和清晰。改善歧义、不必要的复杂和错误的被动。

    例如,你是否倾向于写长句子?如果答案是是,尝试将不同的主题分开到不同的句子中。修改你的文字时,通常超过25个字的句子要转变成短句,每个句子中一个主题。进行一些练习,你的写作风格会明显改善。

    标题和摘要的写作

    作为一个读者,什么可以吸引你去读一篇文章?

    标题和摘要通常来说是最早进入读者视线的,是文章的第一印象。文章的读者类型多种多样,其中不仅有领域内的专家、学者、审稿人,也有相关领域的人员甚至普通大众。你的标题和摘要需要有足够的吸引力来获得更多人的关注。

    从直觉上讲,同行是你文章关注的目标,他们非常专业,可以理解你文章的每一个部分。你的同行很容易通过相同的关键词发现你的文章。但是,专业人士毕竟是少数。一篇文章,想要具备一定的影响力,就需要更多的人来引用。而引用文章的人可以是同行,也可以是其他相关专业的人员。

    大多数读者喜欢使用类似PubMed这样的搜索引擎。结果一般会列出标题,或者一些简要信息。无论发表多久的文章,好的标题和摘要都能让人提起兴趣浏览全文。

    一般来讲,看题目的人最多,其次是摘要,最后之后很少的人看文章内容。有吸引力的标题让人更想看摘要,有吸引力的摘要让人更想看内容,提高读者的范围和数量,并且让他们在文章中找到他们想要的,就会增加引用率。

    大多数读者只阅读或付费他们想要的内容。因此,在合理的范围内,标题和摘要都应该是独立齐全且易读的,以便潜在读者理解你的发现。

    特色标题的价值

    人们看的最多的文章部分就是标题,它是一个切入点。因此,文章的标题一定要易于理解。你针对的目标不要只限于专业人士,还要广泛争取相关专业的读者。

    标题可以容易并免费得到。请尝试让其具有独立性,即概括工作的主题,采用的方法,关键点,以及你想要读者了解的核心参数或发现,以便吸引他们看后面的内容。

    基于以上几点,你在标题上花费的时间会比你想象中还要多。它可能并不是你写文章的第一件事,文章完成后去修改他可能有更多的意义。

    以下情况请慎重考虑:我已经完整的写下来,我已经全览了所有内容。现在标题是最适合的吗?已经表达了我想要表达的吗?花一些时间考虑,它是否合理,是否易于阅读,是否符合目标杂志的要求。

    很多杂志编辑可能会帮你修改标题,记住,当编辑建议你修改标题时不要惊讶。这是整个过程的必要部分,可以让你的工作更清晰的展现。

    有效的撰写标题

    如何有效撰写标题?引起他们的兴趣并阅读摘要。

    写出陈述性、有吸引力和专注的标题

    标题要具有“陈述性”、“有吸引力”和“专注”。

    写英文文章,需要准确、简练和清晰,英文标题也是一样,还需要增加陈述、吸引力和专注。

    陈述是清晰的告诉读者它们需要什么,例如,如果你发现了对某事情的影响,不要只说有影响,要说影响是什么,是增加了?抑制了?等等。这种细节会在标题中起到很大作用。

    吸引力并非是在标题中使用流行语,它不是用讲笑话或类似的方式吸引人。它是你文章的发现,要让读者清晰的看出你的发现,没有其他的。

    专注是关注主要发现,而不是解释整篇研究,不要把所有研究内容都概括进来。常规的研究和分析不是读者感兴趣的点,只有那些新发现才能吸引读者继续读下去。

    写一个特别的标题

    自然物理作者带来的一个标题,叫做 “拓扑薛定谔猫”。有些人看了可能会很感到兴奋,它的信息量不大,说实话,甚至缺乏可信度。它非常适合作为新闻或观点的标题,但是不适合作为研究论文的标题。研究论文的标题必须特别的,信息丰富的,包含必要的关键词,所以我们将其编辑为信息量更大但有点无聊的标题——拓扑缺陷的非局部量子叠加。

    小节

    题目要包含做了什么,有什么发现(效应),试验条件(如体外体内),什么引起了差异(如果是已知的蛋白或激素,需要给出名字),顺序并不固定。

    避免在标题中出现

    复杂描述

    一般,在写作标题时,不要出现复杂的描述,它会让你的标题过长,占用篇幅,读起来也别扭,也容易引起歧义。比如,“过量水分重量移除器”这样的描述应该直接用“干燥器”来代替。在英文中,如果换行,通常会增加破折号,而复杂描述在换行时,中间出现破折号,会改变原来的意思,因此不推荐使用。同时,避免在题目中使用首字母缩写,这很难让人理解,尤其是外行。

    缩略词或缩写

    缩略词和缩写是你给读者挖的坑。在标题中使用它们的问题是,你的读者在阅读全文前,根本不知道这个缩写的含义,标题永远是最先出现读者面前的。

    尽量避免缩略词或缩写出现在标题,尤其是不常见的,甚至是你自己定义的词,绝不能出现。所以,请尝试将其拼写出来,那些知道的人会主动跳过这里去阅读其他部分。

    标题中的问句

    在标题中提出设问或疑问应该尽量避免。标题中的问句可以吸引眼球,但你最终给出的答案可能并不是读者想要的,非常容易引起读者的不满和失望。因此,尽量不要这么做。比如:“一个巨大的精子有多长?”这是一个非常普遍的问题,在当时也很新潮。但是,没人能在标题中找到真正的意义。

    Reference

    Pitnick, S., Spicer, G. & Markow, T. How long is a giant sperm? Nature 375, 109-109 (1995).

    双关语

    直接了当的标题可能会让人感觉有些无聊。有些人会想,是不是应该在标题中加入个玩笑,或者双关语或者有趣的陈述,或者隐喻、比喻等等?

    这么做通常是危险的,而且经常事与愿违。读者的英语水平可能无法理解你的玩笑,或者你为了加入玩笑,有些重要的信息没有写在标题中。读者可能会感觉,有些重要信息没有直接表达,或者看上去什么也没说,最终好像只说了一个笑话。

    读者可能会对这种标题表示反感,他们会觉得这里没有想要的答案,或者认为作者不够严谨,没有认真对待这篇文章。仅仅阅读标题,找不到文章要说的重点是什么。

    知识检测:确定有效标题(生命科学例子)

    读一读下面的摘要,找出最适合的标题。用你之前所学,回想标题需要哪些特性。

    摘要

    为了抑制全球530万人死亡,全球体育大流行。我们需要理解支配身体活动的基本原则。然而,全球自由人口的大规模体育活动部分发现了一个缺乏。在全球范围内,智能手机充当了杠杆的作用,去测定体育活动。我们研究了全球111个国家,717527个人在680万天的数据,它提供了一个了解体育活动的窗口。我们发现在国家内部,体育活动的分布不均匀,它与使用均值相比,可以更准确的预测肥胖的流行率。女性活动量的降低在活动不均衡中占了很大部分。内置环境方面,例如提倡行走的城市,在活动量和活动不均衡方面与性别有较小关联。在更提倡行走的城市,日活动量、周活动量跨越年龄、性别和身体质量指数(body mass index,BMI),女性的增长幅度最大。我们的发现对全球公众健康政策、城市计划有启示,突出了活动不平衡和环境内盖上活动量和健康的作用。

    问题

    下面哪个标题最合适:

    – 城市环境影响活动不平衡和肥胖

    – 一步一步理解物理活动水平和肥胖

    – 全球体育活动测定发现活动不平衡,它受环境因子影响,比用均值预测更准确

    – 大规模体育活动测定数据揭示全球活动部平衡

    – 全球活动部平衡在环境内削弱

    – 体育活动水平等于世界人口?

    答案

    – 大规模体育活动测定数据揭示全球活动部平衡

    选择论文的关键字

    当你写出了一个好文章,你需要的是让更多的人能够读到它。这需要更多的人能够发现它,有必须把文章提交到网络搜索,这就需要你选择好关键词。我们建议每200字的摘要选择3-5个关键词,如果可以,尽量包括标题关键字。有些期刊需要在它们提供的列表中选择5个或更多关键词,有些则可以自定义。这有助于你的文章在数据库或网站索引中被发现。

    如何写摘要

    不同的领域和不同的期刊对摘要的要求和惯例不同。然而,一般而言,摘要是文章的迷你版,需要反映你的工作是什么,读者根据摘要认定是否值得查看全文。

    五问题技术

    构建摘要的方式有很多种,有时候,它需要根据欲投稿期刊的要求特别格式书写。常用的构建方法还有5问题方式,它提出5个问题,然后一一回答:

    – 为什么做?

    – 做什么?

    – 怎么做?

    – 发现了什么?

    – 有什么意义?

    首先,为什么做是工作的大背景。你要回答的一般问题是什么?什么是最大的挑战?你需要填补哪些知识缺失?为什么你想把这个研究放在首位?

    然后,做什么?你用什么精确的方法去回答上个问题?你想要尝试哪些事情?为了理解问题和解决问题,你采用哪种方法?

    如何做?比上一步更进一步,解决问题的细节,特别的方法。

    你发现了什么?将所有的发现放到初始框架下,结果是否与答案相关?答案是什么?你发现的事实是什么?

    它有什么意义?总结整件事情,联系上下文。现在我们理解了X,现在告诉我们首要的问题或者上下文中更广泛的问题,或者研究可以从哪里开始?

    你可以想象一个沙漏,沙漏的形状代表了宽度和广度。5个问题中的第一个问题写的要大,考虑更大的事件和更广的关联。随后慢慢缩窄,落在独特和具体的着力点上。后面,再次变大,从你的结果出发,未来的研究应该从哪里开始。

    Nature的摘要段落

    自然杂志这编辑建议读者使用的摘要段落,包括Natrue Letters。

    该结构是Natrue的特别结构,也适用于其他杂志:

    – 有介绍

    – 有一个句子关于缺失的知识或你想要解决的问题

    – 这里我们展示了…,真正发现了什么,你的主要声明(我们做了什么,得到了什么结果)

    – 你做了什么的简要介绍

    – 你工作的启示。

    介绍部分不要超过3句,给你的研究要给大背景。你想要解释的,在领域中发生的现象,我们建议这部分具体写。

    然后,描述缺失的知识,突出问题,一般不超过一句话。

    简要提出你的声明。

    用几个句子,通常是摘要中的最大部分,描述你做了什么,用什么方法来支持声明。

    启示方面,再一次具体些。这些结果中有什么新观点,这个方法或具体研究开放了那些研究领域?

    摘要中要避免的

    空句子

    很多人在声名的时候,总是喜欢用里程碑、基石、核心等等,但是在文中却没有这些文字的证明信息。不要提未经证实的内容。

    小结

    标题:

    陈述性:清晰描述你的主要发现

    有吸引力:唤起读者继续看全文

    专注:给读者提供最有用的信息

    摘要:

    – 为什么做?

    – 做什么?

    – 怎么做?

    – 发现了什么?

    – 有什么意义?

    少数人喜欢先写摘要,需要注意的是,随着草稿的书写,摘要可能需要相应做修改,请在全文完成后再次审视摘要。

    写论文:画一张图

    重要特性:

    – 目标和全文基调

    – 叙述流

    全文,有点像扩写的摘要。从背景写起,然后提出目标,展示结果,下结论。

    目标撑起整篇文章,你怎么解决目标,有什么发现等。最重要的是,文章所有内容必须和目标相关联。

    前言是非常少量的背景信息,让读者快速的聚焦到你的目标上面。

    讨论时对结果部分的深度挖掘,并讨论相关问题。同样,检测你的结果是否适合前言中的文献。

    纵览全文,论点集中在具体的目标跟合理性上面,也包括特殊结果。简要总结新发现。仔细审视重要性、稳定性,找出进一步工作和启示。

    如何写引言

    引言是科技手稿中的核心部分之一。但它不是长篇大论,不是文献综述,不是大篇幅的。它应该只涵盖你的主题。

    引言第一部分是背景,让读者明白这篇论文是关于什么的。

    引言第二部分是吸引,告诉你的读者,为什么他们会对这篇稿件感兴趣。是临床上未被解释的现象?以前未应用在该领域的技术?或者其他一些能够让读者感兴趣继续读下去的内容。

    引言的例子

    下面是Natrue Letters的一个例子,结构分明。本来是一段,为方便理解分成3部分。

    1. 一般性的介绍

    回声定位系统是蝙蝠和鲸鱼等独立进化出来的复杂表型性状,包括发出、接收和超声脉冲的声音处理,用于躲避障碍、辨别方向和捕猎。

    2. 面向该领域的读者,需要进一步调查

    最近的进化研究发现,蝙蝠的回声定位系统并不都是一个血统,旧世界水果蝙蝠没有这个系统。说明,蝙蝠至少进化了两次回声定位系统,亦或旧世界水果蝙蝠进化早期丧失了该能力。新的证据支持之前的观点:蝙蝠回声定位的分界点似乎是听力相关的几个基因的碱基替换。此外,蝙蝠和鲸鱼的一些听觉候选基因也出现平行改变,这也是高频率听觉的证据。潜在的其他回声定位系统的基因已经在鲸鱼和蝙蝠序列中定位到了。

    3. 声明本研究的目标

    这里,使用回声定位进化作为表型收敛的模型,我们调查了蝙蝠和鲸鱼独立进化过程中已经发生在基因组上面的平行改变程度。

    如何写方法部分

    方法部分是你的文章与同样专家交流的重要组成部分。方法部分要足够详细,让同行能够重复实验并获得相同的结果。

    如果你培育一个材料,你需要足够的细节让同行可以培育类似的材料。如果你使用光谱方法,你需要介绍所有光谱方法需要的参数。

    假如你不清楚是否提供了足够的细节,可以将这部分交给同行,看其使用你提供的方法能否重复实验。

    如何写结果部分

    显然,结果部分要包含结果,可以是实验测量的数据,也可以是计算数据。

    记住,不是讲数据罗列在这里。确保指出数据的关键特征,同时,确保你解释你的结果。你如何做的实验、如何计算和为什么要做。

    这个部分也包含图表,用于帮助读者理解你的数据和你的收获。有逻辑地呈现你的数据和结果,不要按照时间顺序。确保你强调了真正有意义的结果特性,并提供描述。不要把所有结果列出来!

    结果的结构有很多,可以按照自己的逻辑书写,这里提出一个例子。

    第一,分开所有的实验和计算。每段对应一个实验或计算。

    第二,每段像这样写:第一句是实验的目的:”为了调查A或B对X的响应,我们调查了这些“。然后呈现相关的图表,通常用图、表或两者都用。最后用简短的句子概括基本结论,通常这样说,”这表明这或那显著依赖于X或Y。“

    接下来,下一段与上一段用相同的方式描述。你可以联系以下前一段,这样文章更通顺。

    数据提交存储

    将自己的数据提交到数据存储库(知识库)有很多好处。

    首先,一些杂志,比如Natrue系列的杂志会要求作者将数据共享或提交到知识库。

    第二,这样做可以方便的让其他研究者在你所作的基础上继续进行进一步研究,它会提高你在学术领域的知名度和地位。

    第三,便于其他研究者和自己理解数据。当时间比较长,感兴趣的其他研究人员问到你的数据时,你可能已经忘记了具体的步骤和结果,甚至找不到原始数据存放在哪里。将数据提交存储到公共库,可以随时进行查看,回答其他研究者的问题。

    第四,尽早地将数据提交到知识库,可以利用知识库固定的格式查看自己数据的问题,修正数据的错误,同时,可以分享给你的合作伙伴,协作处理数据。统一的格式有助于全球科学家共同发展。绝大多数知识库都具有私有属性,在发表前,不会公开。

    最后,将数据共享到知识库是研究人员的责任,无论所选的期刊是否有这方面的要求。在知识库,数据可以存放5年甚至20年的时间,它不会改变,不会丢失,任何人想要在此基础上进行研究都能很容易的得到。
     

    Recommended data repositories – 推荐的知识库列表

    高频问题

    将数据提交到知识库的最常见问题:

    1. 我应该选择什么样的知识库?

    2. 提交到知识库我应该做什么准备?

    3. 谁有责任对知识库中的数据进行公开和维护?

    请首先选择自己的领域的知识库。在生物学中,可以去biosharing.com这类网站,了解信息标准。此外,Scientific Data网站列出了70个数据知识库,你可以浏览一下,查看哪些是应用在你所在领域的。网站也列出了一些复合数据知识库,在那里可以存放不同类型的数据。我们也鼓励作者将数据提交给自己单位的相关部分,最近,很多单位图书馆设置了提交选项。

    当你准备提交数据到知识库时,最好先浏览一下知识库的网站的要求,了解数据类型的最小信息标准,这些都能在知识库网站上找到。知识库官方会提供一个建议提交标准,最好按照该标准准备数据。请确保你提供了数据相关的足够信息,也叫做元数据,其他人可以通过这些信息理解数据,它怎么生成?用什么技术?表如何组织等。数据的版本也很重要,如果你的数据在不断发展,请确保你有最原始的版本,方便其他人回溯到原始版本重复分析过程。

    数据知识库由不同的组织维护。最早的和最大的知识库由国家发放或国家基础服务资助。NCBI是最早的数据知识库系统之一,由美国政府资助,EIB基本上是其欧洲的双胞胎。中小规模的知识库通常由个别机构或个人项目资助。DataSite系统将数据链接起来,你释放了数据后会得到一个DOI,方便引用。

    如何写讨论

    讨论部分在结果部分下面,是对结果的解释和评估。最重要的是讨论启示,为什么它重要以及发现了什么。

    从审稿人和读者的角度去看问题,预测他们可能产生的疑问,然后自我批判。不要怕解决研究中或数据搜集中的问题,也不要害怕去解释为什么你的研究中有些情况是不可能的。

    这部分也要写“为什么读者要相信你的数据?你用了正确的方法吗?”更重要的是,你需要回到最初的问题,你前面声明的一般目标,你需要说服读者,你确实回答了先前研究问题。

    结果和讨论部分的结合

    有些期刊会要去将结果和讨论部分合并,而不是单独分开。可以有很多写作手法,这里提供一个。

    可以这样开头,你想要调查这或那,为了调差它,你测定了某某某。然后你呈现结果(图或表)。接下来,提供这些结果的简短结论。再进入简短的讨论,你需要关注结果的评估和解读,最重要的可能是结果的意义。

    这个结合部分的核心是讨论。从一般的目标开始,得到结果,然后提供讨论。

    结果和讨论的动词形式

    动词的形式决定准确性水平。请确保你所谈论的假设、事实和新发现用了正确的形式。

    避免过度炒作你的工作

    不要夸大结果的意义,比如用改变了模式或者改变了游戏规则这样的描述。在结论的结尾处,很多人喜欢这样说。这种改变很少,可能一个世纪的内都可以用手数出来。所以,请小心的考虑真实的启示。

    知识检测:写一段话

    牢记所学的结构,平时做小实验的时候可以进行练习。一般400个英文字左右。每一段讨论一个实验,勇敢地解决你收集数据时出现的问题。

    • 实验目的:一句话描述实验的目标。
    • 实验方法:一句话到两句话说明你用什么方法实现目标。
    • 关键结果:简短几句介绍最重要的结果,引用图表。
    • 评估和解释:你的结果有什么含义?从结果中能推断出什么?
    • 启示:一两句话指出结果对该领域的影响,在此基础上下一步该做什么或能做什么?

    获得反馈

    邀请同事查看你写的段落,同时也帮同事看他的文字。尝试给出5条反馈意见,这里有一些建议:

    • 首局是否清晰描述了实验目的?太宽泛还是太具体?
    • 整段是否在讨论同一件事?
    • 段落中相关的各种想法是否都得到清晰的阐述?
    • 是否缺失了某些部分?
    • 段落过长或过短?需要增减。
    • 文字是否明确清晰?
    • 单个句子是否过长(英文)?过长请拆分成短句。

    自检列表

    一般问题:

    • 我的稿件讲了一个故事?
    • 文章前面清晰提出了主题?
    • 段落逻辑结构正确?
    • 所有段落平滑过渡?
    • 每个段落只包含了一个主题、思想或问题?

    引言:我们做了什么?为什么要做?

    • 清晰和连贯的背景介绍?
    • 报道了领域中所有相关工作的贡献?
    • 清晰的界定了文章的目标读者和读者范围?
    • 简要的介绍了我完成了什么研究?
    • 简要描述了我的主要发现和结论?
    • 相对于以前的研究,我当前的工作有意义吗?

    方法:如何做?

    • 已经提供了足够的细节,其他人可以用来重复实验?
    • 有逻辑的组织和呈现?
    • 合理的使用了所有的方法?
    • 统计方法描述足够详细?
    • 解释了所有数据删减?

    结果和讨论:你发现了什么?有什么意义?为什么要相信你的数据?我为什么关心?

    • 有逻辑地、清晰地呈现数据,提取和描述重要趋势?
    • 清晰的阐述主要发现?
    • 足够的证据证明结论?
    • 讨论点清晰,得出确切结论?

    结论:下一步做什么?

    • 已经讨论了新发现的意义?我的研究是否与我的目标直接相关?
    • 清晰描绘结果在相关领域的作用,提供改进或新的发展方向?

    数据管理

    科学来自于数据,数据是研究活动生成或搜集的材料或信息,它是研究的最终产物。数据管理在研究过程中非常重要。

    数据管理重要性有几个原因:

    1. 科学家有责任收集、管理、保存数据。科学家将数据分享出去,其他科学家可以重新计算、验证和在此基础上进行研究。因此,数据需要完整和准确。强大的数据管理计划可以帮助他们。
    2. 一些资助者、研究组织和学术团体要求数据必须保存或者可以保持可用,比如英国研究行为准则要求研究数据要保存十年以上。
    3. 一些顶级期刊要求数据公开、透明,以便其他研究人员可以方便的验证和使用。
    4. 好的数据管理计划帮助其他研究者不犯相同的错误,避免资源和资金的浪费。如何他们需要的数据已经存在,他们没有必要再做一遍相同的事。
    5. 越来越多的资助者(基金会)要求采用数据管理计划。
    6. 美国国家自然科学基金和英国威康信托都要求基金申请者采用数据管理计划。

    数据管理不善的风险

    数据管理不善很常见。牛顿的狗曾经碰倒了燃着的蜡烛,导致几年积累的数据付之一炬。现今时代,很少用到蜡烛,但数据管理不善依然常见。

    数据管理不善容易引起知识丢失。比如,一个知道特殊信息的且知道怎么样获取和使用该信息的雇员离职。

    数据安全计划也包含数据的信息,如果有人处理数据,但数据信息只有这个人知道,那么其他人在使用数据时就会造成困扰。

    用过时的或罕见的格式存储数据很难使用。例如:用软盘、光盘存储数据,很可能由于物理磨损等原因使数据丢失。

    数据丢失是损失最大的情况。目前,已经有很多免费的数据存储计划可供使用,用标准的格式存储自己的数据,方便自己的使用,也方便数据的分享流通。

    创建数据管理计划

    对所有科学领域来说,创建数据管理计划都是很好的做法。多数资助机构要求数据管理计划作为资助申请的一部分,那么,好的数据管理计划需要做什么?

    数据管理计划应该包括你的数据要如何处理,如何存储,以及项目结束后其他人如何获取到它。它不应该是一个固定不变的文件。

    理想状态下,文件应该随着时间的推移而升级。让它随着项目的进展发挥越来越大的作用。主要的调查人员和整个研究团队是计划的主管。事实上,任何直接参与数据的创建、收集和管理活动的人都是数据管理者。这个理念要传达给整个团队。

    可以确定的是,每个计划都应该根据搜集的数据类型而有所不同。不同的资助者或机构有不同的要求,但是,总有一些相同的部分。

    数据所有权是指谁控制和具有数据管理和使用的权力。资助者提出条件,研究机构和数据源,到底谁将最终拥有数据。一般情况下,如果是政府资助,机构或组织最终拥有数据,PI和资助机构只有数据的连接和使用权。通常,PI作为组织代表实际保管数据。然而,这些规则会根据机构或资助来源的政策而改变。

    数据收集包括元数据和纪录。在了解了数据如何创建和数据的实际情况后,你可能想要使用它。这就要求,你知道谁在保管这些记录,谁创建了原始数据,描述方法是什么。这些既适合于原始数据,也适合于数据处理过程。

    多版本保存是确保数据有效的必要方式。同时,也需要考虑数据的存储。例如,你要知道数据长度多少比较合适,同时,版本控制也需要注意,过多的版本也会占用大量存储空间。你是否已经需要转换成全部主流的数据格式?在计划中要明确这一点。文件命名也有讲究,标准的命名可以让团队中的成员快速找到想要的文件。

    请确保你的数据在今后仍然可用并且可以重复利用。你也需要说明你的数据想如何分享,在哪里分享。例如:你想要将数据分享到哪个知识库?你需要提供数据的详细信息。

    总之,数据管理计划最重要的是数据所有者、数据收集、数据记录、数据保存和数据分享。当你已经有了数据管理计划,记得告诉团队的每一个人并确保他们能够理解。然后,充分利用它进行下一步研究工作。

    分享数据的重要性

    大多数研究者在原则上拥护数据分享。在同行评议中,你的数据需要分享给审稿人和编辑。同样的,大多数研究者想要在文章发表后分享自己的数据给其他人,同时也希望其他人将数据分享给自己。

    可以说,数据分享已经成为学术交流生态系统中的一部分。问题是,这些交流大多在幕后完成,较少发生在公开的知识库,通常他们的交流仅限个人水平。

    为什么要进行数据分享?首先,电子邮件已经过时,联系变得困难。假如研究人员能够联系得上,电子邮件地址也没有更换,大多数数据存储在他的私人硬盘,这有很大风险。我们需要稳定的介质存储数据,以便其可以长期用于研究。公共知识库能够做到这一点。

    另一个原因,数据可以在很多方式上供人使用,也可以共享给个体研究者和小型公司。个人研究者可能难以决定数据怎么样进行再利用。非常好的范例:人类基因组计划,已经为美国经济做了9650亿美元的贡献,不仅是通过研究,也通过私人公司和研究人员等开发的工具。

    数据分享在今天非常迫切的被需要。在寨卡病毒或安伯拉病毒爆发时,起初数据分享非常顺畅,科学家和研究人员分享他们的序列数据和流行病学数据。然而,我们发现,仅仅开始时分享足够好,当事件离开媒体视线后,出现了数据缺失。

    我们需要持续性的分享数据。一些研究问题,对于个人或机构来说,究显得太巨大了,它需要全球的关注而不是个人或小团队单独研究。

    癌症是一个很好的范例。为了战胜癌症,我们需要强大的力量,每个人都可以查看所有数据。基于此,我们发现了各种癌症类型,非常多的突变,只有在共享和开放数据的情况下,我们才能做到。

    数据分享也对研究者有益。文章引用会出现额外的链接,这个链接的意义在于,公共知识库中的数据可以连接到你的文章,增加了你的文章被发现的机会,潜在的增加引用率。

    数据分享可以让更多的研究人员测试、验证你的数据,让你的数据更可信。另外,引用率也能让你获得更多的合作者。

    Dr Chris和他的研究团队,将他们发表的数据分享到了Scientific Data。另外,他们还在Twitter发推文,鼓励研究人员使用他们的原始数据和最新数据。

    Dr Daniele是神经科学计算学家,他使用了Dr Chris的数据。他觉得这个数据集组织的非常好,非常完整,包含了很多关键的信息和参数。

    FAIR是Findable,Accessible,Interoperable和Re-usable的缩写。译为:可发现、可连接、可互作、可重利用。它由科学家和组织联合体所提倡,目的是确保科学数据更高效的利用。

    元数据的意义

    无论你将数据分享到知识库还是你自己的个人服务器,如果没有包含一些必要的信息,它未必对其他人有用。包含了数据来自哪里,如何生成,其他人怎样使用的数据,叫做元数据。

    你需要考虑,其他人可能用你的元数据做什么,他们是想重新使用数据?他们使用它来验证你的数据集?还是直接通知你,你需要补充记录的方面?如果你使用了知识库,你会收到存储结构通知,告知你填写实验和必须信息。例如GenBank,提交元数据有必须填写的信息。

    将数据上传到知识库将让这件事变得容易。系统会根据你提交的数据类型指引你完成信息填写。如果你想要上传的知识库包含很多类型的数据,那么你需要考虑数据集中所有元数据类型,并在数据报告中写清楚。

    数据报告中只记录数据本身的来源和测定方法,而不记录分析、不记录结论。Scientific Data中会包含重新利用数据的所有关键信息。

    数据报告记录元数据信息,基于这一点,你可能需要让机器能够抓取它,这有助于其他人找到你的数据,甚至找到相关的其他数据。比如,用Google搜索。怎样让机器能够抓取?

    一种方法是,ISA-Tab格式,当然也有其他格式。主要是用一些词汇让机器能够抓取。

    你也应该在元数据中加入许可条件,你需要让人知道如何可以获得数据,他们需要签署数据使用协议或是其他条件。

    元数据应该包含

    1. 通用的文件格式
    2. 提供单位
    3. 简要的格式信息介绍
    4. 避免使用不常用的缩写
    5. 避免合并单元格
    6. 避免使用特殊字符

    布局

    1. 数据集的名称和参考信息
    2. 数据集的标识符(如果需要)
    3. 数据集描述:解释数据的收集和生成,它的来源、性质、规模和谁可以使用它,是或否发表。

    分享数据

    分享数据的益处:

    • 合作,获得新的发现
    • 更高的知名度
    • 从更多视角审视数据
    • 满足资助机构的要求

    使用分享的数据:

    • 从其他人的数据中获取新视角
    • 可以使用自己很难独立收集的数据集
    • 做数据所有者想象不到的研究

    高频问题

    1. 电子记录本在数据管理上能规避风险吗?是的,电子记录本比纸质材料更能规避风险。使用电子记录,你可以方便的修改格式,用于以后的分析;也可以避免转抄时候出错,电子记录直接复制,不会出现这类错误。
    1. 发表文章时,准备的数据和平时使用的有什么不同?发表文章时,你需要考虑,读者最可能用你的数据做什么?是重复你的结果?或是将你的数据用于其他研究。这就需要你的数据有足够详细的描述,让他们在使用的时候没有障碍。他可能比你在做研究时使用的数据更加详尽一些,因为不是你自己团队的人需要了解数据,而是让你相关专业或者外行人使用数据。
    2. 科学家需要分享研究数据吗?从趋势上来讲,是的。一些基金会、研究资助机构或者一些期刊网站都强制要求这一点。现在的问题是,谁来执行。
    3. 从其他研究者哪里获得数据的最好方法?如果你读的文章里没有将数据放在公共知识库或者附件,那么你可以发邮件给作者,是否可以获取数据。如果长时间未得到回复或者作者拒绝给你数据,你可以向杂志社的编辑求助,一般情况下,编辑能够帮助你。

    小结

    数据并非在你完成发表后被丢弃和遗忘。可能你在做其他研究的时候,还需要再次用到,让然,其他人也是一样。共享你的数据,其他人可以验证你的结果是否正确,可以利用你的数据做一些你自己无法想象的科学研究。

    请尽量

    • 采用数据管理计划
    • 做有持续性的研究
    • 设计有用的数据部署
    • 分享元数据

    Resources

    数据展示原则

    数据通过图形来高效的传达信息。原则是:

    1. 确定你要传达的主要信息是什么。
    2. 用最简单、最清晰的方式呈现数据。
    3. 你准备发表文章的图像时,需要做什么,不需要做什么。
    4. 美学加工。让图片更加简洁美观。

    确定主要信息

    图表的作用很明显,但是很多杂志限制图片数量。因此,你需要考虑,你主要传达的意思,用图片将关键的信息传达出来。

    领域的专家往往知道你所作的试验的全部细节,他们可以提出建设性的意见,让你改进图表。你的图表需要紧紧围绕主题,有些不重要的图可以放在附表。

    你的读者未必是专家,不同的读者知识结构也不尽相同,在制作图表时,要将必要的细节介绍清楚,以便其他的读者能够理解你传达的意思。

    用哪种图呈现数据?

    饼状图、柱状图、箱线图、提琴图等等,都可以反应数据的情况。一般来说,使用国际通用的,或领域常用的方式呈现数据,以便读者可以更容易的理解你想传递的意思。

    图像类型选择的原则是,用最简单的图像尽可能多的包含主要信息。

    一些信息在图片上可能不能直接放映,比如数据差异是否显著,仅仅从误差线上是不能下结论的,因此,必须另外做显著性检测才能确定。

  • Flarum支持中文搜索

    cd /www/wwwroot/lecent.wang
    curl -L https://install.meilisearch.com | sh
    chmod +x meilisearch
    mv meilisearch /usr/local/bin/
    meilisearch --version
    screen -S meili
    meilisearch --master-key="SDT8rEyLVsVpQSHEzVuGhxPGFJz_mIiieuzmXnc2xA4"
    composer require clarkwinkelmann/flarum-ext-scout
    php flarum scout:import "Flarum\User\User"

  • flarum上传插件权限设置

    # 1. 进入您的网站根目录
    cd /www/wwwroot/lecent.wang
    # 2. 将所有文件所有权分配给 Web 服务器用户(宝塔一般是 www 用户)
    chown -R www:www .
    # 3. 给 Flarum 需要写入的两个核心目录赋予 775 权限
    chmod -R 775 storage
    chmod -R 775 public/assets

    宝塔面板,PHP,安装exif

  • 宝塔安装Flarum

    cd /www/wwwroot/lecent.wang
    composer create-project flarum/flarum .
    chown -R www:www /www/wwwroot/lecent.wang
    chmod -R 755 /www/wwwroot/lecent.wang

    设置伪静态

    宝塔 → 网站 → lecent.wang → 设置 → 伪静态

    填入:

    location / {
    try_files $uri $uri/ /index.php?$query_string;
    }

    http://lecent.wang

    完成安装

  • Autorun.inf病毒自救

    Autorun.inf类病毒应该算是一类比较古老的病毒,在U盘最火的那几年非常流行,它主要利用了Windows自动运行对权限管理不严的漏洞。随着网络技术的发展,网络硬盘盛行起来,加上越来越严格的运行权限限制,U盘病毒的影响越来越低。

    比较典型的U盘病毒会隐藏所有文件夹,然后生成一个文件夹图标的exe,双击该exe可以进入到隐藏的文件夹目录,当然也运行了病毒,这样,病毒得以周而复始的传播。

    很多流行的杀毒软件都可以杀掉这类U盘病毒,包括Autorun.inf和可跳转至隐藏文件的exe,但未必会将隐藏的文件恢复。要恢复这些隐藏文件,可以使用专用的U盘病毒杀毒工具,也可以手动修改这些目录的属性。

    手动修改属性方法:

    按WIN+R快捷键运行命令提示符,输入[f:]回车,f:为你的U盘或移动硬盘的盘符。

    列出隐藏文件夹[dir /a:dh /b](可选)

    输入

    for /f "delims=" %D in ('dir /a:dh /b /s') do attrib -h -s "%D"
    

    解读:attrib是设置属性,-s表示去除系统属性,-h表示去除隐藏属性,/s表示子目录做相同处理,delims= 表示 不设置分隔符,也就是整行原样读入,%D 是循环变量,每次循环保存一行输出(一个隐藏文件夹的完整路径)。输入时不用写方括号“[]”。