2016-09-13 2 views
3

Я пытаюсь очистить кадр данных, и я хотел бы заменить NA в одном столбце, с соответствующим значением из другого столбца. Я также хотел бы сделать это для нескольких столбцов сразу.Как заменить NA в нескольких столбцах на значение из соответствующих столбцов.

Пример данных.

set.seed(123) 

dates <- seq(as.Date("2016-01-01"), by = "day", length = 10) 
names <- rep(c("John Doe", "Jane Smith"), each = 5) 
var1_group <- runif(10) 
var2_group <- runif(10) 
var1_person <- runif(10) 
var2_person <- runif(10) 

myDF <- data.frame(names, var1_group, var2_group, var1_person, var2_person) 
myDF <- cbind(dates, myDF) 

После некоторых манипуляций с использованием dplyr ...

myDF <- myDF %>% mutate_each(funs(lag), contains("group")) 
myDF <- myDF %>% group_by(names) %>% mutate_each(funs(lag), contains("person")) 

Я получаю кучу ... ВПЛ

 dates  names var1_group var2_group var1_person var2_person 
1 2016-01-01 John Doe   NA   NA   NA   NA 
2 2016-01-02 John Doe 0.2875775 0.95683335 0.8895393 0.9630242 
3 2016-01-03 John Doe 0.7883051 0.45333416 0.6928034 0.9022990 
4 2016-01-04 John Doe 0.4089769 0.67757064 0.6405068 0.6907053 
5 2016-01-05 John Doe 0.8830174 0.57263340 0.9942698 0.7954674 
6 2016-01-06 Jane Smith 0.9404673 0.10292468   NA   NA 
7 2016-01-07 Jane Smith 0.0455565 0.89982497 0.7085305 0.4777960 
8 2016-01-08 Jane Smith 0.5281055 0.24608773 0.5440660 0.7584595 
9 2016-01-09 Jane Smith 0.8924190 0.04205953 0.5941420 0.2164079 
10 2016-01-10 Jane Smith 0.5514350 0.32792072 0.2891597 0.3181810 

То, что я хотел бы сделать сейчас, заменить Nas из столбцов * _person с соответствующим значением из столбца * _group. (См ряд 6)

 dates  names var1_group var2_group var1_person var2_person 
1 2016-01-01 John Doe   NA   NA   NA   NA 
2 2016-01-02 John Doe 0.2875775 0.95683335 0.8895393 0.9630242 
3 2016-01-03 John Doe 0.7883051 0.45333416 0.6928034 0.9022990 
4 2016-01-04 John Doe 0.4089769 0.67757064 0.6405068 0.6907053 
5 2016-01-05 John Doe 0.8830174 0.57263340 0.9942698 0.7954674 
6 2016-01-06 Jane Smith 0.9404673 0.10292468 0.9404673 0.1029246  
7 2016-01-07 Jane Smith 0.0455565 0.89982497 0.7085305 0.4777960 
8 2016-01-08 Jane Smith 0.5281055 0.24608773 0.5440660 0.7584595 
9 2016-01-09 Jane Smith 0.8924190 0.04205953 0.5941420 0.2164079 
10 2016-01-10 Jane Smith 0.5514350 0.32792072 0.2891597 0.3181810 

Это работает для одной колонки ...

myDF$var1_person <- ifelse(is.na(myDF$var1_person), myDF$var1_group, myDF$var1_person) 

Но я хотел бы сделать это для всех столбцов сразу. В моем фактическом фрейме данных каждая группа составляет около 20 столбцов. Я пробовал кучу других вещей, но я не хочу загромождать этот пост своей глупостью.

* Бонусные баллы, если вы можете получить код для n переменных на основе префикса столбца.

var1_group > var1_person 
var2_group > var2_person 
... 
varn_group > varn_person 

Спасибо!

+4

Ваша проблема возникает из-за того, что ваши данные не аккуратны - вы кодируете некоторое значение индекса в именах столбцов. Растопите данные в длинный формат (колонка 1 человек, 1 группа столбцов, 1 индексный столбец), затем сделайте замену, вернитесь в широкий формат. См. 'Reshape2' или' tidyr' для преобразования между длинными и широкими форматами. – Gregor

+0

Кроме того, поскольку вы уже используете 'dplyr',' coalesce (var1_person, var1_group) 'эквивалентно вашему' 'ifelse()' statement. – Gregor

+0

Насколько эффективен такой подход? Для очень большого кадра данных, скажем, 1 мил строк и 100 столбцов, будет ли это по-прежнему жизнеспособным?Я думал, что я мог бы также попытаться «применить» что-то вроде 'coalesce (col_list_person, col_list_group)' – JPete

ответ

2

Вот один вариант использования set из data.table, который делает замену на месте

library(data.table) 
#convert the data.frame to data.table 
setDT(myDF) 
#get the column name of 'group' and 'person' columns 
nm1 <- grep("group", names(myDF), value = TRUE) 
nm2 <- grep("person", names(myDF), value = TRUE) 
#loop through the sequence of 'nm1' 
for(j in seq_along(nm1)){ 
#set the elements in the row that are NA for each 'period' column 
#with the corresponding row from 'group' column specified in the "value" 
    set(myDF, i = which(is.na(myDF[[nm2[j]]])), j = nm2[j], 
        value = myDF[[nm1[j]]][is.na(myDF[[nm2[j]]])]) 
} 

