Превращение символьных столбцов, содержащих числа, в числовые в R

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

ID Group timestamp location
1    2     12 secs c(50,120)
2    1     3 secs  c(20,45)
3    1     7 secs  c(12,30)
4    2     18 secs c(45,100)
5    3     4 secs  c(0,80)

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

Пытался удалить символы и использовать as.numeric, но при запуске любого mutate орка со столбцами получаю ошибку non-numeric argument to binary operator.

data= data %>%
  mutate(timestamp = gsub("\\secs", "", timestamp)) %>%
  mutate(location = gsub("\\c()", "", location)) %>%
  separate(location, c("location.x", "location.y"), sep = ",") %>%
  drop_na(timestamp,
          location.y)

as.numeric(data$timestamp)
as.numeric(data&location.y)

data = data %>%
  group_by(Group) %>%
  mutate(av_location.y = mean(location.y),
         av_time = max(timestamp) - min(timestamp))

Если кто-нибудь знает, как я могу обойти эту проблему с вектором символов, я буду признателен.


63
4

Ответы:

Предполагая, что вы действительно имеете дело с символьными столбцами:

library(dplyr, warn.conflicts = FALSE)
data <- tribble(
~ID, ~Group, ~timestamp, ~location,
 1,   2,     "12 secs", "c(50,120)",
 2,   1,     "3 secs" , "c(20,45)",
 3,   1,     "7 secs" , "c(12,30)",
 4,   2,     "18 secs", "c(45,100)",
 5,   3,     "4 secs" , "c(0,80)") 


data |> 
  mutate(timestamp = readr::parse_number(timestamp),
         location = purrr::map(location, \(loc) textConnection(loc) |> dget())) |> 
  tidyr::unnest_wider(location, names_sep = ".")
#> # A tibble: 5 × 5
#>      ID Group timestamp location.1 location.2
#>   <dbl> <dbl>     <dbl>      <dbl>      <dbl>
#> 1     1     2        12         50        120
#> 2     2     1         3         20         45
#> 3     3     1         7         12         30
#> 4     4     2        18         45        100
#> 5     5     3         4          0         80

Выражения типа as.numeric(data$timestamp) сами по себе не сохраняют никаких изменений, вам нужно будет присвоить этот результат, т.е.

data$timestamp <- as.numeric(data$timestamp)

Решено

Мы предполагаем, что данные воспроизводимо показаны в примечании в конце. Это либо выглядит как data, где столбец location представляет собой символ, либо как data2, где столбец location представляет собой список числовых векторов. Код обрабатывает оба варианта, но если это вектор символов, то строку {...} можно при желании опустить, ничего не меняя.

Извлеките временную метку, используя separate. Это также создаст ненужный столбец, который мы устраним, используя показанную NA. convert=TRUE приводит к преобразованию номеров символов в числовые.

Следующая строка проверяет, является ли location столбцом списка, и если да, то преобразует его в символьный столбец. Эту строку можно было бы опустить, если бы мы знали, что location — это символ.

Наконец, снова используйте separate на location.

library(dplyr)
library(tidyr)

data %>%
  separate(timestamp, c("timestamp", NA), convert = TRUE) %>%
  { if (is.list(.$location)) mutate(., location = paste(location)) else . } %>%
  separate(location, c(NA,"location1", "location2", NA), convert = TRUE)

предоставление

  ID Group timestamp location1 location2
1  1     2        12        50       120
2  2     1         3        20        45
3  3     1         7        12        30
4  4     2        18        45       100
5  5     3         4         0        80

Примечание

data <- data.frame(
  ID = 1:5,
  Group = c(2L, 1L, 1L, 2L, 3L),
  timestamp = c("12 secs", "3 secs", "7 secs", "18 secs", "4 secs"),
  location = c("c(50,120)", "c(20,45)", "c(12,30)", "c(45,100)", "c(0,80)")


data2 <- data %>%
  mutate(location = lapply(location, \(x) eval(parse(text = x))))

На всякий случай, если кому-то интересно, вот как я бы это сделал, используя только базовые функции R. Было бы немного странно использовать базу R для работы с тибблом, но это работает, и, возможно, это поможет кому-то еще с аналогичным вопросом.

Я использую gsub() для удаления нечисловых символов, strsplit() для отделения двух местоположений друг от друга и lapply() для получения только первого или только второго элемента каждого элемента списка. Смотрите комментарии в коде!

Спасибо пользователю margusl (из другого ответа) за код для создания данных.

library(dplyr)
## stack overflow user margusl's code to create your data: 
data <- tribble(
  ~ID, ~Group, ~timestamp, ~location,
  1,   2,     "12 secs", "c(50,120)",
  2,   1,     "3 secs" , "c(20,45)",
  3,   1,     "7 secs" , "c(12,30)",
  4,   2,     "18 secs", "c(45,100)",
  5,   3,     "4 secs" , "c(0,80)")

## Make a new column that removes non-numeric characters from the
## timestamps and converts the type to numeric
data$time <- as.numeric(gsub("\\D", "", data$timestamp))

## Split the strings containing the two locations by the comma so we
## have a list of vectors each of length 2 where the first element has
## the first location and the second has the second
split_by_comma <- strsplit(data$location, ',')

## Then get the first element from each list element
data$loc1 <- lapply(split_by_comma, '[', 1)
## And remove all non-numeric characters and convert to numeric type
data$loc1 <- as.numeric(gsub("\\D", "", data$loc1))

## Repeat for the second element of each list element 
data$loc2 <- lapply(split_by_comma, '[', 2)
data$loc2 <- as.numeric(gsub("\\D", "", data$loc2))

Неоптимизированный базовый подход R,

lapply(df0[sapply(df0, is.character)], \(a) { 
  lapply(regmatches(a, gregexpr("[[:digit:]]+", a)) , strtoi) |> 
    list2DF() |> t() }) |> do.call(what = "cbind") |> 
  `colnames<-`(c("timestamp", "location.1" ,"location.2")) 

который не включает проверки (длины), дает

 timestamp location.1 location.2
        12         50        120
         3         20         45
         7         12         30
        18         45        100
         4          0         80

Другим вариантом может быть

cbind(strtoi(sub("\\D+", "", df0$timestamp)), 
      t(vapply(df0$location, \(i) eval(str2expression(i)), numeric(2L), USE.NAMES=FALSE))) |>
  `colnames<-`(c("timestamp", "location.1" ,"location.2")) 

(где , USE.NAMES=FALSE))) |> `colnames<-`(c("timestamp", "location.1" ,"location.2")) — косметика и может быть заменена одним закрывающимся ).)

Это очень редко. Я подозреваю, что мы имеем дело с xy-проблемой . Откуда берется location = c("c(50,120)", "c(20,45)", ...)?