1. 什么是计算变量?

计算变量就是根据已有变量,创建新的变量。

例如学生成绩数据中有:

chinese, math, english

我们可以计算:

total = chinese + math + english
average = total / 3

这两个新变量就是计算变量。


2. 创建示例数据

student <- data.frame(
  id = 1:8,
  name = c("A", "B", "C", "D", "E", "F", "G", "H"),
  gender = c("女", "男", "男", "女", "男", "女", "男", "女"),
  chinese = c(85, 92, 78, 88, 95, 70, 82, 90),
  math = c(90, 85, 80, 92, 96, 75, 88, 91),
  english = c(88, 89, 76, 90, 94, 72, 84, 93)
)

student
##   id name gender chinese math english
## 1  1    A     女      85   90      88
## 2  2    B     男      92   85      89
## 3  3    C     男      78   80      76
## 4  4    D     女      88   92      90
## 5  5    E     男      95   96      94
## 6  6    F     女      70   75      72
## 7  7    G     男      82   88      84
## 8  8    H     女      90   91      93

查看数据结构:

str(student)
## 'data.frame':    8 obs. of  6 variables:
##  $ id     : int  1 2 3 4 5 6 7 8
##  $ name   : chr  "A" "B" "C" "D" ...
##  $ gender : chr  "女" "男" "男" "女" ...
##  $ chinese: num  85 92 78 88 95 70 82 90
##  $ math   : num  90 85 80 92 96 75 88 91
##  $ english: num  88 89 76 90 94 72 84 93

3. 基础R:计算总分

假设我们要计算三科总分:

student$total <- student$chinese + student$math + student$english

student
##   id name gender chinese math english total
## 1  1    A     女      85   90      88   263
## 2  2    B     男      92   85      89   266
## 3  3    C     男      78   80      76   234
## 4  4    D     女      88   92      90   270
## 5  5    E     男      95   96      94   285
## 6  6    F     女      70   75      72   217
## 7  7    G     男      82   88      84   254
## 8  8    H     女      90   91      93   274

解释:

student$total

表示在 student 数据框中创建一个新变量 total

右边:

student$chinese + student$math + student$english

表示三科分数相加。


4. 基础R:计算平均分

student$average <- student$total / 3

student
##   id name gender chinese math english total  average
## 1  1    A     女      85   90      88   263 87.66667
## 2  2    B     男      92   85      89   266 88.66667
## 3  3    C     男      78   80      76   234 78.00000
## 4  4    D     女      88   92      90   270 90.00000
## 5  5    E     男      95   96      94   285 95.00000
## 6  6    F     女      70   75      72   217 72.33333
## 7  7    G     男      82   88      84   254 84.66667
## 8  8    H     女      90   91      93   274 91.33333

也可以直接写成:

student$average2 <- (student$chinese + student$math + student$english) / 3

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333

5. 基础R:计算差值

例如计算数学比语文高多少分:

student$math_minus_chinese <- student$math - student$chinese

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese
## 1                  5
## 2                 -7
## 3                  2
## 4                  4
## 5                  1
## 6                  5
## 7                  6
## 8                  1

如果结果是正数,说明数学比语文高。

如果结果是负数,说明数学比语文低。


6. 基础R:计算比例

例如计算语文分数占总分的比例:

student$chinese_ratio <- student$chinese / student$total

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio
## 1                  5     0.3231939
## 2                 -7     0.3458647
## 3                  2     0.3333333
## 4                  4     0.3259259
## 5                  1     0.3333333
## 6                  5     0.3225806
## 7                  6     0.3228346
## 8                  1     0.3284672

如果想显示成百分比,可以乘以 100:

student$chinese_percent <- student$chinese_ratio * 100

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent
## 1                  5     0.3231939        32.31939
## 2                 -7     0.3458647        34.58647
## 3                  2     0.3333333        33.33333
## 4                  4     0.3259259        32.59259
## 5                  1     0.3333333        33.33333
## 6                  5     0.3225806        32.25806
## 7                  6     0.3228346        32.28346
## 8                  1     0.3284672        32.84672

7. round():保留小数

比例经常会有很多小数,可以用 round() 保留小数位。

student$chinese_percent_round <- round(student$chinese_percent, 2)

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round
## 1                  5     0.3231939        32.31939                 32.32
## 2                 -7     0.3458647        34.58647                 34.59
## 3                  2     0.3333333        33.33333                 33.33
## 4                  4     0.3259259        32.59259                 32.59
## 5                  1     0.3333333        33.33333                 33.33
## 6                  5     0.3225806        32.25806                 32.26
## 7                  6     0.3228346        32.28346                 32.28
## 8                  1     0.3284672        32.84672                 32.85

