#r #dplyr #data.table
#r #dplyr #данные.таблица
Вопрос:
У меня есть набор данных с 40
датчиками с нечетными именами (например A_B_Loc_1
, ). Мне нужно преобразовать эти данные в длинный формат, чтобы отобразить их. Мне нужно разделить имена, чтобы я знал имя датчика (например, from A_B_Loc_1, name=AB
) и местоположение датчика (например, from 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))
Это около 50 миллионов строк, поэтому это очень медленно:
РЕДАКТИРОВАТЬ: Ой! В некоторых именах нет «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"))
Можно ли это ускорить? A left join
с таблицей подстановки, которая добавляет столбцы на основе имен датчиков?
Комментарии:
1.
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", "")]
Комментарии:
1.
tstrsplit(variable, "_Loc_")
может быть, будет лучше2. Тогда ваши имена все равно заканчиваются A_B , разделение зависит от того, насколько надежны имена столбцов, хотя, как упоминает OP в другом комментарии, Loc внезапно отсутствует всегда. Я выбрал простой способ, основанный на наличии Loc, чтобы сначала удалить все подчеркивания.
3. Ага, вы правы.
Ответ №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 строк):
Создано 2021-12-08 пакетом reprex (v2.0.1)
Комментарии:
1. Большое вам спасибо. Что произойдет, если в имени нет «Loc»? Извините, я только что увидел, что в некоторых именах нет Loc, а есть только A_B_1 или B_2, например. Однако число всегда присутствует.
2. Ах, тогда я думаю, что ваш разделитель выше (
(?=[0-9])
) может работать лучше всего?3. разделение может быть медленнее, но НЕ настолько медленным 😉 и ваш образец набора данных не выделил так много памяти. В вашем отдельном примере вы включаете сбор ложных данных, который занимает более 4,25 секунды. Несправедливо наказывать этот метод очисткой вашей собственной R-памяти (перед тестированием). Еще одно замечание: для метода data.table несправедливо включать преобразование в data.table и нет необходимости переназначать dt, поскольку dt обновляется по ссылке.
4. Очень верно! это было довольно быстрое и грязное сравнение, чтобы попытаться сравнить время, которое мы заметили, с наборами данных 1m row. Это заставило меня осознать относительную полезность
data.table
though 🙂