Conditionally creating new variables for data manipulation
You can make a couple easy changes to improve readability. First, use with or transform so you don't have to carry a lot of iris$. Second, reorganize your logical tests so each nested ifelse appear in the "if-false" part of the previous ifelse, not in the "if-true" part. Hopefully, this will make sense:

iris$Speciec2 &lt;- with(iris,
  ifelse(!Species %in% c("setosa", "virginica"), as.character(Species),
  ifelse(Sepal.Length &lt; 5,                       "Species1",
  ifelse(Sepal.Length &lt; 7,                       "Species2",
                                                 "Species3")))
)


I think it reads a lot easier this way, where each line contains a test and the output value if the test is passed. If the test fails, we move onto the next line. And at the end, the last line contains the value if all tests have failed. I also like this layout because it looks a lot like how you would nest Perl's ternary operator:

result = condition1 ? value_if_condition1_is_true
       : condition2 ? value_if_condition2_is_true
       : condition3 ? value_if_condition3_is_true
       :              value_if_condition3_is_false


Another solution would be to use the cut or findInterval functions to handle the conditions you have on Sepal.Length. These functions become really handy when you have many cases ("breaks") in mind, although here you only have three. You could do:

new_labels &lt;- cut(iris$Sepal.Length, breaks = c(-Inf, 5, 7, +Inf), 
                  labels = paste0("Species", 1:3), right = FALSE)
iris$Speciec2 &lt;- with(iris, ifelse(Species %in% c("setosa", "virginica"),
                                   as.character(new_labels),
                                   as.character(Species)))




Now about performance, since you mentioned it. Know that ifelse and cut are vectorized functions so unless you are handling many millions of rows or doing some high frequency trading, you should be fine :-)

Minor speed improvements might be made by replacing the ifelse with in-place replacements (x[idx] &lt;- value), I let you decide if this is more readable:

iris$Speciec2 &lt;- with(iris, {
  res &lt;- as.character(Species)
  need_change &lt;- Species %in% c("setosa", "virginica")
  res[need_change &amp; Sepal.Length &lt; 5]                     &lt;- "Species1"
  res[need_change &amp; Sepal.Length &gt;= 5 &amp; Sepal.Length &lt; 7] &lt;- "Species2"
  res[need_change &amp; Sepal.Length &gt;= 7]                    &lt;- "Species3"
  res
})


Finally, switching to a data.table (from the R package of the same name) is always recommended when dealing with large size data.frames and when speed is a concern.