Como remodelar dados de formato longo para amplo


263

Estou tendo problemas para reorganizar o seguinte quadro de dados:

set.seed(45)
dat1 <- data.frame(
    name = rep(c("firstName", "secondName"), each=4),
    numbers = rep(1:4, 2),
    value = rnorm(8)
    )

dat1
       name  numbers      value
1  firstName       1  0.3407997
2  firstName       2 -0.7033403
3  firstName       3 -0.3795377
4  firstName       4 -0.7460474
5 secondName       1 -0.8981073
6 secondName       2 -0.3347941
7 secondName       3 -0.5013782
8 secondName       4 -0.1745357

Quero reformulá-lo para que cada variável "name" seja um nome de arquivo, com os "valores" como observações ao longo dessa linha e os "números" como nomes de colunas. Mais ou menos assim:

     name          1          2          3         4
1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
5 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

Eu olhei melte castalgumas outras coisas, mas nenhuma parece fazer o trabalho.



4
@ Frank: este é um título muito melhor. longo forma e de largura-forma são as condições convencionais utilizadas. A outra resposta não pode ser encontrada pesquisando sobre esses termos.
SMCI

mais uma pergunta: como mudar de volta?
HappyLiang

Respostas:


257

Usando a reshapefunção:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")

13
+1 e você não precisa confiar em pacotes externos, pois reshapevem com ele stats. Sem mencionar que é mais rápido! =)
aL3xa

@indra_patil - Eu provavelmente usaria o pacote reshape2 conforme indicado em uma das outras respostas. Você pode criar uma nova pergunta específica para o seu caso de uso e publicá-la se não conseguir descobrir.
Chase

5
reshapeé um exemplo excelente para uma API de função horrível. É muito perto de inútil.
NoBackingDown 26/10/19

14
Os reshapecomentários e nomes de argumentos semelhantes não são tão úteis. No entanto, descobri que, por muito tempo, você deve fornecer data =seus dados.frame, idvar= a variável que identifica seus grupos, v.names= as variáveis ​​que se tornarão várias colunas em formato amplo, timevar= a variável que contém os valores que serão anexados para v.namesem formato amplo,, direction = widee sep = "_". Claro o suficiente? ;)
Brian D

3
Eu diria que a base R ainda ganha em termos de voto por um fator de cerca de 2 para 1
vonjd 22/11/18

129

O novo tidyrpacote (em 2014) também faz isso de forma simples, com gather()/ spread()sendo os termos para melt/cast .

Edit: Agora, em 2019, o tidyr v 1.0 foi iniciado e definido spreade gatherem um caminho de descontinuação, preferindo pivot_widere pivot_longer, que você pode encontrar descrito nesta resposta . Leia se você quiser uma breve visão da vida curta de spread/gather.

library(tidyr)
spread(dat1, key = numbers, value = value)

No github ,

tidyré uma reformulação reshape2projetada para acompanhar a estrutura de dados organizada e trabalhar de mãos dadas com magrittredplyr construir um pipeline sólido para análise de dados.

Assim como reshape2fez menos que remodelar, tidyrfaz menos que reshape2. Ele foi projetado especificamente para arrumar dados, não a remodelagem geral que reshape2faz ou a agregação geral que remodelou. Em particular, os métodos internos funcionam apenas para quadros de dados e tidyrnão fornecem margens ou agregação.


5
Só queria adicionar um link à página do R Cookbook que discuta o uso dessas funções de tidyre reshape2. Ele fornece bons exemplos e explicações.
21417 Jake

71

Você pode fazer isso com a reshape()função ou com as funções melt()/ cast()no pacote de remodelação. Para a segunda opção, o código de exemplo é

library(reshape)
cast(dat1, name ~ numbers)

Ou usando reshape2

library(reshape2)
dcast(dat1, name ~ numbers)