解释:

round(x, 2)

表示保留 2 位小数。


8. 计算变量常用函数

函数 作用
+ 加法
- 减法
* 乘法
/ 除法
round(x, 2) 保留 2 位小数
sqrt(x) 开平方
log(x) 取自然对数
abs(x) 绝对值
mean(x) 平均值
sum(x) 求和

9. 使用 ifelse() 创建判断变量

ifelse() 可以根据条件生成新变量。

语法:

ifelse(条件, 条件成立时的结果, 条件不成立时的结果)

例如:

ifelse(score >= 60, "及格", "不及格")

10. 判断是否及格

根据平均分判断是否及格:

student$pass <- ifelse(student$average >= 60, "及格", "不及格")

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格

11. 创建优秀标记

假设平均分大于等于 90 是优秀:

student$excellent <- ifelse(student$average >= 90, "优秀", "非优秀")

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent
## 1    非优秀
## 2    非优秀
## 3    非优秀
## 4      优秀
## 5      优秀
## 6    非优秀
## 7    非优秀
## 8      优秀

12. 多条件分类:成绩等级

如果要分成多个等级,比如:

平均分 等级
>= 90 A
>= 80 且 < 90 B
>= 70 且 < 80 C
< 70 D

可以嵌套使用 ifelse()

student$grade <- ifelse(
  student$average >= 90, "A",
  ifelse(
    student$average >= 80, "B",
    ifelse(student$average >= 70, "C", "D")
  )
)

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade
## 1    非优秀     B
## 2    非优秀     B
## 3    非优秀     C
## 4      优秀     A
## 5      优秀     A
## 6    非优秀     C
## 7    非优秀     B
## 8      优秀     A

解释:


13. 把等级转成有序因子

等级 A、B、C、D 是有顺序的。

student$grade_f <- factor(
  student$grade,
  levels = c("D", "C", "B", "A"),
  ordered = TRUE
)

student
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade grade_f
## 1    非优秀     B       B
## 2    非优秀     B       B
## 3    非优秀     C       C
## 4      优秀     A       A
## 5      优秀     A       A
## 6    非优秀     C       C
## 7    非优秀     B       B
## 8      优秀     A       A

查看水平:

levels(student$grade_f)
## [1] "D" "C" "B" "A"

14. 使用 rowSums() 计算多列总和

如果有很多列要相加,用 rowSums() 更方便。

先创建一个新数据:

score_df <- data.frame(
  id = 1:5,
  chinese = c(80, 90, 75, 88, 95),
  math = c(85, 92, 78, 90, 96),
  english = c(82, 89, 77, 91, 93)
)

score_df
##   id chinese math english
## 1  1      80   85      82
## 2  2      90   92      89
## 3  3      75   78      77
## 4  4      88   90      91
## 5  5      95   96      93

计算三科总分:

score_df$total <- rowSums(score_df[, c("chinese", "math", "english")])

score_df
##   id chinese math english total
## 1  1      80   85      82   247
## 2  2      90   92      89   271
## 3  3      75   78      77   230
## 4  4      88   90      91   269
## 5  5      95   96      93   284

解释:

score_df[, c("chinese", "math", "english")]

表示选中这三列。

rowSums()

表示按行求和。


15. 使用 rowMeans() 计算多列平均值

score_df$average <- rowMeans(score_df[, c("chinese", "math", "english")])

score_df
##   id chinese math english total  average
## 1  1      80   85      82   247 82.33333
## 2  2      90   92      89   271 90.33333
## 3  3      75   78      77   230 76.66667
## 4  4      88   90      91   269 89.66667
## 5  5      95   96      93   284 94.66667

解释:

rowMeans()

表示按行计算平均值。


16. 缺失值 NA 对计算的影响

如果数据里有缺失值,计算时要注意。

score_na <- data.frame(
  id = 1:5,
  chinese = c(80, 90, NA, 88, 95),
  math = c(85, NA, 78, 90, 96),
  english = c(82, 89, 77, NA, 93)
)

score_na
##   id chinese math english
## 1  1      80   85      82
## 2  2      90   NA      89
## 3  3      NA   78      77
## 4  4      88   90      NA
## 5  5      95   96      93

直接计算总分:

score_na$total_wrong <- rowSums(score_na[, c("chinese", "math", "english")])

