R中for循环的替代方法,用于通过匹配两个变量(country,year)来添加数据

时间:2017-06-19 14:24:55

标签: r dataframe dplyr

我有一个数据集,按年填充了一堆国家的数据。我需要为某些地区(如比利时 - 卢森堡)创建数据,方法是为其他一些国家(在本例中为比利时和卢森堡)添加或以其他方式操纵数据,并将结果值填入该地区的相应年份。

例如,比方说我有比利时(BEL)和卢森堡(LUX)2001 - 2010年的数据。我需要能够说,添加,BEL-2001和LUX-2001来创建BLX-2001。数据集的列为iso(国家代码 - BEL,LUX,BLX等),年份和VARIABLE。它已经具有BEL,LUX和BLX所需的所有行(当然,在我们填充它之前BLX是空的)。

示例DATAFRAME将是:

    iso year    colname
    BEL 1990    NA
    BEL 1991    10
    BEL 1992    20
    BEL 1993    30
    BEL 1994    10
a few rows of other countries we don't care for in this case
    LUX 1990    5
    LUX 1991    3
    LUX 1992    NA
    LUX 1993    7
    LUX 1994    3
a few rows of other countries we don't care for in this case
    BLX 1990    NA
    BLX 1991    NA
    BLX 1992    NA
    BLX 1993    NA
    BLX 1994    NA

在上述情况下,我们将仅填充1991,1992和1994年的BLX数据(添加BEL和LUX值) - 因为这些是BEL和LUX都具有所需数据的唯一年份。这会给我们:

    iso year    colname
    BEL 1990    NA
    BEL 1991    10
    BEL 1992    20
    BEL 1993    30
    BEL 1994    10
a few rows of other countries we don't care for in this case
    LUX 1990    5
    LUX 1991    3
    LUX 1992    NA
    LUX 1993    7
    LUX 1994    3
a few rows of other countries we don't care for in this case
    BLX 1990    NA
    BLX 1991    13
    BLX 1992    NA
    BLX 1993    37
    BLX 1994    13


目前,我正在使用dplyr实现此功能,该功能采用列名称并简单地添加每个可用年份的值。这是最简单的例子,更复杂的操作看起来更加混乱:

BLXCalc <- function(colname){

  LUXData <- filter(DATAFRAME, iso == "LUX" & !is.na(get(colname, envir=as.environment(DATAFRAME)))) # get only those LUX and BEL rows that have the reqd data

  BELData <- filter(DATAFRAME, iso == "BEL" & !is.na(get(colname, envir=as.environment(DATAFRAME))))

  BLXrange <- grep("BLX", DATAFRAME$iso) # get all BLX rows

  ifelse(length(LUXData$year)<length(BELData$year), BLXyears <- LUXData$year, BLXyears <- BELData$year) # use the shorter list for the for loop

  for(i in 1:length(BLXyears)){

    BLXcurrentyear <- filter(DATAFRAME, iso == "LUX" & year == BLXyears[i])[[colname]] + filter(DATAFRAME, iso == "BEL" & year == BLXyears[i])[[colname]]

    BLXrow <- match("BLX", DATAFRAME$iso) + match(BLXyears[i], DATAFRAME$year[BLXrange[1]:BLXrange[length(BLXrange)]]) - 1 # find the corresponding year in BLX

    DATAFRAME[[colname]][BLXrow] <<- BLXcurrentyear
  }
}

即使是这样一个简单的操作(添加),这也是一个混乱的代码,并不是很容易阅读。我正在做的基本细分是:

  1. 从具有所需变量/列数据的所需国家/地区获取所有年份
  2. 查找可用年份数最短的国家/地区(因为我们需要每个必需的国家/地区都有特定年份的数据才能计算同一年的区域数据) 现在我们循环了这个国家的数据:
  3. 对于此国家/地区有可用数据的每一年,请获取该年度所需国家/地区的给定列的值。
  4. 如果所有其他国家/地区都有该年度的数据,请将其加总(或其他操作 - 如平均值/加权平均值等)
  5. 在今年的同一栏
  6. 中将此金额填入本地区的行

    步骤3,4,5循环可用年份直到我们完成。

    这适用于我们正在使用的数据,但我知道循环不是使用R的最佳方式。还有其他更多的“R”方法来实现同样的功能吗?对于较大的数据集,可能会更快,并且总体上更容易阅读。

