tidyverse笔记——tidyr包
未完待续
tidry:Tidy Messy Data
观摩完 Hadley Wickham 大佬的 R for Data Science,我对有关 tidyr 的章节印象最深刻的是它的开场白,第一句话引用了列夫托尔斯泰的名言:幸福的家庭是相似的,不幸的家庭却各有各的不幸。第二句话是作者“拙劣”的模仿:整洁的数据集是相似的,脏的数据集却各有各的脏法!
- Happy families are all alike; every unhappy family is unhappy in its own way.
—Leo Tolstoy - Tidy datasets are all alike, but every messy dataset is messy in its own way.
—Hadley Wickham
对于应对过“脏数据”的人来说可谓是深有同感,然后我们还不得不接受这样一个事实:当我们真正做一些自主性的数据分析,正准备处理一些生活中的数据和问题,绝大时候我们遇到的都是“脏数据”。
很多时候,我们其实并没有事先意识到它是脏的或者不知道脏在哪?做着做着就出现问题了。
数院有一位很厉害的老师跟我提及过:实际上数据预处理是非常难做的。我们今日尝试着用 tidyr 以较合理的方法解决其中的一部分问题。
常用函数及其功能
tidyr——Reshape
Gather
- Gather columns into key-value pairs
这是帮助文档的解释,实际上是把原来一群有相同性质的变量(列名)转换成新的列的值,实现了从 “键” 到 “值” 的转变。直接上例子,这里用的是relig_income
,它统计了不同区域不同薪资水平的人数。
> relig_income %>% print(n = 5)
# A tibble: 18 × 11
religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Agnostic 27 34 60 81 76 137 122
2 Atheist 12 27 37 52 35 70 73
3 Buddhist 27 21 30 34 33 58 62
4 Catholic 418 617 732 670 638 1116 949
5 Don’t kno… 15 14 15 11 10 35 21
# … with 13 more rows, and 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
# `Don't know/refused` <dbl>
可以看到从第二列开始,变量都是关于薪资的,那么为何不直接把薪资作为新的变量,不同的薪资水平作为值呢?比如说第一行,就会被拆为10行,地区都是Agnostic
,薪资变量下的值是不一样的,用 gather() 实现如下:
relig_income %>% gather(2:11, key = "income", value = "count") %>% arrange(religion)
便会得到
# A tibble: 180 × 3
religion income count
<chr> <chr> <dbl>
1 Agnostic <$10k 27
2 Agnostic $10-20k 34
3 Agnostic $20-30k 60
4 Agnostic $30-40k 81
5 Agnostic $40-50k 76
6 Agnostic $50-75k 137
7 Agnostic $75-100k 122
8 Agnostic $100-150k 109
9 Agnostic >150k 84
10 Agnostic Don't know/refused 96
# … with 170 more rows
Spread
- Spread a key-value pair across multiple columns
这是帮助文档的解释,简单来讲,它是 gather() 的逆操作。它以某一列的不同值创建新的变量,将键值对分布在多个列上。 - 所以正确使用 spread() 和 gather()的关键在于,思考清楚:何为键,何为值?
还用上面的例子:
relig_income_ <- relig_income %>% gather(2:11, key = "income", value = "count")
relig_income_ %>% spread(key = income, value = count) %>% print(n = 5)
便可以得到原来的数据集。
pivot_longer & pivot_wider
简单认为,pivot_longer == spread
,pivot_wider == gather
早些版本的 tidyr 并没有 spread
和 gather
,我们来看看作者本人是怎么想的Pivoting,他的大概意思是:原来的名字不够直观,减少列增加行、减少行增加列从直观上暗示该操作的意义。Many people don’t find the names intuitive and find it hard to remember which direction corresponds to spreading and which to gathering.
老实说,我是有些没必要的。不过,语法在进步。以spread的“精装版”为例,打开帮助文档,可以看到它的参数非常多,也变具有更灵活写法的可能。
pivot_longer(
data,
cols,
names_to = "name",
names_prefix = NULL,
names_sep = NULL,
names_pattern = NULL,
names_ptypes = NULL,
names_transform = NULL,
names_repair = "check_unique",
values_to = "value",
values_drop_na = FALSE,
values_ptypes = NULL,
values_transform = NULL,
...
)
拿之前的例子,可以这么写:
relig_income %>%
pivot_longer(cols = c(2:11), names_to = "income", values_to = "value")
Spread & Gather等价写法
依旧用上面的例子relig_income
,下面详细介绍如何构造一句话完成等价于spread的操作。
religion = as.vector(unlist(rep(relig_income[,1], dim(relig_income)[2] - 1)))
cbind
的第一个参数利用了rep
函数,因为按2到11列展开,所以,行数应该是原来的十倍(dim(relig_income)[2] - 1)
,由于cbind
函数不能接受list
类型的参数,便用unlist
进行类型转换。
stack(relig_income[, -1])
cbind
的第二个参数利用了stack
函数,它能以列序为主序展开列表或数据框
(Stacking vectors concatenates multiple vectors into a single vector along with a factor indicating where each observation originated. Unstacking reverses this operation.)
cbind(religion = as.vector(unlist(rep(relig_income[,1], dim(relig_income)[2] - 1))), stack(relig_income[, -1]))
其实也可以用reshape
函数,不过参数列表令人眼花缭乱,这里便用更简明易懂的写法了。
gather函数的等价写法就有点麻烦了,我们还是按一种合理的思路想,我可以这么做:创建新列,确定行数,填充每一行。写起来非常麻烦,目前没有找到一种比较好的等价写法
Complete
- Complete a data frame with missing combinations of data
- 数学上的一个理解就是集合的 “笛卡尔积”
举个例子就懂,
> df = data.frame(x1=c(1, 2, 3), x2=c(2, 3, 3), value=c(11, 3, 5))
> df %>% complete(x1, x2)
# A tibble: 6 × 3
x1 x2 value
<dbl> <dbl> <dbl>
1 1 2 11
2 1 3 NA
3 2 2 NA
4 2 3 3
5 3 2 NA
6 3 3 5
等价的写法可以借助merge实现。如果两个都是向量的话,等价写法很简单
temp = unique(v2)
cbind(rep(unique(v1), each = length(temp)), temp)
如果包含data.frame
等其他数据类型,最大的不同在于它的本质类型是list
类型,而不是double
,在使用rep
函数时就会产生与预期不同的效果,需要借助unlist
函数进行处理。
Separate
- separate() pulls apart one column into multiple columns, by splitting wherever a separator character appears.
- separate()通过在出现分隔符的位置拆分,将一列拆分为多列。
Example(from R for Data Science)
table3
#> # A tibble: 6 x 3
#> country year rate
#> * <chr> <int> <chr>
#> 1 Afghanistan 1999 745/19987071
#> 2 Afghanistan 2000 2666/20595360
#> 3 Brazil 1999 37737/172006362
#> 4 Brazil 2000 80488/174504898
#> 5 China 1999 212258/1272915272
#> 6 China 2000 213766/1280428583
table3 %>%
separate(rate, into = c("cases", "population"))
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <int> <chr> <chr>
#> 1 Afghanistan 1999 745 19987071
#> 2 Afghanistan 2000 2666 20595360
#> 3 Brazil 1999 37737 172006362
#> 4 Brazil 2000 80488 174504898
#> 5 China 1999 212258 1272915272
#> 6 China 2000 213766 1280428583
可以看到它自动识别原本列rate里的分隔符——“/”,等价的操作可用 stringr 代替,它也是tidyverse系列中的一个包,当然,要是一些很复杂的,可以考虑正则表达式。
Unite
- 可简单视为 separate() 的逆函数,将多列合并为一列
- 默认的连接符是下划线“_”,但这往往不是我们想要的,可以利用参数 sep 选择合适的分隔符,如果想直接连接,可以置sep参数为空:sep=“”
tidyr——Handle Missing Value
Hadley Wickham 大佬对于缺失值又有骚话曰:
- An explicit missing value is the presence of an absence; an implicit missing value is the absence of a presence.
非常有意思,他用了一个不同年份的各季度数据举例,详情请点击.
因为我想到一个更生动的例子,所以这里和大家分享以下。小学的时候,我们得做黄冈小状元,老实说,我从来没有完整完成过任何一本,我很懒不想写,我是怎么做的呢?有些麻烦的就空在那里了,这种老师一看就看到了,这就是“显式缺失值”(explicit missing value)。要是量很多,来不及补,我会偷偷把其中几页拿固体胶粘在一起,只粘边缘,如果不幸被老师发现了,我还能把责任推给出版社,是他们印刷有问题,我也没看到。也就是说,我把这几页偷偷藏起来,这就是“隐式缺失值”。是的,老师可能发现,也可能没发现。我们在对数据做一些处理的时候,对这些隐式缺失值有时候确实是没有反应过来,没有意识到有缺项,正如当年我的老师(笑了)。