score_na
##   id chinese math english total_wrong
## 1  1      80   85      82         247
## 2  2      90   NA      89          NA
## 3  3      NA   78      77          NA
## 4  4      88   90      NA          NA
## 5  5      95   96      93         284

你会发现只要某一行有 NA,总分就会变成 NA。


17. 忽略 NA 计算

如果想忽略缺失值,可以加:

na.rm = TRUE
score_na$total_ignore_na <- rowSums(
  score_na[, c("chinese", "math", "english")],
  na.rm = TRUE
)

score_na
##   id chinese math english total_wrong total_ignore_na
## 1  1      80   85      82         247             247
## 2  2      90   NA      89          NA             179
## 3  3      NA   78      77          NA             155
## 4  4      88   90      NA          NA             178
## 5  5      95   96      93         284             284

平均分也一样:

score_na$average_ignore_na <- rowMeans(
  score_na[, c("chinese", "math", "english")],
  na.rm = TRUE
)

score_na
##   id chinese math english total_wrong total_ignore_na average_ignore_na
## 1  1      80   85      82         247             247          82.33333
## 2  2      90   NA      89          NA             179          89.50000
## 3  3      NA   78      77          NA             155          77.50000
## 4  4      88   90      NA          NA             178          89.00000
## 5  5      95   96      93         284             284          94.66667

18. 注意:忽略 NA 的含义

如果一名学生缺考一科,直接忽略 NA 计算平均分,可能会让结果偏高。

例如:

80, 85, NA

如果忽略 NA:

平均分 = (80 + 85) / 2

但如果把缺考看作 0 分:

平均分 = (80 + 85 + 0) / 3

所以是否使用 na.rm = TRUE,要看具体业务含义。


19. 把 NA 当作 0 计算

可以先把 NA 替换为 0。

score_zero <- score_na

score_zero[is.na(score_zero)] <- 0

score_zero
##   id chinese math english total_wrong total_ignore_na average_ignore_na
## 1  1      80   85      82         247             247          82.33333
## 2  2      90    0      89           0             179          89.50000
## 3  3       0   78      77           0             155          77.50000
## 4  4      88   90       0           0             178          89.00000
## 5  5      95   96      93         284             284          94.66667

重新计算总分:

score_zero$total <- rowSums(score_zero[, c("chinese", "math", "english")])

score_zero
##   id chinese math english total_wrong total_ignore_na average_ignore_na total
## 1  1      80   85      82         247             247          82.33333   247
## 2  2      90    0      89           0             179          89.50000   179
## 3  3       0   78      77           0             155          77.50000   155
## 4  4      88   90       0           0             178          89.00000   178
## 5  5      95   96      93         284             284          94.66667   284

20. dplyr:使用 mutate() 计算变量

在实际数据处理中,dplyrmutate() 很常用。

如果没有安装,可以先运行:

install.packages("dplyr")

加载包:

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

21. mutate() 创建新变量

student2 <- student %>%
  mutate(
    total2 = chinese + math + english,
    average3 = total2 / 3
  )

student2
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade grade_f total2 average3
## 1    非优秀     B       B    263 87.66667
## 2    非优秀     B       B    266 88.66667
## 3    非优秀     C       C    234 78.00000
## 4      优秀     A       A    270 90.00000
## 5      优秀     A       A    285 95.00000
## 6    非优秀     C       C    217 72.33333
## 7    非优秀     B       B    254 84.66667
## 8      优秀     A       A    274 91.33333

解释:

mutate()

表示在数据框中新增或修改变量。

在同一个 mutate() 中,后面可以使用前面刚创建的变量。


22. mutate() 修改已有变量

比如把平均分保留 1 位小数:

student2 <- student2 %>%
  mutate(
    average3 = round(average3, 1)
  )

student2
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade grade_f total2 average3
## 1    非优秀     B       B    263     87.7
## 2    非优秀     B       B    266     88.7
## 3    非优秀     C       C    234     78.0
## 4      优秀     A       A    270     90.0
## 5      优秀     A       A    285     95.0
## 6    非优秀     C       C    217     72.3
## 7    非优秀     B       B    254     84.7
## 8      优秀     A       A    274     91.3

如果变量名已经存在,mutate() 会覆盖原变量。


23. mutate() 配合 ifelse()

student2 <- student2 %>%
  mutate(
    pass2 = ifelse(average3 >= 60, "及格", "不及格"),
    excellent2 = ifelse(average3 >= 90, "优秀", "非优秀")
  )

