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

person user1491868    schedule 04.09.2014    source источник
comment
пример данных — это не несбалансированная панель, это сбалансированная панель с пропущенными значениями.   -  person Antonio    schedule 05.08.2020


Ответы (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
person nograpes    schedule 04.09.2014
comment
Если кого-то интересует обратный путь (сделать несбалансированную панель сбалансированной, заполнив NA), можно использовать функцию make.pbalanced из пакета plm (требуется последняя версия для разработчиков с r-forge.r-project.org/R/?group_id=406) - person Helix123; 28.06.2016
comment
Официальный выпуск CRAN (1.6-4) plm теперь включает make.pbalanced (и с помощью аргумента balance.type = c("fill", "shared") можно выбрать, расширять данные или сокращать их. - person Helix123; 30.11.2016

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

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

Первый параметр — это data.frame, который вы хотите подмножить. Второй параметр (ID=) представляет собой вектор символов имен столбцов, которые идентифицируют каждого «человека» в наборе данных. Тогда параметр TIME= также является вектором символов, определяющим различные времена наблюдения для каждого идентификатора. Наконец, вы можете дополнительно указать аргумент VARS=, чтобы указать, какие поля должны быть NA (по умолчанию для всех значений, кроме значений ID или TIME). Наконец, есть последний параметр с именем 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 для всех ЧЕЛОВЕК/ГОД, и, поскольку это верно для всех записей, дополнительная настройка не выполняется.

Если вы сделаете

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)

теперь не возвращает ничего, кроме

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 годы, т.к. у всех лиц есть данные за эти годы.

person MrFlick    schedule 04.09.2014
comment
Очень хорошо. Я признаю, что не понимал, что имелось в виду под сбалансированным, пока не прочитал об этом. Это гораздо более приятное общее решение. - person nograpes; 05.09.2014

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

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

library(data.table)
library(reshape2)

Во-первых, возьмите подмножество вашего основного фрейма данных (несбалансированного), состоящего только из переменной ID («ИМЯ»), переменной времени («ГОД») и интересующей переменной («X» или «Y»).

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

Во-вторых, измените форму нового фрейма данных, чтобы сделать его широкоформатным. Это создает фрейм данных, в котором каждое «ИМЯ» представляет собой одну строку, а «X» для каждого года — столбец.

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

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

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)
person R. Buchanan    schedule 09.05.2017

Это решение, которое я использую — оно использует удобные функции (включая хорошие возможности слияния) пакета data.table и предполагает, что ваши данные уже являются объектом data.table. Это относительно просто и, надеюсь, легко следовать. Он возвращает сбалансированную панель с записями для каждой уникальной комбинации «индивидуумов» и «периодов времени», то есть панель, где есть наблюдение для каждого индивидуума в каждый период времени.

library(data.table)
Balance_Panel = function(Data, Indiv_ColName, Time_ColName){
    Individuals = unique(Data[, get(Indiv_ColName)])
    Times = unique(Data[, get(Time_ColName)])

    Full_Panel = data.table(expand.grid(Individuals, Times))
    setnames(Full_Panel, c(Indiv_ColName, Time_ColName))
    setkeyv(Full_Panel, c(Indiv_ColName, Time_ColName))
    setkeyv(Data, c(Indiv_ColName, Time_ColName))
    return(Data[Full_Panel])
}

Пример использования:

Balanced_Data = Balance_Panel(Data, "SubjectID", "ObservationTime")
person Michael Ohlrogge    schedule 20.08.2016