title |
author |
date |
header-includes |
output |
**玩转数据120题——R语言tidyverse版本(2023版)** |
张敬信 |
`r Sys.Date()` |
|
pdf_document |
urlcolor |
linkcolor |
|
blue |
red |
|
```{r, setup, include=FALSE}
knitr::opts_chunk$set(
warning = FALSE,
message = FALSE)
```
```{r include=FALSE}
options(tibble.print_max = 6, tibble.print_min = 6)
library(showtext)
showtext_auto()
```
**关于作者**:
- 张敬信,哈尔滨商业大学,数学与应用数学,副教授
- 热爱学习,热爱编程,热爱 R 语言
- 我的代表作《R 语言编程:基于 tidyverse》已于 2023 年 2 月正式上市,京东、天猫、当当等各大平台有售:
```{r echo=FALSE, out.width="55%", fig.align="center"}
knitr::include_graphics("MyRbook.png")
```
- 也有免费学习资源:[课件和鲸在线版(可调试代码)](https://www.heywhale.com/mw/project/641dc0d4feb4fe02b2ce72f7) ,[知乎交流平台](https://www.zhihu.com/column/c_1375580759047237632),欢迎您的阅读品鉴、交流讨论!
- 该书的 QQ 读者1群、2群:875664831、222427909, 交流、答疑,欢迎您的加入!
\newpage
**玩转数据120题**来自刘早起的 *Pandas 进阶修炼 120 题*,涵盖了数据处理、计算、可视化等常用操作,希望通过 120 道精心挑选的习题吃透 pandas.
后来,中山大学博士陈熹提供了 R 语言版本。我[^ft1][^ft2][^ft3]再来个更能体现 R 语言最新技术的 tidyverse 版本。
关于**更新版**:感谢`@鼠大米`对部分解法不够tidyverse的题目,提供了新解法(再加上我稍微修正),主要是加入更好用的新函数`slice_*()`。2023年,部分题目又做了一些改进。
[^ft1]: 我的 Github: https://github.com/zhjx19
[^ft2]: 我的知乎: https://www.zhihu.com/people/huc_zhangjingxin
[^ft3]: 我的和鲸项目(包含本文件的在线可调试版):https://www.heywhale.com/home/user/profile/5d4a42a636e903002c0e0c66/project
- 先加载包:
```{r}
library(tidyverse)
```
## Part I 入门
### 题目1(创建数据框):将下面的字典创建为DataFrame
```data = {"grammer": ["Python","C","Java","GO",np.nan,"SQL","PHP","Python"], "score": [1,2,np.nan,4,5,6,7,10]}```
**难度:**$\star$
**代码及运行结果:**
```{r}
df = tibble(
grammer = c("Python","C","Java","GO", NA,"SQL","PHP","Python"),
score = c(1,2,NA,4,5,6,7,10))
df
```
- **补充:**按行录入式创建数据框
```{r}
df = tribble(
~ grammer, ~ score,
"Python", 1,
"C", 2,
"Java", NA,
"GO", 4,
NA, 5,
"SQL", 6,
"PHP", 7,
"Python", 10)
```
### 问题2(筛选行):提取含有字符串"Python"的行
**难度:**$\star$
**代码及运行结果:**
```{r echo=TRUE}
df %>%
filter(grammer == "Python")
```
### 题目3(查看列名):输出df的所有列名
**难度:**$\star$
**代码及运行结果:**
```{r}
names(df)
```
### 题目4(修改列名):修改第2列列名为"popularity"
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df = df %>%
rename(popularity = score)
df
```
### 题目5(统计频数):统计grammer列中每种编程语言出现的次数
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
count(grammer)
```
### 题目6(缺失值处理):将空值用上下值的平均值填充
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df = df %>%
mutate(popularity = zoo::na.approx(popularity))
df
```
**注:** tidyr包提供了`fill()`函数,可以用前值或后值插补缺失值。
### 题目7(筛选行):提取popularity列中值大于3的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(popularity > 3)
```
### 题目8(数据去重):按grammer列进行去重
**难度:** $\star \star$
**代码及运行结果:**
```{r}
df %>%
distinct(grammer, .keep_all = TRUE)
```
### 题目9(数据计算):计算popularity列平均值
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
summarise(avg = mean(popularity))
```
### 题目10(格式转换):将grammer列转换为序列
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
pull(grammer) # 或者df$grammer
```
**注:** R从数据框中提取出来就是字符向量。
### 题目11(数据保存):将数据框保存为Excel
**难度:**$\star\star$
**代码及运行结果:**
```{r}
writexl::write_xlsx(df, "data/filename.xlsx")
```
### 题目12(数据查看):查看数据的行数列数
**难度:**$\star$
**代码及运行结果:**
```{r}
dim(df)
```
### 题目13(筛选行):提取popularity列值大于3小于7的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(popularity > 3 & popularity < 7)
```
### 题目14(调整列位置):交互两列的位置
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
select(popularity, grammer)
```
**注:** 可配合`everything()`放置“其余列”,更强大的调整列位置的函数是dplyr1.0将提供的`relocate()`.
### 题目15(筛选行):提取popularity列最大值所在的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
slice_max(popularity, n = 1)
# 或者
# df %>%
# filter(popularity == max(popularity))
```
### 题目16(查看数据):查看最后几行数据
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
slice_tail(n = 6) # 或者tail(df, 6)
```
**注:** 此外,`dplyr`包还提供了`slice_head()`查看前`n`行或前某比例的行,`slice_sample()`随机查看`n`行或某比例的行。
### 题目17(修改数据):删除最后一行数据
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
slice(-n())
```
### 题目18(修改数据):添加一行数据:"Perl", 6
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
add_row(grammer = "Perl", popularity = 6)
# 或者
# df %>%
# bind_rows(tibble(grammer = "Perl", popularity = 6))
```
### 题目19(数据整理):对数据按popularity列值从到大到小排序
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
arrange(-popularity)
```
**注:** 默认从小到大排序。
### 题目20(字符统计):统计grammer列每个字符串的长度
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(strlen = str_length(grammer))
```
## Part II 基础
### 题目21(读取数据):读取本地Excel数据
**难度:**$\star$
**代码及运行结果:**
```{r}
df = readxl::read_xlsx("data/21-50数据.xlsx")
df
```
### 题目22(查看数据):查看df数据的前几行
**难度:**$\star$
**代码及运行结果:**
```{r}
head(df, 5)
```
### 题目23(数据计算):将salary列数据转换为最大值与最小值的平均值
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
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`做循环计算:
```{r eval=FALSE}
calc = function(x) sum(as.numeric(unlist(x))) * 1000 / 2
df %>%
mutate(salary = map_dbl(str_extract_all(salary, "\\d+"), calc)) # 结果同上(略)
```
### 题目24(分组汇总):根据学历分组,并计算平均薪资
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
group_by(education) %>%
summarise(salary_avg = mean(salary))
```
### 题目25(时间转换):将createTime列转换为"月-日"
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
library(lubridate)
df %>%
mutate(createTime = str_sub(createTime, 6, 10))
```
### 题目26(查看数据):查看数据结构信息
**难度:**$\star$
**代码及运行结果:**
```{r}
glimpse(df) # 或者用str()
object.size(df) # 查看对象占用内存
```
### 题目27(查看数据):查看数据汇总信息
**难度:**$\star$
**代码及运行结果:**
```{r}
summary(df)
```
### 题目28(修改列):新增一列将salary离散化为三水平值
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
df = df %>%
mutate(class = case_when(
salary >= 0 & salary < 5000 ~ "低",
salary >= 5000 & salary < 20000 ~ "中",
.default = "高"))
df
```
- 或者用`cut()`函数:
```{r eval=FALSE}
df %>%
mutate(class = cut(salary,
breaks = c(0,5000,20000,Inf),
labels = c("低", "中", "高"),
right = FALSE))
```
- 或者用`sjmisc`包中的`rec()`,和SPSS的重新编码一样强大。
```{r eval=FALSE}
df %>%
mutate(class = sjmisc::rec(salary,
rec = "min:5000 = 低; 5000:20000 = 中; 20000:max = 高"))
```
### 题目29(数据整理):按salary列对数据降序排列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
arrange(-salary)
```
### 题目30(筛选行):提取第33行数据
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
slice(33) # 或者df[33,]
```
### 题目31(数据计算):计算salary列的中位数
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
summarise(med = median(salary)) # 或者median(df$salary)
```
### 题目32(数据可视化):绘制salary的频率分布直方图
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(salary)) +
geom_histogram(bins = 10, fill = "steelblue", color = "black")
```
### 题目33(数据可视化):绘制salary的频率密度曲线图
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(salary)) +
geom_density(color = "red")
```
### 题目34(数据删除):删除最后一列class
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
select(-class)
# 或者
# df %>%
# select(-last_col()) # 同last_col(0)
```
### 题目35(数据操作):将df的第1列与第2列合并为新的一列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
unite("newcol", 1:2, sep = " ")
```
### 题目36(数据操作):将education列与第salary列合并为新的一列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
unite("newcol", c(education, salary), sep = " ")
```
### 题目37(数据计算):计算salary最大值与最小值之差
**难度:**$\star\star$
**代码及运行结果:**
```{r}
max(df$salary) - min(df$salary)
```
或者用
```{r}
df %>%
summarise(range = max(salary) - min(salary))
```
### 题目38(数据操作):将第一行与最后一行拼接
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
slice(1, n())
```
### 题目39(数据操作):将第8行添加到末尾
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
bind_rows(slice(., 8))
```
### 题目40(查看数据):查看每一列的数据类型
**难度:**$\star$
**代码及运行结果:**
```{r}
glimpse(df) # 或者用str()
```
### 题目41(数据操作):将createTime列设置为行索引
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
distinct(createTime, .keep_all = TRUE) %>%
column_to_rownames("createTime")
```
**注:** 行索引不允许有重复,所以先做了一步去重。
### 题目42(数据创建):生成一个和df长度相同的随机数数据框
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df1 = tibble(rnums = sample(10, nrow(df), replace = TRUE))
df1
```
### 题目43(数据连接):将上面生成的数据框与df按列合并
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df = bind_cols(df, df1)
df
```
**注:**实际上,42,43题应该合并成一个题,这是数据操作中最常规的修改列:
```{r}
df %>%
mutate(rnums = sample(10, n(), replace = TRUE))
```
### 题目44(修改列):生成新列new为salary列减去随机数列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df = df %>%
mutate(new = salary - rnums)
df
```
### 题目45(检查缺失值):检查数据中是否含有任何缺失值
**难度:**$\star\star$
**代码及运行结果:**
```{r}
anyNA(df)
anyNA(df$salary)
```
**注:** naniar包提供了更强大的探索缺失值及缺失模式的函数,其中`miss_var_summary()`和`miss_case_summary()`可检查各列和各行缺失情况。
### 题目46(类型转换):将salary列的类型转换为浮点数
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(rnums = as.double(rnums))
```
### 题目47(数据汇总):计算salary列大于10000的次数
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
count(salary > 10000)
```
或者用
```{r}
df %>%
summarise(n = sum(salary > 10000))
```
### 题目48(统计频数):查看每种学历出现的次数
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
count(education)
```
### 题目49(数据汇总):查看education列共有几种学历
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
distinct(education)
```
### 题目50(筛选行):提取salary与new列之和大于60000的最后3行
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(salary + new > 60000) %>%
slice_tail(n = 3)
```
## Part III 提高
### 题目51(读取数据):使用绝对路径读取本地Excel数据
**难度:**$\star$
**代码及运行结果:**
```{r}
df = readxl::read_xls("data/51-80数据.xls")
df
```
### 题目52(查看数据):查看数据框的前3行
**难度:**$\star$
**代码及运行结果:**
```{r}
head(df, 3)
```
**说明:**当前数据不包含缺失值,接下来关于缺失值的题目53-56,改用自带的 `starwars`数据演示。
### 题目53(查看缺失值):查看每列数据缺失值情况
**难度:**$\star\star$
**代码及运行结果:**
```{r}
map_int(starwars, ~ sum(is.na(.x)))
```
**注:** 也可以用`naniar`包中的 `miss_var_summary()`函数。
### 题目54(查看缺失值):查看日期列含有缺失值的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
starwars %>%
filter(is.na(hair_color))
```
### 题目55(查看缺失值):查看每列缺失值在哪些行
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
map(starwars, ~ which(is.na(.x)))
```
### 题目56(缺失值处理):删除所有存在缺失值的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
starwars %>%
drop_na()
```
**注:** 若要删除某些列包含缺失值的行,提供列名即可。
### 题目57(数据可视化):绘制收盘价的折线图
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(日期, `收盘价(元)`)) +
geom_line(color = "red")
```
### 题目58(数据可视化):同时绘制开盘价与收盘价
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
select(日期, `开盘价(元)`, `收盘价(元)`) %>%
pivot_longer(-日期, names_to = "type", values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
```
**注:** 为了自动添加图例,先对数据做了宽变长转换。
### 题目59(数据可视化):绘制涨跌幅的直方图
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(`涨跌幅(%)`)) +
geom_histogram(fill = "steelblue", color = "black")
```
### 题目60(数据可视化):让直方图更细致
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(`涨跌幅(%)`)) +
geom_histogram(bins = 50, fill = "steelblue", color = "black")
```
### 题目61(数据创建):用df的列名创建数据框
**难度:**$\star\star$
**代码及运行结果:**
```{r}
tibble(Name = names(df))
```
### 题目62(异常值处理):输出所有换手率不是数字的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(`换手率(%)` = parse_number(`换手率(%)`)) %>%
filter(is.na(`换手率(%)`))
```
### 题目63(异常值处理):输出所有换手率为--的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(`换手率(%)` == "--")
```
### 题目64(数据操作):重置df的行号
**难度:**$\star$
**代码及运行结果:**
```{r}
rownames(df) = NULL # R中无行号就是数字索引
```
### 题目65(异常值处理):删除所有换手率为非数字的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(`换手率(%)` = parse_number(`换手率(%)`)) %>%
filter(!is.na(`换手率(%)`))
```
```{r}
library(lubridate)
df = df %>%
mutate(across(4:18, as.numeric), 日期 = as_date(日期))
df
```
### 题目66(数据可视化):绘制换手率的密度曲线
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
ggplot(aes(`换手率(%)`)) +
geom_density()
```
### 题目67(数据计算):计算前一天与后一天收盘价的差值
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(delta = `收盘价(元)` - lag(`收盘价(元)`)) %>%
select(日期, `收盘价(元)`, delta)
```
### 题目68(数据计算):计算前一天与后一天收盘价的变化率
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(change = (`收盘价(元)` - lag(`收盘价(元)`)) / `收盘价(元)`) %>%
select(日期, `收盘价(元)`, change)
```
### 题目69(数据操作):设置日期为行索引
**难度:**$\star$
**代码及运行结果:**
```{r}
df %>%
column_to_rownames("日期") %>% # 将从tibble变成data.frame
head()
```
### 题目70(数据计算):对收盘价做步长为5的滑动平均
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
library(slider)
df %>%
mutate(avg_5 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE,
.before = 2, .after = 2)) %>%
select(日期, `收盘价(元)`, avg_5)
```
### 题目71(数据计算):对收盘价做步长为5的滑动求和
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(sum_5 = slide_dbl(`收盘价(元)`, sum, na.rm = TRUE,
.before = 2, .after = 2)) %>%
select(日期, `收盘价(元)`, sum_5)
```
### 题目72(数据可视化):将收盘价及其5日均线、20日均线绘制在同一个图上
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(avg_5 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE,
.before = 2, .after = 2),
avg_20 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE,
.before = 10, .after = 9)) %>%
pivot_longer(c(`收盘价(元)`, avg_5, avg_20),
names_to = "type", values_to = "price") %>%
ggplot(aes(日期, price, color = type)) +
geom_line()
```
### 题目73(数据重采样):按周为采样规则,计算一周收盘价最大值
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
library(tsibble)
weekmax = df %>%
group_by(weeks = yearweek(日期)) %>% # 年-周
slice_max(`收盘价(元)`) # 默认n = 1
weekmax
```
### 题目74(数据可视化):绘制重采样数据与原始数据
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
weekmax %>%
ggplot(aes(weeks, `收盘价(元)`)) +
geom_line(color = "red") +
geom_line(data = df, aes(日期, `收盘价(元)`), color = "steelblue")
```
### 题目75(数据操作):将数据往后移动5天
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(across(4:18, ~ lag(.x, 5)))
```
**注:**这是批量做后移,单个变量做后移用`mutate(var = lag(var, 5)`即可。
### 题目76(数据操作):将数据往前移动5天
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(across(4:18, ~ lead(.x, 5)))
```
### 题目77(数据操作):计算开盘价的累积平均
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
rlt = df %>%
mutate(累积平均 = cummean(`开盘价(元)`)) %>%
select(日期, `开盘价(元)`, 累积平均)
rlt
```
### 题目78(数据计算):绘制开盘价的累积平均与原始数据的折线图
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
rlt %>%
pivot_longer(-日期, names_to = "类型", values_to = "价格") %>%
ggplot(aes(日期, 价格, color = 类型)) +
geom_line()
```
### 题目79(数据计算):计算布林指标
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
boll = df %>%
mutate(avg_20 = slide_dbl(`收盘价(元)`, mean, na.rm = TRUE,
.before = 10, .after = 9),
sd_20 = slide_dbl(`收盘价(元)`, sd, na.rm = TRUE,
.before = 10, .after = 9),
up = avg_20 + 2 * sd_20,
down = avg_20 - 2 * sd_20) %>%
select(日期, `收盘价(元)`, avg_20, up, down)
boll %>%
slice_sample(n = 10)
```
### 题目80(数据可视化):绘制布林曲线
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
boll %>%
pivot_longer(-日期, names_to = "类型", values_to = "价格") %>%
ggplot(aes(日期, 价格, color = 类型)) +
geom_line()
```
## Part VI 数据生成
### 题目81(加载查看包):加载并查看tidyverse包版本
**难度:**$\star$
**代码及运行结果:**
```{r}
# 得是首次加载
library(tidyverse)
```
### 题目82(生成随机数):生成20个0~100的随机数,创建数据框
**难度:**$\star$
**代码及运行结果:**
```{r}
set.seed(123) # 保证结果出现
df1 = tibble(nums = sample(100, 20))
df1
```
### 题目83(生成等差数):生成20个0~100固定步长的数,创建数据框
**难度:**$\star$
**代码及运行结果:**
```{r}
df2 = tibble(nums = seq(0, 99, by = 5))
df2
```
### 题目84(生成指定分布随机数):生成20个标准正态分布的随机数,创建数据框
**难度:**$\star$
**代码及运行结果:**
```{r}
set.seed(123)
df3 = tibble(nums = rnorm(20, 0, 1))
df3
```
### 题目85(合并数据):将df1, df2, df3按行合并为新数据框
**难度:**$\star$
**代码及运行结果:**
```{r}
bind_rows(df1, df2, df3)
```
### 题目86(合并数据):将df1, df2, df3按列合并为新数据框
**难度:**$\star$
**代码及运行结果:**
```{r}
df = bind_cols(df1, df2, df3)
df
```
### 题目87(查看数据):查看df所有数据的最小值、25%分位数、中位数、75%分位数、最大值
**难度:**$\star\star$
**代码及运行结果:**
```{r}
unlist(df) %>%
summary()
```
### 题目88(修改列名):修改列名为col1, col2, col3
**难度:**$\star$
**代码及运行结果:**
```{r}
df = df %>%
set_names(str_c("col", 1:3))
df
```
**注:**若只修改个别列名,用`rename(newname = oldname)`.
### 题目89(数据操作):提取在第1列中而不在第2列中的数
**难度:**$\star\star$
**代码及运行结果:**
```{r}
setdiff(df$col1, df$col2)
```
### 题目90(数据操作):提取在第1列和第2列出现频率最高的三个数字
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
tibble(nums = c(df$col1, df$col2)) %>%
count(nums, sort = TRUE) %>%
slice(1:3)
```
### 题目91(数据操作):提取第1列可以整除5的数的位置
**难度:**$\star\star$
**代码及运行结果:**
```{r}
which(df$col1 %% 5 == 0)
```
- 选取满足条件的索引,通常用途还是用来选出满足条件的行,不兜圈子做法:
```{r}
df %>%
filter(col1 %% 5 == 0)
```
### 题目92(数据计算):计算第1列的1阶差分
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(diff1 = col1 - lag(col1))
```
**注:**若只是要数值,用`diff(df$col1)`即可。
### 题目93(数据操作):将col1, col2, col3三列顺序颠倒
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
select(rev(names(df)))
```
**注:**更灵活的调整列序,dplyr 1.0提供的`relocate()`函数。
### 题目94(数据操作):提取第一列位置在1,10,15的数
**难度:**$\star$
**代码及运行结果:**
```{r}
df[c(1,10,15), 1]
```
### 题目95(数据操作):查找第一列的局部最大值位置
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(diff = sign(col1 - lag(col1)) + sign(col1 - lead(col1)))
which(rlt$diff == 2)
```
- 不兜圈子做法:
```{r}
df %>%
mutate(diff = sign(col1 - lag(col1)) + sign(col1 - lead(col1))) %>%
filter(diff == 2)
```
### 题目96(数据计算):按行计算df每一行的均值
**难度:**$\star\star$
**代码及运行结果:**
```{r}
rowMeans(df) # 或者apply(df, 1, mean)
# 或者
df %>%
mutate(row_avg = pmap_dbl(., ~ mean(c(...))))
```
### 题目97(数据计算):对第二列计算步长为3的移动平均值
*难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(avg_3 = slide_dbl(col2, mean, .before = 1, .after = 1))
```
### 题目98(数据计算):按第三列值的大小升序排列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
arrange(col3)
```
### 题目99(数据操作):按第一列大于50的数修改为"高"
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(col1 = ifelse(col1 > 50, "高", col1))
```
### 题目100(数据计算):计算第一列与第二列的欧氏距离
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
(df$col1 - df$col2) ^ 2 |> sum() |> sqrt()
```
## Part V 高级
### 题目101(数据读取):从csv文件中读取指定数据:读取前10行, positionName和salary列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
read_csv("data/数据1_101-120涉及.csv", n_max = 10,
col_select = c(positionName, salary))
```
### 题目102(数据读取):从csv文件中读取数据,将薪资大于10000的改为"高"
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df = read_csv("data/数据2_101-120涉及.csv") %>%
mutate(薪资水平 = if_else(薪资水平 > 10000, "高", "低"))
```
### 题目103(数据操作):从df中对薪资水平每隔20行进行抽样
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
slice(seq(1, n(), by = 20))
```
### 题目104(数据操作):取消使用科学记数法
**难度:**$\star\star$
**代码及运行结果:**
```{r}
set.seed(123)
df = tibble(val = runif(10) ^ 10)
# 三位小数
df %>%
mutate(val = scales::number(val, accuracy = 0.001))
# 科学记数法
df %>%
mutate(val = scales::scientific(val, 2))
```
### 题目105(数据操作):将上一题的数据转换为百分数
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(val = scales::percent(val, 0.01))
```
### 题目106(数据操作):查找上一题数据中第3大值的行号
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
order(df$val, decreasing = TRUE)[3]
```
- 不兜圈子做法:
```{r}
df %>%
arrange(-val) %>%
slice(3)
```
### 题目107(数据操作):反转df的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
slice(rev(1:n())) # 或者df[nrow(df):1,]
```
### 题目108(数据连接:全连接):根据多列匹配合并数据,保留df1和df2的观测
**难度:**$\star\star$
**代码及运行结果:**
```{r}
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))
df1
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))
df2
df1 %>%
full_join(df2, by = c("key1", "key2"))
```
### 题目109(数据连接:左连接):根据多列匹配合并数据,只保留df1的观测
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df1 %>%
left_join(df2, by = c("key1", "key2"))
```
**注:**dplyr包还提供了右连接:`right_join()`,内连接:`inner_join()`,以及用于过滤的连接:半连接:`semi_join()`,反连接:`anti_join()`.
### 题目110(数据处理):再次读取数据1并显示所有列
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df = read_csv("data/数据1_101-120涉及.csv")
glimpse(df)
```
### 题目111(数据操作):查找secondType与thirdType值相等的行号
**难度:**$\star\star$
**代码及运行结果:**
```{r}
which(df$secondType == df$thirdType)
```
- 不兜圈子:
```{r}
df %>%
filter(secondType == thirdType)
```
### 题目112(数据操作):查找薪资大于平均薪资的第三个数据
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(salary > mean(salary)) %>%
slice(3)
```
### 题目113(数据操作):将上一题数据的salary列开根号
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
mutate(salary_sqrt = sqrt(salary)) %>%
select(salary, salary_sqrt)
```
### 题目114(数据操作):将上一题数据的linestation列按_拆分
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
separate(linestaion, into = c("line", "station"), sep = "_", remove = FALSE) %>%
select(linestaion, line, station)
```
**注:**正常需要先按“;”分割,再分别按“-”分割。
### 题目115(数据查看):查看上一题数据一共有多少列
**难度:**$\star$
**代码及运行结果:**
```{r}
ncol(df)
```
### 题目116(数据操作):提取industryField列以"数据"开头的行
**难度:**$\star\star$
**代码及运行结果:**
```{r}
df %>%
filter(str_detect(industryField, "^数据"))
```
### 题目117(数据分组汇总):以salary score和positionID做数据透视表
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
group_by(positionId) %>%
summarise(salary_avg = mean(salary), score_avg = mean(score))
```
### 题目118(数据分组汇总):同时对salary、score两列进行汇总计算
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
summarise(across(c(salary, score),
list(sum=sum, mean=mean, min=min),
.names = "{.col}_{.fn}"))
```
**注:**若要分组再这样汇总,前面加上`group_by(var)`即可。
### 题目119(数据分组汇总):同时对不同列进行不同的汇总计算:对salary求平均,对score求和
**难度:**$\star\star\star$
**代码及运行结果:**
```{r}
df %>%
summarise(salary_avg = mean(salary),
score_sum = sum(score))
```
**注:**若要分组再这样汇总,前面加上`group_by(var)`即可。
### 题目120(数据分组汇总):计算并提取平均薪资最高的区
**难度:**$\star\star\star\star$
**代码及运行结果:**
```{r}
df %>%
group_by(district) %>%
summarise(salary_avg = mean(salary)) %>%
slice_max(salary_avg) # 默认n = 1
```