2

我有一个包含多个变量(CPI - Workers、、等等)的长表CPI - 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 - 平均值)取每个日期前两个变量的平均值。请忽略这个平均值在经济上没有意义的事实,我只是想为这个例子做一个简单的计算。

这样的计算在宽格式中非常简单。所以让我们先将数据转换为宽,然后进行计算。

DT_wide <- DT_long %>% pivot_wider(names_from = variable_name, values_from = value)

DT_wide_with_average <- DT_wide %>% 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 到我的长表的末尾......而不必转换为宽格式?

谢谢你的帮助!

4

3 回答 3

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 都存在,并且每个组中只有一次,并且您希望它们均匀加权。它可能会变得更加复杂,并且在许多情况下,您是绝对正确的,许多涉及变量之间关系的计算在宽格式中更加直接。

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

于 2021-02-16T21:43:44.827 回答
0

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

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

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

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

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

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

第四,我们需要一种方法来区分所选变量。在最初的例子中这不是必需的,但在一般情况下(以及我们的新计算)它是必要的。这可以通过用语来完成keyby = .(variable_name)data.table即按字母顺序排列变量。因此,现在我们可以将其CPI Consumers称为 value[1] 和CPI Workersvalue[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 每年报告一次,这将是 ((CPI t- CPI t-1 ) / CPI t-1 ) * 100,但当然是每月报告一次,因此t-1变为t-12。但是,有时我们想要每月的通货膨胀率,所以我们会使用t-1

国内生产总值 (GDP) 每季度报告一次,因此对于年度增长,我们要计算过去 4 个季度的增长,((GDP t - GDP t-4 ) / GDP t-4 ) * 100。

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

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

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

x 是我们要计算增长的数据列,l 是滞后数,即从月度数据到年同比增长的 12 到 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”,值列称为“value”,但是,您的长表可能有这些列的其他名称。使用相应的参数指定这些名称,该函数将找到并使用这些列。

因此,使用我们的测试长表,称为“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 <- gr.rate(temp[, .(value)], lag_periods)行交换为适当的计算。这可能需要一些实验。更好的是,如果我们可以参数化那条线,那就更好了。

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

于 2021-02-17T18:54:46.727 回答
0

这是我尝试使用辅助函数和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}
)
于 2021-02-17T21:31:15.377 回答