使用pivot_longer和pivot_wider进行长宽数据转换

获取更多R语言和生信知识,请欢迎关注公众号:医学和生信笔记

医学和生信笔记 公众号主要分享:1.医学小知识、肛肠科小知识;2.R语言和Python相关的数据分析、可视化、机器学习等;3.生物信息学学习资料和自己的学习笔记!

前面介绍了长宽数据转换的常见的几种情况,今天介绍下一些比较特殊的情况。

先变长,再变宽

有些数据单纯的一次变宽或变长是不能解决的,需要联合使用pivot_longerpivot_wider

使用world_bank_pop数据集演示,这个数据集是2000年到2017年的每个国家的人口数据。

world_bank_pop
## # A tibble: 1,056 x 20
##    country indicator        `2000` `2001` `2002` `2003`  `2004`  `2005`   `2006`
##    <chr>   <chr>             <dbl>  <dbl>  <dbl>  <dbl>   <dbl>   <dbl>    <dbl>
##  1 ABW     SP.URB.TOTL    42444    4.30e4 4.37e4 4.42e4 4.47e+4 4.49e+4  4.49e+4
##  2 ABW     SP.URB.GROW        1.18 1.41e0 1.43e0 1.31e0 9.51e-1 4.91e-1 -1.78e-2
##  3 ABW     SP.POP.TOTL    90853    9.29e4 9.50e4 9.70e4 9.87e+4 1.00e+5  1.01e+5
##  4 ABW     SP.POP.GROW        2.06 2.23e0 2.23e0 2.11e0 1.76e+0 1.30e+0  7.98e-1
##  5 AFG     SP.URB.TOTL  4436299    4.65e6 4.89e6 5.16e6 5.43e+6 5.69e+6  5.93e+6
##  6 AFG     SP.URB.GROW        3.91 4.66e0 5.13e0 5.23e0 5.12e+0 4.77e+0  4.12e+0
##  7 AFG     SP.POP.TOTL 20093756    2.10e7 2.20e7 2.31e7 2.41e+7 2.51e+7  2.59e+7
##  8 AFG     SP.POP.GROW        3.49 4.25e0 4.72e0 4.82e0 4.47e+0 3.87e+0  3.23e+0
##  9 AGO     SP.URB.TOTL  8234766    8.71e6 9.22e6 9.77e6 1.03e+7 1.09e+7  1.15e+7
## 10 AGO     SP.URB.GROW        5.44 5.59e0 5.70e0 5.76e0 5.75e+0 5.69e+0  4.92e+0
## # ... with 1,046 more rows, and 11 more variables: 2007 <dbl>, 2008 <dbl>,
## #   2009 <dbl>, 2010 <dbl>, 2011 <dbl>, 2012 <dbl>, 2013 <dbl>, 2014 <dbl>,
## #   2015 <dbl>, 2016 <dbl>, 2017 <dbl>

首先这个数据很明显不是整洁数据,所以我们先把它变成整洁数据:

pop2 <- world_bank_pop %>% 
  pivot_longer(
    cols = "2000":"2017", # 因为是字符型需要加引号
    names_to = "year",
    values_to = "value"
  )

pop2
## # A tibble: 19,008 x 4
##    country indicator   year  value
##    <chr>   <chr>       <chr> <dbl>
##  1 ABW     SP.URB.TOTL 2000  42444
##  2 ABW     SP.URB.TOTL 2001  43048
##  3 ABW     SP.URB.TOTL 2002  43670
##  4 ABW     SP.URB.TOTL 2003  44246
##  5 ABW     SP.URB.TOTL 2004  44669
##  6 ABW     SP.URB.TOTL 2005  44889
##  7 ABW     SP.URB.TOTL 2006  44881
##  8 ABW     SP.URB.TOTL 2007  44686
##  9 ABW     SP.URB.TOTL 2008  44375
## 10 ABW     SP.URB.TOTL 2009  44052
## # ... with 18,998 more rows

接下来看看indicator这一列:

pop2 %>% count(indicator)
## # A tibble: 4 x 2
##   indicator       n
##   <chr>       <int>
## 1 SP.POP.GROW  4752
## 2 SP.POP.TOTL  4752
## 3 SP.URB.GROW  4752
## 4 SP.URB.TOTL  4752

这里SP.POP.GROW是人口增长量,SP.POP.TOTL是人口总量,SP.URB*也是一样的,不过是城市人口。很明显这一列包含了多个信息,下面我们把这一列拆分为多列:

pop3 <- pop2 %>% 
  separate(indicator, 
           c(NA,"area","variable"),
           sep = "\\."
           )

