R элегантный способ сбалансировать несбалансированные данные панели

есть ли элегантный способ сбалансировать несбалансированный набор данных панели? Я хотел бы начать с несбалансированной панели (т. е. некоторым людям не хватает некоторых данных) и закончить сбалансированной панелью (т. е. всем людям не хватает данных). Ниже приведен пример кода. Правильный конечный результат состоит в том, чтобы все наблюдения над "Фрэнком" и "Эдвардом" остались, а все наблюдения над "Тони" были удалены, поскольку у него есть некоторые недостающие данные. Спасибо.

unbal <- data.frame(PERSON=c(rep('Frank',5),rep('Tony',5),rep('Edward',5)), YEAR=c(2001,2002,2003,2004,2005,2001,2002,2003,2004,2005,2001,2002,2003,2004,2005), Y=c(21,22,23,24,25,5,6,NA,7,8,31,32,33,34,35), X=c(1:15))
unbal

4 ответов


один из способов сбалансировать панель-удалить людей с неполными данными, другой способ-заполнить значение, такое как NA или 0 для отсутствующих наблюдений. Для первого подхода, вы можете использовать complete.cases чтобы найти строки, которые не имеют NA в них. Тут вы можете найти все PERSON С, по крайней мере, одно дело.

missing.at.least.one <- unique(unbal$PERSON[!complete.cases(unbal)])
unbal[!(unbal$PERSON %in% missing.at.least.one),]
#    PERSON YEAR  Y  X
# 1   Frank 2001 21  1
# 2   Frank 2002 22  2
# 3   Frank 2003 23  3
# 4   Frank 2004 24  4
# 5   Frank 2005 25  5
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 13 Edward 2003 33 13
# 14 Edward 2004 34 14
# 15 Edward 2005 35 15

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

balanced<-function(data, ID, TIME, VARS, required=c("all","shared")) {
    if(is.character(ID)) {
        ID <- match(ID, names(data))
    }
    if(is.character(TIME)) {
        TIME <- match(TIME, names(data))
    }
    if(missing(VARS)) { 
        VARS <- setdiff(1:ncol(data), c(ID,TIME))
    } else if (is.character(VARS)) {
        VARS <- match(VARS, names(data))
    }
    required <- match.arg(required)
    idf <- do.call(interaction, c(data[, ID, drop=FALSE], drop=TRUE))
    timef <- do.call(interaction, c(data[, TIME, drop=FALSE], drop=TRUE))
    complete <- complete.cases(data[, VARS])
    tbl <- table(idf[complete], timef[complete])
    if (required=="all") {
        keep <- which(rowSums(tbl==1)==ncol(tbl))
        idx <- as.numeric(idf) %in% keep
    } else if (required=="shared") {
        keep <- which(colSums(tbl==1)==nrow(tbl))
        idx <- as.numeric(timef) %in% keep
    }
    data[idx, ]
}

вы можете получить желаемый результат с

balanced(unbal, "PERSON","YEAR")

#    PERSON YEAR  Y  X
# 1   Frank 2001 21  1
# 2   Frank 2002 22  2
# 3   Frank 2003 23  3
# 4   Frank 2004 24  4
# 5   Frank 2005 25  5
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 13 Edward 2003 33 13
# 14 Edward 2004 34 14
# 15 Edward 2005 35 15

первый параметр-это данные.кадр, который вы хотите подмножество. Второй параметр (ID=) - это символьный вектор имен столбцов, которые идентифицируют каждого "человека" в наборе данных. Тогда TIME= параметр также является символьным вектором, указывающим другое наблюдение раз для каждого ID. Наконец, вы можете дополнительно указать VARS= аргумент, чтобы указать, какие поля должны быть NA (по умолчанию все, кроме значений ID или времени). Наконец, последний параметр с именем required в котором указано, должен ли каждый идентификатор иметь наблюдение для каждого времени (по умолчанию) или если вы установите его в "общий", он будет возвращать только время, когда все идентификаторы имеют не отсутствующие значения.

например

balanced(unbal, "PERSON","YEAR", "X")

