R:使用数据表重新排序因子级别(用于Plotly)

sal*_*der 4 r plotly data.table

TL;博士

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

  • 这有效,但不是通过引用(链接):table$x <- factor(table$x, levels = c("giraffes", "orangutans", "monkeys"))
  • 这通过引用工作,但对表的其他列(链接)有意想不到的影响:setattr(mydt$value,"levels",c(...))

接受的答案使用:=符号提供了一种没有副作用的方法.

该问题的背景是需要重新排序Plotly中的条形图,该条形图规定了重新排序因子水平(链接).


原始问题

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

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

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"
Run Code Online (Sandbox Code Playgroud)

因此,如果我想按国家/地区列反向排序,我可以这样做(2):

col <- "country"
column.levels <- levels(data[[col]])
column.levels <- sort(column.levels, T)
data$country <- factor(data$country, levels = column.levels)
Run Code Online (Sandbox Code Playgroud)

但在这里,国家是硬编码的,排序不是参考.我期望在一个函数中使用此代码,该函数应该使用不同的数据集,以及其他列名.那么如何让它与存储在变量中的列名一起使用,并且可能通过引用呢?

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

非常感谢帮助!


澄清回复:factor()vs.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>)
Run Code Online (Sandbox Code Playgroud)

这是当我使用factor重新排序country列时会发生什么:

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
Run Code Online (Sandbox Code Playgroud)

该表是不受干扰的,但是根据需要对级别进行重新排序.

但是这是使用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
Run Code Online (Sandbox Code Playgroud)

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

Uwe*_*Uwe 7

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

col <- "country"
DT[, (col) := factor(get(col), levels = rev(levels(get(col))))]
str(DT)
Run Code Online (Sandbox Code Playgroud)
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 ...
Run Code Online (Sandbox Code Playgroud)
DT
Run Code Online (Sandbox Code Playgroud)
        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
Run Code Online (Sandbox Code Playgroud)

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

作为factor()默认字母顺序排列的水平,rev()用于扭转现有因子水平的顺序.

列名称以变量形式给出col.因此,get()用于访问列.或者,这可以写成

DT[, (col) := lapply(.SD, factor, levels = rev(levels(DT[[col]]))), .SDcols = col]
Run Code Online (Sandbox Code Playgroud)

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

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


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

setattr() 似乎只是改变了级别的标签,而不是OP想要的级别编号.

DT
Run Code Online (Sandbox Code Playgroud)
        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
Run Code Online (Sandbox Code Playgroud)
DT[, as.integer(country)]
Run Code Online (Sandbox Code Playgroud)
[1] 1 2 3 4 5 6
Run Code Online (Sandbox Code Playgroud)
setattr(DT[[col]], "levels", rev(levels(DT[[col]])))
DT
Run Code Online (Sandbox Code Playgroud)
        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
Run Code Online (Sandbox Code Playgroud)
DT[, as.integer(country)]
Run Code Online (Sandbox Code Playgroud)
[1] 1 2 3 4 5 6
Run Code Online (Sandbox Code Playgroud)

如果使用上面的代码,则相应地更改级别的编号:

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

(根据DT需要进行修改,请始终使用新的副本DT)