本文作者:张敬信
玩转数据处理120题之P1-P20(R语言tidyverse版本)
玩转数据处理120题之P21-P50(R语言tidyverse版本)
玩转数据处理120题之P51-P80(R语言tidyverse版本)
玩转数据处理120题之P81-P100(R语言tidyverse版本)
玩转数据处理120题之P101-P120(R语言tidyverse版本)
关于代码学习:不要复制粘贴,一个一个敲出来
一直都在想怎么开启R的更新,很多人都觉得R就是用来画图,其实不然,R做数据分析也是一个很不错的工具。生信中大量需要做数据分析,因此在进行R语言绘图前,有必要推荐数据处理120题,把dirty data 变成 clean data。这也是耗时最长的部分,考验耐心的时候到了。数据分析流程如下图。(生物数据分析也是如此)
本文非常适合初学者,是张老师根据刘早起老师的panda版本更改而来,我自己也把这120个题目敲下来,又有一些感悟。感谢张老师,也感谢原作者早起python:刘早起。
题目1(创建数据框):创建DataFrame
代码及运行结果:
### 方法1
df <- data.frame(
"grammer" = c("Python","C","Java","GO",NA,"SQL","PHP","Python"),
"score" = c(1,2,NA,4,5,6,7,10)
)
df
### 方法2
library(tibble)
df <- tibble(
"grammer" = c("Python","C","Java","GO",NA,"SQL","PHP","Python"),
"score" = c(1,2,NA,4,5,6,7,10)
)
问题2(筛选行):提取含字符串"Python"的行
代码及运行结果:
df %>%
filter(grammer == "Python")
题目3(查看列名):输出df的所有列名
代码及运行结果:
names(df)
题目4(改列名):改第2列列名为popularity
代码及运行结果:
df = df %>%
rename(popularity = score)
df
题目5(统计频数):统计grammer列中每种编程语言出现的次数
代码及运行结果:
df %>%
count(grammer) # 或者用 table(df$grammer)
题目6(缺失值处理):将空值用上下值的平均值填充
代码及运行结果:
df = df %>%
mutate(popularity = zoo::na.approx(popularity))
df
注:dplyr包提供了fill()函数,可以用前值或后值插补缺失值。
题目7(筛选行):提取popularity列中值大于3的行
代码及运行结果:
df %>%
filter(popularity > 3)
题目8(数据去重):按grammer列进行去重
代码及运行结果:
df %>%
distinct(grammer, .keep_all = TRUE)
题目9(数据计算):计算popularity列平均值
代码及运行结果:
df %>%
summarise(popularity_avg = mean(popularity))
题目10(格式转换):将grammer列转换为序列
代码及运行结果:
df$grammer
注:R从数据框中提取出来就是字符向量。
题目11(数据保存):将数据框保存为Excel
代码及运行结果:
writexl::write_xlsx(df, "filename.xlsx")
题目12(数据查看):查看数据的行数列数
代码及运行结果:
dim(df)
题目13(筛选行):提取popularity列值大于3小于7的行
代码及运行结果:
df %>%
filter(popularity > 3 & popularity < 7)
题目14(调整列位置):交互两列的位置
代码及运行结果:
df %>%
select(popularity, grammer)
注:可配合everything()放置“其余列”,更强大的调整列位置的函数是dplyr1.0将提供的relacate().
题目15(筛选行):提取popularity列最大值所在的行
代码及运行结果:
df %>%
filter(popularity == max(popularity))
# 或者用df %>% top_n(1, popularity)
题目16(查看数据):查看最后几行数据
代码及运行结果:
tail(df) # 默认是最后6行
注:此外,head()查看前几行,dplyr包还提供了sample_n()和sample_frac()随机查看n行或某比例的行。
题目17(修改数据):删除最后一行数据
代码及运行结果:
df %>%
slice(-n())
题目18(修改数据):添加一行数据:"Perl", 6
代码及运行结果:
newrow = tibble(grammer="Perl", popularity=6)
df %>%
bind_rows(newrow)
题目19(数据整理):对数据按popularity列值从到大到小排序
代码及运行结果:
df %>%
arrange(desc(popularity))
注:不套一层desc(), 是默认从小到大排序。
题目20(字符统计):统计grammer列每个字符串的长度
代码及运行结果:
df %>%
mutate(strlen = str_length(grammer))
题目21(读取数据):读取本地Excel数据
代码及运行结果:
df = readxl::read_xlsx("21-50数据.xlsx")
df
题目22(查看数据):查看df数据的前几行
代码及运行结果:
head(df)
注:此外,tail()查看后几行,dplyr包还提供了sample_n()和sample_frac()随机查看n行或某比例的行。
题目23(数据计算):将salary列数据转换为最大值与最小值的平均值
代码及运行结果:
df = df %>%
separate(salary, into = c("low", "high"), sep = "-") %>% # sep="-"也可以省略
mutate(salary = (parse_number(low) + parse_number(high)) * 1000 / 2) %>%
select(-c(low, high))
df
或者来个高级的,用正则表达式提取数字,定义做计算的函数,再purrr::map_dbl做循环计算:
calc = function(x) sum(as.numeric(unlist(x))) * 1000 / 2
df %>%
mutate(salary = map_dbl(str_extract_all(salary, "d+"), calc)) # 结果同上
题目24(分组汇总):根据学历分组,并计算平均薪资
代码及运行结果:
df %>%
group_by(education) %>%
summarise(salary_avg = mean(salary))
题目25(时间转换):将createTime列转换为"月-日"
代码及运行结果:
library(lubridate)
df %>%
mutate(createTime = str_c(month(createTime), "-", day(createTime)))
题目26(查看数据):查看数据结构信息
代码及运行结果:
df %>%
glimpse() # 或者用str()
object.size(df) # 查看对象占用内存
题目27(查看数据):查看数据汇总信息
代码及运行结果:
summary(df)
题目28(修改列):新增一列将salary离散化为三水平值
代码及运行结果:
df = df %>%
mutate(class = case_when(
salary >= 0 & salary < 5000 ~ "低",
salary >= 5000 & salary < 20000 ~ "中",
TRUE ~ "高")) # TRUE效果是其它
df
或者用cut()函数:
df %>%
mutate(class = cut(salary,
breaks = c(0,5000,20000,Inf),
labels = c("低", "中", "高")))
注:也可以用sjmisc包中的rec(),和SPSS的重新编码一样强大。
题目29(数据整理):按salary列对数据降序排列
代码及运行结果:
df %>%
arrange(desc(salary))
题目30(筛选行):提取第33行数据
代码及运行结果:
df[33,]
或者用
df %>%
slice(33)
题目31(数据计算):计算salary列的中位数
代码及运行结果:
median(df$salary)
或者用
df %>%
summarise(salary_med = median(salary))
题目32(数据可视化):绘制salary的频率分布直方图
代码及运行结果:
df %>%
ggplot(aes(x = salary)) +
geom_histogram(bins = 10)
题目33(数据可视化):绘制salary的频率密度曲线图
代码及运行结果:
df %>%
ggplot(aes(x = salary)) +
geom_density()
题目34(数据删除):删除最后一列class
代码及运行结果:
df %>%
select(-class)
题目35(数据操作):将df的第1列与第2列合并为新的一列
代码及运行结果:
df %>%
unite("newcol", 1:2, sep = " ")
题目36(数据操作):将education列与第salary列合并为新的一列
代码及运行结果:
df %>%
unite("newcol", c(education, salary), sep = " ")
题目37(数据计算):计算salary最大值与最小值之差
代码及运行结果:
max(df$salary) - min(df$salary)
或者用
df %>%
summarise(range = max(salary) - min(salary))
题目38(数据操作):将第一行与最后一行拼接
代码及运行结果:
bind_rows(df[1,], df[nrow(df),])
题目39(数据操作):将第8行添加到末尾
代码及运行结果:
bind_rows(df, df[8,]) %>%
tail()
题目40(查看数据):查看每一列的数据类型
代码及运行结果:
df %>%
glimpse() # 或者用str()
题目41(数据操作):将createTime列设置为行索引
代码及运行结果:
df %>%
distinct(createTime, .keep_all = TRUE) %>%
column_to_rownames("createTime")
注:行索引不允许有重复,所以先做了一步去重。
题目42(数据创建):生成一个和df长度相同的随机数数据框
代码及运行结果:
df1 = tibble(rnums = sample.int(10, nrow(df), replace = TRUE))
df1
题目43(数据连接):将上面生成的数据框与df按列合并
代码及运行结果:
df = bind_cols(df, df1)
df
题目44(修改列):生成新列new为salary列减去随机数列
代码及运行结果:
df = df %>%
mutate(new = salary - rnums)
df
题目45(检查缺失值):检查数据中是否含有任何缺失值
代码及运行结果:
anyNA(df)
anyNA(df$salary)
注:naniar包提供了更强大的探索缺失值及缺失模式的函数,其中miss_var_summary()和miss_case_summary()可检查各列和各行缺失情况。
题目46(类型转换):将salary列的类型转换为浮点数
代码及运行结果:
df %>%
mutate(rnums = as.double(rnums))
题目47(数据汇总):计算salary列大于10000的次数
代码及运行结果:
df %>%
summarise(n = sum(salary > 10000))
或者用
df %>%
count(salary > 10000)
题目48(统计频数):查看每种学历出现的次数
代码及运行结果:
df %>%
count(education)
或者用
table(df$education)
题目49(数据汇总):查看education列共有几种学历
代码及运行结果:
df %>%
distinct(education)
题目50(筛选行):提取salary与new列之和大于60000的最后3行
代码及运行结果:
df %>%
filter(salary + new > 60000) %>%
slice((n()-2):n())
题目51(读取数据):使用绝对路径读取本地Excel数据
代码及运行结果:
df = readxl::read_xls("E:/R语言/R语言学习系列/R数据操作/codes/120problams/51-80数据.xls")
df
题目52(查看数据):查看数据框的前3行
代码及运行结果:
df %>%
head(3)
题目53(查看缺失值):查看每列数据缺失值情况
代码及运行结果:
library(naniar)
df %>%
miss_var_summary()
题目54(查看缺失值):查看日期列含有缺失值的行
代码及运行结果:
df %>%
filter(is.na(日期))
which(is.na(df$日期)) # 日期列缺失的行号
题目55(查看缺失值):查看每列缺失值在哪些行
代码及运行结果:
naIdx = df %>%
where_na() # 返回NA的行列索引, 需要naniar包
split(naIdx[,1], naIdx[,2])
题目56(缺失值处理):删除所有存在缺失值的行
代码及运行结果:
df %>%
drop_na()
注:若要删除某些列包含缺失值的行,提供列名即可。
题目57(数据可视化):绘制收盘价的折线图
代码及运行结果:
df %>%
ggplot(aes(日期, `收盘价(元)`)) +
geom_line()
题目58(数据可视化):同时绘制开盘价与收盘价
代码及运行结果:
df %>%
select(日期, `开盘价(元)`, `收盘价(元)`) %>%
pivot_longer(-日期,
names_to = "type",
values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
注:为了自动添加图例,先对数据做了宽变长转换。
题目59(数据可视化):绘制涨跌幅的直方图
代码及运行结果:
df %>%
ggplot(aes(`涨跌幅(%)`)) +
geom_histogram()
题目60(数据可视化):让直方图更细致
代码及运行结果:
df %>%
ggplot(aes(`涨跌幅(%)`)) +
geom_histogram(bins = 40)
题目61(数据创建):用df的列名创建数据框
代码及运行结果:
as_tibble(names(df))
题目62(异常值处理):输出所有换手率不是数字的行
代码及运行结果:
df %>%
mutate(`换手率(%)` = parse_number(`换手率(%)`)) %>%
filter(is.na(`换手率(%)`))
题目63(异常值处理):输出所有换手率为--的行
代码及运行结果:
df %>%
filter(`换手率(%)` == "--")
题目64(数据操作):重置df的行号
代码及运行结果:
rownames(df) = NULL # R中无行号就是数字索引
题目65(异常值处理):删除所有换手率为非数字的行
代码及运行结果:
df %>%
mutate(`换手率(%)` = parse_number(`换手率(%)`)) %>%
filter(!is.na(`换手率(%)`))
补充:为了便于后续处理,做批量数值型转化, 并转化为tsibble对象
library(tsibble)
df = df %>%
mutate_at(vars(4:18), as.numeric) %>%
mutate(日期 = lubridate::as_date(日期)) %>%
as_tsibble(index = 日期, key = c(代码, 简称))
df
题目66(数据可视化):绘制换手率的密度曲线
代码及运行结果:
df %>%
ggplot(aes(`换手率(%)`)) +
geom_density()
题目67(数据计算):计算前一天与后一天收盘价的差值
代码及运行结果:
df %>%
mutate(delta = `收盘价(元)` - lag(`收盘价(元)`)) %>%
select(日期, `收盘价(元)`, delta)
题目68(数据计算):计算前一天与后一天收盘价的变化率
代码及运行结果:
df %>%
mutate(change = (`收盘价(元)` - lag(`收盘价(元)`)) / `收盘价(元)`) %>%
select(日期, `收盘价(元)`, change)
题目69(数据操作):设置日期为行索引
代码及运行结果:
df %>%
column_to_rownames("日期")
题目70(数据计算):对收盘价做步长为5的滑动平均
代码及运行结果:
df %>%
mutate(avg_5 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE,
.size = 5, .align = "center")) %>%
select(日期, `收盘价(元)`, avg_5)
题目71(数据计算):对收盘价做步长为5的滑动求和
代码及运行结果:
df %>%
mutate(sum_5 = slide_dbl(`收盘价(元)`, sum, na.rm = TRUE,
.size = 5, .align = "center-left")) %>%
select(日期, `收盘价(元)`, sum_5)
题目72(数据可视化):将收盘价及其5日均线、20日均线绘制在同一个图上
代码及运行结果:
df %>%
mutate(avg_5 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE, .size = 5,
.align = "center"),
avg_20 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE, .size = 20,
.align = "center-left")) %>%
pivot_longer(c(`收盘价(元)`, avg_5, avg_20),
names_to = "type",
values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
题目73(数据重采样):按周为采样规则,计算一周收盘价最大值
代码及运行结果:
weekmax = df %>%
index_by(weeks = ~ yearweek(.)) %>% #周度汇总
summarise(max_week = max(`收盘价(元)`, na.rm = TRUE))
weekmax
题目74(数据可视化):绘制重采样数据与原始数据
代码及运行结果:
ggplot() +
geom_line(data = df, aes(日期, `收盘价(元)`), color = "steelblue") +
geom_line(data = weekmax, aes(weeks, max_week), color = "red")
题目75(数据操作):将数据往后移动5天
代码及运行结果:
bind_cols(df[,1:3], map_dfc(df[,-(1:3)], lag, n = 5))
注:这是批量做后移,单个变量做后移用mutate(var = lag(var, 5)即可。
题目76(数据操作):将数据往前移动5天
代码及运行结果:
bind_cols(df[,1:3], map_dfc(df[,-(1:3)], lead, n = 5))
题目77(数据操作):计算开盘价的累积平均
代码及运行结果:
rlt = df %>%
mutate(累积平均 = cummean(`开盘价(元)`)) %>%
select(日期, `开盘价(元)`, 累积平均)
rlt
题目78(数据计算):绘制开盘价的累积平均与原始数据的折线图
代码及运行结果:
rlt %>%
pivot_longer(-日期, names_to = "type", values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
题目79(数据计算):计算布林指标
代码及运行结果:
boll = df %>%
mutate(avg_20 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE, .size = 20,
.align = "center-left"),
sd_20 = slide_dbl(`收盘价(元)`, sd, na.rm = TRUE, .size = 20,
.align = "center-left"),
up = avg_20 + 2 * sd_20,
down = avg_20 - 2 * sd_20) %>%
select(日期, `收盘价(元)`, avg_20, up, down)
boll %>% sample_n(10)
题目80(数据可视化):绘制布林曲线
代码及运行结果:
boll %>%
pivot_longer(-日期, names_to = "type", values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
题目81(加载查看包):加载并查看tidyverse包版本
代码及运行结果:
library(tidyverse)
题目82(生成随机数):生成20个0~100的随机数,创建数据框
代码及运行结果:
set.seed(123) # 保证结果出现
df1 = tibble(nums = sample.int(100, 20))
df1
题目83(生成等差数):生成20个0~100固定步长的数,创建数据框
代码及运行结果:
df2 = tibble(nums = seq(0, 99, by = 5))
df2
题目84(生成指定分布随机数):生成20个标准正态分布的随机数,创建数据框
代码及运行结果:
set.seed(111)
df3 = tibble(nums = rnorm(20, 0, 1))
df3
题目85(合并数据):将df1, df2, df3按行合并为新数据框
代码及运行结果:
bind_rows(df1, df2, df3)
题目86(合并数据):将df1, df2, df3按列合并为新数据框
代码及运行结果:
df = bind_cols(df1, df2, df3)
df
题目87(查看数据):查看df所有数据的最小值、25%分位数、中位数、75%分位数、最大值
代码及运行结果:
unlist(df) %>%
summary()
题目88(修改列名):修改列名为col1, col2, col3
代码及运行结果:
df = df %>%
set_names(str_c("col", 1:3))
df
注:若只修改个别列名,用rename(newname=oldname).
题目89(数据操作):提取在第1列中而不在第2列中的数
代码及运行结果:
setdiff(df$col1, df$col2)
题目90(数据操作):提取在第1列和第2列出现频率最高的三个数字
代码及运行结果:
tibble(nums = c(df$col1, df$col2)) %>%
group_by(nums) %>%
summarise(frq = n()) %>%
arrange(desc(frq)) %>%
slice(1:3)
或者用
c(df$col1, df$col2) %>%
table() %>%
sort(decreasing = TRUE) %>%
.[1:3]
或者用
rlt = tibble(nums = c(df$col1, df$col2)) %>%
sjmisc::frq(nums, sort.frq = "desc")
rlt[[1]][1:3,]
题目91(数据操作):提取第1列可以整除5的数的位置
代码及运行结果:
which(df$col1 %% 5 == 0)
题目92(数据计算):计算第1列的1阶差分
代码及运行结果:
df %>%
mutate(diff1 = col1 - lag(col1))
注:若只是要数值,用diff(df$col1)即可。
题目93(数据操作):将col1, col2, col3三列顺序颠倒
代码及运行结果:
df %>%
select(rev(names(df)))
注:更灵活的调整列序,dplyr1.0将提供relocate()函数。
题目94(数据操作):提取第一列位置在1,10,15的数
代码及运行结果:
df[c(1,10,15),1]
或者用
df %>%
select(col1) %>%
slice(1,10,15)
题目95(数据操作):查找第一列的局部最大值位置
代码及运行结果:
rlt = df %>%
mutate(diff = sign(col1 - lag(col1)) + sign(col1 - lead(col1)))
which(rlt$diff == 2)
题目96(数据计算):按行计算df每一行的均值
代码及运行结果:
rowMeans(df) # 或者 apply(df, 1, mean)
题目97(数据计算):对第二列计算步长为3的移动平均值
代码及运行结果:
df %>%
mutate(avg_3 = tsibble::slide_dbl(col2, mean, .size = 3, .align = "center"))
题目98(数据计算):按第三列值的大小升序排列
代码及运行结果:
df %>%
arrange(col3)
题目99(数据操作):按第一列大于50的数修改为"高"
代码及运行结果:
df %>%
mutate(col1 = sjmisc::rec(col1, rec = "50:max=高; else=copy"))
# 或者用 df[df$col1 > 50, 1] = "高"
注:这里采用更有实用价值的重新编码。
题目100(数据计算):计算第一列与第二列的欧氏距离
代码及运行结果:
dist(t(df[,1:2]))
题目101(数据读取):从csv文件中读取指定数据:读取前10行, positionName和salary列
代码及运行结果:
read.csv("数据1_101-120涉及.csv", nrows = 10) %>%
select(positionName, salary)
注1:该数据是GBK编码,为避免中文乱码,GBK编码的csv或txt用read.csv()读取;UTF-8编码的csv或txt用readr::read_csv()读取;若用read_csv()读取GBK编码文件,需要设置编码(见题目110)。
注2:R中常规读取数据不能在读取时选择列,采用读取之后选择列。
题目102(数据读取):从csv文件中读取数据,将薪资大于10000的改为"高"
代码及运行结果:
df = read_csv("数据2_101-120涉及.csv") %>%
mutate(薪资水平 = if_else(薪资水平 > 10000, "高", "低"))
题目103(数据操作):从df中对薪资水平每隔20行进行抽样
代码及运行结果:
df %>%
slice(seq(1, n(), by = 20))
# 或者用df[seq(1, nrow(df), 20),]
题目104(数据操作):取消使用科学记数法
代码及运行结果:
set.seed(123)
df = tibble(data = runif(10) ^ 10) %>%
round(3)
df
题目105(数据操作):将上一题的数据转换为百分数
代码及运行结果:
df %>%
mutate(val = scales::percent(val, 0.01))
题目106(数据操作):查找上一题数据中第3大值的行号
代码及运行结果:
order(df$val, decreasing = TRUE)[3]
题目107(数据操作):反转df的行
代码及运行结果:
df %>%
slice(rev(1:n()))
# 或者 df[rev(1:nrow(df)),]
题目108(数据连接:全连接):根据多列匹配合并数据,保留df1和df2的观测
代码及运行结果:
df1 <- tibble(
key1 = c("K0","K0","K1","K2"),
key2 = c("K0","K1","K0","K1"),
A = str_c('A', 0:3),
B = str_c('B', 0:3)
)
df2 <- tibble(
key1 = c("K0","K1","K1","K2"),
key2 = str_c("K", rep(0,4)),
C = str_c('C', 0:3),
D = str_c('D', 0:3)
)
df1
df2
df1 %>%
full_join(df2, by = c("key1", "key2"))
题目109(数据连接:左连接):根据多列匹配合并数据,只保留df1的观测
代码及运行结果:
df1 %>%
left_join(df2, by = c("key1", "key2"))
注:dplyr包还提供了右连接:right_join(),内连接:inner_join(),以及用于过滤的连接:半连接:semi_join(),反连接:anti_join().
题目110(数据处理):再次读取数据1并显示所有列
代码及运行结果:
df <- read_csv("数据1_101-120涉及.csv",
locale = locale(encoding = "GBK")) %>%
glimpse()
题目111(数据操作):查找secondType与thirdType值相等的行号
代码及运行结果:
which(df$secondType == df$thirdType)
题目112(数据操作):查找薪资大于平均薪资的第三个数据
代码及运行结果:
df %>%
filter(salary > mean(salary)) %>%
slice(3)
题目113(数据操作):将上一题数据的salary列开根号
代码及运行结果:
df %>%
mutate(salary_sqrt = sqrt(salary)) %>%
select(salary, salary_sqrt)
题目114(数据操作):将上一题数据的linestation列按_拆分
代码及运行结果:
df %>%
separate(linestaion, into = c("line", "station"), sep = "_", remove = FALSE) %>%
select(linestaion, line, station)
注:正常需要先按“;”分割,再分别按“-”分割。
题目115(数据查看):查看上一题数据一共有多少列
代码及运行结果:
ncol(df)
题目116(数据操作):提取industryField列以"数据"开头的行
代码及运行结果:
df %>%
filter(str_detect(industryField, "^数据"))
题目117(数据分组汇总):以salary score和positionID做数据透视表
代码及运行结果:
df %>%
group_by(positionId) %>%
summarise(salary_avg = mean(salary),
score_avg = mean(score))
题目118(数据分组汇总):同时对salary、score两列进行汇总计算
代码及运行结果:
df %>%
summarise_at(vars(salary, score), list(~sum(.), ~mean(.), ~min(.)))
注:若要分组再这样汇总,前面加上group_by(grpvar)即可;若改用dplyr1.0的cross()会更加简洁。
题目119(数据分组汇总):同时对不同列进行不同的汇总计算:对salary求平均,对score求和
代码及运行结果:
df %>%
summarise(salary_avg = mean(salary),
score_sum = sum(score))
注:若要分组再这样汇总,前面加上group_by(grpvar)即可。
题目120(数据分组汇总):计算并提取平均薪资最高的区
代码及运行结果:
df %>%
group_by(district) %>%
summarise(salary_avg = mean(salary)) %>%
top_n(1, salary_avg)
虽然题目不是很难,但是涉及的面还是比较广的,在进行R语言绘图前(文中介绍了部分数据可视化的内容,但对于科研绘图来讲还有很多不足),充分了解R的数据处理操作十分必要,数据分析大量的时间是在数据预处理的工作上,这部分内容的技巧性较强,还需要多多理解,多总结经验,再次感谢作者整理~