pop3
## # A tibble: 19,008 x 5
##    country area  variable year  value
##    <chr>   <chr> <chr>    <chr> <dbl>
##  1 ABW     URB   TOTL     2000  42444
##  2 ABW     URB   TOTL     2001  43048
##  3 ABW     URB   TOTL     2002  43670
##  4 ABW     URB   TOTL     2003  44246
##  5 ABW     URB   TOTL     2004  44669
##  6 ABW     URB   TOTL     2005  44889
##  7 ABW     URB   TOTL     2006  44881
##  8 ABW     URB   TOTL     2007  44686
##  9 ABW     URB   TOTL     2008  44375
## 10 ABW     URB   TOTL     2009  44052
## # ... with 18,998 more rows

这样一个数据还不是很直观,我们可以把variable这一列“变宽”:

pop3 %>% 
  pivot_wider(
    names_from = variable,
    values_from = value
  )
## # A tibble: 9,504 x 5
##    country area  year   TOTL    GROW
##    <chr>   <chr> <chr> <dbl>   <dbl>
##  1 ABW     URB   2000  42444  1.18  
##  2 ABW     URB   2001  43048  1.41  
##  3 ABW     URB   2002  43670  1.43  
##  4 ABW     URB   2003  44246  1.31  
##  5 ABW     URB   2004  44669  0.951 
##  6 ABW     URB   2005  44889  0.491 
##  7 ABW     URB   2006  44881 -0.0178
##  8 ABW     URB   2007  44686 -0.435 
##  9 ABW     URB   2008  44375 -0.698 
## 10 ABW     URB   2009  44052 -0.731 
## # ... with 9,494 more rows

这样的数据看起来是不是更加一目了然呢?

问卷调查数据

在临床中常用到问卷调查,每一个问题都有1个或多个选择(比如ABCD)等,这样的数据都会变得非常宽,类似这样:

multi <- tribble(
  ~id, ~q1, ~q2, ~q3,
  1, "A", "B", "C",
  2, "C", "B",  NA,
  3, "D",  NA,  NA,
  4, "B", "D",  NA
)

multi
## # A tibble: 4 x 4
##      id q1    q2    q3   
##   <dbl> <chr> <chr> <chr>
## 1     1 A     B     C    
## 2     2 C     B     <NA> 
## 3     3 D     <NA>  <NA> 
## 4     4 B     D     <NA>

这样的数据id就是id,没啥实际意义,q1/q2/q3代表不同的问题,比如上面的数据表示第一个问题选择了ABCD四个选项。

现在我们想把ABCD变成单独的4列,然后每一个问题是一行,有没有选择用TRUE和FALSE表示。

可以通过2步实现:

multi2 <- multi %>% 
  pivot_longer(
  cols = !id,
  names_to = "choice", # 这种情况下这个和下面那个参数可以省略
  values_to = "value", # 可省略
  values_drop_na = T
  ) %>% 
  mutate(checked = TRUE)

multi2
## # A tibble: 8 x 4
##      id choice value checked
##   <dbl> <chr>  <chr> <lgl>  
## 1     1 q1     A     TRUE   
## 2     1 q2     B     TRUE   
## 3     1 q3     C     TRUE   
## 4     2 q1     C     TRUE   
## 5     2 q2     B     TRUE   
## 6     3 q1     D     TRUE   
## 7     4 q1     B     TRUE   
## 8     4 q2     D     TRUE

接下来再变成宽数据,并且把缺失值用FALSE代表:

multi2 %>% 
  pivot_wider(
    id_cols = id,
    names_from = value,
    values_from = checked,
    values_fill = FALSE
  )
## # A tibble: 4 x 5
##      id A     B     C     D    
##   <dbl> <lgl> <lgl> <lgl> <lgl>
## 1     1 TRUE  TRUE  TRUE  FALSE
## 2     2 FALSE TRUE  TRUE  FALSE
## 3     3 FALSE FALSE FALSE TRUE 
## 4     4 FALSE TRUE  FALSE TRUE

这样就达到目的了,每一行是一个问题,列是不同的选项,如果选了就是TRUE,如果没选就是FALSE,很好看。

手动操作

长宽数据转换通过之前的示例讲解,基本覆盖了大多数情况,但是复杂的日常生活总是有很多意想不到的场景需求。因此作者开发了更为强大和自由的手动操作过程。

基本理念就是先创建一个类似模板的东西,然后根据这个模板进行转换。

变长

还是以relig_income这个数据集为例。

