有时候,我们需要对数据框添加新的列,比较常见的场景就是需要根据现有数据框的某列增加新的分类。比如样本分为正常与肿瘤,成绩按照排名区分低、中、高。
比较粗暴的方式当然就是循环了。可是,众所周知,R 不喜欢循环,因此这里总结一些其他的如向量化的处理方法。
这里先创建一个数据框:
> my_d = data.frame(a = sample(30:100, 100, replace = T), b = sample(LETTERS[1:5], 100, replace = T))
> table(my_d$a)
30 33 35 37 38 39 40 41 42 43 44 46 47 48 49 50 51 54 56 57
6 4 2 1 1 4 2 1 4 3 1 1 1 2 2 1 2 1 4 1
58 59 60 61 62 63 67 68 70 71 72 73 74 75 76 77 79 80 82 83
3 2 3 1 4 3 1 1 1 1 1 2 2 2 1 1 2 1 2 1
84 85 87 88 90 91 92 93 94 97 99 100
1 2 1 3 4 1 2 1 1 2 1 2
> table(my_d$b)
A B C D E
21 19 17 16 27
这里主要是针对列的数据为分类变量的,比如样本名等。
这个主要针对简单的分类变量,比如需要判断“是否为XX” 或者“结果是否等于XX”这种简单的问题。比如看成绩是否及格:
> my_d[which(my_d$a >= 60), "c"] = "pass"
> !my_d$a >= 60
[1] FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE
[13] FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE TRUE TRUE FALSE TRUE
[25] TRUE TRUE FALSE TRUE TRUE TRUE TRUE FALSE FALSE TRUE TRUE TRUE
[37] FALSE FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE
[49] TRUE FALSE FALSE TRUE TRUE FALSE TRUE TRUE FALSE TRUE TRUE FALSE
[61] TRUE TRUE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE
[73] FALSE TRUE TRUE TRUE TRUE FALSE FALSE TRUE FALSE FALSE TRUE TRUE
[85] TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE TRUE
[97] TRUE FALSE FALSE TRUE
> my_d[which(!my_d$a >= 60), "c"] = "not pass"
> table(my_d$c)
not pass pass
49 51
这里which 用于获取逻辑值的下标。
自认为这个函数比逻辑值要直观很多,但做的也是和逻辑值差不多的是。其可以根据逻辑值的返回结果进行创建等长的向量,分别对应True 与False 的条件:
> head(my_d)
a b c
1 73 A pass
2 48 E not pass
3 30 D not pass
4 75 B pass
5 79 E pass
6 61 D pass
> head(ifelse(my_d$b == "A", 'A group', 'not A group'))
[1] "A group" "not A group" "not A group" "not A group" "not A group"
[6] "not A group"
相比逻辑值的好处是,其比较直观,而且可以连续的套娃:
> cbind(head(my_d$b),head(ifelse(my_d$b == "A", 'A group', ifelse(my_d$b == 'B', 'B group', "not A& B"))))
[,1] [,2]
[1,] "A" "A group"
[2,] "E" "not A& B"
[3,] "D" "not A& B"
[4,] "B" "B group"
[5,] "E" "not A& B"
[6,] "D" "not A& B"
但如果是连续变量或者复杂的分类变量,还是不建议这么玩,毕竟一层套一层的,代码都不好意思给别人看~
这种需求其实还是蛮少的。
> my_d2
b f
1 A 1st
2 E 2ed
3 D 3rd
4 B 4st
5 C 5st
> head(merge(my_d, my_d2, by = 'b')[sample(1:100, 100),])
b a c f
91 E 99 pass 2ed
63 D 40 not pass 3rd
34 B 75 pass 4st
67 D 35 not pass 3rd
85 E 74 pass 2ed
84 E 56 not pass 2ed
cut 用来切割连续性变量,可以将其按照breaks 中的向量区间分割。可以通过labels 参数指定向量,使其元素作为breaks 分割后的新值,ordered_result 默认True,返回有序型因子:
> a
[1] 80 22 88 54 52 19 65 56 25 99 2 59 79 82 23 7 87 61 49 55
> labels
[1] "差" "中" "良" "好"
> breaks = fivenum(a)
> breaks
[1] 2.0 24.0 55.5 79.5 99.0
b = cut(a, breaks = breaks, labels = labels, include.lowest = T)
> b
[1] 好 差 好 中 中 差 良 良 中 好 差 良 良 好 差 差 好 良 中 中
Levels: 差 中 良 好
> c = cbind(as.data.frame(a), b)
> head(c)
a b
1 80 好
2 22 差
3 88 好
4 54 中
5 52 中
6 19 差
在设置cut 参数的breaks 时,我们除了使用fivenum() 函数获取数值的四分位数,还可以结合pretty 函数,获取指定分段长的数字,pretty 会帮助我们获得等间距的整值:
> pretty(a, 5)
[1] 0 20 40 60 80 100
> labels
[1] "差" "中" "良" "好" "极好"
> breaks = pretty(a, 5)
b = cut(a, breaks = breaks, labels = labels, include.lowest = T)
> b = cut(a, breaks = breaks, labels = labels, include.lowest = T)
> b
[1] 好 中 极好 良 良 差 好 良 中 极好 差 良 好 极好 中
[16] 差 极好 好 良 良
Levels: 差 中 良 好 极好
相当于高级版的ifelse,就是用起来语法有点怪~