2 个答案:

答案 0 :(得分:2)

这是一个可能的解决方案。您首先在国家/地区拆分并创建一个列表,其中每个国家/地区都是不同的元素。使用Reduce,您可以按名称合并任意数量的元素(function(...))。最后,使用rowSums而不删除NA以添加所需的变量。如果您引用函数(fill_countries),则可以将结果分配给感兴趣的数据子集(再次按名称指定子集)。

l1 <- split(df, df$iso)
d1 <- Reduce(function(...)merge(..., by = 'year'), l1[names(l1) %in% c('BEL', 'LUX')])
rowSums(d1[grepl('colname', names(d1))])
#[1] NA 13 NA 37 13

你也可以把它变成一个功能,

fill_countries <- function(df, country_to_fill, countries_to_use){
  l1 <- split(df, df$iso)
  d1 <- Reduce(function(...)merge(..., by = 'year'), l1[names(l1) %in% countries_to_use])
  df$colname[df$iso == country_to_fill] <- rowSums(d1[grepl('colname', names(d1))])
  return(df)
}

fill_countries(df, 'BLX', c('BEL', 'LUX'))
#   iso year colname
#1  BEL 1990      NA
#2  BEL 1991      10
#3  BEL 1992      20
#4  BEL 1993      30
#5  BEL 1994      10
#6  LUX 1990       5
#7  LUX 1991       3
#8  LUX 1992      NA
#9  LUX 1993       7
#10 LUX 1994       3
#11 BLX 1990      NA
#12 BLX 1991      13
#13 BLX 1992      NA
#14 BLX 1993      37
#15 BLX 1994      13

答案 1 :(得分:0)

使用data.table,这可以通过“单行”来解决:

library(data.table) # CRAN version 1.10.4 used
# select countries, aggregate by year, 
# finally, append resulting rows to original data.frame 
rbind(DF, setDT(DF)[iso %in% c("BEL", "LUX"), 
                    .(iso = "BLX", colname = sum(colname)), by = year])

返回:

    iso year colname
 1: BEL 1990      NA
 2: BEL 1991      10
 3: BEL 1992      20
 4: BEL 1993      30
 5: BEL 1994      10
 6: LUX 1990       5
 7: LUX 1991       3
 8: LUX 1992      NA
 9: LUX 1993       7
10: LUX 1994       3
11: BLX 1990      NA
12: BLX 1991      13
13: BLX 1992      NA
14: BLX 1993      37
15: BLX 1994      13
OP表示,他需要将几个地区联合起来,而不仅仅是比利时和卢森堡。上述代码可以嵌入到lapply()的调用中,以便同时组合多个区域:

# define countries and names of regions
map <- list(
  BLX = c("BEL", "LUX"),
  BNL = c("BEL", "NLD", "LUX"), # BeNeLux countries
  IBE = c("AND", "ESP", "GIB", "PRT") # Iberian peninsula
)
# aggregate regions and add to original data set
setDT(DF)
rbindlist(c(
  list(DF),
  lapply(seq_along(map), function(i) 
    DF[iso %in% map[[i]], .(iso = names(map)[i], colname = sum(colname)), by = year]
  )), use.names = TRUE)

请注意,索引号i用于访问map中的名称。 lapply()会返回data.table个对象的列表,因此rbindlist()用于将所有对象追加,但我们需要明确设置use.names = TRUE

    iso year colname
 1: BEL 1990      NA
 2: BEL 1991      10
 3: BEL 1992      20
 4: BEL 1993      30
 5: BEL 1994      10
 6: LUX 1990       5
 7: LUX 1991       3
 8: LUX 1992      NA
 9: LUX 1993       7
10: LUX 1994       3
11: BLX 1990      NA
12: BLX 1991      13
13: BLX 1992      NA
14: BLX 1993      37
15: BLX 1994      13
16: BNL 1990      NA
17: BNL 1991      13
18: BNL 1992      NA
19: BNL 1993      37
20: BNL 1994      13