relig_income
## # A tibble: 18 x 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 k~      15        14        15        11        10        35         21
##  6 Evangel~     575       869      1064       982       881      1486        949
##  7 Hindu          1         9         7         9        11        34         47
##  8 Histori~     228       244       236       238       197       223        131
##  9 Jehovah~      20        27        24        24        21        30         15
## 10 Jewish        19        19        25        25        30        95         69
## 11 Mainlin~     289       495       619       655       651      1107        939
## 12 Mormon        29        40        48        51        56       112         85
## 13 Muslim         6         7         9        10         9        23         16
## 14 Orthodox      13        17        23        32        32        47         38
## 15 Other C~       9         7        11        13        13        14         18
## 16 Other F~      20        33        40        46        49        63         46
## 17 Other W~       5         2         3         4         2         7          3
## 18 Unaffil~     217       299       374       365       341       528        407
## # ... with 3 more variables: $100-150k <dbl>, >150k <dbl>,
## #   Don't know/refused <dbl>

现在我们像把这个数据变为长数据,我们可以先建立一个模板,语法并没有什么不同:

spec <- relig_income %>% 
  build_longer_spec(
    cols = !religion, 
    names_to = "income",
    values_to = "count"
    ) # 建立模板

spec
## # A tibble: 10 x 3
##    .name              .value income            
##    <chr>              <chr>  <chr>             
##  1 <$10k              count  <$10k             
##  2 $10-20k            count  $10-20k           
##  3 $20-30k            count  $20-30k           
##  4 $30-40k            count  $30-40k           
##  5 $40-50k            count  $40-50k           
##  6 $50-75k            count  $50-75k           
##  7 $75-100k           count  $75-100k          
##  8 $100-150k          count  $100-150k         
##  9 >150k              count  >150k             
## 10 Don't know/refused count  Don't know/refused

可以看到多了.name.value列,这就是模板的作用,可以用在接下来的转换中。

# 使用模板进行转换
pivot_longer_spec(relig_income, spec)
## # A tibble: 180 x 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

上面的2步其实和正常的1步效果一样,只是为了演示模板的基础用法,在实际情况中大家可以自己设定更为精确的模板。

relig_income %>% 
  pivot_longer(
    cols = !religion,
    names_to = "income",
    values_to = "count"
  )
## # A tibble: 180 x 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

变宽

us_rent_income
## # A tibble: 104 x 5
##    GEOID NAME       variable estimate   moe
##    <chr> <chr>      <chr>       <dbl> <dbl>
##  1 01    Alabama    income      24476   136
##  2 01    Alabama    rent          747     3
##  3 02    Alaska     income      32940   508
##  4 02    Alaska     rent         1200    13
##  5 04    Arizona    income      27517   148
##  6 04    Arizona    rent          972     4
##  7 05    Arkansas   income      23789   165
##  8 05    Arkansas   rent          709     5
##  9 06    California income      29454   109
## 10 06    California rent         1358     3
## # ... with 94 more rows
# 简单的变宽
us_rent_income %>% 
  pivot_wider(
    names_from = variable,
    values_from = c(estimate, moe)
  )
## # A tibble: 52 x 6
##    GEOID NAME                 estimate_income estimate_rent moe_income moe_rent
##    <chr> <chr>                          <dbl>         <dbl>      <dbl>    <dbl>
##  1 01    Alabama                        24476           747        136        3
##  2 02    Alaska                         32940          1200        508       13
##  3 04    Arizona                        27517           972        148        4
##  4 05    Arkansas                       23789           709        165        5
##  5 06    California                     29454          1358        109        3
##  6 08    Colorado                       32401          1125        109        5
##  7 09    Connecticut                    35326          1123        195        5
##  8 10    Delaware                       31560          1076        247       10
##  9 11    District of Columbia           43198          1424        681       17
## 10 12    Florida                        25952          1077         70        3
## # ... with 42 more rows

现在你不太喜欢这个列名,你想要income_moe/rent_moe这样的列名,这可以通过创建模板来实现。

# 创建模板
spec1 <- us_rent_income %>% 
  build_wider_spec(names_from = variable, values_from = c(estimate, moe))
spec1
## # A tibble: 4 x 3
##   .name           .value   variable
##   <chr>           <chr>    <chr>   
## 1 estimate_income estimate income  
## 2 estimate_rent   estimate rent    
## 3 moe_income      moe      income  
## 4 moe_rent        moe      rent

可以发现这个原始模板不是我们想要的,稍作修改:

spec2 <- spec1 %>% 
  mutate(.name = paste0(variable, ifelse(.value == "moe", "_moe", "")))

spec2
## # A tibble: 4 x 3
##   .name      .value   variable
##   <chr>      <chr>    <chr>   
## 1 income     estimate income  
## 2 rent       estimate rent    
## 3 income_moe moe      income  
## 4 rent_moe   moe      rent

