Подтвердить что ты не робот

Отменить структуру списка

ЗАДАЧА

Учитывая список списков, моя цель - отменить его структуру (язык R).

Итак, я хочу, чтобы элементы вложенных списков были элементами списка первого уровня.

Вероятно, пример лучше указывает мою цель. Дано:

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

Я хочу, чтобы результат был эквивалентен последующему объекту R:

o <- list(a = list(z1 = 1, z2 = 1), b = list(z1 = 2, z2 = 4), c = list(z1 = 3, z2 = 0))

SOLUTIONS

МОЕ РЕШЕНИЕ

Я создал свое собственное решение, которое я прилагаю ниже, но дайте мне знать, если есть что-то лучше.

revert_list_str_1 <- function(ls) {
  res <- lapply(names(ls[[1]]), function(n, env) {
    name <- paste(n, 'elements', sep = '_')
    assign(name, vector('list', 0))
    inner <- sapply(ls, function(x) {
      assign(name, c(get(name), x[which(names(x) == n)]))
    })
    names(inner) <- names(ls)

    inner
  })
  names(res) <- names(ls[[1]])

  res
}

Выполнение str(revert_list_str_1(z)) Я получаю следующий вывод, соответствующий тому, что я хотел.

List of 3
 $ a:List of 2
  ..$ z1: num 1
  ..$ z2: num 1
 $ b:List of 2
  ..$ z1: num 2
  ..$ z2: num 4
 $ c:List of 2
  ..$ z1: num 3
  ..$ z2: num 0

Но, как я сказал , я бы хотел изучить (и узнать) существование более элегантного и динамичного решения.

Фактически мое решение работает полностью, только если все вложенные списки имеют одинаковые имена (также в другом порядке). Это из-за names(ls[[1]]). Я также хотел бы указать, что он действует только в списках из 2 уровней, как и сообщалось.

Итак, знаете ли вы другие более динамичные решения? Могут ли функции rapply и/или Filter быть полезными для этой задачи?

end edit 1.

АНАЛИЗ ПРЕДЛАГАЕМЫХ РЕШЕНИЙ

Я немного проанализировал предлагаемые решения, и все вы! Анализ состоит из проверки следующих точек для всех функций:

  • принятые классы (вложенные элементы списка)
    • сохраняется и при наличии элементов с разными типами (если они являются атомарными)
    • содержащийся в сохраненных элементах (например, в матрице)
  • рассмотрены столбцы (для столбцов я имею в виду имена вложенных списков)
    • не считаются общие столбцы (классификация "не" понимается положительно в этом случае)
    • не сохранены общие столбцы
    • он работает также, когда столбцы не совпадают (основанные только на именах первого вложенного списка)

Во всех этих случаях классификация "да" понимается положительно для точки 2.1.

Это все функции, которые я рассмотрел (комментарии относятся к элементам анализа, упомянутым выше):

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, not 2.3
revert_list_str_1 <- function(ls) { # @leodido
    # see above
}

# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_2 <- function(ls) { # @mnel
  # convert each component of list to a data.frame
  # so rbind.data.frame so named elements are matched
  x <- data.frame((do.call(rbind, lapply(ls, data.frame))))
  # convert each column into an appropriately named list
  o <- lapply(as.list(x), function(i, nam) as.list(`names<-`(i, nam)), nam = rownames(x))

  o
}

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_3 <- function(ls) { # @mnel
  # unique names
  nn <- Reduce(unique, lapply(ls, names))
  # convert from matrix to list `[` used to ensure correct ordering
  as.list(data.frame(do.call(rbind,lapply(ls, `[`, nn))))
}

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_4 <- function(ls) { # @Josh O'Brien
  # get sub-elements in same order
  x <- lapply(ls, `[`, names(ls[[1]]))
  # stack and reslice
  apply(do.call(rbind, x), 2, as.list) 
}

# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_5 <- function(ls) { # @mnel
  apply(data.frame((do.call(rbind, lapply(ls, data.frame)))), 2, as.list)
}

