按顺序迭代 R 中的两个列表

Iterate sequentially over two lists in R

我有两个看起来像这样的 df

library(tidyverse)
iris <- iris%>% mutate_at((1:4),~.+2)
iris2 <- iris 
names(iris2)<-sub(".", "_", names(iris2), fixed = TRUE)

我的目标是减少 iris 中高于 iris2 中相应变量最大值的变量值,以匹配 iris2 中的最大值。

我已经编写了一个函数来执行此操作。

max(iris$Sepal.Length) 
[1] 9.9
max(iris2$Sepal_Length)
[1] 7.9
# i want every value of iris that is >= to max value of iris2 to be equal to the max value of iris 2.

# my function:
fixmax<- function(data,data2,var1,var2) {
  data<- data %>% 
    mutate("{var1}" := ifelse(get(var1)>=max(data2[[var2]],na.rm = T),
                              max(data2[[var2]],na.rm = T),get(var1)))
  return(data)
}

# apply my function to a variable
tst_iris <- fixmax(iris,iris2,"Sepal.Length","Sepal_Length")
max(tst_iris$Sepal.Length)
7.9 # it works!

我面临的挑战是我想按顺序迭代我的函数超过两个变量列表 - 即Sepal.Length Sepal_LengthSepal.WidthSepal_Width

有人知道我该怎么做吗?

我尝试使用 Map 但我做错了。

lst1 <- names(iris[,1:4])
lst2 <- names(iris2[,1:4])
final_iris<- Map(fixmax,iris, iris2,lst1,lst2)

我的目标是获得一个 df (final_iris),其中每个变量都已使用 fixmax 指定的标准进行了调整。 我知道我可以通过 运行 在每个变量上设置我的函数来做到这一点。

final_iris <- iris
final_iris <- fixmax(final_iris,iris2,"Sepal.Length","Sepal_Length")
final_iris <- fixmax(final_iris,iris2,"Sepal.Width","Sepal_Width")
final_iris <- fixmax(final_iris,iris2,"Petal.Length","Petal_Length")
final_iris <- fixmax(final_iris,iris2,"Petal.Width","Petal_Width")

但在实际数据中,我必须 运行 这个操作数十次,我希望能够按顺序循环我的函数。 有谁知道我如何按顺序在 lst1lst2 上循环我的 fixmax

这是您所期待的吗?

my_a <- iris %>% mutate_at((1:4),~.+2)
iris2 <- iris
names(iris2)<-sub(".", "_", names(iris2), fixed = TRUE)

my_var <- which(my_a$Sepal.Length >= max(iris2$Sepal_Length) & my_a$Sepal.Width >= max(iris2$Sepal_Width))
if (length(my_var)) {
  my_a <- my_a[my_var,]
}

您的问题很可能与数据框本身就是列表这一事实有关。 Map() 期望非函数参数是相同长度的列表。任何比最长列表短的参数都会被“回收”以匹配它的长度。

目前,您有:

final_iris<- Map(fixmax,iris, iris2,lst1,lst2)

这实际上等同于:

final_iris<- Map(fixmax,
                 list(iris$Sepal.Length,
                      iris$Sepal.Width,
                      iris$Petal.Length,
                      iris$Petal.Width,
                      iris$Species),
                 list(iris2$Sepal_Length,
                      iris2$Sepal_Width,
                      iris2$Petal_Length,
                      iris2$Petal_Width,
                      iris2$Species),
                 lst1,
                 lst2)

我怀疑您希望将 irisiris2 提供给对 fixmax() 的每次调用。为了让 Map() 像这样回收它们,它们需要是单元素列表。那就是你可能想要的:

final_iris<- Map(fixmax, list(iris), list(iris2),lst1,lst2)

要将数据帧列表合并为一个数据帧,请执行

do.call(rbind, final_iris)

你的函数乍一看似乎很复杂,难以阅读。我们可以使用快速函数