#    PERSON YEAR  Y  X
# 1   Frank 2001 21  1
# 2   Frank 2002 22  2
# 3   Frank 2003 23  3
# 4   Frank 2004 24  4
# 5   Frank 2005 25  5
# 6    Tony 2001  5  6
# 7    Tony 2002  6  7
# 8    Tony 2003 NA  8
# 9    Tony 2004  7  9
# 10   Tony 2005  8 10
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 13 Edward 2003 33 13
# 14 Edward 2004 34 14
# 15 Edward 2005 35 15

требуется только, чтобы "X" был NA для всех Человек / лет, и поскольку это верно для всех записей, настройка sub не выполняется.

если у вас

balanced(unbal, "PERSON","YEAR", required="shared")

#    PERSON YEAR  Y  X
# 1   Frank 2001 21  1
# 2   Frank 2002 22  2
# 4   Frank 2004 24  4
# 5   Frank 2005 25  5
# 6    Tony 2001  5  6
# 7    Tony 2002  6  7
# 9    Tony 2004  7  9
# 10   Tony 2005  8 10
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 14 Edward 2004 34 14
# 15 Edward 2005 35 15

затем вы получаете данные за 2001, 2002, 2004, 2005 годы для всех людей, так как все они имеют данные за эти годы.

теперь давайте использовать создать немного другой набор данных образца

unbal2 <- unbal 
unbal2[15, 2] <- 2006
tail(unbal2)

#    PERSON YEAR  Y  X
# 10   Tony 2005  8 10
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 13 Edward 2003 33 13
# 14 Edward 2004 34 14
# 15 Edward 2006 35 15

обратите внимание, что Эдвард-единственный человек, который имеет значение для 2006. Это значит, что

balanced(unbal2, "PERSON","YEAR")
# [1] PERSON YEAR   Y      X     
# <0 rows> (or 0-length row.names)

теперь ничего не возвращает но!--12-->

balanced(unbal2, "PERSON","YEAR", required="shared")

#    PERSON YEAR  Y  X
# 1   Frank 2001 21  1
# 2   Frank 2002 22  2
# 4   Frank 2004 24  4
# 6    Tony 2001  5  6
# 7    Tony 2002  6  7
# 9    Tony 2004  7  9
# 11 Edward 2001 31 11
# 12 Edward 2002 32 12
# 14 Edward 2004 34 14

вернет данные за 2001,2002, 2004, так как все люди имеют данные за эти годы.


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

Я использую следующие библиотеки:

library(data.table)
library(reshape2)

во-первых, возьмите подмножество вашего основного фрейма данных (unbal), то есть просто переменную ID ("имя"), переменную времени ("год") и переменная интереса ("X "или"Y").

df<- unbal[c("NAME", "YEAR", "X" )]

во-вторых, измените новый фрейм данных, чтобы сделать его широкоформатным. Это создает фрейм данных, в котором каждое "имя" является одной строкой, а "X" для каждого года-столбцом.

df <- dcast(df, NAME ~ YEAR, value.var = "X")

в-третьих, завершить.кейсы для каждой строки. Любое имя с отсутствующими данными будет полностью удалено.

df <- df[complete.cases(df),]

в-четвертых, измените фрейм данных обратно в длинный формат (по умолчанию это дает вашим переменным общие имена, поэтому вы можете измените имена на прежние).

df <- melt(df, id.vars = "ID")
setnames(df, "variable", "YEAR")

Примечание: год становится факторной переменной по умолчанию с использованием подхода. Если переменная YEAR является числовой, вы захотите изменить ее соответствующим образом. Например:

test4$year <- as.character(test4$year)
test4$year <- as.numeric(test4$year)

пятый и шестой, возьмите только переменные" имя "и" год " в созданном вами фрейме данных, а затем объедините его с исходным фреймом данных (и не забудьте удалить случаи в исходном фрейме данных, которые не найдены в фрейме данных d вы создали)

df <- df[c("NAME", "YEAR")]
balanced <- merge.data.frame(df, unbal, by = c("NAME", "YEAR"), all.x = TRUE)

это решение, которое я использую - оно использует удобные функции (включая хорошие способности слияния)data.table пакета и предполагает, что ваши данные уже