【问题标题】:R: Reorder factor levels with data table (for use with Plotly)R:使用数据表重新排序因子水平(用于 Plotly)
【发布时间】:2017-10-16 17:35:58
【问题描述】:

tl;博士

是否可以通过引用重新排序数据表中列的因子水平?据我所知,不在现有答案中:

  • 这可行,但不能通过引用 (link):table$x <- factor(table$x, levels = c("giraffes", "orangutans", "monkeys"))
  • 这通过引用起作用,但对表的其他列产生了意想不到的影响 (link):setattr(mydt$value,"levels",c(...))

接受的答案使用:= 表示法提供了一种没有副作用的工作方式。

问题的背景是需要在 Plotly 中重新排序条形图,它规定了重新排序因子水平 (link)。


原始问题

我需要重新排序数据表框架中列的因子水平,如果有的话,我想以原生数据表的方式进行。我知道我可以用数据框的方式来做,但这需要硬编码的列名。我想使用动态列名,如果可能的话,通过引用进行更新。我找到了一种通过引用重新排序列的方法 (link),但它不会相应地重新排序其他列,我也无法使用动态列引用。

我有以下数据(人均二氧化碳排放量):

library(data.table)
print(data)
        indicator        country year     value
1: EN.ATM.CO2E.PC         Canada 2011 15.639760
2: EN.ATM.CO2E.PC          China 2011  7.241515
3: EN.ATM.CO2E.PC European Union 2011  7.079374
4: EN.ATM.CO2E.PC          India 2011  1.476686
5: EN.ATM.CO2E.PC   Saudi Arabia 2011 17.702307
6: EN.ATM.CO2E.PC  United States 2011 16.972417

class(data)
[1] "data.table" "data.frame"

print(str(lapply(data, class)))
List of 8
 $ indicator     : chr "factor"
 $ country       : chr "factor"
 $ year          : chr "factor"
 $ value         : chr "numeric"

所以如果我想按国家列反向排序,我可以这样做(2):

col <- "country"
column.levels <- levels(data[[col]])
column.levels <- sort(column.levels, T)
data$country <- factor(data$country, levels = column.levels)

但在这里,国家/地区是硬编码的,并且不是通过引用进行排序的。我预计在应该使用不同数据集和其他列名的函数中使用此代码。那么如何让它与存储在变量中的列名一起使用,并且可能通过引用来工作?

作为参考,数据旨在使用 Plotly 以条形图的形式呈现。我想在哪里操纵订单。正如这里所讨论的 (2),这样做的方法是重新排序因子水平。

非常感谢您的帮助!


澄清回复:factor()setattr()

感谢@Uwe 的反馈。让我试着更清楚一点。我认为是问题所在。我设法动态选择列,这只是使用data[["column"]] 而不是data$country 的简单问题,但我第一次尝试时一定做错了什么。这留下了通过引用重新排序级别的问题。

这是数据的输入:

dput(data)
structure(list(indicator = structure(c(1L, 1L, 1L, 1L, 1L, 1L
), .Label = "EN.ATM.CO2E.PC", class = "factor"), country = structure(1:6, .Label = c("Canada", 
"China", "European Union", "India", "Saudi Arabia", "United States"
), class = "factor"), year = structure(c(1L, 1L, 1L, 1L, 1L, 
1L), class = "factor", .Label = "2011"), value = c(15.6397596234201, 
7.24151541889549, 7.07937396032502, 1.47668634979755, 17.7023072439215, 
16.9724170879273)), .Names = c("indicator", "country", "year", 
"value"), sorted = c("indicator", "country", "year"), class = c("data.table", 
"data.frame"), row.names = c(NA, -6L), .internal.selfref = <pointer: 0x00000000001f0788>)

当我使用因子对国家列重新排序时会发生以下情况:

column.levels <- levels(data[["country"]])
column.levels <- sort(column.levels, T)
data[["country"]] <- factor(data[["country"]], levels = column.levels)

levels(data[["country"]]) #just as desired
[1] "United States"  "Saudi Arabia"   "India"          "European Union" "China"          "Canada" 

print(data) #nominal order unchanged
        indicator        country year     value
1: EN.ATM.CO2E.PC         Canada 2011 15.639760
2: EN.ATM.CO2E.PC          China 2011  7.241515
3: EN.ATM.CO2E.PC European Union 2011  7.079374
4: EN.ATM.CO2E.PC          India 2011  1.476686
5: EN.ATM.CO2E.PC   Saudi Arabia 2011 17.702307
6: EN.ATM.CO2E.PC  United States 2011 16.972417

桌子没有受到干扰,但级别会根据需要重新排序。

但使用 setattr() 会发生以下情况:

setattr(data[["country"]], "levels", column.levels)
#Also tried this, same result: data[,setattr(country, "levels", column.levels)]

levels(data[["country"]]) #well this looks good...
[1] "United States"  "Saudi Arabia"   "India"          "European Union" "China"          "Canada" 

print(data) #but this is absurd...
        indicator        country year     value