# not 1.1
# not 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_6 <- function(ls) { # @baptiste + @Josh O'Brien
  b <- recast(z, L2 ~ L1)
  apply(b, 1, as.list)
}

# yes 1.1
# yes 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_7 <-  function(ll) { # @Josh O'Brien
  nms <- unique(unlist(lapply(ll, function(X) names(X))))
  ll <- lapply(ll, function(X) setNames(X[nms], nms))
  ll <- apply(do.call(rbind, ll), 2, as.list)
  lapply(ll, function(X) X[!sapply(X, is.null)])
}

ОТХОДОВ

Из этого анализа вытекает, что:

  • Функции revert_list_str_7 и revert_list_str_6 являются наиболее гибкими относительно имен вложенного списка
  • функции revert_list_str_4, revert_list_str_3, за которыми следуют мои собственные функции, достаточно полные, хорошие компромиссы.
  • наиболее полная в абсолютной функции revert_list_str_7.

ОРИЕНТИРЫ

Для завершения работы я выполнил несколько небольших тестов (с пакетом microbenchmark R) для этих 4 функций (times = 1000 для каждого теста).

BENCHMARK 1

Input:

list(z1 = list(a = 1, b = 2, c = 3), z2 = list(a = 0, b = 3, d = 22, f = 9)).

Результаты:

Unit: microseconds
    expr       min         lq     median         uq       max
1 func_1   250.069   467.5645   503.6420   527.5615  2028.780
2 func_3   204.386   393.7340   414.5485   429.6010  3517.438
3 func_4    89.922   173.7030   189.0545   194.8590  1669.178
4 func_6 11295.463 20985.7525 21433.8680 21934.5105 72476.316
5 func_7   348.585   387.0265   656.7270   691.2060  2393.988

Победитель: revert_list_str_4.

BENCHMARK 2

Input:

list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5)).

revert_list_str_6 исключен, поскольку он не поддерживает разные типы вложенных дочерних элементов.

Результаты:

Unit: microseconds
    expr     min       lq   median       uq      max
1 func_1 249.558 483.2120 502.0915 550.7215 2096.978
2 func_3 210.899 387.6835 400.7055 447.3785 1980.912
3 func_4  92.420 170.9970 182.0335 192.8645 1857.582
4 func_7 257.772 469.9280 477.8795 487.3705 2035.101

Победитель: revert_list_str_4.

BENCHMARK 3

Input:

list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m)).

m - это матрица 3x3 целых чисел, а revert_list_str_6 снова исключена.

Результаты:

Unit: microseconds
expr     min       lq   median       uq      max
1 func_1 261.173 484.6345 503.4085 551.6600 2300.750
2 func_3 209.322 393.7235 406.6895 449.7870 2118.252
3 func_4  91.556 174.2685 184.5595 196.2155 1602.983
4 func_7 252.883 474.1735 482.0985 491.9485 2058.306

Победитель: revert_list_str_4. Опять же!

end edit 2.

Заключение

Прежде всего: благодаря всем замечательным решениям.

На мой взгляд, если вы заранее знаете, что список будет иметь вложенный список с одинаковыми именами, reverse_str_4 является победителем как лучший компромисс между выступлениями и поддержкой для разных типов.

Наиболее полное решение revert_list_str_7, хотя полная гибкость приводит к тому, что в среднем примерно в 2,5 раза ухудшается производительность по сравнению с reverse_str_4 (полезно, если ваш вложенный список имеет разные имена).

4b9b3361

Ответ 1

Edit:

Здесь более гибкая версия, которая будет работать над списками, элементы которых не обязательно содержат один и тот же набор подэлементов.

fun <-  function(ll) {
    nms <- unique(unlist(lapply(ll, function(X) names(X))))
    ll <- lapply(ll, function(X) setNames(X[nms], nms))
    ll <- apply(do.call(rbind, ll), 2, as.list)
    lapply(ll, function(X) X[!sapply(X, is.null)])
}

