长到宽格式:保留行顺序并仅为新创建的列名使用部分行值

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了长到宽格式:保留行顺序并仅为新创建的列名使用部分行值相关的知识,希望对你有一定的参考价值。

我的数据:

> print(DT)
          scenario     hyear          P
 1:  flux_0_P1.0_1 2013-2014 0.14044214
 2:  flux_0_P1.0_1 2014-2015 0.09141671
 3:  flux_0_P1.0_2 2013-2014 0.69610343
 4:  flux_0_P1.0_2 2014-2015 0.52359157
 5:  flux_0_P1.0_3 2013-2014 0.89724457
 6:  flux_0_P1.0_3 2014-2015 0.78003786
 7: flux_0_P1.0_10 2013-2014 0.73752843
 8: flux_0_P1.0_10 2014-2015 0.62216371
 9: flux_0_P1.0_11 2013-2014 0.14259943
10: flux_0_P1.0_11 2014-2015 0.15309200
11: flux_0_P1.0_12 2013-2014 0.81472886
12: flux_0_P1.0_12 2014-2015 0.66015071

我想从长格式改为宽格式:

  • 将行顺序保存在新创建的宽数据框(data.table)列的scenario列中,例如1, 2, 3, 10, 11, 12不是1, 10, 11, 12, 2, 3
  • 仅使用scenario列中行值的部分(匹配和替换模式)作为宽数据帧(data.table)中的列名称,例如从flux_0_P1.0_1P_0_P1.0_1P是原始数据框中值列的名称) hyear P_0_P1.0_1 P_0_P1.0_2 P_0_P1.0_3 P_0_P1.0_10 P_0_P1.0_11 P_0_P1.0_12 1 2013-2014 0.140 0.696 0.897 0.738 0.143 0.815 2 2014-2015 0.0914 0.524 0.780 0.622 0.153 0.660

到目前为止我的尝试:spreaddcast都改变了key列的顺序

### tidyverse
DT_wide_tidyr <- tidyr::spread(DT, scenario, P)
DT_wide_tidyr

> DT_wide_tidyr
# A tibble: 2 x 7
  hyear     flux_0_P1.0_1 flux_0_P1.0_10 flux_0_P1.0_11 flux_0_P1.0_12 flux_0_P1.0_2 flux_0_P1.0_3
  <chr>             <dbl>          <dbl>          <dbl>          <dbl>         <dbl>         <dbl>
1 2013-2014        0.140           0.738          0.143          0.815         0.696         0.897
2 2014-2015        0.0914          0.622          0.153          0.660         0.524         0.780

### data.table
DT_wide_dcast <- data.table::dcast(DT, hyear ~ scenario, value.var = "P")
DT_wide_dcast

> DT_wide_dcast
       hyear flux_0_P1.0_1 flux_0_P1.0_10 flux_0_P1.0_11 flux_0_P1.0_12 flux_0_P1.0_2 flux_0_P1.0_3
1: 2013-2014    0.14044214      0.7375284      0.1425994      0.8147289     0.6961034     0.8972446
2: 2014-2015    0.09141671      0.6221637      0.1530920      0.6601507     0.5235916     0.7800379

使用的数据

> dput(as.data.frame(DT))
structure(list(scenario = c("flux_0_P1.0_1", "flux_0_P1.0_1", 
"flux_0_P1.0_2", "flux_0_P1.0_2", "flux_0_P1.0_3", "flux_0_P1.0_3", 
"flux_0_P1.0_10", "flux_0_P1.0_10", "flux_0_P1.0_11", "flux_0_P1.0_11", 
"flux_0_P1.0_12", "flux_0_P1.0_12"), hyear = c("2013-2014", "2014-2015", 
"2013-2014", "2014-2015", "2013-2014", "2014-2015", "2013-2014", 
"2014-2015", "2013-2014", "2014-2015", "2013-2014", "2014-2015"
), P = structure(c(0.140442142857143, 0.0914167142857143, 0.696103428571428, 
0.523591571428571, 0.897244571428571, 0.780037857142857, 0.737528428571428, 
0.622163714285714, 0.142599428571429, 0.153092, 0.814728857142857, 
0.660150714285714))), .Names = c("scenario", 
"hyear", "P"), class = "data.frame", row.names = c(NA, -12L))

