values - Cómo llenar NA con LOCF por factores en el marco de datos, dividido por país
remove na in r data frame (8)
Tengo el siguiente marco de datos (simplificado) con la variable de país como factor y la variable de valor tiene valores perdidos:
country value
AUT NA
AUT 5
AUT NA
AUT NA
GER NA
GER NA
GER 7
GER NA
GER NA
Lo siguiente genera el marco de datos anterior:
data <- data.frame(country=c("AUT", "AUT", "AUT", "AUT", "GER", "GER", "GER", "GER", "GER"), value=c(NA, 5, NA, NA, NA, NA, 7, NA, NA))
Ahora, me gustaría reemplazar los valores de NA en cada subconjunto de países utilizando el método de última observación llevada a cabo (LOCF). Conozco el comando na.locf
en el paquete del zoo . data <- na.locf(data)
me daría el siguiente marco de datos:
country value
AUT NA
AUT 5
AUT 5
AUT 5
GER 5
GER 5
GER 7
GER 7
GER 7
Sin embargo, la función solo debe utilizarse en los subconjuntos individuales divididos por el país . La siguiente es la salida que necesitaría:
country value
AUT NA
AUT 5
AUT 5
AUT 5
GER NA
GER NA
GER 7
GER 7
GER 7
No puedo pensar en una manera fácil de implementarlo. Antes de comenzar con for-loops, me preguntaba si alguien tiene alguna idea de cómo resolver esto.
¡¡Muchas gracias!!
Aquí hay una solución ddply
. Prueba esto
library(plyr)
ddply(DF, .(country), na.locf)
country value
1 AUT <NA>
2 AUT 5
3 AUT 5
4 AUT 5
5 GER <NA>
6 GER <NA>
7 GER 7
8 GER 7
9 GER 7
Editar De ayuda ddply
puede encontrar que
.variables: variables to split data frame by,
as quoted variables, a formula or character vector.
Así que otras alternativas para conseguir lo que quieres son:
ddply(DF, "country", na.locf)
ddply(DF, ~country, na.locf)
tenga en cuenta que no se permite reemplazar .variables
con la DF$variable
, por eso se produjo un error al hacer esto.
DF
es tu data.frame
Divida el data.frame
con by
y use na.locf
en los subconjuntos:
do.call(rbind,by(data,data$country,na.locf))
Si desea eliminar los nombres de las filas:
do.call(rbind,unname(by(data,data$country,na.locf)))
La forma tidyverse, aunque no usa locf, es:
library(tidyverse)
data %>%
group_by(country) %>%
fill(value)
Source: local data frame [9 x 2]
Groups: country [2]
country value
(fctr) (dbl)
1 AUT NA
2 AUT 5
3 AUT 5
4 AUT 5
5 GER NA
6 GER NA
7 GER 7
8 GER 7
9 GER 7
Si la velocidad es una consideración, entonces esta unstack
para unstack
/ stack
es aproximadamente 4 a 6 veces más rápida que las otras en mi sistema, aunque conlleva una línea de código ligeramente más larga:
stack(lapply(unstack(data, value ~ country), na.locf, na.rm = FALSE))
Otro enfoque es:
transform(data, value = ave(value, country, FUN = na.locf0))
Simplemente necesita dividir por país, luego hacer un zoo::na.locf() o na.fill, llenando a la derecha. Aquí hay un ejemplo que muestra explícitamente la sintaxis arg de tres componentes de na.fill:
library(plyr)
library(zoo)
data <- data.frame(country=c("AUT", "AUT", "AUT", "AUT", "GER", "GER", "GER", "GER", "GER"), value=c(NA, 5, NA, NA, NA, NA, 7, NA, NA))
# The following is equivalent to na.locf
na.fill.right <- function(...) { na.fill(..., list(left=NA,interior=NA,right="extend")) }
ddply(data, .(country), na.fill.right)
country value
1 AUT <NA>
2 AUT 5
3 AUT 5
4 AUT 5
5 GER <NA>
6 GER <NA>
7 GER 7
8 GER 7
9 GER 7
Una combinación de los paquetes dplyr e imputeTS puede hacer el trabajo.
library(dplyr)
library(imputeTS)
data %>% group_by(country) %>%
mutate(value = na.locf(value, na.remaining="keep"))
Con el parámetro na.remaining de la función na.locf de imputeTS , tiene además la opción de elegir qué hacer con los NA finales.
Estas son las opciones:
- "mantener" - devuelve la serie con NA
- "rm" - elimina las NA restantes
- "media": reemplaza las NA restantes por la media general
- "rev" - ejecuta nocb / locf desde la dirección inversa
Al elegir "media", por ejemplo, obtendría un resultado con 7 por cada GER en el ejemplo específico.
Una versión moderna de la solución ddply
es usar el paquete dplyr
:
library(dplyr)
DF %>%
group_by(county) %>%
mutate(value = na.locf(value, na.rm = F))
data.table
un poco tarde a esta conversación, pero aquí hay una data.table
, que será mucho más rápida para conjuntos de datos más grandes:
library(zoo)
library(data.table)
# Convert to data table
setDT(data)
data[, value := na.locf(value, na.rm = FALSE), by = country]
data
country value
1: AUT NA
2: AUT 5
3: AUT 5
4: AUT 5
5: GER NA
6: GER NA
7: GER 7
8: GER 7
9: GER 7
# And if you want to convert "data" back to a data frame...
setDF(data)