#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% быстрее.