Как разделить строку на несколько столбцов?

#r #string #dplyr #split #tidyverse

#r #строка #dplyr #разделить #аккуратная вселенная

Вопрос:

У меня есть строка, которая выглядит следующим образом:

 # character string
string <- "lambs:    cows: 281        chickens: 20   goats: 3     trees: 13"
 

Я хочу создать фрейм данных, который выглядит следующим образом:

 # structure
lambs <- NA
cows <- 281
chickens <- 20
goats <- 3
trees <- 13

# dataframe
df <- 
  cbind(lambs, cows, chickens, goats, trees)  %>% 
  as.data.frame()
 

Это то, что я пробовал до сих пор:

 # split string
test <- strsplit(string, " ")
test
 

Данные довольно нечистые, поэтому интервал не всегда согласован, и иногда есть ягнята, а иногда нет ягнят (как в: "lamb: 5 cow: 50" и "lamb: cow: 40" . Какой самый простой способ сделать это с помощью tidyverse?

Комментарии:

1. Это случайно файл фиксированной ширины? Как данные стали такими запутанными в первую очередь? Вам нужно будет сделать некоторые серьезные предположения о данных, чтобы импортировать их. Но только с одной примерной строкой трудно сказать, что может происходить.

2. еще один: read.table(text = gsub('\b(?=[A-z])', 'n', string, perl = TRUE), sep = ':')

Ответ №1:

Вы можете использовать str_match_all и передавать шаблон для извлечения.

 tmp <- stringr::str_match_all(string, '\s*(.*?):\s*(\d )?')[[1]][, -1]
data <- type.convert(data.frame(tmp), as.is = TRUE)

#        X1  X2
#1    lambs  NA
#2     cows 281
#3 chickens  20
#4    goats   3
#5    trees  13
 

Это делит данные на два столбца, где первый столбец — это все, что стоит перед двоеточием ( : ), кроме пробела, а второй столбец — это число, которое следует за ним. Я сделал числовую часть необязательной, чтобы учесть случаи, подобные 'lambs' тем, у которых нет номера.

Комментарии:

1. Отличный подход, как всегда. Вы можете легко преобразовать все решения здесь в фантастическую книгу. Спасибо, что поделились

2. Спасибо! Я только что попробовал это с реальными данными, но забыл упомянуть, что некоторые числа имеют запятые (5000 или 13,14,00) и т. Д. Есть ли способ решить эту проблему? Я заметил, что код обрезал его до первой цифры перед запятой.

3. Я думаю, что простым решением без изменения большей части кода было бы удаление запятой с помощью gsub i.e stringr::str_match_all(gsub(',', '', string), '\s*(.*?):\s*(\d )?')[[1]][, -1]

4. Потрясающе, большое вам спасибо! Также только что заметил, что в некоторых числах есть пробел, например «5 23 336», Вы случайно не знаете простого решения для этого? (nvm, понял это, создав список и отключив список в аргументе шаблона)

5. @KarthikS Да, это правильно, поскольку у нас есть многократное вхождение одного и того же шаблона.

Ответ №2:

Попробуй это:

 gre <- gregexpr("\b([A-Za-z] :\s*[0-9]*)\b", string)
regmatches(string, gre)
# [[1]]
# [1] "lambs:    "   "cows: 281"    "chickens: 20" "goats: 3"     "trees: 13"   
lapply(regmatches(string, gre), strcapture, pattern = "(.*):(.*)", proto = list(anim = character(0), n = character(0)))
# [[1]]
#       anim    n
# 1    lambs     
# 2     cows  281
# 3 chickens   20
# 4    goats    3
# 5    trees   13
frames <- lapply(regmatches(string, gre), strcapture,
                 pattern = "(.*):(.*)", proto = list(anim = character(0), n = character(0)))
 

Если у вас есть несколько строк (а не только одна), то это гарантирует, что каждая строка обрабатывается, а затем все данные объединяются.

 alldat <- do.call(rbind, frames)
alldat$n <- as.integer(alldat$n)
alldat
#       anim   n
# 1    lambs  NA
# 2     cows 281
# 3 chickens  20
# 4    goats   3
# 5    trees  13
 

Если вместо этого вам действительно нужны данные в «широком» формате, то

 do.call(rbind, lapply(frames, function(z) do.call(data.frame, setNames(as.list(as.integer(z$n)), z$anim))))
#   lambs cows chickens goats trees
# 1    NA  281       20     3    13
 

Ответ №3:

Вы можете попробовать read.table . Проблема «без ягнят» может быть решена путем ввода нуля с gsub помощью .

 r <- na.omit(unlist(read.table(text=gsub(": ", " 0", string), sep=" ")))
r <- replace(r, r == 0, NA)

## long format
type.convert(as.data.frame(matrix(r, ncol=2, byrow=TRUE)), as.is=TRUE)
#         V1  V2
# 1    lambs  NA
# 2     cows 281
# 3 chickens  20
# 4    goats   3
# 5    trees  13

## wide format
setNames(type.convert(r[seq(r) %% 2 == 0]), r[seq(r) %% 2 == 1])
# lambs     cows chickens    goats    trees 
#    NA      281       20        3       13