【问题标题】:How do I calculate new variables in R long tables?如何计算 R 长表中的新变量?
【发布时间】:2021-02-16 21:08:48
【问题描述】:

我有一个包含多个变量的长表(CPI - WorkersCPI - Consumers(Seas) Unemployment Level (thous) 等等,但为了简洁起见,我将数据集截断为 3 个变量和 6 个时间段。我想创建一个新变量, 是前两者的组合。我们称它为CPI - Average 当然只是前两者的平均值或(CPI - Workers + CPI - Consumers) / 2。这是一个在宽表中的简单计算,但是,为了满足 ggplot,我以长格式存储了我的数据。

注意,我将所有变量都存储在一张长表中。当我需要可视化趋势时,我会在 ggplot 命令中过滤到所需的一个或多个变量。

我的问题是如何在不先将数据转换为宽格式的情况下创建新变量?

首先,这是我的数据集:

DT_long <- as.data.table(read.table(header=TRUE, text='year period periodName value variable_name date 
1994  M01    January 143.8 "CPI - Workers" 1994-01-01
1994  M02   February 144.0 "CPI - Workers" 1994-02-01
1994  M03      March 144.3 "CPI - Workers" 1994-03-01
1994  M04      April 144.5 "CPI - Workers" 1994-04-01
1994  M05        May 144.8 "CPI - Workers" 1994-05-01
1994  M06       June 145.3 "CPI - Workers" 1994-06-01
1994  M01    January 146.3 "CPI - Consumers" 1994-01-01
1994  M02   February 146.7 "CPI - Consumers" 1994-02-01
1994  M03      March 147.1 "CPI - Consumers" 1994-03-01
1994  M04      April 147.2 "CPI - Consumers" 1994-04-01
1994  M05        May 147.5 "CPI - Consumers" 1994-05-01
1994  M06       June 147.9 "CPI - Consumers" 1994-06-01
1994  M01    January  8630 "(Seas) Unemployment Level (thous)" 1994-01-01
1994  M02   February  8583 "(Seas) Unemployment Level (thous)" 1994-02-01
1994  M03      March  8470 "(Seas) Unemployment Level (thous)" 1994-03-01
1994  M04      April  8331 "(Seas) Unemployment Level (thous)" 1994-04-01
1994  M05        May  7915 "(Seas) Unemployment Level (thous)" 1994-05-01
1994  M06       June  7927 "(Seas) Unemployment Level (thous)" 1994-06-01
'))

其次,计算的输出应该是这样的:

DT_long <- as.data.table(read.table(header=TRUE, text='year period periodName value variable_name date 
1994  M01    January 143.8 "CPI - Workers" 1994-01-01
1994  M02   February 144.0 "CPI - Workers" 1994-02-01
1994  M03      March 144.3 "CPI - Workers" 1994-03-01
1994  M04      April 144.5 "CPI - Workers" 1994-04-01
1994  M05        May 144.8 "CPI - Workers" 1994-05-01
1994  M06       June 145.3 "CPI - Workers" 1994-06-01
1994  M01    January 146.3 "CPI - Consumers" 1994-01-01
1994  M02   February 146.7 "CPI - Consumers" 1994-02-01
1994  M03      March 147.1 "CPI - Consumers" 1994-03-01
1994  M04      April 147.2 "CPI - Consumers" 1994-04-01
1994  M05        May 147.5 "CPI - Consumers" 1994-05-01
1994  M06       June 147.9 "CPI - Consumers" 1994-06-01
1994  M01    January  8630 "(Seas) Unemployment Level (thous)" 1994-01-01
1994  M02   February  8583 "(Seas) Unemployment Level (thous)" 1994-02-01
1994  M03      March  8470 "(Seas) Unemployment Level (thous)" 1994-03-01
1994  M04      April  8331 "(Seas) Unemployment Level (thous)" 1994-04-01
1994  M05        May  7915 "(Seas) Unemployment Level (thous)" 1994-05-01
1994  M06       June  7927 "(Seas) Unemployment Level (thous)" 1994-06-01
1994  M01    January 145.05 "CPI - Average" 1994-01-01
1994  M02   February 145.35 "CPI - Average" 1994-02-01
1994  M03      March 145.70 "CPI - Average" 1994-03-01
1994  M04      April 148.85 "CPI - Average" 1994-04-01
1994  M05        May 146.15 "CPI - Average" 1994-05-01
1994  M06       June 146.60 "CPI - Average" 1994-06-01
'))

第四个变量(CPI - 平均值)取每个日期前两个变量的平均值。请忽略这个平均值在经济上没有意义的事实,我只是想为这个例子做一个简单的计算。

此类计算在宽格式中非常简单。所以我们先把数据转换成wide,然后进行计算。

DT_wide &lt;- DT_long %&gt;% pivot_wider(names_from = variable_name, values_from = value)

DT_wide_with_average &lt;- DT_wide %&gt;% mutate(`CPI - Average` = (`CPI - Workers` + `CPI - Consumers`) / 2)

这将获取宽表并添加一个包含计算结果的新列:

DT_wide_with_average <- as.data.table(read.table(header=TRUE, text='year period periodName date `CPI - Workers` `CPI - Consumers` `(Seas) Unemployment Level (thous)` `CPI - Average`
1994 M01  January  1994-01-01  144.    146.       8630        145.
1994 M02  February 1994-02-01  144     147.       8583        145.
1994 M03  March    1994-03-01  144.    147.       8470        146.
1994 M04  April    1994-04-01  144.    147.       8331        146.
1994 M05  May      1994-05-01  145.    148.       7915        146.
1994 M06  June     1994-06-01  145.    148.       7927        147.
'))

请忽略小数已被 pivot_wider 截断的事实。

在宽模式下工作、创建变量、分析变量、修改计算、重新排序列顺序、删除不需要的列是我们人类在分析简单数据表时的思维方式。

不幸的是,ggplot 需要长格式,被 R 之神认为是“整洁”的,但在我们这些凡人眼中却相当混乱。很抱歉,如果我把沙发、桌子、椅子、灯和地毯堆放在房间的一个角落里,那会很乱,而如果我像平时一样把它们留在房间里,它们会很乱整齐的。在现实世界中,我可能会将家具堆放在一个角落里,以便粉刷房间或打磨地板。这对手头的任务很有用,但它会被认为是杂乱无章的,对普通生活没有用处。因此,将长桌视为整洁而将宽桌视为凌乱是违反直觉的。当我第一次被介绍到 tidyverse 时,我花了很长时间才弄清楚这个违反直觉的逻辑。很抱歉咆哮,但希望这是对 R 之神有用的客户反馈。至少,如果神承认违反直觉的命名法,这将对 R 学习者有所帮助。如果我在进入浴室之前被警告说带“H”的水龙头把手是冷水,带“C”的水龙头把手是热水,我就不太可能烫手了!

数据分析是迭代的。我不想每次迭代都采取以下步骤:

  1. pivot_wider
  2. 计算新变量
  3. pivot_longer
  4. 检查 ggplot 中的趋势

我宁愿:

  1. 计算新变量
  2. 检查 ggplot 中的趋势

简而言之,我想专注于我的经济分析,而不是不必要的 R 编程。

那么,我如何从我的长格式表中选择一个变量子集,在计算中使用它们来创建一个新变量,并确保新变量是 rbind-ed 到我的长表末尾。 .无需转换为宽格式?

感谢您的帮助!

【问题讨论】:

    标签: r calculated-columns longtable


    【解决方案1】:

    这个怎么样?

    bind_rows(
      DT_long,
      DT_long %>%
        filter(variable_name %>% str_detect("CPI")) %>%
        group_by(year, period, periodName, date) %>%
        summarize(value = mean(value)) %>%
        mutate(variable_name = "CPI - Average")
    )
    

    在这种情况下,可以通过整个组的平均值来完成数学运算,但这假设工人/消费者 CPI 都存在,并且每个组中只有一次,并且您希望它们均匀加权。它可能会变得更加复杂,而且在许多情况下,涉及变量之间关系的许多计算在宽格式中更加直接,这绝对是正确的。

    (特别是在这种情况下,这些不同的数据点是真正不同的观察结果还是同一“经济快照”观察结果的不同维度是一个灰色区域,因此可以说您的广泛版本已经“整洁”了。)

    【讨论】:

    • 该解决方案非常适合这种情况,但我正在寻找一种更通用的方法。我正在构建您的方法,可以针对任意计算进行定制。
    • 并非如此。正如我所说,我们的工作流程通常是迭代的。试试这个,然后尝试下一个。每一个都是一个迭代。
    【解决方案2】:

    这是我尝试使用辅助函数和data.table 更通用的尝试。

    helper <- function(name, value, formula) {
      # get the variable and value field name
      vn_name <- substitute(name)
      vn_value <- substitute(value)
      
      # new name is given by formula's LHS
      if(length(formula)==3) {
        new_name <- as.character(formula[[2]])
        formula <- formula[-2]
      } else
        stop("formula should be of the form new_name ~ ...")
      
      # build named list from variable names and values
      .x <- setNames(as.list(value), name)
      attr(formula,".Environment") <- list2env(.x)
    
      # build function from one sided formula
      f <- rlang::as_function(formula)
      
      # return result as a named list using provided variable names and new_name 
      setNames(
        list(new_name, f()),
        c(vn_name,vn_value)
      )
    }
    
    # test
    rbind(
      DT_long,
      DT_long[, by="year,period,periodName,date", 
        helper(variable_name, value, 
          `CPI - Average` ~ (`CPI - Workers` + `CPI - Consumers`) / 2
        )
      ]
    )
    

    或者使用基于melt/dcast 函数的辅助函数的替代公式,我称之为long_mutate。 它可以很容易地在expr 上进行矢量化,以允许连续进行多次计算。

    long_mutate <- function(x, id.vars, variable.name="variable", value.name="value", result.name=NULL, expr) {
      # names can be provided as strings or identifiers
      variable.name <- as.character(substitute(variable.name))
      value.name <- as.character(substitute(value.name))
      result.name <- as.character(substitute(result.name))
    
      # if id.vars not provided, defaults to all variables but variable and value
      if(missing(id.vars)) {
        id.vars <- setdiff(names(x), c(variable.name, value.name))
      }
      
      # expression can be given as 
      #   a one sided formula (result.name must be provided)
      #   a two sided formula (left part becomes result.name)
      #   a function (with no or only ... arguments)
      if(rlang::is_formula(expr)) {
        if(length(expr)==3) {
          result.name <- as.character(expr[[2]])
          expr <- rlang::as_function(expr[-2])
        } else {
          expr <- rlang::as_function(expr)
          if(length(result.name)!=1)
            stop("Need a result.name in case of one sided formula!")
        }
      } else if(is.function(expr)) {
        if(length(result.name)!=1)
          stop("Need a result.name in case of function!")
        args <- formalArgs(expr)
        if(!(is.null(args) || ((length(args)==1)&&(args=="..."))))
          stop("Function must have no or only ... as arguments!")
      }
    
      # wrapper to inject variables and values in the environment
      # and return result name and value using variable.name and value.name
      f <- function(sd) {
        ev <- list2env(setNames(as.list(sd[[2]]), sd[[1]]))
        environment(expr) <- ev
        setNames(list(result.name, expr()), c(variable.name, value.name))
      }
    
      # keep input variable order
      x.vars <- intersect(names(x), c(id.vars, variable.name, value.name))
      rbind(
        x[, ..x.vars],
        x[, by=id.vars, f(.SD), .SDcols=c(variable.name, value.name)]
      )
    }
    
    # example with two sided formula expression
    long_mutate(DT_long,
      variable.name = "variable_name",value.name = "value", 
      expr=`CPI - Average` ~ (`CPI - Workers` + `CPI - Consumers`) / 2
    )
    
    # example with function
    long_mutate(DT_long,
      variable.name = "variable_name",value.name = "value", 
      result.name = "CPI - Average",
      expr=function() {(`CPI - Workers` + `CPI - Consumers`) / 2}
    )
    

    【讨论】:

    • 已编辑以消除对 .x$ 的需要。现在可以直接使用变量了
    • 进一步研究我写了一个更好的辅助函数来封装整个过程
    【解决方案3】:

    Jon Spring 的答案非常适合我最初描述的情况,但真正需要的是一种更通用的方法来允许任意计算。作为普通人,我们将数据概念化为行和列,因此诀窍是将计算的宽表概念转化为长表实现。

    跨列、每行内的宽表计算,应用于长表

    根据他的解决方案,我们可以将其推广到跨列但在一行内的任意计算的情况(想想宽表框架或仅引用同一行中的单元格的电子表格公式)。计算通常是数学计算,但也可能是字符串操作。

    首先我们需要剖析计算。让我们举一个需要特定引用每个变量的任意计算的示例,这与我之前给出的 Jon 响应的示例不同。 ((VarA * 6) / VarB) / (VarB) / (VarA * 6)),当然,除非 VarA 或 VarB 为 0,否则它始终为 1。如果我们的测试数据全为 1,那么我们知道我们的解决方案有效,因为没有 0。

    其次,我们选择变量。在我们的测试数据中,我们将使用CPI - ConsumersCPI - Workers 而不是(Seas) Unemployment Level (thous)。我们通过 Jon 的过滤命令或 data.table 用语中的 DT_long[variable_name %in% c("CPI - Workers", "CPI - Consumers") 来做到这一点。请注意,我使用列表来确保变量的唯一选择。

    第三,我们需要确保计算仅限于行(以宽表格式思考)。那就是将计算限制为日期的 group_by 命令。这将是宽表中的唯一行。

    第四,我们需要一种方法来区分所选变量。在最初的例子中这不是必需的,但在一般情况下(以及我们的新计算)它是必要的。这可以通过data.table 用语中的keyby = .(variable_name) 来完成,它将变量按字母顺序排列。所以现在我们可以将CPI Consumers 称为 value[1] 和 CPI Workers 作为 value[2] 因为在长表中我们的(宽表)数据列变成了行并且通过将我们的计算限制在唯一的日期,我们知道对于每个计算只有两个值,按各自的variable_name 排序。所以我们的计算变成了summarize( value = ((value[1] * 6) / value[2]) * (value[2] / (value[1] * 6)) )

    第五,我们用 Jon 的 mutate 命令给我们新计算的值一个变量名。

    第六,我们使用bind_rows 命令将新数据附加到我们的长表中。

    综上所述,我们有:

    bind_rows(
        DT_long,
        DT_long[variable_name %in% c("CPI - Workers", "CPI - Consumers"), .SD, keyby = .(variable_name)] %>%
            group_by(year, period, periodName, date) %>%
            summarize( value = ((value[1] * 6) / value[2]) * (value[2] / (value[1] * 6)) )  %>%
            mutate(variable_name = "CPI - Average3")
    )
    

    这完全适用于所有 1。

    现在我们已经概括了在宽表中的同一行创建任意计算的步骤,但在长表上实现。

    跨行、每列的宽表计算,应用于长表

    经济学家经常考虑随时间变化。价格每年上涨多少?这些年来通货膨胀率是上升还是下降?我们无法从CPI(消费者价格指数指数)中看到,但可以计算出来。考虑宽表,这个问题不是跨同一行(同一时间段内)内的列(变量)的计算。它是对跨时间的单个变量或跨行的单个列的计算。

    这是一个尝试:

    bind_rows(
        DT_long,
        DT_long[variable_name %in% c("CPI - Workers"),] %>%
            summarize( for(i in 1:6) {value = (((value[i+1] - value[i]) / value[i]) * 100)})  %>%
            mutate(variable_name = "CPI_growth")
    )
    

    唉,这失败了。

    但这里有一个解决方案,用于涉及一个变量的经济学家的常见计算,并随着时间的推移进行计算。这是逐年增长计算或更一般地逐周期增长计算。 CPI 是一个价格指数,以特定的基年为 100 开始。(实际上基数是 1982 年至 1984 年,请参见下面的链接。)如果在接下来的一年中价格上涨 10%,那么该年的指数为 110 . 如果继续以 10% 的速度增长,该指数在第二年达到 121。看看这个数字,我们立即知道自指数为 100 的基准年以来价格上涨了 21%。但是在第二年内价格上涨了多少并不直观。我们需要的是计算每年价格的增长率。如果每年报告 CPI,这将是 ((CPIt - CPIt-1) / CPIt-1) * 100,但是当然是每月报告一次,所以 t-1 变成 t-12。但是,有时我们想要每月的通货膨胀率,所以我们会使用 t-1

    国内生产总值 (GDP) 按季度报告,因此对于年度增长,我们要计算过去 4 个季度的增长,((GDPt - GDPt-4 sub>) / GDPt-4) * 100.

    当我们的数据存储在长表中时,我们如何进行这种计算,轻松调整周期性?

    我们从增长率函数开始。请注意,tis 包中的growth.rate 不是很灵活,并且强制计算始终为每年一次。请注意,此解决方案假定您的数据按日期升序排列。

    gr.rate <- function(x, l=1){
      (x - lag(x, l)) / lag(x, l) * 100
    }
    

    x 是我们要计算增长的数据列,l 是滞后数,即从月度数据到年同比增长的 12 到 12 之间。

    现在我们需要将它应用到我们的示例长数据表 DT_long。我们使用以下函数来做到这一点。

    gr.rate.long <- function(x, var_title, var_name, val_title, new_var_name, lag_periods){
      temp <-x
      names(temp)[grep(val_title, colnames(x))] <- "value"
      names(temp)[grep(var_title, colnames(x))] <- "variable_name"
      temp <- temp[variable_name == var_name]
      temp$value <- gr.rate(temp[, .(value)], lag_periods)
      temp$variable_name <- new_var_name
      names(temp)[grep("value", colnames(x))] <- val_title
      names(temp)[grep("variable_name", colnames(x))] <- var_title
      return(bind_rows(x,temp))
    }
    

    接下来我们用以下参数调用它:

    • x = 我们正在使用的长表的名称
    • var_title = 变量名列的名称
    • var_name = 我们要使用的特定变量的名称
    • val_title = 值列的名称
    • new_var_name = 我们正在创建的新变量的名称
    • lag_periods = 滞后期数,即 12 表示月度数据的同比增长率计算,4 表示季度。

    请注意,在我们的长表示例中,变量名称列称为“variable_name”,值列称为“值”,但是,您的长表可能对这些列有其他名称。使用相应的参数指定这些名称,该函数将找到并使用这些列。

    因此,使用我们的测试长表,称为“DT_long”,我们可以通过以下调用此函数来计算每月通货膨胀率:

    gr.rate.long(DT_long, "variable_name", "CPI - Workers", "value", "CPI-W-growth rate", 1)
    

    跨时间计算单个变量还有其他原因。例如,如果我们知道鸡蛋在一段时间内的价格(以美元和美分表示),并希望将它们转换为像 CPI 这样的指数,我们可以将其称为 EPI。或者也许我们想将 CPI 的基准年从当前的 1982 年到 1984 年* 期间更改为 2020 年。

    要调整这些函数,我们需要将temp$value &lt;- gr.rate(temp[, .(value)], lag_periods) 行交换为适当的计算。这可能需要一些实验。更好的是,如果我们可以参数化那条线,那就更好了。

    我倾向于有点罗嗦,但我喜欢在现实世界场景中将编码置于上下文中。我希望这个讨论对其他人有用。如果您觉得有用,请发表评论。

    【讨论】:

    • 长!= 整洁。在您的情况下,我认为一个广泛的版本,其中与一次相关的不同测量值位于不同的列中,您可以轻松地进行矢量化计算并关联相邻的时间行,这是“整洁”的形式。只有当您希望在同一个图中将不同的指标作为系列(例如 CPI、unemp 水平等可能有不同的单位、不同可变性的强有力指标)时,才对我转换为长版本才有意义。我很欣赏你上面的创造力,但它似乎与 R 的习语背道而驰,我不知道通过 add'l 并发症获得了什么。
    • “这样的计算在宽格式中非常简单。”不直接这样做的理由是什么?
    猜你喜欢
    • 1970-01-01
    • 2022-06-22
    • 2016-10-19
    • 1970-01-01
    • 2019-01-18
    • 2013-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多