web-dev-qa-db-fra.com

Remplacer les valeurs manquantes (NA) par les plus récentes non-NA par groupe

Je voudrais résoudre le problème suivant avec dplyr. Préférable avec l'une des fonctions de fenêtre . J'ai un bloc de données avec les maisons et les prix d'achat. Ce qui suit est un exemple: 

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

Je voudrais faire un cadre de données comme ceci:

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

Voici quelques données au bon format:

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

Y a-t-il un moyen de faire cela?

36
Peter S

Ceux-ci utilisent tous na.locf du paquet Zoo:

dplyr

library(dplyr)
library(Zoo)

df %>% group_by(houseID) %>% na.locf %>% ungroup

donnant:

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

Les autres solutions ci-dessous donnent un résultat assez similaire, nous ne le répéterons donc pas, sauf dans les cas où le format diffère considérablement.

Une autre possibilité consiste à combiner la solution by (présentée ci-dessous) avec dplyr:

df %>% by(df$houseID, na.locf) %>% rbind_all

par

library(Zoo)

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

ave

library(Zoo)

na.locf2 <- function(x) na.locf(x, na.rm = FALSE)
transform(df, price = ave(price, houseID, FUN = na.locf2))

data.table

library(data.table)
library(Zoo)

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

Zoo Cette solution utilise uniquement Zoo. Il retourne un résultat large plutôt que long:

library(Zoo)

z <- read.Zoo(df, index = 2, split = 1, FUN = identity)
na.locf(z, na.rm = FALSE)

donnant:

       1  2  3
1995  NA NA NA
1996 100 NA 44
1997 100 NA 44
1998 120 30 44
1999 120 30 44

Cette solution pourrait être combinée avec dplyr comme ceci:

library(dplyr)
library(Zoo)

df %>% read.Zoo(index = 2, split = 1, FUN = identity) %>% na.locf(na.rm = FALSE)

contribution

Voici l'entrée utilisée pour les exemples ci-dessus:

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

R&EACUTE;VIS&EACUTE;Réorganisé et ajouté plus de solutions. Solution révisée de dplyr/Zoo pour se conformer aux dernières modifications apportées à dplyr.

40
G. Grothendieck

tidyr::fill rend maintenant cela bêtement facile:

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

Vous pouvez faire une auto-jointure par roulement, supportée par 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

Solution pure dplyr (pas de zoo).

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

La partie intéressante de l'exemple de solution se trouve à la fin de 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

sans dplyr:

prices$price <-unlist(lapply(split(prices$price,prices$houseID),function(x) 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
1
hvollmeier

Voici une combinaison dplyr et imputeTS .

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

Vous pouvez également remplacer na.locf par des fonctions plus avancées de remplacement de données (imputation) de imputeTS . Par exemple na.interpolation ou na.kalman . Pour cela, remplacez simplement na.locf par le nom de la fonction que vous aimez.

0
stats0007