## An example of an 'unbalanced' list
z <- list(z1 = list(a = 1, b = 2), 
          z2 = list(b = 4, a = 1, c = 0))
## Try it out
fun(z)

Оригинальный ответ

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

zz <- lapply(z, `[`, names(z[[1]]))   ## Get sub-elements in same order
apply(do.call(rbind, zz), 2, as.list) ## Stack and reslice

Ответ 2


EDIT - работа с предложением @Josh O'Briens и моими собственными импровизациями

Проблема заключалась в том, что do.call rbind не вызывал rbind.data.frame, который выполняет некоторые сопоставления имен. rbind.data.frame должен работать, потому что data.frames - это списки, и каждый подсписок - это список, поэтому мы можем просто вызвать его напрямую.

apply(do.call(rbind.data.frame, z), 1, as.list)

Однако, хотя это может быть succicint, оно медленное, потому что do.call(rbind.data.frame, ...) по своей сути медленнее.


Что-то вроде (в два этапа)

 # convert each component of z to a data.frame
 # so rbind.data.frame so named elements are matched
 x <- data.frame((do.call(rbind, lapply(z, data.frame))))
 # convert each column into an appropriately named list
 o <- lapply(as.list(x), function(i,nam) as.list(`names<-`(i, nam)), nam = rownames(x))
 o
$a
$a$z1
[1] 1

$a$z2
[1] 1


$b
$b$z1
[1] 2

$b$z2
[1] 4


$c
$c$z1
[1] 3

$c$z2
[1] 0

И альтернатива

# unique names
nn <- Reduce(unique,lapply(z, names))
# convert from matrix to list `[` used to ensure correct ordering
as.list(data.frame(do.call(rbind,lapply(z, `[`, nn))))

Ответ 3

изменить форму можно закрыть,

library(reshape)
b = recast(z, L2~L1)
split(b[,-1], b$L2)

Ответ 4

Как насчет этого простого решения, которое является полностью общим и почти таким же быстрым, как исходный ответ Джоша О'Брайена, который предполагал общие внутренние имена (# 4).

zv <- unlist(unname(z), recursive=FALSE)
ans <- split(setNames(zv, rep(names(z), lengths(z))), names(zv))

И вот общая версия, которая надежно не имеет имен:

invertList <- function(z) {
    zv <- unlist(unname(z), recursive=FALSE)
    zind <- if (is.null(names(zv))) sequence(lengths(z)) else names(zv)
    if (!is.null(names(z)))
        zv <- setNames(zv, rep(names(z), lengths(z)))
    ans <- split(zv, zind)
    if (is.null(names(zv))) 
        ans <- unname(ans)
    ans
}

Ответ 5

Недавно выпущенный purrr содержит функцию transpose, целью которой является "вернуть" структуру списка. Существует существенная оговорка к функции transpose, она соответствует положению, а не имени, https://cran.r-project.org/web/packages/purrr/purrr.pdf. Это означает, что это не правильный инструмент для теста 1 выше. Поэтому я рассматриваю только контрольные показатели 2 и 3.

Контрольный показатель 2

B2 <- list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))

revert_list_str_8 <-  function(ll) { # @z109620
  transpose(ll)
}

microbenchmark(revert_list_str_1(B2), revert_list_str_3(B2), revert_list_str_4(B2), revert_list_str_7(B2), revert_list_str_8(B2), times = 1e3)
Unit: microseconds
                  expr     min       lq       mean   median       uq      max neval
 revert_list_str_1(B2) 228.752 254.1695 297.066646 268.8325 293.5165 4501.231  1000
 revert_list_str_3(B2) 211.645 232.9070 277.149579 250.9925 278.6090 2512.361  1000
 revert_list_str_4(B2)  79.673  92.3810 112.889130 100.2020 111.4430 2522.625  1000
 revert_list_str_7(B2) 237.062 252.7030 293.978956 264.9230 289.1175 4838.982  1000
 revert_list_str_8(B2)   2.445   6.8440   9.503552   9.2880  12.2200  148.591  1000

