方便地移动列

时间:2018-08-30 12:19:38

标签: r dplyr

关于如何将列移到第一位或最后一位有很好的疑问和答案。

使用dplyr的最佳答案分别类似于:

iris2 <- iris %>% head(2)
iris2 %>% select( Sepal.Width, everything()) # move Sepal.Width to first
#   Sepal.Width Sepal.Length Petal.Length Petal.Width Species
# 1         3.5          5.1          1.4         0.2  setosa
# 2         3.0          4.9          1.4         0.2  setosa

iris2 %>% select(-Sepal.Width, Sepal.Width) # move Sepal.Width to last
#   Sepal.Length Petal.Length Petal.Width Species Sepal.Width
# 1          5.1          1.4         0.2  setosa         3.5
# 2          4.9          1.4         0.2  setosa         3.0

但是我找不到在给定列之后或之前移动列的任何简便方法。

我在下面发布了一个粗略的解决方案,但:

  • 我觉得笨拙
  • 它没有利用tidyverse函数的灵活性来使用数字索引,名称,字符串等...

我相信使用vars我们还可以移动一列列表,或者移动一组在名称中显示模式的列,等等。但是我对tidyverse的风格还不太满意编程。

所以我挑战您做得更好/更聪明,或者指出我错过的显而易见的解决方案。

预期输出:

iris2 %>% move_at(Species, Sepal.Width, side = "before") 
#   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
# 1          5.1  setosa         3.5          1.4         0.2
# 2          4.9  setosa         3.0          1.4         0.2

iris2 %>% move_at(Species, Sepal.Width, side = "after")
#   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
# 1          5.1         3.5  setosa          1.4         0.2
# 2          4.9         3.0  setosa          1.4         0.2

4 个答案:

答案 0 :(得分:12)

UPDATE:使用rlang::enquo可以使它更好,然后使用@Zsombor的答案可以使它更短,更优雅。答案末尾的旧解(以R为底)

#' Move column or selection of columns
#'
#' Column(s) described by \code{cols} are moved before (default) or after the reference 
#'   column described by \code{ref}
#'
#' @param data A \code{data.frame}
#' @param cols unquoted column name or numeric or selection of columns using a select helper
#' @param ref unquoted column name
#' @param side \code{"before"} or \code{"after"}
#'
#' @return A data.frame with reordered columns
#' @export
#'
#' @examples
#' iris2 <- head(iris,2)
#' move(iris2, Species, Sepal.Width)
#' move(iris2, Species, Sepal.Width, "after")
#' move(iris2, 5, 2)
#' move(iris2, 4:5, 2)
#' move(iris2, one_of("Sepal.Width","Species"), Sepal.Width)
#' move(iris2, starts_with("Petal"), Sepal.Width)
move <- function(data, cols, ref, side = c("before","after")){
  if(! requireNamespace("dplyr")) 
    stop("Make sure package 'dplyr' is installed to use function 'move'")
  side <- match.arg(side)
  cols <- rlang::enquo(cols)
  ref  <- rlang::enquo(ref)
  if(side == "before") 
    dplyr::select(data,1:!!ref,-!!ref,-!!cols,!!cols,dplyr::everything()) 
  else
    dplyr::select(data,1:!!ref,-!!cols,!!cols,dplyr::everything())
}

示例:

iris2 %>% move(Species, Sepal.Width)
#   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
# 1          5.1  setosa         3.5          1.4         0.2
# 2          4.9  setosa         3.0          1.4         0.2

iris2 %>% move(Species, Sepal.Width, "after")
#   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
# 1          5.1         3.5  setosa          1.4         0.2
# 2          4.9         3.0  setosa          1.4         0.2

iris2 %>% move(5, 2)
#   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
# 1          5.1  setosa         3.5          1.4         0.2
# 2          4.9  setosa         3.0          1.4         0.2

iris2 %>% move(4:5, 2)
#   Sepal.Length Petal.Width Species Sepal.Width Petal.Length
# 1          5.1         0.2  setosa         3.5          1.4
# 2          4.9         0.2  setosa         3.0          1.4

