在 pivot_longer 之前清理标题
Posted
技术标签:
【中文标题】在 pivot_longer 之前清理标题【英文标题】:Clean headers before pivot_longer 【发布时间】:2022-01-13 09:57:36 【问题描述】:我有一个包含 40
传感器的数据集,这些传感器的名称很奇怪(例如 A_B_Loc_1
)。我需要将这些数据转换为长格式来绘制它们。我需要拆分名称,以便知道传感器名称(例如来自A_B_Loc_1, name=AB
)和传感器位置(例如来自A_B_Loc_1, location=1
)。
require(dplyr)
require(janitor)
require(tidyfast)
require(tidyr)
df<-data.frame(time=c("2021-02-27 22:06:20","2021-02-27 23:06:20"),A_Loc_1=c(500,600),A_Loc_2=c(500,600),A_B_Loc_1=c(500,600),A_B_Loc_2=c(500,600),B_Loc_1=c(500,600),B_3=c(500,600))
大约有 5000 万行,所以很慢:
编辑:哎哟!有些名称没有“Loc”(例如 B_3 是传感器 B,位置 3)。
#旋转它:
df %>%
tidyfast::dt_pivot_longer( #tidyfast package uses data.table instead of tidyr, so much faster
cols = -time,
names_to = "name",
values_to = "value"
) %>% drop_na()->df
#拆分名称
df %>%
separate(name,
into = c("sensor", "location"),
sep = "(?=[0-9])"
) %>%
mutate(sensor=janitor::make_clean_names(sensor, case = "big_camel"))
这可以加速吗? left join
有一个根据传感器名称添加列的查找表?
【问题讨论】:
make_clean_names
在pivot_longer
之后多次执行完全相同的操作。我会在pivot_longer
之前这样做以加快速度
【参考方案1】:
library(data.table)
setDT(df)
dt <- melt(df, id.vars = c("time"))
dt[, c("name", "location") := tstrsplit(str_replace_all(variable, "_", ""), "Loc")]
dt
# time variable value name location
# 1: 2021-02-27 22:06:20 A_Loc_1 500 A 1
# 2: 2021-02-27 23:06:20 A_Loc_1 600 A 1
# 3: 2021-02-27 22:06:20 A_Loc_2 500 A 2
# 4: 2021-02-27 23:06:20 A_Loc_2 600 A 2
# 5: 2021-02-27 22:06:20 A_B_Loc_1 500 AB 1
# 6: 2021-02-27 23:06:20 A_B_Loc_1 600 AB 1
# 7: 2021-02-27 22:06:20 A_B_Loc_2 500 AB 2
# 8: 2021-02-27 23:06:20 A_B_Loc_2 600 AB 2
# 9: 2021-02-27 22:06:20 B_Loc_1 500 B 1
# 10: 2021-02-27 23:06:20 B_Loc_1 600 B 1
编辑: OP 提到 Loc 并不总是存在,因此我们在最后一个下划线处拆分以获得数字。然后我们在第二步中清理名称以删除下划线和 - 如果存在 - “Loc”
dt <- melt(df, id.vars = c("time"))
dt[, c("name", "location") := tstrsplit(variable, "_(?!.*_)", perl = T)]
dt[, name := str_replace_all(name, "_|Loc", "")]
【讨论】:
tstrsplit(variable, "_Loc_")
可能会更好
那么你的名字仍然以 A_B 结尾,拆分完全取决于列名的健壮程度,正如 OP 在另一条评论中提到的那样,Loc 突然并不总是存在。我根据 Loc 的存在选择了一种简单的方法来首先删除所有下划线。
啊哈,你是对的。【参考方案2】:
我们尝试了几种通过正则表达式拆分列的方法。 separate
非常慢,但最快的似乎是 stringr::str_split(..., simplify=TRUE)
来创建新列(用于 tibble):
require(dplyr)
require(janitor)
require(tidyr)
require(stringr)
df <-
data.frame(
time = c("2021-02-27 22:06:20", "2021-02-27 23:06:20"),
A_Loc_1 = c(500, 600),
A_Loc_2 = c(500, 600),
A_B_Loc_1 = c(500, 600),
A_B_Loc_2 = c(500, 600),
B_Loc_1 = c(500, 600)
)
df1 <- df %>%
# Suggestion from above about cleaning names first?
clean_names(case = "big_camel") %>%
tidyfast::dt_pivot_longer(
cols = -Time,
names_to = "name",
values_to = "value") %>%
drop_na() %>%
as_tibble
df1[c("sensor", "location")] <-
str_split(df1$name, "Loc", simplify = TRUE)
这是假设你最大的时间消耗者是分隔列部分!
编辑
至少有四种拆分方式,根据拆分的复杂程度,使用其他方法(例如data.table::tstrsplit
)可能会更快,但其中一些需要在所有行之间进行一致的“拆分” :
library(tidyverse)
library(data.table)
# a sample of 100,000 pivoted rows
n <- 1e5
df <- data.frame(condition = c(rep("ABLoc1", times = n),
rep("ABLoc2", times = n),
rep("ACLoc1", times = n),
rep("ACLoc2", times = n),
rep("AALoc4", times = n)))
(speeds <- bench::mark(
separate =
df_sep <- df %>%
separate(condition,sep = "Loc", into = c("part1", "part2"), remove = FALSE)
,
dt =
df_dt <- data.table::data.table(df)
df_dt <-
df_dt[, c("part1" , "part2") := tstrsplit(condition, split = "Loc", fixed = TRUE)]
,
stringr =
df_str <- df
df_str[c("part1", "part2")] <- str_split(df_str$condition, "Loc", simplify = TRUE)
,
gsub =
df_vec <- df
df_vec$part1 <- gsub("(^.*)Loc.*", "\\1", df$condition)
df_vec$part2 <- gsub(".*Loc(.*$)", "\\1", df$condition)
,
iterations = 10,
check = FALSE
))
#> # A tibble: 4 x 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 separate 4.63s 5.19s 0.191 3.89GB 4.25
#> 2 dt 99.44ms 112.32ms 8.95 28.91MB 0.895
#> 3 stringr 296.11ms 306.5ms 3.16 59.53MB 0.632
#> 4 gsub 502.85ms 528.69ms 1.63 7.63MB 0.163
plot(speeds, type = "beeswarm")
每种方法的绘图速度(迭代超过 100,000 行):
由reprex package (v2.0.1) 于 2021-12-08 创建
【讨论】:
非常感谢。如果名称中没有“Loc”会怎样?抱歉,我刚刚看到有些名称没有 Loc,而只有 A_B_1 或 B_2。但是这个数字总是存在的。 啊,那么我认为上面的分隔符 ((?=[0-9])
) 可能效果最好?
separate 可能会更慢,但不会那么慢 ;) 并且您的示例数据集没有分配这么多内存。在您的单独示例中,您包含超过 4.25 秒的虚假垃圾收集。通过清理你自己的 R 内存(在测试之前)来惩罚这种方法是不公平的。另一个注意事项,对于 data.table 方法,包含到 data.table 的转换是不公平的,也没有必要重新分配 dt,因为 dt 通过引用更新。
非常真实!这是一个相当快速和肮脏的比较,试图将我们注意到的时间与 1m+ 行数据集进行比较。它让我意识到data.table
的相对有用性:)以上是关于在 pivot_longer 之前清理标题的主要内容,如果未能解决你的问题,请参考以下文章