【问题标题】:R loop through columns in list of data framesR循环遍历数据框列表中的列
【发布时间】:2021-02-08 17:52:19
【问题描述】:

假设以下数据框(实际上我的数据框有数千行):

year<-c(2010,2010,2010,2011,2011,2011,2012,2012,2013,2013)
a1<-rnorm(10)
a2<-rnorm(10)
b1<-rnorm(10)
b2<-rnorm(10)
c1<-rnorm(10)
c2<-rnorm(10)

我使用以下代码创建了一个由多个数据框组成的列表,它将原始数据框按年份拆分为子集。

#split datasets into years
df.list<-split(df, df$year)

#Name of datasets df plus year
dfnames <- str_c("df", names(df.list))
names(df.list)<-dfnames

我想对列表的所有数据框应用以下循环:

#df_target is a new data frame that stores the results and j is the indicator for it:
df_target <- NULL
j <- 1

for(i in seq(2, 7, 2)) {
  df_target[[j]] <- (df[i]*df[i+1])/(sum(df[i+1]))
  j <- j+1
  }
}

代码适用于一个数据框,但是,我想将数据框拆分为按年份分组的多个数据框,然后遍历列。

因此,我使用以下函数将上述循环应用于列表中的所有数据帧:

df_target <- NULL
j <- 1

fnc <- function(x){
  for(i in seq(2, 7, 2)) {
  df_target[[j]] <- (x[i]*x[i+1])/(sum(x[i+1]))
  j <- j+1
  }
}

sapply(df.list, fnc)

使用此代码,我没有收到任何错误消息,但是列表中的两个数据帧都是 NULL。 我到底做错了什么?

df_target 应该是包含列 a_new= (a1a2)/sum(a2)、b_new= (b1b2)/sum(b2) 和 c_new= (c1*c2)/ 的数据框sum(c2) 但每年单独计算。

【问题讨论】:

  • df 只有 7 列,但您的 i 索引尝试选择不存在的第 8 列和第 10m 列,因此我无法运行您的代码。另外,你能解释一下你想要达到的目标吗?你想让df_target 成为...?
  • @RicardoSemiãoeCastro 抱歉,我已经编辑了我的问题。它现在应该可以工作了。
  • 将数据拆分成多个小的data.frames很少是一个好策略,最好对数据进行reshape,然后使用dplyr::group_bydplyr::mutate
  • @RichardTelford 然后我可以使用循环吗?我不想按名称或索引选择列,因为有时我有 90 列的数据集。因此,我正在使用循环,并希望将循环应用于数据帧列表。

标签: r list dataframe loops


【解决方案1】:

这是一个tidyverse 解决方案。试着一点一点地运行它,这样你就可以看到它的作用了。

首先,它将 rowid 添加为列,以确保以后可以识别唯一的行。然后它使用pivot_longer 重塑数据以将数据转换为长格式,然后使用pivot_wider 部分反转它。 然后对数据进行分组并运行计算。这是在内部运行一个循环。

library(tidyverse)
set.seed(123)
tibble(
  year = c(2010, 2010, 2010, 2011, 2011, 2011, 2012, 2012, 2013, 2013),
  a1 = rnorm(10),
  a2 = rnorm(10),
  b1 = rnorm(10),
  b2 = rnorm(10),
  c1 = rnorm(10),
  c2 = rnorm(10)
) %>% 
  rowid_to_column() %>% 
  pivot_longer(cols = -c(year, rowid), names_to = c("nameA", "name12"), names_pattern = "(\\w)(\\d)" ) %>% 
  pivot_wider(names_from = name12, values_from = value) %>% 
  group_by(nameA) %>% 
  mutate(j = `1` * `2` / (sum(`2`)))