1: EN.ATM.CO2E.PC  United States 2011 15.639760
2: EN.ATM.CO2E.PC   Saudi Arabia 2011  7.241515
3: EN.ATM.CO2E.PC          India 2011  7.079374
4: EN.ATM.CO2E.PC European Union 2011  1.476686
5: EN.ATM.CO2E.PC          China 2011 17.702307
6: EN.ATM.CO2E.PC         Canada 2011 16.972417

因此 factor() 保持数据的名义顺序不变。但是 setattr() 实际上改变了国家列的名义顺序,造成了破坏。那么这里出了什么问题?我对行为上的差异感到困惑。是否可以使用 setattr() 或其他通过引用重新排序列因子级别的方法?希望我现在清楚了!

【问题讨论】:

  • 但是是否可以在不使用其他包的情况下重新排序数据表列的因子水平,即替代data$country &lt;- factor(data$country, levels = column.levels) 的数据表?
  • 这里的解决方案之一 (link) 是我最接近的解决方案 (d[, setattr(x, "levels", lev)]),但它只更新它工作的列,而不是重新排序其他列中的数据factor() 的方式。
  • @Uwe,您知道是否可以在不使用其他包的情况下重新排序数据表列的因子水平,即替代data$country &lt;- factor(data$country, levels = column.levels) 的数据表?如果没有,那么我将用我发现的内容更新我的问题,包括您建议的解决方案。
  • “以factor() 的方式重新排序其他列中的数据”是什么意思? factor() 总是只对一个向量起作用。
  • 拜托,你能edit你的问题并添加dput(data)的结果吗?这将有助于准确地重现您的数据(这似乎有一些细节,例如 year 列是一个因素而不是一个整数)。谢谢。

标签: r data.table plotly


【解决方案1】:

要修改 data.table 对象的列引用,即不复制整个对象,:= 运算符可以使用如下:

col <- "country"
DT[, (col) := factor(get(col), levels = rev(levels(get(col))))]
str(DT)
Classes ‘data.table’ and 'data.frame':    6 obs. of  4 variables:
 $ indicator: Factor w/ 1 level "EN.ATM.CO2E.PC": 1 1 1 1 1 1
 $ country  : Factor w/ 6 levels "United States",..: 6 5 4 3 2 1
 $ year     : Factor w/ 1 level "2011": 1 1 1 1 1 1
 $ value    : num  15.64 7.24 7.08 1.48 17.7 ...
DT
        indicator        country year     value
1: EN.ATM.CO2E.PC         Canada 2011 15.639760
2: EN.ATM.CO2E.PC          China 2011  7.241515
3: EN.ATM.CO2E.PC European Union 2011  7.079374
4: EN.ATM.CO2E.PC          India 2011  1.476686
5: EN.ATM.CO2E.PC   Saudi Arabia 2011 17.702307
6: EN.ATM.CO2E.PC  United States 2011 16.972417

请注意,DT 用作 data.table 对象的名称,以避免与 data() 函数发生名称冲突。

由于factor() 默认按字母顺序排列水平,rev() 用于反转现有因子水平的顺序。

列名在变量col 中给出。因此,get() 用于访问列。或者,这可以写成

DT[, (col) := lapply(.SD, factor, levels = rev(levels(DT[[col]]))), .SDcols = col]

使用特殊符号.SD.SDcols 参数。

要验证DT 是否通过引用更新,可以使用address(DT)


为什么setattr() 没有按预期工作?

setattr() 似乎只是改变了关卡的标签,而不是 OP 想要的关卡编号。

DT
        indicator        country year     value
1: EN.ATM.CO2E.PC         Canada 2011 15.639760
2: EN.ATM.CO2E.PC          China 2011  7.241515
3: EN.ATM.CO2E.PC European Union 2011  7.079374
4: EN.ATM.CO2E.PC          India 2011  1.476686
5: EN.ATM.CO2E.PC   Saudi Arabia 2011 17.702307
6: EN.ATM.CO2E.PC  United States 2011 16.972417
DT[, as.integer(country)]
[1] 1 2 3 4 5 6
setattr(DT[[col]], "levels", rev(levels(DT[[col]])))
DT
        indicator        country year     value
1: EN.ATM.CO2E.PC  United States 2011 15.639760
2: EN.ATM.CO2E.PC   Saudi Arabia 2011  7.241515
3: EN.ATM.CO2E.PC          India 2011  7.079374
4: EN.ATM.CO2E.PC European Union 2011  1.476686
5: EN.ATM.CO2E.PC          China 2011 17.702307
6: EN.ATM.CO2E.PC         Canada 2011 16.972417
DT[, as.integer(country)]
[1] 1 2 3 4 5 6

如果使用上面的代码,级别的编号会相应改变:

DT[, (col) := factor(get(col), levels = rev(levels(get(col))))]
DT[, as.integer(country)]
[1] 6 5 4 3 2 1

(由于DT 已就地修改,请始终使用DT 的新副本)

【讨论】:

  • 谢谢你,太完美了。我在更新问题后尝试使用:= 表示法,但我没有使用get(),所以我也无法让它工作。一千次谢谢你。
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-14
  • 1970-01-01
相关资源
最近更新 更多