为列中的每个值整理函数 return max(x, max_val)
#function to correct max  
adjust_max <- function(x, max_val) {  
  return(ifelse(x >= max_val, max_val, x))  
}  

最后,我们希望使用这两个数据帧自动并按顺序应用它。我们将使用一个简单的 for 循环。附上设置问题的代码。

#libraries
library(tidyverse)


#set up fake data
iris_big <- iris%>% mutate_at((1:4),~.+2)
iris_small <- iris 
names(iris_small)<- sub(".", "_", names(iris_small), fixed = TRUE)

#check which is the bigger one and the smaller
max(iris_big$Sepal.Length)  #bigger
max(iris_small$Sepal_Length)  #smaller


#function to correct max
adjust_max <- function(x, max_val) {
  return(ifelse(x >= max_val, max_val, x))
}


#apply it to get a final result
iris_final <- iris_big

# iterate over columns, assuming same positions
# you can edit the 1:ncol(iris_final) to only take the columns you want
for (i in 1:ncol(iris_final)) {
  #check numeric
  if (is.numeric(iris_final[,i])) {
    #applies the function - notice we call iris_final and iris_small
    iris_final[,i] <- sapply(iris_final[,i], 
                             adjust_max,
                             max_val = max(iris_small[,i]))
  }
}

#check answer is correct
apply(iris_final[,1:4], 2, max)
apply(iris_small[,1:4], 2, max)

tail(iris_final)

您应该考虑使用列索引;一个完整的(不包括数据框架结构)基础 R 解决方案可能如下所示:

# Resolve the indices of the numeric vectors in 
# iris: num_cols => integer vector
num_cols <- which(
  vapply(
      iris, 
      is.numeric, 
      logical(1)
    ),
  arr.ind = TRUE
)

# Map the pmin function over iris to select the
# minimum of the vector element in iris and the 
# maximum values of that vector in iris2: 
# iris => data.frame
iris[,num_cols] <- Map(function(i){
  pmin(
    iris[,i], 
    max(
      iris2[,i],
      na.rm = TRUE
      )
    )
  }, 
  num_cols
)

对于 tidyverse 方法,您可以使用 transmute 而不是 mutatetransmute 每次迭代中只会 return 一列,而 mutate 每次都会 return 所有列。

除此之外,为了让它更 tidyverse 友好,我使用 .data 而不是 get。还使用 pmin 而不是复杂的 ifelse 解决方案。

library(dplyr)
library(purrr)

fixmax<- function(data,data2,var1,var2) {
  data<- data %>%  transmute("{var1}" := pmin(.data[[var1]], max(data2[[var2]])))
  return(data)
}

要将该函数应用到每一对列,您可以使用 map2_dfc,它还会将结果合并到一个数据框中。

lst1 <- names(iris[,1:4])
lst2 <- names(iris2[,1:4])

在应用函数之前比较两个数据帧的最大值。

map_dbl(iris[lst1], max)
#Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#         9.9          6.4          8.9          4.5 

map_dbl(iris2[lst2], max)

#Sepal_Length  Sepal_Width Petal_Length  Petal_Width 
#         7.9          4.4          6.9          2.5 

应用函数-

iris[lst1] <- map2_dfc(lst1, lst2, ~fixmax(iris, iris2, .x, .y))

应用函数后比较两个数据帧的最大值。

map_dbl(iris[lst1], max)

#Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#         7.9          4.4          6.9          2.5 

map_dbl(iris2[lst2], max)
#Sepal_Length  Sepal_Width Petal_Length  Petal_Width 
#         7.9          4.4          6.9          2.5 

您可以利用 R 中内置的矢量化,而不是按名称显式迭代不同的数据集和列。如果数据帧具有相同的 column/variable,则使用 mapplypurrr::map2 对映射到两个数据帧的函数进行排序将逐列迭代,而无需指定列名。