任何建议表示赞赏!谢谢你,新年快乐!

编辑

基于@G提供的解决方案。格洛腾迪克,这是我最终使用的:

# Set row order in scenario column
DT[, scenario := factor(scenario, levels = unique(scenario))]

# tidyr
DT_wide_tidyr <- tidyr::spread(DT, scenario, P) %>% 
  dplyr::rename_at(vars(contains("flux")), funs(sub("flux", names(DT)[3], .)))
DT_wide_tidyr

# data.table
DT_wide_dcast <- data.table::dcast(DT, hyear ~ scenario, value.var = names(DT)[3])
names(DT_wide_dcast) <- gsub("flux", names(DT)[3], names(DT_wide_dcast))
DT_wide_dcast
答案

如果将scenario列更改为具有所需顺序的给定级别的因子,那么您的两种解决方案都将起作用。

如果DF是问题末尾显示的输入,那么请使用此处显示的DF2代码:

DF2 <- transform(DF, scenario = factor(scenario, levels = unique(scenario)))

如果wide是您的代码的结果,那么这将在列名中将flux更改为P

names(wide) <- sub("flux", "P", names(wide))
另一答案

Solution

DT$scenario <- gsub('flux_', 'P_', DT$scenario)
DT$scenario <- gsub('(?<=0_)(\d)$', '0\1', DT$scenario, perl = TRUE)
DT <- tidyr::spread(DT, scenario, P)

Result

      hyear P_0_P1.0_01 P_0_P1.0_02 P_0_P1.0_03 P_0_P1.0_10 P_0_P1.0_11
1 2013-2014  0.14044214   0.6961034   0.8972446   0.7375284   0.1425994
2 2014-2015  0.09141671   0.5235916   0.7800379   0.6221637   0.1530920
  P_0_P1.0_12
1   0.8147289
2   0.6601507

Explanation

你的问题是因为字母顺序“1”,“2”,“10”导致“1”,“10”,“2”。如果添加前导零,此问题就会消失。

Update

您可以使用以下函数对此进行概括:

custom_spread <- function(data, key, value, strip_name = NULL) {
    if ( !is.null(strip_name) ) {
        data[, key] <- gsub(strip_name, key, data[, key])
    }
    data[, key] <- gsub('(?<=0_)(\d)$', '0\1', data[, key], perl = TRUE)
    data <- tidyr::spread(data, key, value)
    colnames(data) <- gsub('(?<=0_)0(\d)$', '\1', colnames(data), perl = TRUE)
    return(data)
}

例如,将它与您的问题一起使用:

custom_spread(DT, 'scenario', 'P', strip_name = 'flux')

仍然给出相同的结果:

      hyear scenario_0_P1.0_1 scenario_0_P1.0_2 scenario_0_P1.0_3
1 2013-2014        0.14044214         0.6961034         0.8972446
2 2014-2015        0.09141671         0.5235916         0.7800379
  scenario_0_P1.0_10 scenario_0_P1.0_11 scenario_0_P1.0_12
1          0.7375284          0.1425994          0.8147289
2          0.6221637          0.1530920          0.6601507

但您可以将此用于任何其他值列名称,如“T”,“U”等。这也会删除添加的前导零以使列排序正确。如果你想保持前导零,只需在return()之前注释掉该行。

以上是关于长到宽格式:保留行顺序并仅为新创建的列名使用部分行值的主要内容,如果未能解决你的问题,请参考以下文章

Redshift SQL - 高/长到宽格式

熊猫从长到宽重塑,由两个变量

athena presto - 从长到宽的多列

从长到宽重塑并创建具有二进制值的列

在 R 中使用多个观察值从长到宽转换

使用熊猫将数据帧从长到宽转换-单行输出