2
Pode valer a pena notar que apenas usar castou dcastnão funcionará bem se você não tiver uma coluna "valor" clara. Tente dat <- data.frame(id=c(1,1,2,2),blah=c(8,4,7,6),index=c(1,2,1,2)); dcast(dat, id ~ index); cast(dat, id ~ index)e você não conseguirá o que espera. Você precisa observar explicitamente o value/value.var- cast(dat, id ~ index, value="blah")e dcast(dat, id ~ index, value.var="blah")por exemplo.
thelatemail

45

Outra opção se o desempenho é uma preocupação é usar data.tablea extensão dereshape2 funções de fusão e transmissão

( Referência: Remodelagem eficiente usando data.tables )

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

E, a partir do data.table v1.9.6, podemos converter em várias colunas

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627

5
data.tableabordagem é a melhor! muito eficiente ... você verá a diferença quando namehouver uma combinação de 30 a 40 colunas !!
Joel.wilson 31/08

E se eu quisesse tirar o máximo?
19419 T.Fung

@ T.Fung Eu não entendo o que você está perguntando. Pode ser melhor abrir uma nova pergunta?
SymbolixAU

@SymbolixAU na pergunta do op 'nome' e 'números' são combinações únicas. E se eles não estivessem e eu quisesse buscar o valor máximo de cada combinação após a rotação? Não é um problema, se for uma pergunta muito complicada. Apenas comida para pensamentos. Obrigado.
50519 TFung

Ótima resposta. Obrigado. Para várias colunas, recebi "Erro no .subset2 (x, i, exato = exato)" e poderia corrigir isso forçando o uso do dcast data.table: consulte stackoverflow.com/a/44271092/190791
Timothée HENRY

26

Usando seu exemplo de quadro de dados, poderíamos:

xtabs(value ~ name + numbers, data = dat1)

2
este é bom, mas o resultado é da tabela formato que não pode não ser tão fácil de manusear como data.frame ou data.table, ambos tem uma abundância de pacotes
cloudscomputes

18

Outras duas opções:

Pacote base:

df <- unstack(dat1, form = value ~ numbers)
rownames(df) <- unique(dat1$name)
df

sqldf pacote:

