【问题标题】:How to create new columns based on values and names of existing columns in R?如何根据 R 中现有列的值和名称创建新列?
【发布时间】:2020-12-14 17:11:10
【问题描述】:

我有一个包含许多列的数据集,例如 2014 年 1 月降水的 1_2014_precip。这是两个数据集的合并。第一个是农艺变量,例如在给定年份收集的谷物产量。第二个是天气数据,它是在进行任何实验的整个时间段内下载的。所以我收集了一年的粮食产量,例如 2013 年,我目前有关于 2011-2019 年在该地点经历的天气的列。我想要一个数据集,其中只有与收集产量的年份相对应的数据。

该数据集涵盖所有 12 个月、7 个天气变量和 10 年。我想制作诸如2_mean_temp 之类的列,对应于二月份的平均温度,并使用列Year 告诉R 在哪里寻找正确的数据(所以如果Year 中的条目是2019,脚本将拉来自现有列2_2019_mean_temp 的新列2_mean_temp 的值。我包括两年内两个月内两个天气变量的数据示例,以了解我需要如何操作它。我已经想出了如何在 Python 中执行此操作,但出于工作流程的目的,我需要能够在 R 中执行此操作。我在 R 中的主要问题是我不知道如何告诉 R 根据给定列的值 - 如果没有自动化,我什至无法生成第一列。

    dput(head(df))
       structure(list(Experiment = c("IREE- N Rate", "IREE- N Rate", 
       "IREE- N Rate", "IREE- N Rate", "IREE- N Rate", "IREE- N Rate"), 
        Site = c("Waseca", "Waseca", "Waseca", "Waseca", "Waseca", "Waseca"), 
        Year = c(2013L, 2013L, 2013L, 2013L, 2014L, 2014L),
        `1_2013_mean_temp` = c(-8.58677419354839, -8.58677419354839, -8.58677419354839, -8.58677419354839, -8.58677419354839, -8.58677419354839), 
        `1_2013_precip` = c(14.17, 14.17, 14.17, 14.17, 14.17, 14.17), 
        `1_2014_mean_temp` = c(-14.0787096774194, -14.0787096774194, -14.0787096774194, -14.0787096774194, -14.0787096774194, -14.0787096774194), 
        `1_2014_precip` = c(21.97, 21.97, 21.97, 21.97, 21.97, 21.97), 
        `2_2013_mean_temp` = c(-7.22428571428571, -7.22428571428571, -7.22428571428571, -7.22428571428571, -7.22428571428571, -7.22428571428571), 
        `2_2013_precip` = c(27.94, 27.94, 27.94, 27.94, 27.94, 27.94), 
        `2_2014_mean_temp` = c(-13.5003571428571, -13.5003571428571, -13.5003571428571, -13.5003571428571, -13.5003571428571, -13.5003571428571), 
        `2_2014_precip` = c(28.95, 28.95, 28.95, 28.95, 28.95, 28.95)), row.names = c(195L, 223L, 245L, 271L, 196L, 224L), class = "data.frame")

这就是我希望这个数据样本在被处理后的样子。请注意,列名中不再有年份,并且数据已从与相应年份匹配的相应列中移动(前四种情况下为 2013_month_variable,后两种情况下为 2014_month_variable)。

df2 <- data.frame(Experiment = c("IREE- N Rate", "IREE- N Rate", "IREE- N Rate", "IREE- N Rate", "IREE- N Rate", "IREE- N Rate"),
                  Site = c("Waseca", "Waseca", "Waseca", "Waseca", "Waseca", "Waseca"),
                  Year = c(2013, 2013, 2013, 2013, 2014, 2014),
                  1_mean_temp = c(-8.585774, -8.585774, -8.585774, -8.585774, -14.07871, -14.07871),
                  1_precip = c(14.17, 14.17, 14.17, 14.17, 21.97, 21.97),
                  2_mean_temp = c(-7.224286,-7.224286, -7.224286, -7.224286, -13.50036, -13.50036),
                  2_precip = c(27.94, 27.94, 27.94, 27.94, 28.95, 28.95))

这是我在 Python 中的做法。

    months = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
    variables = ['mean_temp', 'mean_max_temp', 'mean_min_temp', 'min_min_temp', 'mean_rh', 'precip', 'VPD']
    years = [2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
    
   for m in months:
           for var in variables:
               for year in years:
                    try:
                        df.loc[(df['Year'] == year), '_' + m + '_' + var] = df[m + '_' + year + '_' + var]
                    except:
                        print(year + '_' + m + '_' + var)

我可以在 R 中使用 for 循环重新创建列名,然后我就卡住了。我已经尝试在没有自动化的情况下执行此操作,但我似乎无法找到一种方法让 R 根据不同列的值来使 R 查询哪个列的值。

years <- list("2013", "2014")
months <- list("1", "2")
vars <- list("mean_temp", "precip")
for (year in years) (
  for (month in months) (
    for (var in vars) {
      x = paste(year,"_", month,"_",var, sep="") 
    }
  )
)

按年份重塑不会解决问题,因为它会创建诸如“2013_2011_1_mean_temp”之类的无意义的列,并且不会自动将天气数据连接到收集谷物的适当年份。

【问题讨论】:

  • reshape(unique(df), idvar=c("Experiment", "Site"), timevar="Year", direction="wide") 呢?
  • 您的预期输出不清楚。
  • 为什么即使不同年份的数据在不同的列中,原始数据集中还有Year 列?
  • 这是两个数据集的合并。第一个是农艺变量,例如在给定年份收集的谷物产量。第二个是天气数据,它是在进行任何实验的整个时间段内下载的。因此,如果我在 2013 年收集粮食产量,我目前有关于 2011-2019 年在该地点经历的天气的列。我想要一个数据集,其中只有与收集产量的年份相对应的数据。为了清楚起见,我会将其添加到帖子中。
  • 我不希望重新调整数据 - 我希望它是长格式的,以便每个谷物产量观察都有一个条目。我只想合并天气数据,以便只保留收集谷物的年份。使用 Python,我生成了我所谓的“相对列”,然后在 R 中我删除了原始列。

标签: r


【解决方案1】:

我相信这就是您正在寻找的结果;此解决方案使用 tidyverse 函数。

library(tidyverse)

# create empty tibble to store results
df.out <- tibble()

# loop over years
for (i in unique(df$Year)) {

  this.year <- df %>%
    # grab number of rows for this year
    filter(Year == i) %>%
    # only grab the weather columns for this specific year
    select(Experiment, Site, Year, contains(paste0("_", i, "_"))) %>%
    # this function uses a regex to rename columns with "_" and the current year by removing the part of the name with "_" then 4 numbers
    rename_with(function(x) {str_replace(x, "\\_[0-9][0-9][0-9][0-9]", "")}, contains(paste0("_", i, "_")))
  
  # add this year to your output tibble
  df.out <- df.out %>%
    bind_rows(this.year)
  
}

【讨论】:

  • 这是一个很好的开始!当我在我的数据上运行它时,输出的行数是输入的 9 倍(它乘以我的 9 个天气变量吗?)。我注意到您的输出的行数是我提供的标题的两倍。 dim(df) [1] 5664 1054 &gt; dim(df.out) [1] 50976 111
  • 哎呀!好的答案已编辑;只需要在执行操作之前按特定年份过滤数据框以保留该行数。还删除了我拥有的Year的娱乐,这是不必要的。
  • 这很好用。由于某种原因,输出的行数比输入少两行。这可能是数据集的问题,但 Python 代码没有这个问题。我无法诊断它。
【解决方案2】:

使用data.table 和滥用.SD 是一个简洁的解决方案:

library(data.table)
setDT(df)
idvars <- c("Experiment", "Site", "Year") 
df2 <- df[, .SD[, .SD, .SDcols = names(.SD) %flike% last(.BY)], by = idvars]
setnames(df2, sub("\\d{4}_", "", names(df2)))


#      Experiment   Site Year 1_mean_temp 1_precip 2_mean_temp 2_precip
# 1: IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 2: IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 3: IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 4: IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 5: IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95
# 6: IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95

仅使用基础 R:

dfspl <- split(df, df$Year)
for (i in seq_along(dfspl)) {
  df2 <- dfspl[[i]][!(names(df) %in% idvars | grepl(names(dfspl)[i], names(df)))] <-
    NULL
  names(dfspl[[i]]) <- sub("\\d{4}_", "", names(dfspl[[i]]))
}
do.call(rbind, dfspl)

#            Experiment   Site Year 1_mean_temp 1_precip 2_mean_temp 2_precip
# 2013.195 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 2013.223 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 2013.245 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 2013.271 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
# 2014.196 IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95
# 2014.224 IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95

【讨论】:

  • 如果您想转换回标准 data.frame 结束,请执行 setDF(df2)
  • 运行 .SD 时,我收到错误 Error in `[.data.table`(df, , .SD[, .SD, .SDcols = names(.SD) %flike% : Column 44 of result for group 2 is type 'integer' but expecting type 'double'. Column types must be consistent for each group. 这是一个问题,因为我实际上有几十个列要保留,并且无法将它们全部设为同一个类。尽管出现错误,我还是继续进行,得到的数据集比预期的短 2000 行。
  • 基本 R 解决方案效果很好,与其他有效解决方案一样,与原始数据集相比,它缺少两行。
【解决方案3】:

通过使用rowwisemap 将每行中的列名与Year 的值进行匹配,您可以在没有任何旋转或显式循环的情况下实现您的目标。

这会创建一个列表列,您可以通过unnest 为每年的数据生成正确的值。

library(tidyverse)

cols <- colnames(df)
df <- tibble(df)

df %>%
  mutate(Year = as.character(Year)) %>%
  rowwise() %>%
  mutate(
    tmp_df = list(
      df %>% select(one_of(cols[map_lgl(cols, ~str_detect(., c_across(Year)))])) %>% 
        rename_with(~str_replace(., "_\\d{4}", "")) %>%
        slice(1)
      )
    ) %>%
  select(Experiment, Site, Year, tmp_df) %>%
  unnest_wider(tmp_df)

输出:

# A tibble: 6 x 7
  Experiment   Site   Year  `1_mean_temp` `1_precip` `2_mean_temp` `2_precip`
  <chr>        <chr>  <chr>         <dbl>      <dbl>         <dbl>      <dbl>
1 IREE- N Rate Waseca 2013          -8.59       14.2         -7.22       27.9
2 IREE- N Rate Waseca 2013          -8.59       14.2         -7.22       27.9
3 IREE- N Rate Waseca 2013          -8.59       14.2         -7.22       27.9
4 IREE- N Rate Waseca 2013          -8.59       14.2         -7.22       27.9
5 IREE- N Rate Waseca 2014         -14.1        22.0        -13.5        29.0
6 IREE- N Rate Waseca 2014         -14.1        22.0        -13.5        29.0

【讨论】:

  • 我喜欢这种方法。在更大的数据集上,这段代码需要很长时间才能运行——我不确定如果我给它机会它会完成运行。我创建了一个新数据集,其中仅包含气候列和 Experiment、Site 和 Year,并且只保留了少数气候列,但它仍然没有在合理的时间内运行。
【解决方案4】:

这可以通过从长到宽再返回的往返枢轴并过滤其间的数据来完成:

library(dplyr)
library(tidyr)
library(tibble)

df %>%
  rowid_to_column() %>%
  pivot_longer(-c(Experiment, Site, Year, rowid), names_pattern = "(\\d+)_(\\d{4})_(.*)", names_to = c("obs", "yr", "meas")) %>%
  filter(Year == yr) %>%
  select(-yr) %>%
  pivot_wider(names_from = c(obs, meas), values_from = value, names_prefix = "X")

# A tibble: 6 x 8
  rowid Experiment   Site    Year X1_mean_temp X1_precip X2_mean_temp X2_precip
  <int> <chr>        <chr>  <int>        <dbl>     <dbl>        <dbl>     <dbl>
1     1 IREE- N Rate Waseca  2013        -8.59      14.2        -7.22      27.9
2     2 IREE- N Rate Waseca  2013        -8.59      14.2        -7.22      27.9
3     3 IREE- N Rate Waseca  2013        -8.59      14.2        -7.22      27.9
4     4 IREE- N Rate Waseca  2013        -8.59      14.2        -7.22      27.9
5     5 IREE- N Rate Waseca  2014       -14.1       22.0       -13.5       29.0
6     6 IREE- N Rate Waseca  2014       -14.1       22.0       -13.5       29.0

【讨论】:

  • 这很好用。就像 Kevin A 的回答一样,出于某种原因,输出的行数比输入少两行。这可能是数据集的问题,但 Python 代码没有这个问题。我无法诊断它。
【解决方案5】:

方法 1: 这应该可以正常工作,[编辑:但如果列类型不同则不行]

library(dplyr)
library(tibble)
library(tidyr)

df_left <- df %>% select(Experiment:Year)
df_right <- df %>% select(-Experiment:-Year)

df_right_new <- 
  df_right %>%
  pivot_longer(everything()) %>%
  separate(name, into = c("Month", "Year", "Property"), extra = 'merge') %>%
  unite("Month_Property", c(Month, Property) ) %>%
  pivot_wider(names_from = Month_Property, values_from = value, values_fn = unique) %>%
  mutate(Year = as.integer(Year))

df_new <- left_join(df_left, df_right_new)

df_new的输出

    Experiment   Site Year 1_mean_temp 1_precip 2_mean_temp 2_precip
1 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
2 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
3 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
4 IREE- N Rate Waseca 2013   -8.586774    14.17   -7.224286    27.94
5 IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95
6 IREE- N Rate Waseca 2014  -14.078710    21.97  -13.500357    28.95

方法 2(改进):适用于混合数据类型,比第一个解决方案更快

library(dplyr)
library(tibble)
library(tidyr)
library(purrr)

df_left <- df %>% select(Experiment:Year)
df_right <- df %>% select(-Experiment:-Year) %>% distinct()
year_name <- names(df_right) %>%
  strsplit(fixed = TRUE, split = "_") %>%
  sapply(function(i) c(i[2], paste(i[-2], collapse = "_")))

df_new <- lapply(seq(unique(year_name[2,])), function(i) {
  name_match <- which(year_name[2,] == unique(year_name[2, i]))
  tibble(
    Year = as.integer(year_name[1, name_match]),
    Value = unlist(df_right[name_match])
  ) %>% rename(!!unique(year_name[2,])[[i]] := Value)
}) %>% 
  append(list(as_tibble(df_left)), 0) %>%
  purrr::reduce(left_join, by = "Year")

【讨论】:

  • 这是一种可靠的方法,但它不适用于更大的数据集,因为我需要保留几十个非气候列(例如纬度、粮食产量、地块编号) .我收到以下错误:Error: Can't combine `LAT` &lt;double&gt; and `Key` &lt;character&gt;
  • 好调用@ginger_cat,我添加了处理混合数据类型的第二种方法。我在更大数据集上的第一种方法的错误是由于试图将双精度和字符强制放入同一列。新方法处理得更仔细,速度提高了 10%。
猜你喜欢
  • 2018-10-07
  • 2021-04-19
  • 1970-01-01
  • 2023-01-10
  • 2020-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多