web-dev-qa-db-ja.com

グループごとに欠損値(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))))

それを行う方法はありますか?

39
Peter S

これらはすべて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

以下のその他のソリューションでは、出力が非常に似ているため、形式が大幅に異なる場合を除いて繰り返しません。

もう1つの可能性は、byソリューション(さらに下に表示)とdplyrを組み合わせることです。

df %>% by(df$houseID, na.locf2) %>% bind_rows

by

library(Zoo)

do.call(rbind, by(df, df$houseID, na.locf2))

ave

library(Zoo)

transform(df, price = ave(price, houseID, FUN = na.locf0))

data.table

library(data.table)
library(Zoo)

data.table(df)[, na.locf2(.SD), by = houseID]

Zooこのソリューションでは、Zooのみを使用しています。長い結果ではなく、広い結果を返します。

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))

[〜#〜] revised [〜#〜]ソリューションを再配置して追加しました。最新の変更dplyrに適合するようにdplyr/Zooソリューションを修正しました。固定および因数分解を適用na.locf2すべてのソリューションから。

44
G. Grothendieck

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
62
alistaire

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
13
ilir

純粋な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
9
Wojciech Sobala

dplyrimputeTSの組み合わせ。

library(dplyr)
library(imputeTS)
df %>% group_by(houseID) %>% 
mutate(price = na.locf(price, na.remaining="keep"))  

na.locfimputeTSからのより高度な欠損データ置換(補完)関数に置き換えることもできます。たとえば、na.interpolationまたはna.kalmanです。このためには、na.locfを好きな関数の名前に置き換えてください。

2
stats0007

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
2
hvollmeier