将数据框从宽格式转换为长格式

249

我有些困难需要将我的 data.frame 从宽表转换为长表。目前看起来是这样的:

Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246

现在我想要将这个data.frame转换成一个长形式的data.frame,就像这样:

Code Country        Year    Value
AFG  Afghanistan    1950    20,249
AFG  Afghanistan    1951    21,352
AFG  Afghanistan    1952    22,532
AFG  Afghanistan    1953    23,557
AFG  Afghanistan    1954    24,555
ALB  Albania        1950    8,097
ALB  Albania        1951    8,986
ALB  Albania        1952    10,058
ALB  Albania        1953    11,123
ALB  Albania        1954    12,246

我已经查看并尝试使用melt()reshape()函数,因为有些人在类似的问题中建议这样做。然而,到目前为止,我只得到了混乱的结果。

如果可能的话,我想使用reshape()函数来完成,因为它看起来更容易处理。


3
不知道是否是问题的原因,但"reshape"包中的函数是melt和cast(以及recast)。 - Eduardo Leoni
3
reshape包已被reshape2所取代。 - IRTFM
7
现在,reshape2已被tidyr取代。 - drhagen
4
现在,tidyr包中的gatherspread函数已经被pivot_*函数所取代。 - NelsonGon
8个回答

228

两种替代方案:

1) 使用 :

您可以使用 melt 函数:

library(data.table)
long <- melt(setDT(wide), id.vars = c("Code","Country"), variable.name = "year")

这将会给出:

> long
    Code     Country year  value
 1:  AFG Afghanistan 1950 20,249
 2:  ALB     Albania 1950  8,097
 3:  AFG Afghanistan 1951 21,352
 4:  ALB     Albania 1951  8,986
 5:  AFG Afghanistan 1952 22,532
 6:  ALB     Albania 1952 10,058
 7:  AFG Afghanistan 1953 23,557
 8:  ALB     Albania 1953 11,123
 9:  AFG Afghanistan 1954 24,555
10:  ALB     Albania 1954 12,246

一些备选符号表示:

melt(setDT(wide), id.vars = 1:2, variable.name = "year")
melt(setDT(wide), measure.vars = 3:7, variable.name = "year")
melt(setDT(wide), measure.vars = as.character(1950:1954), variable.name = "year")

2) 使用 :

使用 pivot_longer():

library(tidyr)

long <- wide %>% 
  pivot_longer(
    cols = `1950`:`1954`, 
    names_to = "year",
    values_to = "value"
)

注意:
  • names_tovalues_to 默认为分别为"name""value",因此您可以将其简写为wide %>% pivot_longer(`1950`:`1954`)
  • cols 参数使用高度灵活的tidyselect DSL,因此您可以使用否定选择(!c(Code, Country)),选择帮助器(starts_with("19"); matches("^\\d{4}$")),数字索引(3:7)等来选择相同的列。
  • tidyr::pivot_longer()tidyr::gather()reshape2::melt() 的继承者,后两者已不再开发。

转换值

数据的另一个问题是数值将被R读取为字符值(由于数字中的,)。您可以在重塑之前使用gsubas.numeric进行修复:

long$value <- as.numeric(gsub(",", "", long$value))

或在使用 data.tabletidyr 进行重塑时:

# data.table
long <- melt(setDT(wide),
             id.vars = c("Code","Country"),
             variable.name = "year")[, value := as.numeric(gsub(",", "", value))]

# tidyr
long <- wide %>%
  pivot_longer(
    cols = `1950`:`1954`, 
    names_to = "year",
    values_to = "value",
    values_transform = ~ as.numeric(gsub(",", "", .x))
  )

数据:

wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

