【问题标题】:Avoid For-Loops in R避免 R 中的 For 循环
【发布时间】:2016-02-23 03:59:03
【问题描述】:

我确定这个问题之前已经提出过,但想就我的具体问题提供一些意见。作为对您的帮助的回报,我将使用一个有趣的例子。

Sean Lahman 提供了 MLB 棒球统计数据的庞大数据集,可在他的网站 (http://www.seanlahman.com/baseball-archive/statistics/) 上免费获取。

我想用这些数据来回答以下问题:美国职业棒球大联盟每十年记录的每场比赛的平均本垒打数是多少?

下面我已经粘贴了所有相关的脚本:

teamdata = read.csv("Teams.csv", header = TRUE)

decades = c(1870,1880,1890,1900,1910,1920,1930,1940,1950,1960,1970,1980,1990,2000,2010,2020)

i = 0
meanhomers = c()
for(i in c(1:length(decades))){
    meanhomers[i] = mean(teamdata$HR[teamdata$yearID>=decades[i] & teamdata$yearID<decades[i+1]]);
    i = i+1
}

我的主要问题是,如何在不使用可怕的 for 循环的情况下确定这个答案?

附带问题:什么简单的脚本会为我生成 decades 向量?

(对于那些对棒球问题的答案感兴趣的人,请参阅下文。)

meanhomers
 [1]   4.641026  23.735849  34.456522  20.421053  25.755682  61.837500  84.012500
 [8]  80.987500 130.375000 132.166667 120.093496 126.700000 148.737410 173.826667
[15] 152.973333   NaN

为清楚起见进行编辑:原来我回答了错误的问题;上面提供的答案表示每支球队每的本垒打数,而不是每场比赛。稍微修正一下分母就会得到正确的结果。

【问题讨论】:

  • i = 0i = i+1 在这段代码中都没有执行任何有用的操作。
  • 这些数据集也可以在 R 包中获得 Lahman

标签: r for-loop


【解决方案1】:

这是一个data.table 示例。因为其他人展示了如何使用cut,所以我采取了另一种方法将数据分成十年:

teamdata[,list(HRperYear=mean(HR)),by=10*floor((yearID)/10)]

但是,最初的问题提到的是每场比赛的平均 HR,而不是每年(尽管代码和答案清楚地涉及每年的 HR)。

以下是如何计算每场比赛的平均 HR(以及每支球队每年的平均比赛场次):

teamdata[,list(HRperYear=mean(HR),HRperGame=sum(HR)/sum(G),games=mean(G)),by=10*floor(yearID/10)]

    floor  HRperYear  HRperGame     games
 1:  1870   4.641026 0.08911866  52.07692
 2:  1880  23.735849 0.21543555 110.17610
 3:  1890  34.456522 0.25140108 137.05797
 4:  1900  20.421053 0.13686067 149.21053
 5:  1910  25.755682 0.17010657 151.40909
 6:  1920  61.837500 0.40144445 154.03750
 7:  1930  84.012500 0.54593453 153.88750
 8:  1940  80.987500 0.52351325 154.70000
 9:  1950 130.375000 0.84289640 154.67500
10:  1960 132.166667 0.81977946 161.22222
11:  1970 120.093496 0.74580935 161.02439
12:  1980 126.700000 0.80990313 156.43846
13:  1990 148.737410 0.95741873 155.35252
14:  2000 173.826667 1.07340167 161.94000
15:  2010 152.973333 0.94427984 162.00000

(1980 年代和 1990 年代的平均比赛总数较低是由于 1981 年和 1994-5 赛季的球员罢工)。

【讨论】:

    【解决方案2】:

    PS:问题写得很好,但如果您提供一个完全可重现的示例,这样我就不必去下载 CSV 来回答您的问题了。制作虚拟数据是可以的。

    您可以使用seq 生成序列。

    decades <- seq(1870, 2020, by=10)
    

    您可以使用cut 将数值变量拆分为区间。

    teamdata$decade <- cut(teamdata$yearID, breaks=decades, dig.lab=4)
    

    基本上它为每十年创建一个具有一个级别的因子(由breaks 指定)。 dig.lab=4 就是这样打印年份,例如“1870”不是“1.87e+03”。 请参阅?cut 了解更多配置(例如,“1980”是否包含在这十年或下一个十年中,等等。如果您认为自己会使用它们,您甚至可以配置标签。)

    然后每十年做一些事情,使用plyr 包(data.tabledplyr 是其他选项,但我认为plyr 具有最简单的学习曲线,而且您的数据似乎不是很大需要data.table)。

    library(plyr)
    ddply(teamdata, .(decade), summarize, meanhomers=mean(HR))
            decade meanhomers
    1  (1870,1880]   4.930233
    2  (1880,1890]  25.409091
    3  (1890,1900]  35.115702
    4  (1900,1910]  20.068750
    5  (1910,1920]  27.284091
    6  (1920,1930]  67.681250
    7  (1930,1940]  84.050000
    8  (1940,1950]  84.125000
    9  (1950,1960] 130.718750
    10 (1960,1970] 133.349515
    11 (1970,1980] 117.745968
    12 (1980,1990] 127.584615
    13 (1990,2000] 155.053191
    14 (2000,2010] 170.226667
    15 (2010,2020] 152.775000
    

    我的和你的有点不同,因为我的间隔是(, ],而你的间隔是[, )。可以调整cut来切换这些。

    【讨论】:

    • 不错的答案 (+1),但是,虽然 plyr 可能更容易学习,但我建议它变得越来越过时,所以你会更好直接去dplyrdata.table
    • 我不认为它已经过时了。对我来说 plyr v dplyr 只是你是否喜欢管道语法。 data.table 用于“严肃”的东西;)
    【解决方案3】:

    您还可以使用 sqldf 包来对数据使用 SQL 查询。

    代码如下:

    library(sqldf)
    sqldf("select floor(yearID/10)*10 as decade,avg(hr) as count
    from Teams
    group by decade;")
    
     decade      count
    1    1870   4.641026
    2    1880  23.735849
    3    1890  34.456522
    4    1900  20.421053
    5    1910  25.755682
    6    1920  61.837500
    7    1930  84.012500
    8    1940  80.987500
    9    1950 130.375000
    10   1960 132.166667
    11   1970 120.093496
    12   1980 126.700000
    13   1990 148.737410
    14   2000 173.826667
    15   2010 152.973333
    

    【讨论】:

      【解决方案4】:

      aggregate 对这类事情很方便。您可以使用 decades 对象和 findInterval 将年份放入垃圾箱:

      aggregate(HR ~ findInterval(yearID, decades), data=teamdata, FUN=mean)
      ##    findInterval(yearID, decades)         HR
      ## 1                              1   4.641026
      ## 2                              2  23.735849
      ## 3                              3  34.456522
      ## 4                              4  20.421053
      ## 5                              5  25.755682
      ## 6                              6  61.837500
      ## 7                              7  84.012500
      ## 8                              8  80.987500
      ## 9                              9 130.375000
      ## 10                            10 132.166667
      ## 11                            11 120.093496
      ## 12                            12 126.700000
      ## 13                            13 148.737410
      ## 14                            14 173.826667
      ## 15                            15 152.973333
      

      请注意,使用的间隔是左闭的,如您所愿。另请注意,间隔不必是规则的。你的是,这导致了如何生成decades 向量的“附带问题”:甚至不计算它。相反,直接计算每年属于哪个十年:

      aggregate(HR ~ I(10 * (yearID %/% 10)), data=teamdata, FUN=mean)
      ##    I(10 * (yearID%/%10))         HR
      ## 1                   1870   4.641026
      ## 2                   1880  23.735849
      ## 3                   1890  34.456522
      ## 4                   1900  20.421053
      ## 5                   1910  25.755682
      ## 6                   1920  61.837500
      ## 7                   1930  84.012500
      ## 8                   1940  80.987500
      ## 9                   1950 130.375000
      ## 10                  1960 132.166667
      ## 11                  1970 120.093496
      ## 12                  1980 126.700000
      ## 13                  1990 148.737410
      ## 14                  2000 173.826667
      ## 15                  2010 152.973333
      

      我通常更喜欢公式接口而不是上面使用的aggregate,但是您可以通过使用非公式接口直接获得更好的名称。以下是上述各项的示例:

      with(teamdata, aggregate(list(mean.HR=HR), list(Decade=findInterval(yearID,decades)), FUN=mean))
      ##    Decade    mean.HR
      ## 1       1   4.641026
      ## ...
      
      with(teamdata, aggregate(list(mean.HR=HR), list(Decade=10 * (yearID %/% 10)),  FUN=mean))
      ##    Decade    mean.HR
      ## 1    1870   4.641026
      ## ...
      

      【讨论】:

        【解决方案5】:

        dplyr::group_by,与cut 混合在这里是一个不错的选择,并且可以避免循环。 decades 向量只是一个阶梯序列。

        decades <- seq(1870,2020,by=10)
        

        cut 将数据分成几类,为了清楚起见,我用几十年来标记了这些类别。

        teamdata$decade <- cut(teamdata$yearID, breaks=decades, right=FALSE, labels=decades[1:(length(decades)-1)])
        

        然后dplyr 尽可能整齐地处理分组汇总

        library(dplyr)
        teamdata %>% group_by(decade) %>% summarise(meanhomers=mean(HR))
        #    decade meanhomers
        #    (fctr)      (dbl)
        # 1    1870   4.641026
        # 2    1880  23.735849
        # 3    1890  34.456522
        # 4    1900  20.421053
        # 5    1910  25.755682
        # 6    1920  61.837500
        # 7    1930  84.012500
        # 8    1940  80.987500
        # 9    1950 130.375000
        # 10   1960 132.166667
        # 11   1970 120.093496
        # 12   1980 126.700000
        # 13   1990 148.737410
        # 14   2000 173.826667
        # 15   2010 152.973333
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-09
          • 2011-06-21
          • 1970-01-01
          • 2021-08-19
          • 2018-01-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多