给定两个输入数据帧(df_smalldf_big),步骤是:

  1. 计算df_small中每列的最大值以创建df_small_max
  2. pmin 函数应用到 df_big 的每一列和 df_small_max 的每个值,使用 mapply(或者 purr::map2_dfc 如果你喜欢 tidyverse映射)
#set up fake data
df_small <- iris[,1:4]
df_big <- df_small + 2

# find max of each col in df_small
df_small_max <- sapply(df_small, max)

# replace values of df_big which are larger than df_small_max
df_big_fixed <- mapply(pmin, df_big, df_small_max)




# sanity check -- Note the change in Sepal.Width
df_small_max
#> Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#>          7.9          4.4          6.9          2.5
head(df_big, 3)
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1          7.1         5.5          3.4         2.2
#> 2          6.9         5.0          3.4         2.2
#> 3          6.7         5.2          3.3         2.2
head(df_big_fixed, 3)
#>      Sepal.Length Sepal.Width Petal.Length Petal.Width
#> [1,]          7.1         4.4          3.4         2.2
#> [2,]          6.9         4.4          3.4         2.2
#> [3,]          6.7         4.4          3.3         2.2

reprex package (v2.0.0)

于 2021-07-31 创建

这是一个基本的方法。我还重命名了变量,因为我在复制时遇到了一些麻烦,因为最初该方法会保存 iris 对象。

我们的方法是不改变 data.frame 对象,而是只 return 我们修改后的函数的预期值向量。然后,我们将这些值重新分配回我们原来的 data.frame.

fixmax2 = function(x, y) {
  max_y = max(y, na.rm = TRUE)
  ifelse(x >= max_y, max_y, y)
}
cols = which(sapply(df_plus, is.numeric))
df_plus[cols] = Map(fixmax2, df_plus[cols], df_iris[cols])
df_plus

原始数据:

library(dplyr)
df_plus = iris %>% mutate_at((1:4), ~. + 2) ## let's not save over iris
df_iris = iris
names(df_iris)<-sub(".", "_", names(df_iris), fixed = TRUE)

您可以通过创建一个在每列中重复的最大值矩阵并使用 pmin 来获取 iris2 中的最大值和其他数据帧中的值之间的最小值来实现此目的。我创建了一个新的 fixmax 函数,它只将两个数据帧作为参数。

正在准备数据

library(tidyverse)

initial <- iris %>%  mutate_at(1:4, ~.+2)
iris2 <- iris 
names(iris2)<-sub(".", "_", names(iris2), fixed = TRUE)

print(max(initial$Sepal.Length))
# [1] 9.9
print(max(iris2$Sepal_Length))
# [1] 7.9

正在创建函数


fixmax <- function(df, dfmax){
  
  colids <- which(unlist(lapply(dfmax, is.numeric)))
  dfmax <-  apply(dfmax[, colids], 2, max) %>% 
            matrix(nrow=nrow(dfmax), ncol=length(colids), byrow=TRUE) %>% 
            as.data.frame()
  
  df[, colids] <- pmin(df[,colids], dfmax)
  
  return(df)
}

测试功能

newiris <- fixmax(initial, iris2)

print(max(newiris$Sepal.Length))
# [1] 7.9

assertthat::assert_that(!identical(newiris, iris2))
# [1] TRUE
assertthat::assert_that(all((initial == newiris) || (iris2 == newiris)))
# [1] TRUE
imax = apply(iris2[, 1:4], 2, max) %>% 
       matrix(nrow=nrow(iris2), ncol=4, byrow=TRUE) %>% 
       as.data.frame()
assertthat::assert_that(all(newiris[, 1:4] <= imax))
# [1] TRUE

print(head(newiris))
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          7.1         4.4          3.4         2.2  setosa
# 2          6.9         4.4          3.4         2.2  setosa
# 3          6.7         4.4          3.3         2.2  setosa
# 4          6.6         4.4          3.5         2.2  setosa
# 5          7.0         4.4          3.4         2.2  setosa
# 6          7.4         4.4          3.7         2.4  setosa