Какой самый элегантный способ рассчитать средние сезонные значения с помощью R?

У меня есть временные ряды с равномерно распределенными средними данными наблюдений за день.

Как проще всего вычислить сезонные средние значения? Сезоны должны соответствовать метеорологической номенклатуре с DJF (= зима: декабрь, январь, февраль), MAM, JJA и SON.

Это означает, что декабрьские значения относятся к году x-1.

Расчет месячных средних значений красиво представлен здесь: Как рассчитать среднемесячное среднее значение?

Этой идее можно следовать при вычислении среднесезонных значений. Однако несколько предостережений делают его не очень прозрачным, и нужно быть осторожным!

Я также рассмотрел небольшую часть этой проблемы уже в предыдущем потоке: Как переключать строки в R?

Вот теперь полная история:

0: создайте случайный временной ряд.

ts.pdsi <- data.frame(date = seq(
                from=as.Date("1901-01-01"), 
                to=as.Date("2009-12-31"), 
                by="day"))
ts.pdsi$scPDSI <- rnorm(dim(ts.foo)[1],  mean=1, sd=1)    # add some data

1-й: используйте пакет sea и добавьте сезоны в свой временной ряд, который должен быть отформатирован как data.frame.

library(seas)
# add moth/seasons
ts.pdsi$month  <- mkseas(ts.pdsi,"mon")   # add months
ts.pdsi$seas <- mkseas(ts.pdsi,"DJF")     # add seasons
ts.pdsi$seasyear <- paste(format(ts.pdsi[,1],"%Y"), 
                          ts.pdsi$seas ,sep="")   # add seasyears, e.g. 1950DJF

это дает

> head(ts.pdsi)
    date      scPDSI month seas seasyear
1 1901-01-01 -0.10881074   Jan  DJF  1901DJF
2 1901-02-01 -0.22287750   Feb  DJF  1901DJF
3 1901-03-01 -0.12233192   Mär  MAM  1901MAM
4 1901-04-01 -0.04440915   Apr  MAM  1901MAM
5 1901-05-01 -0.36334082   Mai  MAM  1901MAM
6 1901-06-01 -0.52079030   Jun  JJA  1901JJA

2-й. Затем вы можете рассчитать средние за сезон, используя вышеупомянутый подход, используя столбец $ seasyear.

> MEAN <- tapply(pdsi$scPDSI, ts.pdsi$seasyear, mean, na.rm = T)
> head(MEAN)
1901DJF     1901JJA     1901MAM     1901SON     1902DJF     1902JJA 
-0.45451556 -0.72922229 -0.17669396 -1.12095590 -0.86523850 -0.04031273 

ПРИМЕЧАНИЕ: весна (MAM) и лето (JJA) переключаются из-за строгой сортировки по алфавиту.

3-й: верните его

foo <- MEAN
for(i in 1:length(MEAN)) {
    if (mod (i,4) == 2) {
        foo[i+1] <- foo[i]    #switch 2nd 3rd row (JJA <-> MAM)
        foo[i] <- MEAN[i+1]
    }
}
# and generate new names for the array
d <- data.frame(date=seq(from=as.Date("1901-01-01"), to=as.Date("2009-12-31"), by="+3 month"))
d$seas <- mkseas(d,"DJF") 
d$seasyear <- paste(format(d[,1],"%Y"), d$seas ,sep="")
names(foo)<-d$seasyear  # add right order colnames
MEAN <-foo

Наконец, это приводит к временному ряду сезонных средних значений. Что ж, я решил, что это слишком сложно, и я думаю, что есть гораздо более простые решения.

Кроме того, это решение также имеет действительно серьезную проблему с зимним сезоном DJF: декабрь пока не выбирается из предыдущего года. Это довольно легко исправить (я думаю), но усложняет данный путь.

Я очень надеюсь, что есть идеи получше!


person stephan    schedule 24.09.2013    source источник
comment
Этот фрагмент кода может помочь: dd <- c(Sys.Date(), as.Date(c("2013-11-30", "2013-12-01"))); season_year <- as.numeric(format(dd + 31, "%Y")).   -  person Josh O'Brien    schedule 24.09.2013
comment
хороший момент, фрагмент может быть полезен   -  person stephan    schedule 25.09.2013
comment
Чтобы решить проблему зимнего сезона (в DJF D должно быть D года n-1), одна идея состоит в том, чтобы создать столбец фиктивного года со значением текущего года для каждого месяца, кроме декабря, где вы используете n + 1.   -  person user2165907    schedule 07.03.2014


Ответы (4)


Я этого по-твоему?

# # create some data: daily values for three years
df <- data.frame(date = seq(from = as.Date("2007-01-01"),
                            to = as.Date("2009-12-31"),
                            by = "day"))
df$vals <- rnorm(nrow(df))