Ясно, что функция transpose является победителем! Он также использует гораздо меньше кода.

Контрольный показатель 3

B3 <- list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))

microbenchmark(revert_list_str_1(B3), revert_list_str_3(B3), revert_list_str_4(B3), revert_list_str_7(B3), revert_list_str_8(B3), times = 1e3)

 Unit: microseconds
                  expr     min       lq       mean  median      uq      max neval
 revert_list_str_1(B3) 229.242 253.4360 280.081313 266.877 281.052 2818.341  1000
 revert_list_str_3(B3) 213.600 232.9070 271.793957 248.304 272.743 2739.646  1000
 revert_list_str_4(B3)  80.161  91.8925 109.713969  98.980 108.022 2403.362  1000
 revert_list_str_7(B3) 236.084 254.6580 287.274293 264.922 280.319 2718.628  1000
 revert_list_str_8(B3)   2.933   7.3320   9.140367   9.287  11.243   55.233  1000

Опять же, transpose превосходит все остальные.

Проблема с этими тестовыми тестами состоит в том, что они сосредоточены на очень маленьких списках. По этой причине многочисленные петли, вложенные в функции 1-7, не представляют слишком большой проблемы. По мере увеличения размера списка и, следовательно, увеличения итерации увеличение скорости transpose, скорее всего, увеличится.

Пакет purrr является потрясающим! Это намного больше, чем повторные списки. В сочетании с пакетом dplyr пакет purrr позволяет выполнять большую часть вашего кодирования, используя мощную и красивую функциональную парадигму программирования. Благодарю господина Хэдли!

Ответ 6

Я хотел бы добавить еще одно решение в эту ценную коллекцию (к которой я обращался много раз):

revert_list_str_9 <- function(x) do.call(Map, c(c, x))

Если бы это был код гольф, у нас был бы явный победитель! Конечно, это требует, чтобы отдельные записи списка были в том же порядке. Это можно расширить, используя различные идеи сверху, такие как

revert_list_str_10 <- function(x) {
  nme <- names(x[[1]]) # from revert_list_str_4
  do.call(Map, c(c, lapply(x, '[', nme)))
}

revert_list_str_11 <- function(x) {
  nme <- Reduce(unique, lapply(x, names)) # from revert_list_str_3
  do.call(Map, c(c, lapply(x, '[', nme)))
}

По производительности это тоже не слишком потрепанный. Если вещи отсортированы правильно, у нас есть новое решение R Base. Если нет, сроки все еще очень конкурентоспособны.

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

microbenchmark::microbenchmark(
  revert_list_str_1(z), revert_list_str_2(z),  revert_list_str_3(z),
  revert_list_str_4(z), revert_list_str_5(z),  revert_list_str_7(z),
  revert_list_str_9(z), revert_list_str_10(z), revert_list_str_11(z),
  times = 1e3
)

#> Unit: microseconds
#>                   expr     min       lq      mean   median       uq       max
#>   revert_list_str_1(z)  51.946  60.9845  67.72623  67.2540  69.8215  1293.660
#>   revert_list_str_2(z) 461.287 482.8720 513.21260 490.5495 498.8110  1961.542
#>   revert_list_str_3(z)  80.180  89.4905  99.37570  92.5800  95.3185  1424.012
#>   revert_list_str_4(z)  19.383  24.2765  29.50865  26.9845  29.5385  1262.080
#>   revert_list_str_5(z) 499.433 525.8305 583.67299 533.1135 543.4220 25025.568
#>   revert_list_str_7(z)  56.647  66.1485  74.53956  70.8535  74.2445  1309.346
#>   revert_list_str_9(z)   6.128   7.9100  11.50801  10.2960  11.5240  1591.422
#>  revert_list_str_10(z)   8.455  10.9500  16.06621  13.2945  14.8430  1745.134
#>  revert_list_str_11(z)  14.953  19.8655  26.79825  22.1805  24.2885  2084.615

К сожалению, это не созданием, но любезно предоставлено @thelatemail.