|
|
干练的地瓜 · 食品饮料行业深度报告:为何说调味品是一门好生 ...· 11 月前 · |
|
|
乐观的桔子 · 竺可桢与妻子的故事-立德树人网· 11 月前 · |
|
|
非常酷的消防车 · 複合式購物中心遊客空間認知之感質與情緒感受量 ...· 1 年前 · |
|
|
乐观的卤蛋 · 三英战吕布_百度百科· 1 年前 · |
|
|
睿智的登山鞋 · 《茶花女(译文精选)》电子书在线阅读-【法】 ...· 1 年前 · |
evalCpp()
转换单一计算表达式
cppFunction()
转换简单的C++函数—Fibnacci例子
sourceCpp()
转换C++程序—正负交替迭代例子
sourceCpp()
转换C++源文件中的程序—正负交替迭代例子
sourceCpp()
转换C++源程序文件—卷积例子
wrap()
把C++变量返回到R中
as()
函数把R变量转换为C++类型
as()
和
wrap()
的隐含调用
//[[Rcpp::export]]
sourceCpp()
函数中直接包含C++源程序字符串
cppFunction()
函数中直接包含C++函数源程序字符串
evalCpp()
函数中直接包含C++源程序表达式字符串
depends
指定要链接的库
invisible
要求函数结果不自动显示
clone
函数
is_na
seq_along
seq_len
pmin
和
pmax
ifelse
sapply
和
lapply
sign
diff
kable()
函数制作表格
数据集经常需要选行子集、选列子集、排序、定义新变量、横向合并、长宽转换等操作,
而且经常会用若干个连续的操作分步处理,
R的管道运算符
|>
和magrittr包的
%>%
特别适用于这种分步处理。
dplyr包和tidyr包定义了一系列“动词”,
可以用比较自然的方式进行数据整理。
较复杂的分组操作还可以利用purrr包的
map
类函数。
为了使用这些功能,可以载入
tidyverse
包,
则magrittr包,readr包,dplyr包和tidyr包都会被自动载入:
下面的例子中用如下的一个班的学生数据作为例子,
保存在如下
data/class.csv
文件中:
name,sex,age,height,weight
Alice,F,13,56.5,84
Becka,F,13,65.3,98
Gail,F,14,64.3,90
Karen,F,12,56.3,77
Kathy,F,12,59.8,84.5
Mary,F,15,66.5,112
Sandy,F,11,51.3,50.5
Sharon,F,15,62.5,112.5
Tammy,F,14,62.8,102.5
Alfred,M,14,69,112.5
Duke,M,14,63.5,102.5
Guido,M,15,67,133
James,M,12,57.3,83
Jeffrey,M,13,62.5,84
John,M,12,59,99.5
Philip,M,16,72,150
Robert,M,12,64.8,128
Thomas,M,11,57.5,85
William,M,15,66.5,112
读入为tibble:
d.class <- read_csv( "data/class.csv", col_types=cols( .default = col_double(), name=col_character(), sex=col_factor(levels=c("M", "F"))这个数据框有19个观测, 有如下5个变量:
height weight 另一个例子数据集是R的NHANES扩展包提供的NHANES, 这是一个规模更大的示例数据框, 可以看作是美国扣除住院病人以外的人群的一个随机样本, 有10000个观测,有76个变量, 主题是个人的健康与营养方面的信息。 仅作为教学使用而不足以作为严谨的科研用数据。 原始数据的情况详见http://www.cdc.gov/nchs/nhanes.htm。 载入NHANES数据框:
## [1] 10000 76## [1] "ID" "SurveyYr" "Gender" "Age" ## [5] "AgeDecade" "AgeMonths" "Race1" "Race3" ## [9] "Education" "MaritalStatus" "HHIncome" "HHIncomeMid" ## [13] "Poverty" "HomeRooms" "HomeOwn" "Work" ## [17] "Weight" "Length" "HeadCirc" "Height" ## [21] "BMI" "BMICatUnder20yrs" "BMI_WHO" "Pulse" ## [25] "BPSysAve" "BPDiaAve" "BPSys1" "BPDia1" ## [29] "BPSys2" "BPDia2" "BPSys3" "BPDia3" ## [33] "Testosterone" "DirectChol" "TotChol" "UrineVol1" ## [37] "UrineFlow1" "UrineVol2" "UrineFlow2" "Diabetes" ## [41] "DiabetesAge" "HealthGen" "DaysPhysHlthBad" "DaysMentHlthBad" ## [45] "LittleInterest" "Depressed" "nPregnancies" "nBabies" ## [49] "Age1stBaby" "SleepHrsNight" "SleepTrouble" "PhysActive" ## [53] "PhysActiveDays" "TVHrsDay" "CompHrsDay" "TVHrsDayChild" ## [57] "CompHrsDayChild" "Alcohol12PlusYr" "AlcoholDay" "AlcoholYear" ## [61] "SmokeNow" "Smoke100" "Smoke100n" "SmokeAge" ## [65] "Marijuana" "AgeFirstMarij" "RegularMarij" "AgeRegMarij" ## [69] "HardDrugs" "SexEver" "SexAge" "SexNumPartnLife" ## [73] "SexNumPartYear" "SameSex" "SexOrientation" "PregnantNow"变量ID是受试者编号, SurveyYr是调查年份, 同一受试者可能在多个调查年份中有数据。 变量中包括性别、年龄、种族、收入等人口学数据, 包括体重、身高、脉搏、血压等基本体检数据, 以及是否糖尿病、是否抑郁、是否怀孕、已生产子女数等更详细的健康数据, 运动习惯、饮酒、性生活等行为方面的数据。 这个教学用数据集最初的使用者是Cashmere高中的Michelle Dalrymple 和新西兰奥克兰大学的Chris Wild。
23.2 查看数据框一般信息
对一个数据框(包括tibble)
d, 可以用dim(d)获得行、列数, 用names(d)获得各列的变量名, 用str(d)显示类型、大小、各列变量名以及类型、前几个值, 以及其他属性。dplyr::glimpse(d)则可以显示数据框的大小、各列变量名、类型等,如:## Rows: 19 ## Columns: 5 ## $ name <chr> "Alice", "Becka", "Gail", "Karen", "Kathy", "Mary", "Sandy", "S… ## $ sex <fct> F, F, F, F, F, F, F, F, F, M, M, M, M, M, M, M, M, M, M ## $ age <dbl> 13, 13, 14, 12, 12, 15, 11, 15, 14, 14, 14, 15, 12, 13, 12, 16,… ## $ height <dbl> 56.5, 65.3, 64.3, 56.3, 59.8, 66.5, 51.3, 62.5, 62.8, 69.0, 63.… ## $ weight <dbl> 84.0, 98.0, 90.0, 77.0, 84.5, 112.0, 50.5, 112.5, 102.5, 112.5,…23.3 用
filter()选择行子集数据框的任何行子集仍为数据框,即使只有一行而且都是数值也是如此。 行子集可以用行下标选取, 如
d.class[8:12,]。 函数head()取出数据框的前面若干行,tail()取出数据框的最后若干行。dplyr包的
height weightfilter()函数可以按条件选出符合条件的行组成的子集。 下例从d.class中选出年龄在13岁和13岁以下的女生:23.4 按行序号选择行子集
基本R的utils包的函数
height weighthead(x, n)可以用来选择数据框x前面n行,tail(x, n)可以用来选择数据框x后面n行,如:slice_min(x, n=1)提取x值最小的n行;slice_max(x, n=1)提取x值最大的n行;slice_sample(n=5)随机无放回抽取n行, 也可以用sample_n(size = n)函数。这些函数也可以用
prop=0.1取代n=5这样的写法, 输入一个要提取的行数的比例。23.5 用
sample_n()对观测随机抽样dplyr包的
height weightsample_n(tbl, size)函数可以从数据集tbl中随机无放回抽取size行,如:R的字符串函数(如
paste())和正则表达式函数可以用来生成变量名子集, 然后在select中配合all_of()使用。all_of()要求指定的所有变量名都是数据框中存在的; 如果指定的变量有些可能是不存在的, 想将确实存在的那些变量选取进来, 应使用any_of()。
select()有若干个配套函数可以按名字的模式选择变量列,starts_with("se"): 选择名字以“se”`开头的变量列;ends_with("ght"): 选择名字以“ght”`结尾的变量列;contains("no"): 选择名字中含有子串“no”`的变量列;matches("^[[:alpha:]]+[[:digit:]]+$"), 选择列名匹配某个正则表达式模式的变量列, 这里匹配前一部分是字母,后一部分是数字的变量名,如abc12。num_range("x", 1:3),选择x1,x2,x3。everything(): 代指所有选中的变量, 这可以用来将指定的变量次序提前, 其它变量排在后面。选择变量时, 可以用“新变量=老变量”的格式同时改名,
gender 可以用pull(.data, !!sym(varname))这种格式; 如果varname是函数的自变量, 可以用pull(.data, {{ varname }})这种格式。 先选择仅有一个变量的子数据框再用pull(),如:## [1] "Alice:Becka:Gail"基于基本R, 也可以用
d.class[["name"]]这种格式取出一列为普通变量, 如果varname保存了变量名, 可以用d.class[[varname]]这种格式。不能用
d.class[,"name"]这种方法, 对于tibble类型, 其结果仍是一个子数据框; 用d.class["name"]这种格式, 结果也是一个子数据框。23.10 用
arrange()排序dplyr包的
height weightarrange()按照数据框的某一列或某几列排序, 返回排序后的结果,如注意这样改名字不是对原始数据框修改而是返回改了名字后的新数据框。 也可以利用赋值运算符
->写成:
rename()这个函数可能出现在其它包中, 保险起见写成dplyr::rename()。如果数据框中有大批变量名需要按某种规范统一修改, 可以考虑使用
janitor::clean_names()。23.12 用
relocate()调整变量次序height weight
relocate(df, var1, var2)将指定的变量调整到最前面。上面程序中的
if_else()是dplyr包对基本R函数ifelse()的一个改进版本, 能够好处理缺失值和结果类属的问题。用
mutate()计算新变量时如果计算比较复杂, 也可以用多个语句组成复合语句,如:height weight 使得新定义的变量添加到原有变量的最前面。 可以用d.class |> mutate( sexc = { x <- rep("男", length(sex)) x[!is.na(sex) & sex == "F"] <- "女" } ) |> head(n=3) |> knitr::kable().after=变量名使得新添加的变量放在指定的变量后面。 可以用选项.keep="used"仅保留新变量以及计算新变量时用到的变量。dplyr定义了一些新的函数:
row_number()返回每个观测的行号。min_rank(x),计算秩统计量,冲突时按最小值, 类似函数有dense_rank(x),percent_rank(x),cume_rank(x)等。 基本R的rank(x)函数配合ties.method选项实现类似功能。dplyr::lag(x)将x看成一个时间为1:n的时间序列, 然后在第\(i\)个时间点返回第\(i-1\)个时间点的值, 如dplyr::lag(c(2,3,5,7))返回NA, 2, 3, 5。dplyr::lead(x)则在时间点\(i\)返回时间点\(i+1\)的值, 如dplyr::lead(c(2,3,5,7))返回3,5,7,NA。 注意stats::lag()函数有很大的差别, 它主要针对时间序列数据, 对一般向量反而不好用。23.14 用
tranmute()生成新变量的数据框函数
transmute()用法与mutate()类似, 但是仅保留新定义的变量, 不保留原来的所有变量。height_cm weight_kgd.class |> transmute( height_cm = round(height*2.54), weight_kg = round(weight*0.4535924), bmi = weight_kg / (height_cm / 100)^2) |> head(n=3) |> knitr::kable()可见结果中仅保留了新定义的变量。
定义新变量也可以直接为数据框的新变量赋值:
这样的做法与
mutate()的区别是这样不会生成新数据框, 新变量是在原数据框中增加的。给数据框中某个变量赋值为
NULL可以修改数据框, 从数据框中删去该变量。23.15 缺失值填补
从统计角度出发考虑如何填补数据中的缺失值是比较复杂的。 这里仅给出从编程考虑的做法。
有些非整洁的数据在某变量值持续多行保持不变时, 往往仅输入第一个值, 随后重复的值就输入为空白, 在读入为R数据框时自动变成缺失值。 对于这样的缺失值, 可以用tidyr::fill()函数, 可以指定需要填补的变量。 ## 6 2 3 14
有时某个变量的缺失值实际上有含义,比如取值为0。
这时,可以用
dplyr::coalesce(x, default)
使
x
的缺失值用
default
取代。如:
d.miss |> mutate( x = coalesce(n, 0)## # A tibble: 6 × 4 ## A B n x ## <dbl> <dbl> <dbl> <dbl> ## 1 1 1 12 12 ## 2 NA 2 10 10 ## 3 NA 3 NA 0 ## 4 2 1 15 15 ## 5 NA 2 NA 0 ## 6 NA 3 14 14有时,有可能缺失值被输入成了一个特殊数值,
x的值仅取正数, 输入了-1就表示缺失值。 在读入CSV文件时, 如果所有缺失值都用-1表示, 可以在read_csv()中加选项na = -1。 如果不同列有不同的缺失值编码, 可以用na_if(x, na_value)将x中等于na_value的值改为缺失值, 可以用在mutate()中, 如mutate(x = na_if(x, -1))。在涉及到交叉分组的数据时, 有可能某些交叉分组没有出现在数据中, 这可能对某些分析程序造成影响。 可以用
complete(A, B)这样的方法将数据框中A,B的交叉分类补全。 如果这样不能包含所有可取值, 也可以生成一个包含所有组合的小数据框, 用dplyr::full_join()作外连接。dplyr::anti_join()可以用变量取值全集的数据框来比对, 发现没有出现的变量取值。在使用
dplyr::count()对因子进行频数统计时, 数据中不出现的水平或水平组合默认在结果中也不出现, 但可以加选项.drop = FALSE使得这些水平或水平组合以频数0出现。 用dplyr::group_by()时, 也可以加.drop = FALSE使得没有观测的分组(但因子水平存在)也出现在结果中。 但更好的办法是在分组汇总完以后, 用complete(A, B)这样的办法补全汇总结果中的因子水平或水平组合。23.16 用管道连接多次操作
管道运算符特别适用于对同一数据集进行多次操作。 例如,对
height weightd.class数据,先选出所有女生, 再去掉性别和age变量:结果的数据框d有三个变量: group是大组,共分3个大组,每组4个观测; subgroup是子组,在每个大组内分为2个子组,每个子组2个观测。 共有\(3 \times 2 \times 2 = 12\)个观测(行)。
23.18 宽表转换为长表
“整洁数据”的要求是每行为一个观测, 每列为一个变量(属性), 如果有同一个体在不同时间的测量值应该放在不同的观测中, 如果同一个体在同一时间的不同属性应该放在同一观测中。 满足这些要求的数据框才容易作为作图、汇总、统计建模函数的输入使用。
实际数据经常不满足上述的要求, 所以需要将数据框进行长宽格式的转换。 以典型的纵向数据为例, 每个受试者有多次随访的记录值, 如果每个受试者的所有随访记录值存放在一个观测中, 就称为宽表, 这不符合整洁数据要求; 如果每个受试者的随访记录值同一时间的多个属性用了多个观测保存, 称为长表, 也不符合整洁数据要求。
有些数据将受试者的每个属性放在一行, 每一列代表一个受试者, 这也不符合整洁数据要求。
tidyr的
pivot_longer()可以将宽表转换成整洁数据,pivot_wider()可以将长表转换成整洁数据。 基本R的reshape()函数也可以进行长宽格式的转换。 reshape2包也提供了较丰富的长宽表转换功能。 建议优先使用tidyr包的功能。23.18.1
pivot_longer函数tidyr的
subject pivot_longer(cols = starts_with("FU"), names_to = "time", values_to = "response", names_prefix = "FU", names_transform = list(time = as.integer), values_drop_na = TRUE) |> knitr::kable()pivot_longer()函数可以将横向的多次观测堆叠在一列中。 下面的数据:
其中的
cols = starts_with("FU")
也可以写成
cols = paste0("FU", 1:4)
。
考虑nlmeU扩展包的armd.wide数据框。 这个数据框中有240个受试者的信息, 每行为一个受试者, 包括5个时间点:visual0, visual4, visual12, visual24, visual52。
## Attaching package: 'nlmeU'## The following object is masked from 'package:stats':
## sigma
values_to =
"visual"
,
names_prefix =
"visual"
,
names_transform =
list
(
time =
as.integer),
values_drop_na =
TRUE
)
|>
head
(
10
)
|>
knitr
::
kable
()
F1
,
M1
,
F2
,
M2
,
则应将上面程序中的
names_sep = "_"
修改为
names_pattern = "(F|M)(1|2)"
。
其中的
"(F|M)(1|2)"
就是正则表达式,
F|M
表示F或者M,
1|2
表示1或者2,
圆括号表示分组,
前后两组分别表示两个分类变量
gender
和
response
。
设有多个属性的多次测量用编号的列名保存在了同一观测中, 基本R软件中的anscombe数据集的一部分行:
dwide4 <- anscombe |> slice(1:3) |> mutate(id = 1:3) |> select(id, x1, x2, y1, y2) knitr::kable(dwide4)
这可以看成是每个受试者的x, y两个变量的2次随访的值保存在了一个观测中。
用
names_pattern
指定切分变量名和随访号的模式,
在对应的
names_to
中用特殊的
".value"
名字表示切分出来的那一部分实际是变量名,
这时不需要
values_to
选项。
程序如下:
dwide4 |> pivot_longer( -id, names_pattern = "(x|y)([[:digit:]])", names_to = c(".value", "time") knitr::kable()
## Rows: 3 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): succ/total
## dbl (1): testid
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
testid
succ/total
用
separate()
函数将变量名和性别值分开:
函数
extract()
可以按照某种正则表达式表示的模式从指定列拆分出对应于正则表达式中捕获组的一列或多列内容。
例如,下面的数据中factors水平AA, AB, BA, BB实际是两个因子的组合,
将其拆分出来:
dexp <- tibble( design = c("AA", "AB", "BA", "BB"), response = c(120, 110, 105, 95)) knitr::kable(dexp)
tidyr::separate_wider_delim(x, delim, names)
将用分隔符分隔的内容拆分为多列,
新的列名用
names
给出。
相当于
separate()
的一个简化版本。
separate_wider_position(x, width)
用指定宽度从字符串中提取多列,
width
用一个有名向量格式指定,
输入的元素名作为提取的列名。
tidyr::separate_longer_delim()
将某列中用分隔符分开的值拆分出来并堆叠在同一列中,如:
d.tmp <- tibble( xmixed=c("1,2", "3,4,5")) d.tmp |> mutate( x = separate_longer_delim(xmixed, sep=",") )
类似地,
separate_longer_position(x, width)
可以按指定宽度拆分字符串中的内容并堆叠到一列中。
tidyr::unite()
函数可以将同一行的两列或多列的内容合并成一列。
这是
separate()
的反向操作,
d.sep |> separate(`succ/total`, into=c("succ", "total"), sep="/", convert=TRUE) |> unite(ratio, succ, total, sep=":") |> knitr::kable()
ratio
变量),
其它参数是要合并的变量名,
sep
指定分隔符。
实际上用
mutate()
、
paste()
或者
sprintf()
也能完成合并。
矩阵或数据框要纵向合并,使用
rbind
函数即可。
dplyr包的
bind_rows()
函数也可以对两个或多个数据框纵向合并。
要求变量集合是相同的,变量次序可以不同。
比如,有如下两个分开男生、女生的数据框:
d3.class <- d.class |> select(name, sex, age) |> filter(sex=="M") d4.class <- d.class |> select(name, sex, age) |> filter(sex=="F")
合并行如下:
filter (age <= 12 ) |> select (name, age, height, weight)
用dplyr包的
inner_join()
函数将两个数据框按键值横向合并,
仅保留能匹配的观测。因为d1.class中丢失了Sandy的观测,
所以合并后的数据框中也没有Sandy的观测:
## Joining, by = "name"
height
weight
如果在
inner_join()
或
left_join()
中使用
multiple = "all"
,
则意味着允许左表的一个观测可以和右表的多个观测匹配。
如果左右表的一对一连接不应该有遗漏,
如果有任何不匹配意味着数据有错,
可以在
inner_join()
中加选项
unmatched = "error"
,
其默认值是
"drop"
。
两个表的横向连接, 经常是多对一连接, 这用于从右表向左表中添加一些额外的变量。
d.stu
中有学生学号、班级号、姓名、性别,
d.cl
中有班级号、班主任名、年级,
可以通过班级号将两个表连接起来:
d.stu <- tibble( sid=c(1,2,3,4,5,6), cid=c(1,2,1,2,1,2), sname=c("John", "Mary", "James", "Kitty", "Jasmine", "Kim"), sex=c("M", "F", "M", "F", "F", "M")) knitr::kable(d.stu)
R的
intersect()
,
union()
,
setdiff()
本来是以向量作为集合进行集合操作。
dplyr包也提供了这些函数,
但是将两个tibble的各行作为元素进行集合操作。
设x是各列都为数值的列表(包括数据框和tibble)或数值型矩阵,
用
scale(x)
可以把每一列都标准化,
即每一列都减去该列的平均值,然后除以该列的样本标准差。
用
scale(x, center=TRUE, scale=FALSE)
仅中心化而不标准化。
## height weight
## [1,] -1.13843504 -0.70371312
## [2,] 0.57794313 -0.08897522
## [3,] 0.38290015 -0.44025402
## [4,] -1.17744363 -1.01108207
## [5,] -0.49479323 -0.68175819
## [6,] 0.81199469 0.52576268
## [7,] -2.15265850 -2.17469309
## [8,] 0.03182280 0.54771760
## [9,] 0.09033569 0.10861910
## [10,] 1.29960213 0.54771760
## [11,] 0.22686577 0.10861910
## [12,] 0.90951618 1.44786952
## [13,] -0.98240066 -0.74762297
## [14,] 0.03182280 -0.70371312
## [15,] -0.65082761 -0.02311045
## [16,] 1.88473105 2.19433697
## [17,] 0.48042164 1.22832027
## [18,] -0.94339207 -0.65980327
## [19,] 0.81199469 0.52576268
## attr(,"scaled:center")
## height weight
## 62.33684 100.02632
## attr(,"scaled:scale")
## height weight
## 5.127075 22.773933
为了把
x
的每列变到
\([0,1]\)
内,可以用如下的方法:
d.class %>% select(height, weight) %>% scale(center=apply(., 2, min), scale=apply(., 2, max) - apply(., 2, min))
其中的
.
在
%>%
管道操作中表示被传递处理的变量(一般是数据框)。
也可以写一个自定义的进行零一标准化的函数:
scale01 <- function(x){ mind <- apply(x, 2, min) maxd <- apply(x, 2, max) scale(x, center=mind, scale=maxd-mind) d.class |> select(height, weight) |> scale01()
## height weight
## [1,] 0.2512077 0.3366834
## [2,] 0.6763285 0.4773869
## [3,] 0.6280193 0.3969849
## [4,] 0.2415459 0.2663317
## [5,] 0.4106280 0.3417085
## [6,] 0.7342995 0.6180905
## [7,] 0.0000000 0.0000000
## [8,] 0.5410628 0.6231156
## [9,] 0.5555556 0.5226131
## [10,] 0.8550725 0.6231156
## [11,] 0.5893720 0.5226131
## [12,] 0.7584541 0.8291457
## [13,] 0.2898551 0.3266332
## [14,] 0.5410628 0.3366834
## [15,] 0.3719807 0.4924623
## [16,] 1.0000000 1.0000000
## [17,] 0.6521739 0.7788945
## [18,] 0.2995169 0.3467337
## [19,] 0.7342995 0.6180905
## attr(,"scaled:center")
## height weight
## 51.3 50.5
## attr(,"scaled:scale")
## height weight
## 20.7 99.5
函数
sweep()
可以执行对每列更一般的变换。
设同类的数据放置在了多个文件中, 这时可以利用多种技术, 将多个文件读入并纵向合并, 需要时增加文件名代表的变量信息。
作为举例,
我们将
d.class
分为男生和女生两个数据框,
分别保存到
data
子目录中:
d.class |> filter(sex == "F") |> select(-sex) |> write_csv(file = "data/class-F.csv") d.class |> filter(sex == "M") |> select(-sex) |> write_csv(file = "data/class-M.csv")
如果要读入这两个文件并合并为一个大数据框, 当然可以使用重复的写法:
dc1 <- read_csv( "data/class-F.csv", show_col_types = FALSE) dc1[["sex"]] <- "F" dc2 <- read_csv( "data/class-M.csv", show_col_types = FALSE) dc2[["sex"]] <- "M" dc <- rbind(dc1, dc2) dc[c(1:2, 11:12), ]
## # A tibble: 4 × 5
## name age height weight sex
## <chr> <dbl> <dbl> <dbl> <chr>
## 1 Alice 13 56.5 84 F
## 2 Becka 13 65.3 98 F
## 3 Duke 14 63.5 102. M
## 4 Guido 15 67 133 M
如果有许多个文件要合并, 这样的硬编码方式就不够简洁, 也容易出错。 可以联合使用许多技巧。
list.files()
函数可以指定某种文件名模式,
获取所有要读入的文件名,如:
flis <- list.files( "data", pattern = "class-[[:alnum:]]+[.]csv")## [1] "class-F.csv" "class-M.csv"其中的
pattern选项输入了一个模式(正则表达式), 见第49章。可以用
purrr::map()(见§25.2.1)将所有文件读入为一个数据框的列表, 用purrr::list_rbind()函数将作为列表元素的数据框纵向合并:dca <- file.path("data", flis) |> map(\(file) read_csv(file, show_col_types = FALSE)) |> list_rbind() dca |> slice(c(1:2, 11:12))## # A tibble: 4 × 4 ## name age height weight ## <chr> <dbl> <dbl> <dbl> ## 1 Alice 13 56.5 84 ## 2 Becka 13 65.3 98 ## 3 Duke 14 63.5 102. ## 4 Guido 15 67 133这个程序的缺点是丢失了性别变量, 这个变量的值保存在文件名内。 可以用
purrr::set_names()给各个文件名指定元素名, 这个函数的第二自变量可以直接输入元素名向量, 也可以指定一个函数, 从元素值产生元素名。 用map()读入了数据框列表后, 列表元素名会沿用文件名向量的元素名, 然后在list_rbind()中可以用names_to选项, 将数据框列表的元素名转换为合并后大数据框的一列:dca2 <- file.path("data", flis) |> set_names(basename) |> map(\(file) read_csv(file, show_col_types = FALSE)) |> list_rbind(names_to = "filename") dca2 |> slice(c(1:2, 11:12))## # A tibble: 4 × 5 ## filename name age height weight ## <chr> <chr> <dbl> <dbl> <dbl> ## 1 class-F.csv Alice 13 56.5 84 ## 2 class-F.csv Becka 13 65.3 98 ## 3 class-M.csv Duke 14 63.5 102. ## 4 class-M.csv Guido 15 67 133可以用适当的字符串处理函数提取文件名中的变量值,如:
dca3 <- file.path("data", flis) |> set_names(basename) |> map(\(file) read_csv(file, show_col_types = FALSE)) |> list_rbind(names_to = "filename") |> mutate(sex = str_sub(filename, 7,7)) |> select(-filename) dca3 |> slice(c(1:2, 11:12))## # A tibble: 4 × 5 ## name age height weight sex ## <chr> <dbl> <dbl> <dbl> <chr> ## 1 Alice 13 56.5 84 F ## 2 Becka 13 65.3 98 F ## 3 Duke 14 63.5 102. M ## 4 Guido 15 67 133 M这里用了
str_sub取子串的方法提取变量值, 这适用于文件名中的变量值占据固定的位置的情形。 更复杂的文件名, 可以用正则表达式, 或者tidyr::separate_wider_delim()指定分隔符分割成多列。dca4 <- file.path("data", flis) |> set_names(basename) |> map(\(file) read_csv(file, show_col_types = FALSE)) |> list_rbind(names_to = "filename") |> separate_wider_delim( filename, delim="-", names=c(NA, "sexext")) |> separate_wider_delim( sexext, delim=".", names=c("sex", NA)) dca4 |> slice(c(1:2, 11:12))上面的程序先用“
-”分割成两部分, 再用“.”将第二部分分割成两部分。也可以直接使用正则表达式,
dca5 <- file.path("data", flis) |> set_names(basename) |> map(\(file) read_csv(file, show_col_types = FALSE)) |> list_rbind(names_to = "filename") |> mutate( sex = str_replace( filename, "class-([[:alnum:]]+)[.]csv", "\\1") ) |> select(-filename) dca5 |> slice(c(1:2, 11:12))data.table包的rbindlist可以将保存为列表元素的多个数据框纵向合并, 运行效率高。
23.27 使用data.table包
data.table包不仅能高效地读写文本格式的数据, 也能高效地进行数据整理。 它可以利用CPU的多个核心并行处理, 并可以巧妙地使用内存, 避免冗余的复制。
data.table在读入数据时不会自动将字符型数据转换为因子, 也不会生成数据框行名。 在显示data.table时, 过大的表会自动简化显示。
## Attaching package: 'data.table'
## The following objects are masked from 'package:dplyr':
## between, first, last
## The following object is masked from 'package:purrr':
## transpose
用如下命令快速查看基本用法示例:
data.table的
[
运算符支持增强的查询功能,
可以指定行子集条件、列子集条件和分组条件,
一般格式为
DT[i, j, by]
。
以nycflights13中的航班数据为例。
## Warning: package 'nycflights13' was built under R version 4.2.2
仅提供行子集条件,
注意可以直接使用数据框变量名,
也不需要写成
DT[i,]
的格式:
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## 1: 2013 6 1 2 2359 3 341 350
## 2: 2013 6 1 538 545 -7 925 922
## 3: 2013 6 1 539 540 -1 832 840
## 4: 2013 6 1 553 600 -7 700 711
## 5: 2013 6 1 554 600 -6 851 908
## 6: 2013 6 1 557 600 -3 934 942
## arr_delay carrier flight tailnum origin dest air_time distance hour minute
## 1: -9 B6 739 N618JB JFK PSE 200 1617 23 59
## 2: 3 B6 725 N806JB JFK BQN 203 1576 5 45
## 3: -8 AA 701 N5EAAA JFK MIA 140 1089 5 40
## 4: -11 EV 5716 N835AS JFK IAD 42 228 6 0
## 5: -17 UA 1159 N33132 JFK LAX 330 2475 6 0
## 6: -8 B6 715 N766JB JFK SJU 198 1598 6 0
## time_hour
## 1: 2013-06-01 23:00:00
## 2: 2013-06-01 05:00:00
## 3: 2013-06-01 05:00:00
## 4: 2013-06-01 06:00:00
## 5: 2013-06-01 06:00:00
## 6: 2013-06-01 06:00:00
按行号取行子集:
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## 1: 2013 1 1 517 515 2 830 819
## 2: 2013 1 1 533 529 4 850 830
## arr_delay carrier flight tailnum origin dest air_time distance hour minute
## 1: 11 UA 1545 N14228 EWR IAH 227 1400 5 15
## 2: 20 UA 1714 N24211 LGA IAH 227 1416 5 29
## time_hour
## 1: 2013-01-01 05:00:00
## 2: 2013-01-01 05:00:00
返回按某些列排序的结果,如:
在
[]
中的
order()
函数是data.table提供的改进版本,
可以用减号表示降序,
排序速度更快。
用
DT[,j]
可以将指定的列提取成一个R向量,
这与data.frame的做法相同,
但与tibble做法不同。
## [1] 11 20 33 -18 -25
注意变量名不需要用撇号保护。
选择多列,
或者选择一列但需要结果为data.table,
将这些列名写在
.()
内,如:
## arr_delay dep_delay
## 1: 11 2
## 2: 20 4
## 3: 33 2
## 4: -18 -1
## 5: -25 -6
取列子集时可以改变量名,
如
dt_flights[, .(dalaya = arr_delay, dalayd = dep_delay)]
。
因为在
j
的位置直接使用变量名,
而不是用撇号保护起来的变量名,
所以如果将变量名保存在了某个R变量中(如
vars
),
希望访问这样的列子集,
就需要用
..vars
的格式,
## arr_delay dep_delay
## 1: 11 2
## 2: 20 4
## 3: 33 2
## 4: -18 -1
## 5: -25 -6
可以在
DT[i,j,by]
的
j
位置计算新变量或者汇总统计量。
计算总延误小于0的个数:
## [1] 188401
取行子集并计算统计量:
dt_flights[origin == "JFK" & month == 6, .(m_arr = mean(arr_delay, na.rm=TRUE), m_dep = mean(dep_delay, na.rm=TRUE))]
## m_arr m_dep
## 1: 17.59693 20.49973
计算子集行数:
## [1] 9472
其中
.N
是data.table提供的特殊语法。
这比先取子集再用
nrow()
计算的好处是不需要先生成子集,
而是直接计数。
用
by =
指定一个或多个分组变量,
可以进行分组汇总。
## origin N
## 1: EWR 120835
## 2: LGA 104662
## 3: JFK 111279
注意
j
和
by =
的参数都用了
.()
保护。
当
j
和
by =
的参数仅有一个变量时,
也可以不用
.()
的格式,如:
## origin N
## 1: EWR 120835
## 2: LGA 104662
## 3: JFK 111279
by =
后面也可以使用保存了变量名的字符型变量,
## origin dest N
## 1: EWR IAH 3973
## 2: LGA IAH 2951
## 3: JFK MIA 3314
## 4: JFK BQN 599
## 5: LGA ATL 10263
## ---
## 220: LGA TVC 77
## 221: LGA MYR 3
## 222: EWR TVC 24
## 223: EWR ANC 8
## 224: EWR LGA 1
但是,这种语法设计有问题,
在上例中无法分辨
vars
是保存了变量名的变量,
还是数据框中的变量名,
直接使用变量名时用
by = .(dest)
这样的格式较好。
取子集并分组汇总计数的例子:
## origin N
## 1: JFK 13783
## 2: LGA 15459
## 3: EWR 3487
计算该子集按出发机场分组后的平均延误:
dt_flights[carrier == "AA", .(marr = mean(arr_delay, na.rm=TRUE), mdep = mean(dep_delay, na.rm=TRUE)), by = .(origin)]
## origin marr mdep
## 1: JFK 2.0812500 10.302155
## 2: LGA -1.3317539 6.705769
## 3: EWR 0.9776985 10.035419
用
by =
分组汇总时,
输出的各组次序是在原始数据中各组最先出现的次序。
为了使得汇总结果按组别取值排序,
将
by =
改为
keyby =
。
## origin N
## 1: EWR 120835
## 2: JFK 111279
## 3: LGA 104662
也可以先用
by
分组汇总,
再用
order()
排序,
这样还可以降序排列,
dt_flights[carrier == "AA", .(marr = mean(arr_delay, na.rm=TRUE), mdep = mean(dep_delay, na.rm=TRUE)), by = .(origin, dest)][order(origin, -dest)] |> head(6)
## origin dest marr mdep
## 1: EWR MIA 0.06332703 9.204524
## 2: EWR LAX 0.91643454 5.861496
## 3: EWR DFW 1.48612539 11.250254
## 4: JFK TPA 5.20000000 10.918831
## 5: JFK STT -4.49501661 4.036545
## 6: JFK SJU -0.77603687 8.600917
.SD
data.table的
[]
运算内用
.SD
表示对一个数据子集(Subset of Data)的处理。
用这个特殊语法,
可以在分组后统一对数据子集进行分析,
dt_flights[ carrier == "AA", lapply(.SD, \(x) mean(x, na.rm = TRUE)), by = .(origin, dest, month), .SDcols = c("arr_delay", "dep_delay")] |> head(6)
## origin dest month arr_delay dep_delay
## 1: JFK MIA 1 0.4789474 12.236842
## 2: LGA ORD 1 1.6497462 5.972152
## 3: LGA DFW 1 2.1135802 5.148780
## 4: EWR MIA 1 7.6559140 15.494624
## 5: LGA MIA 1 -4.8750000 2.823171
## 6: JFK SJU 1 1.5040650 11.766129
在上例中,
用
SDcols =
指定对每个数据子集要分析的列,
用
lapply(.SD, f)
指定对每个数据子集的每一要分析的列应用
f
进行汇总。
这里的
f
用了R的无名函数形式。
data.table使用特殊的
:=
运算符,
可以直接以引用方式增添、修改、删除data.table列,
这样做的好处是对大型数据节约了存储量,
提高了效率。
参见该包的帮助文档中名为“Reference semantics”的vignette。
data.table不再使用行名,
但可以指定一个或多个关键列,
起到与行名类似的作用,
但功能更强,
访问效率更高。
关键列不需要唯一区分各行。
用
setkey()
或者
setkeyv()
给data.table指定关键列,
这会使得数据框按照这些列的升序排列。
dt_flights["JFK"]
可以取出满足
origin == "JFK"
条件的行,
又如
dt_flights[c("JFK", "LGA")]
取出origin等于JFK或LGA的行。
这样取行子集时,
因为用了二分法定位,
所以在大型数据上比
dt_flights[origin == "JFK"]
这样的线性访问算法的行子集计算效率高得多。
data.table对每个表仅允许设置一组关键列,
而不允许设置多组。
可以指定多列作为关键列,
取出origin为JFK,dest为MIA的子集:
为了明确表示用了关键列帮助定位子集,
可以加上
on =
选项,如:
这个语法主要用在辅助索引列功能, 但是在使用关键列时, 也可以使得程序意图更为明确。
取出origin为JFK或LGA, dest为MIA的子集:
取出dest为MIA,origin为任意值的子集:
这里用了
unique(origin)
来获取origin的所有可取值。
在行下标中使用关键列后, 仍可以用列下标取列子集或汇总计算, 用by分组。
计算出发机场为JFK的航班每个月的延误最大值:
可以用
mult = "first"
要求仅返回每个组的第一行,
用
mult = "last"
仅返回每个组的最后一行。
可以用
nomatch = NULL
指定关键列的值对应的行不存在时,
就在结果中不包含对应的结果行。
关键列仅允许有一组, 而且指定关键列会按关键列对数据框排序, 这都是使用关键列的障碍。
data.table提供了辅助索引列(secondary indeces), 能够起到关键列的作用, 但是不需要对数据框重排, 还可以有多组。 增加辅助索引列, 仅增加一个按指定的列重排所用的行号序列。 还可以在取行子集时临时生成辅助索引列。
类似于
setkey()
和
setkeyv()
,
可以用
setindex()
和
setindexv()
设置关键列。
注意这设置了两组辅助索引列。
因为允许有多组辅助索引列,
所以在用辅助索引加速取行子集操作时,
需要用
on =
选项指定所用的辅助索引列,
与使用关键列类似,
仍可以取列子集或者进行汇总,
用
by =
或
sortby =
进行分组计算。
如果
on =
指定的辅助索引列没有预先建立索引,
这会临时创建需要的辅助索引,
并在完成查询任务后自动删除。
data.table设置了自动索引功能, 一旦根据某个变量值进行查询, 就自动设置该变量的辅助索引, 并且不会自动删除。
一对一横向合并,如:
dt1_class <- setDT(d1.class) dt2_class <- setDT(d2.class) dt3_class <- dt1_class[dt2_class, on = .(name)] dt3_class
## name sex age height weight
## 1: Karen F 12 56.3 77.0
## 2: Kathy F 12 59.8 84.5
## 3: Sandy <NA> 11 51.3 50.5
## 4: James M 12 57.3 83.0
## 5: John M 12 59.0 99.5
## 6: Robert M 12 64.8 128.0
## 7: Thomas M 11 57.5 85.0
一对一横向合并用了行子集的语法, 将要合并的右表写在左表的行子集位置。 这作的是右外连接, 包括与右表匹配的每一个观测, 左表中不存在的观测用缺失值代替, 仅在左表中存在的观测被忽略。
为了作内连接,
只要加选项
nomatch = NULL
。
如果左表与右表用来对齐的列名不相同,
可以用如
a[b, on = c(avar = "bvar")]
的形式。
结果中这一列的列名用
avar
,
取值则用
bvar
的值。
可以用
a[!b, on=.(id)]
的格式表示选取所有不能与b匹配的观测,
即反向连接(anti-join)。
这个语法还有许多功能,
用如下命令查看
[
运算功能:
用
melt()
函数将宽表转换为长表。
data.table可以对很大的表(比如,占用几个GB内存的表)进行高速转换。
dt_dwide1 <- setDT(dwide1) dt2 <- melt(dt_dwide1, id.vars = c("subject"), measure.vars = c("1", "2", "3", "4"), variable.name = "time", value.name = "y") dt2[!is.na(y)]
## subject time y
## 1: 1 1 1
## 2: 3 1 5
## 3: 2 2 7
## 4: 3 2 10
## 5: 4 3 9
## 6: 2 4 4
可以用
dcast()
将长表转换为宽表,如:
## subject 1 2 3 4
## 1: 1 1 NA NA NA
## 2: 2 NA 7 NA 4
## 3: 3 5 10 NA NA
## 4: 4 NA NA 9 NA
当有多个测量变量时, data.table也提供了相应的语法, 详见该包的帮助文档中题目为“Efficient reshaping using data.tables”的vignette。
关于data.table的使用, 还可参见:
|
|
干练的地瓜 · 食品饮料行业深度报告:为何说调味品是一门好生意_手机新浪网 11 月前 |
|
|
乐观的桔子 · 竺可桢与妻子的故事-立德树人网 11 月前 |
|
|
乐观的卤蛋 · 三英战吕布_百度百科 1 年前 |