R 中 for 循环的替代方法,用于通过匹配两个变量(国家/地区、年份)来添加数据



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

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

示例数据帧将是:

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. 将此总和填写到同一列中今年的区域行中

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

这对于我们正在处理的数据来说效果很好,但我知道 for 循环不是使用 R 的最佳方式。还有其他"R"方法可以实现相同的功能吗?使用更大的数据集可能会更快,最好整体上更容易阅读。

这是一个可能的解决方案。您首先在国家/地区上拆分,然后创建一个列表,每个国家/地区作为不同的元素。使用Reduce,你可以合并任意数量的元素(function(...)),这些元素是你通过名称指定的。最后,在不删除 NA 的情况下使用rowSums来添加所需的变量。如果引用函数 (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

有了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

最新更新