# add year
df$year <- format(df$date, "%Y")

# add season
df$seas <- mkseas(x = df, width = "DJF")

# calculate mean per season within each year
df2 <- aggregate(vals ~ seas + year, data = df, mean)

df2
#    seas year         vals
# 1   DJF 2007 -0.048407610
# 2   MAM 2007  0.086996842
# 3   JJA 2007  0.013864555
# 4   SON 2007 -0.081323367
# 5   DJF 2008  0.170887946
# 6   MAM 2008  0.147830260
# 7   JJA 2008  0.003008866
# 8   SON 2008 -0.057974215
# 9   DJF 2009 -0.043437437
# 10  MAM 2009 -0.048345979
# 11  JJA 2009  0.023860506
# 12  SON 2009 -0.060076870

Поскольку mkseas преобразует даты в сезонный фактор с уровнями в желаемом порядке, порядок верен также после агрегирования по году и сезону.

person Henrik    schedule 24.09.2013
comment
Хенрик, этот выглядит действительно красиво / элегантно! Правда, mkseas держит в порядке и для DJF. - person stephan; 25.09.2013
comment
Наконец, я добавил переменную даты для построения временного ряда с помощью ››› df2 $ date ‹- seq (from = min (df $ date), to = max (df $ date), by = + 3 месяца) - person stephan; 25.09.2013
comment
Это не работает с ежемесячными данными (где DJF охватывает два года), ежемесячное решение добавлено в качестве ответа. - person mlcyo; 05.02.2021

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

pdsi <- data.frame(date = seq(
            from=as.Date("1901-01-01"), 
            to=as.Date("2009-12-31"), 
            by="day"))
pdsi$scPDSI <- rnorm(nrow(pdsi),  mean=1, sd=1)
pdsi$mon<-mon(pdsi$date)+1
pdsi$seas<-floor((pdsi$mon %% 12)/3)+1
pdsi$year<-year(pdsi$date)+1900
pdsi$syear<-pdsi$year
pdsi$syear[pdsi$mon==12]<-pdsi$syear[pdsi$mon==12]+1

Чтобы вычислить средние сезонные значения, вы можете просто сделать это:

meanArray<-tapply(pdsi$scPDSI,list(year=pdsi$syear,seas=pdsi$seas),mean)

И теперь у вас есть

>head(meanArray)
      seas
year           1         2         3         4
  1901 1.0779676 1.0258306 1.1515175 0.9682434
  1902 0.9900312 0.8964994 1.1028336 1.0074296
  1903 0.9912233 0.9858088 1.1346901 1.0569518
  1904 0.7933653 1.1566892 1.1223454 0.8914211
  1905 1.1441863 1.1824074 0.9044940 0.8971485
  1906 0.9900826 0.9933909 0.9185972 0.8922987

Если вы хотите, чтобы это был плоский массив с соответствующими именами, вы сначала выполняете транспонирование, а затем сглаживаете массив и добавляете имена

colnames(meanArray)<-c("DJF","MAM","JJA","SON")
meanArray<-t(meanArray)
MEAN<-array(meanArray)
names(MEAN)<-paste(colnames(meanArray)[col(meanArray)],rownames(meanArray)[row(meanArray)],sep="")

Это даст вам желаемый результат

> head(MEAN)
  1901DJF   1901MAM   1901JJA   1901SON   1902DJF   1902MAM 
1.0779676 1.0258306 1.1515175 0.9682434 0.9900312 0.8964994  
person mrip    schedule 24.09.2013


У меня была та же проблема, но с ежемесячными данными, и aggregate не допускал разделения DJF на протяжении многих лет. Чтобы обойти это, вы можете добавить столбец синтетического года, в котором декабрьские значения назначаются следующему году.

library(dplyr)
library(seas)
library(lubridate)

df <- data.frame(yearmonth = c("187601", "187602", "187603", "187604", "187605", "187606", "187607","187608", "187609", "187610", "187611", "187612", "187701", "187702", "187703", "187704", "187705", "187706", "187707", "187708", "187709", "187710", "187711", "187712", "187801", "187802", "187803", "187804", "187805", "187806", "187807", "187808", "187809", "187810", "187811", "187812", "187901", "187902", "187903", "187904", "187905", "187906", "187907", "187908", "187909", "187910", "187911", "187912"), 
                 SOI = rnorm(n = 48, mean = 0, sd = 4))


df %>% 
  mutate(yearmonth = lubridate::ymd(yearmonth, truncated = 1),
         year = year(yearmonth),
         month = month(yearmonth),
         seas = mkseas(yearmonth, width = "DJF"),
         year2 = ifelse(test = month == 12,
                        yes = year + 1,
                        no = year)) %>% 
  group_by(year2, seas) %>% 
  summarise(meanSOI = mean(SOI))
person mlcyo    schedule 05.02.2021