Замените отсутствующие значения (NA) на самые последние не-NA по группам
Я хотел бы решить следующую проблему с dplyr. Предпочтительно с одной из оконных функций.
У меня есть кадр данных с домами и ценами на покупку. Ниже приведен пример:
houseID year price
1 1995 NA
1 1996 100
1 1997 NA
1 1998 120
1 1999 NA
2 1995 NA
2 1996 NA
2 1997 NA
2 1998 30
2 1999 NA
3 1995 NA
3 1996 44
3 1997 NA
3 1998 NA
3 1999 NA
Я хотел бы создать такой кадр данных:
houseID year price
1 1995 NA
1 1996 100
1 1997 100
1 1998 120
1 1999 120
2 1995 NA
2 1996 NA
2 1997 NA
2 1998 30
2 1999 30
3 1995 NA
3 1996 44
3 1997 44
3 1998 44
3 1999 44
Вот некоторые данные в правильном формате:
# Number of houses
N = 15
# Data frame
df = data.frame(houseID = rep(1:N,each=10), year=1995:2004, price =ifelse(runif(10*N)>0.15, NA,exp(rnorm(10*N))))
Есть ли dplyr-способ сделать это?
Ответы
Ответ 1
Все они используют na.locf
из пакета zoo. Также обратите внимание, что na.locf0
(также определенный в zoo) похож на na.locf
за исключением того, что по умолчанию он na.rm = FALSE
и требует одного векторного аргумента. na.locf2
определенный в первом решении, также используется в некоторых других.
dplyr
library(dplyr)
library(zoo)
na.locf2 <- function(x) na.locf(x, na.rm = FALSE)
df %>% group_by(houseID) %>% do(na.locf2(.)) %>% ungroup
давая:
Source: local data frame [15 x 3]
Groups: houseID
houseID year price
1 1 1995 NA
2 1 1996 100
3 1 1997 100
4 1 1998 120
5 1 1999 120
6 2 1995 NA
7 2 1996 NA
8 2 1997 NA
9 2 1998 30
10 2 1999 30
11 3 1995 NA
12 3 1996 44
13 3 1997 44
14 3 1998 44
15 3 1999 44
Вариант этого:
df %>% group_by(houseID) %>% mutate(price = na.locf0(price)) %>% ungroup
Другие решения ниже дают вывод, который очень похож, поэтому мы не будем повторять его, за исключением случаев, когда формат существенно отличается.
Еще одна возможность заключается в том, чтобы объединить с by
раствора ( как показано ниже) с dplyr:
df %>% by(df$houseID, na.locf2) %>% bind_rows
от
library(zoo)
do.call(rbind, by(df, df$houseID, na.locf2))
пр
library(zoo)
transform(df, price = ave(price, houseID, FUN = na.locf0))
Таблица данных
library(data.table)
library(zoo)
data.table(df)[, na.locf2(.SD), by = houseID]
зоопарк Это решение использует только зоопарк. Возвращает широкий, а не длинный результат:
library(zoo)
z <- read.zoo(df, index = 2, split = 1, FUN = identity)
na.locf2(z)
давая:
1 2 3
1995 NA NA NA
1996 100 NA 44
1997 100 NA 44
1998 120 30 44
1999 120 30 44
Это решение может быть объединено с dplyr следующим образом:
library(dplyr)
library(zoo)
df %>% read.zoo(index = 2, split = 1, FUN = identity) %>% na.locf2
вход
Вот входные данные, используемые для примеров выше:
df <- structure(list(houseID = c(1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L,
2L, 3L, 3L, 3L, 3L, 3L), year = c(1995L, 1996L, 1997L, 1998L,
1999L, 1995L, 1996L, 1997L, 1998L, 1999L, 1995L, 1996L, 1997L,
1998L, 1999L), price = c(NA, 100L, NA, 120L, NA, NA, NA, NA,
30L, NA, NA, 44L, NA, NA, NA)), .Names = c("houseID", "year",
"price"), class = "data.frame", row.names = c(NA, -15L))
ПЕРЕСМОТРЕНО Перестроено и добавлено больше решений. Пересмотренное решение dplyr/zoo для соответствия последним изменениям dplyr. Применяется фиксированный и na.locf2
из всех решений na.locf2
.
Ответ 2
tidyr::fill
теперь делает это глупо:
library(dplyr)
library(tidyr)
# or library(tidyverse)
df %>% group_by(houseID) %>% fill(price)
# Source: local data frame [15 x 3]
# Groups: houseID [3]
#
# houseID year price
# (int) (int) (int)
# 1 1 1995 NA
# 2 1 1996 100
# 3 1 1997 100
# 4 1 1998 120
# 5 1 1999 120
# 6 2 1995 NA
# 7 2 1996 NA
# 8 2 1997 NA
# 9 2 1998 30
# 10 2 1999 30
# 11 3 1995 NA
# 12 3 1996 44
# 13 3 1997 44
# 14 3 1998 44
# 15 3 1999 44
Ответ 3
Вы можете выполнить автоматическое объединение, поддерживаемое data.table
:
require(data.table)
setDT(df) ## change it to data.table in place
setkey(df, houseID, year) ## needed for fast join
df.woNA <- df[!is.na(price)] ## version without the NA rows
# rolling self-join will return what you want
df.woNA[df, roll=TRUE] ## will match previous year if year not found
Ответ 4
Чистое решение dplyr (без зоопарка).
df %>%
group_by(houseID) %>%
mutate(price_change = cumsum(0 + !is.na(price))) %>%
group_by(price_change, add = TRUE) %>%
mutate(price_filled = nth(price, 1)) %>%
ungroup() %>%
select(-price_change) -> df2
Входящая часть примерного решения находится в конце df2.
> tail(df2, 20)
Source: local data frame [20 x 4]
houseID year price price_filled
1 14 1995 NA NA
2 14 1996 NA NA
3 14 1997 NA NA
4 14 1998 NA NA
5 14 1999 0.8374778 0.8374778
6 14 2000 NA 0.8374778
7 14 2001 NA 0.8374778
8 14 2002 NA 0.8374778
9 14 2003 2.1918880 2.1918880
10 14 2004 NA 2.1918880
11 15 1995 NA NA
12 15 1996 0.3982450 0.3982450
13 15 1997 NA 0.3982450
14 15 1998 1.7727000 1.7727000
15 15 1999 NA 1.7727000
16 15 2000 NA 1.7727000
17 15 2001 NA 1.7727000
18 15 2002 7.8636329 7.8636329
19 15 2003 NA 7.8636329
20 15 2004 NA 7.8636329
Ответ 5
Без dplyr
:
prices$price <-unlist(lapply(split(prices$price,prices$houseID),
function(x) zoo::na.locf(x,na.rm=FALSE)))
prices
houseID year price
1 1 1995 NA
2 1 1996 100
3 1 1997 100
4 1 1998 120
5 1 1999 120
6 2 1995 NA
7 2 1996 NA
8 2 1997 NA
9 2 1998 30
10 2 1999 30
11 3 1995 NA
12 3 1996 44
13 3 1997 44
14 3 1998 44
15 3 1999 44
Ответ 6
dplyr
и imputeTS
.
library(dplyr)
library(imputeTS)
df %>% group_by(houseID) %>%
mutate(price = na.locf(price, na.remaining="keep"))
Вы также можете заменить na.locf
более продвинутыми функциями замены (вменения) отсутствующих данных из imputeTS
. Например na.interpolation
или na.kalman
. Для этого просто замените na.locf
на имя функции, которая вам нравится.