在R中使用for循环创建字符向量。

5

我正在尝试使用for循环创建一个日期向量(格式化为字符字符串,而不是日期)。我已经查阅了一些其他的SO问题,例如(如何使用循环创建字符字符串向量?),但它们并没有帮助我。我已经创建了以下for循环:

start_dates <- c("1993-12-01")
j <- 1
start_dates <- for(i in 1994:as.numeric(format(Sys.Date(), "%Y"))){
                   date <- sprintf("%s-01-01", i)
                   j <- j + 1
                   start_dates[j] <- date  
               }

然而,它返回一个NULL(空) 向量 start_dates。当我手动增加i 索引时它就能工作了,例如:

> years <- 1994:as.numeric(format(Sys.Date(), "%Y"))
> start_dates <- c("1993-12-01")
> j <- 1
> i <- years[1]
> date <- sprintf("%s-01-01", i)
> j <- j + 1
> start_dates[j] <- date
> start_dates
[1] "1993-12-01" "1994-01-01"
> i <- years[2]
> date <- sprintf("%s-01-01", i)
> j <- j + 1
> start_dates[j] <- date
> start_dates
[1] "1993-12-01" "1994-01-01" "1995-01-01"

这一定与我的for()语句的构造有关,但我想不出来。我相信它非常简单。提前感谢。

2个回答

10

有什么问题:

sprintf("%s-01-01", 1994:2015)

> sprintf("%s-01-01", 1994:2015)
 [1] "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01"
 [6] "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01" "2003-01-01"
[11] "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01"
[16] "2009-01-01" "2010-01-01" "2011-01-01" "2012-01-01" "2013-01-01"
[21] "2014-01-01" "2015-01-01"

sprintf()函数已经完全向量化,利用它的优势。

循环中的问题

主要问题是当for()循环结束时,将for()函数的值赋给start_dates,因此覆盖了循环所做的所有工作。 实际上正在发生以下情况:

j <- 1
foo <- for (i in 1:10) {
  j <- j + 1
}
foo

> foo
NULL

阅读 'for',我们可以看到这种行为是有意为之的:

Value:

     ....

     ‘for’, ‘while’ and ‘repeat’ return ‘NULL’ invisibly.
解决方案:不要赋值for()的返回值。因此模板可能如下所示:
for(i in foo) {
  # ... do stuff
  start_dates[j] <- bar
}

修复这一点后,你仍然会遇到问题;在将第一个日期分配给输出时,j 将会是 2,因为你从 j <- 1 开始并在循环中进行递增(在赋值之前)

如果您让 i 从序列1、2、…、n取值而不是实际年份,那么这将更容易。您可以使用 i 来索引年份向量并且作为 start_dates 元素的索引。

不是说你应该像这样做循环,但是,如果你想要...

years <- seq.int(1994, 2015)
start_dates <- numeric(length = length(years))
for (i in seq_along(years)) {
  start_dates[i] <- sprintf("%s-01-01", years[i])
}

这将会给出:

> start_dates
 [1] "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01"
 [6] "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01" "2003-01-01"
[11] "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01"
[16] "2009-01-01" "2010-01-01" "2011-01-01" "2012-01-01" "2013-01-01"
[21] "2014-01-01" "2015-01-01"
有时候,循环实际向量的值(就像你所做的)而不是它的索引(就像我刚才所做的)会更有帮助,但只有在特定情况下才是如此。对于像你这里的一般操作,这只是你需要解决的额外复杂性。话虽如此,在尝试循环之前,请考虑在R中进行矢量化操作。

好建议。比必要的更难了。@LyzandeR 直接解决了我的编程错误,但你的解决方案对于我的特定问题来说更好。谢谢。 - Ursus Frost
我同意Ursus的看法。这是一个不错的解决方案。 - LyzandeR
我也注意到了真正的错误;请看我的编辑,但最重要的是不要for()调用的结果分配给start_dates,这只会在循环运行时抹掉你所做的一切。 - Gavin Simpson

3

不应将循环赋值给变量。请改写为:

start_dates <- c("1993-12-01")
j <- 1
for(i in 1994:as.numeric(format(Sys.Date(), "%Y"))){ #use the for-loop on its own. Don't assign it to a variable
  date <- sprintf("%s-01-01", i )
  j <- j + 1
  start_dates[j] <- date  
}

并且您很好:

> start_dates
 [1] "1993-12-01" "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01" "1999-01-01" "2000-01-01" "2001-01-01"
[10] "2002-01-01" "2003-01-01" "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01" "2009-01-01" "2010-01-01"
[19] "2011-01-01" "2012-01-01" "2013-01-01" "2014-01-01" "2015-01-01"

没关系,这种事情每个人都会遇到的 :) - LyzandeR
请注意,由于在赋值完成之前增加了j,因此第一次迭代将使用j = 2,因此j仍会偏移1。在循环外设置j <- 0或交换循环代码的最后两行将纠正这个问题。 - Gavin Simpson
2
@GavinSimpson 我认为这是由于原帖作者有意而为之,因为他想让值c("1993-12-01")成为他的列表的第一个元素,除非我弄错了。 - LyzandeR
我明白了,那么就有另一个问题:不要在R循环中增加对象 :-) (是的,我错过了最初的赋值,抱歉。) - Gavin Simpson
@GavinSimpson 没关系。我也不确定。无论如何,你的回答非常优秀和有用。 - LyzandeR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接