#r #tidyverse
#r #tidyverse
Вопрос:
У меня есть tibble следующим образом:
uuu <- structure(list(IsCharacter = c("a", "b"),
ShouldBeCharacter = list("One", "Another"),
IsList = list("Element1", c("Element2", "Element3"))
),
.Names = c("IsCharacter", "ShouldBeCharacter", "IsList"),
row.names = c(NA, -2L), class = c("tbl_df", "tbl", "data.frame"))
uuu
## A tibble: 2 × 3
# IsCharacter ShouldBeCharacter IsList
# <chr> <list> <list>
#1 a <chr [1]> <chr [1]>
#2 b <chr [1]> <chr [2]>
Я хотел бы преобразовать столбцы типа «ShouldBeCharacter», где все элементы имеют одинаковую длину и тип, в столбец, аналогичный «IsCharacter», оставив остальные столбцы нетронутыми.
Пока у меня есть следующая функция, которая решает проблему, но для меня она выглядит довольно банальной. Я хотел бы знать, есть ли лучшее решение, которое я не рассматриваю:
lists_to_atomic <- function(data) {
# Elements of length larger than one should be kept as lists.
# So we compute the maximum length for each column
length_column_elements <- apply(data, 2,
function(x) max(sapply(x, function(y) length(y))))
# to_simplify will contain column names of class list and with all elements of length 1
to_simplify <- colnames(data)[length_column_elements == 1 amp; sapply(data, class) == "list"]
# Do the conversion
data[,to_simplify] <- tibble::as_tibble(lapply(as.list(data[,to_simplify]), function(x) {do.call(c, x)}))
return(data)
}
Вот результат, который я получаю, обратите внимание, как изменился тип ShouldBeCharacter:
lists_to_atomic(uuu)
## A tibble: 2 × 3
# IsCharacter ShouldBeCharacter IsList
# <chr> <chr> <list>
#1 a One <chr [1]>
#2 b Another <chr [2]>
as_tibble(lapply(as.list(... do.call(c,...)))
Строка выглядит слишком сложной для меня, но я не могу найти более простую альтернативу.
Есть ли какое-либо упрощение, которое делает мою lists_to_atomic
функцию более надежной?
Обновить
Я не рассматривал возможность использования tidyr::unnest
столбцов типа list и элементов длиной 1, но, следуя ответу @taavi-p, я смог упростить функцию до этого:
lists_to_atomic <- function(data) {
# Elements of length larger than one should be kept as lists.
# So we compute the maximum length for each column
length_column_elements <- apply(data, 2,
function(x) max(sapply(x, function(y) length(y))))
# to_simplify will contain column names of class list and with all elements of length 1
to_simplify <- colnames(data)[length_column_elements == 1 amp;
vapply(data,
FUN = function(x) "list" %in% class(x),
FUN.VALUE = logical(1))]
# Do the conversion
data2 <- tidyr::unnest_(data, unnest_cols = to_simplify)
data2 <- data2[, colnames(data)] # Preserve original column order
return(data2)
}
Комментарии:
1. Как вы с самого начала получили такую структуру? Возможно, было бы проще устранить основную проблему, а не убирать беспорядок после.
2. @MrFlick У меня есть куча текстовых файлов. Каждый текстовый файл содержит несколько полей «Ключ: значение», а некоторые из «Значений» представляют собой массивы переменной длины. Если я создам фрейм данных с одной строкой на файл и одним столбцом на ключ, некоторые столбцы будут похожи на ShouldBeCharacter, а другие — на isList . Насколько я знаю, есть группа пользователей R, которые хранят линейные модели в столбцах dataframe, поэтому наличие массивов не показалось мне таким уж запутанным…
Ответ №1:
Вы можете попробовать:
library(tidyr)
uuu %>% unnest(ShouldBeCharacter)
Дополнительные примеры работы со столбцами списка можно найти в разделе «R для науки о данных»: http://r4ds.had.co.nz/many-models.html#list-columns-1
Комментарии:
1. Одна из проблем с unnest заключается в том, что я не знаю, какие столбцы нуждаются в преобразовании заранее. Однако я никогда не думал об использовании unnest со столбцами списка длиной 1, пока не прочитал ваш ответ, поэтому с вашим предложением я смог немного упростить свою функцию!