iris2 %>% move(one_of("Sepal.Width","Species"), Sepal.Width)
#   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
# 1          5.1         3.5  setosa          1.4         0.2
# 2          4.9         3.0  setosa          1.4         0.2

iris2 %>% move(starts_with("Petal"), Sepal.Width)
#   Sepal.Length Petal.Length Petal.Width Sepal.Width Species
# 1          5.1          1.4         0.2         3.5  setosa
# 2          4.9          1.4         0.2         3.0  setosa

有问题的旧解决方案

这是仅使用基础R编程的简单解决方案:

move_at <- function(data, col, ref, side = c("before","after")){
  side = match.arg(side)
  col_pos <- match(as.character(substitute(col)),names(data))
  ref_pos <- match(as.character(substitute(ref)),names(data))
  sorted_pos <- c(col_pos,ref_pos)
  if(side =="after") sorted_pos <- rev(sorted_pos)
  data[c(setdiff(seq_len(ref_pos-1),col_pos),
         sorted_pos,
         setdiff(seq_along(data),c(seq_len(ref_pos),col_pos)))]
}

iris2 %>% move_at(Species, Sepal.Width)
#   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
# 1          5.1  setosa         3.5          1.4         0.2
# 2          4.9  setosa         3.0          1.4         0.2

iris2 %>% move_at(Species, Sepal.Width, "after")
#   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
# 1          5.1         3.5  setosa          1.4         0.2
# 2          4.9         3.0  setosa          1.4         0.2

答案 1 :(得分:6)

不管原始列的顺序如何,这似乎都是可行的(感谢@Moody_Mudskipper的评论):

iris %>% select(1:Sepal.Width, -Species, Species, everything()) %>% head(2)
#>   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
#> 1          5.1         3.5  setosa          1.4         0.2
#> 2          4.9         3.0  setosa          1.4         0.2
iris %>% select(1:Sepal.Width, -Sepal.Width, -Species, Species, everything()) %>% head(2)
#>   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
#> 1          5.1  setosa         3.5          1.4         0.2
#> 2          4.9  setosa         3.0          1.4         0.2

答案 2 :(得分:3)

仅作记录,另一种解决方案是

library(tidyverse)
data(iris)

iris %>% 
  select(-Species) %>% 
  add_column(Specis = iris$Species, .before = "Petal.Length") %>% 
  head()

#>   Sepal.Length Sepal.Width Specis Petal.Length Petal.Width
#> 1          5.1         3.5 setosa          1.4         0.2
#> 2          4.9         3.0 setosa          1.4         0.2
#> 3          4.7         3.2 setosa          1.3         0.2
#> 4          4.6         3.1 setosa          1.5         0.2
#> 5          5.0         3.6 setosa          1.4         0.2
#> 6          5.4         3.9 setosa          1.7         0.4

reprex package(v0.2.0)于2018-08-31创建。

答案 3 :(得分:2)

我发现了一个非常适合该问题的有趣函数(由{A5C1D2H2I1M1N2O1R2T1编写)moveMe

source('https://raw.githubusercontent.com/mrdwab/SOfun/master/R/moveMe.R')

head(iris[ moveMe(names(iris), 'Species before Sepal.Width') ], 2)
#   Sepal.Length Species Sepal.Width Petal.Length Petal.Width
# 1          5.1  setosa         3.5          1.4         0.2
# 2          4.9  setosa         3.0          1.4         0.2


head(iris[ moveMe(names(iris), 'Species after Sepal.Width') ], 2)
#   Sepal.Length Sepal.Width Species Petal.Length Petal.Width
# 1          5.1         3.5  setosa          1.4         0.2
# 2          4.9         3.0  setosa          1.4         0.2

它还允许更复杂的说明:

head(iris[ moveMe(names(iris), 'Species after Sepal.Width; Petal.Width first; Sepal.Length last') ], 2)
#   Petal.Width Sepal.Width Species Petal.Length Sepal.Length
# 1         0.2         3.5  setosa          1.4          5.1
# 2         0.2         3.0  setosa          1.4          4.9