将 data.frame 从宽格式重塑为长格式

问题描述 投票:0回答:9

我在将

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()
函数来做到这一点,因为 看起来好处理一点。

r dataframe reshape r-faq
9个回答
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_to
    values_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 将读取这些值作为字符值(由于数字中的

,
)。您可以在重塑之前使用
gsub
as.numeric
进行修复:

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

或者在重塑过程中,使用

data.table
tidyr
:

# 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)

137
投票

reshape()
需要一段时间才能习惯,就像
melt
/
cast
一样。这是一个重塑的解决方案,假设您的数据框名为
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))

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
s——它仅在
is.vector
TRUE
时才有效,并且从
is.vector
的文档中,我们发现:

如果 x 是指定模式的向量,除了名称之外没有任何属性,则

is.vector
返回
TRUE
。否则返回 FALSE


我正在使用示例数据
来自@Jaap的答案

,其中年份列中的值是factors。


这是

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



11
投票
gather

中的

tidyr
的使用。您可以选择要
gather
的列,方法是单独删除它们(就像我在这里所做的那样),或者明确包含您想要的年份。

请注意,为了处理逗号(如果未设置

check.names = FALSE

,则添加 X),我还使用

dplyr
的 mutate 与
parse_number
中的
readr
将文本值转换回数字。这些都是
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

解决方案: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
不幸的是,我认为 
PIVOT

UNPIVOT
不适用于
R
SQLite
。如果您想以更复杂的方式编写查询,您还可以查看这些帖子:


1
投票
cdata

包,它使用(转换)控制表的概念:

# 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)

我目前正在探索该软件包,发现它很容易访问。它是为更复杂的转换而设计的,并且包括反向转换。有
教程

可用。


0
投票
x=unlist(m)

而不是

x=c(m)
):
> m=matrix(sample(1:100,6),3,dimnames=list(2021:2023,c("male","female")))
> m
     male female
2021   89     42
2022   39     96
2023   26     40
> cbind(expand.grid(rownames(m),colnames(m)),x=c(m))
  Var1   Var2  x
1 2021   male 89
2 2022   male 39
3 2023   male 26
4 2021 female 42
5 2022 female 96
6 2023 female 40
> data.frame(row=rownames(m),col=colnames(m)[col(m)],x=c(m))
   row    col  x
1 2021   male 89
2 2022   male 39
3 2023   male 26
4 2021 female 42
5 2022 female 96
6 2023 female 40

您还可以使用 
as.table

后跟

as.data.frame
,但它将行和列名称转换为因子,如果您的输入是数据帧,那么您必须首先将其转换为矩阵:
> as.data.frame(as.table(m))
  Var1   Var2 Freq
1 2021   male   89
2 2022   male   39
3 2023   male   26
4 2021 female   42
5 2022 female   96
6 2023 female   40
> as.data.frame(as.table(m))|>sapply(class)
     Var1      Var2      Freq
 "factor"  "factor" "integer"
> d=as.data.frame(m)
> as.data.frame(as.table(d))
Error in h(simpleError(msg, call)) :
  error in evaluating the argument 'x' in selecting a method for function 'as.data.frame': cannot coerce to a table
> as.data.frame(as.table(as.matrix(d)))
  Var1   Var2 Freq
1 2021   male   89
2 2022   male   39
3 2023   male   26
4 2021 female   42
5 2022 female   96
6 2023 female   40

stack

将前两列转换为因子,当输入是矩阵时(但不是当输入是数据帧时),第二列转换为 Rle 因子:

> stack(m)
DataFrame with 6 rows and 3 columns
       row    col     value
  <factor>  <Rle> <integer>
1     2021   male        89
2     2022   male        39
3     2023   male        26
4     2021 female        42
5     2022 female        96
6     2023 female        40

stack

的输入是数据框时,行名称不会作为列包含在内,因此您必须

cbind
它们:
> d=as.data.frame(m);cbind(row=rownames(d),stack(d))
   row values    ind
1 2021     89   male
2 2022     39   male
3 2023     26   male
4 2021     42 female
5 2022     96 female
6 2023     40 female

© www.soinside.com 2019 - 2024. All rights reserved.