Как создать новые столбцы на основе значений и имен существующих столбцов в R?

#r

#r

Вопрос:

У меня есть набор данных с несколькими столбцами, например, 1_2014_precip для осадков за январь 2014 года. Это слияние двух наборов данных. Первые — это агрономические переменные, например урожайность зерна, которые были собраны в данном году. Второе — данные о погоде, которые были загружены за весь период времени, в течение которого проводился какой-либо эксперимент. Итак, я собрал урожай зерна за один год, например, за 2013 год, и в настоящее время у меня есть столбцы для погоды, которая наблюдалась на этом участке в 2011-2019 годах. Я хочу иметь набор данных, в котором у меня есть только данные, соответствующие году сбора урожая.

Этот набор данных охватывает все 12 месяцев, 7 погодных переменных и 10 лет. Я хочу создать такие столбцы, как 2_mean_temp , соответствующие средней температуре февраля, и использовать столбец Year , чтобы указать R, где искать правильные данные (поэтому, если запись в Year — 2019, скрипт извлечет значение для нового столбца 2_mean_temp из существующего столбца 2_2019_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)
 

Я могу воссоздать имена столбцов, используя цикл for в R, и после этого я застрял. Я пытался сделать это без автоматизации, но, похоже, я не могу найти способ заставить 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» и не автоматизировало бы привязку данных о погоде к соответствующему году, в котором было собрано зерно.

Комментарии:

1. О чем reshape(unique(df), idvar=c("Experiment", "Site"), timevar="Year", direction="wide") ?

2. Ваш ожидаемый результат неясен.

3. Почему Year в исходном наборе данных есть столбец, хотя данные за разные годы находятся в разных столбцах?

4. Это слияние двух наборов данных. Первые — это агрономические переменные, например урожайность зерна, которые были собраны в данном году. Второе — данные о погоде, которые были загружены за весь период времени, в течение которого проводился какой-либо эксперимент. Итак, если я собрал урожай зерна в 2013 году, в настоящее время у меня есть столбцы для погоды, которая наблюдалась на этом участке в 2011-2019 годах. Я хочу иметь набор данных, в котором у меня есть только данные, соответствующие году сбора урожая. Я добавлю это в сообщение для ясности.

5. Я не хочу, чтобы данные изменялись — я хочу, чтобы они были в длинной форме, чтобы для каждого наблюдения за урожайностью зерна была одна запись. Я просто хочу объединить данные о погоде, чтобы остался только год, в котором было собрано зерно. Используя Python, я сгенерировал то, что я называю «относительными столбцами», а затем в 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)
  
}
 

конечный фрейм данных

Комментарии:

1. Это отличное начало! Когда я запускаю его на своих данных, на выходе в 9 раз больше строк, чем на входе (умножилось ли это на мои 9 погодных переменных?). Я заметил, что в вашем выводе в два раза больше строк, чем в заголовке, который я также предоставил. dim(df) [1] 5664 1054 > dim(df.out) [1] 50976 111

2. упс! ок, отредактированный ответ; просто нужно отфильтровать фрейм данных по конкретному году, чтобы сохранить это количество строк, прежде чем выполнять манипуляции. также удалено воссоздание года, которое у меня было, это не нужно.

3. Это отлично работает. По какой-то причине выходные данные содержат на две строки меньше, чем входные. Скорее всего, это проблема с набором данных, но в коде 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
 

Комментарии:

1. Если вы хотите преобразовать обратно в стандартный data.frame конец, выполнив setDF(df2)

2. При запуске . 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 строк короче, чем ожидалось.

3. Базовое решение R работает отлично, и, как и в других работающих решениях, в нем отсутствуют две строки по сравнению с исходным набором данных.

Ответ №3:

Вы можете достичь своей цели без каких-либо поворотов или явных циклов, сопоставив имена столбцов со значением Year в каждой строке, используя rowwise и map .

Это создает столбец списка, в котором вы можете 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
 

Комментарии:

1. Мне нравится подход. Для выполнения этого кода с большим набором данных требуется очень много времени — я не уверен, что он завершит выполнение, если я дам ему шанс. Я создал новый набор данных, содержащий только климатические столбцы и эксперимент, сайт и год, и сохранил только несколько климатических столбцов, и он все равно не выполнялся в разумные сроки.

Ответ №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
 

Комментарии:

1. Это отлично работает. Как и в ответе Кевина А, по какой-то причине на выходе на две строки меньше, чем на входе. Вероятно, это проблема с набором данных, но в коде 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")
 

Комментарии:

1. Это надежный подход, но он не работает с большим набором данных, потому что у меня есть еще десятки неклиматических столбцов, которые мне нужно сохранить (например, широта, урожайность зерна, номер участка). Я получаю следующую ошибку: Error: Can't combine `LAT` <double> and `Key` <character>

2. Хороший звонок @ginger_cat, я добавил 2-й метод, который обрабатывает смешанные типы данных. Ошибка моего первого метода в вашем большем наборе данных связана с попыткой принудительно ввести удвоения и символы в один и тот же столбец. Новый метод обрабатывает это более тщательно и на 10% быстрее.