myDF 
#  dates  names var1_group var2_group var1_person var2_person 
#1: 2016-01-01 John Doe   NA   NA   NA   NA 
#2: 2016-01-02 John Doe 0.2875775 0.95683335 0.8895393 0.9630242 
#3: 2016-01-03 John Doe 0.7883051 0.45333416 0.6928034 0.9022990 
#4: 2016-01-04 John Doe 0.4089769 0.67757064 0.6405068 0.6907053 
#5: 2016-01-05 John Doe 0.8830174 0.57263340 0.9942698 0.7954674 
#6: 2016-01-06 Jane Smith 0.9404673 0.10292468 0.9404673 0.1029247 
#7: 2016-01-07 Jane Smith 0.0455565 0.89982497 0.7085305 0.4777960 
#8: 2016-01-08 Jane Smith 0.5281055 0.24608773 0.5440660 0.7584595 
#9: 2016-01-09 Jane Smith 0.8924190 0.04205953 0.5941420 0.2164079 
#10:2016-01-10 Jane Smith 0.5514350 0.32792072 0.2891597 0.3181810 
+1

Я думаю, что это не будет обрабатывать случаи, когда есть не соответствующие пары человек/группа. Например, также должно быть «var3_person <- runif (10); var5_group <- runif (10) 'в' myDF', это заменит NA var3_person' на значение 'var5_group' –

+2

@ StevenBeaupré. Я следовал примеру OP. – akrun

+1

Хорошо. Просто хотел упомянуть об этом, если реальные данные OP не были абсолютно симметричными. –

2

Вот подход «тид-обратного». Обратите внимание, что, как комментирует @Gregor, это помогает упорядочить ваши данные. Следующее обрабатывает это для вас, а также возвращает несколько аккуратный фрейм данных. Я оставлю это вам, чтобы вернуться в исходный формат, если это необходимо.

Обратите внимание, что я использовал функцию mutate_cond(), которая находится в here.

library(tidyverse) 
library(stringr) 

myDF %>% 
    gather(key = col, value = val, -dates, -names) %>% 
    mutate(col = str_replace(col, "var", "")) %>% 
    separate(col, into = c("var", "group")) %>% 
    spread(key = group, value = val) %>% 
    mutate_cond(is.na(person), person = group) 

#> Source: local data frame [20 x 5] 
#> Groups: names [2] 
#> 
#>   dates  names var  group person 
#> *  <date>  <fctr> <chr>  <dbl>  <dbl> 
#> 1 2016-01-01 John Doe  1   NA  NA 
#> 2 2016-01-01 John Doe  2   NA  NA 
#> 3 2016-01-02 John Doe  1 0.28757752 0.8895393 
#> 4 2016-01-02 John Doe  2 0.95683335 0.9630242 
#> 5 2016-01-03 John Doe  1 0.78830514 0.6928034 
#> 6 2016-01-03 John Doe  2 0.45333416 0.9022990 
#> 7 2016-01-04 John Doe  1 0.40897692 0.6405068 
#> 8 2016-01-04 John Doe  2 0.67757064 0.6907053 
#> 9 2016-01-05 John Doe  1 0.88301740 0.9942698 
#> 10 2016-01-05 John Doe  2 0.57263340 0.7954674 
#> 11 2016-01-06 Jane Smith  1 0.94046728 0.9404673 
#> 12 2016-01-06 Jane Smith  2 0.10292468 0.1029247 
#> 13 2016-01-07 Jane Smith  1 0.04555650 0.7085305 
#> 14 2016-01-07 Jane Smith  2 0.89982497 0.4777960 
#> 15 2016-01-08 Jane Smith  1 0.52810549 0.5440660 
#> 16 2016-01-08 Jane Smith  2 0.24608773 0.7584595 
#> 17 2016-01-09 Jane Smith  1 0.89241904 0.5941420 
#> 18 2016-01-09 Jane Smith  2 0.04205953 0.2164079 
#> 19 2016-01-10 Jane Smith  1 0.55143501 0.2891597 
#> 20 2016-01-10 Jane Smith  2 0.32792072 0.3181810 

Все, кроме последней строки, посвящено уборке данных. Последняя строка (mutate_cond()) обрабатывает замену значений NA. Если ваши столбцы названы таким образом, то это должно распространяться на любой n.

2

Вот идея:

tag <- c("person", "group") 

# Create a list of 2 elements, persons and groups. 
lst <- lapply(tag, function(x) { myDF[grepl(x, colnames(myDF))] }) 

# Extract everything before the underscore "_" in the column names 
ext <- lapply(seq_along(lst), function(x) { 
    stringi::stri_extract(colnames(lst[[x]]), regex = "^[^_]+(?=_)") }) 

# Find the common elements between the two 
int <- intersect(ext[[1]], ext[[2]]) 

# Create a new list with only the matching subset 
match_list <- lapply(lst, function(x) { select(x, matches(paste(int, collapse = "|"))) }) 

# Replace all NA values in 'person' by the corresponding values in 'group' 
res <- mapply(function(x, y) { replace(x, is.na(x), y[is.na(x)]) }, 
       match_list[[1]], match_list[[2]]) 

# Assign the result back to the original data.frame 
myDF[, colnames(res)] <- res 

Это должно игнорировать пары человек/группы несовпадающих и заменить только на соответствующих Варс