现在列名终于是我们想要的了,接下来就可以以此为模板进行转换了。

us_rent_income %>% 
  pivot_wider_spec(spec = spec2)
## # A tibble: 52 x 6
##    GEOID NAME                 income  rent income_moe rent_moe
##    <chr> <chr>                 <dbl> <dbl>      <dbl>    <dbl>
##  1 01    Alabama               24476   747        136        3
##  2 02    Alaska                32940  1200        508       13
##  3 04    Arizona               27517   972        148        4
##  4 05    Arkansas              23789   709        165        5
##  5 06    California            29454  1358        109        3
##  6 08    Colorado              32401  1125        109        5
##  7 09    Connecticut           35326  1123        195        5
##  8 10    Delaware              31560  1076        247       10
##  9 11    District of Columbia  43198  1424        681       17
## 10 12    Florida               25952  1077         70        3
## # ... with 42 more rows

非常成功!

完全手动操作

有时候通过函数生成模板比较困难,还不如直接纯手动构建!

construction
## # A tibble: 9 x 9
##    Year Month   `1 unit` `2 to 4 units` `5 units or mor~ Northeast Midwest South
##   <dbl> <chr>      <dbl> <lgl>                     <dbl>     <dbl>   <dbl> <dbl>
## 1  2018 January      859 NA                          348       114     169   596
## 2  2018 Februa~      882 NA                          400       138     160   655
## 3  2018 March        862 NA                          356       150     154   595
## 4  2018 April        797 NA                          447       144     196   613
## 5  2018 May          875 NA                          364        90     169   673
## 6  2018 June         867 NA                          342        76     170   610
## 7  2018 July         829 NA                          360       108     183   594
## 8  2018 August       939 NA                          286        90     205   649
## 9  2018 Septem~      835 NA                          304       117     175   560
## # ... with 1 more variable: West <dbl>

纯手动构建模板:

spec <- tribble(
  ~.name,            ~.value, ~units,  ~region,     
  "1 unit",          "n",     "1",     NA,          
  "2 to 4 units",    "n",     "2-4",   NA,          
  "5 units or more", "n",     "5+",    NA,          
  "Northeast",       "n",     NA,      "Northeast", 
  "Midwest",         "n",     NA,      "Midwest",   
  "South",           "n",     NA,      "South",     
  "West",            "n",     NA,      "West",      
)

我们想要把上面这个数据集变长,下面就可以用模板了。

进行转换:

pivot_longer_spec(construction, spec)
## # A tibble: 63 x 5
##     Year Month    units region        n
##    <dbl> <chr>    <chr> <chr>     <dbl>
##  1  2018 January  1     <NA>        859
##  2  2018 January  2-4   <NA>         NA
##  3  2018 January  5+    <NA>        348
##  4  2018 January  <NA>  Northeast   114
##  5  2018 January  <NA>  Midwest     169
##  6  2018 January  <NA>  South       596
##  7  2018 January  <NA>  West        339
##  8  2018 February 1     <NA>        882
##  9  2018 February 2-4   <NA>         NA
## 10  2018 February 5+    <NA>        400
## # ... with 53 more rows

理念

使用spec(模板)进行长宽转换是完全镜像的操作,只要用同一个spec,结果是完全可以回复的。

# 和原数据一模一样
construction %>% 
  pivot_longer_spec(spec) %>% 
  pivot_wider_spec(spec)
## # A tibble: 9 x 9
##    Year Month   `1 unit` `2 to 4 units` `5 units or mor~ Northeast Midwest South
##   <dbl> <chr>      <dbl>          <dbl>            <dbl>     <dbl>   <dbl> <dbl>
## 1  2018 January      859             NA              348       114     169   596
## 2  2018 Februa~      882             NA              400       138     160   655
## 3  2018 March        862             NA              356       150     154   595
## 4  2018 April        797             NA              447       144     196   613
## 5  2018 May          875             NA              364        90     169   673
## 6  2018 June         867             NA              342        76     170   610
## 7  2018 July         829             NA              360       108     183   594
## 8  2018 August       939             NA              286        90     205   649
## 9  2018 Septem~      835             NA              304       117     175   560
## # ... with 1 more variable: West <dbl>

使用模板可以帮我们精确控制数据转换的过程,在宽数据变为长数据后,将会有:nrow(df) * nrow(spec) rows, and ncol(df) - nrow(spec) + ncol(spec) - 2 列。

获取更多R语言和生信知识,请欢迎关注公众号:医学和生信笔记

医学和生信笔记 公众号主要分享:1.医学小知识、肛肠科小知识;2.R语言和Python相关的数据分析、可视化、机器学习等;3.生物信息学学习资料和自己的学习笔记!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值