《R语言医学数据分析实战》学习记录|第三章 数据框的操作
第三章 数据框的操作
内容记录
TLDR记录
- 数据框结构数据的基本属性(清单列表:
head(),tail(),摘要数据:str(),epiDisplay::des()) - 操作数据框数据:选取子集
subset(),增删元素 - dplyr包对数据框数据的操作:筛选,排序,增删变量,统计分析,拆分合并
- 数据框的长宽转换,一般应转换为长格式。
- 缺失值的识别和处理(删除、常规替换、插值补全)。
RAW记录
- 数据框的基本属性(和pandas中的dataframe非常像):
head(),tail(),names(),str(),attributes(),attr(),attributes()$var.labels,epiDisplay::des()
library(epiDisplay)
# 加载数据
data(Familydata)
# 探索数据结构structure
str(Familydata)
# 分别显示数据属性的全部信息
attributes(Familydata)
# 为var.labels变量的第一个和最后一个赋值
attr(Familydata, "var.labels")[1] <- "Identification Number"
attr(Familydata, "var.labels")[6] <- "Gender"
# 查看var.labels变量
attributes(Familydata)$var.labels
# des函数属于epiDisplay包,三方工具查看数据属性
des(Familydata)
- 数据框的操作:子集选取
subset(),随机取样sample(),排序order(),查重duplicated(),增删元素,感叹号!使得值取反,减号-取相反数;尽量避免将数据框添加到搜索路径中(个人意见)。
- 取子集的不同方式
# 以下两表达式等价
Familydata[,3]
Familydata$ht
# 以下两表达式等价
Familydata[1:3,c(3,4,6)]
Familydata[1:3,c("ht","wt", "sex")]
# 以下两表达式等价
Familydata[Familydata$sex == "F",]
subset(Familydata, sex == "F")
# subset函数变量2筛选行,变量3筛选列
subset(Familydata, sex == "F", select = c("ht", "wt"))
sample.rows <- sample(1:nrow(Familydata), size = 3)
sample.rows
# 得到的行顺序和sample.rows顺序一致
Familydata[sample.rows, ]
# 以下两表达式等价
# Familydata$age默认为升序,取-后变降序
Familydata[order(Familydata$age, decreasing = TRUE),]
Familydata[order(-Familydata$age), ]
# 以下示例如何删除重复的行
Familydata1 <- Familydata
Familydata1[12, ] <- Familydata[2, ]
table(duplicated(Familydata1$code))
which(duplicated(Familydata1$code))
# !操作使FALSE为TRUE
unique.code.data <- Familydata1[!duplicated(Familydata1$code),]
identical(unique.code.data, Familydata)
- dplyr包:统一的更规范高效地处理数据
- 按行筛选:
filter()和slice(),stats里有同名filter函数;按某变量做行排序arrange();按列筛选select(),MASS包有同名select;
library(dplyr)
data(birthwt, package = "MASS")
filter(birthwt, age > 35)
filter(birthwt, age > 35, bwt<2500 | bwt >4000)
# slice是按行号筛选
slice(birthwt, 3:6)
# arrange默认是升序
arrange(birthwt, bwt, age)
# 以下两表达式等价
arrange(birthwt, desc(bwt))
arrange(birthwt, -bwt)
select(birthwt, bwt, age, race, smoke)
-
不改变原dataframe增加元素/属性:添加新变量(新列)
mutate(data, 变量名=表达式value),变量名已存在则替代原变量;增加统计量summarise(data, (变量名=)统计量计算表达式);分组group_by(, 列名)不改变dataframe结构增加分组属性; -
传递符%>%,组合连接多个操作(函数),前一个操作的输出结果代替后一个操作的第一个输入参数。
以下代码的目的:将birthwt数据中的race变量转换成因子变量并添加对应标签,按race分组,计算各组中bwt的平均值
birthwt %>%
mutate(race = factor(race, labels = c("white", "black", "other"))) %>%
group_by(race) %>%
summarise(mean(bwt))
-
数据框的合并:纵向合并
rbind(),row-bind;横向合并cbind(),column-bind;按共有变量合并merge(data1, data2, by = "id"),或dplyr::full_join(data1, data2, by = "id");dplyr有相同功能的系列函数:bind_rows(),bind_cols(),left_join(),right_join(); -
数据框形式的长宽变换
- 系统自带函数
reshape(),但参数名不清晰,具体使用时看help了解参数; - 简洁版函数:
tidyr::pivot_wider(data,names_from="从原数据中获得新数据的列名来源",values_from="从数据中获得新数据的值来源" )和tidyr::pivot_longer(data, cols(从原数据获得新的宽数据的关键列),names_to="原数据中的列名变成新数据的某一列的值,这个列的名字",values_to="原数据中的值变成新数据的某一列的值,这个列名"); - 数据分析之前一般应转换为长格式。
- 缺失值的处理
- 识别缺失值
is.na(),系统内置函数对NA值的处理
# 以mean函数为例了解函数自带对NA值的处理
height <- c(100,150,NA,160)
# 以下两表达式结果等价
mean(height, na.rm = TRUE)
mean(na.omit(height))
# 区别:na.omit()是独立函数,忽略输入对象中的缺省值,na.rm只是函数的一个内部参数
# 部分函数“天生”忽略NA值,如summary
summary(height)
- 探索缺失值:以经典的iris数据集来举例NA值的探索模式
library(missForest)
data("iris")
# 生成随机数种子,使结果具有可重复性
set.seed(1234)
# prodNA函数来自missForest包,人为产生NA值,默认生成10%的数据为NA(noNA参数控制)
iris.miss <- prodNA(iris)
summary(iris.miss)
library(VIM)
# VIM::aggr()函数展示NA值的分布情况,具体函数和参数介绍见第4章
aggr(iris.miss, prop=FALSE, numbers=TRUE, cex.axis=0.7)
- 填充缺失值的常用方式:(1)删除,删除带有缺失值的变量或记录,如
na.omit();(2)替换,用均值、中位数、众数或其他值替代,如均值mean()填充;(3)补全,基于统计模型推测和补充缺失值,如多重插补函数包library(mice)
# 1. 缺失值数量很小时,可以做删除操作。
# 以下两行语句等价
iris.sub <- na.omit(iris.miss)
iris.sub <- iris.miss[complete.cases(iris.miss),]
# 2. 不想删除缺失值时,可以用特定值替代,比如平均值
Sepal.Length.Mean <- mean(iris.miss$Sepal.Length, na.rm = TRUE)
iris.miss1 <- iris.miss
iris.miss1$Sepal.Length[is.na(iris.miss1$Sepal.Length)] <- Sepal.Length.Mean
# 查看补齐后的数据分布情况,可以看到偏差平均在0.6%,极值在25%左右
summary((iris$Sepal.Length-iris.miss1$Sepal.Length)/iris$Sepal.Length)
# 3. 多重插补(multiple imputation)是基于重复模拟的处理缺失值的方法。R中实现该方法的包有Amelia、mi、mice等。其中 mice 包使用链式方程的多变量补全法。mice包假设数据是随机缺失的,并根据变量的类型建立模型得到预测值以代替缺失值。
# 常用预测方法有:(1)预测均值匹配(pmm),实质上就是线性回归,适用于数值型变量;(2)Logistic 回归 (logreg),适用于二分类变量;(3)多分类 Logistic回归(ployreg),适用于无序多分类变量;(4)比例优势比模型(polr),适用于有序多分类变量。
# 用函数 mice()补全数据框 iris.miss 里的缺失值。
library(mice)
imputated.data <- mice(iris.miss, seed = 1234)
summary(imputated.data)
# 输出结果的矩阵 PredictorMatrix 里,每一行代表含有缺失值的变量名,如果该行对应的某一列元素为 1,代表该列变量被用于建模预测。本例中,对于每一个变量,其余变量都被用于它的缺失值预测。
# 函数 mice( ) 的输出结果是一个列表,其中的对象 imp 也是一个列表,存放的是每个变量缺失值的插补值。
# 观察生成的Sepal.Length的值可知,mice函数通过Gibbs抽样完成5次随机抽样(5列)。
imputated.data$imp$Sepal.Length
# 选择其中第3组来补全
complete.data <- complete(imputated.data, 3)
# 对于数值型变量,可以计算插补值与原始变量值的偏差。
summary((iris$Sepal.Length-complete.data$Sepal.Length)/iris$Sepal.Length)
# 查看补齐后的数据分布情况,可以看到偏差平均在0.07%,极值在9%-14%左右。比均值方法好。
table(iris$Species, complete.data$Species)
# 这种表被称为混淆矩阵(confusion matrix),经常用于评价模型预测的准确度。对角线上的数字代表预测值和真实值一致的个数,非对角线上的数字代表预测值和真实值不一致的个数。从上面的输出结果可以看出,变量 Species 的19个缺失值插补的正确率约为100%。
- 大型数据的处理策略
- 清理工作空间:
rm(list = ls(all = TRUE));ls()显示当前工作空间的对象,参数all=TRUE则清除对象包括隐藏对象。
rm(list = ls(all = TRUE))
-
读取csv文件:utils系统自带的
read.csv(),对于大型数据,处理速度很慢;readr::read_csv();data.table::fread()。三个函数读取速度递增,最后一个学习成本高,但值得投入时间学习。 -
模拟大数据集:
rnorm()生成服从标准正态分布的随机数
# 模拟生成一个50000*200的数据集
bigdata <- as.data.frame(matrix(rnorm(50000*200),ncol = 200))
# 快速生成一个200个变量的变量名群
varnames <- NULL
for(i in letters[1:20]){
for (j in 1:10){
varnames <- c(varnames, paste(i, j, sep = "_"))
}
}
names(bigdata) <- varnames
# 如果不想用循环产生名字,可以用以下方式
# apply函数会产生多余空格
# apply(expand.grid(1:20, letters[1:20]), 1, function(x) paste(x[2], x[1], sep="_"))
sprintf("%s_%s", expand.grid(1:10,letters[1:20])[,2],expand.grid(1:10,letters[1:20])[,1])
as.vector(t(outer(letters[1:20], 1:10, paste, sep="_")))
- 剔除不需要的变量:tidyselect包的
starts_with(),ends_with(),contains()函数辅助做筛选过程。
library(tidyselect)
library(dplyr)
# 按前缀或后缀选择
subdata1 <- select(bigdata, starts_with("a"))
names(subdata1)
subdata2 <- select(bigdata, ends_with("2"))
names(subdata2)
# 按或关系筛选数据,a开头或b开头
subdata3 <- select_at(bigdata, vars(starts_with("a"), starts_with("b")))
names(subdata3)
# 按包含关系筛选
subdata4 <- select_at(bigdata, vars(contains("1")))
names(subdata4)
# contains("1")是包含10的数字的,下方式可去除
subdata4.1 <- select_at(bigdata, vars(contains("1"), -contains("10")))
names(subdata4.1)
# 要剔除指定变量,在对应函数前加上-号
subdata5 <- select_at(bigdata, vars(-contains("1"), -contains("5")))
names(subdata5)
- 选取随机样本:随机采样函数
sample_n(data, size = 行数),sample_frac(data, size = 抽样比例)
sampledata1 <- sample_n(bigdata, size = 500)
nrow(sampledata1)
sampledata2 <- sample_frac(bigdata, size = 0.02)
nrow(sampledata2)
习题
3-1 构建一个数据集:
下表中的数据来源于某地方病研究机构关于大骨节病患儿的一项调查研究。其中肌酐含量为 24 小时测得的尿肌酐(单位为 mmol)。分类变量 group 中,0 代表未患病(正常)儿童,1 代表患病儿童。(1)将数据录入 R 并保存为数据框 UCR;(2)为数据框和数据框中的变量加上合适的标签;(3)将添加标签后的数据框保存为 R 数据文件 UCR.rdata。
# 构建数据
age <- c(13, 11, 9, 6, 8, 10, 12, 7, 10, 9, 11, 12, 15, 16, 8, 7, 10, 15)
ucr <- c(3.54, 3.01, 3.09, 2.48, 2.56, 3.36, 3.18, 2.65, 3.01, 2.83, 2.92, 3.09, 3.98, 3.89, 2.21, 2.39, 2.74, 3.36)
group <- c(rep(0,8), rep(1,10))
group <- factor(group, levels = 0:1, labels = c("Normal", "Patient"))
UCR <- data.frame(age, ucr, group)
# 设置属性
str(UCR)
attr(UCR, "datalabel") <- "UCR in Kaschin-Beck disease children"
attr(UCR, "var.labels") <- c("Age in years",
"Urine creatinine (mmol)",
"Type of children")
# 保存数据
save(UCR, file = "UCR.rdata")
3-2 导入上述数据,并选取患者的子数据集
将上面创建的数据文件 UCR.rdata 导入 R 中,并选取患病儿童的子数据集。
load("UCR.rdata")
subset(UCR, group == "Patient")
3-3 导入Planning数据集,查看数据信息并整理数据
epiDisplay 包里的数据集 Planning 来自 20 世纪 80 年代中期泰国的一项计划生育调查研究,请通过其帮助文件查看数据信息并整理该数据集。
library(epiDisplay)
data(Planning)
# 观察数据概况:列描述des() 数据统计摘要summary()
des(Planning)
names(Planning) <- tolower(names(Planning)) # 把变量名变为小写字母
summary(Planning)
table(duplicated(Planning$id)) # 查看是否有重复id
which(duplicated(Planning$id)) # 找出重复id的行号
Planning$id
Planning$id[216] <- 216 # 修正重复id
library(dplyr)
Planning <- mutate(
Planning,
relig = ifelse(relig == 9, NA, relig), # 将变量relig中的9变成NA
ped = ifelse(ped == 0 | ped == 9, NA, ped), # 将变量ped中的0和9变成NA
income = ifelse(income == 9, NA, income), # 将变量income中的9变成NA
am = ifelse(am == 99, NA, am), # 将变量am中的99变成NA
reason = ifelse(reason == 9, NA, reason), # 将变量reason中的9变成NA
bps = ifelse(bps == 0 | bps == 999, NA, bps), # 将变量bps中的0和999变成NA
bpd = ifelse(bpd == 0 | bpd == 999, NA, bpd), # 将变量bpd中的0和999变成NA
wt = ifelse(wt == 0 | wt > 99, NA, wt), # 将变量wt中的0和大于99的值变成NA
ht = ifelse(ht == 0 | ht > 300, NA, ht) # 将变量ht中的0和大于300的值变成NA
)
# 数据结构str()
str(Planning)
3-4 长格式和宽格式的互相转换
MASS 包里的数据集 bacteria 是长格式,其中的变量 week 为时间变量,变量 y 为结局变量。试将其转换成宽格式,然后再将宽格式数据重新转换回原来的长格式。请注意数据中的缺失值。
使用函数reshape()
data(bacteria, package = "MASS")
wide <- reshape(bacteria, idvar = "ID", timevar = "week", v.names = "y", direction = "wide")
long <- reshape(wide, idvar = "ID", varying = list(5:9), v.names = "y", direction = "long")
long <- na.omit(long)
使用tidyr包
library(tidyr)
wide <- pivot_wider(bacteria, names_from = week, values_from = y)
long <- pivot_longer(wide, -c(1:4),names_to = "week", values_to = "y")
long <- na.omit(long)
3-5 使用mice包计算补齐
试探索 VIM 包的数据集 sleep 中的缺失值模式,并使用 mice 包补全缺失数据。
library(VIM)
data(sleep)
aggr(sleep, prop = FALSE, numbers = TRUE)
summary(sleep)
library(mice)
# 设立随机数种子,使用mice函数补全NA值形成补全值的dataframe
imp <- mice(sleep, seed = 1234)
complete.data <- complete(imp, 3)
summary(complete.data)

浙公网安备 33010602011771号