denormalize/coerce 列表(带有嵌套向量)到 R 中的 data.frame
denormalize/coerce list (with nested vectors) to data.frame in R
我正在读取类似
的 yaml 文件
- person_id: 111
person_name: Russell
time:
- 1
- 2
- 3
value:
- a
- b
- c
- person_id: 222
person_name: Steven
time:
- 1
- 2
value:
- d
- e
我想反规范化为:
person_id person_name time value
1 111 Russell 1 a
2 111 Russell 2 b
3 111 Russell 3 c
4 222 Steven 1 d
5 222 Steven 2 e
我有一个解决方案,但我希望有更简洁的方法。这是嵌套列表:
l <- list(
list(
person_id = 111L,
person_name = "Russell",
time = 1:3,
value = letters[1:3]
),
list(
person_id = 222L,
person_name = "Steven",
time = 1:2,
value = letters[4:5]
)
)
关于可能的重复,这个问题类似于(1), but the structure is different (the round
/diff
/saldo
structure is transposed compared to time
/value
here), and to (2) Split comma-separated column into separate rows,但是time
是向量,而不是像director
那样的逗号分隔元素。我希望这种不同的结构有所帮助。
这可行,但不太理想,因为 (a) 新 data.frame 中的每个向量都需要处理,并且 (b) 每个向量的类型是明确的 (eg、purrr:map_chr
对比 purrr:map_int
)
# Step 1: Determine how many time the 'parent' rows need to be replicated.
values_per_person <- l %>%
purrr::modify_depth(2, length) %>%
purrr::map_int("value")
# Step 2: Pull out the parent rows and replicate the elements to match `time`.
id_replicated <- l %>%
purrr::map_int("person_id") %>%
rep(times=values_per_person)
name_replicated <- l %>%
purrr::map_chr("person_name") %>%
rep(times=values_per_person)
# Step 3: Pull out the nested/child rows.
time <- l %>%
purrr::modify_depth(1, "time") %>%
purrr::flatten_int()
value <- l %>%
purrr::modify_depth(1, "value") %>%
purrr::flatten_chr()
# Step 4: Combine the vectors in a data frame.
data.frame(
person_id = id_replicated,
person_name = name_replicated,
time = time,
value = value
)
Reduce(rbind,lapply(l,data.frame))
为了赞美@lmo 和@submartingale 的 ideas/approaches,这里有一个 purrr/tidyverse 版本,可以将每个嵌套列表转换为数据。frame/tibble(通过复制 parent name & id 的元素),然后将它们堆叠成一个小标题。
l %>%
purrr::map_df(tibble::as_tibble)
感谢大家提出如此简洁和通用的东西。
一个简单的基础 R 方法是使用 lapply
和 data.frame
到 return data.frames 的列表,然后使用 do.call
和 rbind
将 data.frames 组合成一个 data.frame 对象。
do.call(rbind, lapply(l, data.frame))
其中return
person_id person_name time value
1 111 Russell 1 a
2 111 Russell 2 b
3 111 Russell 3 c
4 222 Steven 1 d
5 222 Steven 2 e
请注意 person_name 和值将是因子向量,使用起来可能很烦人。如果需要,您可以使用 stringsAsFactors
参数将它们转换为字符向量。
do.call(rbind, lapply(l, data.frame, stringsAsFactors=FALSE))
打印输出看起来一样,但这两个变量的基础数据类型发生了变化。
(四年后,我仍然每月使用一次或两次。)yaml 包提供了一个 map handler. In this case, each map/person is converted into a tibble. Then dplyr::bind_rows()
堆栈所有的 tibbles 来创建一个更长的单曲。
path_yaml |> # Replace this line with code below to see a working example.
yaml::read_yaml(
handlers = list(map = \(x) tibble::as_tibble(x))
) |>
dplyr::bind_rows()
额外的细节:对于这个简单的数据集,甚至不需要处理程序 -- bind_rows()
会自动转换每个片段。但我怀疑它是否总是知道如何在堆叠之前强制每张地图。再加上这个显式处理程序可以更好地传达意图。
如果您想玩一个可重现的示例,请将文件路径(即,第一行)替换为
string <-
"- person_id: 111
person_name: Russell
time:
- 1
- 2
- 3
value:
- a
- b
- c
- person_id: 222
person_name: Steven
time:
- 1
- 2
value:
- d
- e
"
textConnection(string) |>
yaml::read_yaml(...
我正在读取类似
的 yaml 文件- person_id: 111
person_name: Russell
time:
- 1
- 2
- 3
value:
- a
- b
- c
- person_id: 222
person_name: Steven
time:
- 1
- 2
value:
- d
- e
我想反规范化为:
person_id person_name time value
1 111 Russell 1 a
2 111 Russell 2 b
3 111 Russell 3 c
4 222 Steven 1 d
5 222 Steven 2 e
我有一个解决方案,但我希望有更简洁的方法。这是嵌套列表:
l <- list(
list(
person_id = 111L,
person_name = "Russell",
time = 1:3,
value = letters[1:3]
),
list(
person_id = 222L,
person_name = "Steven",
time = 1:2,
value = letters[4:5]
)
)
关于可能的重复,这个问题类似于(1)round
/diff
/saldo
structure is transposed compared to time
/value
here), and to (2) Split comma-separated column into separate rows,但是time
是向量,而不是像director
那样的逗号分隔元素。我希望这种不同的结构有所帮助。
这可行,但不太理想,因为 (a) 新 data.frame 中的每个向量都需要处理,并且 (b) 每个向量的类型是明确的 (eg、purrr:map_chr
对比 purrr:map_int
)
# Step 1: Determine how many time the 'parent' rows need to be replicated.
values_per_person <- l %>%
purrr::modify_depth(2, length) %>%
purrr::map_int("value")
# Step 2: Pull out the parent rows and replicate the elements to match `time`.
id_replicated <- l %>%
purrr::map_int("person_id") %>%
rep(times=values_per_person)
name_replicated <- l %>%
purrr::map_chr("person_name") %>%
rep(times=values_per_person)
# Step 3: Pull out the nested/child rows.
time <- l %>%
purrr::modify_depth(1, "time") %>%
purrr::flatten_int()
value <- l %>%
purrr::modify_depth(1, "value") %>%
purrr::flatten_chr()
# Step 4: Combine the vectors in a data frame.
data.frame(
person_id = id_replicated,
person_name = name_replicated,
time = time,
value = value
)
Reduce(rbind,lapply(l,data.frame))
为了赞美@lmo 和@submartingale 的 ideas/approaches,这里有一个 purrr/tidyverse 版本,可以将每个嵌套列表转换为数据。frame/tibble(通过复制 parent name & id 的元素),然后将它们堆叠成一个小标题。
l %>%
purrr::map_df(tibble::as_tibble)
感谢大家提出如此简洁和通用的东西。
一个简单的基础 R 方法是使用 lapply
和 data.frame
到 return data.frames 的列表,然后使用 do.call
和 rbind
将 data.frames 组合成一个 data.frame 对象。
do.call(rbind, lapply(l, data.frame))
其中return
person_id person_name time value
1 111 Russell 1 a
2 111 Russell 2 b
3 111 Russell 3 c
4 222 Steven 1 d
5 222 Steven 2 e
请注意 person_name 和值将是因子向量,使用起来可能很烦人。如果需要,您可以使用 stringsAsFactors
参数将它们转换为字符向量。
do.call(rbind, lapply(l, data.frame, stringsAsFactors=FALSE))
打印输出看起来一样,但这两个变量的基础数据类型发生了变化。
(四年后,我仍然每月使用一次或两次。)yaml 包提供了一个 map handler. In this case, each map/person is converted into a tibble. Then dplyr::bind_rows()
堆栈所有的 tibbles 来创建一个更长的单曲。
path_yaml |> # Replace this line with code below to see a working example.
yaml::read_yaml(
handlers = list(map = \(x) tibble::as_tibble(x))
) |>
dplyr::bind_rows()
额外的细节:对于这个简单的数据集,甚至不需要处理程序 -- bind_rows()
会自动转换每个片段。但我怀疑它是否总是知道如何在堆叠之前强制每张地图。再加上这个显式处理程序可以更好地传达意图。
如果您想玩一个可重现的示例,请将文件路径(即,第一行)替换为
string <-
"- person_id: 111
person_name: Russell
time:
- 1
- 2
- 3
value:
- a
- b
- c
- person_id: 222
person_name: Steven
time:
- 1
- 2
value:
- d
- e
"
textConnection(string) |>
yaml::read_yaml(...