1
非常好的答案,只有一个小提醒:在您的数据框中除了 idtime 之外,请不要放置任何变量,否则 melt 将无法确定您想要做什么。 - Jason Goal
1
@JasonGoal,您能详细说明一下吗?根据我的理解,这应该不是问题。只需指定id.varsmeasure.vars即可。 - Jaap
1
抱歉打扰这篇帖子,有人能解释一下为什么3会起作用吗?我已经测试过它可以工作,但是当dplyr看到“-c(var1,var2)”时,我不明白它在做什么... - user5930691
1
@ReputableMisnomer 当 [tag:tidyr] 看到 -c(var1, var2) 时,它会在将数据从宽格式转换为长格式时省略这些变量。 - Jaap
10
根据tidyverse博客,函数gather现已过时,已被pivot_longer取代。他们表示:“新的pivot_longer()pivot_wider()spread()gather()的现代替代品。它们经过精心重新设计,更易于学习和记忆,并包含许多新功能。spread()gather()不会消失,但它们已被弃用,这意味着它们不再处于活跃开发状态。” - Evan Rosica
显示剩余3条评论

137

reshape()需要一些时间来适应,就像melt/cast一样。这里是使用reshape的解决方案,假设您的数据框名为d

reshape(d, 
        direction = "long",
        varying = list(names(d)[3:7]),
        v.names = "Value",
        idvar = c("Code", "Country"),
        timevar = "Year",
        times = 1950:1954)

63

使用 tidyr_1.0.0,另一个选择是 pivot_longer

library(tidyr)
pivot_longer(df1, -c(Code, Country), values_to = "Value", names_to = "Year")
# A tibble: 10 x 4
#   Code  Country     Year  Value 
#   <fct> <fct>       <chr> <fct> 
# 1 AFG   Afghanistan 1950  20,249
# 2 AFG   Afghanistan 1951  21,352
# 3 AFG   Afghanistan 1952  22,532
# 4 AFG   Afghanistan 1953  23,557
# 5 AFG   Afghanistan 1954  24,555
# 6 ALB   Albania     1950  8,097 
# 7 ALB   Albania     1951  8,986 
# 8 ALB   Albania     1952  10,058
# 9 ALB   Albania     1953  11,123
#10 ALB   Albania     1954  12,246

数据

df1 <- structure(list(Code = structure(1:2, .Label = c("AFG", "ALB"), class = "factor"), 
    Country = structure(1:2, .Label = c("Afghanistan", "Albania"
    ), class = "factor"), `1950` = structure(1:2, .Label = c("20,249", 
    "8,097"), class = "factor"), `1951` = structure(1:2, .Label = c("21,352", 
    "8,986"), class = "factor"), `1952` = structure(2:1, .Label = c("10,058", 
    "22,532"), class = "factor"), `1953` = structure(2:1, .Label = c("11,123", 
    "23,557"), class = "factor"), `1954` = structure(2:1, .Label = c("12,246", 
    "24,555"), class = "factor")), class = "data.frame", row.names = c(NA, 
-2L))

12
这篇文章需要更多点赞。根据Tidyverse Blog的说法,“gather”已经被淘汰了,“pivot_longer”现在是正确的完成此操作的方法。 - Evan Rosica
9
只有在他们决定再次更改功能之前才会生效 :p - andschar
-c(...) 是什么作用?看起来像是删除一列,但实际上并不是。你能解释一下语法吗? - undefined

39

使用 reshape 包:

#data
x <- read.table(textConnection(
"Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246"), header=TRUE)

library(reshape)

x2 <- melt(x, id = c("Code", "Country"), variable_name = "Year")
x2[,"Year"] <- as.numeric(gsub("X", "" , x2[,"Year"]))

36

由于此答案标记为,所以我觉得分享另一个基于R的选择——stack会很有用。

但要注意,stack不能与factor一起使用——只有在is.vectorTRUE时它才能正常工作。从is.vector的文档中可以发现:

如果x是没有名称之外的其他属性的指定模式的向量,则is.vector返回TRUE。否则,它将返回FALSE

我使用来自@Jaap的答案中的示例数据,其中年份列中的值是factor

以下是stack方法:

