Chapter 3 数据转换

绘制图表很简单。然而,有时要将数据整理成生成图表所需的形式并不那么容易。

3.1 Data transformation

3.2 什么是tidy(整齐)的数据

下面是 Hadley Wickham 对整洁数据的定义:

数据集是杂乱的还是整洁的,取决于行、列和表如何与观察值、变量和类型进行匹配。整齐的数据:

  1. 每个变量形成一列。(Each variable forms a column.)
  2. 每个观察点组成一行。(Each observation forms a row.)
  3. 每个观测单位在表中形成一个值。(Each observational unit forms a value in the table.)

在本章节极少对数据进行转换的一些工具.

3.3 pivot_longer

将宽数据转换成为长数据, 首先查看原始数据

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

接下来使用pivot_longer 将数据集转换成为长数据:

mtcars1 <- mtcars %>%  
  rownames_to_column("carname") %>%
  pivot_longer(cols = !carname, names_to = "Parameters",values_to = "value") 

mtcars1 %>%
  head()
## # A tibble: 6 × 3
##   carname   Parameters  value
##   <chr>     <chr>       <dbl>
## 1 Mazda RX4 mpg         21   
## 2 Mazda RX4 cyl          6   
## 3 Mazda RX4 disp       160   
## 4 Mazda RX4 hp         110   
## 5 Mazda RX4 drat         3.9 
## 6 Mazda RX4 wt           2.62

3.4 pivot_wider

将长数据转化成为宽数据:

mtcars1 %>% pivot_wider(names_from = Parameters, values_from = value)
## # A tibble: 32 × 12
##    carname       mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
##    <chr>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Mazda RX4    21       6  160    110  3.9   2.62  16.5     0     1     4     4
##  2 Mazda RX4 …  21       6  160    110  3.9   2.88  17.0     0     1     4     4
##  3 Datsun 710   22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
##  4 Hornet 4 D…  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
##  5 Hornet Spo…  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
##  6 Valiant      18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
##  7 Duster 360   14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
##  8 Merc 240D    24.4     4  147.    62  3.69  3.19  20       1     0     4     2
##  9 Merc 230     22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
## 10 Merc 280     19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
## # ℹ 22 more rows

长数据与宽数据的转换是非常常用的操作.

3.5 常用数据处理函数

我们将使用来自 MASS 的biopsy数据集作为例子。

library(MASS)
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
head(biopsy)
##        ID V1 V2 V3 V4 V5 V6 V7 V8 V9     class
## 1 1000025  5  1  1  1  2  1  3  1  1    benign
## 2 1002945  5  4  4  5  7 10  3  2  1    benign
## 3 1015425  3  1  1  1  2  2  3  1  1    benign
## 4 1016277  6  8  8  1  3  4  3  7  1    benign
## 5 1017023  4  1  1  3  2  1  3  1  1    benign
## 6 1017122  8 10 10  8  7 10  9  7  1 malignant

3.5.1 rename

在获取数据时,我们注意到列的名称非常模糊。我们希望更改列的名称,以便查看者能够了解它们所引用的值。我们使用重命名来修改列名。

biopsy_new <- rename(biopsy,
       thickness = V1,cell_size = V2,
       cell_shape = V3, marg_adhesion = V4,
       epithelial_cell_size = V5, bare_nuclei = V6,
       chromatin = V7, norm_nucleoli = V8, mitoses = V9)

head(biopsy_new)
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1000025         5         1          1             1                    2
## 2 1002945         5         4          4             5                    7
## 3 1015425         3         1          1             1                    2
## 4 1016277         6         8          8             1                    3
## 5 1017023         4         1          1             3                    2
## 6 1017122         8        10         10             8                    7
##   bare_nuclei chromatin norm_nucleoli mitoses     class
## 1           1         3             1       1    benign
## 2          10         3             2       1    benign
## 3           2         3             1       1    benign
## 4           4         3             7       1    benign
## 5           1         3             1       1    benign
## 6          10         9             7       1 malignant

3.5.2 select

Select 是按列执行的操作。具体来说,只返回指定的列。

#selecting all except the columns chromatin and mitoses
biopsy_new <- biopsy_new %>% dplyr::select(-chromatin,-mitoses)

head(biopsy_new,5)
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1000025         5         1          1             1                    2
## 2 1002945         5         4          4             5                    7
## 3 1015425         3         1          1             1                    2
## 4 1016277         6         8          8             1                    3
## 5 1017023         4         1          1             3                    2
##   bare_nuclei norm_nucleoli  class
## 1           1             1 benign
## 2          10             2 benign
## 3           2             1 benign
## 4           4             7 benign
## 5           1             1 benign

3.5.3 mutate

Mutate 函数从已存在的变量中计算新变量,并将它们添加到数据集中。它提供数据已经包含但从未显示的信息。

#normalize the bare nuclei values 
maximum_bare_nuclei<-max(biopsy_new$bare_nuclei,na.rm=TRUE)
biopsy_new <- biopsy_new %>% mutate(bare_nuclei=bare_nuclei/maximum_bare_nuclei)

