Приведение в порядок наборов данных с несколькими разделами / заголовками в переменных положениях - PullRequest
2 голосов
/ 08 января 2020

Контекст

Я пытаюсь прочитать и привести в порядок файл Excel с несколькими заголовками / разделами, расположенными в переменных положениях. Содержимое этих заголовков необходимо добавить как переменную. Входные файлы - это относительно большие файлы Excel, отформатированные с учетом читаемости (человека), но не более того.

Входные данные:

Допустим, набор данных содержит распределение типов автомобилей (в зависимости от топлива, которое они используют) для ряда городов. Как вы увидите, в исходном файле название города используется в качестве заголовка (или разделителя, как вам нужно). Нам нужен этот заголовок как переменная. К сожалению, не все типы перечислены и некоторые значения отсутствуют. Вот пример вымышленного примера:

 df <- data.frame(
        col1= c("Seattle","Diesel","Gasoline","LPG","Electric","Boston","Diesel","Gasoline","Electric"),
        col2= c(NA, 80 ,NA,10,10,NA,65,25,10)
 )
      col1 col2
1  Seattle   NA
2   Diesel   80
3 Gasoline   NA
4      LPG   10
5 Electric   10
6   Boston   NA
7   Diesel   65
8 Gasoline   25
9 Electric   10

Желаемый результат:

     city     type value
1 Seattle   Diesel    80
2 Seattle Gasoline    NA
3 Seattle      LPG    10
4 Seattle Electric    10
5  Boston   Diesel    65
6  Boston Gasoline    25
7  Boston Electric    10

Моя попытка:

Самое близкое, что я получил, это использование dplyr dense_rank() и lag(), но это не было идеальным решением.

Любой вклад приветствуется!

Ответы [ 4 ]

4 голосов
/ 08 января 2020

Предполагая, что у вас есть конечный список мер (дизель, электри c и т. Д. c), вы можете составить список для проверки. Любое значение col1, не входящее в этот набор мер, предположительно является городом. Извлеките их (обратите внимание, что в настоящее время это фактор, поэтому я использовал as.character), заполните и удалите все строки заголовков.

library(dplyr)

meas <- c("Diesel", "Gasoline", "LPG", "Electric")

df %>%
  mutate(city = ifelse(!col1 %in% meas, as.character(col1), NA)) %>%
  tidyr::fill(city) %>%
  filter(col1 != city)
#>       col1 col2    city
#> 1   Diesel   80 Seattle
#> 2 Gasoline   NA Seattle
#> 3      LPG   10 Seattle
#> 4 Electric   10 Seattle
#> 5   Diesel   65  Boston
#> 6 Gasoline   25  Boston
#> 7 Electric   10  Boston
3 голосов
/ 08 января 2020

Вот вариант, основанный на создании группы на основе набора данных us.cities из maps путем сопоставления элементов в 'city' со столбцом 'name' из 'us.cities' для создания группы, а затем создайте элемент first для col1 в качестве 'city', удалите первую строку (slice(-1))

library(maps)
library(dplyr)
library(stringr)
df %>% 
   group_by(grp = cumsum(str_detect(col1,str_c("\\b(", 
        str_c(word(us.cities$name, 1), collapse="|"), ")\\b")))) %>% 
   mutate(city = first(col1)) %>% 
   slice(-1) %>% 
   ungroup %>% 
   select(city, type = col1, value = col2)
# A tibble: 7 x 3
#  city    type     value
#  <fct>   <fct>    <dbl>
#1 Seattle Diesel      80
#2 Seattle Gasoline    NA
#3 Seattle LPG         10
#4 Seattle Electric    10
#5 Boston  Diesel      65
#6 Boston  Gasoline    25
#7 Boston  Electric    10

Или другой вариант использует str_extract вместо группировки, а затем fill как в другом посте

df %>% 
   mutate(city = str_extract(col1, str_c("\\b(", 
     str_c(word(us.cities$name, 1), collapse="|"), ")\\b"))) %>% 
   fill(city) %>% 
   filter(col1 != city) %>% 
   select(city, type = col1, value = col2)

ПРИМЕЧАНИЕ: Это также будет работать, если в 'col1' есть сотни других элементов, кроме 'city'. Здесь мы рассмотрели только города США, если в него также входят города из других стран, используйте world.cities данные из того же пакета

2 голосов
/ 08 января 2020

Параметр data.table.

Как и в ответе @ camille, я предполагаю, что вы можете сделать некоторый вектор мер, и если значение col1 отсутствует в этом списке, это город. Это группирует по cumsum, а не (!) col1 %in% meas, то есть номер группы, который увеличивается на 1 каждый раз, когда col1 не найден в meas. В каждой группе city устанавливается как first значение col1, а col1 / col2 соответствующим образом переименовываются. Затем я фильтрую только те строки, где city не равен col1 (теперь переименован type), и удаляю группирующую переменную g.

library(data.table)
setDT(df)

meas <- c("Diesel", "Gasoline", "LPG", "Electric")

df[, .(city = first(col1), type = col1, value = col2), 
   by = .(g = cumsum(!col1 %in% meas))
  ][city != type, -'g']

#       city     type value
# 1: Seattle   Diesel    80
# 2: Seattle Gasoline    NA
# 3: Seattle      LPG    10
# 4: Seattle Electric    10
# 5:  Boston   Diesel    65
# 6:  Boston Gasoline    25
# 7:  Boston Electric    10
2 голосов
/ 08 января 2020

Для полноты картины, вот базовое решение R, которое также зависит от того, можно ли сделать вектор из элементов col1, которые не являются названиями городов, и использовать его для справки:

# make your vector of non-city elements of col1 for reference
types <- c("Diesel","Gasoline","LPG","Electric")

# use that reference vector to flag city names
df$city = ifelse(!df$col1 %in% types, 1, 0)
# use cumsum with that flag to create a group id
df$group = cumsum(df$city) 

# use the split/apply/combine approach, splitting on that group id, restructuring
# each element of the resulting list as desired through lapply, then recombining 
# the results with do.call and rbind
newdf <- do.call(rbind, lapply(split(df, df$group), function(x) {

  data.frame(city = x$col1[1], type = x$col1, value = x$col2, stringsAsFactors = FALSE)[-1,]

}))

Результат:

> newdf
       city     type value
1.2 Seattle   Diesel    80
1.3 Seattle Gasoline    NA
1.4 Seattle      LPG    10
1.5 Seattle Electric    10
2.2  Boston   Diesel    65
2.3  Boston Gasoline    25
2.4  Boston Electric    10
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...