student2
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade grade_f total2 average3 pass2 excellent2
## 1    非优秀     B       B    263     87.7  及格     非优秀
## 2    非优秀     B       B    266     88.7  及格     非优秀
## 3    非优秀     C       C    234     78.0  及格     非优秀
## 4      优秀     A       A    270     90.0  及格       优秀
## 5      优秀     A       A    285     95.0  及格       优秀
## 6    非优秀     C       C    217     72.3  及格     非优秀
## 7    非优秀     B       B    254     84.7  及格     非优秀
## 8      优秀     A       A    274     91.3  及格       优秀

24. dplyr:case_when() 多条件分类

如果条件很多,case_when() 比嵌套 ifelse() 更清楚。

student2 <- student2 %>%
  mutate(
    grade2 = case_when(
      average3 >= 90 ~ "A",
      average3 >= 80 ~ "B",
      average3 >= 70 ~ "C",
      TRUE ~ "D"
    )
  )

student2
##   id name gender chinese math english total  average average2
## 1  1    A     女      85   90      88   263 87.66667 87.66667
## 2  2    B     男      92   85      89   266 88.66667 88.66667
## 3  3    C     男      78   80      76   234 78.00000 78.00000
## 4  4    D     女      88   92      90   270 90.00000 90.00000
## 5  5    E     男      95   96      94   285 95.00000 95.00000
## 6  6    F     女      70   75      72   217 72.33333 72.33333
## 7  7    G     男      82   88      84   254 84.66667 84.66667
## 8  8    H     女      90   91      93   274 91.33333 91.33333
##   math_minus_chinese chinese_ratio chinese_percent chinese_percent_round pass
## 1                  5     0.3231939        32.31939                 32.32 及格
## 2                 -7     0.3458647        34.58647                 34.59 及格
## 3                  2     0.3333333        33.33333                 33.33 及格
## 4                  4     0.3259259        32.59259                 32.59 及格
## 5                  1     0.3333333        33.33333                 33.33 及格
## 6                  5     0.3225806        32.25806                 32.26 及格
## 7                  6     0.3228346        32.28346                 32.28 及格
## 8                  1     0.3284672        32.84672                 32.85 及格
##   excellent grade grade_f total2 average3 pass2 excellent2 grade2
## 1    非优秀     B       B    263     87.7  及格     非优秀      B
## 2    非优秀     B       B    266     88.7  及格     非优秀      B
## 3    非优秀     C       C    234     78.0  及格     非优秀      C
## 4      优秀     A       A    270     90.0  及格       优秀      A
## 5      优秀     A       A    285     95.0  及格       优秀      A
## 6    非优秀     C       C    217     72.3  及格     非优秀      C
## 7    非优秀     B       B    254     84.7  及格     非优秀      B
## 8      优秀     A       A    274     91.3  及格       优秀      A

解释:

条件 ~ 结果

最后的:

TRUE ~ "D"

表示上面条件都不满足时,结果为 D。


25. case_when() 的顺序很重要

判断等级时,必须先写高分条件:

average >= 90
average >= 80
average >= 70

不能先写:

average >= 70

否则 90 分也会先被归为 C。


26. 计算 BMI

再看一个常见例子:用身高和体重计算 BMI。

BMI 公式:

BMI = 体重kg / 身高m ^ 2

创建数据:

person <- data.frame(
  id = 1:6,
  height_cm = c(160, 172, 168, 180, 155, 175),
  weight_kg = c(50, 70, 65, 85, 48, 90)
)

person
##   id height_cm weight_kg
## 1  1       160        50
## 2  2       172        70
## 3  3       168        65
## 4  4       180        85
## 5  5       155        48
## 6  6       175        90

先把厘米转成米:

person$height_m <- person$height_cm / 100

person$bmi <- person$weight_kg / person$height_m^2

person$bmi <- round(person$bmi, 2)

person
##   id height_cm weight_kg height_m   bmi
## 1  1       160        50     1.60 19.53
## 2  2       172        70     1.72 23.66
## 3  3       168        65     1.68 23.03
## 4  4       180        85     1.80 26.23
## 5  5       155        48     1.55 19.98
## 6  6       175        90     1.75 29.39

27. 根据 BMI 分类

这里用一个简单分类:

BMI 分类
< 18.5 偏瘦
18.5 - 23.9 正常
24 - 27.9 超重
>= 28 肥胖
person$bmi_group <- ifelse(
  person$bmi < 18.5, "偏瘦",
  ifelse(
    person$bmi < 24, "正常",
    ifelse(person$bmi < 28, "超重", "肥胖")
  )
)

