Как изменить данные из длинного в широкий формат

263

У меня проблемы с перестановкой следующего фрейма данных:

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

Я хочу изменить его так, чтобы каждая уникальная переменная «name» представляла собой имя строки, с «значениями» в качестве наблюдений вдоль этой строки и «числами» в качестве имен столбцов. Вроде как это:

     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

Я смотрел meltи castи несколько других вещей, но никто не похоже , чтобы сделать работу.

Стив
источник
3
возможный дубликат кадра данных
Фрэнк
4
@Frank: это гораздо лучший заголовок. длинная форма и широкая форма являются стандартными терминами. Другой ответ не может быть найден путем поиска по этим терминам.
smci
еще один вопрос: как вернуть обратно?
HappyLiang

Ответы:

257

Используя reshapeфункцию:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")
гнаться
источник
13
+1 и вам не нужно полагаться на внешние пакеты, так как reshapeидет с stats. Не говоря уже о том, что это быстрее! =)
aL3xa
@indra_patil - я бы, вероятно, использовал пакет reshape2, как указано в одном из других ответов. Вы можете создать новый вопрос, специфичный для вашего варианта использования, и опубликовать его, если не можете разобраться.
Погоня
5
reshapeявляется выдающимся примером ужасного API функции. Это очень близко к бесполезному.
NoBackingDown
14
Эти reshapeкомментарии и подобные имена аргументов не все , что полезны. Тем не менее, я обнаружил, что для длинных и широких вам нужно предоставить data =ваш data.frame idvar= переменная, которая идентифицирует ваши группы, v.names= переменные, которые станут широкоформатными столбцами, timevar= переменная, содержащая значения, которые будут добавлены чтобы v.namesв широком формате, direction = wideи sep = "_". Достаточно ясно? ;)
Брайан Д
3
Я бы сказал , база R по- прежнему выигрывает голосов умудренного коэффициент примерно 2 к 1
vonjd
129

Новый (в 2014 году) tidyrпакет также делает это просто, так как gather()/ spread()является условиями для melt/ cast.

Edit: Теперь, в 2019 году, tidyr v 1,0 запустил и множество spreadи gatherна пути устаревания, предпочитая вместо этого pivot_widerи pivot_longer, которые вы можете найти описанные в этом ответе . Продолжайте читать, если вы хотите кратко взглянуть на краткую жизнь spread/gather.

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

Из GitHub ,

tidyrпредставляет собой рефрейминг, reshape2предназначенный для сопровождения аккуратной структуры данных, а также для совместной работы magrittrи dplyrсоздания надежного конвейера для анализа данных.

Так же, как reshape2сделал меньше, чем изменить, tidyrделает меньше, чем reshape2. Он разработан специально для упорядочивания данных, а не для общего изменения формы, которое reshape2делает, или общего объединения, которое изменило форму. В частности, встроенные методы работают только для фреймов данных и не tidyrобеспечивают полей или агрегирования.

Грегор Томас
источник
5
Просто хотел добавить ссылку на страницу R Cookbook , где обсуждается использование этих функций от tidyrи reshape2. Это обеспечивает хорошие примеры и объяснения.
Джейк
71

Вы можете сделать это с помощью reshape()функции или с помощью функций melt()/ cast()в пакете изменения формы. Для второго варианта пример кода

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

Или используя reshape2

library(reshape2)
dcast(dat1, name ~ numbers)
Ista
источник
2
Возможно, стоит отметить, что просто использовать castили dcastне будет работать хорошо, если у вас нет четкого столбца «значение». Попробуйте, 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)и вы не получите то, что ожидаете. Вы должны явно отметить value/value.var- cast(dat, id ~ index, value="blah")и, dcast(dat, id ~ index, value.var="blah")например.
thelatemail
45

Другой вариант, если производительность вызывает беспокойство, это использовать data.tableрасширение функций reshape2melt и dcast.

