title: R: Reorder factor levels with data table (for use with Plotly)
body: <h1>tl;dr</h1>
  
  <p>Is is possible to reorder the factor levels of a column in a data table by reference? As far as I can tell, not in existing answers:</p>
  
  <ul>
  <li>This works, but not by reference (<a href="https://stackoverflow.com/a/40149815/8785542">link</a>): <code>table$x &lt;- factor(table$x, levels = c("giraffes", "orangutans", "monkeys"))</code></li>
  <li>This works by reference, but had unanticipated effects on the other columns of the table (<a href="https://stackoverflow.com/a/14635190/8785542">link</a>): <code>setattr(mydt$value,"levels",c(...))</code></li>
  </ul>
  
  <p>The accepted answer provides a way that works without side-effects, using <code>:=</code> notation.</p>
  
  <p>The background for the question was the need to reorder a bar chart in Plotly, which prescribes reordering factor levels (<a href="https://stackoverflow.com/a/40149815/8785542">link</a>).</p>
  
  <hr>
  
  <h1>Original question</h1>
  
  <p>I need to reorder the factor levels of a column in a data table frame and I would like to do it in a native data table way, if there is one. I know I can do it the data frame way, but this requires hard coded column names. I would like to use dynamic column names, and if possible, updating by reference. I found a way of reordering a column by reference (<a href="https://stackoverflow.com/a/14635190/8785542">link</a>), but it does not reorder the other columns accordingly, nor am I able to use a dynamic column reference.</p>
  
  <p>I have the following data (CO2 emissions per capita):</p>
  
  <pre><code>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"
  </code></pre>
  
  <p>So if I want to reverse sort by the country column, I can do (<a href="https://stackoverflow.com/a/40149815/8785542">2</a>): </p>
  
  <pre><code>col &lt;- "country"
  column.levels &lt;- levels(data[[col]])
  column.levels &lt;- sort(column.levels, T)
  data$country &lt;- factor(data$country, levels = column.levels)
  </code></pre>
  
  <p>But here, country is hard coded, and sorting is not by reference. I anticipate using this code in a function that should work with different data sets, with other column names. So how do I get it to work with a column name stored in a variable, and possibly by reference?</p>
  
  <p>For reference, the data is intended to be presented in a bar chart using Plotly. where I would like to manipulate the order. As discussed here (<a href="https://stackoverflow.com/a/40149815/8785542">2</a>), the way to do that is by reordering factor levels. </p>
  
  <p>Help is very much appreciated!</p>
  
  <hr>
  
  <h3>Clarification re: <code>factor()</code> vs. <code>setattr()</code></h3>
  
  <p>Thanks @Uwe for the feedback. Let me try to be clearer wrt. what I think is the problem. I managed to select columns dynamically, that was just a simple matter of using <code>data[["column"]]</code> instead of <code>data$country</code>, but I must have done something wrong the first time I tried. That leaves the problem of reordering levels by reference.</p>
  
  <p>Here's the dput of data:</p>
  
  <pre><code>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 = &lt;pointer: 0x00000000001f0788&gt;)
  </code></pre>
  
  <p>Here's what happens when I use factor to reorder the country column:</p>
  
  <pre><code>column.levels &lt;- levels(data[["country"]])
  column.levels &lt;- sort(column.levels, T)
  data[["country"]] &lt;- 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
  </code></pre>
  
  <p>The table is unmolested, but the levels are reordered as required.</p>
  
  <p>But here's what happens using setattr():</p>
  
  <pre><code>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
  </code></pre>
  
  <p>So factor() leaves the nominal order of the data unchanged. But setattr() actually changes the nominal order of the country column, causing havoc. So what's going wrong here? I'm perplexed by the difference in behaviour. Is it possible to use setattr() or another method that reorders column factor levels by reference? Hope I'm being clear now!</p>