#> # A tibble: 30 x 6
#> # Groups:   nameA [3]
#>    rowid  year nameA     `1`     `2`        j
#>    <int> <dbl> <chr>   <dbl>   <dbl>    <dbl>
#>  1     1  2010 a     -0.560   1.22   -0.329  
#>  2     1  2010 b     -1.07    0.426  -0.141  
#>  3     1  2010 c     -0.695   0.253  -0.0794 
#>  4     2  2010 a     -0.230   0.360  -0.0397 
#>  5     2  2010 b     -0.218  -0.295   0.0200 
#>  6     2  2010 c     -0.208  -0.0285  0.00268
#>  7     3  2010 a      1.56    0.401   0.299  
#>  8     3  2010 b     -1.03    0.895  -0.285  
#>  9     3  2010 c     -1.27   -0.0429  0.0245 
#> 10     4  2011 a      0.0705  0.111   0.00374
#> # … with 20 more rows

reprex package (v0.3.0) 于 2020 年 10 月 26 日创建

【讨论】:

    【解决方案2】:

    您需要在函数内部定义jdf_target,并设置它应该返回什么(就像现在一样,它会计算df_target,但不会返回它):

    fnc <- function(x){
      df_target <- NULL
      j <- 1
      for(i in seq(2, 7, 2)) {
      df_target[[j]] <- (x[i]*x[i+1])/(sum(x[i+1]))
      j <- j+1
      }
      return(df_target)
    }
    

    但请记住,这将输出一个列表矩阵,对于 sapply 将选择的 df.list 的每个元素,您将创建一个 df_target 的 3 元素列表,因此输出将如下所示像这样在控制台中:

    > sapply(df.list, fnc)
         df2010 df2011 df2012 df2013
    [1,] List,1 List,1 List,1 List,1
    [2,] List,1 List,1 List,1 List,1
    [3,] List,1 List,1 List,1 List,1
    

    但是会是这样的:

    要获得cleaner 输出,我们可以设置df_target 来创建一个包含每年值的数据框:

    fnc <- function(x){
      df_target <- as.data.frame(matrix(nrow=nrow(x), ncol=3))
      for(i in seq(2, 7, 2)) {
        df_target[,i/2] <- (x[i]*x[i+1])/(sum(x[i+1]))
      }
    return(df_target)}
    

    这会每年返回一个 df,但如果我们使用sapply,我们将得到类似的列表矩阵输出,因此最好将函数定义为每年都循环低谷:

    fnc <- function(y){
      df_target.list <- list()
      k=1
      for(j in y){
        df_target <- as.data.frame(matrix(nrow=nrow(j), ncol=3))
        for(i in seq(2, 7, 2)) {
          df_target[,i/2] <- (j[i]*j[i+1])/(sum(j[i+1]))
        }
        df_target.list[[names(y)[k]]] = df_target
        k=k+1
      }
      return(df_target.list)}
    

    输出:

    > fnc(df.list)
    $df2010
               V1         V2          V3
    1 -0.10971160 0.01688244 -0.16339367
    2  0.05440564 0.57554210 -0.06803244
    3  0.03185178 0.90598561 -0.68692401
    
    $df2011
               V1           V2         V3
    1 -0.43090055  0.007152131  0.3930606
    2  0.15050644  0.329092942 -0.1367295
    3  0.07336839 -0.423631930 -0.1504056
    
    $df2012
             V1         V2         V3
    1 0.5540294  0.4561862 0.09169914
    2 0.1153931 -1.1311450 0.81853691
    
    $df2013
              V1        V2        V3
    1  0.4322934 0.5286973 0.2136495
    2 -0.2412705 0.1316942 0.1455196
    

    【讨论】:

    • 有没有办法合并属于一年的列表?
    • 谢谢你的作品!有没有办法将列名更改为初始名称?
    • 在定义df_target时,可以在as.data.frame中添加参数col.names=letters[1:3]。如果你想更灵活一点,你可以col.names=colnames(j)[seq(2, 7, 2)]
    猜你喜欢
    • 1970-01-01
    • 2018-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多