person
##   id height_cm weight_kg height_m   bmi bmi_group
## 1  1       160        50     1.60 19.53      正常
## 2  2       172        70     1.72 23.66      正常
## 3  3       168        65     1.68 23.03      正常
## 4  4       180        85     1.80 26.23      超重
## 5  5       155        48     1.55 19.98      正常
## 6  6       175        90     1.75 29.39      肥胖

case_when() 写会更清楚:

person2 <- person %>%
  mutate(
    bmi_group2 = case_when(
      bmi < 18.5 ~ "偏瘦",
      bmi < 24 ~ "正常",
      bmi < 28 ~ "超重",
      TRUE ~ "肥胖"
    )
  )

person2
##   id height_cm weight_kg height_m   bmi bmi_group bmi_group2
## 1  1       160        50     1.60 19.53      正常       正常
## 2  2       172        70     1.72 23.66      正常       正常
## 3  3       168        65     1.68 23.03      正常       正常
## 4  4       180        85     1.80 26.23      超重       超重
## 5  5       155        48     1.55 19.98      正常       正常
## 6  6       175        90     1.75 29.39      肥胖       肥胖

28. 计算订单金额

创建订单数据:

orders <- data.frame(
  order_id = 1:6,
  price = c(100, 200, 150, 300, 500, 80),
  quantity = c(2, 1, 3, 1, 2, 5),
  discount = c(0.9, 0.8, 1.0, 0.85, 0.75, 1.0)
)

orders
##   order_id price quantity discount
## 1        1   100        2     0.90
## 2        2   200        1     0.80
## 3        3   150        3     1.00
## 4        4   300        1     0.85
## 5        5   500        2     0.75
## 6        6    80        5     1.00

计算原价金额:

orders$amount <- orders$price * orders$quantity

orders
##   order_id price quantity discount amount
## 1        1   100        2     0.90    200
## 2        2   200        1     0.80    200
## 3        3   150        3     1.00    450
## 4        4   300        1     0.85    300
## 5        5   500        2     0.75   1000
## 6        6    80        5     1.00    400

计算折后金额:

orders$pay <- orders$amount * orders$discount

orders
##   order_id price quantity discount amount pay
## 1        1   100        2     0.90    200 180
## 2        2   200        1     0.80    200 160
## 3        3   150        3     1.00    450 450
## 4        4   300        1     0.85    300 255
## 5        5   500        2     0.75   1000 750
## 6        6    80        5     1.00    400 400

保留两位小数:

orders$pay <- round(orders$pay, 2)

orders
##   order_id price quantity discount amount pay
## 1        1   100        2     0.90    200 180
## 2        2   200        1     0.80    200 160
## 3        3   150        3     1.00    450 450
## 4        4   300        1     0.85    300 255
## 5        5   500        2     0.75   1000 750
## 6        6    80        5     1.00    400 400

29. 用 mutate() 计算订单金额

orders2 <- orders %>%
  mutate(
    amount2 = price * quantity,
    pay2 = round(amount2 * discount, 2)
  )

orders2
##   order_id price quantity discount amount pay amount2 pay2
## 1        1   100        2     0.90    200 180     200  180
## 2        2   200        1     0.80    200 160     200  160
## 3        3   150        3     1.00    450 450     450  450
## 4        4   300        1     0.85    300 255     300  255
## 5        5   500        2     0.75   1000 750    1000  750
## 6        6    80        5     1.00    400 400     400  400

30. 小练习

下面有一个员工数据:

employee <- data.frame(
  id = 1:8,
  name = c("A", "B", "C", "D", "E", "F", "G", "H"),
  base_salary = c(5000, 6000, 5500, 8000, 7500, 6200, 9000, 7000),
  bonus = c(500, 800, 600, 1200, 1000, 700, 1500, 900),
  tax_rate = c(0.05, 0.08, 0.06, 0.10, 0.09, 0.07, 0.12, 0.08)
)

employee
##   id name base_salary bonus tax_rate
## 1  1    A        5000   500     0.05
## 2  2    B        6000   800     0.08
## 3  3    C        5500   600     0.06
## 4  4    D        8000  1200     0.10
## 5  5    E        7500  1000     0.09
## 6  6    F        6200   700     0.07
## 7  7    G        9000  1500     0.12
## 8  8    H        7000   900     0.08