library(sqldf)
sqldf('SELECT name,
      MAX(CASE WHEN numbers = 1 THEN value ELSE NULL END) x1, 
      MAX(CASE WHEN numbers = 2 THEN value ELSE NULL END) x2,
      MAX(CASE WHEN numbers = 3 THEN value ELSE NULL END) x3,
      MAX(CASE WHEN numbers = 4 THEN value ELSE NULL END) x4
      FROM dat1
      GROUP BY name')

1
Em vez de números codificados, a consulta pode ser configurada da seguinte maneira:ValCol <- unique(dat1$numbers);s <- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym <- gsub('.{1}$','',paste(s, collapse = "\n"));mquery <- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
M-- 29/04/19

13

Usando a aggregatefunção base R :

aggregate(value ~ name, dat1, I)

# name           value.1  value.2  value.3  value.4
#1 firstName      0.4145  -0.4747   0.0659   -0.5024
#2 secondName    -0.8259   0.1669  -0.8962    0.1681

11

Com a versão devel de tidyr ‘0.8.3.9000’, existe pivot_widere pivot_longeré generalizado para a remodelagem (longa -> larga, larga -> longa, respectivamente) de 1 para várias colunas. Usando os dados do OP

coluna única longa -> larga

library(dplyr)
library(tidyr)
dat1 %>% 
    pivot_wider(names_from = numbers, values_from = value)
# A tibble: 2 x 5
#  name          `1`    `2`    `3`    `4`
#  <fct>       <dbl>  <dbl>  <dbl>  <dbl>
#1 firstName   0.341 -0.703 -0.380 -0.746
#2 secondName -0.898 -0.335 -0.501 -0.175

-> criou outra coluna para mostrar a funcionalidade

dat1 %>% 
    mutate(value2 = value * 2) %>% 
    pivot_wider(names_from = numbers, values_from = c("value", "value2"))
# A tibble: 2 x 9
#  name       value_1 value_2 value_3 value_4 value2_1 value2_2 value2_3 value2_4
#  <fct>        <dbl>   <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#1 firstName    0.341  -0.703  -0.380  -0.746    0.682   -1.41    -0.759   -1.49 
#2 secondName  -0.898  -0.335  -0.501  -0.175   -1.80    -0.670   -1.00    -0.349

8

A reshapefunção base funciona perfeitamente bem:

df <- data.frame(
  year   = c(rep(2000, 12), rep(2001, 12)),
  month  = rep(1:12, 2),
  values = rnorm(24)
)
df_wide <- reshape(df, idvar="year", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

Onde

  • idvar é a coluna de classes que separa linhas
  • timevar é a coluna de classes para difundir
  • v.names é a coluna que contém valores numéricos
  • direction especifica o formato amplo ou longo
  • o separgumento opcional é o separador usado entre timevaros nomes das classes e v.namesna saída data.frame.

Se não idvarexistir, crie um antes de usar a reshape()função:

df$id   <- c(rep("year1", 12), rep("year2", 12))
df_wide <- reshape(df, idvar="id", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

Basta lembrar que idvaré necessário! O timevare v.namesparte é fácil. A saída dessa função é mais previsível do que algumas outras, pois tudo é definido explicitamente.


7

Há muito poderoso novo pacote de cientistas de dados Gênio no Win-vetorial (pessoas que fizeram vtreat, seplyre replyr) chamado cdata. Ele implementa os princípios de "dados coordenados" descritos neste documento e também nesta postagem do blog . A idéia é que, independentemente de como você organiza seus dados, deve ser possível identificar pontos de dados individuais usando um sistema de "coordenadas de dados". Aqui está um trecho da recente postagem de blog de John Mount:

Todo o sistema é baseado em duas primitivas ou operadores cdata :: moveValuesToRowsD () e cdata :: moveValuesToColumnsD (). Esses operadores têm codificação dinâmica, não dinâmica, codificação one-hot, transposição, movendo várias linhas e colunas e muitas outras transformações como casos especiais simples.

É fácil escrever muitas operações diferentes em termos das primitivas de cdata. Esses operadores podem trabalhar na memória ou na escala de big data (com bancos de dados e Apache Spark; para big data, use as variantes cdata :: moveValuesToRowsN () e cdata :: moveValuesToColumnsN ()). As transformações são controladas por uma tabela de controle que é um diagrama da (ou imagem) da transformação.

Primeiro criaremos a tabela de controle (consulte publicação no blog para obter detalhes) e depois executaremos a movimentação de dados de linhas para colunas.

library(cdata)
# first build the control table
pivotControlTable <- buildPivotControlTableD(table = dat1, # reference to dataset
                        columnToTakeKeysFrom = 'numbers', # this will become column headers
                        columnToTakeValuesFrom = 'value', # this contains data
                        sep="_")                          # optional for making column names

# perform the move of data to columns
dat_wide <- moveValuesToColumnsD(tallTable =  dat1, # reference to dataset
                    keyColumns = c('name'),         # this(these) column(s) should stay untouched 
                    controlTable = pivotControlTable# control table above
                    ) 
dat_wide

#>         name  numbers_1  numbers_2  numbers_3  numbers_4
#> 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
#> 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

1

maneira muito mais fácil!

devtools::install_github("yikeshu0611/onetree") #install onetree package

library(onetree)
widedata=reshape_toWide(data = dat1,id = "name",j = "numbers",value.var.prefix = "value")
widedata

        name     value1     value2     value3     value4
   firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
  secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

se você quiser voltar de largo para longo, altere apenas Largo para Longo e não há alterações nos objetos.

reshape_toLong(data = widedata,id = "name",j = "numbers",value.var.prefix = "value")

        name numbers      value
   firstName       1  0.3407997
  secondName       1 -0.8981073
   firstName       2 -0.7033403
  secondName       2 -0.3347941
   firstName       3 -0.3795377
  secondName       3 -0.5013782
   firstName       4 -0.7460474
  secondName       4 -0.1745357
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.