head(biopsy_new,5)
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1000025         5         1          1             1                    2
## 2 1002945         5         4          4             5                    7
## 3 1015425         3         1          1             1                    2
## 4 1016277         6         8          8             1                    3
## 5 1017023         4         1          1             3                    2
##   bare_nuclei norm_nucleoli  class
## 1         0.1             1 benign
## 2         1.0             2 benign
## 3         0.2             1 benign
## 4         0.4             7 benign
## 5         0.1             1 benign

3.5.4 filter

Filter 是行操作。它返回只包含某些行的修改后的副本。此函数根据其参数中提供的条件筛选行

biopsy_new <- biopsy_new %>% filter(thickness>5.5)

head(biopsy_new,5)
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1016277         6         8          8             1                    3
## 2 1017122         8        10         10             8                    7
## 3 1044572         8         7          5            10                    7
## 4 1047630         7         4          6             4                    6
## 5 1050670        10         7          7             6                    4
##   bare_nuclei norm_nucleoli     class
## 1         0.4             7    benign
## 2         1.0             7 malignant
## 3         0.9             5 malignant
## 4         0.1             3 malignant
## 5         1.0             1 malignant

3.5.5 arrange

对数据进行排序

head(arrange(biopsy_new,cell_size))
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1050718         6         1          1             1                    2
## 2 1204898         6         1          1             1                    2
## 3 1223967         6         1          3             1                    2
## 4  543558         6         1          3             1                    4
## 5   63375         9         1          2             6                    4
## 6  752904        10         1          1             1                    2
##   bare_nuclei norm_nucleoli     class
## 1         0.1             1    benign
## 2         0.1             1    benign
## 3         0.1             1    benign
## 4         0.5            10 malignant
## 5         1.0             7 malignant
## 6         1.0             4 malignant

3.5.6 group_by and summarize

总结函数使用数据创建一个新的数据框架,其中包含最小值、最大值、平均值等总结统计信息。这些统计函数必须是以值的向量作为输入并输出单个值的聚合函数。

biopsy_grouped <- group_by(biopsy_new,class)
summarize(biopsy_grouped, max(thickness), mean(cell_size), var(norm_nucleoli))
## # A tibble: 2 × 4
##   class     `max(thickness)` `mean(cell_size)` `var(norm_nucleoli)`
##   <fct>                <int>             <dbl>                <dbl>
## 1 benign                   8              2.67                 5.93
## 2 malignant               10              6.73                11.3

3.5.7 slice_max (slice_min)

Slice _ max 函数可以帮助您查找特定列的前 n 个值。

biopsy_new %>% 
  slice_max(order_by = thickness,n=5) %>% 
  head()
##        ID thickness cell_size cell_shape marg_adhesion epithelial_cell_size
## 1 1050670        10         7          7             6                    4
## 2 1054593        10         5          5             3                    6
## 3 1072179        10         7          7             3                    8
## 4 1080185        10        10         10             8                    6
## 5 1099510        10         4          3             1                    3
## 6 1103608        10        10         10             4                    8
##   bare_nuclei norm_nucleoli     class
## 1         1.0             1 malignant
## 2         0.7            10 malignant
## 3         0.5             4 malignant
## 4         0.1             9 malignant
## 5         0.3             5 malignant
## 6         0.1            10 malignant

3.5.8 join

需要组合两个数据集,这就是函数join发挥作用的时候.

band_members %>% inner_join(band_instruments)
## Joining with `by = join_by(name)`
## # A tibble: 2 × 3
##   name  band    plays 
##   <chr> <chr>   <chr> 
## 1 John  Beatles guitar
## 2 Paul  Beatles bass

3.6 Data Cleaning

3.6.1 Splitting Text

将文本进行拆分

library(tidyr)
df <- data.frame(person = c("John_Doe", "Jane_Doe"))

现在我们有了一个数据框架,其中一列包含一个名字和一个由下划线组合的姓氏。现在,让我们将这两个名称分成它们各自独立的列。

df <- df %>% separate(person, c("first_name", "last_name"), "_")

3.6.2 Replace Values

接下来我们将讨论如何替换数据集中的特定值

students <- c("John", "Jane", "Joe", "Janet")
grades <- c(83, 97, 74, 27)
df <- data.frame(student = students, grade = grades)

我们需要用60代替任何低于60的等级

df[which(df$"grade" < 60), "grade"] <- 60

3.6.3 Drop Columns

接下来,我们可以通过指定要保留的列或指定要删除的列来删除列

df <- head(mtcars)
print(df)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
df <- subset(df, select = c(mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear))

也可以是用:

df <- subset(df, select = -c(gear))

如果希望使用索引号而不是列名,可以删除列的另一种方法如下所示

df <- df[,-c(1,3:7)]

3.6.4 Drop Rows

我们还可以使用与删除列相同的方法来删除行

df <- df[-c(1:2),]