要求:

  1. 计算税前工资:gross_salary = base_salary + bonus
  2. 计算税额:tax = gross_salary * tax_rate
  3. 计算税后工资:net_salary = gross_salary - tax
  4. 税后工资保留 2 位小数
  5. 根据税后工资分类:
    • >= 8000:高
    • >= 6000 且 < 8000:中
    • < 6000:低

31. 小练习参考答案:基础R写法

employee$gross_salary <- employee$base_salary + employee$bonus

employee$tax <- employee$gross_salary * employee$tax_rate

employee$net_salary <- employee$gross_salary - employee$tax

employee$net_salary <- round(employee$net_salary, 2)

employee$salary_level <- ifelse(
  employee$net_salary >= 8000, "高",
  ifelse(employee$net_salary >= 6000, "中", "低")
)

employee
##   id name base_salary bonus tax_rate gross_salary  tax net_salary salary_level
## 1  1    A        5000   500     0.05         5500  275       5225           低
## 2  2    B        6000   800     0.08         6800  544       6256           中
## 3  3    C        5500   600     0.06         6100  366       5734           低
## 4  4    D        8000  1200     0.10         9200  920       8280           高
## 5  5    E        7500  1000     0.09         8500  765       7735           中
## 6  6    F        6200   700     0.07         6900  483       6417           中
## 7  7    G        9000  1500     0.12        10500 1260       9240           高
## 8  8    H        7000   900     0.08         7900  632       7268           中

32. 小练习参考答案:dplyr写法

employee2 <- employee %>%
  mutate(
    gross_salary2 = base_salary + bonus,
    tax2 = gross_salary2 * tax_rate,
    net_salary2 = round(gross_salary2 - tax2, 2),
    salary_level2 = case_when(
      net_salary2 >= 8000 ~ "高",
      net_salary2 >= 6000 ~ "中",
      TRUE ~ "低"
    )
  )

employee2
##   id name base_salary bonus tax_rate gross_salary  tax net_salary salary_level
## 1  1    A        5000   500     0.05         5500  275       5225           低
## 2  2    B        6000   800     0.08         6800  544       6256           中
## 3  3    C        5500   600     0.06         6100  366       5734           低
## 4  4    D        8000  1200     0.10         9200  920       8280           高
## 5  5    E        7500  1000     0.09         8500  765       7735           中
## 6  6    F        6200   700     0.07         6900  483       6417           中
## 7  7    G        9000  1500     0.12        10500 1260       9240           高
## 8  8    H        7000   900     0.08         7900  632       7268           中
##   gross_salary2 tax2 net_salary2 salary_level2
## 1          5500  275        5225            低
## 2          6800  544        6256            中
## 3          6100  366        5734            低
## 4          9200  920        8280            高
## 5          8500  765        7735            中
## 6          6900  483        6417            中
## 7         10500 1260        9240            高
## 8          7900  632        7268            中

33. 本节核心代码总结

下面这段是模板代码,所以设置为 eval=FALSE,只展示,不运行。

# 基础R:新增变量
df$new_var <- df$var1 + df$var2

# 基础R:计算平均值
df$average <- (df$x1 + df$x2 + df$x3) / 3

# 保留小数
df$average <- round(df$average, 2)

# ifelse 二分类
df$pass <- ifelse(df$score >= 60, "及格", "不及格")

# ifelse 多分类
df$grade <- ifelse(
  df$score >= 90, "A",
  ifelse(df$score >= 80, "B",
         ifelse(df$score >= 70, "C", "D"))
)

# rowSums 按行求和
df$total <- rowSums(df[, c("x1", "x2", "x3")], na.rm = TRUE)

# rowMeans 按行求平均
df$average <- rowMeans(df[, c("x1", "x2", "x3")], na.rm = TRUE)

# dplyr:mutate 新增变量
df2 <- df %>%
  mutate(
    new_var = var1 + var2,
    average = round((x1 + x2 + x3) / 3, 2)
  )

# dplyr:case_when 多条件分类
df2 <- df %>%
  mutate(
    grade = case_when(
      score >= 90 ~ "A",
      score >= 80 ~ "B",
      score >= 70 ~ "C",
      TRUE ~ "D"
    )
  )

34. 一句话总结

计算变量最核心的意思是:

用已有变量生成新变量。

基础R常用:

df$new_var <- df$var1 + df$var2

dplyr 常用:

df %>%
  mutate(new_var = var1 + var2)

如果要根据条件生成变量,常用:

ifelse()

或者:

case_when()