cbind(wide[1:2], stack(lapply(wide[-c(1, 2)], as.character)))
##    Code     Country values  ind
## 1   AFG Afghanistan 20,249 1950
## 2   ALB     Albania  8,097 1950
## 3   AFG Afghanistan 21,352 1951
## 4   ALB     Albania  8,986 1951
## 5   AFG Afghanistan 22,532 1952
## 6   ALB     Albania 10,058 1952
## 7   AFG Afghanistan 23,557 1953
## 8   ALB     Albania 11,123 1953
## 9   AFG Afghanistan 24,555 1954
## 10  ALB     Albania 12,246 1954

你拯救了全球的软件开发者 - DuckPyjamas

11

这是另一个示例,展示了如何使用tidyr中的gather。您可以通过逐个删除它们(如此处所做)或显式包含所需年份来选择要gather的列。

请注意,为处理逗号(以及如果未设置check.names = FALSE则添加的X),我还使用dplyrmutatereadr中的parse_number将文本值转换为数字。这些都是tidyverse的一部分,因此可以与library(tidyverse)一起加载。

wide %>%
  gather(Year, Value, -Code, -Country) %>%
  mutate(Year = parse_number(Year)
         , Value = parse_number(Value))

返回:

   Code     Country Year Value
1   AFG Afghanistan 1950 20249
2   ALB     Albania 1950  8097
3   AFG Afghanistan 1951 21352
4   ALB     Albania 1951  8986
5   AFG Afghanistan 1952 22532
6   ALB     Albania 1952 10058
7   AFG Afghanistan 1953 23557
8   ALB     Albania 1953 11123
9   AFG Afghanistan 1954 24555
10  ALB     Albania 1954 12246

6

这是一个的解决方案:

sqldf("Select Code, Country, '1950' As Year, `1950` As Value From wide
        Union All
       Select Code, Country, '1951' As Year, `1951` As Value From wide
        Union All
       Select Code, Country, '1952' As Year, `1952` As Value From wide
        Union All
       Select Code, Country, '1953' As Year, `1953` As Value From wide
        Union All
       Select Code, Country, '1954' As Year, `1954` As Value From wide;")

为了不需要输入每一个查询条件,您可以使用以下方法:

感谢G. Grothendieck实现此功能。

ValCol <- tail(names(wide), -2)

s <- sprintf("Select Code, Country, '%s' As Year, `%s` As Value from wide", ValCol, ValCol)
mquery <- paste(s, collapse = "\n Union All\n")

cat(mquery) #just to show the query
 #> Select Code, Country, '1950' As Year, `1950` As Value from wide
 #>  Union All
 #> Select Code, Country, '1951' As Year, `1951` As Value from wide
 #>  Union All
 #> Select Code, Country, '1952' As Year, `1952` As Value from wide
 #>  Union All
 #> Select Code, Country, '1953' As Year, `1953` As Value from wide
 #>  Union All
 #> Select Code, Country, '1954' As Year, `1954` As Value from wide

sqldf(mquery)

 #>    Code     Country Year  Value
 #> 1   AFG Afghanistan 1950 20,249
 #> 2   ALB     Albania 1950  8,097
 #> 3   AFG Afghanistan 1951 21,352
 #> 4   ALB     Albania 1951  8,986
 #> 5   AFG Afghanistan 1952 22,532
 #> 6   ALB     Albania 1952 10,058
 #> 7   AFG Afghanistan 1953 23,557
 #> 8   ALB     Albania 1953 11,123
 #> 9   AFG Afghanistan 1954 24,555
 #> 10  ALB     Albania 1954 12,246

很遗憾,我认为PIVOTUNPIVOT无法在R SQLite中使用。如果您想以更复杂的方式编写查询,您还可以查看以下文章:


1
您也可以使用软件包,它使用(转换)控制表的概念:
# data
wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

library(cdata)
# build control table
drec <- data.frame(
    Year=as.character(1950:1954),
    Value=as.character(1950:1954),
    stringsAsFactors=FALSE
)
drec <- cdata::rowrecs_to_blocks_spec(drec, recordKeys=c("Code", "Country"))

# apply control table
cdata::layout_by(drec, wide)

我目前正在探索这个软件包,发现它非常易于使用。它专为更复杂的转换设计,并包括反向转换。有教程可用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接