转:如何不做多任务工作 — 更简单理智地去工作

转帖评论:

没有所谓的多任务处理。
CPU也是一个个任务分时间段单独执行,或者在独立的核心上面分布处理的呢。
人类在多任务处理方面更加无能,就好比无法一边写代码,一边做饭。特别的,两个不同的任务之间切换的时候,人类需要更多的切换时间,想想你刚刚玩了一局精彩的游戏,至少要回味10分钟😄,才能投入到工作吧。

因此减少任务的切换,是一个解决方案。
也就是说,留出大段不受打扰的时间。
写代码是一种需要精力高度集中的工作,有时看上去我的手指都没有动,一行代码都没有写,事实上,我在脑海中搭建巴别塔呢,这时候,如果来一个人问我一个问题,那么,不管我的巴别塔盖了100层还是50层,一会儿我都要重新从地面开始搭建555。所以如果你作为一个程序员,工作的时候常常不受打扰,珍惜这份工作吧。
这也是为什么,我工作的时候常常戴着耳塞,因为这样就把全世界关掉了呀。


出处:微信公众号“聪明人的个人发展”,译者“晓飞”

你同时忙着两个项目,而老板又在桌上放了两个新工作需求。你正打着电话,此时又来了三封电邮。你正试图按时下班,以便在回家路上为晚餐买些食材。你的黑莓手机没电了,而另一个手机也是如此。你的同事过来索要某份信息数据,而你的博客订阅器里还有100多条未读信息。

你如同马戏团里的杂耍人一样,处理着多项任务。祝贺你如此能干,多任务处理者。

在这个即时科技时代,我们都被过量信息和占用个人时间的各种需求所轰炸。这也是GTD管理系统为何在信息世界如此流行的部分原因 — 这个系统的设计目的就是为了做出快速决定,并让个人的所有生活需求保持秩序井然。但即便在使用GTD管理系统,我们有时仍会被各种事情弄得不堪重负,自己的管理系统也开始分崩离析。

Life Hack网站最近发布了一篇《如何处理多任务》的文章,它是篇针对多任务工作的本质,以及如何在多任务工作的同时,仍能一次专注于一项任务的好文章。

但我这篇文章是《如何不做多任务工作》— 指导你为了身心健康,怎样尽可能简单地去工作。

首先,以下是不做多任务工作的简短理由:

  1. 由于人们需要为处理每项新任务转换工作状态,然后又要切换回先前任务的工作状态,多任务工作并不高效。

  2. 多任务工作更加复杂,因此更容易制造压力和犯错。

  3. 多任务工作繁忙疯狂,而且在这个已经显得混乱的世界里,我们需要掌控内心恐惧,找寻一点理智和沉静心态的绿洲。

下面就是如何不做多任务工作的一些建议:

  1. 首先,请根据你的实际情形,为不同的工作内容(即电话、电脑、杂事、家事、等候事项等)设定待做事项列表。

  2. 为即时记录需要完成的事项,准备一种记录工具(比如一本笔记本)。

  3. 准备一个实物收件箱和电邮收件箱(收件箱数量要尽可能少),以使你收到的所有东西都能汇集到一个地方(实物收件箱用于收纳纸张物品,电邮收件箱用于存放数字信息)。

  4. 以时间块的方式计划当天生活,并在中间为紧急事项预留空白时段。取决于哪种做法对自己最适用,你可以尝试一小时长度的时间块,或半小时长度的时间块。或试试这种:40分钟长度的时间块,中间留出20分钟处理各种杂事。

  5. 早晨第一件事,就是处理个人最重要任务(蓝色字体可点击)。在最重要任务完成前,别干其他任何事情。做完首个任务后休息一会儿,再开始完成下一个最重要任务。如果你能在上午做完2-3个最重要任务,当天剩余时间便无比轻松自在。

  6. 在一个时间块内处理某项任务时,请关闭其他所有分心事物。关掉邮箱,如果可以就断掉互联网。关闭手机。假如可能就努力别接电话。专注于那项任务,在不去担心其他任何事情的情况下,努力将它完成。

  7. 若你感到有查看电邮或转换到其他任务上的冲动,请阻止自己。做做深呼吸。重新专注于自我。回到你手头的那项任务。

  8. 如果在工作时收到其他东西,请把它们放进收件箱,或在记录系统里做好笔记。随后回归你的手头任务。

  9. 当完成手头任务后,请不时处理一下你的笔记和收件箱,将新任务添加到待做事项列表,并在需要时重新制定日程安排。按照定期和预先设定的时间间隔处理个人邮件和其他收件箱。

  10. 有时干扰事项会非常紧急,以致于你无法将它推迟到完成手头任务后再做处理。在此情形下,请努力记下当前的工作进度(若有时间请写下进度笔记),把跟那项任务相关的所有文件或笔记内容放在一起(也许可以放进“行动”文件夹或项目文件夹)。然后,当你回归那项任务时,便可拿出对应文件夹,查看先前笔记来确定自己暂停工作的地方。

  11. 请做做深呼吸,伸伸四肢,时不时休息一下。好好享受生活。出门走走,欣赏自然美景。让自己保持理智状态。

Leo Babauta(里奥·巴伯塔)
2007.02.19


感谢Leo公开放弃自己博客内容(zenhabits.net)的版权
阅读原文

Shiny Server インストール手順

Rは、統計に対して、とても便利ですが、ユーザに向けインタフェースはそんなに良くないと思います。
Shiny Serverは、Webサービスのように、ユーザの入力から、Rで計算して、ブラウザで結果を表示するサーバです。
インストール手順は、下記の通りです。
環境は、イントネットに接続できないCentOS 6.5 x64です。

1.ソースからRのインストール

./configure --prefix=/opt/r --enable-R-shlib 
make 
make install 

注意1:PATHは、ファイル「/etc/profile」で設定してください。
注意2:Rは、他のコンポに依頼します。下記のコマンドは、実行することが必要かもしれません。エラーメッセージにより確認してください。

yum install gcc-gfortran 
yum install readline-devel 
yum install libXt-devel 
yum install gcc-c++ glibc-headers

2.Xvfbのインストール

yum install Xvfb

説明:絵の作るのは、デフォルトが X11ですが、いろいろな問題があります。最後、Xvfbになりました。

3.Shinyのインストール

3.1 Shiny libのインストール

Rのコンソールで、Shiny libをインストールします。

install.packages('shiny')

他のlibに依頼しますが、もしサーバは、いんとねっとに接続できないなら、インストールファイルをダウンロードして、サーバに置いて、ファイルからインストールします。
Rのコンソールから、quit()して、下記のようなコマンドを実行します。

R CMD INSTALL XXXXX.tar.gz 
3.2 Shiny Serverのインストール
yum install --nogpgcheck shiny-server-1.4.2.786-rh5-x86_64.rpm 

設定ファイルは、下記のパスです:

/etc/shiny-server/shiny-server.conf 

確認のために、Shiny libの10個の例を、shiny-serverのサーバディレクトリにコピーします。