( Ссылка: Эффективное изменение формы с использованием 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

И, начиная с data.table v1.9.6, мы можем привести несколько столбцов

## 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
SymbolixAU
источник
5
data.tableподход самый лучший! очень эффективно ... вы увидите разницу, когда nameэто комбинация из 30-40 столбцов!
joel.wilson
Что если я захочу взять максимум?
T.Fung
@ T.Fung Я не понимаю, о чем ты спрашиваешь. Может быть лучше открыть новый вопрос?
SymbolixAU
@SymbolixAU в вопросе op 'name' и 'numbers' являются уникальными комбинациями. Что если бы их не было, и я хотел получить максимальное значение для каждой комбинации после поворота? Не проблема, если слишком сложный вопрос. Просто пища для мыслей. Спасибо.
Т. Фанг
Отличный ответ. Спасибо. Для нескольких столбцов, я получил «Ошибка в .subset2 (х, я, точный = точный)», и может устранить эту проблему , заставляя использовать data.table dcast: см stackoverflow.com/a/44271092/190791
Timothee HENRY
26

Используя ваш пример dataframe, мы могли бы:

xtabs(value ~ name + numbers, data = dat1)
Джим М.
источник
2
это хорошо, но в результате получается таблица формата, которая не может быть не так проста для обработки, как data.frame или data.table, оба имеют множество пакетов
cloudcomputes
18

Другие два варианта:

Базовый пакет:

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

sqldf пакет:

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')
mpalanco
источник
1
Вместо 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)
жесткого
13

Используя базовую aggregateфункцию 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

В версии devel tidyr ‘0.8.3.9000’есть pivot_widerи pivot_longerкоторая обобщается для изменения формы (long -> wide, wide -> long соответственно) от 1 до нескольких столбцов. Использование данных ОП

длинная колонна -> широкая

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

-> создал еще один столбец для отображения функциональности

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
akrun
источник
8

Базовая reshapeфункция работает отлично:

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

куда

  • idvar столбец классов, который разделяет строки
  • timevar столбец классов для широкого
  • v.names столбец, содержащий числовые значения
  • direction указывает широкий или длинный формат
  • необязательный sepаргумент - это разделитель, используемый между timevarименами классов и v.namesв выходных данных data.frame.

Если нет idvar, создайте его перед использованием reshape()функции:

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

Просто помните, что idvarтребуется! timevarИ v.namesчасть легко. Вывод этой функции более предсказуем, чем некоторые другие, так как все явно определено.

Адам Эриксон
источник
7

Существует очень мощный новый пакет от ученых-гениальных данных из Win-Vector (ребята, которые сделали vtreat, seplyrи replyr) cdata. Он реализует принципы «скоординированных данных», описанные в этом документе, а также в этом блоге . Идея состоит в том, что независимо от того, как вы организуете свои данные, должна быть возможность идентифицировать отдельные точки данных, используя систему «координат данных». Вот выдержка из недавнего сообщения в блоге Джона Маунта:

Вся система основана на двух примитивах или операторах cdata :: moveValuesToRowsD () и cdata :: moveValuesToColumnsD (). Эти операторы имеют возможность вращения, разворота, горячего кодирования, транспонирования, перемещения нескольких строк и столбцов и многих других преобразований в виде простых особых случаев.

Легко написать много разных операций в терминах примитивов cdata. Эти операторы могут работать в оперативной памяти или в большом масштабе данных (с базами данных и Apache Spark; для больших данных используйте варианты cdata :: moveValuesToRowsN () и cdata :: moveValuesToColumnsN ()). Преобразования управляются управляющей таблицей, которая сама является диаграммой (или изображением) преобразования.

Сначала мы создадим контрольную таблицу (подробности см. В блоге ), а затем выполним перемещение данных из строк в столбцы.

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
dmi3kno
источник
1

гораздо проще!

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

если вы хотите вернуться от широкого к длинному, измените только Широкий на Длинный, без изменений в объектах.

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
чжан цзин
источник