R Функция, которая заменяет NA на запаздывающие значения

Я пытаюсь написать функцию, которая заменяет отсутствующие значения выбранных переменных во фрейме данных их запаздывающими значениями (я использую задержку на одно наблюдение) в R. Я успешно написал для этого следующий цикл for:

testdata <- data.frame(x1 = c(1:10), 
                       x2 = c(4, 3, NA, 7, 8, NA, 9, NA, 10, 11), 
                       x3 = c(4, 3, NA, 7, 8, NA, 9, NA, NA, 11),
                       x4 = c("a", NA, NA, "d", "e", NA, "f", NA, "g", NA))

for (j in 2:4){
  for (i in 1:10){
    if(is.na(testdata[i, j])){
      testdata[i, j] <- testdata[i - 1, j]
    }}}

Цикл for работает нормально, однако я обобщу этот код и напишу его в функции, которая создаст пустой список. Я написал следующую функцию:

fill_null <- function(df, columns, rows){
  for (j in columns){
    for(i in rows){
      if(is.na(df[i, j])){
        df[i,j] <- df[i - 1, j]
      } else{
        df[i, j] <- df[i, j]
      }}}}

Когда я запускаю эту функцию, используя следующий код:

newdf <- fill_null(testdata, 2:4, 1:10)
str(newdf)

Получаю следующий результат:

> str(newdf)
 NULL

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


person benalbert342    schedule 16.10.2019    source источник
comment
Поместите return(df) в конце вашей функции.   -  person Gregor Thomas    schedule 16.10.2019
comment
Вам также могут быть интересны функции zoo::na.locf (для одного столбца) или tidyr::fill (для всего фрейма данных), которые делают это с большим количеством функций и более эффективно. См., Например, этот FAQ по этой теме. Ваша функция делает то же самое, что и tidyr::fill(testdata)   -  person Gregor Thomas    schedule 16.10.2019
comment
Стабильность - большое преимущество использования хорошо протестированных функций. Например, я думаю, что ваша функция выдаст ошибку выхода за пределы, если в первой строке данных есть NA значения.   -  person Gregor Thomas    schedule 16.10.2019
comment
Я бы также добавил, что у R есть несколько хороших способов применить функцию к определенным столбцам фрейма данных. Вместо этого жесткого кода, который используется в вашей функции, я бы предложил написать более простую функцию, которая работает с одним вектором. Эта функция более гибкая, чем та, что у вас есть, и вы можете применить ее к столбцам, используя обычные методы R, такие как for или lapply, например, testdata[columns] = lapply(testdata[columns], simple_fill_null). Или, если вам действительно нужен интерфейс столбца, напишите оболочку, которая делает это lapply. Небольшие размеры и модульность функций упрощают их отладку и делают их более гибкими в использовании.   -  person Gregor Thomas    schedule 14.11.2019


Ответы (1)


В R функции будут (если не указано иное) возвращать последнее сгенерированное значение. В вашей функции вы можете подумать, что последнее значение df, но на самом деле это цикл for. Согласно документации в ?"for", for циклы возвращают NULL в качестве своего значения. Простой способ продемонстрировать это - test <- for(x in 1:3){x}; test, который возвращает NULL.

Чтобы исправить это, вы можете завершить свою функцию с помощью return(df) или просто df.

Однако, чтобы решить суть вашей проблемы, в пакете dplyr есть функция lag, которая может оказаться вам полезной (testdata$j <- ifelse(is.na(testdata$j), lag(testdata$j), testdata$j)))

person Daniel V    schedule 13.11.2019