cp -R /opt/r/lib64/R/library/shiny/examples/* /srv/shiny-server/

上記により、すべてのAPPは、ディレクトリ「/srv/shiny-server/」に置きます。そして、ブラウザでアクセスできます。

http://localhost:3838/目录名

それ以外、起動・停止コマンド:

start shiny-server 
stop shiny-server 
restart shiny-server 
status shiny-server 
reload shiny-server #サービスを中止しないように、更新内容をロード

ホーンページを確認しますか:

http://localhost:3838/index.html

画面を表示できますが、絵は誤りがありそうです。

4.絵の作り

4.1 Xvfbの起動

X11を使ったら、エラー「can’t start PNG device」になります。
そのために、X11の代わりに,Xvfbを使います。
Xvfbを起動します。

Xvfb :3 -screen 1 1280x1024x24
4.1 設定

ui.Rには、下記の内容を追加します。

Sys.setenv(DISPLAY = ":3.1")

例を確認しませんか。

http://localhost:3838/index.html 
http://localhost:3838/01_hello/ 
http://localhost:3838/02_text/
http://localhost:3838/03_reactivity/
http://localhost:3838/04_mpg/
http://localhost:3838/05_sliders/
http://localhost:3838/06_tabsets/
http://localhost:3838/07_widgets/
http://localhost:3838/08_html/
http://localhost:3838/09_upload/
http://localhost:3838/10_download/
http://localhost:3838/11_timer/

参照:https://www.rstudio.com/products/shiny/shiny-server2/
中国語版:http://youngspring1.github.io/post/2016-03-25-shinyserver/

Shiny Server 安装步骤

R语言非常适合用于统计相关的运算。但是在用户交互方面不够友好。
Shiny Server是RStudio推出的,一个Web服务。它可以接受用户的输入,使用R语言进行计算,最终展示计算结果。这一切都在浏览器上完成。
总之非常实用,现在整理下我自己的安装手册。

1.从代码安装R

./configure --prefix=/opt/r --enable-R-shlib 
make 
make install 

注意1:PATH要设置这个文件,否则可能会找不到R :/etc/profile
注意2:可能依赖其他包比如fortran,你可能还需要执行下面这些命令。

yum install gcc-gfortran 
yum install readline-devel 
yum install libXt-devel 
yum install gcc-c++ glibc-headers

2.安装Xvfb

yum install Xvfb

说明:绘制图形好像默认使用 X11,但是安装过程中遇到了各种麻烦,最后尝试了这个,能够正常显示。

3.安装Shiny

3.1 安装Shiny

R命令行下面安装Shiny的包。

install.packages('shiny')

安装过程中,会安装其他依赖的包。
如果你跟我一样,服务器不能连网络,那就必须下载好压缩包,再传到服务器上,从本地安装。
但是不在R命令行下,而是还要quit()退出来。
安装的命令长这样:

R CMD INSTALL XXXXX.tar.gz 
3.2 安装Shiny Server
yum install --nogpgcheck shiny-server-1.4.2.786-rh5-x86_64.rpm 

配置文件路径:

/etc/shiny-server/shiny-server.conf 

你可以把shiny包里面自带的10个例子,都拷贝到shiny-server的目录下去。

cp -R /opt/r/lib64/R/library/shiny/examples/* /srv/shiny-server/

是的,你以后所有的APP都要在/srv/shiny-server/目录下,然后在浏览器中通过这样的方式来访问:

http://localhost:3838/目录名

另外,启动/停止等命令:

start shiny-server 
stop shiny-server 
restart shiny-server 
status shiny-server 
reload shiny-server #不中断服务的前提下 更新加载配置项

这时可以看下首页:

http://localhost:3838/index.html

页面能够显示,但是绘图的部分好像报错了。

4.绘图引擎

4.1 启动Xvfb

各种泪流满面啊,九牛二虎啊。之前安装一堆东西,企图使用X11,会报错:can’t start PNG device。
后来放弃X11,使用Xvfb。需要启动Xvfb。

Xvfb :3 -screen 1 1280x1024x24
4.1 设置

ui.R需要加入下面这一行:

Sys.setenv(DISPLAY = ":3.1")

访问示例试试:

http://localhost:3838/index.html 
http://localhost:3838/01_hello/ 
http://localhost:3838/02_text/
http://localhost:3838/03_reactivity/
http://localhost:3838/04_mpg/
http://localhost:3838/05_sliders/
http://localhost:3838/06_tabsets/
http://localhost:3838/07_widgets/
http://localhost:3838/08_html/
http://localhost:3838/09_upload/
http://localhost:3838/10_download/
http://localhost:3838/11_timer/

参照:https://www.rstudio.com/products/shiny/shiny-server2/
日文版:http://youngspring1.github.io/post/2016-03-26-shinyserver-jp/

data.table 教程3-主键、基于二分法搜索的subset

目录:

  1. data.table 介绍
  2. 语义引用
  3. 主键、基于二分法搜索的subset
  4. 二次索引和自动索引
  5. 数据拆分和合并

原文地址:
data.table/wiki/Getting-started


本教程主要提供给那些已经熟悉data.table的语法、懂得subset行select列、知道如何添加/更新/删除列的人员学习。如果你对这些不熟悉,请学习上面两讲 data.table 介绍语义引用


数据

我们继续使用上一讲中使用的航班信息flights。

flights <- fread("https://raw.githubusercontent.com/wiki/arunsrinivasan/    flights/NYCflights14/flights14.csv")
flights
#         year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
#      1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1
#      2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3
#      3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21
#      4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29
#      5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117
#     ---                                                                                      
# 253312: 2014    10  31     1459         1     1747       -30         0      UA  N23708   1744
# 253313: 2014    10  31      854        -5     1147       -14         0      UA  N33132   1758
# 253314: 2014    10  31     1102        -8     1311        16         0      MQ  N827MQ   3591
# 253315: 2014    10  31     1106        -4     1325        15         0      MQ  N511MQ   3592
# 253316: 2014    10  31      824        -5     1045         1         0      MQ  N813MQ   3599
#         origin dest air_time distance hour min
#      1:    JFK  LAX      359     2475    9  14
#      2:    JFK  LAX      363     2475   11  57
#      3:    JFK  LAX      351     2475   19   2
#      4:    LGA  PBI      157     1035    7  22
#      5:    JFK  LAX      350     2475   13  47
#     ---                                       
# 253312:    LGA  IAH      201     1416   14  59
# 253313:    EWR  IAH      189     1400    8  54
# 253314:    LGA  RDU       83      431   11   2
# 253315:    LGA  DTW       75      502   11   6
# 253316:    LGA  SDF      110      659    8  24
dim(flights)
# [1] 253316     17

介绍

在这一讲,我们会:

* 介绍“主键”的概念,在参数i里面,设置并使用主键进行基于快速二分法搜索的subset。
* 学习如何将基于主键的subset,与参数i和by相结合,就像以前做的一样。
* 学习另外两个有用的参数 mult 和 nomatch
* 最后总结一下主键的优越性:基于快速二分法搜索的subset的表现,并和传统的vector scan approach对比。

1.主键

a) 什么是主键

data.table 介绍里,我们学习了如何在参数i里指定逻辑表达式和行号subset行,以及如何使用 order().在这一讲,我们会学习如何使用主键subset行,而且这难以置信的快。
但首先,我们从data.frame开始。所有的data.frame都有一个行名的属性。看下面这个data.frame DF。

set.seed(1L)
DF = data.frame(ID1 = sample(letters[1:2], 10, TRUE), 
                ID2 = sample(1:3, 10, TRUE),
                val = sample(10), 
                stringsAsFactors = FALSE,
                row.names = sample(LETTERS[1:10]))
DF
#   ID1 ID2 val
# C   a   3   5
# D   a   1   6
# E   b   2   4
# G   a   1   2
# B   b   1  10
# H   a   2   8
# I   b   1   9
# F   b   2   1
# J   a   3   7
# A   b   2   3

rownames(DF)
#  [1] "C" "D" "E" "G" "B" "H" "I" "F" "J" "A"

我们可以用行名来subset一行,就像这样:

DF["C", ]
#   ID1 ID2 val
# C   a   3   5

行名,或多或少,算是一个data.frame的索引。然而,

  1. 每行都有且只有一个行名。
    但是,一个人可能有两个名字,比如名字和中间名。当编纂电话簿的时候,这就非常有用。
  2. 而且行名必须是独一无二的。

    rownames(DF) = sample(LETTERS[1:5], 10, TRUE)

    Warning: non-unique values when setting ‘row.names’: ‘C’, ‘D’

    Error in row.names<-.data.frame(*tmp*, value = value): duplicate ‘row.names’ are not allowed

下面我们来看看data.table吧。

DT = as.data.table(DF)
DT
#     ID1 ID2 val
#  1:   a   3   5
#  2:   a   1   6
#  3:   b   2   4
#  4:   a   1   2
#  5:   b   1  10
#  6:   a   2   8
#  7:   b   1   9
#  8:   b   2   1
#  9:   a   3   7
# 10:   b   2   3

rownames(DT)
#  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

注意:

* 行名被重置了。
* data.table从来不使用行名。既然data.table集成了data.frame,那它还是有行名这个属性的,但是从来不使用。马上我们就知道为什么了。
如果想保持行名,在调用 as.data.table()的时候指定 keep.rownames = TRUE,这回创建一个叫做 rn的列,并且将列名赋值给这一列。

而在data.table里,我们使用主键。主键是更有效的行名。

主键及其特性
* 我们可以对多个列设置主键,这些列可能是不同的类型-integer, numeric, character, factor, integer64等等。但还不支持list和complex。
* 不强制唯一性,也就是说,不同列的主键可以是一样的。既然行可以通过主键排序,那么排序的时候,具有同样主键的一些行,会被排在一起。
* 设置主键这个过程分两步:
  a.根据指定的列,对data.table重新排序,而且总是按升序排列。
  b.对于data.table,通过设置一个叫做 sorted 的属性,来把那些列标记为主键列。
  既然是排序,一个data.table最多只能有一个主键,因为它不能按照两种方法排序。

在教程接下来的部分,我们一直都是用航班信息 flights 来讲解。

b) 设置/获取/使用主键

-如何将 origin列设置为主键

setkey(flights, origin)
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
# 2: 2014     1   1     1655        -5     2003       -17         0      AA  N5CFAA    172    EWR
# 3: 2014     1   1     1611       191     1910       185         0      AA  N471AA    300    EWR
# 4: 2014     1   1     1449        -1     1753        -2         0      AA  N4WNAA    320    EWR
# 5: 2014     1   1      607        -3      905       -10         0      AA  N5DMAA   1205    EWR
# 6: 2014     1   1      949         4     1243       -17         0      AA  N491AA   1223    EWR
#    dest air_time distance hour min
# 1:  LAX      339     2454   18  24
# 2:  MIA      161     1085   16  55
# 3:  DFW      214     1372   16  11
# 4:  DFW      214     1372   14  49
# 5:  MIA      154     1085    6   7
# 6:  DFW      215     1372    9  49

## alternatively we can provide character vectors to the function 'setkeyv()'
# setkeyv(flights, "origin") # useful to program with

说明:

* 你可以给函数setkey() 传入列名作为参数,不需要引号。这在交互式使用的时候特别方便。
* 换一种方式,你可以给函数setkeyv() 传一个字符型的向量,这个向量里保存的是列名。这在把列作为参数传给一个新创建的函数,来设置主键的时候特别方便。
* 注意,我们不需要将结果赋值给一个变量。这是因为,setkey() 和 setkeyv()可以直接更新输入的data.table,就和上一讲中的操作符":="一样。它们没有返回值。
* 现在这个data.table已经按照我们提供的 origin列重新排序了。虽然是重新排序,但我们只需要请求和data.table的行数等长的一列这么大的内存空间。你看,又节省内存开销了。
* 你也可以在创建data.table的时候,调用函数data.table() 的参数 key=,直接设置主键,参数key的值是列名的字符型向量。

注意:

set* and :=:
在data.table里,操作符":="和所有的以set开头函数(比如setkey,setorder,setname等)一样,它们都会更新输入的原数据。

一旦将某一列设置成data.table的主键,就可以在参数i里指定 .()来subset那些主键了。回忆一下,.()就是 list()的别名。

-使用主键origin 来subset所有origin是”JFK”的行

flights[.("JFK")]
#        year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
#     1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
#     2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
#     3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
#     4: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
#     5: 2014     1   1     2133        -2       37       -18         0      AA  N323AA    185    JFK
#    ---                                                                                             
# 81479: 2014    10  31     1705        -4     2024       -21         0      UA  N596UA    512    JFK
# 81480: 2014    10  31     1827        -2     2133       -37         0      UA  N568UA    514    JFK
# 81481: 2014    10  31     1753         0     2039       -33         0      UA  N518UA    535    JFK
# 81482: 2014    10  31      924        -6     1228       -38         0      UA  N512UA    541    JFK
# 81483: 2014    10  31     1124        -6     1408       -38         0      UA  N590UA    703    JFK
#        dest air_time distance hour min
#     1:  LAX      359     2475    9  14
#     2:  LAX      363     2475   11  57
#     3:  LAX      351     2475   19   2
#     4:  LAX      350     2475   13  47
#     5:  LAX      338     2475   21  33
#    ---                                
# 81479:  SFO      337     2586   17   5
# 81480:  SFO      344     2586   18  27
# 81481:  LAX      320     2475   17  53
# 81482:  SFO      343     2586    9  24
# 81483:  LAX      323     2475   11  24

## alternatively
# flights[J("JFK")] (or) flights[list("JFK")]

说明:

* 因为已经将主键设置为 origin列了,所以只要直接指定"JFK"就可以了。这里 .()用来在data.table的主键(也就是flights 的 origin列)里,查找"JFK"。
* 首先,满足"JFK"条件的行的索引都被获取到。然后,这些行的哪些信息是必要的呢。既然参数j里没有指定任何表达式,这些行的所有列都被返回了。
* 如果主键是字符型的列,那么可以省略 .(),就像用行名subset一个data.frame的行的时候。
flights["JFK"]              ## same as flights[.("JFK")]

* 我们可以根据需要指定多个值
flights[c("JFK", "LGA")]    ## same as flights[.(c("JFK", "LGA"))]
这返回所有 origin列是“JFK” 或者 “LGA”的所有行。

-如何获得被设置为data.table的主键的那一列的列名
使用函数 key()。

key(flights)
# [1] "origin"

说明:

* 函数 key() 返回主键列名的字符型向量。
* 如果data.table没有设置过主键,返回 NULL。

c) 主键和多个列

主键是更有效的行名。我们可以将多个列设置为主键,它们可以是不同的类型。
-如何将 origin列 和 dest列 都设置为主键

setkey(flights, origin, dest)
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   2      724        -2      810       -25         0      EV  N11547   4373    EWR
# 2: 2014     1   3     2313        88        9        79         0      EV  N18120   4470    EWR
# 3: 2014     1   4     1526       220     1618       211         0      EV  N11184   4373    EWR
# 4: 2014     1   4      755        35      848        19         0      EV  N14905   4551    EWR
# 5: 2014     1   5      817        47      921        42         0      EV  N19966   4470    EWR
# 6: 2014     1   5     2301        66        2        62         0      EV  N19966   4682    EWR
#    dest air_time distance hour min
# 1:  ALB       30      143    7  24
# 2:  ALB       29      143   23  13
# 3:  ALB       32      143   15  26
# 4:  ALB       32      143    7  55
# 5:  ALB       26      143    8  17
# 6:  ALB       31      143   23   1

## or alternatively
# setkeyv(flights, c("origin", "dest")) # provide a character vector of column names

key(flights)
# [1] "origin" "dest"

说明:

* data.table先按 origin列 排序,再按 dest列 排序。

-subset所有满足条件 origin是”JFK”、dest是”MIA”的行

flights[.("JFK", "MIA")]
#       year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
#    1: 2014     1   1     1509        -1     1828       -17         0      AA  N5FJAA    145    JFK
#    2: 2014     1   1      917         7     1227        -8         0      AA  N5DWAA   1085    JFK
#    3: 2014     1   1     1227         2     1534        -1         0      AA  N635AA   1697    JFK
#    4: 2014     1   1      546         6      853         3         0      AA  N5CGAA   2243    JFK
#    5: 2014     1   1     1736         6     2043       -12         0      AA  N397AA   2351    JFK
#   ---                                                                                             
# 2746: 2014    10  31     1659        -1     1956       -22         0      AA  N5FNAA   2351    JFK
# 2747: 2014    10  31      826        -3     1116       -20         0      AA  N5EYAA   1085    JFK
# 2748: 2014    10  31      647         2      941       -17         0      AA  N5BTAA   1101    JFK
# 2749: 2014    10  31      542        -3      834       -12         0      AA  N3ETAA   2299    JFK
# 2750: 2014    10  31     1944        29     2232         4         0      AA  N5FSAA   2387    JFK
#       dest air_time distance hour min
#    1:  MIA      161     1089   15   9
#     2:  MIA      166     1089    9  17
#    3:  MIA      164     1089   12  27
#    4:  MIA      157     1089    5  46
#    5:  MIA      154     1089   17  36
#   ---                                
# 2746:  MIA      148     1089   16  59
# 2747:  MIA      146     1089    8  26
# 2748:  MIA      150     1089    6  47
# 2749:  MIA      150     1089    5  42
# 2750:  MIA      146     1089   19  44

说明:

这里发生了什么事?
* 理解内部的处理步骤很重要。首先,用"JFK"和第一个主键 origin列匹配;然后,在匹配上的这些行里,用“MIA”和第二个主键 dest列匹配,这样来获取所有符合这两个条件的行的索引。
* 既然我们没有指定参数j,那就会返回所有符合上面索引的行。

-subset所有仅仅满足条件dest是”MIA”的行

flights[.(unique(origin), "MIA")]
#       year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
#    1: 2014     1   1     1655        -5     2003       -17         0      AA  N5CFAA    172    EWR
#    2: 2014     1   1      607        -3      905       -10         0      AA  N5DMAA   1205    EWR
#    3: 2014     1   1     1125        -5     1427        -8         0      AA  N3AGAA   1623    EWR
#    4: 2014     1   1     1533        43     1840        42         0      UA  N491UA    244    EWR
#    5: 2014     1   1     2130        60       29        49         0      UA  N476UA    308    EWR
#   ---                                                                                             
# 9924: 2014    10  31     1348       -11     1658        -8         0      AA  N3AMAA   2283    LGA
# 9925: 2014    10  31      950        -5     1257       -11         0      AA  N3LFAA   2287    LGA
# 9926: 2014    10  31      658        -2     1017        10         0      AA  N3HNAA   2451    LGA
# 9927: 2014    10  31     1913        -2     2212       -16         0      AA  N3LFAA   2455    LGA
# 9928: 2014    10  31     1530         1     1839       -11         0      US  N768US   1715    LGA
#       dest air_time distance hour min
#    1:  MIA      161     1085   16  55
#    2:  MIA      154     1085    6   7
#    3:  MIA      157     1085   11  25
#    4:  MIA      155     1085   15  33
#    5:  MIA      162     1085   21  30
#   ---                                
# 9924:  MIA      157     1096   13  48
# 9925:  MIA      150     1096    9  50
# 9926:  MIA      156     1096    6  58
# 9927:  MIA      156     1096   19  13
# 9928:  MIA      164     1096   15  30

说明:

这里发生了什么事?
* 回忆一下刚刚讲的处理步骤。首先,找到满足第一个主键origin列的条件的行;然后在这个结果中,找到满足第二个主键dest列是“MIA”的行。我们不能简单地事先跳过第一个主键列。因此,我们必须通过主键 origin列,获得它所有可能的取值。
* “MIA”会被自动补足成跟 unique(origin) 同样的长度,也就是3。

2. 和参数j、参数by一起使用

到目前为止,我们学习的都是同样的概念,也就是通过参数i取得行,只是使用了主键这种新的方法。那么同样的,我们在参数j和参数by里面使用主键,也没什么大惊小怪的。我们通过几个例子来说明。

a) 在参数j里面select

-返回符合 origin = “LGA” 和 dest = “TPA”这两个条件的 arr_delay列

key(flights)
# [1] "origin" "dest"
flights[.("LGA", "TPA"), .(arr_delay)]
#       arr_delay
#    1:         1
#    2:        14
#    3:       -17
#    4:        -4
#    5:       -12
#   ---          
# 1848:        39
# 1849:       -24
# 1850:       -12
# 1851:        21
# 1852:       -11

说明:

* 通过基于主键的subset,我们获得了满足 origin == "LGA" 和 dest == “TPA”这两个条件的行索引。
* 现在我们已经获得了这些行的索引,而参数j只请求了 arr_delay列。那么我们简单地从这些行索引中选取 arr_delay列,就像我们在第一讲中做的那样。
* 同以前一样,我们也可以指定 with = FALSE:
flights[.("LGA", "TPA"), "arr_delay", with=FALSE]

b) Chaining表达式

-在上面的基础上,将结果用chaining表达式按降序排列

flights[.("LGA", "TPA"), .(arr_delay)][order(-arr_delay)]
#       arr_delay
#    1:       486
#    2:       380
#    3:       351
#    4:       318
#    5:       300
#   ---          
# 1848:       -40
# 1849:       -43
# 1850:       -46
# 1851:       -48
# 1852:       -49

c) 在参数j里运算

-找出符合 origin = “LGA” 和 dest = “TPA”这两个条件的航班的最大到达延误时间

flights[.("LGA", "TPA"), max(arr_delay)]
# [1] 486

说明:

* 注意一下,这个结果(486),就是b)的结果的第一行的值。

d) 在参数j里使用操作符”:=”来sub-assign

我们已经在第二讲语义引用里学习了几个例子了。现在来看看filghts里的 hours列。

# get all 'hours' in flights
flights[, sort(unique(hour))]
#  [1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

可以看到,hour列有25种不同的取值。但是0点和24点应该是一样的,我们来把24点全部替换成0点。这次我们用主键来做。

setkey(flights, hour)
key(flights)
# [1] "hour"
flights[.(24), hour := 0L]
key(flights)
# NULL

说明:

* 我们首先将 hour列设置为主键。这会将flights按照 hour列重新排序,并且将 hour列标记为主键。
* 现在我们用 .()标记对hour列来subset。我们subset所有值为24的行的索引。
* 对于这些行,我们将主键列的值替换为0.
* 既然我们替换了主键列的值,flights也不再按照 hour列排序了。因此,主键被自动去除了,它被设置为NULL。

现在,flights的hour列里,应该没有24了。

flights[, sort(unique(hour))]
#  [1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

e) 用参数by聚合

我们先将 origin列 和 dest列设置为主键。

setkey(flights, origin, dest)
key(flights)
# [1] "origin" "dest"

-获取每个月从“JFK”起飞的航班的最大起飞延误时间,按月排序

ans <- flights["JFK", max(dep_delay), keyby=month]
head(ans)
#    month   V1
# 1:     1  881
# 2:     2 1014
# 3:     3  920
# 4:     4 1241
# 5:     5  853
# 6:     6  798
key(ans)
# [1] "month"

说明:

* 我们对主键 origin列进行subset,得到了所有起飞机场是“JFK”的行索引。
* 现在我们已经得到这些行的索引了,我们只需要两列-用来分组的month列,和用来计算每组最大值的dep_delay列。data.table的查询都被优化过了,因此在参数i取得的行的基础上,再subset这两列,效率和内存开销都很可观。
* 在subset的时候,我们按month分组,再计算dep_delay列的最大值。
* 我们使用参数keyby来自动将month设置为结果的主键。现在我们理解了为什么叫keyby吧。它使得结果不仅按month列排序,而且将month设置为主键。

3. 另外两个参数mult和nomatch

a) 参数mult

对于每次查询,我们可以通过参数mult,指定所有符合条件的行“all”都被返回,还是只返回第一行“first”或者最后一行“last”。默认是所有的行“all”。
-获取符合origin = “JFK” 且 dest = “MIA”的数据的第一行

flights[.("JFK", "MIA"), mult="first"]
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      546         6      853         3         0      AA  N5CGAA   2243    JFK
#    dest air_time distance hour min
# 1:  MIA      157     1089    5  46

-获取符合origin = “LGA”或”JFK”或”EWR” 且 dest = “XNA”的数据的最后一行

flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult="last"]
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     5  23     1803       163     2003       148         0      MQ  N515MQ   3553    LGA
# 2:   NA    NA  NA       NA        NA       NA        NA        NA      NA      NA     NA    JFK
# 3: 2014     2   3     1208       231     1516       268         0      EV  N14148   4419    EWR
#    dest air_time distance hour min
# 1:  XNA      158     1147   18   3
# 2:  XNA       NA       NA   NA  NA
# 3:  XNA      184     1131   12   8

说明:

* JFK”, “XNA”不匹配flights的任何一条数据,因此返回 NA。
* 再强调一下,参数i里查询语句的第二个主键dest列,"XNA"会被自动补足成跟第一个主键的取值等长,也就是3。

b) 参数nomatch

我们可以通过参数nomatch,指定在没有找到符合条件的数据的情况下,是返回NA呢,还是跳过(不返回)。
-跟前一个例子一样,选取能找到的数据

flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult="last", nomatch = 0L]
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     5  23     1803       163     2003       148         0      MQ  N515MQ   3553    LGA
# 2: 2014     2   3     1208       231     1516       268         0      EV  N14148   4419    EWR
#    dest air_time distance hour min
# 1:  XNA      158     1147   18   3
# 2:  XNA      184     1131   12   8

说明:

* nomatch的默认是是NA。设置 nomatch = 0L 跳过哪些不存在的数据。
* JFK”, “XNA”不匹配flights的任何一条数据,因此就被跳过了。

4. 二分法搜索 vs 向量扫描

到目前为止,我们学习了如何设置和使用主键来subset。但是它的优点是什么呢?举个例子,除了这么做:

# key by origin,dest columns
flights[.("JFK", "MIA")]

我们还可以这样做:

flights[origin == "JFK" & dest == "MIA"]

一个显而易见的优点是,看上去更短。但是它的优点可不只是这个,事实上,基于二分法搜索的subset非常快速。

a) 二分法搜索

为了说明,我们创建一个有两千万行、三列的样本数据,将它的主键设置为x列和y列。

set.seed(2L)
N = 2e7L
DT = data.table(x = sample(letters, N, TRUE), 
                y = sample(1000L, N, TRUE), 
                val=runif(N), key = c("x", "y"))
print(object.size(DT), units="Mb")
# 381.5 Mb

key(DT)
# [1] "x" "y"

DT大约有 380 MB。这不算特别大,但是足够我们体现二分法搜索的优点了。
用第一讲data.table 介绍我们学过的知识,我们可以subset 那些 x = “g” 和 y = 877 的行。

## (1) Usual way of subsetting - vector scan approach
t1 <- system.time(ans1 <- DT[x == "g" & y == 877L])
t1
#    user  system elapsed 
#   0.871   0.022   0.919
head(ans1)
#    x   y       val
# 1: g 877 0.3946652
# 2: g 877 0.9424275
# 3: g 877 0.7068512
# 4: g 877 0.6959935
# 5: g 877 0.9673482
# 6: g 877 0.4842585
dim(ans1)
# [1] 761   3

现在我们用主键来试着做一下。

## (2) Subsetting using keys
t2 <- system.time(ans2 <- DT[.("g", 877L)])
t2
#    user  system elapsed 
#   0.001   0.000   0.002
head(ans2)
#    x   y       val
# 1: g 877 0.3946652
# 2: g 877 0.9424275
# 3: g 877 0.7068512
# 4: g 877 0.6959935
# 5: g 877 0.9673482
# 6: g 877 0.4842585
dim(ans2)
# [1] 761   3

identical(ans1$val, ans2$val)
# [1] TRUE

(2)比(1)快了460倍!

b) 为什么用主键subset能这么快?

为了理解这些,我们先看第一种方法(1)向量扫描。

向量扫描
* 在所有两千条数据中,逐行搜索 x列里值为“g”的行。这会生成一个有两千行的逻辑向量,根据和x列的批评结果,它每个元素的取值可能是TRUE, FALSE 以及 NA。
* 相似的,在所有两千条数据中,逐行搜索 y列里值为“877”的行,再保存在另一个逻辑向量里面。
* 操作符"&"对上面两个逻辑向量进行“且”运算,返回结果为TRUE的行
这就是所谓的“向量扫描”。效率非常低,特别是数据量很大、需要重复subset的时候。因为它每次不得不对整个数据全盘扫描。

现在我们开看看第二种方法(2)二分法搜索。回忆一下前面“a)什么是主键”里的定义,根据主键列重新排序。既然数据被排序了,我们就不需要再对整个数据进行扫描。我们用二分法搜索的时间开销是 O(log n),而向量扫描的时间开销是 O(n),其中n是data.table的行数。

二分法搜索
这里有一个简单的示例。看看下面这组排过序的数字:
1, 5, 10, 19, 22, 23, 30
假设我们希望找到数字1的位置,用二分法搜索(因为这组数字是排过序的),我们是这么做的:
* 从中间的数开始,它是19,不是1,而且 1<19。
* 既然我们要找的数字1小于19,那它应该排在19前面。所以我们可以无视19后面的那一半数据,因为它们都大于19.
* 现在我们的数据只剩下1, 5, 10。再找到中间的数5,它不是1,而且 1<5。
* 现在我们的数据只剩下1。符合条件。这就是我们要找的数。
相反的,向量扫描需要扫描所有的数字,在这个例子中是7。

显而易见的,我们每次搜索的时候,搜索量都是原先的一半。这就是为什么基于二分法搜索的subset是如此的快。
因为data.table的行在内存中是连续存储的,这种subset的操作也很节省缓存,这有利于处理速度。
另外,既然我们不需要创建超大(跟原数据有同样多的行)的逻辑向量,就能取得匹配的行的索引,这种subset也能节省内存。

总结

在这一讲,我们学习了通过设置主键来subset行。设置主键使用了二分法搜索似的subset的操作变得惊人的快。特别的,我们学习了如何:

* 设置主键,并使用主键subset行。
* 更快的在参数i里通过主键subset行的索引。
* 将主键和参数j、参数by一起使用。注意参数j和参数by的使用方法和以前一样。

我们大概不需要用主键来进行聚合的操作,除非数据了极其巨大,使得我们需要重复地做很多次subset,这就会让效果变得很醒目。
然而,当连结两个data.table的时候,设置主键是必要的。这是下一讲的主题。
我们会详细讲解根据主键列来连结两个data.table。

data.table 教程2-语义引用

目录:

  1. data.table 介绍
  2. 语义引用
  3. 主键、基于二分法搜索的subset
  4. 二次索引和自动索引
  5. 数据拆分和合并

原文地址:
data.table/wiki/Getting-started


本教程讨论data.table的语义引用,它允许通过引用来add/update/delete列,然后通过参数i和by结合。它主要给那些熟悉data.table语法、知道如何subset行/select列/分组的人使用。如果你对这些不熟悉,请学习上一讲 data.table 介绍


数据

我们继续使用上一讲中使用的航班信息flights。

flights <- fread("https://raw.githubusercontent.com/wiki/arunsrinivasan/    flights/NYCflights14/flights14.csv")
flights
#         year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
#      1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1
#      2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3
#      3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21
#      4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29
#      5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117
#     ---                                                                                      
# 253312: 2014    10  31     1459         1     1747       -30         0      UA  N23708   1744
# 253313: 2014    10  31      854        -5     1147       -14         0      UA  N33132   1758
# 253314: 2014    10  31     1102        -8     1311        16         0      MQ  N827MQ   3591
# 253315: 2014    10  31     1106        -4     1325        15         0      MQ  N511MQ   3592
# 253316: 2014    10  31      824        -5     1045         1         0      MQ  N813MQ   3599
#         origin dest air_time distance hour min
#      1:    JFK  LAX      359     2475    9  14
#      2:    JFK  LAX      363     2475   11  57
#      3:    JFK  LAX      351     2475   19   2
#      4:    LGA  PBI      157     1035    7  22
#      5:    JFK  LAX      350     2475   13  47
#     ---                                       
# 253312:    LGA  IAH      201     1416   14  59
# 253313:    EWR  IAH      189     1400    8  54
# 253314:    LGA  RDU       83      431   11   2
# 253315:    LGA  DTW       75      502   11   6
# 253316:    LGA  SDF      110      659    8  24
dim(flights)
# [1] 253316     17

介绍

在这一讲,我们会:

* 简要讨论“语义引用”,然后比较操作符“:=”的两种不同的形式。
* 学习如何在参数j里面使用操作符“:=”来add/update/delete列,如何与参数i和by相结合。
* 了解操作符“:=”的副作用,并学习如何用 copy() 来避免这些副作用。

1. 语义引用

到目前为止,我们学习到的所有的操作都会生成一个新的数据集。接下来,我们会学习如何在原来数据集的基础上,添加/更新/删除那些已经存在的列。

a) 背景

在学习语义引用之前,我们先来看下面这个data.frame:

DF = data.frame(ID = c("b","b","b","a","a","c"), a = 1:6, b = 7:12, c=13:18)
DF
#   ID a  b  c
# 1  b 1  7 13
# 2  b 2  8 14
# 3  b 3  9 15
# 4  a 4 10 16
# 5  a 5 11 17
# 6  c 6 12 18

当我们执行下面的命令:

DF$c <- 18:13               # (1) -- replace entire column
# or
DF$c[DF$ID == "b"] <- 15:13 # (2) -- subassign in column 'c'

在R语言V3.1之前的版本里,上面这两种方法都会导致对整个data.frame的深度拷贝[^1]。而且还会拷贝多次[^2]。为了提高效率避免冗余操作,data.tabel使用了操作符”:=”。R里面本来就有定义了这个操作符,但却没有使用[^3]。
[^1]:Speeding up perception
[^2]:Is data really copied four times in R’s replacement functions?
[^3]:Why has data.table defined := rather than overloading <-?

在R语言V3.1之前的版本里,方法(1)只做影子拷贝,处理性能有了很大提升。然而,方法(2)还是会做深度拷贝。这就意味着,对于同样的查询语句,想要选取的列越多,需要做的深度拷贝就越多。

影子拷贝 vs 深度拷贝
影子拷贝,只是一份指向列的指针向量的拷贝,它会随着data.frame或者data.table的变化而变化。但在内存里,数据不是真的被复制了。   
深度拷贝,正相反,它会复制整个数据,并且保存在内存里。

如果使用操作符”:=”,不管在R语言的什么版本里,不管是方法(1)还是方法(2),都不会再拷贝。这是因为,操作符”:=”通过引用更新列。

b) 操作符“:=”

在参数j中,操作符“:=”有两种使用方法:
a.左右等式的形式

DT[, c("colA", "colB", ...) := list(valA, valB, ...)]

# when you have only one column to assign to you 
# can drop the quotes and list(), for convenience
DT[, colA := valA]

b.函数形式

DT[, `:=`(colA = valA, # valA is assigned to colA
      colB = valB, # valB is assigned to colB
      ...
)]

注意:

上面的两个例子只是用来说明使用的形式,并不是实际可以运行的代码示例。我们会在下一节中,用航班信息flight的data.table来举例说明。

说明:

* 形式(a)比较容易编码,特别是,事先不知道需要被赋值的列的时候。
* 相对而言,形式(b)更加趁手,如果你愿意追加点注释😄。
* 操作符“:=”没有返回值。
* 既然参数j里面可以使用操作符“:=”,那么,就像上一讲中学习到的内容,我们可以和参数i和参数by一起,做些聚合的运算。

在上面两种形式里,注意我们没有把运算的结果赋值给一个变量。因为完全没必要。我们直接更新data.table。让我们通过一些例子来说明。
在接下来的教程里,我们对航班信息flight,这个data.table来示例。

2. 添加/更新/删除列

a) 添加列

-如何对每次航班,添加 speed 和 total delay 两列

flights[, `:=`(speed = distance / (air_time/60), # speed in km/hr
           delay = arr_delay + dep_delay)]       # delay in minutes
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min    speed delay
# 1:  LAX      359     2475    9  14 413.6490    27
# 2:  LAX      363     2475   11  57 409.0909    10
# 3:  LAX      351     2475   19   2 423.0769    11
# 4:  PBI      157     1035    7  22 395.5414   -34
# 5:  LAX      350     2475   13  47 424.2857     3
# 6:  LAX      339     2454   18  24 434.3363     4

## alternatively, using the 'LHS := RHS' form
# flights[, c("speed", "delay") := list(distance/(air_time/60), arr_delay + dep_delay)]

注意:

* 我们不需要将结果赋值给 flights。
* flights 现在包含了刚刚追加的两列。这就是我们说的“添加列”。
* 我们用函数形式,这样就可以在旁边写注释了。当然也可以用等式的形式。

b) 更新列(sub-assign)

现在留意一下 fligths 里的 hour列。

# get all 'hours' in flights
flights[, sort(unique(hour))]
#  [1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

可以看到,hour列有25种不同的取值。但是0点和24点应该是一样的,我们来把24点全部替换成0点。
-将 hour=24 替换成0

# subassign by reference
flights[hour == 24L, hour := 0L]

说明:

* 就像在上一讲中学习的一样,我们可以使用参数i 和 参数j里的操作符“:=”一起使用。
* 只有满足了参数i 中指定的条件 hour == 24L 的那些列,它们的值会被替换成0。
* 操作符“:=”没有返回值。有时候需要查看运行的结果,我们可以在查询语句的最后加一对方括号[],来达到这个目的。
flights[hour == 24L, hour := 0L][]
#         year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
#      1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1
#      2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3
#      3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21
#      4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29
#      5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117
#     ---                                                                                      
# 253312: 2014    10  31     1459         1     1747       -30         0      UA  N23708   1744
# 253313: 2014    10  31      854        -5     1147       -14         0      UA  N33132   1758
# 253314: 2014    10  31     1102        -8     1311        16         0      MQ  N827MQ   3591
# 253315: 2014    10  31     1106        -4     1325        15         0      MQ  N511MQ   3592
# 253316: 2014    10  31      824        -5     1045         1         0      MQ  N813MQ   3599
#         origin dest air_time distance hour min    speed delay
#      1:    JFK  LAX      359     2475    9  14 413.6490    27
#      2:    JFK  LAX      363     2475   11  57 409.0909    10
#      3:    JFK  LAX      351     2475   19   2 423.0769    11
#      4:    LGA  PBI      157     1035    7  22 395.5414   -34
#      5:    JFK  LAX      350     2475   13  47 424.2857     3
#     ---                                                      
# 253312:    LGA  IAH      201     1416   14  59 422.6866   -29
# 253313:    EWR  IAH      189     1400    8  54 444.4444   -19
# 253314:    LGA  RDU       83      431   11   2 311.5663     8
# 253315:    LGA  DTW       75      502   11   6 401.6000    11
# 253316:    LGA  SDF      110      659    8  24 359.4545    -4

现在我们再来看下 hour列。

# check again for '24'
flights[, sort(unique(hour))]
#  [1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

c) 删除列

-删除 delay列

flights[, c("delay") := NULL]
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min    speed
# 1:  LAX      359     2475    9  14 413.6490
# 2:  LAX      363     2475   11  57 409.0909
# 3:  LAX      351     2475   19   2 423.0769
# 4:  PBI      157     1035    7  22 395.5414
# 5:  LAX      350     2475   13  47 424.2857
# 6:  LAX      339     2454   18  24 434.3363

## or using the functional form
# flights[, `:=`(delay = NULL)]

说明:

* 将一列赋值为 NULL,就会删除那一列。删除立即生效。
* 使用左右等式的形式的时候,除了指定列名,我们也可以指定列号。但还是忘记吧,指定列名是个好的编码习惯。
* 为了方便,如果只需要删除一列,可以去掉 c(""),只指定列名,像这样:
flights[, delay := NULL]
这和上面的方法是等效的。

d) “:=”和分组

我们已经在b)里面学习了如何跟参数i 一起使用,现在我们来学习如何跟参数by 一起使用。
-如何追加一列,用来保存某对起飞/到达机场间的最快飞行速度

flights[, max_speed := max(speed), by=.(origin, dest)]
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min    speed max_speed
# 1:  LAX      359     2475    9  14 413.6490  526.5957
# 2:  LAX      363     2475   11  57 409.0909  526.5957
# 3:  LAX      351     2475   19   2 423.0769  526.5957
# 4:  PBI      157     1035    7  22 395.5414  517.5000
# 5:  LAX      350     2475   13  47 424.2857  526.5957
# 6:  LAX      339     2454   18  24 434.3363  518.4507

说明:

* 我们用操作符“:=”追加了一列 max_speed。
* 和上一讲学习到的内容一样,我们将所有数据进行分组。对于每组数据,计算最快速度。对于一对机场,这个最快速度是唯一的。循环复制这个值到一个list,直到跟该组数据的行数一样多。航班信息flights会被就地更新,不会因拷贝浪费内存空间。
* 和上一讲学习到的内容一样,我们也可以对参数by指定一个字符型的向量,形式是这样:
by = c("origin", "dest")

e) “:=”和复数列

-如何再追加两列,用于保存每个月的最大起飞延误时间dep_delay 和到达延误时间arr_delay
小提示:要用到上一讲学习到的 .SD

in_cols  = c("dep_delay", "arr_delay")
out_cols = c("max_dep_delay", "max_arr_delay")
flights[, c(out_cols) := lapply(.SD, max), by = month, .SDcols = in_cols]
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min    speed max_speed max_dep_delay max_arr_delay
# 1:  LAX      359     2475    9  14 413.6490  526.5957           973           996
# 2:  LAX      363     2475   11  57 409.0909  526.5957           973           996
# 3:  LAX      351     2475   19   2 423.0769  526.5957           973           996
# 4:  PBI      157     1035    7  22 395.5414  517.5000           973           996
# 5:  LAX      350     2475   13  47 424.2857  526.5957           973           996
# 6:  LAX      339     2454   18  24 434.3363  518.4507           973           996

说明:

* 为了更好的可读性,我们使用了左右等式的形式。我们事先保存了输入的列名到变量in_cols,作为 .SDcols的参数。我们还事先保存了输出的列名到变量out_cols,作为左边的表达式。
* 注意一下,我们在c)里面讲过,如果只需要追加一列,那么可以省略双引号,只指定列名。但是这里我们需要指定 c(out_cols) 或者 (out_cols)。 
* 左右等式的形式,允许我们操作复数的列。在右边的表达式里,为了对指定在 .SDcols 里的列计算最大值,我们使用了R的基础函数 lapply()。这些我们在上一讲中都学习过了。它返回有两个元素的list,包含每组的 dep_delay 和 arr_delay 这两列的最大值。

在进行下一节的学习之前,让我们删除刚刚追加的几列:speed, max_speed, max_dep_delay 和 max_arr_delay。

# RHS gets automatically recycled to length of LHS
flights[, c("speed", "max_speed", "max_dep_delay", "max_arr_delay") := NULL]
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min
# 1:  LAX      359     2475    9  14
# 2:  LAX      363     2475   11  57
# 3:  LAX      351     2475   19   2
# 4:  PBI      157     1035    7  22
# 5:  LAX      350     2475   13  47
# 6:  LAX      339     2454   18  24

3. “:=”和copy()

操作符“:=”会更新原数据。和我们之前学过的功能不同,有时候,我们希望更新原数据。但有时候,我们不想更新原数据,这种情况下,我们可以用函数 copy()。

a) “:=”的副作用

如果我们想创建一个函数,用于返回每个月的最快速度。但是此时,我们也想对 flights 追加一列 speed。可以像下面这样做:

foo <- function(DT) {
  DT[, speed := distance / (air_time/60)]
  DT[, .(max_speed = max(speed)), by=month]
}
ans = foo(flights)
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min    speed
# 1:  LAX      359     2475    9  14 413.6490
# 2:  LAX      363     2475   11  57 409.0909
# 3:  LAX      351     2475   19   2 423.0769
# 4:  PBI      157     1035    7  22 395.5414
# 5:  LAX      350     2475   13  47 424.2857
# 6:  LAX      339     2454   18  24 434.3363
head(ans)
#    month max_speed
# 1:     1  535.6425
# 2:     2  535.6425
# 3:     3  549.0756
# 4:     4  585.6000
# 5:     5  544.2857
# 6:     6  608.5714

说明:

* 注意一个新的列 speed 被追加到 flight 里了。这时因为我们用了操作符“:=”。既然 DT 和flights都指向内存中同一个对象,对 DT 的操作,也会对 flights 生效。
* 返回值 ans 包含了每月的最快速度。

b) 函数copy()

在前面一节,我们利用了操作符“:=”的副作用来更新原数据。但是不会一直希望这样又是,我们希望给函数传递data.table参数,使用操作符“:=”的功能,但是不想改变原数据。我们可以用函数 copy() 来做到这一点。

函数 copy() 对输入参数进行深度拷贝,因此对副本做的所有更新操作,都不会对原数据生效。

函数 copy() 有两个不可或缺的特点:
1.和前一节内容的情形相反,我们可能不希望传递的参数被修改。举个例子,考虑前一节中,我们不想修改 flights的内容。
我们先删掉前一节中,追加的 speed列:

flights[, speed := NULL]   

现在,我们可以像下面这样做:

foo <- function(DT) {
  DT <- copy(DT)                             ## deep copy
  DT[, speed := distance / (air_time/60)]    ## doesn't affect 'flights'
  DT[, .(max_speed = max(speed)), by=month]
}
ans <- foo(flights)
head(flights)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21    JFK
# 4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29    LGA
# 5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117    JFK
# 6: 2014     1   1     1824         4     2145         0         0      AA  N3DEAA    119    EWR
#    dest air_time distance hour min
# 1:  LAX      359     2475    9  14
# 2:  LAX      363     2475   11  57
# 3:  LAX      351     2475   19   2
# 4:  PBI      157     1035    7  22
# 5:  LAX      350     2475   13  47
# 6:  LAX      339     2454   18  24
head(ans)
#    month max_speed
# 1:     1  535.6425
# 2:     2  535.6425
# 3:     3  549.0756
# 4:     4  585.6000
# 5:     5  544.2857
# 6:     6  608.5714

说明:

* 使用函数 copy() 不会更新 flights。它现在不包含 speed列。
* 返回值 ans 包含了每月的最快速度。
然而,我们可以使用影子拷贝来代替深度拷贝,来大幅度提高这个操作的效率。事实上,我们希望在 Data.Table的V1.9.8的版本里提供这个功能。我们会在data.table的设计里面继续讨论这个内容。

Data.Table V1.9.8 相关资料:
Copy-on-:= at column level, DT[,list(…)] shallow copy and add cols to shallow(DT, cols)

2.当我们将列名保存在变量里的时候,比如:DT_n = names(DT),然后再对 DT 添加/更新/删除列,操作符“:=”也会更新变量 DT_n,除非我们运行 copy(names(DT))。

DT = data.table(x=1, y=2)
DT_n = names(DT)
DT_n
# [1] "x" "y"

## add a new column by reference
DT[, z := 3]

## DT_n also gets updated
DT_n
# [1] "x" "y" "z"

## use `copy()`
DT_n = copy(names(DT))
DT[, w := 4]

## DT_n doesn't get updated
DT_n
# [1] "x" "y" "z"

总结

操作符“:=”
* 操作符“:=”用于添加/更新/删除列。
* 我们也学习了如何跟参数i和参数by一起使用,就像在第一讲中学习的那样。同样,我们也可以使用 keyby,可以用方括号 [] 将操作连结起来,可以给参数by 指定表达式。
* 我们可以利用操作符“:=”更新原数据,也可以用函数 copy() 来避免更新原数据。

到目前为止,我们学习了好多参数j相关的知识,知道了参数i、参数j和参数by如何一起使用。下一讲主键、基于二分法搜索的subset,我们将注意力回到参数i上,来做一些通过主键的超快速的排序。

单车一周旅行计划

D1:南京–溧阳市 110km

很容易

D2:溧阳市–安吉县 115km

容易

D3:安吉县–太阳镇 105km

如果走天荒坪,爬山,难

如果绕过西天目,中等

D4:太阳镇–岛石镇 103km

翻山,华浪线,难

D5:岛石镇–十字镇 130km

走南极,宁国,中等

(可以考虑走荆州公路提高难度)

D6:十字镇–南京 137km

容易

转:某T的职级系统

原文链接:http://www.cyzone.cn/a/20140828/262150.html   

腾讯的职级系统有26个职业通道,如果你是一个一张白纸只有素质没有任何职业能力的毕业生,可以从这个26个通道,比如行政、财务、设计、运维、开发、运营、产品…….的任何一个1-1级开始,修炼,打怪升级,直到千万年薪。如同一个完整的人生指引。

横轴是26个职业通道,专业技能各不相同,纵轴是4个大层级。

以下是按照我的理解来写了。(直接抄原文会被企鹅摔着打的)。

我觉得腾讯的职业四大层级,几乎就是人生发展的四大层级。

第一层是动作执行层、第二层是任务执行层、第三层是战略管理层、第四层是战略决策层。

先说动作执行层。一个企业最多的就是这个层面的员工。或者每个人初入职场,都是从练好一个动作开始。比如,画原型,写代码,写稿子……

而腾讯对动作执行层的要求是:按照品质要求,完成动作、优化效率。注意,没有品质要求的动作,毫无意义。用户体验的不是产品而是品质感。就像你去一个川餐馆吃鱼香肉丝,你感受到的是这家鱼香肉丝的品质。大厨的工作不单是保证把菜炒出来,更是要保证菜品在一个什么样的品质感里。所以麦当劳的桌子永远擦的干净,同样的人在另外一个餐馆未必达到这种清洁标准。因为麦当劳不是要求把桌子擦了,而是清晰地要求达到什么样的清洁品质。

达成品质要求之后,在谈完成动作与优化效率。

任务执行层。就是要把分配的任务及指标,拆解成动作。由不同人组合完成,或者一个团队次序完成。需要在整个过程中,控制人心,安排动作序列,并配置风险,保证完成任务,达成指标。几乎所有铁血创业者都是从这个层级冒出来的。

战略管理层。就是大家永远不理解的那批副总。带兄弟痛快淋漓干活的都是总监。而副总,心累。他们需要根据战略决策,确定任务优先级,配置资源,鼓舞士气。保证战略方向不偏差。

而最高的战略决策层,几乎就是个CEO的活。他需要有前瞻,推动相关资源方做出战略决策,并且获取战略资源。就像亮剑里的李云龙。在除了自己,什么都没有的情况,他可以沟通,说服。一个队伍打没了,马上再拉起一个队伍。只要他还要打下去。

四个层级的核心工作不同,对人的特性的核心需求也不同。

在动作执行层,才气很重要。

在任务执行层,责任心,执行力的价值,远大于才气。甚至需要放弃自己的才气,把时间交给众多的兄弟,才能实现任务的完成。

而一个人能否达到战略管理层,核心要考核的是:心力。心力是什么?就是无止无尽操心的能力。资源永远有限,战略常常在变,兄弟都是亲的,永远没人满意。所以,一堆人拉我去当副总,我都谢谢了。因为自己清楚,心力真心不足啊~

战略决策层。愿力。其实我在《决策》那篇文章中谈过愿力的问题。没有愿景支撑的决策都是机会主义。一个人如果心中没有愿,那真是谁都帮不了他。看上去再大都是纸老虎。

所以,人会在哪个层级呆着,度过一生,其实都是因为吃不了其他层级的苦。其实发展个人才气,在动作层呆着,是人生最舒适的选择。

不过那些以才子自居的人,创业往往格外困难,因为在整个创业的战略确定到达成的过程里,最不值钱的,也就是才气。

演化

自古登堡以来,书籍因为其“不变”的特质,象征着稳定、专注、权威;而电子时代,amazon可以很方便地更新我手中kindle中的内容。
这是一个一直在变化的时代,我们也不用等待准备好了,才去做某件事情。
我们一直处在演化的过程中。

data.table 教程1-data.table 介绍

最近使用data.table这个增强包,来计算数据的聚合信息,比sql语句简单明了不是一点半点,于是想把data.table的资料翻译出来。

目录:

  1. data.table 介绍
  2. 语义引用
  3. 主键、基于二分法搜索的subset
  4. 二次索引和自动索引
  5. 数据拆分和合并

原文地址:
data.table/wiki/Getting-started


注:subset特指对列的选择,select特指对行的选择。这两个单词在中文里没有确切并且简洁的词,所以直接使用不作翻译。后面有类似用原文的也同样直接使用英文。

本教程介绍data.table的语法,大概的形式,如何对行进行select,如何对列进行subset和compute,如何分组聚合。熟悉data.frame的数据结构是有帮助的,不过不熟悉也没关系。


使用data.table分析数据

操作数据的功能,例如subset、group、update、join等都有内在的关联。
保持这些关联的操作可以:

* 通过简洁一致的语法,实行想要的操作,达到目的。
* 从一系列函数到最终的分析,都没有将所有的操作都对应到函数的负担。能够流畅地执行分析。
* 精确地知道每步操作所需要的数据,内部自动优化操作,在运行速度和内存开销两方面都很有效果。

简要的讲,如果你对减小计算复杂度和计算时间有着迫切的需求,那么这个package就是为你量身打造的。data.table被设计出来就是为了完成这样的事情。我们通过这一系列教程,说明这些功能。

数据

在这个教程中,我们使用NYC-flights14的数据。它包含了2014年纽约机场发出的所有航班信息。这份数据只有2014年1月到10月是公开的。
我们可以使用data.table的fread()函数,用下面的方式,快速直接读取航班数据:

flights <- fread("https://raw.githubusercontent.com/wiki/arunsrinivasan/    flights/NYCflights14/flights14.csv")
flights
#         year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
#      1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1
#      2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3
#      3: 2014     1   1     1902         2     2224         9         0      AA  N327AA     21
#      4: 2014     1   1      722        -8     1014       -26         0      AA  N3EHAA     29
#      5: 2014     1   1     1347         2     1706         1         0      AA  N319AA    117
#     ---                                                                                      
# 253312: 2014    10  31     1459         1     1747       -30         0      UA  N23708   1744
# 253313: 2014    10  31      854        -5     1147       -14         0      UA  N33132   1758
# 253314: 2014    10  31     1102        -8     1311        16         0      MQ  N827MQ   3591
# 253315: 2014    10  31     1106        -4     1325        15         0      MQ  N511MQ   3592
# 253316: 2014    10  31      824        -5     1045         1         0      MQ  N813MQ   3599
#         origin dest air_time distance hour min
#      1:    JFK  LAX      359     2475    9  14
#      2:    JFK  LAX      363     2475   11  57
#      3:    JFK  LAX      351     2475   19   2
#      4:    LGA  PBI      157     1035    7  22
#      5:    JFK  LAX      350     2475   13  47
#     ---                                       
# 253312:    LGA  IAH      201     1416   14  59
# 253313:    EWR  IAH      189     1400    8  54
# 253314:    LGA  RDU       83      431   11   2
# 253315:    LGA  DTW       75      502   11   6
# 253316:    LGA  SDF      110      659    8  24
dim(flights)
# [1] 253316     17

既然整个教程我们都会使用这份数据,那你不妨先下载到你的电脑上,然后每次使用的时候再读取。

介绍

在本章中,我们会学习下面两点:

  1. 基础 - 什么是data.table,它的形式,如何subset行,如何select列,如何按列进行运算。
  2. 聚合 - 按组聚合的效果。

1.基础

a) 什么是data.table

data.table是R语言的一个包,它是对data.frames的增强。在上文(读取航班)“数据”的部分,我们通过函数fread()创建了一个data.table。我们也可以通过函数data.table()创建一个data.table,比如这样:

DT = data.table(ID = c("b","b","b","a","a","c"), a = 1:6, b = 7:12, c=13:18)
DT
#    ID a  b  c
# 1:  b 1  7 13
# 2:  b 2  8 14
# 3:  b 3  9 15
# 4:  a 4 10 16
# 5:  a 5 11 17
# 6:  c 6 12 18
class(DT$ID)
# [1] "character"

我们也可以通过as.data.table()将已经存在的对象转化成data.table。

注意:

  • 不同于data.frames,字符型的列,不会被自动转化成因子。
  • 行号后面有个冒号,用于隔开第一列的内容。
  • 如果数据的条目超过了全局选项datatable.print.nrows所定义的数值(默认是100条),那么只会输出数据最开头和最末尾的5行。就如同上文(读取航班)“数据”的部分。

    1
    ```# [1] 100
  • data.table不能设置行的名称。我们会在第三讲中说明原因。

b) 形式-data.table增强了什么

和data.frame相反,我们能做的可不仅仅局限于subset行或者select列。首先介绍下data.table的语法,如下所示:

DT[i, j, by]

##   R:      i                 j        by
## SQL:  where   select | update  group by

如果你有SQL语句的基础,那么你应该能马上明白data.table的语法。

语法是:
对于DT这个data.table,使用 i 来subset行,然后计算 j ,最后用 by 分组。

c) subset行

- 获取六月份所有从”JFK”机场起飞的航班

ans <- flights[origin == "JFK" & month == 6L]
head(ans)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     6   1      851        -9     1205        -5         0      AA  N787AA      1    JFK
# 2: 2014     6   1     1220       -10     1522       -13         0      AA  N795AA      3    JFK
# 3: 2014     6   1      718        18     1014        -1         0      AA  N784AA      9    JFK
# 4: 2014     6   1     1024        -6     1314       -16         0      AA  N791AA     19    JFK
# 5: 2014     6   1     1841        -4     2125       -45         0      AA  N790AA     21    JFK
# 6: 2014     6   1     1454        -6     1757       -23         0      AA  N785AA    117    JFK
#    dest air_time distance hour min
# 1:  LAX      324     2475    8  51
# 2:  LAX      329     2475   12  20
# 3:  LAX      326     2475    7  18
# 4:  LAX      320     2475   10  24
# 5:  LAX      326     2475   18  41
# 6:  LAX      329     2475   14  54

说明:

* 通过data.frame的frame?,列可以像变量一样被引用。因此,我们不需要加上 flights$ 前缀,比如 flights$dest 和 flights$month,而是直接简单地引用 dest 和 month这两列。
* 满足 origin == "JFK" & month == 6L 这两个条件的行会被抽出出来。既然我们没有指定其他的条件,一个包含原数据里面所有列的data.table会被返回。
* 语法里面[i,j,k]的逗号不是必须的,当然如果指定了逗号,比如 flights[dest == "JFK" & month == 6L, ] 也是没问题的。但在data.frame里面,逗号却是必须的。

- 获取 flights 开头的两行

ans <- flights[1:2]
ans
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2: 2014     1   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
#    dest air_time distance hour min
# 1:  LAX      359     2475    9  14
# 2:  LAX      363     2475   11  57

说明:

* 我们没有指定任何条件。行的索引已经自动提供给参数 i 了。因此,我们得到一个包含原数据 flight 里所有列的data.table(for 这些行的索引?)。

- 排序(先按 origin列 的升序,再按 dest 的降序排列)
我们可以通过R语言的基础函数 order() 来完成这个功能。

ans <- flights[order(origin, -dest)]
head(ans)
#    year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014     1   5      836         6     1151        49         0      EV  N12175   4419    EWR
# 2: 2014     1   6      833         7     1111        13         0      EV  N24128   4419    EWR
# 3: 2014     1   7      811        -6     1035       -13         0      EV  N12142   4419    EWR
# 4: 2014     1   8      810        -7     1036       -12         0      EV  N11193   4419    EWR
# 5: 2014     1   9      833        16     1055         7         0      EV  N14198   4419    EWR
# 6: 2014     1  13      923        66     1154        66         0      EV  N12157   4419    EWR
#    dest air_time distance hour min
# 1:  XNA      195     1131    8  36
# 2:  XNA      190     1131    8  33
# 3:  XNA      179     1131    8  11
# 4:  XNA      184     1131    8  10
# 5:  XNA      181     1131    8  33
# 6:  XNA      188     1131    9  23

说明:

内置的 order() 函数
* 我们可以对一个字符型的列,使用减号“-”,来实现降序排列。
* 另外,函数 order() 其实调用了data.table的快速基数排序函数 forder(),它比 base::order 快很多。这是一个说明它们基本区别的例子:

odt = data.table(col=sample(1e7))
(t1 <- system.time(ans1 <- odt[base::order(col)]))  ## uses order from base R
#    user  system elapsed 
#   8.610   0.056   8.708
(t2 <- system.time(ans2 <- odt[order(col)]))        ## uses data.table's forder
#    user  system elapsed 
#   0.526   0.024   0.553
(identical(ans1, ans2))
# [1] TRUE

order() 比 base::order 大约快了16倍。我们会在data.table的内部教程中讨论data.table快速排序的更多细节。
* 因此,使用我们熟悉的函数,就可以显著地提高分析效率。

d) select列

- 选取 arr_delay 列,返回值是向量

ans <- flights[, arr_delay]
head(ans)
# [1]  13  13   9 -26   1   0

说明:

* 既然列可以作为变量被引用,我们可以直接引用我们想选取的列。
* 既然我们想选取所有的行,我们毋需指定参数 i。
* 返回了所有行的 arr_delay 列。

- 选取 arr_delay 列,返回值是data.table

ans <- flights[, list(arr_delay)]
head(ans)
#    arr_delay
# 1:        13
# 2:        13
# 3:         9
# 4:       -26
# 5:         1
# 6:         0

说明:

* 我们用 list() 把列名 arr_delay 包围起来,它可以确保返回值是data.table。正如前面一个例子,如果不这样做,返回值就是一个向量。
* data.table也允许用 .() 来包围列名,它是 list() 的别名,它们的效果是同样的。教程后面会使用 .() 来说明。

注意:

只要参数 j 返回一个list,这个list的每一个元素都会被转换成结果data.table的一列。你马上就会发现,这个功能是多么强大。

- 选取 arr_delay 列和 dep_delay 列

ans <- flights[, .(arr_delay, dep_delay)]
head(ans)
#    arr_delay dep_delay
# 1:        13        14
# 2:        13        -3
# 3:         9         2
# 4:       -26        -8
# 5:         1         2
# 6:         0         4

## alternatively
# ans <- flights[, list(arr_delay, dep_delay)]

说明:

* 使用 .() 或者 list() 都可以。

- 选取 arr_delay 列和 dep_delay 列,并把列名改为 delay_arr 和 delay_dep
既然 .() 是 list() 的别名,那么我们可以在创建 list 的时候对列命名。

ans <- flights[, .(delay_arr = arr_delay, delay_dep = dep_delay)]
head(ans)
#    delay_arr delay_dep
# 1:        13        14
# 2:        13        -3
# 3:         9         2
# 4:       -26        -8
# 5:         1         2
# 6:         0         4

就是这样。

e) 在参数j里运算

- 有多少航班完全没有延误

ans <- flights[, sum((arr_delay + dep_delay) < 0)]
ans
# [1] 141814

说明:

刚刚发生了什么?
* 参数 j 能做的,可不只是选取列这么简单,它能处理表达式,比如对列进行计算。这没什么大惊小怪的,因为列可以作为变量被引用嘛。所以,我们可以对这些变量调用函数。我们刚刚就是对两列求和(sum)了。

f) 在参数i里选取,在参数j里运算

- 在六月份,从”JFK”机场起飞的航班中,计算起飞和到达的平均延误时间

ans <- flights[origin == "JFK" & month == 6L, 
           .(m_arr=mean(arr_delay), m_dep=mean(dep_delay))]
ans
#       m_arr    m_dep
# 1: 5.839349 9.807884

说明:

* 我们首先在i参数里,找到所有符合 origin (机场)是"JFK",并且 month (月份)是 6 这样条件的行。此时,我们还没有subset整个data.table。
* 现在,我们看看参数j,它只使用了两列。我们需要分别计算这两列的平均值 mean()。这个时候,我们才subset那些符合i参数里条件的列,然后计算它们的平均值。
因为这三个参数(i,j和by)都被指定在同一个方括号中,data.table能同时接受这三个参数,并在计算之前,选取最优的计算方法,而不是分步骤计算。所以,我们可以避免对整个data.table计算,同时,在计算速度和内存使用量这两方面,取得最优的效果。

- 在六月份,从”JFK”机场起飞的航班一共有多少

ans <- flights[origin == "JFK" & month == 6L, length(dest)]
ans
# [1] 8422

函数 length() 需要一个参数。我们只需要知道,结果里有多少行数据。我们可以使用任何一列作为函数 length() 的参数。
这一类的操作特别频繁,特别是在下一节里,当我们需要分组的时候,会讲到这个特别的符号 .N。

特别的符号 .N 
.N 是一个内建的变量,它表示当前的分组中,对象的数目。在下一节,当它和 by 一起使用的时候,我们会发现它特别有用。还没有涉及到分组的时候,它只是简单地返回行的数目。

所以,我们可以用 .N 来完成这个任务:

ans <- flights[origin == "JFK" & month == 6L, .N]
ans
# [1] 8422

说明:

* 再说一遍,首先在i参数里,找到所有符合 origin (机场)是"JFK",并且 month (月份)是 6 这样条件的行。   
* 在参数j里,我们只指定了 .N,其他什么也没指定。所以实际上我们什么也没做。我们只是返回了符合条件的行的数目(就是行的 length长度)。   
* 注意,我们没有用 list() 或者 .() 包围 .N,所以返回值是个向量。
我们也可以这样完成这个任务 nrow(flights[origin == "JFK" & month == 6L])。但是,这会从整个data.table里面subset符合条件的行,然后用 nrow() 返回行的数目,这是没有必要的,而且效率低下。我们会在 data.table的设计 这个教程里面说明这一点和其他的优化方法。

g) 太棒了!但我应该如何用参数j里面的名字引用列(就像在data.frame那样)

你可以使用 with = FALSE 来引用列名。
- 用data.frame的方式,选取 arr_delay 和 dep_delay 两列

ans <- flights[, c("arr_delay", "dep_delay"), with=FALSE]
head(ans)
#    arr_delay dep_delay
# 1:        13        14
# 2:        13        -3
# 3:         9         2
# 4:       -26        -8
# 5:         1         2
# 6:         0         4

这个参数叫做 with,是根据 R里面的函数 with() 演变而来的。假设你有一个data.frame叫做 DF,想要subset所有符合 x>1 的行。

DF = data.frame(x = c(1,1,1,2,2,3,3,3), y = 1:8)

## (1) normal way
DF[DF$x > 1, ] # data.frame needs that ',' as well
#   x y
# 4 2 4
# 5 2 5
# 6 3 6
# 7 3 7
# 8 3 8

## (2) using with
DF[with(DF, x > 1), ]
#   x y
# 4 2 4
# 5 2 5
# 6 3 6
# 7 3 7
# 8 3 8

说明:

* 在上面的 (2) using with里面,使用 with(),我们像变量一样使用DF的x列。
因此,在data.table里,我们设置 with=FALSE,使得我们不能再像变量一样引用列了,这被保存在“data.frame mode”中。
* 我们也可以使用 - 或者 ! 来排除列。比如:
## not run

# returns all columns except arr_delay and dep_delay
ans <- flights[, !c("arr_delay", "dep_delay"), with=FALSE]
# or
ans <- flights[, -c("arr_delay", "dep_delay"), with=FALSE]

* R语言从V1.9.5版开始,可以指定开始和结束的列名,比如通过指定 year:day 来选择前三列。
## not run

# returns year,month and day
ans <- flights[, year:day, with=FALSE]
# returns day, month and year
ans <- flights[, day:year, with=FALSE]
# returns all columns except year, month and day
ans <- flights[, -(year:day), with=FALSE]
ans <- flights[, !(year:day), with=FALSE]

这在交互式的工作中特别方便。

with=FALSE 是data.table的默认值,因为我们可以通过参数j表达式,来做更多的事,特别是接下来一节我们要讲到的,和 by 的联合。

2.聚合

在前面一节,我们已经了解了参数i和j,知道了data.table的基本语法。在这一节,我们学习如何跟 by 相结合,做一些分组的操作。先来看看几个例子。

a) 用by分组

- 如何获取每个机场起飞的航班数

ans <- flights[, .(.N), by=.(origin)]
ans
#    origin     N
# 1:    JFK 81483
# 2:    LGA 84433
# 3:    EWR 87400

## or equivalently using a character vector in 'by'
# ans <- flights[, .(.N), by="origin"]

说明:

* 我们知道 .N 表示当前的分组中,对象的数目。先按照 origin 列分组,再用 .N 获取每组的数目。
* 通过 head(flights),我们可以看到结果里面,机场是按照“JFK”, “LGA” 然后 “EWR” 的顺序排列的。原始数据里,被分组的那一列变量的顺序,也体现在结果里面。   
* 既然我们没有在参数j里面指定列名,那这一列就自然是 N 了。
* by 也接受一个包含列名的字符向量作为参数。这在写代码的时候特别有用,比如设计一个函数,它的参数是要被分组的列。
* 当参数j和by里面只有一列,我们可以省略 .(),这实在很方便。刚刚的任务我们可以这样做:
ans <- flights[, .N, by=origin]
ans
#    origin     N
# 1:    JFK 81483
# 2:    LGA 84433
# 3:    EWR 87400

只要允许,我们就会使用这种方便的形式。

- 如何获取美航(carrier code代码是“AA”)在每个机场起飞的航班数
航空公司代码“AA”代表美航。每个航空公司的代码都是唯一的。

ans <- flights[carrier == "AA", .N, by=origin]
ans
#    origin     N
# 1:    JFK 11923
# 2:    LGA 11730
# 3:    EWR  2649

说明:

* 我们首先通过参数i,指定表达式 carrier == "AA",选取符合条件的行。
* 对于这些行,我们再按 origin 分组,获取每组的数目。再次声明,实际上没有列被重新创建,因为参数j表达式不需要获取列,因此在计算速度和内存使用量这两方面,取得最优的效果。

- 如何获取美航在所有机场的起/降的数目

ans <- flights[carrier == "AA", .N, by=.(origin,dest)]
head(ans)
#    origin dest    N
# 1:    JFK  LAX 3387
# 2:    LGA  PBI  245
# 3:    EWR  LAX   62
# 4:    JFK  MIA 1876
# 5:    JFK  SEA  298
# 6:    EWR  MIA  848

## or equivalently using a character vector in 'by'
# ans <- flights[carrier == "AA", .N, by=c("origin", "dest")]

说明:

* 参数by 可以接受多个列。我们可以指定所有我们想分组的列。

- 如何获取美航在所有机场的起/降的平均延误时间

ans <- flights[carrier == "AA", 
    .(mean(arr_delay), mean(dep_delay)), 
    by=.(origin, dest, month)]
ans
#      origin dest month         V1         V2
#   1:    JFK  LAX     1   6.590361 14.2289157
#   2:    LGA  PBI     1  -7.758621  0.3103448
#   3:    EWR  LAX     1   1.366667  7.5000000
#   4:    JFK  MIA     1  15.720670 18.7430168
#   5:    JFK  SEA     1  14.357143 30.7500000
#  ---                                        
# 196:    LGA  MIA    10  -6.251799 -1.4208633
# 197:    JFK  MIA    10  -1.880184  6.6774194
# 198:    EWR  PHX    10  -3.032258 -4.2903226
# 199:    JFK  MCO    10 -10.048387 -1.6129032
# 200:    JFK  DCA    10  16.483871 15.5161290

说明:

* 我们没有在参数j表达式中指定列名,它们会自动命名为(V1, V2)。    * 再次声明,原数据里面的顺序,会反映在结果中。

可是,如果我们想让结果按照 origin, dest 和 month 排序呢?

b) 参数keyby

data.table本身就被设计成能保持原数据的顺序。在一些情况下,必须保持原来的顺序。但是,有时我们希望自动根据分组的变量排序。

- 如何按照分组的变量排序

ans <- flights[carrier == "AA", 
    .(mean(arr_delay), mean(dep_delay)), 
    keyby=.(origin, dest, month)]
ans
#      origin dest month         V1         V2
#   1:    EWR  DFW     1   6.427673 10.0125786
#   2:    EWR  DFW     2  10.536765 11.3455882
#   3:    EWR  DFW     3  12.865031  8.0797546
#   4:    EWR  DFW     4  17.792683 12.9207317
#   5:    EWR  DFW     5  18.487805 18.6829268
#  ---                                        
# 196:    LGA  PBI     1  -7.758621  0.3103448
# 197:    LGA  PBI     2  -7.865385  2.4038462
# 198:    LGA  PBI     3  -5.754098  3.0327869
# 199:    LGA  PBI     4 -13.966667 -4.7333333
# 200:    LGA  PBI     5 -10.357143 -6.8571429

说明:

* 我们做的,只是把 by 改为了 keyby。这会自动的将结果按照升序排列。注意 keyby() 在完成操作后生效,例如,在计算结果后再排序。   
keys:实际上 keyby 做的不只是排序。它在排序之后,设置一个叫做sorted的属性。我们会在下一教程学习更多关于 keys的内容。   
现在,你需要知道的,就是使用 keyby 自动排序。

c) chaining表达式

让我们再来考虑下“获取美航在所有机场的起/降的数目”的问题。

ans <- flights[carrier == "AA", .N, by = .(origin, dest)]

- 如何让 ans 按origin的升序、按dest的降序排列
我们可以将中间结果保存为一个临时变量,再对这个变量使用 order(origin, -dest) 排序。这看上去还挺简洁明了的。

ans <- ans[order(origin, -dest)]
head(ans)
#    origin dest    N
# 1:    EWR  PHX  121
# 2:    EWR  MIA  848
# 3:    EWR  LAX   62
# 4:    EWR  DFW 1618
# 5:    JFK  STT  229
# 6:    JFK  SJU  690

说明:

* 回忆一下,我们在函数 order()中,对一个字符型的列使用 "-" 来降序排列。由于data.table的内部查询优化(internal query optimisation),这样做是可行的。
* 再回忆一下 order(...)已经通过data.table内部的快速基数排序函数 forder()优化过了。那么,我们可以继续使用熟悉的R的基础函数,而不是考虑使用data.table提供的速度快内存消耗少的排序方法。我们会在data.table internals的教程中说明更多细节。

但是这么做会生成一个临时变量,然后再修改这个临时变量。其实我们可以通过添加chaining表达式,避免生成临时变量。

ans <- flights[carrier == "AA", .N, by=.(origin, dest)][order(origin, -dest)]
head(ans, 10)
#     origin dest    N
#  1:    EWR  PHX  121
#  2:    EWR  MIA  848
#  3:    EWR  LAX   62
#  4:    EWR  DFW 1618
#  5:    JFK  STT  229
#  6:    JFK  SJU  690
#  7:    JFK  SFO 1312
#  8:    JFK  SEA  298
#  9:    JFK  SAN  299
# 10:    JFK  ORD  432

说明:

* 我们可以一个接一个地添加表达式,做一系列操作,就像这样:DT[...][...][...]。
* 或者你可以换行写:
DT[...
 ][...
 ][...
 ]

d) by表达式

- 参数by也可以接受表达式吗?还是只能指定列
当然可以接受表达式。举个例子,如果我们想要知道,有多少航班起飞延误但却提前/准时到达的,有多少航班起飞和到达都延误了……

ans <- flights[, .N, .(dep_delay>0, arr_delay>0)]
ans
#    dep_delay arr_delay      N
# 1:      TRUE      TRUE  72836
# 2:     FALSE      TRUE  34583
# 3:     FALSE     FALSE 119304
# 4:      TRUE     FALSE  26593

说明:

* 最后一行,满足 dep_delay > 0 = TRUE 且 arr_delay > 0 = FALSE 的条件。我们知道有26593次航班起飞延误但却提前/准时到达了。
* 注意,我们没有在by表达式里面指定任何列名。然而结果里面,列名还是自动的生成了。
* 我们可以在表达式里面指定其他的列,比如:DT[, .N, by=.(a, b>0)]。

e) 在参数j里面指定多个列

- 必须分别对每列指定 mean() 函数吗
当然不必分别对每列输入 mean(myCol) 了。要是我们有100列要计算平均值,不就惨了吗。
如何高效地计算呢。记不记得这个小贴士-“只要参数j 返回一个list,这个list的每一个元素都会被转换成结果data.table的一列。”假设我们分组的时候,可以像变量一样,引用每个分组的数据,那么就可以循环对所有的列应用函数 lapply() ,而不需要学习新的函数。

特殊的语法 .SD:
data.table提供一个特殊的语法,形式是 .SD。它是 Subset of Data 的缩写。它自身就是一个data.table,包含通过by 分组后的每一组。
回忆一下,一个data.table本质上是一个list,它们的列包含的元素个数都相同(其实就是行数)。

让我们用之前的一个data.table DT来看看 .SD 是如何使用的。

DT
#    ID a  b  c
# 1:  b 1  7 13
# 2:  b 2  8 14
# 3:  b 3  9 15
# 4:  a 4 10 16
# 5:  a 5 11 17
# 6:  c 6 12 18

DT[, print(.SD), by=ID]
#    a b  c
# 1: 1 7 13
# 2: 2 8 14
# 3: 3 9 15
#    a  b  c
# 1: 4 10 16
# 2: 5 11 17
#    a  b  c
# 1: 6 12 18
# Empty data.table (0 rows) of 1 col: ID

说明:

* .SD 包含除了分组依据的那一列以外的所有列。
* 返回值依旧保持了原数据的顺序。首先打印出来的是 ID=“b” 的数据,然后是 ID=“a” 的,最后是  ID=“c” 的。

为了对复数的列进行计算,我们可以简单地使用函数 lapply()。

DT[, lapply(.SD, mean), by=ID]
#    ID   a    b    c
# 1:  b 2.0  8.0 14.0
# 2:  a 4.5 10.5 16.5
# 3:  c 6.0 12.0 18.0 

说明:

* .SD 分别包含了ID是 a、b、c的所有行,它们分别对应了各自的组。我们应用函数 lapply() 对每列计算平均值。
* 每一组返回包含三个平均数的list,这些构成了最终返回的data.table。
* 既然函数 lapply() 返回 list,我们就不需要在外面多加 .() 了。

差不多可以了,再补充一点。在 flights 这个 data.table里面,我们执行计算 arr_delay 和 dep_delay 这两列的平均值。但是,.SD 默认包含用于分组的所有列的平均值。

-如何指定希望计算平均值的列

.SDcols
使用参数 .SDcols。它接受列名或者列索引。比如,.SDcols = c("arr_delay", "dep_delay")能确保.SD之包含 arr_delay 和 dep_delay 这两列。
和 with = FALSE 一样,我们也可以使用 - 或者 ! 来移除列。比如,我们指定 !(colA:colB) 或者 -(colA:colB)表示移除从 colA 到 colB 的所有列。

现在让我们试着用 .SD 和 .SDcols 来获取 arr_delay 和 dep_delay 这两列的平均值,并且按照 origin, dest 和 month 来分组。

flights[carrier == "AA",                     ## Only on trips with carrier "AA"
        lapply(.SD, mean),                   ## compute the mean
        by=.(origin, dest, month),           ## for every 'origin,dest,month'
        .SDcols=c("arr_delay", "dep_delay")] ## for just those specified in .SDcols
#      origin dest month  arr_delay  dep_delay
#   1:    JFK  LAX     1   6.590361 14.2289157
#   2:    LGA  PBI     1  -7.758621  0.3103448
#   3:    EWR  LAX     1   1.366667  7.5000000
#   4:    JFK  MIA     1  15.720670 18.7430168
#   5:    JFK  SEA     1  14.357143 30.7500000
#  ---                                        
# 196:    LGA  MIA    10  -6.251799 -1.4208633
# 197:    JFK  MIA    10  -1.880184  6.6774194
# 198:    EWR  PHX    10  -3.032258 -4.2903226
# 199:    JFK  MCO    10 -10.048387 -1.6129032
# 200:    JFK  DCA    10  16.483871 15.5161290

f) 对每组subset .SD

- 如何返回每个月的前两行

ans <- flights[, head(.SD, 2), by=month]
head(ans)
#    month year day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1:     1 2014   1      914        14     1238        13         0      AA  N338AA      1    JFK
# 2:     1 2014   1     1157        -3     1523        13         0      AA  N335AA      3    JFK
# 3:     2 2014   1      859        -1     1226         1         0      AA  N783AA      1    JFK
# 4:     2 2014   1     1155        -5     1528         3         0      AA  N784AA      3    JFK
# 5:     3 2014   1      849       -11     1306        36         0      AA  N784AA      1    JFK
# 6:     3 2014   1     1157        -3     1529        14         0      AA  N787AA      3    JFK
#    dest air_time distance hour min
# 1:  LAX      359     2475    9  14
# 2:  LAX      363     2475   11  57
# 3:  LAX      358     2475    8  59
# 4:  LAX      358     2475   11  55
# 5:  LAX      375     2475    8  49
# 6:  LAX      368     2475   11  57

说明:

* .SD 包含了每组的所有行。我们可以简单的subset各组数据的前两行。
* 对每组数据,head(.SD, 2)返回的data.table同时也是个list。所以不需要用 .() 包围起来。

g) 为什么参数j这么灵活

这样,我们有了符合R语言风格的语法,我们也使用R语言里面既存的函数定义,而不是定义新的函数。我们用教程一开始创建的DT来说明。

-如何保存按照ID分组后数据中的 a列和 b列 的信息

DT
#    ID a  b  c
# 1:  b 1  7 13
# 2:  b 2  8 14
# 3:  b 3  9 15
# 4:  a 4 10 16
# 5:  a 5 11 17
# 6:  c 6 12 18

DT[, .(val = c(a,b)), by=ID]
#     ID val
#  1:  b   1
#  2:  b   2
#  3:  b   3
#  4:  b   7
#  5:  b   8
#  6:  b   9
#  7:  a   4
#  8:  a   5
#  9:  a  10
# 10:  a  11
# 11:  c   6
# 12:  c  12

说明:

* 就这样,不需要特殊的语法。我们需要知道的,就是用函数 c() 指定需要连结的向量。

- 如何将刚刚的数据,作为一列返回

DT[, .(val = list(c(a,b))), by=ID]
#    ID         val
# 1:  b 1,2,3,7,8,9
# 2:  a  4, 5,10,11
# 3:  c        6,12

说明:

* 我们首先用 c(a,b) 连结了每组的值,然后用 list() 包围起来。那么对于每组数据,我们返回一个所有连结后的值的 list。
* 注意,那些逗号都是用来辅助显示的。一个list中的元素可以包含任何对象。在这个例子里,每个元素是一个向量,它们的长度都不相同。

一旦你对参数j的用法产生了兴趣,你会发现语法是多么强大。理解这些的一个有效的方法就是,在 print() 的帮助下,多多使用。
例如:

## (1) look at the difference between
DT[, print(c(a,b)), by=ID]
# [1] 1 2 3 7 8 9
# [1]  4  5 10 11
# [1]  6 12
# Empty data.table (0 rows) of 1 col: ID

## (2) and
DT[, print(list(c(a,b))), by=ID]
# [[1]]
# [1] 1 2 3 7 8 9
# 
# [[1]]
# [1]  4  5 10 11
# 
# [[1]]
# [1]  6 12
# Empty data.table (0 rows) of 1 col: ID

在(1)里面,每组返回一个向量,它们的长度分别是6,4,2.但是(2)里面,每组返回一个长度都为1的list,它们的第一个元素包含了长度为6,4,2的向量。因此,(1)的结果的长度是6+4+2=12,(2)的结果的长度是1+1+1=3。

总结

data.table的语法形式是:

DT[i, j, by]

指定参数i:

* 类似于data.frame,我们可以subset行,除非不需要重复地使用 DT$,既然我们能将列当做变量来引用。 
* 我们可以使用order()排序。为了得到更快速的效果,order()函数内部使用了data.table的快速排序。 
我们可以通过参数i做更多的事,得到更快速的选取和连结。我们可以在教程“Keys and fast binary search based subsets”和“Joins and rolling joins”中学到这些。 

指定参数j:

* 以data.table的形式选取列:DT[, .(colA, colB)]。
* 以data.frame的形式选取列:DT[, c("colA", "colB"), with=FALSE]。
* 按列进行计算:DT[, .(sum(colA), mean(colB))]。
* 如果需要:DT[, .(sA =sum(colA), mB = mean(colB))]。
* 和i共同使用:DT[colA > value, sum(colB)]。

指定参数by:

* 通过by,我们可以指定列,或者列名,甚至表达式,进行分组。参数j可以很灵活地配置参数i和by实现强大的功能。
* by可以指定多个列,也可以指定表达式。
* 我们可以用 keyby,对分组的结果自动排序。
* 我们可以在参数j中指定 .SD 和 .SDcols,对复数的列进行操作。例如:   
  1.把函数fun 应用到所有 .SDcols指定的列上,同时对参数by指定的列进行分组:DT[, lapply(.SD, fun), by=., .SDcols=...]。 
  2.返回每组册前两行:DT[, head(.SD, 2), by=.]。
  3.三个参数联合使用:DT[col > val, head(.SD, 1), by=.]。

小提示:
只要j返回一个list,这个list的每个元素都会是结果data.table的一列。

下一讲,我们学习如何用reference来add/update/delete某一列,如何通过i和by合并它们。

写给大家看的设计书

即使不是设计师,如果懂一点设计,做出来的产品会更明确和清晰。
况且,一个现代人,总会有书面形式的表达,此时知晓一些设计原则,对文档非常有帮助。

这本设计书,就是写给设计师以外的人看的。
The Non-Designer’s Design Book

在看这本书以前,对于日常能见到的海报、传单、商品包装、图书封面,我都不甚在意;看完这本书之后,同样还是对于这些常见信息的展示形式,有了新的认识。
也就是说,能明确地说出来,这个传单,遵循了什么样的规则,所以产生了什么样的效果。而那个海报,如果稍微如何如何修改一下,或许能够变得更好。

四大设计原则

  • 亲密性
  • 对齐
  • 重复
  • 对比

书上介绍了很多没有遵循这些原则的实例,然后告诉我们应该如何修改这些实例。
通过实例,能够更容易掌握下面这些干巴巴的定义。

亲密性

彼此相关的信息,应当归并在一起,形成一个视觉单元,而不是多个孤立的元素。
这有助于组织信息,减少混乱,为读者提供清晰的结构。

对齐

每个元素都应当于页面上的另一个元素有视觉的联系。
以此建立一种清晰、精巧而且清爽的外观。

重复

同一类元素应当以同样的颜色、形状、材质、空间关系、线宽、字体、大小和图片来重复出现。
既能增加条理性,还可以加强统一性。

对比

不同类别的元素,应当截然不同。

颜色

介绍了调色板里面各种颜色的关系,以及“亮色“和“暗色”的对比,“暖色”和“冷色”的对比,“纸”和“显示屏”的对比。

字体

介绍了不同字体的来源和对比,非常有启发。


亚马逊链接:写给大家看的设计书